-
Notifications
You must be signed in to change notification settings - Fork 30k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Work in progress PR-URL: #43525 Refs: #43344 Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
- Loading branch information
1 parent
f720c58
commit 016749b
Showing
19 changed files
with
4,418 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
'use strict'; | ||
|
||
const { | ||
ArrayPrototypeFilter, | ||
ArrayPrototypeFind, | ||
NumberParseInt, | ||
} = primordials; | ||
const { | ||
codes: { ERR_TAP_VALIDATION_ERROR }, | ||
} = require('internal/errors'); | ||
const { TokenKind } = require('internal/test_runner/tap_lexer'); | ||
|
||
// TODO(@manekinekko): add more validation rules based on the TAP14 spec. | ||
// See https://testanything.org/tap-version-14-specification.html | ||
class TAPValidationStrategy { | ||
validate(ast) { | ||
this.#validateVersion(ast); | ||
this.#validatePlan(ast); | ||
this.#validateTestPoints(ast); | ||
|
||
return true; | ||
} | ||
|
||
#validateVersion(ast) { | ||
const entry = ArrayPrototypeFind( | ||
ast, | ||
(node) => node.kind === TokenKind.TAP_VERSION | ||
); | ||
|
||
if (!entry) { | ||
throw new ERR_TAP_VALIDATION_ERROR('missing TAP version'); | ||
} | ||
|
||
const { version } = entry.node; | ||
|
||
// TAP14 specification is compatible with observed behavior of existing TAP13 consumers and producers | ||
if (version !== '14' && version !== '13') { | ||
throw new ERR_TAP_VALIDATION_ERROR('TAP version should be 13 or 14'); | ||
} | ||
} | ||
|
||
#validatePlan(ast) { | ||
const entry = ArrayPrototypeFind( | ||
ast, | ||
(node) => node.kind === TokenKind.TAP_PLAN | ||
); | ||
|
||
if (!entry) { | ||
throw new ERR_TAP_VALIDATION_ERROR('missing TAP plan'); | ||
} | ||
|
||
const plan = entry.node; | ||
|
||
if (!plan.start) { | ||
throw new ERR_TAP_VALIDATION_ERROR('missing plan start'); | ||
} | ||
|
||
if (!plan.end) { | ||
throw new ERR_TAP_VALIDATION_ERROR('missing plan end'); | ||
} | ||
|
||
const planStart = NumberParseInt(plan.start, 10); | ||
const planEnd = NumberParseInt(plan.end, 10); | ||
|
||
if (planEnd !== 0 && planStart > planEnd) { | ||
throw new ERR_TAP_VALIDATION_ERROR( | ||
`plan start ${planStart} is greater than plan end ${planEnd}` | ||
); | ||
} | ||
} | ||
|
||
// TODO(@manekinekko): since we are dealing with a flat AST, we need to | ||
// validate test points grouped by their "nesting" level. This is because a set of | ||
// Test points belongs to a TAP document. Each new subtest block creates a new TAP document. | ||
// https://testanything.org/tap-version-14-specification.html#subtests | ||
#validateTestPoints(ast) { | ||
const bailoutEntry = ArrayPrototypeFind( | ||
ast, | ||
(node) => node.kind === TokenKind.TAP_BAIL_OUT | ||
); | ||
const planEntry = ArrayPrototypeFind( | ||
ast, | ||
(node) => node.kind === TokenKind.TAP_PLAN | ||
); | ||
const testPointEntries = ArrayPrototypeFilter( | ||
ast, | ||
(node) => node.kind === TokenKind.TAP_TEST_POINT | ||
); | ||
|
||
const plan = planEntry.node; | ||
|
||
const planStart = NumberParseInt(plan.start, 10); | ||
const planEnd = NumberParseInt(plan.end, 10); | ||
|
||
if (planEnd === 0 && testPointEntries.length > 0) { | ||
throw new ERR_TAP_VALIDATION_ERROR( | ||
`found ${testPointEntries.length} Test Point${ | ||
testPointEntries.length > 1 ? 's' : '' | ||
} but plan is ${planStart}..0` | ||
); | ||
} | ||
|
||
if (planEnd > 0) { | ||
if (testPointEntries.length === 0) { | ||
throw new ERR_TAP_VALIDATION_ERROR('missing Test Points'); | ||
} | ||
|
||
if (!bailoutEntry && testPointEntries.length !== planEnd) { | ||
throw new ERR_TAP_VALIDATION_ERROR( | ||
`test Points count ${testPointEntries.length} does not match plan count ${planEnd}` | ||
); | ||
} | ||
|
||
for (let i = 0; i < testPointEntries.length; i++) { | ||
const test = testPointEntries[i].node; | ||
const testId = NumberParseInt(test.id, 10); | ||
|
||
if (testId < planStart || testId > planEnd) { | ||
throw new ERR_TAP_VALIDATION_ERROR( | ||
`test ${testId} is out of plan range ${planStart}..${planEnd}` | ||
); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// TAP14 and TAP13 are compatible with each other | ||
class TAP13ValidationStrategy extends TAPValidationStrategy {} | ||
class TAP14ValidationStrategy extends TAPValidationStrategy {} | ||
|
||
class TapChecker { | ||
static TAP13 = '13'; | ||
static TAP14 = '14'; | ||
|
||
constructor({ specs }) { | ||
switch (specs) { | ||
case TapChecker.TAP13: | ||
this.strategy = new TAP13ValidationStrategy(); | ||
break; | ||
default: | ||
this.strategy = new TAP14ValidationStrategy(); | ||
} | ||
} | ||
|
||
check(ast) { | ||
return this.strategy.validate(ast); | ||
} | ||
} | ||
|
||
module.exports = { | ||
TapChecker, | ||
TAP14ValidationStrategy, | ||
TAP13ValidationStrategy, | ||
}; |
Oops, something went wrong.