-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathverifier.js
More file actions
133 lines (106 loc) · 4.76 KB
/
Copy pathverifier.js
File metadata and controls
133 lines (106 loc) · 4.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { createAllProviders, resolveType, resolveMethodReturn, getGlobals } from './providers.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const snippets = JSON.parse(
readFileSync(join(__dirname, 'data', 'eval-snippets.json'), 'utf8')
);
// Type tag names for readable output
const TYPE_TAGS = { 0: 'Delayed', 1: 'Object', 2: 'Primitive', 3: 'List', 4: 'Method', 5: 'Any' };
// Steps that operate on a series result rather than navigating the type tree.
// When we hit a Method type and the next step is one of these, we've reached
// the series and should stop early rather than report a failure.
const SERIES_OPS = new Set([
'get series', 'get the data',
'with key', 'and value',
'take', 'skip', 'shuffle', 'reverse', 'sortKeys', 'sortValues',
'setProperties', 'map', 'append',
]);
// Follow a single step from the current type. Returns { typ, status } where
// status is 'ok', 'series_stop', 'not_found', or 'wrong_type'.
async function followStep(typ, step) {
typ = await resolveType(typ);
if (!typ) return { typ: null, status: 'wrong_type', detail: 'null type' };
if (typ.tag !== 1) {
if (typ.tag === 4) {
const baseName = step.replace(/\([^)]*\)$/, '');
if (SERIES_OPS.has(baseName) || SERIES_OPS.has(step))
return { typ, status: 'series_stop' };
// Call through the method using its declared arg types to get the return type,
// then retry the step from there.
const retTyp = await resolveMethodReturn(typ);
return retTyp ? followStep(retTyp, step) : { typ, status: 'wrong_type', detail: 'Method (no return type)' };
}
return { typ, status: 'wrong_type', detail: TYPE_TAGS[typ.tag] ?? `tag ${typ.tag}` };
}
const members = typ.fields[0].Members;
// Strip (args) suffix when looking up the member name
const baseName = step.replace(/\([^)]*\)$/, '');
const member = members.find(m => m.Name === baseName || m.Name === step);
if (!member) return { typ, status: 'not_found', memberCount: members.length };
return { typ: member.Type, status: 'ok' };
}
// Returns { ok, navSteps, seriesTail } where navSteps are the verified
// navigation steps and seriesTail is the remaining steps after a series_stop.
async function verifyChain(entities, chain) {
const [providerName, ...steps] = chain.steps;
const entity = entities.find(e => e.Kind.fields[0].Name === providerName);
if (!entity)
return { ok: false, navSteps: [{ step: providerName, status: 'not_found', detail: 'not in globals' }], seriesTail: [] };
let typ = entity.Type;
const navSteps = [];
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
const { typ: nextTyp, status, detail, memberCount } = await followStep(typ, step);
if (status === 'series_stop')
return { ok: true, navSteps, seriesTail: steps.slice(i) };
navSteps.push({ step, status, detail, memberCount });
if (status !== 'ok')
return { ok: false, navSteps, seriesTail: [] };
typ = nextTyp;
}
return { ok: true, navSteps, seriesTail: [] };
}
async function main() {
process.stdout.write('Setting up providers...');
const p = createAllProviders();
const entities = await getGlobals(p);
process.stdout.write(`\r${' '.repeat(30)}\r`);
let totalChains = 0, passed = 0;
const failures = [];
for (const snippet of snippets) {
for (const chain of snippet.chains) {
totalChains++;
process.stdout.write(`Verifying #${snippet.id} [${chain.provider}]...\r`);
const { ok, navSteps, seriesTail } = await verifyChain(entities, chain);
if (ok) {
passed++;
if (seriesTail.length)
console.log(`OK #${snippet.id} [${chain.provider}] → series: ${seriesTail.join(' > ')}`);
} else {
failures.push({ snippet, chain, navSteps });
}
}
}
process.stdout.write(' '.repeat(40) + '\r');
if (failures.length === 0) {
console.log(`All ${totalChains} chains verified OK`);
return;
}
for (const { snippet, chain, navSteps } of failures) {
const bad = navSteps.find(r => r.status !== 'ok');
const okCount = navSteps.filter(r => r.status === 'ok').length;
console.log(`FAIL #${snippet.id} [${chain.provider}] ${snippet.title}`);
console.log(` Passed ${okCount}/${chain.steps.length - 1} steps, stopped at: "${bad.step}"`);
if (bad.status === 'not_found')
console.log(` Not found among ${bad.memberCount} members`);
else if (bad.status === 'wrong_type')
console.log(` Type was: ${bad.detail}`);
else if (bad.detail)
console.log(` ${bad.detail}`);
console.log();
}
console.log(`Results: ${passed}/${totalChains} passed, ${failures.length} failed`);
}
main().catch(e => { console.error(e); process.exit(1); });