From 40fa08e72fe83ca7f8b5fdcbbd1ebff8760a6185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sat, 6 Oct 2018 17:53:27 -0400 Subject: [PATCH 001/105] Add try-commit to test assertions --- lib/test.js | 110 ++++++++++++++++- test/test.js | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 433 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index f417034de..8b6371c52 100644 --- a/lib/test.js +++ b/lib/test.js @@ -57,6 +57,60 @@ function timeout(ms) { this.timeout(ms); } +function tryTest(fn, ...args) { + const running = this + .forAttempt(t => fn.apply(null, [t].concat(args))) + .run(); + + let commitCalled = false; + let discarded = false; + + this.addPendingAttemptAssertion(running.then(ret => { + if (!discarded && !ret.passed) { + throw ret.error; + } + })); + + const finishedRunning = running.then(ret => { + if (discarded) { + return null; + } + + const commitFn = toCommit => { + if (commitCalled) { + this.saveFirstError(new Error('Either commit() or discard() was already called')); + return; + } + commitCalled = true; + + if (ret.passed) { + this.countFinishedAttemptAssertion(toCommit); + } else { + this.countFailedAttemptAssertion(ret.error, toCommit); + } + + if (toCommit) { + for (const log of ret.logs) { + this.addLog(log); + } + } + }; + + return { + ...ret, + commit: () => commitFn(true), + discard: () => commitFn(false) + }; + }); + + finishedRunning.discard = () => { + discarded = true; + this.decrementAttemptAssertion(); + }; + + return finishedRunning; +} + const testMap = new WeakMap(); class ExecutionContext { constructor(test) { @@ -69,6 +123,9 @@ class ExecutionContext { const boundPlan = plan.bind(test); boundPlan.skip = () => {}; + const boundTryTest = tryTest.bind(test); + boundTryTest.skip = skip; + Object.defineProperties(this, assertionNames.reduce((props, name) => { props[name] = {value: assertions[name].bind(test)}; props[name].value.skip = skip; @@ -76,7 +133,8 @@ class ExecutionContext { }, { log: {value: log.bind(test)}, plan: {value: boundPlan}, - timeout: {value: timeout.bind(test)} + timeout: {value: timeout.bind(test)}, + try: {value: boundTryTest} })); this.snapshot.skip = () => { @@ -153,6 +211,21 @@ class Test { this.startedAt = 0; this.timeoutTimer = null; this.timeoutMs = 0; + this.pendingAttemptCount = 0; + this.attemptCount = 0; + + this.forAttempt = fn => { + return new Test({ + ...options, + metadata: { + ...options.metadata, + callback: false, + failing: false + }, + fn, + title: this.title + '.A' + (this.attemptCount++) + }); + }; } bindEndCallback() { @@ -231,6 +304,35 @@ class Test { this.saveFirstError(error); } + addPendingAttemptAssertion(promise) { + if (this.finishing) { + this.saveFirstError(new Error('Assertion passed, but test has already finished')); + } + + this.pendingAttemptCount++; + promise.catch(error => this.saveFirstError(error)); + } + + decrementAttemptAssertion() { + this.pendingAttemptCount--; + } + + countFinishedAttemptAssertion(includeCount) { + if (this.finishing) { + this.saveFirstError(new Error('Assertion passed, but test has already finished')); + } + + if (includeCount) { + this.assertCount++; + } + this.decrementAttemptAssertion(); + } + + countFailedAttemptAssertion(error, includeCount) { + this.countFinishedAttemptAssertion(includeCount); + this.saveFirstError(error); + } + saveFirstError(error) { if (!this.assertError) { this.assertError = error; @@ -296,7 +398,11 @@ class Test { verifyAssertions() { if (!this.assertError) { if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null && this.assertCount === 0) { - this.saveFirstError(new Error('Test finished without running any assertions')); + if (this.pendingAttemptCount > 0) { + this.saveFirstError(new Error('Test finished, but not all attempts were committed')); + } else { + this.saveFirstError(new Error('Test finished without running any assertions')); + } } else if (this.pendingAssertionCount > 0) { this.saveFirstError(new Error('Test finished, but an assertion is still pending')); } diff --git a/test/test.js b/test/test.js index 847836b97..4153c2883 100644 --- a/test/test.js +++ b/test/test.js @@ -797,3 +797,328 @@ test('timeout is refreshed on assert', t => { t.is(result.passed, true); }); }); + +test('try-commit are present', t => { + return ava(a => { + a.pass(); + t.type(a.try, Function); + }).run(); +}); + +test('try-commit works', t => { + const instance = ava(a => { + return a + .try(b => b.pass()) + .then(res => { + t.true(res.passed); + res.commit(); + }); + }); + + return instance.run() + .then(result => { + t.true(result.passed); + t.is(instance.assertCount, 1); + }); +}); + +test('try-commit can discard produced result', t => { + return ava(a => { + return a + .try(b => b.pass()) + .then(res => { + res.discard(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /without running any assertions/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit works with values', t => { + const testValue1 = 123; + const testValue2 = 123; + + return ava(a => { + return a + .try((b, val1, val2) => { + b.is(val1, val2); + }, testValue1, testValue2) + .then(res => { + t.true(res.passed); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit is properly counted', t => { + const instance = ava(a => { + return a + .try(b => b.pass()) + .then(res => { + t.true(res.passed); + t.is(instance.pendingAttemptCount, 1); + res.commit(); + t.is(instance.pendingAttemptCount, 0); + }); + }); + + return instance.run().then(result => { + t.true(result.passed); + t.is(instance.attemptCount, 1); + }); +}); + +test('try-commit is properly counted multiple', t => { + const instance = ava(a => { + return Promise.all([ + a.try(b => b.pass()), + a.try(b => b.pass()), + a.try(b => b.pass()) + ]) + .then(([res1, res2, res3]) => { + t.is(instance.pendingAttemptCount, 3); + res1.commit(); + res2.discard(); + res3.commit(); + t.is(instance.pendingAttemptCount, 0); + }); + }); + + return instance.run().then(result => { + t.true(result.passed); + t.is(instance.assertCount, 2); + }); +}); + +test('try-commit goes as many levels', t => { + t.plan(5); + const instance = ava(a => { + t.ok(a.try); + return a + .try(b => { + t.ok(b.try); + return b + .try(c => { + t.ok(c.try); + c.pass(); + }) + .then(res => { + res.commit(); + }); + }) + .then(res => { + res.commit(); + }); + }); + + return instance.run().then(result => { + t.true(result.passed); + t.is(instance.assertCount, 1); + }); +}); + +test('try-commit fails when not committed', t => { + return ava(a => { + return a + .try(b => b.pass()) + .then(res => { + t.true(res.passed); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /not all attempts were committed/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit fails when no assertions inside try', t => { + return ava(a => { + return a + .try(() => {}) + .then(res => { + t.false(res.passed); + t.ok(res.error); + t.match(res.error.message, /Test finished without running any assertions/); + t.is(res.error.name, 'Error'); + res.commit(); + }); + }).run().then(result => { + t.false(result.passed); + }); +}); + +test('try-commit fails when no assertions inside multiple try', t => { + return ava(a => { + return Promise.all([ + a.try(b => b.pass()).then(res1 => { + res1.commit(); + t.true(res1.passed); + }), + a.try(() => {}).then(res2 => { + t.false(res2.passed); + t.ok(res2.error); + t.match(res2.error.message, /Test finished without running any assertions/); + t.is(res2.error.name, 'Error'); + res2.commit(); + }) + ]); + }).run().then(result => { + t.false(result.passed); + }); +}); + +test('test fails when try-commit committed to failed state', t => { + return ava(a => { + return a.try(b => b.fail()).then(res => { + t.false(res.passed); + res.commit(); + }); + }).run().then(result => { + t.false(result.passed); + }); +}); + +test('try-commit has proper titles, when going in depth and width', t => { + t.plan(6); + return new Test({ + fn(a) { + t.is(a.title, 'foo'); + + return Promise.all([ + a.try(b => { + t.is(b.title, 'foo.A0'); + return Promise.all([ + b.try(c => t.is(c.title, 'foo.A0.A0')), + b.try(c => t.is(c.title, 'foo.A0.A1')) + ]); + }), + a.try(b => t.is(b.title, 'foo.A1')), + a.try(b => t.is(b.title, 'foo.A2')) + ]); + }, + failWithoutAssertions: false, + metadata: {type: 'test', callback: false}, + title: 'foo' + }).run(); +}); + +test('try-commit fails when calling commit twice', t => { + return ava(a => { + return a.try(b => b.pass()).then(res => { + res.commit(); + res.commit(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /was already called/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit allows planning inside the try', t => { + return ava(a => { + return a.try(b => { + b.plan(3); + + b.pass(); + b.pass(); + b.pass(); + }).then(res => { + t.true(res.passed); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit fails when plan is not reached inside the try', t => { + return ava(a => { + return a.try(b => { + b.plan(3); + + b.pass(); + b.pass(); + }).then(res => { + t.false(res.passed); + res.commit(); + }); + }).run().then(result => { + t.false(result.passed); + }); +}); + +test('try-commit passes with failing test', t => { + return ava.failing(a => { + return a + .try(b => b.fail()) + .then(res => { + t.false(res.passed); + t.ok(res.error); + t.match(res.error.message, /Test failed via `t\.fail\(\)`/); + t.is(res.error.name, 'AssertionError'); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit works with callback test', t => { + return ava.cb(a => { + a + .try(b => b.pass()) + .then(res => { + res.commit(); + a.end(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit works with failing callback test', t => { + return ava.cb.failing(a => { + a + .try(b => b.fail()) + .then(res => { + t.false(res.passed); + t.ok(res.error); + t.match(res.error.message, /Test failed via `t\.fail\(\)`/); + t.is(res.error.name, 'AssertionError'); + res.commit(); + }) + .then(() => { + a.end(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit can be discarded', t => { + const instance = ava(a => { + const p = a.try(b => { + return new Promise(resolve => setTimeout(resolve, 500)) + .then(() => b.pass()); + }); + + p.discard(); + + return p.then(res => { + t.is(res, null); + }); + }); + + return instance.run().then(result => { + t.false(result.passed); + t.is(instance.assertCount, 0); + }); +}); From 2521b31dd156e575d08ae9f91c5f6d539032e6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sat, 6 Oct 2018 23:11:42 -0400 Subject: [PATCH 002/105] Don't use rest symbol to support Node 6 --- lib/test.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/test.js b/lib/test.js index 8b6371c52..0d12ee212 100644 --- a/lib/test.js +++ b/lib/test.js @@ -57,7 +57,12 @@ function timeout(ms) { this.timeout(ms); } -function tryTest(fn, ...args) { +function tryTest(fn) { + const args = []; + for (let i = 1, len = arguments.length; i < len; i++) { + args.push(arguments[i]); + } + const running = this .forAttempt(t => fn.apply(null, [t].concat(args))) .run(); @@ -96,11 +101,10 @@ function tryTest(fn, ...args) { } }; - return { - ...ret, + return Object.assign({}, ret, { commit: () => commitFn(true), discard: () => commitFn(false) - }; + }); }); finishedRunning.discard = () => { @@ -215,16 +219,15 @@ class Test { this.attemptCount = 0; this.forAttempt = fn => { - return new Test({ - ...options, - metadata: { - ...options.metadata, + const opts = Object.assign({}, options, { + metadata: Object.assign({}, options.metadata, { callback: false, failing: false - }, + }), fn, title: this.title + '.A' + (this.attemptCount++) }); + return new Test(opts); }; } From 8afddc26e88fa6b0e4b3744c4a0186eb9510dad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 7 Oct 2018 21:15:18 -0400 Subject: [PATCH 003/105] Update TS typings with t.try() --- index.d.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/index.d.ts b/index.d.ts index fca580a4a..7462b8be0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -349,6 +349,7 @@ export interface ExecutionContext extends Assertions { log: LogFn; plan: PlanFn; timeout: TimeoutFn; + try: TryFn; } export interface LogFn { @@ -378,6 +379,38 @@ export interface TimeoutFn { (ms: number): void; } +export interface TryFn { + ( + impl: (t: ExecutionContext, ...args: T) => ImplementationResult, + ...args: T + ): AttemptReturnValue; + + skip(...values: Array): void; +} + +// todo: would rather remove 'null |' from Promise definition +// that is because in typescript, it will be required to check if it is not +// null all the time. +export type AttemptReturnValue = Promise void, + discard: () => void, + passed: boolean, + error: null | Error, + duration: number, + title: string, + logs: string[], + metadata: { + always: boolean, + callback: boolean, + exclusive: boolean, + failing: boolean, + serial: boolean, + skipped: boolean, + todo: boolean, + type: 'test', + }, +}> & { discard: () => void } + /** The `t` value passed to implementations for tests & hooks declared with the `.cb` modifier. */ export interface CbExecutionContext extends ExecutionContext { /** From d64a6fb38af438cb9f4ef2d364716cdb02355789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Wed, 10 Oct 2018 14:45:54 -0400 Subject: [PATCH 004/105] Refactor tryTest: pull toCommit() defn from promise --- lib/test.js | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/test.js b/lib/test.js index 0d12ee212..8939e73a4 100644 --- a/lib/test.js +++ b/lib/test.js @@ -63,13 +63,34 @@ function tryTest(fn) { args.push(arguments[i]); } + let commitCalled = false; + let discarded = false; + let finished = false; + + const commitFn = (ret, toCommit) => { + if (commitCalled) { + this.saveFirstError(new Error('Either commit() or discard() was already called')); + return; + } + commitCalled = true; + + if (ret.passed) { + this.countFinishedAttemptAssertion(toCommit); + } else { + this.countFailedAttemptAssertion(ret.error, toCommit); + } + + if (toCommit) { + for (const log of ret.logs) { + this.addLog(log); + } + } + }; + const running = this .forAttempt(t => fn.apply(null, [t].concat(args))) .run(); - let commitCalled = false; - let discarded = false; - this.addPendingAttemptAssertion(running.then(ret => { if (!discarded && !ret.passed) { throw ret.error; @@ -81,29 +102,10 @@ function tryTest(fn) { return null; } - const commitFn = toCommit => { - if (commitCalled) { - this.saveFirstError(new Error('Either commit() or discard() was already called')); - return; - } - commitCalled = true; - - if (ret.passed) { - this.countFinishedAttemptAssertion(toCommit); - } else { - this.countFailedAttemptAssertion(ret.error, toCommit); - } - - if (toCommit) { - for (const log of ret.logs) { - this.addLog(log); - } - } - }; - + finished = true; return Object.assign({}, ret, { - commit: () => commitFn(true), - discard: () => commitFn(false) + commit: () => commitFn(ret, true), + discard: () => commitFn(ret, false) }); }); From fccf41bb7402b42b18619b76333b43ff99800f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Wed, 10 Oct 2018 15:00:29 -0400 Subject: [PATCH 005/105] Make sure commit()/discard() only called once --- lib/test.js | 19 +++++++++++++++++-- test/test.js | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index 8939e73a4..5b09f9d88 100644 --- a/lib/test.js +++ b/lib/test.js @@ -69,7 +69,11 @@ function tryTest(fn) { const commitFn = (ret, toCommit) => { if (commitCalled) { - this.saveFirstError(new Error('Either commit() or discard() was already called')); + if (discarded) { + this.saveFirstError(new Error('The discard() was already called')); + } else { + this.saveFirstError(new Error('The commit() was already called')); + } return; } commitCalled = true; @@ -105,11 +109,22 @@ function tryTest(fn) { finished = true; return Object.assign({}, ret, { commit: () => commitFn(ret, true), - discard: () => commitFn(ret, false) + discard: () => { + discarded = true; + return commitFn(ret, false); + } }); }); finishedRunning.discard = () => { + if (finished) { + this.saveFirstError(new Error('Attempt is already resolved')); + return; + } + if (discarded) { + this.saveFirstError(new Error('The discard() was already called')); + return; + } discarded = true; this.decrementAttemptAssertion(); }; diff --git a/test/test.js b/test/test.js index 4153c2883..8c96e02d5 100644 --- a/test/test.js +++ b/test/test.js @@ -1017,7 +1017,54 @@ test('try-commit fails when calling commit twice', t => { }).run().then(result => { t.false(result.passed); t.ok(result.error); - t.match(result.error.message, /was already called/); + t.match(result.error.message, /The commit\(\) was already called/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit fails when calling discard twice', t => { + return ava(a => { + return a.try(b => b.pass()).then(res => { + res.discard(); + res.discard(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /The discard\(\) was already called/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit fails when calling discard on promise twice', t => { + return ava(a => { + const pr = a.try(b => b.pass()); + pr.discard(); + pr.discard(); + + return pr.then(res => { + t.is(res, null); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /The discard\(\) was already called/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit fails when calling discard on promise after attempt resolved', t => { + return ava(a => { + const attemptPromise = a.try(b => b.pass()); + return attemptPromise.then(res => { + t.true(res.passed); + res.commit(); + attemptPromise.discard(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Attempt is already resolved/); t.is(result.error.name, 'Error'); }); }); From d1d559fe17b228fb8ca318ae8ccd42390dffb1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Wed, 10 Oct 2018 15:02:34 -0400 Subject: [PATCH 006/105] No need to save return error of attempt twice --- lib/test.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/test.js b/lib/test.js index 5b09f9d88..66aba9e66 100644 --- a/lib/test.js +++ b/lib/test.js @@ -95,11 +95,7 @@ function tryTest(fn) { .forAttempt(t => fn.apply(null, [t].concat(args))) .run(); - this.addPendingAttemptAssertion(running.then(ret => { - if (!discarded && !ret.passed) { - throw ret.error; - } - })); + this.addPendingAttemptAssertion(); const finishedRunning = running.then(ret => { if (discarded) { @@ -324,13 +320,12 @@ class Test { this.saveFirstError(error); } - addPendingAttemptAssertion(promise) { + addPendingAttemptAssertion() { if (this.finishing) { this.saveFirstError(new Error('Assertion passed, but test has already finished')); } this.pendingAttemptCount++; - promise.catch(error => this.saveFirstError(error)); } decrementAttemptAssertion() { From 6be281fb82c916881369d5231728f5cf6ff8ec00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 14 Jan 2019 21:23:34 -0500 Subject: [PATCH 007/105] Use rest and spread on arguments to try function --- lib/test.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/test.js b/lib/test.js index 66aba9e66..8260247be 100644 --- a/lib/test.js +++ b/lib/test.js @@ -57,12 +57,7 @@ function timeout(ms) { this.timeout(ms); } -function tryTest(fn) { - const args = []; - for (let i = 1, len = arguments.length; i < len; i++) { - args.push(arguments[i]); - } - +function tryTest(fn, ...args) { let commitCalled = false; let discarded = false; let finished = false; @@ -92,7 +87,7 @@ function tryTest(fn) { }; const running = this - .forAttempt(t => fn.apply(null, [t].concat(args))) + .forAttempt(t => fn(t, ...args)) .run(); this.addPendingAttemptAssertion(); From eeb861b9ba07525439dbb92644c189e19efb0896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 14 Jan 2019 21:24:38 -0500 Subject: [PATCH 008/105] Count pending before executing passed fn --- lib/test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index 8260247be..d221b69fb 100644 --- a/lib/test.js +++ b/lib/test.js @@ -86,12 +86,12 @@ function tryTest(fn, ...args) { } }; + this.addPendingAttemptAssertion(); + const running = this .forAttempt(t => fn(t, ...args)) .run(); - this.addPendingAttemptAssertion(); - const finishedRunning = running.then(ret => { if (discarded) { return null; From 7153337604d1ef2d82eaaa7e9cd7fb9a03361a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 14 Jan 2019 21:28:03 -0500 Subject: [PATCH 009/105] Make behaviour of try more implicit The commit() and discard() functions are split. Counting of attempt is increased by the increment value. Behavior of try, commit() and discard() are more implicit. Removes unneeded fields from commit(). --- index.d.ts | 11 ------- lib/test.js | 83 ++++++++++++++++++++++++++++------------------------- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/index.d.ts b/index.d.ts index 7462b8be0..704043a59 100644 --- a/index.d.ts +++ b/index.d.ts @@ -396,19 +396,8 @@ export type AttemptReturnValue = Promise void, passed: boolean, error: null | Error, - duration: number, title: string, logs: string[], - metadata: { - always: boolean, - callback: boolean, - exclusive: boolean, - failing: boolean, - serial: boolean, - skipped: boolean, - todo: boolean, - type: 'test', - }, }> & { discard: () => void } /** The `t` value passed to implementations for tests & hooks declared with the `.cb` modifier. */ diff --git a/lib/test.js b/lib/test.js index d221b69fb..bae76a547 100644 --- a/lib/test.js +++ b/lib/test.js @@ -58,31 +58,14 @@ function timeout(ms) { } function tryTest(fn, ...args) { - let commitCalled = false; + let committed = false; let discarded = false; - let finished = false; - const commitFn = (ret, toCommit) => { - if (commitCalled) { - if (discarded) { - this.saveFirstError(new Error('The discard() was already called')); - } else { - this.saveFirstError(new Error('The commit() was already called')); - } - return; - } - commitCalled = true; - - if (ret.passed) { - this.countFinishedAttemptAssertion(toCommit); + const countAttempt = result => { + if (result.passed) { + this.countFinishedAttemptAssertion(result.inc); } else { - this.countFailedAttemptAssertion(ret.error, toCommit); - } - - if (toCommit) { - for (const log of ret.logs) { - this.addLog(log); - } + this.countFailedAttemptAssertion(result.error, result.inc); } }; @@ -97,23 +80,47 @@ function tryTest(fn, ...args) { return null; } - finished = true; - return Object.assign({}, ret, { - commit: () => commitFn(ret, true), + const {passed, error, title, logs} = ret; + + return { + passed, + error, + title, + logs, + commit: () => { + if (committed) { + return; + } + if (discarded) { + this.saveFirstError(new Error('Can\'t commit the result that was previously discarded')); + return; + } + committed = true; + + countAttempt({passed, error, inc: 1}); + + for (const log of logs) { + this.addLog(log); + } + }, discard: () => { + if (committed) { + this.saveFirstError(new Error('Can\'t discard the result that was previously committed')); + return; + } + if (discarded) { + return; + } discarded = true; - return commitFn(ret, false); + + countAttempt({passed, error, inc: 0}); } - }); + }; }); finishedRunning.discard = () => { - if (finished) { - this.saveFirstError(new Error('Attempt is already resolved')); - return; - } - if (discarded) { - this.saveFirstError(new Error('The discard() was already called')); + if (committed) { + this.saveFirstError(new Error('Attempt is already committed')); return; } discarded = true; @@ -327,19 +334,17 @@ class Test { this.pendingAttemptCount--; } - countFinishedAttemptAssertion(includeCount) { + countFinishedAttemptAssertion(increment) { if (this.finishing) { this.saveFirstError(new Error('Assertion passed, but test has already finished')); } - if (includeCount) { - this.assertCount++; - } + this.assertCount += increment; this.decrementAttemptAssertion(); } - countFailedAttemptAssertion(error, includeCount) { - this.countFinishedAttemptAssertion(includeCount); + countFailedAttemptAssertion(error, increment) { + this.countFinishedAttemptAssertion(increment); this.saveFirstError(error); } From 62ffd9b94c617f0da2ead6d6437f20049e9f6bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 14 Jan 2019 21:38:26 -0500 Subject: [PATCH 010/105] Return array of assertion errors from commit() --- index.d.ts | 5 ++++- lib/test.js | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 704043a59..2aade4e3f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -388,6 +388,9 @@ export interface TryFn { skip(...values: Array): void; } +export class AssertionError extends Error { +} + // todo: would rather remove 'null |' from Promise definition // that is because in typescript, it will be required to check if it is not // null all the time. @@ -395,7 +398,7 @@ export type AttemptReturnValue = Promise void, discard: () => void, passed: boolean, - error: null | Error, + errors: AssertionError[], title: string, logs: string[], }> & { discard: () => void } diff --git a/lib/test.js b/lib/test.js index bae76a547..9218cacaa 100644 --- a/lib/test.js +++ b/lib/test.js @@ -81,10 +81,11 @@ function tryTest(fn, ...args) { } const {passed, error, title, logs} = ret; + const errors = error ? Array.isArray(error) ? [...error] : [error] : []; return { passed, - error, + errors, title, logs, commit: () => { From ba690b7426fea45c9ffb877e988678e3fb7ad88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 14 Jan 2019 21:38:58 -0500 Subject: [PATCH 011/105] Fix tests of the test class --- test/test.js | 68 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/test/test.js b/test/test.js index 8c96e02d5..9786451e4 100644 --- a/test/test.js +++ b/test/test.js @@ -943,9 +943,11 @@ test('try-commit fails when no assertions inside try', t => { .try(() => {}) .then(res => { t.false(res.passed); - t.ok(res.error); - t.match(res.error.message, /Test finished without running any assertions/); - t.is(res.error.name, 'Error'); + t.ok(res.errors); + t.is(res.errors.length, 1); + const error = res.errors[0]; + t.match(error.message, /Test finished without running any assertions/); + t.is(error.name, 'Error'); res.commit(); }); }).run().then(result => { @@ -962,9 +964,11 @@ test('try-commit fails when no assertions inside multiple try', t => { }), a.try(() => {}).then(res2 => { t.false(res2.passed); - t.ok(res2.error); - t.match(res2.error.message, /Test finished without running any assertions/); - t.is(res2.error.name, 'Error'); + t.ok(res2.errors); + t.is(res2.errors.length, 1); + const error = res2.errors[0]; + t.match(error.message, /Test finished without running any assertions/); + t.is(error.name, 'Error'); res2.commit(); }) ]); @@ -1008,21 +1012,19 @@ test('try-commit has proper titles, when going in depth and width', t => { }).run(); }); -test('try-commit fails when calling commit twice', t => { +test('try-commit does not fail when calling commit twice', t => { return ava(a => { return a.try(b => b.pass()).then(res => { res.commit(); res.commit(); }); }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /The commit\(\) was already called/); - t.is(result.error.name, 'Error'); + t.true(result.passed); + t.false(result.error); }); }); -test('try-commit fails when calling discard twice', t => { +test('try-commit does not fail when calling discard twice', t => { return ava(a => { return a.try(b => b.pass()).then(res => { res.discard(); @@ -1031,12 +1033,12 @@ test('try-commit fails when calling discard twice', t => { }).run().then(result => { t.false(result.passed); t.ok(result.error); - t.match(result.error.message, /The discard\(\) was already called/); + t.match(result.error.message, /Test finished without running any assertions/); t.is(result.error.name, 'Error'); }); }); -test('try-commit fails when calling discard on promise twice', t => { +test('try-commit does not fail when calling discard on promise twice', t => { return ava(a => { const pr = a.try(b => b.pass()); pr.discard(); @@ -1048,12 +1050,12 @@ test('try-commit fails when calling discard on promise twice', t => { }).run().then(result => { t.false(result.passed); t.ok(result.error); - t.match(result.error.message, /The discard\(\) was already called/); + t.match(result.error.message, /Test finished without running any assertions/); t.is(result.error.name, 'Error'); }); }); -test('try-commit fails when calling discard on promise after attempt resolved', t => { +test('try-commit fails when calling discard on promise after attempt committed', t => { return ava(a => { const attemptPromise = a.try(b => b.pass()); return attemptPromise.then(res => { @@ -1064,7 +1066,23 @@ test('try-commit fails when calling discard on promise after attempt resolved', }).run().then(result => { t.false(result.passed); t.ok(result.error); - t.match(result.error.message, /Attempt is already resolved/); + t.match(result.error.message, /Attempt is already committed/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit does not fail when calling discard on promise after attempt discarded', t => { + return ava(a => { + const attemptPromise = a.try(b => b.pass()); + return attemptPromise.then(res => { + t.true(res.passed); + res.discard(); + attemptPromise.discard(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Test finished without running any assertions/); t.is(result.error.name, 'Error'); }); }); @@ -1108,9 +1126,11 @@ test('try-commit passes with failing test', t => { .try(b => b.fail()) .then(res => { t.false(res.passed); - t.ok(res.error); - t.match(res.error.message, /Test failed via `t\.fail\(\)`/); - t.is(res.error.name, 'AssertionError'); + t.ok(res.errors); + t.is(res.errors.length, 1); + const error = res.errors[0]; + t.match(error.message, /Test failed via `t\.fail\(\)`/); + t.is(error.name, 'AssertionError'); res.commit(); }); }).run().then(result => { @@ -1137,9 +1157,11 @@ test('try-commit works with failing callback test', t => { .try(b => b.fail()) .then(res => { t.false(res.passed); - t.ok(res.error); - t.match(res.error.message, /Test failed via `t\.fail\(\)`/); - t.is(res.error.name, 'AssertionError'); + t.ok(res.errors); + t.is(res.errors.length, 1); + const error = res.errors[0]; + t.match(error.message, /Test failed via `t\.fail\(\)`/); + t.is(error.name, 'AssertionError'); res.commit(); }) .then(() => { From a4f0fee2b898283215d32606793c482b2cc00ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 14 Jan 2019 21:42:48 -0500 Subject: [PATCH 012/105] Fix error message when test finished early --- lib/test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index 9218cacaa..1b2e39805 100644 --- a/lib/test.js +++ b/lib/test.js @@ -325,7 +325,7 @@ class Test { addPendingAttemptAssertion() { if (this.finishing) { - this.saveFirstError(new Error('Assertion passed, but test has already finished')); + this.saveFirstError(new Error('Adding the attempt, but the test has already finished')); } this.pendingAttemptCount++; @@ -337,7 +337,7 @@ class Test { countFinishedAttemptAssertion(increment) { if (this.finishing) { - this.saveFirstError(new Error('Assertion passed, but test has already finished')); + this.saveFirstError(new Error('Attempt is complete, but the test has already finished')); } this.assertCount += increment; From 7750dfc7378c86891cf181694af5c7b7a9a50a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 20 Jan 2019 12:52:59 -0500 Subject: [PATCH 013/105] Save logs from discard if needed --- index.d.ts | 8 ++++++-- lib/test.js | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/index.d.ts b/index.d.ts index 2aade4e3f..0305e7d9e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -395,14 +395,18 @@ export class AssertionError extends Error { // that is because in typescript, it will be required to check if it is not // null all the time. export type AttemptReturnValue = Promise void, - discard: () => void, + commit: (opts?: CommitDiscardOptions) => void, + discard: (opts?: CommitDiscardOptions) => void, passed: boolean, errors: AssertionError[], title: string, logs: string[], }> & { discard: () => void } +export interface CommitDiscardOptions { + retainLogs?: boolean +} + /** The `t` value passed to implementations for tests & hooks declared with the `.cb` modifier. */ export interface CbExecutionContext extends ExecutionContext { /** diff --git a/lib/test.js b/lib/test.js index 1b2e39805..e8a5db457 100644 --- a/lib/test.js +++ b/lib/test.js @@ -69,6 +69,14 @@ function tryTest(fn, ...args) { } }; + const saveLogs = (retainLogs, logs) => { + if (retainLogs) { + for (const log of logs) { + this.addLog(log); + } + } + }; + this.addPendingAttemptAssertion(); const running = this @@ -88,7 +96,7 @@ function tryTest(fn, ...args) { errors, title, logs, - commit: () => { + commit: ({retainLogs = true} = {}) => { if (committed) { return; } @@ -99,12 +107,9 @@ function tryTest(fn, ...args) { committed = true; countAttempt({passed, error, inc: 1}); - - for (const log of logs) { - this.addLog(log); - } + saveLogs(retainLogs, logs); }, - discard: () => { + discard: ({retainLogs = false} = {}) => { if (committed) { this.saveFirstError(new Error('Can\'t discard the result that was previously committed')); return; @@ -115,6 +120,7 @@ function tryTest(fn, ...args) { discarded = true; countAttempt({passed, error, inc: 0}); + saveLogs(retainLogs, logs); } }; }); From 476148c48d95574966671027e479adcb2245fb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 20 Jan 2019 14:09:34 -0500 Subject: [PATCH 014/105] Remove discard before promise resolved --- index.d.ts | 7 ++----- lib/test.js | 17 +---------------- test/test.js | 49 ------------------------------------------------- 3 files changed, 3 insertions(+), 70 deletions(-) diff --git a/index.d.ts b/index.d.ts index 0305e7d9e..34627959c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -391,17 +391,14 @@ export interface TryFn { export class AssertionError extends Error { } -// todo: would rather remove 'null |' from Promise definition -// that is because in typescript, it will be required to check if it is not -// null all the time. -export type AttemptReturnValue = Promise void, discard: (opts?: CommitDiscardOptions) => void, passed: boolean, errors: AssertionError[], title: string, logs: string[], -}> & { discard: () => void } +}> export interface CommitDiscardOptions { retainLogs?: boolean diff --git a/lib/test.js b/lib/test.js index e8a5db457..13d10c33b 100644 --- a/lib/test.js +++ b/lib/test.js @@ -83,11 +83,7 @@ function tryTest(fn, ...args) { .forAttempt(t => fn(t, ...args)) .run(); - const finishedRunning = running.then(ret => { - if (discarded) { - return null; - } - + return running.then(ret => { const {passed, error, title, logs} = ret; const errors = error ? Array.isArray(error) ? [...error] : [error] : []; @@ -124,17 +120,6 @@ function tryTest(fn, ...args) { } }; }); - - finishedRunning.discard = () => { - if (committed) { - this.saveFirstError(new Error('Attempt is already committed')); - return; - } - discarded = true; - this.decrementAttemptAssertion(); - }; - - return finishedRunning; } const testMap = new WeakMap(); diff --git a/test/test.js b/test/test.js index 9786451e4..b067d5470 100644 --- a/test/test.js +++ b/test/test.js @@ -1038,55 +1038,6 @@ test('try-commit does not fail when calling discard twice', t => { }); }); -test('try-commit does not fail when calling discard on promise twice', t => { - return ava(a => { - const pr = a.try(b => b.pass()); - pr.discard(); - pr.discard(); - - return pr.then(res => { - t.is(res, null); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /Test finished without running any assertions/); - t.is(result.error.name, 'Error'); - }); -}); - -test('try-commit fails when calling discard on promise after attempt committed', t => { - return ava(a => { - const attemptPromise = a.try(b => b.pass()); - return attemptPromise.then(res => { - t.true(res.passed); - res.commit(); - attemptPromise.discard(); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /Attempt is already committed/); - t.is(result.error.name, 'Error'); - }); -}); - -test('try-commit does not fail when calling discard on promise after attempt discarded', t => { - return ava(a => { - const attemptPromise = a.try(b => b.pass()); - return attemptPromise.then(res => { - t.true(res.passed); - res.discard(); - attemptPromise.discard(); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /Test finished without running any assertions/); - t.is(result.error.name, 'Error'); - }); -}); - test('try-commit allows planning inside the try', t => { return ava(a => { return a.try(b => { From 42a7245651b8dbf7ceddeb57fc347f737e653571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 20 Jan 2019 15:25:24 -0500 Subject: [PATCH 015/105] Move attempt counting into Test class --- lib/test.js | 47 ++++++++++++++--------------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/lib/test.js b/lib/test.js index 13d10c33b..13908babc 100644 --- a/lib/test.js +++ b/lib/test.js @@ -61,22 +61,6 @@ function tryTest(fn, ...args) { let committed = false; let discarded = false; - const countAttempt = result => { - if (result.passed) { - this.countFinishedAttemptAssertion(result.inc); - } else { - this.countFailedAttemptAssertion(result.error, result.inc); - } - }; - - const saveLogs = (retainLogs, logs) => { - if (retainLogs) { - for (const log of logs) { - this.addLog(log); - } - } - }; - this.addPendingAttemptAssertion(); const running = this @@ -101,9 +85,7 @@ function tryTest(fn, ...args) { return; } committed = true; - - countAttempt({passed, error, inc: 1}); - saveLogs(retainLogs, logs); + this.countAttemptAssertion({inc: 1, result: ret, retainLogs}); }, discard: ({retainLogs = false} = {}) => { if (committed) { @@ -114,9 +96,7 @@ function tryTest(fn, ...args) { return; } discarded = true; - - countAttempt({passed, error, inc: 0}); - saveLogs(retainLogs, logs); + this.countAttemptAssertion({inc: 0, result: ret, retainLogs}); } }; }); @@ -322,22 +302,23 @@ class Test { this.pendingAttemptCount++; } - decrementAttemptAssertion() { - this.pendingAttemptCount--; - } - - countFinishedAttemptAssertion(increment) { + countAttemptAssertion({inc, result: {passed, error, logs}, retainLogs}) { if (this.finishing) { this.saveFirstError(new Error('Attempt is complete, but the test has already finished')); } - this.assertCount += increment; - this.decrementAttemptAssertion(); - } + this.assertCount += inc; + this.pendingAttemptCount--; - countFailedAttemptAssertion(error, increment) { - this.countFinishedAttemptAssertion(increment); - this.saveFirstError(error); + if (!passed) { + this.saveFirstError(error); + } + + if (retainLogs) { + for (const log of logs) { + this.addLog(log); + } + } } saveFirstError(error) { From e71ee0b553ccc8079404c316a1fc75107bed9b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 20 Jan 2019 16:03:30 -0500 Subject: [PATCH 016/105] Add optional title to attempt --- index.d.ts | 5 +++++ lib/test.js | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 34627959c..a8758b20d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -380,6 +380,11 @@ export interface TimeoutFn { } export interface TryFn { + ( + title: string, + impl: (t: ExecutionContext, ...args: T) => ImplementationResult, + ...args: T, + ): AttemptReturnValue; ( impl: (t: ExecutionContext, ...args: T) => ImplementationResult, ...args: T diff --git a/lib/test.js b/lib/test.js index 13908babc..4aed65390 100644 --- a/lib/test.js +++ b/lib/test.js @@ -57,14 +57,20 @@ function timeout(ms) { this.timeout(ms); } -function tryTest(fn, ...args) { +function tryTest(title, fn, ...args) { + if (typeof title === 'function') { + args.unshift(fn); + fn = title; + title = undefined; + } + let committed = false; let discarded = false; this.addPendingAttemptAssertion(); const running = this - .forAttempt(t => fn(t, ...args)) + .forAttempt(title, t => fn(t, ...args)) .run(); return running.then(ret => { @@ -205,14 +211,17 @@ class Test { this.pendingAttemptCount = 0; this.attemptCount = 0; - this.forAttempt = fn => { + this.forAttempt = (title, fn) => { + const attemptId = this.attemptCount++; + title = title || (this.title + '.A' + attemptId); + const opts = Object.assign({}, options, { metadata: Object.assign({}, options.metadata, { callback: false, failing: false }), fn, - title: this.title + '.A' + (this.attemptCount++) + title }); return new Test(opts); }; From a5f077287b3fe0bab6c178c2baba5eb3d0230d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 27 Jan 2019 20:45:43 -0500 Subject: [PATCH 017/105] Fix errors in strings returned to a user --- lib/test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index 4aed65390..2d4e13ba2 100644 --- a/lib/test.js +++ b/lib/test.js @@ -87,7 +87,7 @@ function tryTest(title, fn, ...args) { return; } if (discarded) { - this.saveFirstError(new Error('Can\'t commit the result that was previously discarded')); + this.saveFirstError(new Error('Can\'t commit a result that was previously discarded')); return; } committed = true; @@ -95,7 +95,7 @@ function tryTest(title, fn, ...args) { }, discard: ({retainLogs = false} = {}) => { if (committed) { - this.saveFirstError(new Error('Can\'t discard the result that was previously committed')); + this.saveFirstError(new Error('Can\'t discard a result that was previously committed')); return; } if (discarded) { @@ -396,7 +396,7 @@ class Test { if (!this.assertError) { if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null && this.assertCount === 0) { if (this.pendingAttemptCount > 0) { - this.saveFirstError(new Error('Test finished, but not all attempts were committed')); + this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded')); } else { this.saveFirstError(new Error('Test finished without running any assertions')); } From eb49df2f4533023dc3a26325c82f246661de93b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 27 Jan 2019 21:00:51 -0500 Subject: [PATCH 018/105] Add documentation to AttemptReturnValue --- index.d.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/index.d.ts b/index.d.ts index a8758b20d..20686adb9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -397,15 +397,36 @@ export class AssertionError extends Error { } export type AttemptReturnValue = Promise<{ + /** + * Commit the attempt + */ commit: (opts?: CommitDiscardOptions) => void, + /** + * Discard the attempt + */ discard: (opts?: CommitDiscardOptions) => void, + /** + * The attempt succeeded or failed + */ passed: boolean, + /** + * Assertion errors raised in the attempt + */ errors: AssertionError[], + /** + * Title of the attempt to distinguish attempts from each other + */ title: string, + /** + * Strings that were logged in the attempt + */ logs: string[], }> export interface CommitDiscardOptions { + /** + * The logs created in the attempt could be either discarded or passed to the parent test + */ retainLogs?: boolean } From a22e97cdb7462ea466ff73130245e3d9fb68ad3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 27 Jan 2019 21:01:51 -0500 Subject: [PATCH 019/105] Rename AttemptReturnValue into AttemptResult --- index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 20686adb9..948df9237 100644 --- a/index.d.ts +++ b/index.d.ts @@ -384,11 +384,11 @@ export interface TryFn { title: string, impl: (t: ExecutionContext, ...args: T) => ImplementationResult, ...args: T, - ): AttemptReturnValue; + ): AttemptResult; ( impl: (t: ExecutionContext, ...args: T) => ImplementationResult, ...args: T - ): AttemptReturnValue; + ): AttemptResult; skip(...values: Array): void; } @@ -396,7 +396,7 @@ export interface TryFn { export class AssertionError extends Error { } -export type AttemptReturnValue = Promise<{ +export type AttemptResult = Promise<{ /** * Commit the attempt */ From 46772a70e16030a54f7f92713b42e86519233dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 27 Jan 2019 23:35:40 -0500 Subject: [PATCH 020/105] Test snapshots as part of try-commit --- test/fixture/try-snapshot.js.md | 25 ++++++++ test/fixture/try-snapshot.js.snap | Bin 0 -> 203 bytes test/try-snapshot.js | 91 ++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 test/fixture/try-snapshot.js.md create mode 100644 test/fixture/try-snapshot.js.snap create mode 100644 test/try-snapshot.js diff --git a/test/fixture/try-snapshot.js.md b/test/fixture/try-snapshot.js.md new file mode 100644 index 000000000..b32330f00 --- /dev/null +++ b/test/fixture/try-snapshot.js.md @@ -0,0 +1,25 @@ +# Snapshot report for `try-snapshot.js` + +The actual snapshot is saved in `try-snapshot.js.snap`. + +Generated by [AVA](https://ava.li). + +## test + +> Snapshot 1 + + 'hello' + +## test.A0 + +> Snapshot 1 + + true + +## test.A1 + +> Snapshot 1 + + { + foo: 'bar', + } diff --git a/test/fixture/try-snapshot.js.snap b/test/fixture/try-snapshot.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..9e463ac75f6d3315e483a0f40c40bb846fb201e4 GIT binary patch literal 203 zcmV;+05tzWRzVms$RvS$&Hx*v-O000000009E zVPIfjX5j44KI^7o;?gc-r2@<2S- z@V>hBsVnYoefs(3i?q)(f<=qK=COnIGqN%WGO}i*=H%qVg_s!l;cN~@COsf+0 manager.compare(options) + }); + + return {ava, manager}; +} + +test('try-commit snapshots serially', t => { + const {ava, manager} = setup(a => { + a.snapshot('hello'); + + const attempt1 = t2 => { + t2.snapshot(true); + }; + + const attempt2 = t2 => { + t2.snapshot({foo: 'bar'}); + }; + + return a.try(attempt1).then(first => { + first.commit(); + return a.try(attempt2); + }).then(second => { + second.commit(); + }); + }); + + return ava.run().then(result => { + manager.save(); + t.true(result.passed); + if (!result.passed) { + console.log(result.error); + } + }); +}); + +test('try-commit snapshots concurrently', t => { + const {ava, manager} = setup(a => { + a.snapshot('hello'); + + const attempt1 = t2 => { + t2.snapshot(true); + }; + + const attempt2 = t2 => { + t2.snapshot({foo: 'bar'}); + }; + + return Promise.all([a.try(attempt1), a.try(attempt2)]) + .then(([first, second]) => { + first.commit(); + second.commit(); + }); + }); + + return ava.run().then(result => { + manager.save(); + t.true(result.passed); + if (!result.passed) { + console.log(result.error); + } + }); +}); From 40ede9269dab982fa60b0d3495ba4df1303e94b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 27 Jan 2019 22:04:26 -0500 Subject: [PATCH 021/105] Format with xo --- lib/test.js | 4 ++++ test/try-snapshot.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index 2d4e13ba2..21c39dc60 100644 --- a/lib/test.js +++ b/lib/test.js @@ -86,10 +86,12 @@ function tryTest(title, fn, ...args) { if (committed) { return; } + if (discarded) { this.saveFirstError(new Error('Can\'t commit a result that was previously discarded')); return; } + committed = true; this.countAttemptAssertion({inc: 1, result: ret, retainLogs}); }, @@ -98,9 +100,11 @@ function tryTest(title, fn, ...args) { this.saveFirstError(new Error('Can\'t discard a result that was previously committed')); return; } + if (discarded) { return; } + discarded = true; this.countAttemptAssertion({inc: 0, result: ret, retainLogs}); } diff --git a/test/try-snapshot.js b/test/try-snapshot.js index 8df72a5a4..0c81e65ee 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -3,7 +3,7 @@ require('../lib/chalk').set(); require('../lib/worker/options').set({color: false}); const path = require('path'); -const test = require('tap').test; +const {test} = require('tap'); const snapshotManager = require('../lib/snapshot-manager'); const Test = require('../lib/test'); From fd7be328e12f98aded42e3eb5763818291aae19a Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 10 Feb 2019 17:59:05 +0100 Subject: [PATCH 022/105] Adjust TypeScript definition --- index.d.ts | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/index.d.ts b/index.d.ts index 948df9237..b6196f777 100644 --- a/index.d.ts +++ b/index.d.ts @@ -384,11 +384,11 @@ export interface TryFn { title: string, impl: (t: ExecutionContext, ...args: T) => ImplementationResult, ...args: T, - ): AttemptResult; + ): Promise; ( impl: (t: ExecutionContext, ...args: T) => ImplementationResult, ...args: T - ): AttemptResult; + ): Promise; skip(...values: Array): void; } @@ -396,36 +396,41 @@ export interface TryFn { export class AssertionError extends Error { } -export type AttemptResult = Promise<{ +export interface AttemptResult { /** - * Commit the attempt + * Commit the attempt. */ - commit: (opts?: CommitDiscardOptions) => void, + commit: (opts?: CommitDiscardOptions) => void; + /** - * Discard the attempt + * Discard the attempt. */ - discard: (opts?: CommitDiscardOptions) => void, + discard: (opts?: CommitDiscardOptions) => void; + /** - * The attempt succeeded or failed + * Indicates whether attempt passed or failed. */ - passed: boolean, + passed: boolean; + /** - * Assertion errors raised in the attempt + * Assertion errors raised during the attempt. */ - errors: AssertionError[], + errors: AssertionError[]; + /** - * Title of the attempt to distinguish attempts from each other + * Title of the attempt, helps you distinguish attempts from each other. */ - title: string, + title: string; + /** - * Strings that were logged in the attempt + * Logs created during the attempt. Contains formatted values. */ - logs: string[], -}> + logs: string[]; +} -export interface CommitDiscardOptions { +export type CommitDiscardOptions = { /** - * The logs created in the attempt could be either discarded or passed to the parent test + * Whether the logs should be included in the parent test. */ retainLogs?: boolean } From 279057fea0c58042b9f969289b9ccb5630f98871 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 10 Feb 2019 18:00:30 +0100 Subject: [PATCH 023/105] Remove (currently) unnecessary check for test results containing multiple errors --- lib/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index 21c39dc60..94e087d2d 100644 --- a/lib/test.js +++ b/lib/test.js @@ -75,7 +75,7 @@ function tryTest(title, fn, ...args) { return running.then(ret => { const {passed, error, title, logs} = ret; - const errors = error ? Array.isArray(error) ? [...error] : [error] : []; + const errors = error ? [error] : []; return { passed, From ed5db8059fe4ff84d0d9dbde075a8ad61aa04cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 12:40:27 -0400 Subject: [PATCH 024/105] Count number of assertions performed in subtest --- lib/test.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/test.js b/lib/test.js index 94e087d2d..a7923d295 100644 --- a/lib/test.js +++ b/lib/test.js @@ -69,11 +69,9 @@ function tryTest(title, fn, ...args) { this.addPendingAttemptAssertion(); - const running = this - .forAttempt(title, t => fn(t, ...args)) - .run(); + const attemptTest = this.forAttempt(title, t => fn(t, ...args)); - return running.then(ret => { + return attemptTest.run().then(ret => { const {passed, error, title, logs} = ret; const errors = error ? [error] : []; @@ -93,7 +91,7 @@ function tryTest(title, fn, ...args) { } committed = true; - this.countAttemptAssertion({inc: 1, result: ret, retainLogs}); + this.countAttemptAssertion({inc: attemptTest.assertCount, result: ret, retainLogs}); }, discard: ({retainLogs = false} = {}) => { if (committed) { From 51323c1e2e955885f2023614cfc219574cedd67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 13:49:36 -0400 Subject: [PATCH 025/105] Fix error where failed attempt was not discarded --- lib/test.js | 18 ++++++++++++++---- test/test.js | 11 +++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/test.js b/lib/test.js index a7923d295..ee60ebac8 100644 --- a/lib/test.js +++ b/lib/test.js @@ -91,7 +91,12 @@ function tryTest(title, fn, ...args) { } committed = true; - this.countAttemptAssertion({inc: attemptTest.assertCount, result: ret, retainLogs}); + this.countAttemptAssertion({ + inc: attemptTest.assertCount, + commit: true, + result: ret, + retainLogs + }); }, discard: ({retainLogs = false} = {}) => { if (committed) { @@ -104,7 +109,12 @@ function tryTest(title, fn, ...args) { } discarded = true; - this.countAttemptAssertion({inc: 0, result: ret, retainLogs}); + this.countAttemptAssertion({ + inc: 0, + commit: false, + result: ret, + retainLogs + }); } }; }); @@ -313,7 +323,7 @@ class Test { this.pendingAttemptCount++; } - countAttemptAssertion({inc, result: {passed, error, logs}, retainLogs}) { + countAttemptAssertion({inc, commit, result: {passed, error, logs}, retainLogs}) { if (this.finishing) { this.saveFirstError(new Error('Attempt is complete, but the test has already finished')); } @@ -321,7 +331,7 @@ class Test { this.assertCount += inc; this.pendingAttemptCount--; - if (!passed) { + if (commit && !passed) { this.saveFirstError(error); } diff --git a/test/test.js b/test/test.js index b067d5470..3fcf59279 100644 --- a/test/test.js +++ b/test/test.js @@ -822,6 +822,17 @@ test('try-commit works', t => { }); }); +test('try-commit discards failed attempt', t => { + return ava(a => { + return a + .try(b => b.fail()) + .then(res => res.discard()) + .then(() => a.pass()); + }).run().then(result => { + t.true(result.passed); + }); +}); + test('try-commit can discard produced result', t => { return ava(a => { return a From fe8ebf8e3d092f080befd123d5cc325120f28f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 13:54:08 -0400 Subject: [PATCH 026/105] Adjust test counting number of assertions performed --- test/test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test.js b/test/test.js index 3fcf59279..9f2e64c57 100644 --- a/test/test.js +++ b/test/test.js @@ -869,7 +869,11 @@ test('try-commit works with values', t => { test('try-commit is properly counted', t => { const instance = ava(a => { return a - .try(b => b.pass()) + .try(b => { + b.is(1, 1); + b.is(2, 2); + b.pass(); + }) .then(res => { t.true(res.passed); t.is(instance.pendingAttemptCount, 1); @@ -880,7 +884,7 @@ test('try-commit is properly counted', t => { return instance.run().then(result => { t.true(result.passed); - t.is(instance.attemptCount, 1); + t.is(instance.attemptCount, 3); }); }); From c28152f191d368628b0072bdcb6e0100956e1b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 14:09:42 -0400 Subject: [PATCH 027/105] Always fail when there are some non committed/discarded attempts --- lib/test.js | 4 ++-- test/test.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index ee60ebac8..b2f92896a 100644 --- a/lib/test.js +++ b/lib/test.js @@ -406,10 +406,10 @@ class Test { verifyAssertions() { if (!this.assertError) { - if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null && this.assertCount === 0) { + if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null) { if (this.pendingAttemptCount > 0) { this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded')); - } else { + } else if (this.assertCount === 0) { this.saveFirstError(new Error('Test finished without running any assertions')); } } else if (this.pendingAssertionCount > 0) { diff --git a/test/test.js b/test/test.js index 9f2e64c57..bf2d0c10b 100644 --- a/test/test.js +++ b/test/test.js @@ -848,6 +848,18 @@ test('try-commit can discard produced result', t => { }); }); +test('try-commit fails when not all assertions were committed/discarded', t => { + return ava(a => { + a.pass(); + return a.try(b => b.pass()); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /not all attempts were committed/); + t.is(result.error.name, 'Error'); + }); +}); + test('try-commit works with values', t => { const testValue1 = 123; const testValue2 = 123; From 7fa7cf58f359aab84abc24d2deba33f17bfa0829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 14:24:44 -0400 Subject: [PATCH 028/105] Add support for macros passed to attempt --- lib/test.js | 121 +++++++++++++++++++++++++++++---------------------- test/test.js | 59 +++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 52 deletions(-) diff --git a/lib/test.js b/lib/test.js index b2f92896a..6b5f9f6b9 100644 --- a/lib/test.js +++ b/lib/test.js @@ -57,67 +57,84 @@ function timeout(ms) { this.timeout(ms); } -function tryTest(title, fn, ...args) { - if (typeof title === 'function') { - args.unshift(fn); - fn = title; - title = undefined; - } - - let committed = false; - let discarded = false; +function tryTest(...args) { + // Few lines dealing with title/macro are taken from runner.js + const specifiedTitle = typeof args[0] === 'string' ? + args.shift() : + undefined; + // Used to make sure we return single object for single implementation function passed. + const singleImplementation = !Array.isArray(args[0]); + const implementations = singleImplementation ? + args.splice(0, 1) : + args.shift(); - this.addPendingAttemptAssertion(); + if (implementations.length === 0) { + throw new TypeError('Expected an implementation.'); + } - const attemptTest = this.forAttempt(title, t => fn(t, ...args)); + const attemptPromises = implementations.map(implementation => { + const title = implementation.title ? + implementation.title(specifiedTitle, ...args) : + specifiedTitle; - return attemptTest.run().then(ret => { - const {passed, error, title, logs} = ret; - const errors = error ? [error] : []; + let committed = false; + let discarded = false; - return { - passed, - errors, - title, - logs, - commit: ({retainLogs = true} = {}) => { - if (committed) { - return; - } + this.addPendingAttemptAssertion(); - if (discarded) { - this.saveFirstError(new Error('Can\'t commit a result that was previously discarded')); - return; - } + const attemptTest = this.forAttempt(title, t => implementation(t, ...args)); - committed = true; - this.countAttemptAssertion({ - inc: attemptTest.assertCount, - commit: true, - result: ret, - retainLogs - }); - }, - discard: ({retainLogs = false} = {}) => { - if (committed) { - this.saveFirstError(new Error('Can\'t discard a result that was previously committed')); - return; - } + return attemptTest.run().then(ret => { + const {passed, error, title, logs} = ret; + const errors = error ? [error] : []; - if (discarded) { - return; + return { + passed, + errors, + title, + logs, + commit: ({retainLogs = true} = {}) => { + if (committed) { + return; + } + + if (discarded) { + this.saveFirstError(new Error('Can\'t commit a result that was previously discarded')); + return; + } + + committed = true; + this.countAttemptAssertion({ + inc: attemptTest.assertCount, + commit: true, + result: ret, + retainLogs + }); + }, + discard: ({retainLogs = false} = {}) => { + if (committed) { + this.saveFirstError(new Error('Can\'t discard a result that was previously committed')); + return; + } + + if (discarded) { + return; + } + + discarded = true; + this.countAttemptAssertion({ + inc: 0, + commit: false, + result: ret, + retainLogs + }); } - - discarded = true; - this.countAttemptAssertion({ - inc: 0, - commit: false, - result: ret, - retainLogs - }); - } - }; + }; + }); }); + + return Promise.all(attemptPromises) + .then(results => singleImplementation ? results[0] : results); } const testMap = new WeakMap(); diff --git a/test/test.js b/test/test.js index bf2d0c10b..570e2df65 100644 --- a/test/test.js +++ b/test/test.js @@ -1169,3 +1169,62 @@ test('try-commit can be discarded', t => { t.is(instance.assertCount, 0); }); }); + +test('try-commit accepts macros', t => { + const macro = b => { + t.is(b.title, ' Title'); + b.pass(); + }; + + macro.title = providedTitle => `${providedTitle ? providedTitle : ''} Title`; + + return ava(a => { + return a + .try(macro) + .then(res => { + t.true(res.passed); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit accepts multiple macros', t => { + const macros = [b => b.pass(), b => b.fail()]; + return ava(a => { + return a.try(macros) + .then(([res1, res2]) => { + t.true(res1.passed); + res1.commit(); + t.false(res2.passed); + res2.discard(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit returns results in the same shape as when implementations are passed', t => { + return ava(a => { + return Promise.all([ + a.try(b => b.pass()).then(results => { + t.match(results, {passed: true}); + results.commit(); + }), + a.try([b => b.pass()]).then(results => { + t.is(results.length, 1); + t.match(results, [{passed: true}]); + results[0].commit(); + }), + a.try([b => b.pass(), b => b.fail()]).then(results => { + t.is(results.length, 2); + t.match(results, [{passed: true}, {passed: false}]); + results[0].commit(); + results[1].discard(); + }) + ]); + }).run().then(result => { + t.true(result.passed); + }); +}); From 95c85850bf958d30e2965b7926fc9b694224d5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 14:31:31 -0400 Subject: [PATCH 029/105] Make sure try-commit follows timeout settings --- lib/test.js | 2 ++ test/test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/test.js b/lib/test.js index 6b5f9f6b9..121f087a1 100644 --- a/lib/test.js +++ b/lib/test.js @@ -357,6 +357,8 @@ class Test { this.addLog(log); } } + + this.refreshTimeout(); } saveFirstError(error) { diff --git a/test/test.js b/test/test.js index 570e2df65..eba523ee7 100644 --- a/test/test.js +++ b/test/test.js @@ -1228,3 +1228,29 @@ test('try-commit returns results in the same shape as when implementations are p t.true(result.passed); }); }); + +test('try-commit abides timeout', t => { + return ava(a => { + a.timeout(10); + return a.try(b => { + b.pass(); + return delay(200); + }).then(result => result.commit()); + }).run().then(result => { + t.is(result.passed, false); + t.match(result.error.message, /timeout/); + }); +}); + +test('try-commit refreshes the timeout on commit/discard', t => { + return ava.cb(a => { + a.timeout(10); + a.plan(3); + setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 5); + setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 10); + setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 15); + setTimeout(() => a.end(), 20); + }).run().then(result => { + t.is(result.passed, true); + }); +}); From b43626d5ae55a1dd83c189cc934b0a6f2737a6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 19:39:24 -0400 Subject: [PATCH 030/105] Get rid of runtime conditional types Instead of this, define _Macro as Macro or UntitledMacro. Defining two versions of _Macro helps TS with types. --- index.d.ts | 133 ++++++++++++++++------------------------------------- 1 file changed, 39 insertions(+), 94 deletions(-) diff --git a/index.d.ts b/index.d.ts index b6196f777..76bf7c6c0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -448,10 +448,9 @@ export type ImplementationResult = PromiseLike | ObservableLike | void; export type Implementation = (t: ExecutionContext) => ImplementationResult; export type CbImplementation = (t: CbExecutionContext) => ImplementationResult; +export type UntitledMacro = (t: ExecutionContext, ...args: Args) => ImplementationResult; /** A reusable test or hook implementation. */ -export interface Macro { - (t: ExecutionContext, ...args: Args): ImplementationResult; - +export type Macro = UntitledMacro & { /** * Implement this function to generate a test (or hook) title whenever this macro is used. `providedTitle` contains * the title provided when the test or hook was declared. Also receives the remaining test arguments. @@ -459,56 +458,32 @@ export interface Macro { title?: (providedTitle: string | undefined, ...args: Args) => string; } +type _Macro = Macro | UntitledMacro; + /** Alias for a single macro, or an array of macros. */ -export type OneOrMoreMacros = Macro | [Macro, ...Macro[]]; +export type OneOrMoreMacros = _Macro | [_Macro, ..._Macro[]]; +export type UntitledCbMacro = (t: CbExecutionContext, ...args: Args) => ImplementationResult /** A reusable test or hook implementation, for tests & hooks declared with the `.cb` modifier. */ export interface CbMacro { (t: CbExecutionContext, ...args: Args): ImplementationResult; title?: (providedTitle: string | undefined, ...args: Args) => string; } -/** Alias for a single macro, or an array of macros, used for tests & hooks declared with the `.cb` modifier. */ -export type OneOrMoreCbMacros = CbMacro | [CbMacro, ...CbMacro[]]; - -/** Infers the types of the additional arguments the macro implementations should be called with. */ -export type InferArgs = - OneOrMore extends Macro ? Args : - OneOrMore extends Macro[] ? Args : - OneOrMore extends CbMacro ? Args : - OneOrMore extends CbMacro[] ? Args : - never; - -export type TitleOrMacro = string | OneOrMoreMacros - -export type MacroOrFirstArg = - TitleOrMacro extends string ? OneOrMoreMacros : - TitleOrMacro extends OneOrMoreMacros ? InferArgs[0] : - never - -export type TitleOrCbMacro = string | OneOrMoreCbMacros - -export type CbMacroOrFirstArg = - TitleOrMacro extends string ? OneOrMoreCbMacros : - TitleOrMacro extends OneOrMoreCbMacros ? InferArgs[0] : - never +type _CbMacro = CbMacro | UntitledCbMacro; -export type RestArgs = - MacroOrFirstArg extends OneOrMoreMacros ? InferArgs : - MacroOrFirstArg extends OneOrMoreCbMacros ? InferArgs : - TitleOrMacro extends OneOrMoreMacros ? Tail> : - TitleOrMacro extends OneOrMoreCbMacros ? Tail> : - never +/** Alias for a single macro, or an array of macros, used for tests & hooks declared with the `.cb` modifier. */ +export type OneOrMoreCbMacros = _CbMacro | [_CbMacro, ..._CbMacro[]]; export interface TestInterface { /** Declare a concurrent test. */ (title: string, implementation: Implementation): void; /** Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void /** Declare a concurrent test that uses one or more macros. The macro is responsible for generating a unique test title. */ - (macro: OneOrMoreMacros<[], Context>): void + (macros: OneOrMoreMacros, ...rest: T): void; /** Declare a hook that is run once, after all tests have passed. */ after: AfterInterface; @@ -545,10 +520,10 @@ export interface AfterInterface { (title: string, implementation: Implementation): void; /** Declare a hook that is run once, after all tests have passed. Additional arguments are passed to the macro. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void; /** Declare a hook that is run once, after all tests have passed. */ - (macro: OneOrMoreMacros<[], Context>): void + (macros: OneOrMoreMacros, ...rest: T): void; /** Declare a hook that is run once, after all tests are done. */ always: AlwaysInterface; @@ -567,10 +542,10 @@ export interface AlwaysInterface { (title: string, implementation: Implementation): void; /** Declare a hook that is run once, after all tests are done. Additional arguments are passed to the macro. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void; /** Declare a hook that is run once, after all tests are done. */ - (macro: OneOrMoreMacros<[], Context>): void + (macros: OneOrMoreMacros, ...rest: T): void; /** Declare a hook that must call `t.end()` when it's done. */ cb: HookCbInterface; @@ -586,10 +561,10 @@ export interface BeforeInterface { (title: string, implementation: Implementation): void; /** Declare a hook that is run once, before all tests. Additional arguments are passed to the macro. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void; /** Declare a hook that is run once, before all tests. */ - (macro: OneOrMoreMacros<[], Context>): void + (macros: OneOrMoreMacros, ...rest: T): void; /** Declare a hook that must call `t.end()` when it's done. */ cb: HookCbInterface; @@ -605,13 +580,13 @@ export interface CbInterface { * Declare a concurrent test that uses one or more macros. The macros must call `t.end()` when they're done. * Additional arguments are passed to the macro. */ - , MoA extends CbMacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreCbMacros, ...rest: T): void; /** * Declare a concurrent test that uses one or more macros. The macros must call `t.end()` when they're done. * The macro is responsible for generating a unique test title. */ - (macro: OneOrMoreCbMacros<[], Context>): void + (macros: OneOrMoreCbMacros, ...rest: T): void; /** Declare a test that is expected to fail. */ failing: CbFailingInterface; @@ -628,13 +603,13 @@ export interface CbFailingInterface { * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done. * Additional arguments are passed to the macro. The test is expected to fail. */ - , MoA extends CbMacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreCbMacros, ...rest: T): void; /** * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done. * The test is expected to fail. */ - (macro: OneOrMoreCbMacros<[], Context>): void + (macros: OneOrMoreCbMacros, ...rest: T): void; only: CbOnlyInterface; skip: CbSkipInterface; @@ -650,13 +625,13 @@ export interface CbOnlyInterface { * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done. * Additional arguments are passed to the macro. Only this test and others declared with `.only()` are run. */ - , MoA extends CbMacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreCbMacros, ...rest: T): void; /** * Declare a test that uses one or more macros. The macros must call `t.end()` when they're done. * Additional arguments are passed to the macro. Only this test and others declared with `.only()` are run. */ - (macro: OneOrMoreCbMacros<[], Context>): void + (macros: OneOrMoreCbMacros, ...rest: T): void; } export interface CbSkipInterface { @@ -664,10 +639,10 @@ export interface CbSkipInterface { (title: string, implementation: CbImplementation): void; /** Skip this test. */ - , MoA extends CbMacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreCbMacros, ...rest: T): void; /** Skip this test. */ - (macro: OneOrMoreCbMacros<[], Context>): void + (macros: OneOrMoreCbMacros, ...rest: T): void; } export interface FailingInterface { @@ -678,13 +653,13 @@ export interface FailingInterface { * Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro. * The test is expected to fail. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void; /** * Declare a concurrent test that uses one or more macros. The macro is responsible for generating a unique test title. * The test is expected to fail. */ - (macro: OneOrMoreMacros<[], Context>): void + (macros: OneOrMoreMacros, ...rest: T): void; only: OnlyInterface; skip: SkipInterface; @@ -701,12 +676,12 @@ export interface HookCbInterface { * Declare a hook that uses one or more macros. The macros must call `t.end()` when they're done. * Additional arguments are passed to the macro. */ - , MoA extends CbMacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreCbMacros, ...rest: T): void; /** * Declare a hook that uses one or more macros. The macros must call `t.end()` when they're done. */ - (macro: OneOrMoreCbMacros<[], Context>): void + (macros: OneOrMoreCbMacros, ...rest: T): void; skip: HookCbSkipInterface; } @@ -719,10 +694,10 @@ export interface HookCbSkipInterface { (title: string, implementation: CbImplementation): void; /** Skip this hook. */ - , MoA extends CbMacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreCbMacros, ...rest: T): void; /** Skip this hook. */ - (macro: OneOrMoreCbMacros<[], Context>): void + (macros: OneOrMoreCbMacros, ...rest: T): void; } export interface HookSkipInterface { @@ -733,10 +708,10 @@ export interface HookSkipInterface { (title: string, implementation: Implementation): void; /** Skip this hook. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void; /** Skip this hook. */ - (macro: OneOrMoreMacros<[], Context>): void + (macros: OneOrMoreMacros, ...rest: T): void; } export interface OnlyInterface { @@ -747,13 +722,13 @@ export interface OnlyInterface { * Declare a test that uses one or more macros. Additional arguments are passed to the macro. * Only this test and others declared with `.only()` are run. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void; /** * Declare a test that uses one or more macros. The macro is responsible for generating a unique test title. * Only this test and others declared with `.only()` are run. */ - (macro: OneOrMoreMacros<[], Context>): void + (macros: OneOrMoreMacros, ...rest: T): void; } export interface SerialInterface { @@ -761,12 +736,12 @@ export interface SerialInterface { (title: string, implementation: Implementation): void; /** Declare a serial test that uses one or more macros. Additional arguments are passed to the macro. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void; /** * Declare a serial test that uses one or more macros. The macro is responsible for generating a unique test title. */ - (macro: OneOrMoreMacros<[], Context>): void + (macros: OneOrMoreMacros, ...rest: T): void; /** Declare a serial hook that is run once, after all tests have passed. */ after: AfterInterface; @@ -796,10 +771,10 @@ export interface SkipInterface { (title: string, implementation: Implementation): void; /** Skip this test. */ - , MoA extends MacroOrFirstArg>(titleOrMacro: ToM, macroOrArg: MoA, ...rest: RestArgs): void; + (title: string, macros: OneOrMoreMacros, ...rest: T): void; /** Skip this test. */ - (macro: OneOrMoreMacros<[], Context>): void + (title: string, macros: OneOrMoreMacros, ...rest: T): void; } export interface TodoDeclaration { @@ -850,33 +825,3 @@ export const todo: TodoDeclaration; /** Meta data associated with the current process. */ export const meta: MetaInterface; - -/* -Tail type from . - -MIT License - -Copyright (c) 2017 Thomas Crockett - -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. -*/ - -/** Get all but the first element of a tuple. */ -export type Tail = - ((...args: T) => any) extends ((head: any, ...tail: infer R) => any) ? R : never; From 226044cf93343cd10707cded3978ff4181b878fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 19:40:45 -0400 Subject: [PATCH 031/105] Type try fn to accept macros --- index.d.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/index.d.ts b/index.d.ts index 76bf7c6c0..9a279ad8e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -380,15 +380,8 @@ export interface TimeoutFn { } export interface TryFn { - ( - title: string, - impl: (t: ExecutionContext, ...args: T) => ImplementationResult, - ...args: T, - ): Promise; - ( - impl: (t: ExecutionContext, ...args: T) => ImplementationResult, - ...args: T - ): Promise; + (title: string, fn: OneOrMoreMacros, ...args: T): Promise; + (fn: OneOrMoreMacros, ...args: T): Promise; skip(...values: Array): void; } From c9643f7522462543f4745ab6a06325c98b8d1189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 19:41:46 -0400 Subject: [PATCH 032/105] Add macro test with parameter inference --- test/ts-types/macros.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/ts-types/macros.ts b/test/ts-types/macros.ts index 89d19e07d..ca0b4dab9 100644 --- a/test/ts-types/macros.ts +++ b/test/ts-types/macros.ts @@ -62,3 +62,14 @@ import test, {ExecutionContext, Macro} from '../..'; t.is(input.length, expected) }, 'bar', 3) } + +// Completely infer parameters +{ + test('has length 3', (t, input, expected) => { + t.is(input.length, expected); + }, 'foo', 3); + + test((t, input, expected) => { + t.is(input.length, expected) + }, 'foo', 3); +} From cc5d4ae17f136e4a7f3ef0eb3736e97c21736156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 17 Mar 2019 20:16:30 -0400 Subject: [PATCH 033/105] Test types of try fn --- test/ts-types/try-commit.ts | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 test/ts-types/try-commit.ts diff --git a/test/ts-types/try-commit.ts b/test/ts-types/try-commit.ts new file mode 100644 index 000000000..de067c8e1 --- /dev/null +++ b/test/ts-types/try-commit.ts @@ -0,0 +1,76 @@ +import test, {ExecutionContext, Macro} from "../.."; + +{ + test("attempt", async t => { + const attempt = await t.try( + (u, a, b) => { + u.is(a.length, b); + }, + "string", + 6 + ); + attempt.commit(); + }); + + test("attempt with title", async t => { + const attempt = await t.try( + "attempt title", + (u, a, b) => { + u.is(a.length, b); + }, + "string", + 6 + ); + attempt.commit(); + }); +} + +{ + const lengthCheck = (t: ExecutionContext, a: string, b: number) => { + t.is(a.length, b); + }; + + test("attempt with helper", async t => { + const attempt = await t.try(lengthCheck, "string", 6); + attempt.commit(); + }); + + test("attempt with title", async t => { + const attempt = await t.try(lengthCheck, "string", 6); + attempt.commit(); + }); +} + +{ + test("all possible variants to pass to t.try", async t => { + // no params + t.try(tt => tt.pass()); + /* fails as expected */ // t.try([]); + t.try([tt => tt.pass()]); + t.try([tt => tt.pass(), tt => tt.fail()]); + + t.try("test", tt => tt.pass()); + /* fails as expected */ // t.try("test", []); + t.try("test", [tt => tt.pass()]); + t.try("test", [tt => tt.pass(), tt => tt.fail()]); + + // some params + t.try((tt, a, b) => tt.is(a.length, b), "hello", 5); + /* fails as expected */ // t.try([], "hello", 5); + t.try([(tt, a, b) => tt.is(a.length, b)], "hello", 5); + t.try([(tt, a, b) => tt.is(a.length, b), (tt, a, b) => tt.is(a.slice(b), "")], "hello", 5); + + t.try("test", (tt, a, b) => tt.is(a.length, b), "hello", 5); + /* fails as expected */ // t.try("test", [], "hello", 5); + t.try("test", [(tt, a, b) => tt.is(a.length, b)], "hello", 5); + t.try("test", [(tt, a, b) => tt.is(a.length, b), (tt, a, b) => tt.is(a.slice(b), "")], "hello", 5); + + // macro with title + const macro1: Macro<[string, number]> = (tt, a, b) => tt.is(a.length, b); + macro1.title = (title, a, b) => `${title ? `${title} `: ''}str: "${a}" with len: "${b}"`; + const macro2: Macro<[string, number]> = (tt, a, b) => tt.is(a.slice(b), ''); + + t.try([macro1, macro2], 'hello', 5); + t.try('title', [macro1, macro2], 'hello', 5); + }); +} From 538487677fe5781845e57e724fbd2a6a943f6304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Apr 2019 03:03:10 -0400 Subject: [PATCH 034/105] Add new metadata type: inline --- lib/runner.js | 1 + lib/test.js | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/runner.js b/lib/runner.js index 35d65125b..d90ba30d8 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -161,6 +161,7 @@ class Runner extends Emittery { todo: false, failing: false, callback: false, + inline: false, always: false }, meta); } diff --git a/lib/test.js b/lib/test.js index 121f087a1..2a5d0d3c5 100644 --- a/lib/test.js +++ b/lib/test.js @@ -245,10 +245,7 @@ class Test { title = title || (this.title + '.A' + attemptId); const opts = Object.assign({}, options, { - metadata: Object.assign({}, options.metadata, { - callback: false, - failing: false - }), + metadata: Object.assign({}, options.metadata, {inline: true}), fn, title }); @@ -257,13 +254,17 @@ class Test { } bindEndCallback() { - if (this.metadata.callback) { + if (!this.metadata.inline && this.metadata.callback) { return (error, stack) => { this.endCallback(error, stack); }; } - throw new Error('`t.end()`` is not supported in this context. To use `t.end()` as a callback, you must use "callback mode" via `test.cb(testName, fn)`'); + if (this.metadata.inline) { + throw new Error('`t.end()` is not supported in this context. You have to return promise for asynchronous attempt.'); + } else { + throw new Error('`t.end()` is not supported in this context. To use `t.end()` as a callback, you must use "callback mode" via `test.cb(testName, fn)`'); + } } endCallback(error, stack) { @@ -532,7 +533,7 @@ class Test { promise = Promise.resolve(result.retval); } - if (this.metadata.callback) { + if (!this.metadata.inline && this.metadata.callback) { if (returnedObservable || returnedPromise) { const asyncType = returnedObservable ? 'observables' : 'promises'; this.saveFirstError(new Error(`Do not return ${asyncType} from tests declared via \`test.cb(...)\`, if you want to return a promise simply declare the test via \`test(...)\``)); @@ -614,7 +615,7 @@ class Test { let error = this.assertError; let passed = !error; - if (this.metadata.failing) { + if (!this.metadata.inline && this.metadata.failing) { passed = !passed; if (passed) { From 786a446ed1f82cb17f9945668843c4af97a6e3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Apr 2019 03:36:57 -0400 Subject: [PATCH 035/105] Fix complicated if-else statement It seems that there were some tests failing when nested if-else was introduced. To fix, copy of the same conditions as for assertCount is created. --- lib/test.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/test.js b/lib/test.js index 2a5d0d3c5..c54e35529 100644 --- a/lib/test.js +++ b/lib/test.js @@ -426,12 +426,10 @@ class Test { verifyAssertions() { if (!this.assertError) { - if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null) { - if (this.pendingAttemptCount > 0) { - this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded')); - } else if (this.assertCount === 0) { - this.saveFirstError(new Error('Test finished without running any assertions')); - } + if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null && this.pendingAttemptCount > 0) { + this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded')); + } else if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null && this.assertCount === 0) { + this.saveFirstError(new Error('Test finished without running any assertions')); } else if (this.pendingAssertionCount > 0) { this.saveFirstError(new Error('Test finished, but an assertion is still pending')); } From 6533c76ee32cdedad7cbb2062074e2ae27f7c02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Apr 2019 03:38:08 -0400 Subject: [PATCH 036/105] Fix test to check assertCount instead of attemptCount --- test/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index eba523ee7..3b6cc2803 100644 --- a/test/test.js +++ b/test/test.js @@ -896,7 +896,7 @@ test('try-commit is properly counted', t => { return instance.run().then(result => { t.true(result.passed); - t.is(instance.attemptCount, 3); + t.is(instance.assertCount, 3); }); }); From 42f4b7c78509725365e876c91d64620f7254cead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Apr 2019 04:11:01 -0400 Subject: [PATCH 037/105] Add test checking that .end() is not allowed inside try-commit --- test/test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/test.js b/test/test.js index 3b6cc2803..bbb962351 100644 --- a/test/test.js +++ b/test/test.js @@ -1150,6 +1150,27 @@ test('try-commit works with failing callback test', t => { }); }); +test('try-commit does not allow to use .end() in attempt when parent is callback test', t => { + return ava.cb(a => { + a + .try(b => { + b.pass(); + b.end(); + }) + .then(res => { + res.commit(); + a.end(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Error thrown in test/); + t.is(result.error.name, 'AssertionError'); + t.match(result.error.values[0].formatted, /t\.end.*not supported/); + t.match(result.error.values[0].formatted, /return promise for asynchronous attempt/); + }); +}); + test('try-commit can be discarded', t => { const instance = ava(a => { const p = a.try(b => { From 6b4dc2137bead040ccf309e4df6869e0654b8d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Apr 2019 04:25:48 -0400 Subject: [PATCH 038/105] Move ava test initializer into helper file --- test/helper/ava-test.js | 57 +++++++++++++++++++++++++++++++++++++++++ test/test.js | 55 +-------------------------------------- 2 files changed, 58 insertions(+), 54 deletions(-) create mode 100644 test/helper/ava-test.js diff --git a/test/helper/ava-test.js b/test/helper/ava-test.js new file mode 100644 index 000000000..f51e3ed8a --- /dev/null +++ b/test/helper/ava-test.js @@ -0,0 +1,57 @@ +const Test = require('../../lib/test'); + +class ContextRef { + constructor() { + this.value = {}; + } + + get() { + return this.value; + } + + set(newValue) { + this.value = newValue; + } +} + +function ava(fn, contextRef) { + return new Test({ + contextRef: contextRef || new ContextRef(), + failWithoutAssertions: true, + fn, + metadata: {type: 'test', callback: false}, + title: 'test' + }); +} + +ava.failing = (fn, contextRef) => { + return new Test({ + contextRef: contextRef || new ContextRef(), + failWithoutAssertions: true, + fn, + metadata: {type: 'test', callback: false, failing: true}, + title: 'test.failing' + }); +}; + +ava.cb = (fn, contextRef) => { + return new Test({ + contextRef: contextRef || new ContextRef(), + failWithoutAssertions: true, + fn, + metadata: {type: 'test', callback: true}, + title: 'test.cb' + }); +}; + +ava.cb.failing = (fn, contextRef) => { + return new Test({ + contextRef: contextRef || new ContextRef(), + failWithoutAssertions: true, + fn, + metadata: {type: 'test', callback: true, failing: true}, + title: 'test.cb.failing' + }); +}; + +exports.ava = ava; diff --git a/test/test.js b/test/test.js index bbb962351..3b09df155 100644 --- a/test/test.js +++ b/test/test.js @@ -9,63 +9,10 @@ const delay = require('delay'); const snapshotManager = require('../lib/snapshot-manager'); const Test = require('../lib/test'); const HelloMessage = require('./fixture/hello-message'); +const {ava} = require('./helper/ava-test'); const failingTestHint = 'Test was expected to fail, but succeeded, you should stop marking the test as failing'; -class ContextRef { - constructor() { - this.value = {}; - } - - get() { - return this.value; - } - - set(newValue) { - this.value = newValue; - } -} - -function ava(fn, contextRef) { - return new Test({ - contextRef: contextRef || new ContextRef(), - failWithoutAssertions: true, - fn, - metadata: {type: 'test', callback: false}, - title: 'test' - }); -} - -ava.failing = (fn, contextRef) => { - return new Test({ - contextRef: contextRef || new ContextRef(), - failWithoutAssertions: true, - fn, - metadata: {type: 'test', callback: false, failing: true}, - title: 'test.failing' - }); -}; - -ava.cb = (fn, contextRef) => { - return new Test({ - contextRef: contextRef || new ContextRef(), - failWithoutAssertions: true, - fn, - metadata: {type: 'test', callback: true}, - title: 'test.cb' - }); -}; - -ava.cb.failing = (fn, contextRef) => { - return new Test({ - contextRef: contextRef || new ContextRef(), - failWithoutAssertions: true, - fn, - metadata: {type: 'test', callback: true, failing: true}, - title: 'test.cb.failing' - }); -}; - test('run test', t => { return ava(a => { a.fail(); From 92ff5248205375e85d37125295d2b6982db63594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Apr 2019 04:30:07 -0400 Subject: [PATCH 039/105] Move try-commit tests into separate file --- test/test-try-commit.js | 486 ++++++++++++++++++++++++++++++++++++++++ test/test.js | 478 --------------------------------------- 2 files changed, 486 insertions(+), 478 deletions(-) create mode 100644 test/test-try-commit.js diff --git a/test/test-try-commit.js b/test/test-try-commit.js new file mode 100644 index 000000000..31a34bb82 --- /dev/null +++ b/test/test-try-commit.js @@ -0,0 +1,486 @@ +'use strict'; +require('../lib/chalk').set(); +require('../lib/worker/options').set({color: false}); + +const {test} = require('tap'); +const delay = require('delay'); +const Test = require('../lib/test'); +const {ava} = require('./helper/ava-test'); + +test('try-commit are present', t => { + return ava(a => { + a.pass(); + t.type(a.try, Function); + }).run(); +}); + +test('try-commit works', t => { + const instance = ava(a => { + return a + .try(b => b.pass()) + .then(res => { + t.true(res.passed); + res.commit(); + }); + }); + + return instance.run() + .then(result => { + t.true(result.passed); + t.is(instance.assertCount, 1); + }); +}); + +test('try-commit discards failed attempt', t => { + return ava(a => { + return a + .try(b => b.fail()) + .then(res => res.discard()) + .then(() => a.pass()); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit can discard produced result', t => { + return ava(a => { + return a + .try(b => b.pass()) + .then(res => { + res.discard(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /without running any assertions/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit fails when not all assertions were committed/discarded', t => { + return ava(a => { + a.pass(); + return a.try(b => b.pass()); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /not all attempts were committed/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit works with values', t => { + const testValue1 = 123; + const testValue2 = 123; + + return ava(a => { + return a + .try((b, val1, val2) => { + b.is(val1, val2); + }, testValue1, testValue2) + .then(res => { + t.true(res.passed); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit is properly counted', t => { + const instance = ava(a => { + return a + .try(b => { + b.is(1, 1); + b.is(2, 2); + b.pass(); + }) + .then(res => { + t.true(res.passed); + t.is(instance.pendingAttemptCount, 1); + res.commit(); + t.is(instance.pendingAttemptCount, 0); + }); + }); + + return instance.run().then(result => { + t.true(result.passed); + t.is(instance.assertCount, 3); + }); +}); + +test('try-commit is properly counted multiple', t => { + const instance = ava(a => { + return Promise.all([ + a.try(b => b.pass()), + a.try(b => b.pass()), + a.try(b => b.pass()) + ]) + .then(([res1, res2, res3]) => { + t.is(instance.pendingAttemptCount, 3); + res1.commit(); + res2.discard(); + res3.commit(); + t.is(instance.pendingAttemptCount, 0); + }); + }); + + return instance.run().then(result => { + t.true(result.passed); + t.is(instance.assertCount, 2); + }); +}); + +test('try-commit goes as many levels', t => { + t.plan(5); + const instance = ava(a => { + t.ok(a.try); + return a + .try(b => { + t.ok(b.try); + return b + .try(c => { + t.ok(c.try); + c.pass(); + }) + .then(res => { + res.commit(); + }); + }) + .then(res => { + res.commit(); + }); + }); + + return instance.run().then(result => { + t.true(result.passed); + t.is(instance.assertCount, 1); + }); +}); + +test('try-commit fails when not committed', t => { + return ava(a => { + return a + .try(b => b.pass()) + .then(res => { + t.true(res.passed); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /not all attempts were committed/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit fails when no assertions inside try', t => { + return ava(a => { + return a + .try(() => {}) + .then(res => { + t.false(res.passed); + t.ok(res.errors); + t.is(res.errors.length, 1); + const error = res.errors[0]; + t.match(error.message, /Test finished without running any assertions/); + t.is(error.name, 'Error'); + res.commit(); + }); + }).run().then(result => { + t.false(result.passed); + }); +}); + +test('try-commit fails when no assertions inside multiple try', t => { + return ava(a => { + return Promise.all([ + a.try(b => b.pass()).then(res1 => { + res1.commit(); + t.true(res1.passed); + }), + a.try(() => {}).then(res2 => { + t.false(res2.passed); + t.ok(res2.errors); + t.is(res2.errors.length, 1); + const error = res2.errors[0]; + t.match(error.message, /Test finished without running any assertions/); + t.is(error.name, 'Error'); + res2.commit(); + }) + ]); + }).run().then(result => { + t.false(result.passed); + }); +}); + +test('test fails when try-commit committed to failed state', t => { + return ava(a => { + return a.try(b => b.fail()).then(res => { + t.false(res.passed); + res.commit(); + }); + }).run().then(result => { + t.false(result.passed); + }); +}); + +test('try-commit has proper titles, when going in depth and width', t => { + t.plan(6); + return new Test({ + fn(a) { + t.is(a.title, 'foo'); + + return Promise.all([ + a.try(b => { + t.is(b.title, 'foo.A0'); + return Promise.all([ + b.try(c => t.is(c.title, 'foo.A0.A0')), + b.try(c => t.is(c.title, 'foo.A0.A1')) + ]); + }), + a.try(b => t.is(b.title, 'foo.A1')), + a.try(b => t.is(b.title, 'foo.A2')) + ]); + }, + failWithoutAssertions: false, + metadata: {type: 'test', callback: false}, + title: 'foo' + }).run(); +}); + +test('try-commit does not fail when calling commit twice', t => { + return ava(a => { + return a.try(b => b.pass()).then(res => { + res.commit(); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + t.false(result.error); + }); +}); + +test('try-commit does not fail when calling discard twice', t => { + return ava(a => { + return a.try(b => b.pass()).then(res => { + res.discard(); + res.discard(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Test finished without running any assertions/); + t.is(result.error.name, 'Error'); + }); +}); + +test('try-commit allows planning inside the try', t => { + return ava(a => { + return a.try(b => { + b.plan(3); + + b.pass(); + b.pass(); + b.pass(); + }).then(res => { + t.true(res.passed); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit fails when plan is not reached inside the try', t => { + return ava(a => { + return a.try(b => { + b.plan(3); + + b.pass(); + b.pass(); + }).then(res => { + t.false(res.passed); + res.commit(); + }); + }).run().then(result => { + t.false(result.passed); + }); +}); + +test('try-commit passes with failing test', t => { + return ava.failing(a => { + return a + .try(b => b.fail()) + .then(res => { + t.false(res.passed); + t.ok(res.errors); + t.is(res.errors.length, 1); + const error = res.errors[0]; + t.match(error.message, /Test failed via `t\.fail\(\)`/); + t.is(error.name, 'AssertionError'); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit works with callback test', t => { + return ava.cb(a => { + a + .try(b => b.pass()) + .then(res => { + res.commit(); + a.end(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit works with failing callback test', t => { + return ava.cb.failing(a => { + a + .try(b => b.fail()) + .then(res => { + t.false(res.passed); + t.ok(res.errors); + t.is(res.errors.length, 1); + const error = res.errors[0]; + t.match(error.message, /Test failed via `t\.fail\(\)`/); + t.is(error.name, 'AssertionError'); + res.commit(); + }) + .then(() => { + a.end(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit does not allow to use .end() in attempt when parent is callback test', t => { + return ava.cb(a => { + a + .try(b => { + b.pass(); + b.end(); + }) + .then(res => { + res.commit(); + a.end(); + }); + }).run().then(result => { + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Error thrown in test/); + t.is(result.error.name, 'AssertionError'); + t.match(result.error.values[0].formatted, /t\.end.*not supported/); + t.match(result.error.values[0].formatted, /return promise for asynchronous attempt/); + }); +}); + +test('try-commit can be discarded', t => { + const instance = ava(a => { + const p = a.try(b => { + return new Promise(resolve => setTimeout(resolve, 500)) + .then(() => b.pass()); + }); + + p.discard(); + + return p.then(res => { + t.is(res, null); + }); + }); + + return instance.run().then(result => { + t.false(result.passed); + t.is(instance.assertCount, 0); + }); +}); + +test('try-commit accepts macros', t => { + const macro = b => { + t.is(b.title, ' Title'); + b.pass(); + }; + + macro.title = providedTitle => `${providedTitle ? providedTitle : ''} Title`; + + return ava(a => { + return a + .try(macro) + .then(res => { + t.true(res.passed); + res.commit(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit accepts multiple macros', t => { + const macros = [b => b.pass(), b => b.fail()]; + return ava(a => { + return a.try(macros) + .then(([res1, res2]) => { + t.true(res1.passed); + res1.commit(); + t.false(res2.passed); + res2.discard(); + }); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit returns results in the same shape as when implementations are passed', t => { + return ava(a => { + return Promise.all([ + a.try(b => b.pass()).then(results => { + t.match(results, {passed: true}); + results.commit(); + }), + a.try([b => b.pass()]).then(results => { + t.is(results.length, 1); + t.match(results, [{passed: true}]); + results[0].commit(); + }), + a.try([b => b.pass(), b => b.fail()]).then(results => { + t.is(results.length, 2); + t.match(results, [{passed: true}, {passed: false}]); + results[0].commit(); + results[1].discard(); + }) + ]); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('try-commit abides timeout', t => { + return ava(a => { + a.timeout(10); + return a.try(b => { + b.pass(); + return delay(200); + }).then(result => result.commit()); + }).run().then(result => { + t.is(result.passed, false); + t.match(result.error.message, /timeout/); + }); +}); + +test('try-commit refreshes the timeout on commit/discard', t => { + return ava.cb(a => { + a.timeout(10); + a.plan(3); + setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 5); + setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 10); + setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 15); + setTimeout(() => a.end(), 20); + }).run().then(result => { + t.is(result.passed, true); + }); +}); diff --git a/test/test.js b/test/test.js index 3b09df155..4d86a9f05 100644 --- a/test/test.js +++ b/test/test.js @@ -744,481 +744,3 @@ test('timeout is refreshed on assert', t => { t.is(result.passed, true); }); }); - -test('try-commit are present', t => { - return ava(a => { - a.pass(); - t.type(a.try, Function); - }).run(); -}); - -test('try-commit works', t => { - const instance = ava(a => { - return a - .try(b => b.pass()) - .then(res => { - t.true(res.passed); - res.commit(); - }); - }); - - return instance.run() - .then(result => { - t.true(result.passed); - t.is(instance.assertCount, 1); - }); -}); - -test('try-commit discards failed attempt', t => { - return ava(a => { - return a - .try(b => b.fail()) - .then(res => res.discard()) - .then(() => a.pass()); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit can discard produced result', t => { - return ava(a => { - return a - .try(b => b.pass()) - .then(res => { - res.discard(); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /without running any assertions/); - t.is(result.error.name, 'Error'); - }); -}); - -test('try-commit fails when not all assertions were committed/discarded', t => { - return ava(a => { - a.pass(); - return a.try(b => b.pass()); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /not all attempts were committed/); - t.is(result.error.name, 'Error'); - }); -}); - -test('try-commit works with values', t => { - const testValue1 = 123; - const testValue2 = 123; - - return ava(a => { - return a - .try((b, val1, val2) => { - b.is(val1, val2); - }, testValue1, testValue2) - .then(res => { - t.true(res.passed); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit is properly counted', t => { - const instance = ava(a => { - return a - .try(b => { - b.is(1, 1); - b.is(2, 2); - b.pass(); - }) - .then(res => { - t.true(res.passed); - t.is(instance.pendingAttemptCount, 1); - res.commit(); - t.is(instance.pendingAttemptCount, 0); - }); - }); - - return instance.run().then(result => { - t.true(result.passed); - t.is(instance.assertCount, 3); - }); -}); - -test('try-commit is properly counted multiple', t => { - const instance = ava(a => { - return Promise.all([ - a.try(b => b.pass()), - a.try(b => b.pass()), - a.try(b => b.pass()) - ]) - .then(([res1, res2, res3]) => { - t.is(instance.pendingAttemptCount, 3); - res1.commit(); - res2.discard(); - res3.commit(); - t.is(instance.pendingAttemptCount, 0); - }); - }); - - return instance.run().then(result => { - t.true(result.passed); - t.is(instance.assertCount, 2); - }); -}); - -test('try-commit goes as many levels', t => { - t.plan(5); - const instance = ava(a => { - t.ok(a.try); - return a - .try(b => { - t.ok(b.try); - return b - .try(c => { - t.ok(c.try); - c.pass(); - }) - .then(res => { - res.commit(); - }); - }) - .then(res => { - res.commit(); - }); - }); - - return instance.run().then(result => { - t.true(result.passed); - t.is(instance.assertCount, 1); - }); -}); - -test('try-commit fails when not committed', t => { - return ava(a => { - return a - .try(b => b.pass()) - .then(res => { - t.true(res.passed); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /not all attempts were committed/); - t.is(result.error.name, 'Error'); - }); -}); - -test('try-commit fails when no assertions inside try', t => { - return ava(a => { - return a - .try(() => {}) - .then(res => { - t.false(res.passed); - t.ok(res.errors); - t.is(res.errors.length, 1); - const error = res.errors[0]; - t.match(error.message, /Test finished without running any assertions/); - t.is(error.name, 'Error'); - res.commit(); - }); - }).run().then(result => { - t.false(result.passed); - }); -}); - -test('try-commit fails when no assertions inside multiple try', t => { - return ava(a => { - return Promise.all([ - a.try(b => b.pass()).then(res1 => { - res1.commit(); - t.true(res1.passed); - }), - a.try(() => {}).then(res2 => { - t.false(res2.passed); - t.ok(res2.errors); - t.is(res2.errors.length, 1); - const error = res2.errors[0]; - t.match(error.message, /Test finished without running any assertions/); - t.is(error.name, 'Error'); - res2.commit(); - }) - ]); - }).run().then(result => { - t.false(result.passed); - }); -}); - -test('test fails when try-commit committed to failed state', t => { - return ava(a => { - return a.try(b => b.fail()).then(res => { - t.false(res.passed); - res.commit(); - }); - }).run().then(result => { - t.false(result.passed); - }); -}); - -test('try-commit has proper titles, when going in depth and width', t => { - t.plan(6); - return new Test({ - fn(a) { - t.is(a.title, 'foo'); - - return Promise.all([ - a.try(b => { - t.is(b.title, 'foo.A0'); - return Promise.all([ - b.try(c => t.is(c.title, 'foo.A0.A0')), - b.try(c => t.is(c.title, 'foo.A0.A1')) - ]); - }), - a.try(b => t.is(b.title, 'foo.A1')), - a.try(b => t.is(b.title, 'foo.A2')) - ]); - }, - failWithoutAssertions: false, - metadata: {type: 'test', callback: false}, - title: 'foo' - }).run(); -}); - -test('try-commit does not fail when calling commit twice', t => { - return ava(a => { - return a.try(b => b.pass()).then(res => { - res.commit(); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - t.false(result.error); - }); -}); - -test('try-commit does not fail when calling discard twice', t => { - return ava(a => { - return a.try(b => b.pass()).then(res => { - res.discard(); - res.discard(); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /Test finished without running any assertions/); - t.is(result.error.name, 'Error'); - }); -}); - -test('try-commit allows planning inside the try', t => { - return ava(a => { - return a.try(b => { - b.plan(3); - - b.pass(); - b.pass(); - b.pass(); - }).then(res => { - t.true(res.passed); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit fails when plan is not reached inside the try', t => { - return ava(a => { - return a.try(b => { - b.plan(3); - - b.pass(); - b.pass(); - }).then(res => { - t.false(res.passed); - res.commit(); - }); - }).run().then(result => { - t.false(result.passed); - }); -}); - -test('try-commit passes with failing test', t => { - return ava.failing(a => { - return a - .try(b => b.fail()) - .then(res => { - t.false(res.passed); - t.ok(res.errors); - t.is(res.errors.length, 1); - const error = res.errors[0]; - t.match(error.message, /Test failed via `t\.fail\(\)`/); - t.is(error.name, 'AssertionError'); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit works with callback test', t => { - return ava.cb(a => { - a - .try(b => b.pass()) - .then(res => { - res.commit(); - a.end(); - }); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit works with failing callback test', t => { - return ava.cb.failing(a => { - a - .try(b => b.fail()) - .then(res => { - t.false(res.passed); - t.ok(res.errors); - t.is(res.errors.length, 1); - const error = res.errors[0]; - t.match(error.message, /Test failed via `t\.fail\(\)`/); - t.is(error.name, 'AssertionError'); - res.commit(); - }) - .then(() => { - a.end(); - }); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit does not allow to use .end() in attempt when parent is callback test', t => { - return ava.cb(a => { - a - .try(b => { - b.pass(); - b.end(); - }) - .then(res => { - res.commit(); - a.end(); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /Error thrown in test/); - t.is(result.error.name, 'AssertionError'); - t.match(result.error.values[0].formatted, /t\.end.*not supported/); - t.match(result.error.values[0].formatted, /return promise for asynchronous attempt/); - }); -}); - -test('try-commit can be discarded', t => { - const instance = ava(a => { - const p = a.try(b => { - return new Promise(resolve => setTimeout(resolve, 500)) - .then(() => b.pass()); - }); - - p.discard(); - - return p.then(res => { - t.is(res, null); - }); - }); - - return instance.run().then(result => { - t.false(result.passed); - t.is(instance.assertCount, 0); - }); -}); - -test('try-commit accepts macros', t => { - const macro = b => { - t.is(b.title, ' Title'); - b.pass(); - }; - - macro.title = providedTitle => `${providedTitle ? providedTitle : ''} Title`; - - return ava(a => { - return a - .try(macro) - .then(res => { - t.true(res.passed); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit accepts multiple macros', t => { - const macros = [b => b.pass(), b => b.fail()]; - return ava(a => { - return a.try(macros) - .then(([res1, res2]) => { - t.true(res1.passed); - res1.commit(); - t.false(res2.passed); - res2.discard(); - }); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit returns results in the same shape as when implementations are passed', t => { - return ava(a => { - return Promise.all([ - a.try(b => b.pass()).then(results => { - t.match(results, {passed: true}); - results.commit(); - }), - a.try([b => b.pass()]).then(results => { - t.is(results.length, 1); - t.match(results, [{passed: true}]); - results[0].commit(); - }), - a.try([b => b.pass(), b => b.fail()]).then(results => { - t.is(results.length, 2); - t.match(results, [{passed: true}, {passed: false}]); - results[0].commit(); - results[1].discard(); - }) - ]); - }).run().then(result => { - t.true(result.passed); - }); -}); - -test('try-commit abides timeout', t => { - return ava(a => { - a.timeout(10); - return a.try(b => { - b.pass(); - return delay(200); - }).then(result => result.commit()); - }).run().then(result => { - t.is(result.passed, false); - t.match(result.error.message, /timeout/); - }); -}); - -test('try-commit refreshes the timeout on commit/discard', t => { - return ava.cb(a => { - a.timeout(10); - a.plan(3); - setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 5); - setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 10); - setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 15); - setTimeout(() => a.end(), 20); - }).run().then(result => { - t.is(result.passed, true); - }); -}); From 19c8d8825f15ebcb1e2bfd87f9378ebad6748b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sat, 6 Apr 2019 15:17:55 -0400 Subject: [PATCH 040/105] Check t.try is properly bound --- test/test-try-commit.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 31a34bb82..7a01bb143 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -31,6 +31,16 @@ test('try-commit works', t => { }); }); +test('try-commit is bound', t => { + return ava(a => { + const {try: tryFn} = a; + return tryFn(b => b.pass()) + .then(res => res.commit()); + }).run().then(result => { + t.true(result.passed); + }); +}); + test('try-commit discards failed attempt', t => { return ava(a => { return a From fa7204c30c8943c19357a216aaba9facfc7e5e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 8 Apr 2019 01:42:40 -0400 Subject: [PATCH 041/105] Make sure inline test cannot access context --- lib/test.js | 8 ++++++-- test/test-try-commit.js | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index c54e35529..474efdcb4 100644 --- a/lib/test.js +++ b/lib/test.js @@ -179,11 +179,15 @@ class ExecutionContext { } get context() { - return testMap.get(this).contextRef.get(); + const test = testMap.get(this); + return test.metadata.inline ? undefined : test.contextRef.get(); } set context(context) { - testMap.get(this).contextRef.set(context); + const test = testMap.get(this); + if (!test.metadata.inline) { + test.contextRef.set(context); + } } _throwsArgStart(assertion, file, line) { diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 7a01bb143..9733db196 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -7,6 +7,22 @@ const delay = require('delay'); const Test = require('../lib/test'); const {ava} = require('./helper/ava-test'); +const contextAva = fn => new Test({ + contextRef: { + val: {foo: 'bar'}, + get() { + return this.val; + }, + set(newVal) { + this.val = newVal; + } + }, + failWithoutAssertions: true, + metadata: {type: 'test'}, + title: 'foo', + fn +}); + test('try-commit are present', t => { return ava(a => { a.pass(); @@ -494,3 +510,30 @@ test('try-commit refreshes the timeout on commit/discard', t => { t.is(result.passed, true); }); }); + +test('try-commit cannot access parent test context', t => { + return contextAva(a => { + return a.try(b => { + b.pass(); + const ctx = b.context; + t.is(ctx, undefined); + }).then(res => res.commit()); + }).run().then(result => { + t.is(result.passed, true); + }); +}); + +test('try-commit cannot set parent test context', t => { + return contextAva(a => { + t.strictDeepEqual(a.context, {foo: 'bar'}); + return a.try(b => { + b.pass(); + b.context = {bar: 'foo'}; + }).then(res => { + res.commit(); + t.strictDeepEqual(a.context, {foo: 'bar'}); + }); + }).run().then(result => { + t.is(result.passed, true); + }); +}); From 23fe80c0f7b86b5c6102a3bd4668b0432064c65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Tue, 23 Apr 2019 17:26:55 -0400 Subject: [PATCH 042/105] Fix exported name of attempt function --- lib/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index 517a51524..419b8646a 100644 --- a/lib/test.js +++ b/lib/test.js @@ -69,7 +69,7 @@ class ExecutionContext extends assert.Assertions { test.timeout(ms); }; - this.tryTest = (...args) => { + this.try = (...args) => { // Few lines dealing with title/macro are taken from runner.js const specifiedTitle = typeof args[0] === 'string' ? args.shift() : From dc3060f4f1878027050338b26c563c30a2d45e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Tue, 23 Apr 2019 17:27:41 -0400 Subject: [PATCH 043/105] Use async/await in try implementation --- lib/test.js | 107 ++++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/lib/test.js b/lib/test.js index 419b8646a..9b6b47267 100644 --- a/lib/test.js +++ b/lib/test.js @@ -69,7 +69,7 @@ class ExecutionContext extends assert.Assertions { test.timeout(ms); }; - this.try = (...args) => { + this.try = async (...args) => { // Few lines dealing with title/macro are taken from runner.js const specifiedTitle = typeof args[0] === 'string' ? args.shift() : @@ -84,8 +84,8 @@ class ExecutionContext extends assert.Assertions { throw new TypeError('Expected an implementation.'); } - const attemptPromises = implementations.map(implementation => { - const title = implementation.title ? + const attemptPromises = implementations.map(async implementation => { + const attemptTitle = implementation.title ? implementation.title(specifiedTitle, ...args) : specifiedTitle; @@ -94,60 +94,59 @@ class ExecutionContext extends assert.Assertions { this.addPendingAttemptAssertion(); - const attemptTest = test.forAttempt(title, t => implementation(t, ...args)); - - return attemptTest.run().then(ret => { - const {passed, error, title, logs} = ret; - const errors = error ? [error] : []; - - return { - passed, - errors, - title, - logs, - commit: ({retainLogs = true} = {}) => { - if (committed) { - return; - } - - if (discarded) { - test.saveFirstError(new Error('Can\'t commit a result that was previously discarded')); - return; - } - - committed = true; - test.countAttemptAssertion({ - inc: attemptTest.assertCount, - commit: true, - result: ret, - retainLogs - }); - }, - discard: ({retainLogs = false} = {}) => { - if (committed) { - test.saveFirstError(new Error('Can\'t discard a result that was previously committed')); - return; - } - - if (discarded) { - return; - } - - discarded = true; - test.countAttemptAssertion({ - inc: 0, - commit: false, - result: ret, - retainLogs - }); + const attemptTest = test.forAttempt(attemptTitle, t => implementation(t, ...args)); + + const ret = await attemptTest.run(); + const {passed, error, title, logs} = ret; + const errors = error ? [error] : []; + + return { + passed, + errors, + title, + logs, + commit: ({retainLogs = true} = {}) => { + if (committed) { + return; + } + + if (discarded) { + test.saveFirstError(new Error('Can\'t commit a result that was previously discarded')); + return; + } + + committed = true; + test.countAttemptAssertion({ + inc: attemptTest.assertCount, + commit: true, + result: ret, + retainLogs + }); + }, + discard: ({retainLogs = false} = {}) => { + if (committed) { + test.saveFirstError(new Error('Can\'t discard a result that was previously committed')); + return; } - }; - }); + + if (discarded) { + return; + } + + discarded = true; + test.countAttemptAssertion({ + inc: 0, + commit: false, + result: ret, + retainLogs + }); + } + }; }); - return Promise.all(attemptPromises) - .then(results => singleImplementation ? results[0] : results); - } + const results = await Promise.all(attemptPromises); + return singleImplementation ? results[0] : results; + }; } get end() { From be19cb8a94b2b5c23bc4b601de4d69905fd03936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Tue, 23 Apr 2019 17:29:58 -0400 Subject: [PATCH 044/105] Use object spread instead of Object.assign --- lib/test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index 9b6b47267..ccedb5ce7 100644 --- a/lib/test.js +++ b/lib/test.js @@ -229,11 +229,12 @@ class Test { const attemptId = this.attemptCount++; title = title || (this.title + '.A' + attemptId); - const opts = Object.assign({}, options, { - metadata: Object.assign({}, options.metadata, {inline: true}), + const opts = { + ...options, + metadata: {...options.metadata, inline: true}, fn, title - }); + }; return new Test(opts); }; } From ea651415ee0256f242e450546ece57cf8ad83c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Tue, 23 Apr 2019 17:58:28 -0400 Subject: [PATCH 045/105] Fix error where `this` was used instead of `test` --- lib/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index ccedb5ce7..8ac1839f2 100644 --- a/lib/test.js +++ b/lib/test.js @@ -92,7 +92,7 @@ class ExecutionContext extends assert.Assertions { let committed = false; let discarded = false; - this.addPendingAttemptAssertion(); + test.addPendingAttemptAssertion(); const attemptTest = test.forAttempt(attemptTitle, t => implementation(t, ...args)); From 87603f559256f9d6595257eaf26e027a99fddfc9 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 31 May 2019 15:37:58 +0200 Subject: [PATCH 046/105] Remove TryFn#skip() from type definition It's not implemented, but it wouldn't make sense anyway. `t.try()` returns an object that must be acted upon, so it's unclear effect `t.try.skip()` should have. --- index.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index e9ee7a1a3..218f23823 100644 --- a/index.d.ts +++ b/index.d.ts @@ -394,8 +394,6 @@ export interface TimeoutFn { export interface TryFn { (title: string, fn: OneOrMoreMacros, ...args: T): Promise; (fn: OneOrMoreMacros, ...args: T): Promise; - - skip(...values: Array): void; } export class AssertionError extends Error { From 38c5bee66ee563d8e8497aa0885e59a35b30b284 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 31 May 2019 15:44:22 +0200 Subject: [PATCH 047/105] Change AssertionError to an interface We don't actually export this constructor. --- index.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 218f23823..461092436 100644 --- a/index.d.ts +++ b/index.d.ts @@ -396,8 +396,7 @@ export interface TryFn { (fn: OneOrMoreMacros, ...args: T): Promise; } -export class AssertionError extends Error { -} +export interface AssertionError extends Error {} export interface AttemptResult { /** From 8a6de553616447332b8381a85fde360e66905bba Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 31 May 2019 15:49:28 +0200 Subject: [PATCH 048/105] Tweak type definition & comments --- index.d.ts | 55 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/index.d.ts b/index.d.ts index 461092436..560a31592 100644 --- a/index.d.ts +++ b/index.d.ts @@ -23,6 +23,13 @@ export type ThrowsExpectation = { name?: string; }; +export type CommitDiscardOptions = { + /** + * Whether the logs should be included in those of the parent test. + */ + retainLogs?: boolean +} + /** Options that can be passed to the `t.snapshot()` assertion. */ export type SnapshotOptions = { /** If provided and not an empty string, used to select the snapshot to compare the `expected` value against. */ @@ -392,49 +399,53 @@ export interface TimeoutFn { } export interface TryFn { - (title: string, fn: OneOrMoreMacros, ...args: T): Promise; - (fn: OneOrMoreMacros, ...args: T): Promise; + /** + * Attempt to run some assertions. The result must be explicitly committed or discarded or else + * the test will fail. A macro may be provided. The title may help distinguish attempts from + * one another. + */ + (title: string, fn: OneOrMoreMacros, ...args: Args): Promise; + + /** + * Attempt to run some assertions. The result must be explicitly committed or discarded or else + * the test will fail. A macro may be provided. + */ + (fn: OneOrMoreMacros, ...args: Args): Promise; } export interface AssertionError extends Error {} export interface AttemptResult { /** - * Commit the attempt. - */ - commit: (opts?: CommitDiscardOptions) => void; - - /** - * Discard the attempt. - */ - discard: (opts?: CommitDiscardOptions) => void; + * Title of the attempt, helping you tell attempts aparts. + */ + title: string; /** - * Indicates whether attempt passed or failed. - */ + * Indicates whether all assertions passed, or at least one failed. + */ passed: boolean; /** - * Assertion errors raised during the attempt. - */ + * Errors raised for each failed assertion. + */ errors: AssertionError[]; /** - * Title of the attempt, helps you distinguish attempts from each other. + * Logs created during the attempt using `t.log()`. Contains formatted values. */ - title: string; + logs: string[]; /** - * Logs created during the attempt. Contains formatted values. + * Commit the attempt. Counts as one assertion for the plan count. If the + * attempt failed, calling this will also cause your test to fail. */ - logs: string[]; -} + commit(options?: CommitDiscardOptions): void; -export type CommitDiscardOptions = { /** - * Whether the logs should be included in the parent test. + * Discard the attempt. */ - retainLogs?: boolean + discard(options?: CommitDiscardOptions): void; } /** The `t` value passed to implementations for tests & hooks declared with the `.cb` modifier. */ From f8c6bb1bc8eec30fc2e977c38f7af34fdb0ad2ea Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 31 May 2019 15:52:53 +0200 Subject: [PATCH 049/105] Document metadata.inline --- lib/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runner.js b/lib/runner.js index 8bb5062cb..7c44d1ddb 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -162,7 +162,7 @@ class Runner extends Emittery { todo: false, failing: false, callback: false, - inline: false, + inline: false, // Set for attempt metadata created by `t.try()` always: false }, meta); } From bffb7a718370a23c53f3d01f0a4ef9c3dbaff8f9 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 31 May 2019 16:04:09 +0200 Subject: [PATCH 050/105] Reuse test argument parsing logic --- lib/parse-test-args.js | 9 +++++++++ lib/runner.js | 10 +++------- lib/test.js | 15 ++++----------- 3 files changed, 16 insertions(+), 18 deletions(-) create mode 100644 lib/parse-test-args.js diff --git a/lib/parse-test-args.js b/lib/parse-test-args.js new file mode 100644 index 000000000..8c4872b2e --- /dev/null +++ b/lib/parse-test-args.js @@ -0,0 +1,9 @@ +'use strict'; +function parseTestArgs(...args) { + const specifiedTitle = typeof args[0] === 'string' ? args.shift() : undefined; + const receivedImplementationArray = Array.isArray(args[0]); + const implementations = receivedImplementationArray ? args.shift() : args.splice(0, 1); + return {args, implementations, receivedImplementationArray, specifiedTitle}; +} + +module.exports = parseTestArgs; diff --git a/lib/runner.js b/lib/runner.js index 7c44d1ddb..aa4ffdb91 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -3,6 +3,7 @@ const Emittery = require('emittery'); const matcher = require('matcher'); const ContextRef = require('./context-ref'); const createChain = require('./create-chain'); +const parseTestArgs = require('./parse-test-args'); const snapshotManager = require('./snapshot-manager'); const serializeError = require('./serialize-error'); const Runnable = require('./test'); @@ -44,7 +45,7 @@ class Runner extends Emittery { const meta = Object.freeze({ file: options.file }); - this.chain = createChain((metadata, args) => { // eslint-disable-line complexity + this.chain = createChain((metadata, testArgs) => { // eslint-disable-line complexity if (hasStarted) { throw new Error('All tests and hooks must be declared synchronously in your test file, and cannot be nested within other tests or hooks.'); } @@ -57,12 +58,7 @@ class Runner extends Emittery { }); } - const specifiedTitle = typeof args[0] === 'string' ? - args.shift() : - undefined; - const implementations = Array.isArray(args[0]) ? - args.shift() : - args.splice(0, 1); + const {args, implementations, specifiedTitle} = parseTestArgs(...testArgs); if (metadata.todo) { if (implementations.length > 0) { diff --git a/lib/test.js b/lib/test.js index 8ac1839f2..9433743bc 100644 --- a/lib/test.js +++ b/lib/test.js @@ -6,6 +6,7 @@ const isObservable = require('is-observable'); const plur = require('plur'); const assert = require('./assert'); const nowAndTimers = require('./now-and-timers'); +const parseTestArgs = require('./parse-test-args'); const concordanceOptions = require('./concordance-options').default; function formatErrorValue(label, error) { @@ -69,16 +70,8 @@ class ExecutionContext extends assert.Assertions { test.timeout(ms); }; - this.try = async (...args) => { - // Few lines dealing with title/macro are taken from runner.js - const specifiedTitle = typeof args[0] === 'string' ? - args.shift() : - undefined; - // Used to make sure we return single object for single implementation function passed. - const singleImplementation = !Array.isArray(args[0]); - const implementations = singleImplementation ? - args.splice(0, 1) : - args.shift(); + this.try = async (...attemptArgs) => { + const {args, implementations, receivedImplementationArray, specifiedTitle} = parseTestArgs(...attemptArgs); if (implementations.length === 0) { throw new TypeError('Expected an implementation.'); @@ -145,7 +138,7 @@ class ExecutionContext extends assert.Assertions { }); const results = await Promise.all(attemptPromises); - return singleImplementation ? results[0] : results; + return receivedImplementationArray ? results[0] : results; }; } From aca1d64cb7291c881fcfb0323f8051067ce78a96 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 31 May 2019 16:22:22 +0200 Subject: [PATCH 051/105] Single quotes --- test/ts-types/try-commit.ts | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/test/ts-types/try-commit.ts b/test/ts-types/try-commit.ts index de067c8e1..51d3df208 100644 --- a/test/ts-types/try-commit.ts +++ b/test/ts-types/try-commit.ts @@ -1,24 +1,24 @@ -import test, {ExecutionContext, Macro} from "../.."; +import test, {ExecutionContext, Macro} from '../..'; { - test("attempt", async t => { + test('attempt', async t => { const attempt = await t.try( (u, a, b) => { u.is(a.length, b); }, - "string", + 'string', 6 ); attempt.commit(); }); - test("attempt with title", async t => { + test('attempt with title', async t => { const attempt = await t.try( - "attempt title", + 'attempt title', (u, a, b) => { u.is(a.length, b); }, - "string", + 'string', 6 ); attempt.commit(); @@ -30,40 +30,40 @@ import test, {ExecutionContext, Macro} from "../.."; t.is(a.length, b); }; - test("attempt with helper", async t => { - const attempt = await t.try(lengthCheck, "string", 6); + test('attempt with helper', async t => { + const attempt = await t.try(lengthCheck, 'string', 6); attempt.commit(); }); - test("attempt with title", async t => { - const attempt = await t.try(lengthCheck, "string", 6); + test('attempt with title', async t => { + const attempt = await t.try(lengthCheck, 'string', 6); attempt.commit(); }); } { - test("all possible variants to pass to t.try", async t => { + test('all possible variants to pass to t.try', async t => { // no params t.try(tt => tt.pass()); /* fails as expected */ // t.try([]); t.try([tt => tt.pass()]); t.try([tt => tt.pass(), tt => tt.fail()]); - t.try("test", tt => tt.pass()); - /* fails as expected */ // t.try("test", []); - t.try("test", [tt => tt.pass()]); - t.try("test", [tt => tt.pass(), tt => tt.fail()]); + t.try('test', tt => tt.pass()); + /* fails as expected */ // t.try('test', []); + t.try('test', [tt => tt.pass()]); + t.try('test', [tt => tt.pass(), tt => tt.fail()]); // some params - t.try((tt, a, b) => tt.is(a.length, b), "hello", 5); - /* fails as expected */ // t.try([], "hello", 5); - t.try([(tt, a, b) => tt.is(a.length, b)], "hello", 5); - t.try([(tt, a, b) => tt.is(a.length, b), (tt, a, b) => tt.is(a.slice(b), "")], "hello", 5); + t.try((tt, a, b) => tt.is(a.length, b), 'hello', 5); + /* fails as expected */ // t.try([], 'hello', 5); + t.try([(tt, a, b) => tt.is(a.length, b)], 'hello', 5); + t.try([(tt, a, b) => tt.is(a.length, b), (tt, a, b) => tt.is(a.slice(b), '')], 'hello', 5); - t.try("test", (tt, a, b) => tt.is(a.length, b), "hello", 5); - /* fails as expected */ // t.try("test", [], "hello", 5); - t.try("test", [(tt, a, b) => tt.is(a.length, b)], "hello", 5); - t.try("test", [(tt, a, b) => tt.is(a.length, b), (tt, a, b) => tt.is(a.slice(b), "")], "hello", 5); + t.try('test', (tt, a, b) => tt.is(a.length, b), 'hello', 5); + /* fails as expected */ // t.try('test', [], 'hello', 5); + t.try('test', [(tt, a, b) => tt.is(a.length, b)], 'hello', 5); + t.try('test', [(tt, a, b) => tt.is(a.length, b), (tt, a, b) => tt.is(a.slice(b), '')], 'hello', 5); // macro with title const macro1: Macro<[string, number]> = (tt, a, b) => tt.is(a.length, b); From 5c31c1e1962bc1c7609abd5bcf2e7f89c5e8b7ca Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 31 May 2019 16:23:52 +0200 Subject: [PATCH 052/105] Fix result typing when t.try() is called with an array of implementations --- index.d.ts | 17 +++++++++++++++-- test/ts-types/try-commit.ts | 10 ++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 560a31592..2e9164dc4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -404,13 +404,26 @@ export interface TryFn { * the test will fail. A macro may be provided. The title may help distinguish attempts from * one another. */ - (title: string, fn: OneOrMoreMacros, ...args: Args): Promise; + (title: string, fn: EitherMacro, ...args: Args): Promise; + + /** + * Attempt to run some assertions. The result must be explicitly committed or discarded or else + * the test will fail. A macro may be provided. The title may help distinguish attempts from + * one another. + */ + (title: string, fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; + + /** + * Attempt to run some assertions. The result must be explicitly committed or discarded or else + * the test will fail. A macro may be provided. + */ + (fn: EitherMacro, ...args: Args): Promise; /** * Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. */ - (fn: OneOrMoreMacros, ...args: Args): Promise; + (fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; } export interface AssertionError extends Error {} diff --git a/test/ts-types/try-commit.ts b/test/ts-types/try-commit.ts index 51d3df208..8a4b9dd0d 100644 --- a/test/ts-types/try-commit.ts +++ b/test/ts-types/try-commit.ts @@ -23,6 +23,16 @@ import test, {ExecutionContext, Macro} from '../..'; ); attempt.commit(); }); + + test('multiple attempts', async t => { + const attempts = [ + ...await t.try([tt => tt.pass(), tt => tt.pass()]), + ...await t.try('title', [tt => tt.pass(), tt => tt.pass()]), + ]; + for (const attempt of attempts) { + attempt.commit(); + } + }); } { From e3f9b7c2059bf17149a26a5d82e697e637f5dc87 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 15:07:43 +0200 Subject: [PATCH 053/105] Copy the context ref when building the attempt test Each attempt should get a fres context ref, just like each test does. --- lib/test.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/test.js b/lib/test.js index 9433743bc..e097bc57d 100644 --- a/lib/test.js +++ b/lib/test.js @@ -153,15 +153,11 @@ class ExecutionContext extends assert.Assertions { } get context() { - const test = testMap.get(this); - return test.metadata.inline ? undefined : test.contextRef.get(); + return testMap.get(this).contextRef.get(); } set context(context) { - const test = testMap.get(this); - if (!test.metadata.inline) { - test.contextRef.set(context); - } + testMap.get(this).contextRef.set(context); } _throwsArgStart(assertion, file, line) { @@ -224,6 +220,7 @@ class Test { const opts = { ...options, + contextRef: this.contextRef.copy(), metadata: {...options.metadata, inline: true}, fn, title From 4cbf0e178f65315ce42e473173fc9bcc391ddd79 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 15:09:46 +0200 Subject: [PATCH 054/105] t.try() attempts can never be marked as failing. Override metadata from parent test / attempt. --- lib/test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index e097bc57d..6afecf49b 100644 --- a/lib/test.js +++ b/lib/test.js @@ -221,7 +221,7 @@ class Test { const opts = { ...options, contextRef: this.contextRef.copy(), - metadata: {...options.metadata, inline: true}, + metadata: {...options.metadata, failing: false, inline: true}, fn, title }; @@ -589,7 +589,7 @@ class Test { let error = this.assertError; let passed = !error; - if (!this.metadata.inline && this.metadata.failing) { + if (this.metadata.failing) { passed = !passed; if (passed) { From ded6e4bb4829e8755007b4f286e73e712c5a87a6 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 15:11:28 +0200 Subject: [PATCH 055/105] t.try() attempts can never use callback mode. Override metedata from parent test. --- lib/test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index 6afecf49b..9fc841a4a 100644 --- a/lib/test.js +++ b/lib/test.js @@ -221,7 +221,7 @@ class Test { const opts = { ...options, contextRef: this.contextRef.copy(), - metadata: {...options.metadata, failing: false, inline: true}, + metadata: {...options.metadata, callback: false, failing: false, inline: true}, fn, title }; @@ -230,7 +230,7 @@ class Test { } bindEndCallback() { - if (!this.metadata.inline && this.metadata.callback) { + if (this.metadata.callback) { return (error, stack) => { this.endCallback(error, stack); }; @@ -507,7 +507,7 @@ class Test { promise = Promise.resolve(result.retval); } - if (!this.metadata.inline && this.metadata.callback) { + if (this.metadata.callback) { if (returnedObservable || returnedPromise) { const asyncType = returnedObservable ? 'observables' : 'promises'; this.saveFirstError(new Error(`Do not return ${asyncType} from tests declared via \`test.cb(...)\`, if you want to return a promise simply declare the test via \`test(...)\``)); From a14459f972d6534317fc214268657920dd9c2a92 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 15:12:21 +0200 Subject: [PATCH 056/105] Tweak error thrown when using t.end() inside a t.try() --- lib/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index 9fc841a4a..beb30d361 100644 --- a/lib/test.js +++ b/lib/test.js @@ -237,7 +237,7 @@ class Test { } if (this.metadata.inline) { - throw new Error('`t.end()` is not supported in this context. You have to return promise for asynchronous attempt.'); + throw new Error('`t.end()` is not supported inside `t.try()`'); } else { throw new Error('`t.end()` is not supported in this context. To use `t.end()` as a callback, you must use "callback mode" via `test.cb(testName, fn)`'); } From e5229d12c75fc3606e69dd0276afc1132ee3b9b2 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 15:18:18 +0200 Subject: [PATCH 057/105] Untangle logic in verifyAssertion(), add some comments --- lib/test.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/test.js b/lib/test.js index beb30d361..1173db71e 100644 --- a/lib/test.js +++ b/lib/test.js @@ -401,15 +401,33 @@ class Test { } verifyAssertions() { - if (!this.assertError) { - if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null && this.pendingAttemptCount > 0) { + if (this.assertError) { + return; + } + + if (this.failWithoutAssertions) { + if (this.calledEnd) { + return; // `t.end()` is treated as an assertion. + } + + if (this.planCount !== null) { + return; // `verifyPlan()` will report an error already. + } + + if (this.pendingAttemptCount > 0) { this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded')); - } else if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null && this.assertCount === 0) { + return; + } + + if (this.assertCount === 0) { this.saveFirstError(new Error('Test finished without running any assertions')); - } else if (this.pendingAssertionCount > 0) { - this.saveFirstError(new Error('Test finished, but an assertion is still pending')); + return; } } + + if (this.pendingAssertionCount > 0) { + this.saveFirstError(new Error('Test finished, but an assertion is still pending')); + } } trackThrows(pending) { From a3b0f88b5309fe3e1cf629421e4f2fdf9cb68c78 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 15:20:28 +0200 Subject: [PATCH 058/105] Reorder Test property initializations --- lib/test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index 1173db71e..558950748 100644 --- a/lib/test.js +++ b/lib/test.js @@ -198,6 +198,7 @@ class Test { this.assertCount = 0; this.assertError = undefined; + this.attemptCount = 0; this.calledEnd = false; this.duration = null; this.endCallbackFinisher = null; @@ -206,13 +207,12 @@ class Test { this.finishDueToTimeout = null; this.finishing = false; this.pendingAssertionCount = 0; + this.pendingAttemptCount = 0; this.pendingThrowsAssertion = null; this.planCount = null; this.startedAt = 0; - this.timeoutTimer = null; this.timeoutMs = 0; - this.pendingAttemptCount = 0; - this.attemptCount = 0; + this.timeoutTimer = null; this.forAttempt = (title, fn) => { const attemptId = this.attemptCount++; From 6408c104493d4cbea6a36135cbe015d8f0619c48 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 16:37:05 +0200 Subject: [PATCH 059/105] Refactor test title building and validation For `t.try()`, validate a title is valid. --- lib/parse-test-args.js | 10 ++++++++-- lib/runner.js | 25 +++++++++++-------------- lib/test.js | 23 ++++++++++++++--------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/parse-test-args.js b/lib/parse-test-args.js index 8c4872b2e..9c7ab28d2 100644 --- a/lib/parse-test-args.js +++ b/lib/parse-test-args.js @@ -1,9 +1,15 @@ 'use strict'; function parseTestArgs(...args) { - const specifiedTitle = typeof args[0] === 'string' ? args.shift() : undefined; + const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined; const receivedImplementationArray = Array.isArray(args[0]); const implementations = receivedImplementationArray ? args.shift() : args.splice(0, 1); - return {args, implementations, receivedImplementationArray, specifiedTitle}; + + const buildTitle = implementation => { + const title = implementation.title ? implementation.title(rawTitle, ...args) : rawTitle; + return {title, isSet: typeof title !== undefined, isValid: typeof title === 'string', isEmpty: title === ''}; + }; + + return {args, buildTitle, implementations, rawTitle, receivedImplementationArray}; } module.exports = parseTestArgs; diff --git a/lib/runner.js b/lib/runner.js index aa4ffdb91..56e67d5ec 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -58,35 +58,34 @@ class Runner extends Emittery { }); } - const {args, implementations, specifiedTitle} = parseTestArgs(...testArgs); + const {args, buildTitle, implementations, rawTitle} = parseTestArgs(...testArgs); if (metadata.todo) { if (implementations.length > 0) { throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.'); } - if (specifiedTitle === undefined || specifiedTitle === '') { + if (!rawTitle) { // Either undefined or a string. throw new TypeError('`todo` tests require a title'); } - if (uniqueTestTitles.has(specifiedTitle)) { - throw new Error(`Duplicate test title: ${specifiedTitle}`); - } else { - uniqueTestTitles.add(specifiedTitle); + if (uniqueTestTitles.has(rawTitle)) { + throw new Error(`Duplicate test title: ${rawTitle}`); + uniqueTestTitles.add(rawTitle); } if (this.match.length > 0) { // --match selects TODO tests. - if (matcher([specifiedTitle], this.match).length === 1) { + if (matcher([rawTitle], this.match).length === 1) { metadata.exclusive = true; this.runOnlyExclusive = true; } } - this.tasks.todo.push({title: specifiedTitle, metadata}); + this.tasks.todo.push({title: rawTitle, metadata}); this.emit('stateChange', { type: 'declared-test', - title: specifiedTitle, + title: rawTitle, knownFailing: false, todo: true }); @@ -96,15 +95,13 @@ class Runner extends Emittery { } for (const implementation of implementations) { - let title = implementation.title ? - implementation.title(specifiedTitle, ...args) : - specifiedTitle; + let {title, isSet, isValid, isEmpty} = buildTitle(implementation); - if (title !== undefined && typeof title !== 'string') { + if (isSet && !isValid) { throw new TypeError('Test & hook titles must be strings'); } - if (title === undefined || title === '') { + if (isEmpty) { if (metadata.type === 'test') { throw new TypeError('Tests must have a title'); } else if (metadata.always) { diff --git a/lib/test.js b/lib/test.js index 558950748..7209a8b6f 100644 --- a/lib/test.js +++ b/lib/test.js @@ -71,26 +71,32 @@ class ExecutionContext extends assert.Assertions { }; this.try = async (...attemptArgs) => { - const {args, implementations, receivedImplementationArray, specifiedTitle} = parseTestArgs(...attemptArgs); + const {args, buildTitle, implementations, receivedImplementationArray} = parseTestArgs(...attemptArgs); if (implementations.length === 0) { throw new TypeError('Expected an implementation.'); } - const attemptPromises = implementations.map(async implementation => { - const attemptTitle = implementation.title ? - implementation.title(specifiedTitle, ...args) : - specifiedTitle; + const attemptPromises = implementations.map(implementation => { + let {title, isSet, isValid, isEmpty} = buildTitle(implementation); + if (!isSet || isEmpty) { + title = `${test.title} (attempt ${test.attemptCount + 1})`; + } else if (!isValid) { + throw new TypeError('`t.try()` titles must be strings'); // Throw synchronously! + } + + return {implementation, title}; + }).map(async ({implementation, title}) => { let committed = false; let discarded = false; test.addPendingAttemptAssertion(); - const attemptTest = test.forAttempt(attemptTitle, t => implementation(t, ...args)); + const attemptTest = test.forAttempt(title, t => implementation(t, ...args)); const ret = await attemptTest.run(); - const {passed, error, title, logs} = ret; + const {passed, error, logs} = ret; const errors = error ? [error] : []; return { @@ -215,8 +221,7 @@ class Test { this.timeoutTimer = null; this.forAttempt = (title, fn) => { - const attemptId = this.attemptCount++; - title = title || (this.title + '.A' + attemptId); + this.attemptCount++; const opts = { ...options, From d2b85da5075a1032f0ab3d96c66c03eb0d4ef48b Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 16:38:21 +0200 Subject: [PATCH 060/105] Ensure `t.try()` titles are unique --- lib/runner.js | 23 ++++++++++++++--------- lib/test.js | 4 ++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/runner.js b/lib/runner.js index 56e67d5ec..c54daaaf8 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -40,6 +40,15 @@ class Runner extends Emittery { }; const uniqueTestTitles = new Set(); + this.registerUniqueTitle = title => { + if (uniqueTestTitles.has(title)) { + return false; + } + + uniqueTestTitles.add(title); + return true; + }; + let hasStarted = false; let scheduledStart = false; const meta = Object.freeze({ @@ -69,9 +78,8 @@ class Runner extends Emittery { throw new TypeError('`todo` tests require a title'); } - if (uniqueTestTitles.has(rawTitle)) { + if (!this.registerUniqueTitle(rawTitle)) { throw new Error(`Duplicate test title: ${rawTitle}`); - uniqueTestTitles.add(rawTitle); } if (this.match.length > 0) { @@ -111,12 +119,8 @@ class Runner extends Emittery { } } - if (metadata.type === 'test') { - if (uniqueTestTitles.has(title)) { - throw new Error(`Duplicate test title: ${title}`); - } else { - uniqueTestTitles.add(title); - } + if (metadata.type === 'test' && !this.registerUniqueTitle(title)) { + throw new Error(`Duplicate test title: ${title}`); } const task = { @@ -310,7 +314,8 @@ class Runner extends Emittery { compareTestSnapshot: this.boundCompareTestSnapshot, updateSnapshots: this.updateSnapshots, metadata: task.metadata, - title: task.title + title: task.title, + registerUniqueTitle: this.registerUniqueTitle }); const result = await this.runSingle(test); diff --git a/lib/test.js b/lib/test.js index 7209a8b6f..ae2590af6 100644 --- a/lib/test.js +++ b/lib/test.js @@ -86,6 +86,10 @@ class ExecutionContext extends assert.Assertions { throw new TypeError('`t.try()` titles must be strings'); // Throw synchronously! } + if (!test.registerUniqueTitle(title)) { + throw new Error(`Duplicate test title: ${title}`); + } + return {implementation, title}; }).map(async ({implementation, title}) => { let committed = false; From 2d6cb65c92efeb2184c1e9c7887317b4d4e680c7 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 1 Jun 2019 16:51:31 +0200 Subject: [PATCH 061/105] Refactor t.try() implementation --- lib/test.js | 76 ++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/test.js b/lib/test.js index ae2590af6..543c5a8c4 100644 --- a/lib/test.js +++ b/lib/test.js @@ -97,11 +97,7 @@ class ExecutionContext extends assert.Assertions { test.addPendingAttemptAssertion(); - const attemptTest = test.forAttempt(title, t => implementation(t, ...args)); - - const ret = await attemptTest.run(); - const {passed, error, logs} = ret; - const errors = error ? [error] : []; + const {assertCount, passed, errors, logs} = await test.runAttempt(title, t => implementation(t, ...args)); return { passed, @@ -119,10 +115,12 @@ class ExecutionContext extends assert.Assertions { } committed = true; - test.countAttemptAssertion({ - inc: attemptTest.assertCount, + test.finishAttempt({ + assertCount, commit: true, - result: ret, + errors, + logs, + passed, retainLogs }); }, @@ -137,10 +135,12 @@ class ExecutionContext extends assert.Assertions { } discarded = true; - test.countAttemptAssertion({ - inc: 0, + test.finishAttempt({ + assertCount: 0, commit: false, - result: ret, + errors, + logs, + passed, retainLogs }); } @@ -206,6 +206,27 @@ class Test { } }; + this.runAttempt = async (title, fn) => { + if (this.finishing) { + this.saveFirstError(new Error('Running a `t.try()`, but the test has already finished')); + } + + this.attemptCount++; + this.pendingAttemptCount++; + + const attempt = new Test({ + ...options, + contextRef: this.contextRef.copy(), + metadata: {...options.metadata, callback: false, failing: false, inline: true}, + fn, + title + }); + + const {passed, error, logs} = await attempt.run(); + const errors = error ? [error] : []; + return {passed, errors, logs}; + }; + this.assertCount = 0; this.assertError = undefined; this.attemptCount = 0; @@ -223,19 +244,6 @@ class Test { this.startedAt = 0; this.timeoutMs = 0; this.timeoutTimer = null; - - this.forAttempt = (title, fn) => { - this.attemptCount++; - - const opts = { - ...options, - contextRef: this.contextRef.copy(), - metadata: {...options.metadata, callback: false, failing: false, inline: true}, - fn, - title - }; - return new Test(opts); - }; } bindEndCallback() { @@ -318,24 +326,20 @@ class Test { this.saveFirstError(error); } - addPendingAttemptAssertion() { + finishAttempt({assertCount, commit, passed, errors, logs, retainLogs}) { if (this.finishing) { - this.saveFirstError(new Error('Adding the attempt, but the test has already finished')); - } - - this.pendingAttemptCount++; - } - - countAttemptAssertion({inc, commit, result: {passed, error, logs}, retainLogs}) { - if (this.finishing) { - this.saveFirstError(new Error('Attempt is complete, but the test has already finished')); + if (commit) { + this.saveFirstError(new Error('`t.try()` result was committed, but the test has already finished')); + } else { + this.saveFirstError(new Error('`t.try()` result was discarded, but the test has already finished')); + } } - this.assertCount += inc; + this.assertCount += assertCount; this.pendingAttemptCount--; if (commit && !passed) { - this.saveFirstError(error); + this.saveFirstError(errors[0]); } if (retainLogs) { From c68decace488d3bf5216c833e629e45c9499173a Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 2 Jun 2019 16:26:00 +0200 Subject: [PATCH 062/105] Make Context optional in TryFn interface To be consistent with other interfaces. --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 2e9164dc4..7db8d45bc 100644 --- a/index.d.ts +++ b/index.d.ts @@ -398,7 +398,7 @@ export interface TimeoutFn { (ms: number): void; } -export interface TryFn { +export interface TryFn { /** * Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. The title may help distinguish attempts from From 8767cf969aebb211e1f06bd2bdc95b5ac49c524b Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 2 Jun 2019 16:54:12 +0200 Subject: [PATCH 063/105] Change default type of Context to 'unknown' This fixes a compilation issue with `TryFn` and TypeScript 3.5.1, but probably is a breaking change. Not sure whether this is due to a regression in TypeScript. It may be that this is the more correct type regardless. --- index.d.ts | 48 ++++++++++++++++++++-------------------- test/ts-types/context.ts | 5 +++++ 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/index.d.ts b/index.d.ts index 7db8d45bc..f5376700d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -358,7 +358,7 @@ export interface TruthyAssertion { } /** The `t` value passed to test & hook implementations. */ -export interface ExecutionContext extends Assertions { +export interface ExecutionContext extends Assertions { /** Test context, shared with hooks. */ context: Context; @@ -398,7 +398,7 @@ export interface TimeoutFn { (ms: number): void; } -export interface TryFn { +export interface TryFn { /** * Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. The title may help distinguish attempts from @@ -462,7 +462,7 @@ export interface AttemptResult { } /** The `t` value passed to implementations for tests & hooks declared with the `.cb` modifier. */ -export interface CbExecutionContext extends ExecutionContext { +export interface CbExecutionContext extends ExecutionContext { /** * End the test. If `error` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) the test or hook * will fail. @@ -471,14 +471,14 @@ export interface CbExecutionContext extends ExecutionContext | ObservableLike | void; -export type Implementation = (t: ExecutionContext) => ImplementationResult; -export type CbImplementation = (t: CbExecutionContext) => ImplementationResult; +export type Implementation = (t: ExecutionContext) => ImplementationResult; +export type CbImplementation = (t: CbExecutionContext) => ImplementationResult; /** A reusable test or hook implementation. */ -export type UntitledMacro = (t: ExecutionContext, ...args: Args) => ImplementationResult; +export type UntitledMacro = (t: ExecutionContext, ...args: Args) => ImplementationResult; /** A reusable test or hook implementation. */ -export type Macro = UntitledMacro & { +export type Macro = UntitledMacro & { /** * Implement this function to generate a test (or hook) title whenever this macro is used. `providedTitle` contains * the title provided when the test or hook was declared. Also receives the remaining test arguments. @@ -492,10 +492,10 @@ export type EitherMacro = Macro | Un export type OneOrMoreMacros = EitherMacro | [EitherMacro, ...EitherMacro[]]; /** A reusable test or hook implementation, for tests & hooks declared with the `.cb` modifier. */ -export type UntitledCbMacro = (t: CbExecutionContext, ...args: Args) => ImplementationResult +export type UntitledCbMacro = (t: CbExecutionContext, ...args: Args) => ImplementationResult /** A reusable test or hook implementation, for tests & hooks declared with the `.cb` modifier. */ -export type CbMacro = UntitledCbMacro & { +export type CbMacro = UntitledCbMacro & { title?: (providedTitle: string | undefined, ...args: Args) => string; } @@ -504,7 +504,7 @@ export type EitherCbMacro = CbMacro /** Alias for a single macro, or an array of macros, used for tests & hooks declared with the `.cb` modifier. */ export type OneOrMoreCbMacros = EitherCbMacro | [EitherCbMacro, ...EitherCbMacro[]]; -export interface TestInterface { +export interface TestInterface { /** Declare a concurrent test. */ (title: string, implementation: Implementation): void; @@ -541,7 +541,7 @@ export interface TestInterface { meta: MetaInterface; } -export interface AfterInterface { +export interface AfterInterface { /** Declare a hook that is run once, after all tests have passed. */ (implementation: Implementation): void; @@ -563,7 +563,7 @@ export interface AfterInterface { skip: HookSkipInterface; } -export interface AlwaysInterface { +export interface AlwaysInterface { /** Declare a hook that is run once, after all tests are done. */ (implementation: Implementation): void; @@ -582,7 +582,7 @@ export interface AlwaysInterface { skip: HookSkipInterface; } -export interface BeforeInterface { +export interface BeforeInterface { /** Declare a hook that is run once, before all tests. */ (implementation: Implementation): void; @@ -601,7 +601,7 @@ export interface BeforeInterface { skip: HookSkipInterface; } -export interface CbInterface { +export interface CbInterface { /** Declare a test that must call `t.end()` when it's done. */ (title: string, implementation: CbImplementation): void; @@ -624,7 +624,7 @@ export interface CbInterface { skip: CbSkipInterface; } -export interface CbFailingInterface { +export interface CbFailingInterface { /** Declare a test that must call `t.end()` when it's done. The test is expected to fail. */ (title: string, implementation: CbImplementation): void; @@ -644,7 +644,7 @@ export interface CbFailingInterface { skip: CbSkipInterface; } -export interface CbOnlyInterface { +export interface CbOnlyInterface { /** * Declare a test that must call `t.end()` when it's done. Only this test and others declared with `.only()` are run. */ @@ -663,7 +663,7 @@ export interface CbOnlyInterface { (macros: OneOrMoreCbMacros, ...rest: T): void; } -export interface CbSkipInterface { +export interface CbSkipInterface { /** Skip this test. */ (title: string, implementation: CbImplementation): void; @@ -674,7 +674,7 @@ export interface CbSkipInterface { (macros: OneOrMoreCbMacros, ...rest: T): void; } -export interface FailingInterface { +export interface FailingInterface { /** Declare a concurrent test. The test is expected to fail. */ (title: string, implementation: Implementation): void; @@ -694,7 +694,7 @@ export interface FailingInterface { skip: SkipInterface; } -export interface HookCbInterface { +export interface HookCbInterface { /** Declare a hook that must call `t.end()` when it's done. */ (implementation: CbImplementation): void; @@ -715,7 +715,7 @@ export interface HookCbInterface { skip: HookCbSkipInterface; } -export interface HookCbSkipInterface { +export interface HookCbSkipInterface { /** Skip this hook. */ (implementation: CbImplementation): void; @@ -729,7 +729,7 @@ export interface HookCbSkipInterface { (macros: OneOrMoreCbMacros, ...rest: T): void; } -export interface HookSkipInterface { +export interface HookSkipInterface { /** Skip this hook. */ (implementation: Implementation): void; @@ -743,7 +743,7 @@ export interface HookSkipInterface { (macros: OneOrMoreMacros, ...rest: T): void; } -export interface OnlyInterface { +export interface OnlyInterface { /** Declare a test. Only this test and others declared with `.only()` are run. */ (title: string, implementation: Implementation): void; @@ -760,7 +760,7 @@ export interface OnlyInterface { (macros: OneOrMoreMacros, ...rest: T): void; } -export interface SerialInterface { +export interface SerialInterface { /** Declare a serial test. */ (title: string, implementation: Implementation): void; @@ -795,7 +795,7 @@ export interface SerialInterface { todo: TodoDeclaration; } -export interface SkipInterface { +export interface SkipInterface { /** Skip this test. */ (title: string, implementation: Implementation): void; diff --git a/test/ts-types/context.ts b/test/ts-types/context.ts index ff4962b94..c7f263cf4 100644 --- a/test/ts-types/context.ts +++ b/test/ts-types/context.ts @@ -15,3 +15,8 @@ test.beforeEach(t => { }); test('foo is bar', macro, 'bar'); + +anyTest('default context is unknown', t => { + // @ts-ignore + t.is(t.context.foo, 'bar') +}) From b9dbadb2bd5c2ffb2578570a0bd0d7964b5a139d Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Mon, 3 Jun 2019 15:07:04 +0200 Subject: [PATCH 064/105] Copy logs that are exposed as the attempt result --- lib/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index 543c5a8c4..c3c41c016 100644 --- a/lib/test.js +++ b/lib/test.js @@ -102,8 +102,8 @@ class ExecutionContext extends assert.Assertions { return { passed, errors, + logs: [...logs], // Don't allow modification of logs. title, - logs, commit: ({retainLogs = true} = {}) => { if (committed) { return; From b325cde043fdd397e3f424c5f758aece2c04e8f0 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Mon, 3 Jun 2019 15:08:28 +0200 Subject: [PATCH 065/105] Rename AttemptResult to TryResult --- index.d.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.d.ts b/index.d.ts index f5376700d..29fd3edcc 100644 --- a/index.d.ts +++ b/index.d.ts @@ -404,31 +404,31 @@ export interface TryFn { * the test will fail. A macro may be provided. The title may help distinguish attempts from * one another. */ - (title: string, fn: EitherMacro, ...args: Args): Promise; + (title: string, fn: EitherMacro, ...args: Args): Promise; /** * Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. The title may help distinguish attempts from * one another. */ - (title: string, fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; + (title: string, fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; /** * Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. */ - (fn: EitherMacro, ...args: Args): Promise; + (fn: EitherMacro, ...args: Args): Promise; /** * Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. */ - (fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; + (fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; } export interface AssertionError extends Error {} -export interface AttemptResult { +export interface TryResult { /** * Title of the attempt, helping you tell attempts aparts. */ From 30e522a598c3a490027649da7d35948811a679b3 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Mon, 3 Jun 2019 16:10:15 +0200 Subject: [PATCH 066/105] Only record snapshots when attempt is committed Defer the recording of new snapshots. Move the logic that detects missing entries into the recording implementation. Also check to ensure snapshots are not accidentally replaced. Ensure snapshots created inside attempts are associated with their parent test, not the attempt. Start them at the correct index for the parent test. This allows you to retry attempts a non-deterministic number of times and still compare against the same snapshot. Count the number of snapshot assertions run or skipped during a test or attempt. When committing an attempt, fail if other snapshots have since been committed. Otherwise concurrent attempts may override each other's snapshots. --- lib/snapshot-manager.js | 59 ++++++++++++++++++++++------------ lib/test.js | 71 +++++++++++++++++++++++++++++++---------- 2 files changed, 93 insertions(+), 37 deletions(-) diff --git a/lib/snapshot-manager.js b/lib/snapshot-manager.js index 545bd3651..2fed69aef 100644 --- a/lib/snapshot-manager.js +++ b/lib/snapshot-manager.js @@ -305,45 +305,64 @@ class Manager { compare(options) { const hash = md5Hex(options.belongsTo); const entries = this.snapshotsByHash.get(hash) || []; - if (options.index > entries.length) { - throw new RangeError(`Cannot record snapshot ${options.index} for ${JSON.stringify(options.belongsTo)}, exceeds expected index of ${entries.length}`); - } + const snapshotBuffer = entries[options.index]; - if (options.index === entries.length) { + if (!snapshotBuffer) { if (!this.recordNewSnapshots) { return {pass: false}; } + if (options.deferRecording) { + const record = this.deferRecord(hash, options); + return {pass: true, record}; + } + this.record(hash, options); return {pass: true}; } - const snapshotBuffer = entries[options.index]; const actual = concordance.deserialize(snapshotBuffer, concordanceOptions); - const expected = concordance.describe(options.expected, concordanceOptions); const pass = concordance.compareDescriptors(actual, expected); return {actual, expected, pass}; } - record(hash, options) { + deferRecord(hash, options) { const descriptor = concordance.describe(options.expected, concordanceOptions); - - this.hasChanges = true; const snapshot = concordance.serialize(descriptor); - if (this.snapshotsByHash.has(hash)) { - this.snapshotsByHash.get(hash).push(snapshot); - } else { - this.snapshotsByHash.set(hash, [snapshot]); - } - const entry = formatEntry(options.label, descriptor); - if (this.reportEntries.has(options.belongsTo)) { - this.reportEntries.get(options.belongsTo).push(entry); - } else { - this.reportEntries.set(options.belongsTo, [entry]); - } + + return () => { // Must be called in order! + this.hasChanges = true; + + let snapshots = this.snapshotsByHash.get(hash); + if (!snapshots) { + snapshots = []; + this.snapshotsByHash.set(hash, snapshots); + } + + if (options.index > snapshots.length) { + throw new RangeError(`Cannot record snapshot ${options.index} for ${JSON.stringify(options.belongsTo)}, exceeds expected index of ${snapshots.length}`); + } + + if (options.index < snapshots.length) { + throw new RangeError(`Cannot record snapshot ${options.index} for ${JSON.stringify(options.belongsTo)}, already exists`); + } + + snapshots.push(snapshot); + + if (this.reportEntries.has(options.belongsTo)) { + this.reportEntries.get(options.belongsTo).push(entry); + } else { + this.reportEntries.set(options.belongsTo, [entry]); + } + }; + } + + record(hash, options) { + const record = this.deferRecord(hash, options); + record(); } save() { diff --git a/lib/test.js b/lib/test.js index c3c41c016..dbf86de52 100644 --- a/lib/test.js +++ b/lib/test.js @@ -97,12 +97,12 @@ class ExecutionContext extends assert.Assertions { test.addPendingAttemptAssertion(); - const {assertCount, passed, errors, logs} = await test.runAttempt(title, t => implementation(t, ...args)); + const {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount} = await test.runAttempt(title, t => implementation(t, ...args)); return { - passed, errors, logs: [...logs], // Don't allow modification of logs. + passed, title, commit: ({retainLogs = true} = {}) => { if (committed) { @@ -118,10 +118,13 @@ class ExecutionContext extends assert.Assertions { test.finishAttempt({ assertCount, commit: true, + deferredSnapshotRecordings, errors, logs, passed, - retainLogs + retainLogs, + snapshotCount, + startingSnapshotCount }); }, discard: ({retainLogs = false} = {}) => { @@ -138,10 +141,13 @@ class ExecutionContext extends assert.Assertions { test.finishAttempt({ assertCount: 0, commit: false, + deferredSnapshotRecordings, errors, logs, passed, - retainLogs + retainLogs, + snapshotCount, + startingSnapshotCount }); } }; @@ -188,20 +194,35 @@ class Test { this.title = options.title; this.logs = []; - this.snapshotInvocationCount = 0; - this.compareWithSnapshot = assertionOptions => { - const belongsTo = assertionOptions.id || this.title; - const {expected} = assertionOptions; - const index = assertionOptions.id ? 0 : this.snapshotInvocationCount++; - const label = assertionOptions.id ? '' : assertionOptions.message || `Snapshot ${this.snapshotInvocationCount}`; - return options.compareTestSnapshot({belongsTo, expected, index, label}); + const {snapshotBelongsTo = this.title, nextSnapshotIndex = 0} = options; + this.snapshotBelongsTo = snapshotBelongsTo; + this.nextSnapshotIndex = nextSnapshotIndex; + this.snapshotCount = 0; + + const deferRecording = this.metadata.inline; + this.deferredSnapshotRecordings = []; + this.compareWithSnapshot = ({expected, id, message}) => { + this.snapshotCount++; + + // TODO: In a breaking change, reject non-undefined, falsy IDs and messages. + const belongsTo = id || snapshotBelongsTo; + const index = id ? 0 : this.nextSnapshotIndex++; + const label = id ? '' : message || `Snapshot ${index + 1}`; // Human-readable labels start counting at 1. + + const {record, ...result} = options.compareTestSnapshot({belongsTo, deferRecording, expected, index, label}); + if (record) { + this.deferredSnapshotRecordings.push(record); + } + + return result; }; this.skipSnapshot = () => { if (options.updateSnapshots) { this.addFailedAssertion(new Error('Snapshot assertions cannot be skipped when updating snapshots')); } else { - this.snapshotInvocationCount++; + this.nextSnapshotIndex++; + this.snapshotCount++; this.countPassedAssertion(); } }; @@ -214,17 +235,20 @@ class Test { this.attemptCount++; this.pendingAttemptCount++; + const {contextRef, snapshotBelongsTo, nextSnapshotIndex, snapshotCount: startingSnapshotCount} = this; const attempt = new Test({ ...options, - contextRef: this.contextRef.copy(), - metadata: {...options.metadata, callback: false, failing: false, inline: true}, fn, + metadata: {...options.metadata, callback: false, failing: false, inline: true}, + contextRef: contextRef.copy(), + snapshotBelongsTo, + nextSnapshotIndex, title }); - const {passed, error, logs} = await attempt.run(); + const {deferredSnapshotRecordings, error, logs, passed, snapshotCount} = await attempt.run(); const errors = error ? [error] : []; - return {passed, errors, logs}; + return {deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount}; }; this.assertCount = 0; @@ -326,7 +350,7 @@ class Test { this.saveFirstError(error); } - finishAttempt({assertCount, commit, passed, errors, logs, retainLogs}) { + finishAttempt({assertCount, commit, deferredSnapshotRecordings, errors, logs, passed, retainLogs, snapshotCount, startingSnapshotCount}) { if (this.finishing) { if (commit) { this.saveFirstError(new Error('`t.try()` result was committed, but the test has already finished')); @@ -335,6 +359,17 @@ class Test { } } + if (commit) { + if (startingSnapshotCount === this.snapshotCount) { + this.snapshotCount += snapshotCount; + for (const record of deferredSnapshotRecordings) { + record(); + } + } else { + this.saveFirstError(new Error('Cannot commit `t.try()` result. Do not run concurrent snapshot assertions when using `t.try()`')); + } + } + this.assertCount += assertCount; this.pendingAttemptCount--; @@ -631,11 +666,13 @@ class Test { } return { + deferredSnapshotRecordings: this.deferredSnapshotRecordings, duration: this.duration, error, logs: this.logs, metadata: this.metadata, passed, + snapshotCount: this.snapshotCount, title: this.title }; } From fcbffb7bcd7772210e8c5452495892cdd213143a Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Mon, 3 Jun 2019 16:36:29 +0200 Subject: [PATCH 067/105] Fix detection of callback test that ended with pending assertions remaining --- lib/test.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/test.js b/lib/test.js index dbf86de52..f7039db97 100644 --- a/lib/test.js +++ b/lib/test.js @@ -454,10 +454,6 @@ class Test { } if (this.failWithoutAssertions) { - if (this.calledEnd) { - return; // `t.end()` is treated as an assertion. - } - if (this.planCount !== null) { return; // `verifyPlan()` will report an error already. } @@ -467,7 +463,7 @@ class Test { return; } - if (this.assertCount === 0) { + if (this.assertCount === 0 && !this.calledEnd) { this.saveFirstError(new Error('Test finished without running any assertions')); return; } From bc1c951343fe0e7697e845341614a64409aedce4 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 16 Jun 2019 17:11:50 +0200 Subject: [PATCH 068/105] Just pass an array to parseTestArgs --- lib/parse-test-args.js | 2 +- lib/runner.js | 2 +- lib/test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/parse-test-args.js b/lib/parse-test-args.js index 9c7ab28d2..c3812703d 100644 --- a/lib/parse-test-args.js +++ b/lib/parse-test-args.js @@ -1,5 +1,5 @@ 'use strict'; -function parseTestArgs(...args) { +function parseTestArgs(args) { const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined; const receivedImplementationArray = Array.isArray(args[0]); const implementations = receivedImplementationArray ? args.shift() : args.splice(0, 1); diff --git a/lib/runner.js b/lib/runner.js index c54daaaf8..5e17138c6 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -67,7 +67,7 @@ class Runner extends Emittery { }); } - const {args, buildTitle, implementations, rawTitle} = parseTestArgs(...testArgs); + const {args, buildTitle, implementations, rawTitle} = parseTestArgs(testArgs); if (metadata.todo) { if (implementations.length > 0) { diff --git a/lib/test.js b/lib/test.js index 3f691b49f..5fc4bea7d 100644 --- a/lib/test.js +++ b/lib/test.js @@ -70,7 +70,7 @@ class ExecutionContext extends assert.Assertions { }; this.try = async (...attemptArgs) => { - const {args, buildTitle, implementations, receivedImplementationArray} = parseTestArgs(...attemptArgs); + const {args, buildTitle, implementations, receivedImplementationArray} = parseTestArgs(attemptArgs); if (implementations.length === 0) { throw new TypeError('Expected an implementation.'); From 31be1f74d6ced9725252b3bcb9651dc327e2ccb6 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 16 Jun 2019 17:23:35 +0200 Subject: [PATCH 069/105] Fix regressions introduced by parseTestArgs extraction --- lib/parse-test-args.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parse-test-args.js b/lib/parse-test-args.js index c3812703d..5ea5f0aa4 100644 --- a/lib/parse-test-args.js +++ b/lib/parse-test-args.js @@ -6,7 +6,7 @@ function parseTestArgs(args) { const buildTitle = implementation => { const title = implementation.title ? implementation.title(rawTitle, ...args) : rawTitle; - return {title, isSet: typeof title !== undefined, isValid: typeof title === 'string', isEmpty: title === ''}; + return {title, isSet: typeof title !== 'undefined', isValid: typeof title === 'string', isEmpty: !title}; }; return {args, buildTitle, implementations, rawTitle, receivedImplementationArray}; From c5c5f007a3cdebd2978250f3d4cb85862293d567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 16:19:07 -0400 Subject: [PATCH 070/105] Remove call to removed fn addPendingAttemptAssertion --- lib/test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index 5fc4bea7d..a4cb9f03b 100644 --- a/lib/test.js +++ b/lib/test.js @@ -94,8 +94,6 @@ class ExecutionContext extends assert.Assertions { let committed = false; let discarded = false; - test.addPendingAttemptAssertion(); - const {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount} = await test.runAttempt(title, t => implementation(t, ...args)); return { From a01d4e07915ae66529a6fcaf5cf4e793ab4d65f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 16:19:52 -0400 Subject: [PATCH 071/105] Store registerUniqueTitle() on test instance from options --- lib/test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/test.js b/lib/test.js index a4cb9f03b..2f88394f9 100644 --- a/lib/test.js +++ b/lib/test.js @@ -189,6 +189,7 @@ class Test { this.fn = options.fn; this.metadata = options.metadata; this.title = options.title; + this.registerUniqueTitle = options.registerUniqueTitle; this.logs = []; const {snapshotBelongsTo = this.title, nextSnapshotIndex = 0} = options; From 5baf9add0ff885bce2ea5525eb089a9e9e2c304a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 16:21:29 -0400 Subject: [PATCH 072/105] Add dummy impl of registerUniqueTitle to tests --- test/helper/ava-test.js | 4 ++++ test/test-try-commit.js | 1 + 2 files changed, 5 insertions(+) diff --git a/test/helper/ava-test.js b/test/helper/ava-test.js index f51e3ed8a..a380a3b44 100644 --- a/test/helper/ava-test.js +++ b/test/helper/ava-test.js @@ -19,6 +19,7 @@ function ava(fn, contextRef) { contextRef: contextRef || new ContextRef(), failWithoutAssertions: true, fn, + registerUniqueTitle: () => true, metadata: {type: 'test', callback: false}, title: 'test' }); @@ -29,6 +30,7 @@ ava.failing = (fn, contextRef) => { contextRef: contextRef || new ContextRef(), failWithoutAssertions: true, fn, + registerUniqueTitle: () => true, metadata: {type: 'test', callback: false, failing: true}, title: 'test.failing' }); @@ -39,6 +41,7 @@ ava.cb = (fn, contextRef) => { contextRef: contextRef || new ContextRef(), failWithoutAssertions: true, fn, + registerUniqueTitle: () => true, metadata: {type: 'test', callback: true}, title: 'test.cb' }); @@ -49,6 +52,7 @@ ava.cb.failing = (fn, contextRef) => { contextRef: contextRef || new ContextRef(), failWithoutAssertions: true, fn, + registerUniqueTitle: () => true, metadata: {type: 'test', callback: true, failing: true}, title: 'test.cb.failing' }); diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 9733db196..f774d7d23 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -20,6 +20,7 @@ const contextAva = fn => new Test({ failWithoutAssertions: true, metadata: {type: 'test'}, title: 'foo', + registerUniqueTitle: () => true, fn }); From 589e33293c11d4359d5f3c55f104f4ed84f672ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 16:28:45 -0400 Subject: [PATCH 073/105] Use real ContextRef in test instead of dummy --- test/helper/ava-test.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/test/helper/ava-test.js b/test/helper/ava-test.js index a380a3b44..5d38b4539 100644 --- a/test/helper/ava-test.js +++ b/test/helper/ava-test.js @@ -1,18 +1,5 @@ const Test = require('../../lib/test'); - -class ContextRef { - constructor() { - this.value = {}; - } - - get() { - return this.value; - } - - set(newValue) { - this.value = newValue; - } -} +const ContextRef = require('../../lib/context-ref'); function ava(fn, contextRef) { return new Test({ From 40765fce45ee155c3025dce26bc840a4ad56a050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 16:33:46 -0400 Subject: [PATCH 074/105] Pass real ContextRef instance at call-site in test-try-commit --- test/test-try-commit.js | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index f774d7d23..a434c20e7 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -5,25 +5,9 @@ require('../lib/worker/options').set({color: false}); const {test} = require('tap'); const delay = require('delay'); const Test = require('../lib/test'); +const ContextRef = require('../lib/context-ref'); const {ava} = require('./helper/ava-test'); -const contextAva = fn => new Test({ - contextRef: { - val: {foo: 'bar'}, - get() { - return this.val; - }, - set(newVal) { - this.val = newVal; - } - }, - failWithoutAssertions: true, - metadata: {type: 'test'}, - title: 'foo', - registerUniqueTitle: () => true, - fn -}); - test('try-commit are present', t => { return ava(a => { a.pass(); @@ -513,19 +497,23 @@ test('try-commit refreshes the timeout on commit/discard', t => { }); test('try-commit cannot access parent test context', t => { - return contextAva(a => { + const context = new ContextRef(); + context.set({foo: 'bar'}); + return ava(a => { return a.try(b => { b.pass(); const ctx = b.context; t.is(ctx, undefined); }).then(res => res.commit()); - }).run().then(result => { + }, context).run().then(result => { t.is(result.passed, true); }); }); test('try-commit cannot set parent test context', t => { - return contextAva(a => { + const context = new ContextRef(); + context.set({foo: 'bar'}); + return ava(a => { t.strictDeepEqual(a.context, {foo: 'bar'}); return a.try(b => { b.pass(); @@ -534,7 +522,7 @@ test('try-commit cannot set parent test context', t => { res.commit(); t.strictDeepEqual(a.context, {foo: 'bar'}); }); - }).run().then(result => { + }, context).run().then(result => { t.is(result.passed, true); }); }); From edaf7fea763ab87955051b8e766310c2dc4db344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 17:09:06 -0400 Subject: [PATCH 075/105] Fix try result resolution when passing array of test fns --- lib/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index 2f88394f9..a7397f079 100644 --- a/lib/test.js +++ b/lib/test.js @@ -151,7 +151,7 @@ class ExecutionContext extends assert.Assertions { }); const results = await Promise.all(attemptPromises); - return receivedImplementationArray ? results[0] : results; + return receivedImplementationArray ? results : results[0]; }; } From 7f489b5d3b1479d0f524762dc980b92b558967b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 17:12:33 -0400 Subject: [PATCH 076/105] Return assertCount value from run() --- lib/test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index a7397f079..33ed8889b 100644 --- a/lib/test.js +++ b/lib/test.js @@ -244,9 +244,9 @@ class Test { title }); - const {deferredSnapshotRecordings, error, logs, passed, snapshotCount} = await attempt.run(); + const {deferredSnapshotRecordings, error, logs, passed, assertCount, snapshotCount} = await attempt.run(); const errors = error ? [error] : []; - return {deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount}; + return {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount}; }; this.assertCount = 0; @@ -667,6 +667,7 @@ class Test { metadata: this.metadata, passed, snapshotCount: this.snapshotCount, + assertCount: this.assertCount, title: this.title }; } From b2e9e806567fe4d5d5b9935732d2c62a95a80751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 17:33:42 -0400 Subject: [PATCH 077/105] Adjust test titles in try-commit title check --- test/test-try-commit.js | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index a434c20e7..fca892edd 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -237,25 +237,21 @@ test('test fails when try-commit committed to failed state', t => { test('try-commit has proper titles, when going in depth and width', t => { t.plan(6); - return new Test({ - fn(a) { - t.is(a.title, 'foo'); - - return Promise.all([ - a.try(b => { - t.is(b.title, 'foo.A0'); - return Promise.all([ - b.try(c => t.is(c.title, 'foo.A0.A0')), - b.try(c => t.is(c.title, 'foo.A0.A1')) - ]); - }), - a.try(b => t.is(b.title, 'foo.A1')), - a.try(b => t.is(b.title, 'foo.A2')) - ]); - }, - failWithoutAssertions: false, - metadata: {type: 'test', callback: false}, - title: 'foo' + return ava(a => { + t.is(a.title, 'test'); + + return Promise.all([ + a.try(b => { + t.is(b.title, 'test (attempt 1)'); + + return Promise.all([ + b.try(c => t.is(c.title, 'test (attempt 1) (attempt 1)')), + b.try(c => t.is(c.title, 'test (attempt 1) (attempt 2)')) + ]); + }), + a.try(b => t.is(b.title, 'test (attempt 2)')), + a.try(b => t.is(b.title, 'test (attempt 3)')) + ]); }).run(); }); From 10ec2f5d7f1cd56c521fb849d2b15fb2b26f463c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 17:35:27 -0400 Subject: [PATCH 078/105] Remove redundant error message check --- test/test-try-commit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index fca892edd..a0489edf9 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -383,7 +383,6 @@ test('try-commit does not allow to use .end() in attempt when parent is callback t.match(result.error.message, /Error thrown in test/); t.is(result.error.name, 'AssertionError'); t.match(result.error.values[0].formatted, /t\.end.*not supported/); - t.match(result.error.values[0].formatted, /return promise for asynchronous attempt/); }); }); From 89f6a5a124c212a2b5604d5f1ff35db7e8a461b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 17:37:42 -0400 Subject: [PATCH 079/105] Adjust test to access parent context since it is allowed now --- test/test-try-commit.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index a0489edf9..30f6119d9 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -491,14 +491,14 @@ test('try-commit refreshes the timeout on commit/discard', t => { }); }); -test('try-commit cannot access parent test context', t => { +test('try-commit can access parent test context', t => { const context = new ContextRef(); - context.set({foo: 'bar'}); + const data = {foo: 'bar'}; + context.set(data); return ava(a => { return a.try(b => { b.pass(); - const ctx = b.context; - t.is(ctx, undefined); + t.strictDeepEqual(b.context, data); }).then(res => res.commit()); }, context).run().then(result => { t.is(result.passed, true); From 64461ffb21976b49159e73f9b034e87e3ad9b30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 17:39:03 -0400 Subject: [PATCH 080/105] Extract context data into variable in test checking context --- test/test-try-commit.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 30f6119d9..673f40a3f 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -507,15 +507,16 @@ test('try-commit can access parent test context', t => { test('try-commit cannot set parent test context', t => { const context = new ContextRef(); - context.set({foo: 'bar'}); + const data = {foo: 'bar'}; + context.set(data); return ava(a => { - t.strictDeepEqual(a.context, {foo: 'bar'}); + t.strictDeepEqual(a.context, data); return a.try(b => { b.pass(); b.context = {bar: 'foo'}; }).then(res => { res.commit(); - t.strictDeepEqual(a.context, {foo: 'bar'}); + t.strictDeepEqual(a.context, data); }); }, context).run().then(result => { t.is(result.passed, true); From cdf34037bb5aa7243556deb2d2d03cf7a863d587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 17:49:06 -0400 Subject: [PATCH 081/105] Remove unused variable in test-try-commit --- test/test-try-commit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 673f40a3f..5974f4fc8 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -4,7 +4,6 @@ require('../lib/worker/options').set({color: false}); const {test} = require('tap'); const delay = require('delay'); -const Test = require('../lib/test'); const ContextRef = require('../lib/context-ref'); const {ava} = require('./helper/ava-test'); From 506bddf92266ab33b546f8db57560f3c5951a93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 23:27:04 -0400 Subject: [PATCH 082/105] Allow creating new snapshots with the value of updating --- test/try-snapshot.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index 0c81e65ee..637035c9b 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -19,7 +19,8 @@ function setup(fn) { file: path.join(projectDir, 'try-snapshot.js'), projectDir, fixedLocation: null, - updating + updating, + recordNewSnapshots: updating }); const ava = new Test({ From 4f42253b72dfe4c974031d6cfede20cf76185498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 23:28:13 -0400 Subject: [PATCH 083/105] Pass real contextRef and dummy registerUniqueTitle() --- test/try-snapshot.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index 637035c9b..82cf0bddd 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -6,6 +6,7 @@ const path = require('path'); const {test} = require('tap'); const snapshotManager = require('../lib/snapshot-manager'); const Test = require('../lib/test'); +const ContextRef = require('../lib/context-ref'); function setup(fn) { // Set to `true` to update the snapshot, then run: @@ -27,6 +28,8 @@ function setup(fn) { fn, failWithoutAssertions: true, metadata: {type: 'test', callback: false}, + contextRef: new ContextRef(), + registerUniqueTitle: () => true, title: 'test', compareTestSnapshot: options => manager.compare(options) }); From a374981e6b3d0d258f95e11240672ea1bbda4f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 23:29:26 -0400 Subject: [PATCH 084/105] Allow passing title of the test per test case --- test/try-snapshot.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index 82cf0bddd..0b1774631 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -8,7 +8,7 @@ const snapshotManager = require('../lib/snapshot-manager'); const Test = require('../lib/test'); const ContextRef = require('../lib/context-ref'); -function setup(fn) { +function setup(title, fn) { // Set to `true` to update the snapshot, then run: // "$(npm bin)"/tap --no-cov -R spec test/try-snapshot.js // @@ -30,7 +30,7 @@ function setup(fn) { metadata: {type: 'test', callback: false}, contextRef: new ContextRef(), registerUniqueTitle: () => true, - title: 'test', + title, compareTestSnapshot: options => manager.compare(options) }); @@ -38,7 +38,7 @@ function setup(fn) { } test('try-commit snapshots serially', t => { - const {ava, manager} = setup(a => { + const {ava, manager} = setup('serial', a => { a.snapshot('hello'); const attempt1 = t2 => { @@ -67,7 +67,7 @@ test('try-commit snapshots serially', t => { }); test('try-commit snapshots concurrently', t => { - const {ava, manager} = setup(a => { + const {ava, manager} = setup('concurrent', a => { a.snapshot('hello'); const attempt1 = t2 => { From 2b537bbf50419733ca53ea9088fa96d616e79890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Mon, 1 Jul 2019 23:29:46 -0400 Subject: [PATCH 085/105] Create not equal amount of snapshots in each attempt --- test/try-snapshot.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index 0b1774631..43dc9409e 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -43,6 +43,7 @@ test('try-commit snapshots serially', t => { const attempt1 = t2 => { t2.snapshot(true); + t2.snapshot({boo: 'far'}); }; const attempt2 = t2 => { @@ -72,6 +73,7 @@ test('try-commit snapshots concurrently', t => { const attempt1 = t2 => { t2.snapshot(true); + t2.snapshot({boo: 'far'}); }; const attempt2 = t2 => { From a6fe8d2171241b1aad213754c10cc5ba8b846584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Tue, 2 Jul 2019 00:03:36 -0400 Subject: [PATCH 086/105] Refactor try-snapshot test to have one snapshot manager --- test/try-snapshot.js | 116 +++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index 43dc9409e..f765c04bd 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -8,7 +8,19 @@ const snapshotManager = require('../lib/snapshot-manager'); const Test = require('../lib/test'); const ContextRef = require('../lib/context-ref'); -function setup(title, fn) { +function setup(title, manager, fn) { + return new Test({ + fn, + failWithoutAssertions: true, + metadata: {type: 'test', callback: false}, + contextRef: new ContextRef(), + registerUniqueTitle: () => true, + title, + compareTestSnapshot: options => manager.compare(options) + }); +} + +test(async t => { // Set to `true` to update the snapshot, then run: // "$(npm bin)"/tap --no-cov -R spec test/try-snapshot.js // @@ -24,74 +36,62 @@ function setup(title, fn) { recordNewSnapshots: updating }); - const ava = new Test({ - fn, - failWithoutAssertions: true, - metadata: {type: 'test', callback: false}, - contextRef: new ContextRef(), - registerUniqueTitle: () => true, - title, - compareTestSnapshot: options => manager.compare(options) - }); - - return {ava, manager}; -} + await t.test('try-commit snapshots serially', t => { + const ava = setup('serial', manager, a => { + a.snapshot('hello'); -test('try-commit snapshots serially', t => { - const {ava, manager} = setup('serial', a => { - a.snapshot('hello'); + const attempt1 = t2 => { + t2.snapshot(true); + t2.snapshot({boo: 'far'}); + }; - const attempt1 = t2 => { - t2.snapshot(true); - t2.snapshot({boo: 'far'}); - }; + const attempt2 = t2 => { + t2.snapshot({foo: 'bar'}); + }; - const attempt2 = t2 => { - t2.snapshot({foo: 'bar'}); - }; + return a.try(attempt1).then(first => { + first.commit(); + return a.try(attempt2); + }).then(second => { + second.commit(); + }); + }); - return a.try(attempt1).then(first => { - first.commit(); - return a.try(attempt2); - }).then(second => { - second.commit(); + return ava.run().then(result => { + t.true(result.passed); + if (!result.passed) { + console.log(result.error); + } }); }); - return ava.run().then(result => { - manager.save(); - t.true(result.passed); - if (!result.passed) { - console.log(result.error); - } - }); -}); + await t.test('try-commit snapshots concurrently', t => { + const ava = setup('concurrent', manager, a => { + a.snapshot('hello'); -test('try-commit snapshots concurrently', t => { - const {ava, manager} = setup('concurrent', a => { - a.snapshot('hello'); + const attempt1 = t2 => { + t2.snapshot(true); + t2.snapshot({boo: 'far'}); + }; - const attempt1 = t2 => { - t2.snapshot(true); - t2.snapshot({boo: 'far'}); - }; + const attempt2 = t2 => { + t2.snapshot({foo: 'bar'}); + }; - const attempt2 = t2 => { - t2.snapshot({foo: 'bar'}); - }; + return Promise.all([a.try(attempt1), a.try(attempt2)]) + .then(([first, second]) => { + first.commit(); + second.commit(); + }); + }); - return Promise.all([a.try(attempt1), a.try(attempt2)]) - .then(([first, second]) => { - first.commit(); - second.commit(); - }); + return ava.run().then(result => { + t.true(result.passed); + if (!result.passed) { + console.log(result.error); + } + }); }); - return ava.run().then(result => { - manager.save(); - t.true(result.passed); - if (!result.passed) { - console.log(result.error); - } - }); + manager.save(); }); From a7e2a7f378606deca306dc7331a90d365b5257a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Tue, 2 Jul 2019 00:08:33 -0400 Subject: [PATCH 087/105] Add missing nextSnapshotIndex update when committing to attempt --- lib/test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/test.js b/lib/test.js index 33ed8889b..10ee29fe2 100644 --- a/lib/test.js +++ b/lib/test.js @@ -360,6 +360,7 @@ class Test { if (commit) { if (startingSnapshotCount === this.snapshotCount) { this.snapshotCount += snapshotCount; + this.nextSnapshotIndex += snapshotCount; for (const record of deferredSnapshotRecordings) { record(); } From 5e874680a7d133c4de8ddd787f0ba738f62ab29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Tue, 2 Jul 2019 00:08:02 -0400 Subject: [PATCH 088/105] Update expected snapshot result --- test/fixture/try-snapshot.js.md | 26 ++++++++++++++++++++++---- test/fixture/try-snapshot.js.snap | Bin 203 -> 208 bytes 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/test/fixture/try-snapshot.js.md b/test/fixture/try-snapshot.js.md index b32330f00..5c5987f36 100644 --- a/test/fixture/try-snapshot.js.md +++ b/test/fixture/try-snapshot.js.md @@ -4,21 +4,39 @@ The actual snapshot is saved in `try-snapshot.js.snap`. Generated by [AVA](https://ava.li). -## test +## concurrent > Snapshot 1 'hello' -## test.A0 +> Snapshot 2 + + true + +> Snapshot 3 + + { + boo: 'far', + } + +## serial > Snapshot 1 + 'hello' + +> Snapshot 2 + true -## test.A1 +> Snapshot 3 -> Snapshot 1 + { + boo: 'far', + } + +> Snapshot 4 { foo: 'bar', diff --git a/test/fixture/try-snapshot.js.snap b/test/fixture/try-snapshot.js.snap index 9e463ac75f6d3315e483a0f40c40bb846fb201e4..b4919bad0fdf5a27010703b0269a3099161ef178 100644 GIT binary patch literal 208 zcmV;>05AVRRzVIKjaFb6x)0^~=8> zVg`%efZ``mY|hBQz|6o7HjR;$L6DI(BQ+-{A1=hizz=70Ff!=@X%mp8jBJ98Z2n1E zsmUdbjBuuafB*v{F9RbZ10z3B4U-5XGtg8)M&_jad^kHTv4|Q*rXd-bgk&V$JOKdf K2I7^O0RR9szECIt literal 203 zcmV;+05tzWRzVms$RvS$&Hx*v-O000000009E zVPIfjX5j44KI^7o;?gc-r2@<2S- z@V>hBsVnYoefs(3i?q)(f<=qK=COnIGqN%WGO}i*=H%qVg_s!l;cN~@COsf+0 Date: Tue, 2 Jul 2019 00:11:33 -0400 Subject: [PATCH 089/105] Remove unneeded logging of failed test --- test/try-snapshot.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index f765c04bd..61965cfa9 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -59,9 +59,6 @@ test(async t => { return ava.run().then(result => { t.true(result.passed); - if (!result.passed) { - console.log(result.error); - } }); }); @@ -87,9 +84,6 @@ test(async t => { return ava.run().then(result => { t.true(result.passed); - if (!result.passed) { - console.log(result.error); - } }); }); From 9b416850ce133c2bb787d756f23671185fe76d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Tue, 2 Jul 2019 00:12:12 -0400 Subject: [PATCH 090/105] Update expectation for test with concurrent attempts --- test/try-snapshot.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index 61965cfa9..e2f8aec09 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -83,7 +83,10 @@ test(async t => { }); return ava.run().then(result => { - t.true(result.passed); + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /not run concurrent snapshot assertions when using `t\.try\(\)`/); + t.is(result.error.name, 'Error'); }); }); From aca26c74c315c43cd73b355d942b608d06198e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 15:26:39 -0400 Subject: [PATCH 091/105] Remove unnecessary presence test --- test/test-try-commit.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 5974f4fc8..1bc374bd6 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -7,13 +7,6 @@ const delay = require('delay'); const ContextRef = require('../lib/context-ref'); const {ava} = require('./helper/ava-test'); -test('try-commit are present', t => { - return ava(a => { - a.pass(); - t.type(a.try, Function); - }).run(); -}); - test('try-commit works', t => { const instance = ava(a => { return a From 8abeb18809fd364c2d3baa252a4bfcf66311d77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 15:58:14 -0400 Subject: [PATCH 092/105] Switch try-snapshot tests to use async-await --- test/try-snapshot.js | 46 +++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index e2f8aec09..815d376ea 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -36,8 +36,8 @@ test(async t => { recordNewSnapshots: updating }); - await t.test('try-commit snapshots serially', t => { - const ava = setup('serial', manager, a => { + await t.test('try-commit snapshots serially', async t => { + const ava = setup('serial', manager, async a => { a.snapshot('hello'); const attempt1 = t2 => { @@ -49,21 +49,19 @@ test(async t => { t2.snapshot({foo: 'bar'}); }; - return a.try(attempt1).then(first => { - first.commit(); - return a.try(attempt2); - }).then(second => { - second.commit(); - }); - }); + const first = await a.try(attempt1); + first.commit(); - return ava.run().then(result => { - t.true(result.passed); + const second = await a.try(attempt2); + second.commit(); }); + + const result = await ava.run(); + t.true(result.passed); }); - await t.test('try-commit snapshots concurrently', t => { - const ava = setup('concurrent', manager, a => { + await t.test('try-commit snapshots concurrently', async t => { + const ava = setup('concurrent', manager, async a => { a.snapshot('hello'); const attempt1 = t2 => { @@ -75,19 +73,19 @@ test(async t => { t2.snapshot({foo: 'bar'}); }; - return Promise.all([a.try(attempt1), a.try(attempt2)]) - .then(([first, second]) => { - first.commit(); - second.commit(); - }); + const [first, second] = await Promise.all([ + a.try(attempt1), + a.try(attempt2) + ]); + first.commit(); + second.commit(); }); - return ava.run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /not run concurrent snapshot assertions when using `t\.try\(\)`/); - t.is(result.error.name, 'Error'); - }); + const result = await ava.run(); + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /not run concurrent snapshot assertions when using `t\.try\(\)`/); + t.is(result.error.name, 'Error'); }); manager.save(); From 3b677aea5efe0c2bd4b2942b35f14a86185542d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 15:59:31 -0400 Subject: [PATCH 093/105] Inline attempt functions in try call --- test/try-snapshot.js | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/test/try-snapshot.js b/test/try-snapshot.js index 815d376ea..d524c1d80 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -40,19 +40,15 @@ test(async t => { const ava = setup('serial', manager, async a => { a.snapshot('hello'); - const attempt1 = t2 => { + const first = await a.try(t2 => { t2.snapshot(true); t2.snapshot({boo: 'far'}); - }; - - const attempt2 = t2 => { - t2.snapshot({foo: 'bar'}); - }; - - const first = await a.try(attempt1); + }); first.commit(); - const second = await a.try(attempt2); + const second = await a.try(t2 => { + t2.snapshot({foo: 'bar'}); + }); second.commit(); }); @@ -64,18 +60,14 @@ test(async t => { const ava = setup('concurrent', manager, async a => { a.snapshot('hello'); - const attempt1 = t2 => { - t2.snapshot(true); - t2.snapshot({boo: 'far'}); - }; - - const attempt2 = t2 => { - t2.snapshot({foo: 'bar'}); - }; - const [first, second] = await Promise.all([ - a.try(attempt1), - a.try(attempt2) + a.try(t2 => { + t2.snapshot(true); + t2.snapshot({boo: 'far'}); + }), + a.try(t2 => { + t2.snapshot({foo: 'bar'}); + }) ]); first.commit(); second.commit(); From 6a7114d6b9a2aa141600e3d6cb1934a0dceb883e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 16:22:01 -0400 Subject: [PATCH 094/105] Clarify title for failing test with try-commit --- test/test-try-commit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 1bc374bd6..ef2e4d046 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -306,7 +306,7 @@ test('try-commit fails when plan is not reached inside the try', t => { }); }); -test('try-commit passes with failing test', t => { +test('test expected to fail will pass with failing try-commit within the test', t => { return ava.failing(a => { return a .try(b => b.fail()) From 7b3a698c99fd363b7e0ea35fcf0164d653fa217e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 16:27:52 -0400 Subject: [PATCH 095/105] Remove test which no longer applies This test used to test functionality when we were able to discard promises before they resolved. --- test/test-try-commit.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index ef2e4d046..891978968 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -378,26 +378,6 @@ test('try-commit does not allow to use .end() in attempt when parent is callback }); }); -test('try-commit can be discarded', t => { - const instance = ava(a => { - const p = a.try(b => { - return new Promise(resolve => setTimeout(resolve, 500)) - .then(() => b.pass()); - }); - - p.discard(); - - return p.then(res => { - t.is(res, null); - }); - }); - - return instance.run().then(result => { - t.false(result.passed); - t.is(instance.assertCount, 0); - }); -}); - test('try-commit accepts macros', t => { const macro = b => { t.is(b.title, ' Title'); From ec00c90e7e7dab913c1b40b58ee832afc90a25a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 16:57:19 -0400 Subject: [PATCH 096/105] Convert tests to use async-await --- test/test-try-commit.js | 618 +++++++++++++++++++--------------------- 1 file changed, 290 insertions(+), 328 deletions(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 891978968..838a3d0da 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -7,236 +7,209 @@ const delay = require('delay'); const ContextRef = require('../lib/context-ref'); const {ava} = require('./helper/ava-test'); -test('try-commit works', t => { - const instance = ava(a => { - return a - .try(b => b.pass()) - .then(res => { - t.true(res.passed); - res.commit(); - }); +test('try-commit works', async t => { + const instance = ava(async a => { + const res = await a.try(b => b.pass()); + t.true(res.passed); + res.commit(); }); - return instance.run() - .then(result => { - t.true(result.passed); - t.is(instance.assertCount, 1); - }); + const result = await instance.run(); + + t.true(result.passed); + t.is(instance.assertCount, 1); }); -test('try-commit is bound', t => { - return ava(a => { +test('try-commit is bound', async t => { + const result = await ava(async a => { const {try: tryFn} = a; - return tryFn(b => b.pass()) - .then(res => res.commit()); - }).run().then(result => { - t.true(result.passed); - }); + const res = await tryFn(b => b.pass()); + await res.commit(); + }).run(); + + t.true(result.passed); }); -test('try-commit discards failed attempt', t => { - return ava(a => { - return a - .try(b => b.fail()) - .then(res => res.discard()) - .then(() => a.pass()); - }).run().then(result => { - t.true(result.passed); - }); +test('try-commit discards failed attempt', async t => { + const result = await ava(async a => { + const res = await a.try(b => b.fail()); + await res.discard(); + await a.pass(); + }).run(); + + t.true(result.passed); }); -test('try-commit can discard produced result', t => { - return ava(a => { - return a - .try(b => b.pass()) - .then(res => { - res.discard(); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /without running any assertions/); - t.is(result.error.name, 'Error'); - }); +test('try-commit can discard produced result', async t => { + const result = await ava(async a => { + const res = await a.try(b => b.pass()); + res.discard(); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /without running any assertions/); + t.is(result.error.name, 'Error'); }); -test('try-commit fails when not all assertions were committed/discarded', t => { - return ava(a => { +test('try-commit fails when not all assertions were committed/discarded', async t => { + const result = await ava(async a => { a.pass(); - return a.try(b => b.pass()); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /not all attempts were committed/); - t.is(result.error.name, 'Error'); - }); + await a.try(b => b.pass()); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /not all attempts were committed/); + t.is(result.error.name, 'Error'); }); -test('try-commit works with values', t => { +test('try-commit works with values', async t => { const testValue1 = 123; const testValue2 = 123; - return ava(a => { - return a - .try((b, val1, val2) => { - b.is(val1, val2); - }, testValue1, testValue2) - .then(res => { - t.true(res.passed); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - }); + const result = await ava(async a => { + const res = await a.try((b, val1, val2) => { + b.is(val1, val2); + }, testValue1, testValue2); + t.true(res.passed); + res.commit(); + }).run(); + + t.true(result.passed); }); -test('try-commit is properly counted', t => { - const instance = ava(a => { - return a - .try(b => { - b.is(1, 1); - b.is(2, 2); - b.pass(); - }) - .then(res => { - t.true(res.passed); - t.is(instance.pendingAttemptCount, 1); - res.commit(); - t.is(instance.pendingAttemptCount, 0); - }); - }); +test('try-commit is properly counted', async t => { + const instance = ava(async a => { + const res = await a.try(b => { + b.is(1, 1); + b.is(2, 2); + b.pass(); + }); - return instance.run().then(result => { - t.true(result.passed); - t.is(instance.assertCount, 3); + t.true(res.passed); + t.is(instance.pendingAttemptCount, 1); + res.commit(); + t.is(instance.pendingAttemptCount, 0); }); + + const result = await instance.run(); + + t.true(result.passed); + t.is(instance.assertCount, 3); }); -test('try-commit is properly counted multiple', t => { - const instance = ava(a => { - return Promise.all([ +test('try-commit is properly counted multiple', async t => { + const instance = ava(async a => { + const [res1, res2, res3] = await Promise.all([ a.try(b => b.pass()), a.try(b => b.pass()), a.try(b => b.pass()) - ]) - .then(([res1, res2, res3]) => { - t.is(instance.pendingAttemptCount, 3); - res1.commit(); - res2.discard(); - res3.commit(); - t.is(instance.pendingAttemptCount, 0); - }); - }); + ]); - return instance.run().then(result => { - t.true(result.passed); - t.is(instance.assertCount, 2); + t.is(instance.pendingAttemptCount, 3); + res1.commit(); + res2.discard(); + res3.commit(); + t.is(instance.pendingAttemptCount, 0); }); + + const result = await instance.run(); + + t.true(result.passed); + t.is(instance.assertCount, 2); }); -test('try-commit goes as many levels', t => { +test('try-commit goes as many levels', async t => { t.plan(5); - const instance = ava(a => { + const instance = ava(async a => { t.ok(a.try); - return a - .try(b => { - t.ok(b.try); - return b - .try(c => { - t.ok(c.try); - c.pass(); - }) - .then(res => { - res.commit(); - }); - }) - .then(res => { - res.commit(); + const res1 = await a.try(async b => { + t.ok(b.try); + const res = await b.try(c => { + t.ok(c.try); + c.pass(); }); + res.commit(); + }); + res1.commit(); }); - return instance.run().then(result => { - t.true(result.passed); - t.is(instance.assertCount, 1); - }); + const result = await instance.run(); + + t.true(result.passed); + t.is(instance.assertCount, 1); }); -test('try-commit fails when not committed', t => { - return ava(a => { - return a - .try(b => b.pass()) - .then(res => { - t.true(res.passed); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /not all attempts were committed/); - t.is(result.error.name, 'Error'); - }); +test('try-commit fails when not committed', async t => { + const result = await ava(async a => { + const res = await a.try(b => b.pass()); + t.true(res.passed); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /not all attempts were committed/); + t.is(result.error.name, 'Error'); }); -test('try-commit fails when no assertions inside try', t => { - return ava(a => { - return a - .try(() => {}) - .then(res => { - t.false(res.passed); - t.ok(res.errors); - t.is(res.errors.length, 1); - const error = res.errors[0]; - t.match(error.message, /Test finished without running any assertions/); - t.is(error.name, 'Error'); - res.commit(); - }); - }).run().then(result => { - t.false(result.passed); - }); +test('try-commit fails when no assertions inside try', async t => { + const result = await ava(async a => { + const res = await a.try(() => {}); + t.false(res.passed); + t.ok(res.errors); + t.is(res.errors.length, 1); + const error = res.errors[0]; + t.match(error.message, /Test finished without running any assertions/); + t.is(error.name, 'Error'); + res.commit(); + }).run(); + + t.false(result.passed); }); -test('try-commit fails when no assertions inside multiple try', t => { - return ava(a => { - return Promise.all([ - a.try(b => b.pass()).then(res1 => { - res1.commit(); - t.true(res1.passed); - }), - a.try(() => {}).then(res2 => { - t.false(res2.passed); - t.ok(res2.errors); - t.is(res2.errors.length, 1); - const error = res2.errors[0]; - t.match(error.message, /Test finished without running any assertions/); - t.is(error.name, 'Error'); - res2.commit(); - }) +test('try-commit fails when no assertions inside multiple try', async t => { + const result = await ava(async a => { + const [res1, res2] = await Promise.all([ + a.try(b => b.pass()), + a.try(() => {}) ]); - }).run().then(result => { - t.false(result.passed); - }); + + res1.commit(); + t.true(res1.passed); + + t.false(res2.passed); + t.ok(res2.errors); + t.is(res2.errors.length, 1); + const error = res2.errors[0]; + t.match(error.message, /Test finished without running any assertions/); + t.is(error.name, 'Error'); + res2.commit(); + }).run(); + + t.false(result.passed); }); -test('test fails when try-commit committed to failed state', t => { - return ava(a => { - return a.try(b => b.fail()).then(res => { - t.false(res.passed); - res.commit(); - }); - }).run().then(result => { - t.false(result.passed); - }); +test('test fails when try-commit committed to failed state', async t => { + const result = await ava(async a => { + const res = await a.try(b => b.fail()); + t.false(res.passed); + res.commit(); + }).run(); + + t.false(result.passed); }); -test('try-commit has proper titles, when going in depth and width', t => { +test('try-commit has proper titles, when going in depth and width', async t => { t.plan(6); - return ava(a => { + await ava(async a => { t.is(a.title, 'test'); - return Promise.all([ - a.try(b => { + await Promise.all([ + a.try(async b => { t.is(b.title, 'test (attempt 1)'); - return Promise.all([ + await Promise.all([ b.try(c => t.is(c.title, 'test (attempt 1) (attempt 1)')), b.try(c => t.is(c.title, 'test (attempt 1) (attempt 2)')) ]); @@ -247,98 +220,91 @@ test('try-commit has proper titles, when going in depth and width', t => { }).run(); }); -test('try-commit does not fail when calling commit twice', t => { - return ava(a => { - return a.try(b => b.pass()).then(res => { - res.commit(); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - t.false(result.error); - }); +test('try-commit does not fail when calling commit twice', async t => { + const result = await ava(async a => { + const res = await a.try(b => b.pass()); + res.commit(); + res.commit(); + }).run(); + + t.true(result.passed); + t.false(result.error); }); -test('try-commit does not fail when calling discard twice', t => { - return ava(a => { - return a.try(b => b.pass()).then(res => { - res.discard(); - res.discard(); - }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /Test finished without running any assertions/); - t.is(result.error.name, 'Error'); - }); +test('try-commit does not fail when calling discard twice', async t => { + const result = await ava(async a => { + const res = await a.try(b => b.pass()); + res.discard(); + res.discard(); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Test finished without running any assertions/); + t.is(result.error.name, 'Error'); }); -test('try-commit allows planning inside the try', t => { - return ava(a => { - return a.try(b => { +test('try-commit allows planning inside the try', async t => { + const result = await ava(async a => { + const res = await a.try(b => { b.plan(3); b.pass(); b.pass(); b.pass(); - }).then(res => { - t.true(res.passed); - res.commit(); }); - }).run().then(result => { - t.true(result.passed); - }); + t.true(res.passed); + res.commit(); + }).run(); + + t.true(result.passed); }); -test('try-commit fails when plan is not reached inside the try', t => { - return ava(a => { - return a.try(b => { +test('try-commit fails when plan is not reached inside the try', async t => { + const result = await ava(async a => { + const res = await a.try(b => { b.plan(3); b.pass(); b.pass(); - }).then(res => { - t.false(res.passed); - res.commit(); }); - }).run().then(result => { - t.false(result.passed); - }); + t.false(res.passed); + res.commit(); + }).run(); + + t.false(result.passed); }); -test('test expected to fail will pass with failing try-commit within the test', t => { - return ava.failing(a => { - return a - .try(b => b.fail()) - .then(res => { - t.false(res.passed); - t.ok(res.errors); - t.is(res.errors.length, 1); - const error = res.errors[0]; - t.match(error.message, /Test failed via `t\.fail\(\)`/); - t.is(error.name, 'AssertionError'); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - }); +test('test expected to fail will pass with failing try-commit within the test', async t => { + const result = await ava.failing(async a => { + const res = await a.try(b => b.fail()); + t.false(res.passed); + t.ok(res.errors); + t.is(res.errors.length, 1); + const error = res.errors[0]; + t.match(error.message, /Test failed via `t\.fail\(\)`/); + t.is(error.name, 'AssertionError'); + res.commit(); + }).run(); + + t.true(result.passed); }); -test('try-commit works with callback test', t => { - return ava.cb(a => { +test('try-commit works with callback test', async t => { + const result = await ava.cb(a => { a .try(b => b.pass()) .then(res => { res.commit(); a.end(); }); - }).run().then(result => { - t.true(result.passed); - }); + }).run(); + + t.true(result.passed); }); -test('try-commit works with failing callback test', t => { - return ava.cb.failing(a => { +test('try-commit works with failing callback test', async t => { + const result = await ava.cb.failing(a => { a .try(b => b.fail()) .then(res => { @@ -353,13 +319,13 @@ test('try-commit works with failing callback test', t => { .then(() => { a.end(); }); - }).run().then(result => { - t.true(result.passed); - }); + }).run(); + + t.true(result.passed); }); -test('try-commit does not allow to use .end() in attempt when parent is callback test', t => { - return ava.cb(a => { +test('try-commit does not allow to use .end() in attempt when parent is callback test', async t => { + const result = await ava.cb(a => { a .try(b => { b.pass(); @@ -369,16 +335,16 @@ test('try-commit does not allow to use .end() in attempt when parent is callback res.commit(); a.end(); }); - }).run().then(result => { - t.false(result.passed); - t.ok(result.error); - t.match(result.error.message, /Error thrown in test/); - t.is(result.error.name, 'AssertionError'); - t.match(result.error.values[0].formatted, /t\.end.*not supported/); - }); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Error thrown in test/); + t.is(result.error.name, 'AssertionError'); + t.match(result.error.values[0].formatted, /t\.end.*not supported/); }); -test('try-commit accepts macros', t => { +test('try-commit accepts macros', async t => { const macro = b => { t.is(b.title, ' Title'); b.pass(); @@ -386,111 +352,107 @@ test('try-commit accepts macros', t => { macro.title = providedTitle => `${providedTitle ? providedTitle : ''} Title`; - return ava(a => { - return a - .try(macro) - .then(res => { - t.true(res.passed); - res.commit(); - }); - }).run().then(result => { - t.true(result.passed); - }); + const result = await ava(async a => { + const res = await a.try(macro); + t.true(res.passed); + res.commit(); + }).run(); + + t.true(result.passed); }); -test('try-commit accepts multiple macros', t => { +test('try-commit accepts multiple macros', async t => { const macros = [b => b.pass(), b => b.fail()]; - return ava(a => { - return a.try(macros) - .then(([res1, res2]) => { - t.true(res1.passed); - res1.commit(); - t.false(res2.passed); - res2.discard(); - }); - }).run().then(result => { - t.true(result.passed); - }); + const result = await ava(async a => { + const [res1, res2] = await a.try(macros); + t.true(res1.passed); + res1.commit(); + t.false(res2.passed); + res2.discard(); + }).run(); + + t.true(result.passed); }); -test('try-commit returns results in the same shape as when implementations are passed', t => { - return ava(a => { - return Promise.all([ - a.try(b => b.pass()).then(results => { - t.match(results, {passed: true}); - results.commit(); - }), - a.try([b => b.pass()]).then(results => { - t.is(results.length, 1); - t.match(results, [{passed: true}]); - results[0].commit(); - }), - a.try([b => b.pass(), b => b.fail()]).then(results => { - t.is(results.length, 2); - t.match(results, [{passed: true}, {passed: false}]); - results[0].commit(); - results[1].discard(); - }) +test('try-commit returns results in the same shape as when implementations are passed', async t => { + const result = await ava(async a => { + const [res1, res2, res3] = await Promise.all([ + a.try(b => b.pass()), + a.try([b => b.pass()]), + a.try([b => b.pass(), b => b.fail()]) ]); - }).run().then(result => { - t.true(result.passed); - }); + + t.match(res1, {passed: true}); + res1.commit(); + + t.is(res2.length, 1); + t.match(res2, [{passed: true}]); + res2[0].commit(); + + t.is(res3.length, 2); + t.match(res3, [{passed: true}, {passed: false}]); + res3[0].commit(); + res3[1].discard(); + }).run(); + + t.true(result.passed); }); -test('try-commit abides timeout', t => { - return ava(a => { +test('try-commit abides timeout', async t => { + const result1 = await ava(async a => { a.timeout(10); - return a.try(b => { + const result = await a.try(async b => { b.pass(); - return delay(200); - }).then(result => result.commit()); - }).run().then(result => { - t.is(result.passed, false); - t.match(result.error.message, /timeout/); - }); + await delay(200); + }); + await result.commit(); + }).run(); + + t.is(result1.passed, false); + t.match(result1.error.message, /timeout/); }); -test('try-commit refreshes the timeout on commit/discard', t => { - return ava.cb(a => { +test('try-commit refreshes the timeout on commit/discard', async t => { + const result1 = await ava.cb(a => { a.timeout(10); a.plan(3); setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 5); setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 10); setTimeout(() => a.try(b => b.pass()).then(result => result.commit()), 15); setTimeout(() => a.end(), 20); - }).run().then(result => { - t.is(result.passed, true); - }); + }).run(); + + t.is(result1.passed, true); }); -test('try-commit can access parent test context', t => { +test('try-commit can access parent test context', async t => { const context = new ContextRef(); const data = {foo: 'bar'}; context.set(data); - return ava(a => { - return a.try(b => { + const result = await ava(async a => { + const res = await a.try(b => { b.pass(); t.strictDeepEqual(b.context, data); - }).then(res => res.commit()); - }, context).run().then(result => { - t.is(result.passed, true); - }); + }); + await res.commit(); + }, context).run(); + + t.is(result.passed, true); }); -test('try-commit cannot set parent test context', t => { +test('try-commit cannot set parent test context', async t => { const context = new ContextRef(); const data = {foo: 'bar'}; context.set(data); - return ava(a => { + const result = await ava(async a => { t.strictDeepEqual(a.context, data); - return a.try(b => { + const res = await a.try(b => { b.pass(); b.context = {bar: 'foo'}; - }).then(res => { - res.commit(); - t.strictDeepEqual(a.context, data); }); - }, context).run().then(result => { - t.is(result.passed, true); - }); + res.commit(); + t.strictDeepEqual(a.context, data); + }, context).run(); + + t.is(result.passed, true); }); From c17d2ab6b65da47ff14d924973d5b4c35aefd49e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 16:59:51 -0400 Subject: [PATCH 097/105] Rename tests as suggested by novemberborn --- test/test-try-commit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 838a3d0da..41057f53d 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -425,7 +425,7 @@ test('try-commit refreshes the timeout on commit/discard', async t => { t.is(result1.passed, true); }); -test('try-commit can access parent test context', async t => { +test('try-commit inherits the test context', async t => { const context = new ContextRef(); const data = {foo: 'bar'}; context.set(data); @@ -440,7 +440,7 @@ test('try-commit can access parent test context', async t => { t.is(result.passed, true); }); -test('try-commit cannot set parent test context', async t => { +test('assigning context in try-commit does not affect parent', async t => { const context = new ContextRef(); const data = {foo: 'bar'}; context.set(data); From d64c8d4d9108966d4624aa1c127b3029c37dba11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 17:48:16 -0400 Subject: [PATCH 098/105] Add test cases suggested by novemberborn --- test/test-try-commit.js | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 41057f53d..9e29b84d1 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -275,6 +275,45 @@ test('try-commit fails when plan is not reached inside the try', async t => { t.false(result.passed); }); +test('plan within try-commit is not affected by assertions outside', async t => { + const result = await ava(async a => { + const attempt = a.try(b => { + b.plan(3); + }); + + a.is(1, 1); + a.is(2, 2); + + const res = await attempt; + t.false(res.passed); + res.commit(); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Planned for 3 assertions, but got 0/); +}); + +test('assertions within try-commit do not affect plan in the parent test', async t => { + const result = await ava(async a => { + a.plan(2); + + const res = await a.try(b => { + b.plan(3); + b.pass(); + b.pass(); + b.pass(); + }); + + t.true(res.passed); + res.commit(); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Planned for 2 assertions, but got 1/); +}); + test('test expected to fail will pass with failing try-commit within the test', async t => { const result = await ava.failing(async a => { const res = await a.try(b => b.fail()); @@ -344,6 +383,23 @@ test('try-commit does not allow to use .end() in attempt when parent is callback t.match(result.error.values[0].formatted, /t\.end.*not supported/); }); +test('try-commit does not allow to use .end() in attempt when parent is regular test', async t => { + const result = await ava(async a => { + const res = await a.try(b => { + b.pass(); + b.end(); + }); + + res.commit(); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Error thrown in test/); + t.is(result.error.name, 'AssertionError'); + t.match(result.error.values[0].formatted, /t\.end.*not supported/); +}); + test('try-commit accepts macros', async t => { const macro = b => { t.is(b.title, ' Title'); @@ -412,6 +468,29 @@ test('try-commit abides timeout', async t => { t.match(result1.error.message, /timeout/); }); +test('try-commit fails when it exceeds its own timeout', async t => { + const result = await ava(async a => { + a.timeout(200); + const result = await a.try(async b => { + b.timeout(50); + b.pass(); + await delay(100); + }); + + t.false(result.passed); + t.ok(result.errors); + t.is(result.errors.length, 1); + const error = result.errors[0]; + t.match(error.message, /Test timeout exceeded/); + t.is(error.name, 'Error'); + + result.discard(); + a.pass(); + }).run(); + + t.true(result.passed); +}); + test('try-commit refreshes the timeout on commit/discard', async t => { const result1 = await ava.cb(a => { a.timeout(10); From 2e624e1c201ea72e4e60803c261c4a98bb553cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikita=20Volodin=20=5B=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=5D?= Date: Sun, 14 Jul 2019 18:17:22 -0400 Subject: [PATCH 099/105] Check that assert within attempt does not refresh test timeout --- test/test-try-commit.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 9e29b84d1..830ad2912 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -504,6 +504,30 @@ test('try-commit refreshes the timeout on commit/discard', async t => { t.is(result1.passed, true); }); +test('assertions within try-commit do not refresh the timeout', async t => { + const result = await ava(async a => { + a.timeout(15); + a.pass(); + + // Attempt by itself will refresh timeout, so it has to finish after + // timeout of the test in order to make sure that it does not refresh the + // timeout. However, if assert within attempt is called before test timeout + // expires and will refresh the timeout (which is faulty behavior), then + // the entire test will not fail by timeout. + const res = await a.try(async b => { + await delay(10); + b.is(1, 1); + await delay(10); + }); + res.commit(); + }).run(); + + t.false(result.passed); + t.ok(result.error); + t.match(result.error.message, /Test timeout exceeded/); + t.is(result.error.name, 'Error'); +}); + test('try-commit inherits the test context', async t => { const context = new ContextRef(); const data = {foo: 'bar'}; From d26575e91bf30ee7112bafed0a16b7e987478be8 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Aug 2019 16:15:55 +0200 Subject: [PATCH 100/105] Report pending attempts or assertions before verifying whether there were any --- lib/test.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/test.js b/lib/test.js index 10ee29fe2..1ececbf3a 100644 --- a/lib/test.js +++ b/lib/test.js @@ -452,25 +452,25 @@ class Test { return; } + if (this.pendingAttemptCount > 0) { + this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded')); + return; + } + + if (this.pendingAssertionCount > 0) { + this.saveFirstError(new Error('Test finished, but an assertion is still pending')); + return; + } + if (this.failWithoutAssertions) { if (this.planCount !== null) { return; // `verifyPlan()` will report an error already. } - if (this.pendingAttemptCount > 0) { - this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded')); - return; - } - if (this.assertCount === 0 && !this.calledEnd) { this.saveFirstError(new Error('Test finished without running any assertions')); - return; } } - - if (this.pendingAssertionCount > 0) { - this.saveFirstError(new Error('Test finished, but an assertion is still pending')); - } } trackThrows(pending) { From 192079265eb9a9eea9f1a50aceb06ba4ded8f005 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Aug 2019 16:16:16 +0200 Subject: [PATCH 101/105] Attempts count as a single assertion for parent tests --- lib/test.js | 5 +++-- test/test-try-commit.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index 1ececbf3a..462ed0055 100644 --- a/lib/test.js +++ b/lib/test.js @@ -348,7 +348,7 @@ class Test { this.saveFirstError(error); } - finishAttempt({assertCount, commit, deferredSnapshotRecordings, errors, logs, passed, retainLogs, snapshotCount, startingSnapshotCount}) { + finishAttempt({commit, deferredSnapshotRecordings, errors, logs, passed, retainLogs, snapshotCount, startingSnapshotCount}) { if (this.finishing) { if (commit) { this.saveFirstError(new Error('`t.try()` result was committed, but the test has already finished')); @@ -358,6 +358,8 @@ class Test { } if (commit) { + this.assertCount++; + if (startingSnapshotCount === this.snapshotCount) { this.snapshotCount += snapshotCount; this.nextSnapshotIndex += snapshotCount; @@ -369,7 +371,6 @@ class Test { } } - this.assertCount += assertCount; this.pendingAttemptCount--; if (commit && !passed) { diff --git a/test/test-try-commit.js b/test/test-try-commit.js index 830ad2912..e2be51cae 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -96,7 +96,7 @@ test('try-commit is properly counted', async t => { const result = await instance.run(); t.true(result.passed); - t.is(instance.assertCount, 3); + t.is(instance.assertCount, 1); }); test('try-commit is properly counted multiple', async t => { From 16cc21ef3a0f3f39927516625d834a96a3976fac Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Aug 2019 16:31:16 +0200 Subject: [PATCH 102/105] Fix error when an asynchronous assertion is started after the test has finished --- lib/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index 462ed0055..30a5469e9 100644 --- a/lib/test.js +++ b/lib/test.js @@ -323,7 +323,7 @@ class Test { addPendingAssertion(promise) { if (this.finishing) { - this.saveFirstError(new Error('Assertion passed, but test has already finished')); + this.saveFirstError(new Error('Assertion started, but test has already finished')); } this.assertCount++; From bb458be5353dc42bd48b53786e6302b58e499399 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Aug 2019 16:31:52 +0200 Subject: [PATCH 103/105] Assertions must not be run outside of an active attempt Though you may start multiple attempts. --- lib/test.js | 12 ++++++++++++ test/test-try-commit.js | 32 +++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index 30a5469e9..8479a6a92 100644 --- a/lib/test.js +++ b/lib/test.js @@ -313,6 +313,10 @@ class Test { this.saveFirstError(new Error('Assertion passed, but test has already finished')); } + if (this.pendingAttemptCount > 0) { + this.saveFirstError(new Error('Assertion passed, but an attempt is pending. Use the attempt’s assertions instead')); + } + this.assertCount++; this.refreshTimeout(); } @@ -326,6 +330,10 @@ class Test { this.saveFirstError(new Error('Assertion started, but test has already finished')); } + if (this.pendingAttemptCount > 0) { + this.saveFirstError(new Error('Assertion started, but an attempt is pending. Use the attempt’s assertions instead')); + } + this.assertCount++; this.pendingAssertionCount++; this.refreshTimeout(); @@ -343,6 +351,10 @@ class Test { this.saveFirstError(new Error('Assertion failed, but test has already finished')); } + if (this.pendingAttemptCount > 0) { + this.saveFirstError(new Error('Assertion failed, but an attempt is pending. Use the attempt’s assertions instead')); + } + this.assertCount++; this.refreshTimeout(); this.saveFirstError(error); diff --git a/test/test-try-commit.js b/test/test-try-commit.js index e2be51cae..f2fb8c06b 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -277,13 +277,13 @@ test('try-commit fails when plan is not reached inside the try', async t => { test('plan within try-commit is not affected by assertions outside', async t => { const result = await ava(async a => { + a.is(1, 1); + a.is(2, 2); + const attempt = a.try(b => { b.plan(3); }); - a.is(1, 1); - a.is(2, 2); - const res = await attempt; t.false(res.passed); res.commit(); @@ -559,3 +559,29 @@ test('assigning context in try-commit does not affect parent', async t => { t.is(result.passed, true); }); + +test('do not run assertions outside of an active attempt', async t => { + const passing = await ava(async a => { + await a.try(() => {}); + a.pass(); + }).run(); + + t.false(passing.passed); + t.match(passing.error.message, /Assertion passed, but an attempt is pending. Use the attempt’s assertions instead/); + + const pending = await ava(async a => { + await a.try(() => {}); + await a.throwsAsync(Promise.reject(new Error(''))); + }).run(); + + t.false(pending.passed); + t.match(pending.error.message, /Assertion started, but an attempt is pending. Use the attempt’s assertions instead/); + + const failing = await ava(async a => { + await a.try(() => {}); + a.fail(); + }).run(); + + t.false(failing.passed); + t.match(failing.error.message, /Assertion failed, but an attempt is pending. Use the attempt’s assertions instead/); +}); From c302923cea9c3619824ca26e82f52538726f5e54 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Aug 2019 16:34:25 +0200 Subject: [PATCH 104/105] Mark t.try() as experimental in the type definition --- index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index b1ae5a36f..0953de2f2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -402,27 +402,27 @@ export interface TimeoutFn { export interface TryFn { /** - * Attempt to run some assertions. The result must be explicitly committed or discarded or else + * EXPERIMENTAL. Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. The title may help distinguish attempts from * one another. */ (title: string, fn: EitherMacro, ...args: Args): Promise; /** - * Attempt to run some assertions. The result must be explicitly committed or discarded or else + * EXPERIMENTAL. Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. The title may help distinguish attempts from * one another. */ (title: string, fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; /** - * Attempt to run some assertions. The result must be explicitly committed or discarded or else + * EXPERIMENTAL. Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. */ (fn: EitherMacro, ...args: Args): Promise; /** - * Attempt to run some assertions. The result must be explicitly committed or discarded or else + * EXPERIMENTAL. Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. */ (fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; From 2b8ba3ac99f7ed9ea6ea5297c915a16bbcf28a17 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 8 Sep 2019 17:39:13 +0200 Subject: [PATCH 105/105] Require opt-in for t.try() --- docs/06-configuration.md | 13 +++++- index.d.ts | 8 ++-- lib/load-config.js | 2 +- lib/runner.js | 3 ++ lib/test.js | 5 +++ lib/worker/subprocess.js | 1 + test/helper/ava-test.js | 91 ++++++++++++++++++++++------------------ test/test-try-commit.js | 4 +- test/try-snapshot.js | 1 + 9 files changed, 79 insertions(+), 49 deletions(-) diff --git a/docs/06-configuration.md b/docs/06-configuration.md index 8c46647ff..8b08af336 100644 --- a/docs/06-configuration.md +++ b/docs/06-configuration.md @@ -164,7 +164,7 @@ AVA has a minimum depth of `3`. ## Experiments -From time to time, AVA will implement experimental features. These may change or be removed at any time, not just when there's a new major version. You can opt-in to such a feature by enabling it in the `nonSemVerExperiments` configuration. +From time to time, AVA will implement experimental features. These may change or be removed at any time, not just when there's a new major version. You can opt in to such a feature by enabling it in the `nonSemVerExperiments` configuration. `ava.config.js`: ```js @@ -175,6 +175,15 @@ export default { }; ``` -There are currently no such features available. +You can opt in to the new `t.try()` assertion by specifying `tryAssertion`: + +`ava.config.js`: +```js +export default { + nonSemVerExperiments: { + tryAssertion: true + } +}; +``` [CLI]: ./05-command-line.md diff --git a/index.d.ts b/index.d.ts index 0953de2f2..35b34e983 100644 --- a/index.d.ts +++ b/index.d.ts @@ -402,27 +402,27 @@ export interface TimeoutFn { export interface TryFn { /** - * EXPERIMENTAL. Attempt to run some assertions. The result must be explicitly committed or discarded or else + * Requires opt-in. Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. The title may help distinguish attempts from * one another. */ (title: string, fn: EitherMacro, ...args: Args): Promise; /** - * EXPERIMENTAL. Attempt to run some assertions. The result must be explicitly committed or discarded or else + * Requires opt-in. Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. The title may help distinguish attempts from * one another. */ (title: string, fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; /** - * EXPERIMENTAL. Attempt to run some assertions. The result must be explicitly committed or discarded or else + * Requires opt-in. Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. */ (fn: EitherMacro, ...args: Args): Promise; /** - * EXPERIMENTAL. Attempt to run some assertions. The result must be explicitly committed or discarded or else + * Requires opt-in. Attempt to run some assertions. The result must be explicitly committed or discarded or else * the test will fail. A macro may be provided. */ (fn: [EitherMacro, ...EitherMacro[]], ...args: Args): Promise; diff --git a/lib/load-config.js b/lib/load-config.js index ba4417a2a..17cf53dc0 100644 --- a/lib/load-config.js +++ b/lib/load-config.js @@ -6,7 +6,7 @@ const pkgConf = require('pkg-conf'); const NO_SUCH_FILE = Symbol('no ava.config.js file'); const MISSING_DEFAULT_EXPORT = Symbol('missing default export'); -const EXPERIMENTS = new Set([]); +const EXPERIMENTS = new Set(['tryAssertion']); function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) { // eslint-disable-line complexity let packageConf = pkgConf.sync('ava', {cwd: resolveFrom}); diff --git a/lib/runner.js b/lib/runner.js index 5e17138c6..cfb9cdcde 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -12,6 +12,7 @@ class Runner extends Emittery { constructor(options = {}) { super(); + this.experiments = options.experiments || {}; this.failFast = options.failFast === true; this.failWithoutAssertions = options.failWithoutAssertions !== false; this.file = options.file; @@ -267,6 +268,7 @@ class Runner extends Emittery { async runHooks(tasks, contextRef, titleSuffix) { const hooks = tasks.map(task => new Runnable({ contextRef, + experiments: this.experiments, failWithoutAssertions: false, fn: task.args.length === 0 ? task.implementation : @@ -307,6 +309,7 @@ class Runner extends Emittery { // Only run the test if all `beforeEach` hooks passed. const test = new Runnable({ contextRef, + experiments: this.experiments, failWithoutAssertions: this.failWithoutAssertions, fn: task.args.length === 0 ? task.implementation : diff --git a/lib/test.js b/lib/test.js index 8479a6a92..303e0d2e7 100644 --- a/lib/test.js +++ b/lib/test.js @@ -70,6 +70,10 @@ class ExecutionContext extends assert.Assertions { }; this.try = async (...attemptArgs) => { + if (test.experiments.tryAssertion !== true) { + throw new Error('t.try() is currently an experiment. Opt in by setting `nonSemVerExperiments.tryAssertion` to `true`.'); + } + const {args, buildTitle, implementations, receivedImplementationArray} = parseTestArgs(attemptArgs); if (implementations.length === 0) { @@ -185,6 +189,7 @@ class ExecutionContext extends assert.Assertions { class Test { constructor(options) { this.contextRef = options.contextRef; + this.experiments = options.experiments || {}; this.failWithoutAssertions = options.failWithoutAssertions; this.fn = options.fn; this.metadata = options.metadata; diff --git a/lib/worker/subprocess.js b/lib/worker/subprocess.js index 90b829d6a..861f0ecfa 100644 --- a/lib/worker/subprocess.js +++ b/lib/worker/subprocess.js @@ -31,6 +31,7 @@ ipc.options.then(options => { } const runner = new Runner({ + experiments: options.experiments, failFast: options.failFast, failWithoutAssertions: options.failWithoutAssertions, file: options.file, diff --git a/test/helper/ava-test.js b/test/helper/ava-test.js index 5d38b4539..36cbf67f6 100644 --- a/test/helper/ava-test.js +++ b/test/helper/ava-test.js @@ -1,48 +1,57 @@ const Test = require('../../lib/test'); const ContextRef = require('../../lib/context-ref'); -function ava(fn, contextRef) { - return new Test({ - contextRef: contextRef || new ContextRef(), - failWithoutAssertions: true, - fn, - registerUniqueTitle: () => true, - metadata: {type: 'test', callback: false}, - title: 'test' - }); -} +function withExperiments(experiments = {}) { + function ava(fn, contextRef) { + return new Test({ + contextRef: contextRef || new ContextRef(), + experiments, + failWithoutAssertions: true, + fn, + registerUniqueTitle: () => true, + metadata: {type: 'test', callback: false}, + title: 'test' + }); + } + + ava.failing = (fn, contextRef) => { + return new Test({ + contextRef: contextRef || new ContextRef(), + experiments, + failWithoutAssertions: true, + fn, + registerUniqueTitle: () => true, + metadata: {type: 'test', callback: false, failing: true}, + title: 'test.failing' + }); + }; -ava.failing = (fn, contextRef) => { - return new Test({ - contextRef: contextRef || new ContextRef(), - failWithoutAssertions: true, - fn, - registerUniqueTitle: () => true, - metadata: {type: 'test', callback: false, failing: true}, - title: 'test.failing' - }); -}; + ava.cb = (fn, contextRef) => { + return new Test({ + contextRef: contextRef || new ContextRef(), + experiments, + failWithoutAssertions: true, + fn, + registerUniqueTitle: () => true, + metadata: {type: 'test', callback: true}, + title: 'test.cb' + }); + }; -ava.cb = (fn, contextRef) => { - return new Test({ - contextRef: contextRef || new ContextRef(), - failWithoutAssertions: true, - fn, - registerUniqueTitle: () => true, - metadata: {type: 'test', callback: true}, - title: 'test.cb' - }); -}; + ava.cb.failing = (fn, contextRef) => { + return new Test({ + contextRef: contextRef || new ContextRef(), + experiments, + failWithoutAssertions: true, + fn, + registerUniqueTitle: () => true, + metadata: {type: 'test', callback: true, failing: true}, + title: 'test.cb.failing' + }); + }; -ava.cb.failing = (fn, contextRef) => { - return new Test({ - contextRef: contextRef || new ContextRef(), - failWithoutAssertions: true, - fn, - registerUniqueTitle: () => true, - metadata: {type: 'test', callback: true, failing: true}, - title: 'test.cb.failing' - }); -}; + return ava; +} -exports.ava = ava; +exports.ava = withExperiments(); +exports.withExperiments = withExperiments; diff --git a/test/test-try-commit.js b/test/test-try-commit.js index f2fb8c06b..759e74a9f 100644 --- a/test/test-try-commit.js +++ b/test/test-try-commit.js @@ -5,7 +5,9 @@ require('../lib/worker/options').set({color: false}); const {test} = require('tap'); const delay = require('delay'); const ContextRef = require('../lib/context-ref'); -const {ava} = require('./helper/ava-test'); +const {withExperiments} = require('./helper/ava-test'); + +const ava = withExperiments({tryAssertion: true}); test('try-commit works', async t => { const instance = ava(async a => { diff --git a/test/try-snapshot.js b/test/try-snapshot.js index d524c1d80..864533af1 100644 --- a/test/try-snapshot.js +++ b/test/try-snapshot.js @@ -10,6 +10,7 @@ const ContextRef = require('../lib/context-ref'); function setup(title, manager, fn) { return new Test({ + experiments: {tryAssertion: true}, fn, failWithoutAssertions: true, metadata: {type: 'test', callback: false},