Skip to content

Commit 89f9cda

Browse files
committed
Create schema-test-coverage.mjs
1 parent 20fe7a7 commit 89f9cda

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

scripts/schema-test-coverage.mjs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { readdir, readFile } from "node:fs/promises";
2+
import YAML from "yaml";
3+
import { join } from "node:path";
4+
import { argv } from "node:process";
5+
import "@hyperjump/json-schema/draft-2020-12";
6+
import { compile, getSchema, interpret, Validation } from "@hyperjump/json-schema/experimental";
7+
import * as Instance from "@hyperjump/json-schema/instance/experimental";
8+
9+
/**
10+
* @import { AST } from "@hyperjump/json-schema/experimental"
11+
* @import { Json } from "@hyperjump/json-schema"
12+
*/
13+
14+
import contentTypeParser from "content-type";
15+
import { addMediaTypePlugin } from "@hyperjump/browser";
16+
import { buildSchemaDocument } from "@hyperjump/json-schema/experimental";
17+
18+
addMediaTypePlugin("application/schema+yaml", {
19+
parse: async (response) => {
20+
const contentType = contentTypeParser.parse(response.headers.get("content-type") ?? "");
21+
const contextDialectId = contentType.parameters.schema ?? contentType.parameters.profile;
22+
23+
const foo = YAML.parse(await response.text());
24+
return buildSchemaDocument(foo, response.url, contextDialectId);
25+
},
26+
fileMatcher: (path) => path.endsWith(".yaml")
27+
});
28+
29+
/** @type (testDirectory: string) => AsyncGenerator<Json> */
30+
const tests = async function* (testDirectory) {
31+
for (const file of await readdir(testDirectory, { recursive: true, withFileTypes: true })) {
32+
if (!file.isFile() || !file.name.endsWith(".yaml")) {
33+
continue;
34+
}
35+
36+
const testPath = join(file.parentPath, file.name);
37+
const testJson = await readFile(testPath, "utf8");
38+
yield YAML.parse(testJson);
39+
}
40+
};
41+
42+
/** @type (testDirectory: string) => Promise<void> */
43+
const runTests = async (testDirectory) => {
44+
for await (const test of tests(testDirectory)) {
45+
const instance = Instance.fromJs(test);
46+
47+
interpret(compiled, instance);
48+
}
49+
};
50+
51+
/** @type (ast: AST) => string[] */
52+
const keywordLocations = (ast) => {
53+
/** @type string[] */
54+
const locations = [];
55+
for (const schemaLocation in ast) {
56+
if (schemaLocation === "metaData") {
57+
continue;
58+
}
59+
60+
if (Array.isArray(ast[schemaLocation])) {
61+
for (const keyword of ast[schemaLocation]) {
62+
if (Array.isArray(keyword)) {
63+
locations.push(keyword[1]);
64+
}
65+
}
66+
}
67+
}
68+
69+
return locations;
70+
};
71+
72+
///////////////////////////////////////////////////////////////////////////////
73+
74+
const schema = await getSchema(argv[2]);
75+
const compiled = await compile(schema);
76+
77+
/** @type Set<string> */
78+
const visitedLocations = new Set();
79+
const baseInterpret = Validation.interpret;
80+
Validation.interpret = (url, instance, ast, dynamicAnchors, quiet) => {
81+
if (Array.isArray(ast[url])) {
82+
for (const keywordNode of ast[url]) {
83+
if (Array.isArray(keywordNode)) {
84+
visitedLocations.add(keywordNode[1]);
85+
}
86+
}
87+
}
88+
return baseInterpret(url, instance, ast, dynamicAnchors, quiet);
89+
};
90+
91+
await runTests(argv[3]);
92+
Validation.interpret = baseInterpret;
93+
94+
// console.log("Covered:", visitedLocations);
95+
96+
const allKeywords = keywordLocations(compiled.ast);
97+
const notCovered = allKeywords.filter((location) => !visitedLocations.has(location));
98+
console.log("NOT Covered:", notCovered.length, "of", allKeywords.length,);
99+
100+
const firstNotCovered = notCovered.slice(0, 20);
101+
if (notCovered.length > 20) firstNotCovered.push("...");
102+
console.log(firstNotCovered);
103+
104+
console.log("Covered:", visitedLocations.size, "of", allKeywords.length, "(" + Math.floor(visitedLocations.size / allKeywords.length * 100) + "%)");

0 commit comments

Comments
 (0)