Tests & Failure Lab

The Course
•
3 min read

The project’s pool.test.js is part of the course, not decoration. It verifies bounded starts and ordered results using explicit gates, first-failure behavior while a peer is held, cancellation before and during work, invalid limits, source errors, cleanup and undefined causes.

Run npm test in the extracted folder on Node.js 22+. Node’s built-in test(name, callback) groups each case; assert.deepEqual compares complete values and assert.rejects checks awaited failures. These are the same contract ideas practised in the console helpers, with a standard reporting tool.

Mutate only one behavior at a time. Replace indexed assignment with push, skip iterator.return, or remove the no-new-work guard. The appropriate test should fail. Restore the source before continuing. A test that fails for a syntax error does not prove it detects the behavioral defect; the mutation should remain a runnable program.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Each editor is independent. Async examples end with main() so this editor waits for their work.

Predict, then run JavaScript
async function main() {
  let release;
  const gate = new Promise(resolve => {
    release = resolve;
  });
  let settled = false;
  const work = gate.then(() => {
    settled = true;
  });

  console.log(settled);
  release();
  await work;
  console.log(settled);
}

main();
Check your prediction
false
true

Match a test to the guarantee it distinguishes

A result-order mutation needs reverse completion; a cleanup mutation needs an unfinished iterator; a start-guard mutation needs more work available after failure. Ordinary completion cannot distinguish all three. Read fixtures as controlled collaborators that expose public behavior, not as snapshots of private implementation details.

Boundary or caseWhat to inspect
Bounded startsObserve peak work and refill before a held peer completes
First failureObserve source closure and no new pulls while peers settle
External signalPreserve caller reason and release its listener

Predict the failed assertion before changing the maintained implementation. If your change causes a syntax error or hangs, that is not evidence of the intended regression test. Restore source, confirm the baseline, and only then try another mutation.

Your turn

Exercise

Tests & Failure Lab: practise

Write a runtime assertion showing a generator closes on early break. Its finally sets closed=true. After breaking after one value, assert closed and print cleanup verified.

Your workspace JavaScript
// Keep the assertion outside the iterator cleanup itself.
Hint 1: the idea

The public observation is that cleanup happened before the consuming loop finished.

Hint 2: a first step

Initialize closed=false outside the generator and set it in finally.

Show one solution
async function main() {
  let closed = false;

  function* values() {
    try {
      yield 1;
      yield 2;
    } finally {
      closed = true;
    }
  }

  for (const value of values()) {
    if (value === 1) {
      break;
    }
  }

  if (!closed) {
    throw new Error("Cleanup missing");
  }

  console.log("cleanup verified");
}

main();
cleanup verified

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

Run all downloaded tests before and after each mutation. Identify the exact test that catches missing cleanup and the one that catches completion-order results. Add a separate caller-cancellation test if you change the signal contract.

Was this article helpful?

Your response is saved on this device.