Skip to content

Commit 65bc2c4

Browse files
rmgarayitsfarseen
authored andcommitted
added new report with failing methods
1 parent 098c21d commit 65bc2c4

File tree

2 files changed

+167
-45
lines changed

2 files changed

+167
-45
lines changed

tests/api/api.test.ts

+139-44
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// This module tests that the classes generated in out.ts match the CSL API
22
// at the method level
33
import fs from "node:fs";
4-
import { ClassInfo, ClassRename, MethodInfo, ParamInfo, SomeType } from "../test_types";
4+
import { ClassInfo, ClassRename, MethodComparisonResult, MethodInfo, ParamInfo, SomeType, TypeComparisonResult } from "../test_types";
55
import grammar, { TypeDefsSemantics } from "./grammar.ohm-bundle"
66
import { describe, test } from "@jest/globals";
77

@@ -190,6 +190,7 @@ const cdlClasses: Array<ClassInfo> = semantics(cdlMatch).classes();
190190
let cdlClassesMap: Map<string, MethodInfo[]> = new Map(cdlClasses.map((cls) => [cls.name, cls.methods]))
191191
console.log("Extracting renaming exports from CDL type definitions...");
192192
const classRenames: Array<ClassRename> = semantics(cdlMatch).renames();
193+
const classRenamesMap: Map<string, string> = new Map(classRenames.map((rename) => [rename.originalName, rename.newName]))
193194

194195
// We trace all the classes as parsed
195196
if (traceClassInfos) {
@@ -201,11 +202,10 @@ if (traceClassInfos) {
201202
}
202203
}
203204

