From 0bd2396d205a3a7a156d3d1c4c1ea0fdd99c1841 Mon Sep 17 00:00:00 2001 From: ugin <157204743+ugin-man@users.noreply.github.com> Date: Wed, 9 Sep 2026 02:27:04 +0900 Subject: [PATCH 1/3] test: cover setup failures and terminal solver initialization --- tests/setup-failure.test.ts | 98 +++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/setup-failure.test.ts diff --git a/tests/setup-failure.test.ts b/tests/setup-failure.test.ts new file mode 100644 index 0000000..ae1cf10 --- /dev/null +++ b/tests/setup-failure.test.ts @@ -0,0 +1,98 @@ +import { test } from "bun:test" +import { strict as assert } from "node:assert" +import { BaseSolver } from "../lib/BaseSolver" + +class SetupFailureSolver extends BaseSolver { + setupCalls = 0 + stepCalls = 0 + setupError = new Error("invalid input during setup") + + override _setup() { + this.setupCalls++ + throw this.setupError + } + + override _step() { + this.stepCalls++ + } +} + +for (const method of ["step", "solve"] as const) { + test(`${method} records setup failure and rethrows the original error`, () => { + const solver = new SetupFailureSolver() + assert.throws(() => solver[method](), (error) => error === solver.setupError) + assert.equal(solver.failed, true) + assert.equal(solver.solved, false) + assert.equal(solver.iterations, 0) + assert.equal(solver.stepCalls, 0) + assert.equal(solver._setupDone, false) + assert.equal(solver.error, `SetupFailureSolver error: ${solver.setupError}`) + }) +} + +test("step does not retry setup after initialization has failed", () => { + const solver = new SetupFailureSolver() + assert.throws(() => solver.step()) + assert.doesNotThrow(() => solver.step()) + assert.equal(solver.setupCalls, 1) + assert.equal(solver.stepCalls, 0) +}) + +for (const state of ["solved", "failed"] as const) { + test(`step does not initialize an already ${state} solver`, () => { + const solver = new SetupFailureSolver() + solver[state] = true + assert.doesNotThrow(() => solver.step()) + assert.equal(solver.setupCalls, 0) + assert.equal(solver.iterations, 0) + }) + + test(`setup can mark a solver ${state} without running a step`, () => { + class TerminalSetupSolver extends BaseSolver { + override _setup() { + this[state] = true + } + + override _step() { + assert.fail("a terminal solver must not perform work") + } + } + const solver = new TerminalSetupSolver() + solver.step() + assert.equal(solver[state], true) + assert.equal(solver._setupDone, true) + assert.equal(solver.iterations, 0) + }) +} + +test("successful initialization still runs once across multiple steps", () => { + class SuccessfulSolver extends BaseSolver { + setupCalls = 0 + override _setup() { + this.setupCalls++ + } + override _step() { + this.solved = this.iterations === 2 + } + } + const solver = new SuccessfulSolver() + solver.solve() + assert.equal(solver.setupCalls, 1) + assert.equal(solver.iterations, 2) + assert.equal(solver.solved, true) + assert.equal(solver.failed, false) +}) + +test("step exceptions retain their existing failure behavior", () => { + const failure = new Error("step failure") + class StepFailureSolver extends BaseSolver { + override _step() { + throw failure + } + } + const solver = new StepFailureSolver() + assert.throws(() => solver.step(), (error) => error === failure) + assert.equal(solver.failed, true) + assert.equal(solver.iterations, 1) + assert.equal(solver.error, `StepFailureSolver error: ${failure}`) +}) From 94f1b15681ce91a078bca4b7c1a311d6e2b003e7 Mon Sep 17 00:00:00 2001 From: ugin <157204743+ugin-man@users.noreply.github.com> Date: Wed, 9 Sep 2026 02:27:19 +0900 Subject: [PATCH 2/3] fix: record setup exceptions during solver stepping --- lib/BaseSolver.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/BaseSolver.ts b/lib/BaseSolver.ts index e071bf8..ecfa4af 100644 --- a/lib/BaseSolver.ts +++ b/lib/BaseSolver.ts @@ -39,13 +39,14 @@ export class BaseSolver { /** DO NOT OVERRIDE! Override _step() instead */ step() { - if (!this._setupDone) { - this.setup() - } if (this.solved) return if (this.failed) return - this.iterations++ try { + if (!this._setupDone) { + this.setup() + } + if (this.solved || this.failed) return + this.iterations++ this._step() } catch (e) { this.error = `${this.getSolverName()} error: ${e}` From d3a16d9183de027b280c8754f8c9be62bc677794 Mon Sep 17 00:00:00 2001 From: ugin <157204743+ugin-man@users.noreply.github.com> Date: Wed, 9 Sep 2026 02:29:07 +0900 Subject: [PATCH 3/3] style: format setup failure assertions --- tests/setup-failure.test.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/setup-failure.test.ts b/tests/setup-failure.test.ts index ae1cf10..629e4cb 100644 --- a/tests/setup-failure.test.ts +++ b/tests/setup-failure.test.ts @@ -20,10 +20,14 @@ class SetupFailureSolver extends BaseSolver { for (const method of ["step", "solve"] as const) { test(`${method} records setup failure and rethrows the original error`, () => { const solver = new SetupFailureSolver() - assert.throws(() => solver[method](), (error) => error === solver.setupError) + assert.throws( + () => solver[method](), + (error) => error === solver.setupError, + ) assert.equal(solver.failed, true) assert.equal(solver.solved, false) assert.equal(solver.iterations, 0) + assert.equal(solver.iterations, solver.stepCalls) assert.equal(solver.stepCalls, 0) assert.equal(solver._setupDone, false) assert.equal(solver.error, `SetupFailureSolver error: ${solver.setupError}`) @@ -91,7 +95,10 @@ test("step exceptions retain their existing failure behavior", () => { } } const solver = new StepFailureSolver() - assert.throws(() => solver.step(), (error) => error === failure) + assert.throws( + () => solver.step(), + (error) => error === failure, + ) assert.equal(solver.failed, true) assert.equal(solver.iterations, 1) assert.equal(solver.error, `StepFailureSolver error: ${failure}`)