204-
// Before filtering, we rename all classes that are re-exported with a different name
205+
// Before filtering, we add all classes that are re-exported with a different name
205206
for(const rename of classRenames) {
206207
const clsValue = cdlClassesMap.get(rename.originalName)
207208
if(clsValue) {
208-
cdlClassesMap.delete(rename.originalName);
209209
cdlClassesMap.set(rename.newName, clsValue);
210210
}
211211
}
@@ -277,9 +277,12 @@ for (const cls of cdlClasses) {
277277
console.log("compareToCslTests.length: ", compareToCslTests.length)
278278
console.log("compareToCdlTests.length: ", compareToCdlTests.length)
279279

280+
// We open a report file to write down each method comparison failure as we find it
281+
let methodFailuresFile = fs.openSync("tests/reports/api_failing_methods.csv", "w");
282+
fs.writeFileSync(methodFailuresFile, "Affected class,Method,Failure reason,Failure message\n");
283+
280284
// Used for debugging
281-
// 984
282-
const testN = 984;
285+
const testN = 2752;
283286
const testNConfig = { testTable: compareToCdlTests, srcMap: cslClassesMap };
284287
test.skip(`Test N. ${testN}`, () => {
285288
const { testTable, srcMap } = testNConfig;
@@ -288,25 +291,19 @@ test.skip(`Test N. ${testN}`, () => {
288291
});
289292

290293
// Tests
291-
describe("API coverage tests", () => {
292-
test("There should be no missing classes", () => {
293-
expect(missingClasses).toHaveLength(0);
294-
})
295-
test("There should be no missing methods", () => {
296-
expect(missingMethods).toHaveLength(0);
297-
})
298-
})
299-
describe("Compare each CSL class/method to its CDL counterpart", () => {
300-
test.each(compareToCslTests)("($n) Comparing CDL's $class . $comparedToMethod.name to CSL's", (params) => {
301-
compareToClass(cdlClassesMap, params.class, params.comparedToMethod);
302-
})
303-
});
304-
305-
describe("Compare each CDL class/method to its CSL counterpart", () => {
306-
test.each(compareToCdlTests)("($n) Comparing CSL's $class . $comparedToMethod.name to CDL's", (params) => {
307-
compareToClass(cslClassesMap, params.class, params.comparedToMethod);
308-
})
309-
});
294+
describe("API coverage tests", () => {
295+
test("There should be no missing classes", () => {
296+
expect(missingClasses).toHaveLength(0);
297+
})
298+
test("There should be no missing methods", () => {
299+
expect(missingMethods).toHaveLength(0);
300+
})
301+
})
302+
describe("Compare each CDL class/method to its CSL counterpart", () => {
303+
test.each(compareToCdlTests)("($n) Comparing CSL's $class . $comparedToMethod.name to CDL's", (params) => {
304+
compareToClass(cslClassesMap, params.class, params.comparedToMethod);
305+
})
306+
});
310307

311308
// Test Utils
312309
function prettyClassInfo(cls: ClassInfo): void {
@@ -320,36 +317,82 @@ function prettyClassInfo(cls: ClassInfo): void {
320317
console.log(msg);
321318
}
322319

320+
function prettyType(ty: SomeType): string {
321+
switch(ty.tag) {
322+
case "simple":
323+
return ty.ident;
324+
case "array":
325+
return `${prettyType(ty.type)}[]`;
326+
case "tuple":
327+
let tyStrs = ty.types.map((ty) => prettyType(ty));
328+
return `[${tyStrs.join(", ")}]`;
329+
case "nullable":
330+
return `(${prettyType(ty.type)} | undefined)`
331+
case "object": {
332+
let msg = "{";
333+
for (const [attrName, attrType] of ty.attrMap.entries()) {
334+
msg = `${msg} ${attrName}: ${prettyType(attrType)};`;
335+
}
336+
msg = `${msg} }`;
337+
return msg;
338+
}
339+
}
340+
}
341+
323342
function compareToClass(clss: Map<string, MethodInfo[]>, cls: string, comparedToMethod: MethodInfo) {
324343
const methods = clss.get(cls);
325344
if (methods) {
326345
const method = methods.find((info) => info.name == comparedToMethod.name)
327-
compareToMethod(method, comparedToMethod);
346+
if (method) {
347+
const cmpResult = compareToMethod(method, comparedToMethod);
348+
// Before testing, we write the result in the report file
349+
if (cmpResult != "success") {
350+
fs.writeFileSync(methodFailuresFile, `${cls},${method.name},${cmpResult.reason},${cmpResult.msg}\n`);
351+
}
352+
expect(cmpResult).toStrictEqual("success");
353+
}
328354
}
329355
}
330356

331-
function compareToMethod(method1: MethodInfo | undefined, method2: MethodInfo) {
332-
if (method1) {
333-
// Method names should match
334-
expect(method1.name).toEqual(method2.name);
335-
// Return types should match
336-
compareTypes(method1.returnType, method2.returnType);
337-
// Static qualifiers should match
338-
expect(method1.static).toEqual(method2.static);
339-
// Methods should have the same number of parameters
340-
expect(method1.params.length).toEqual(method2.params.length);
341-
// All parameter types should match (names do not, however)
342-
for (let i = 0; i < method1.params.length; i++) {
343-
const param1 = method1.params[i];
344-
const param2 = method2.params[i];
345-
// Types should match
346-
compareTypes(param1.type, param2.type);
357+
function compareToMethod(method1: MethodInfo, method2: MethodInfo): MethodComparisonResult {
358+
// Method names should match
359+
if (method1.name != method2.name) {
360+
return { reason: "method_names_dont_match", msg: `Expected ${method2.name}, received ${method1.name}`};
361+
}
362+
// Return types should match
363+
const returnCmp = compareTypes(method1.returnType, method2.returnType);
364+
if (returnCmp != "success") {
365+
return { reason: "return_types_dont_match", msg: `Expected ${prettyType(returnCmp.expected)}, received ${prettyType(returnCmp.received)}`};
366+
}
367+
// Static qualifiers should match
368+
if (method1.static != method2.static) {
369+
return { reason: "static_qualifiers_dont_match", msg: `Expected ${method2.static}, received ${method1.static}`};
370+
}
371+
// Methods should have the same number of parameters
372+
if (method1.params.length != method2.params.length) {
373+
return { reason: "number_of_parameters_doesnt_match", msg: `Expected ${method2.params.length}, received ${method1.params.length}`}
374+
}
375+
// All parameter types should match (names do not, however)
376+
let paramErrMsgs: Array<string> = [];
377+
for (let i = 0; i < method1.params.length; i++) {
378+
const param1 = method1.params[i];
379+
const param2 = method2.params[i];
380+
// Types should match
381+
const paramCmp = compareTypes(param1.type, param2.type);
382+
if (paramCmp != "success") {
383+
paramErrMsgs.push(`Expected ${param1.name} to have type ${prettyType(paramCmp.expected)}, instead it has type ${prettyType(paramCmp.received)}`);
347384
}
348385
}
386+
if (paramErrMsgs.length > 0) {
387+
return { reason: "parameter_types_dont_match", msg:paramErrMsgs.join(" | ")}
388+
}
389+
390+
return "success";
349391
}
350392

351393
// We normalize types. At runtime, `undefined` and `T` are equivalent, and so are `undefined | T`
352394
// and `T`. For this reason, we strip the `undefined`s to make type comparisons simpler.
395+
// We also take into account classes that are exported under a different name.
353396
function normalizeType(ty: SomeType): SomeType {
354397
switch (ty.tag) {
355398
case "nullable": {
@@ -369,22 +412,74 @@ function normalizeType(ty: SomeType): SomeType {
369412
return normalized;
370413
}
371414
case "simple": {
415+
const renamedTo = classRenamesMap.get(ty.ident);
372416
if (ty.ident == "BigNum") {
373417
return { tag: "simple", ident: "bigint" }
418+
} else if (renamedTo){
419+
return { tag: "simple", ident: renamedTo };
374420
} else {
375421
return ty;
376422
}
377423
}
378424
}
379425
}
380426

381-
function compareTypes(ty1: SomeType, ty2: SomeType) {
427+
function compareTypes(ty1: SomeType, ty2: SomeType): TypeComparisonResult {
382428
const nty1 = normalizeType(ty1);
383429
const nty2 = normalizeType(ty2);
384430
// if one of the types is undefined, then the types are equivalent
385431
if ((nty1.tag == "simple" && nty1.ident == "undefined") || (nty2.tag == "simple" && nty2.ident == "undefined")) {
386-
expect(true).toBeTruthy();
432+
return "success";
433+
// otherwise, check for strict equality
434+
} else if (!someTypeEquals(nty1, nty2)) {
435+
return { expected: nty2, received: nty1 }
387436
} else {
388-
expect(nty1).toEqual(nty2);
437+
return "success";
389438
}
390439
}
440+
441+
// strict SomeType equality
442+
function someTypeEquals(ty1: SomeType, ty2: SomeType) {
443+
if (ty1.tag == "simple" && ty2.tag == "simple") {
444+
// console.log("comparing simples")
445+
return ty1.ident === ty2.ident;
446+
}
447+
448+
if (ty1.tag == "nullable" && ty2.tag == "nullable") {
449+
// console.log("comparing nullables")
450+
return someTypeEquals(ty1.type, ty2.type);
451+
}
452+
453+
if (ty1.tag == "array" && ty2.tag == "array") {
454+
// console.log("comparing arrays")
455+
return someTypeEquals(ty1.type, ty2.type);
456+
}
457+
458+
if (ty1.tag == "tuple" && ty2.tag == "tuple") {
459+
// console.log("comparing tuples")
460+
if (ty1.types.length != ty2.types.length) {
461+
return false;
462+
}
463+
for (let i = 0; i < ty1.types.length; i++) {
464+
if (!someTypeEquals(ty1.types[i], ty2.types[i])) {
465+
return false;
466+
}
467+
}
468+
return true;
469+
}
470+
471+
if (ty1.tag == "object" && ty2.tag == "object") {
472+
// console.log("comparing objects")
473+
for (const [attrName, attrType1] of ty1.attrMap.entries()) {
474+
const attrType2 = ty2.attrMap.get(attrName);
475+
if (!attrType2 || !someTypeEquals(attrType1, attrType2)) {
476+
return false;
477+
}
478+
}
479+
return true;
480+
}
481+
482+
// tags don't match
483+
// console.log("tags don't match")
484+
return false;
485+
}

tests/test_types.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Type for transaction obtained from the blockchain
22
export type TransactionInfo = { hash: string, cbor: string };
33

4-
// Type for method signatures of a given class
4+
// Types for method signatures of a given class
55
export type ClassInfo = { name: string, methods: Array<MethodInfo> }
66
export type MethodInfo = { name: string, static: boolean, params: Array<ParamInfo>, returnType: SomeType }
77
export type ParamInfo = { name: string, type: SomeType }
@@ -22,4 +22,31 @@ export type SomeType =
2222
tag: "object",
2323
attrMap: Map<string, SomeType>
2424
}
25+
26+
// Type for exports that rename classes
2527
export type ClassRename = { originalName: string, newName: string };
28+
29+
// Types for method comparisons
30+
export type MethodComparisonResult = "success" | MethodComparisonFailure
31+
32+
export type MethodComparisonFailure =
33+
{
34+
"reason": MethodComparisonFailureReason
35+
"msg": string
36+
}
37+
38+
export type MethodComparisonFailureReason =
39+
"method_names_dont_match"
40+
| "return_types_dont_match"
41+
| "static_qualifiers_dont_match"
42+
| "number_of_parameters_doesnt_match"
43+
| "parameter_types_dont_match"
44+
45+
// Types for type comparison
46+
export type TypeComparisonResult = "success" | TypeComparisonFailure
47+
48+
export type TypeComparisonFailure =
49+
{
50+
"expected": SomeType
51+
, "received": SomeType
52+
}

0 commit comments

Comments
 (0)