From 80ca1e61b5d1151ec3951ce63b2675941786983f Mon Sep 17 00:00:00 2001 From: xpyctumo <30053565+xpyctumo@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:17:33 +0000 Subject: [PATCH] test: More AST generators. Simplified logger by removing `dummySrcInfo` in the output --- jest.config.js | 1 + jest.setup.js | 53 +++++++++++++++++ src/test/prettyPrint/expressions.spec.ts | 8 +++ src/test/utils/expression/randomAst.ts | 72 ++++++++++++++++++++++++ 4 files changed, 134 insertions(+) diff --git a/jest.config.js b/jest.config.js index 6e143384e..e4a63d203 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,7 @@ module.exports = { testPathIgnorePatterns: ["/node_modules/", "/dist/"], maxWorkers: "50%", globalSetup: "./jest.setup.js", + setupFiles: ["./jest.setup.js"], globalTeardown: "./jest.teardown.js", snapshotSerializers: ["@tact-lang/ton-jest/serializers"], }; diff --git a/jest.setup.js b/jest.setup.js index 454fcbf08..338fe28cb 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,8 +1,61 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const coverage = require("@tact-lang/coverage"); +const fc = require("fast-check"); module.exports = async () => { if (process.env.COVERAGE === "true") { coverage.beginCoverage(); } }; + +function sanitizeObject( + obj, + options = { + excludeKeys: [], + valueTransformers: {}, + }, +) { + const { excludeKeys, valueTransformers } = options; + + if (Array.isArray(obj)) { + return obj.map((item) => sanitizeObject(item, options)); + } else if (obj !== null && typeof obj === "object") { + const newObj = {}; + for (const [key, value] of Object.entries(obj)) { + if (!excludeKeys.includes(key)) { + const transformer = valueTransformers[key]; + newObj[key] = transformer + ? transformer(value) + : sanitizeObject(value, options); + } + } + return newObj; + } + return obj; +} + +fc.configureGlobal({ + reporter: (log) => { + if (log.failed) { + const sanitizedCounterexample = sanitizeObject(log.counterexample, { + excludeKeys: ["id", "loc"], + valueTransformers: { + value: (val) => + typeof val === "bigint" ? val.toString() : val, + }, + }); + + const errorMessage = ` +=== + Property failed after ${log.numRuns} tests + Seed: ${log.seed} + Path: ${log.counterexamplePath} + Counterexample: ${JSON.stringify(sanitizedCounterexample, null, 0)} + Errors: ${log.error ? log.error : "Unknown error"} +=== + `; + + throw new Error(errorMessage); + } + }, +}); diff --git a/src/test/prettyPrint/expressions.spec.ts b/src/test/prettyPrint/expressions.spec.ts index 3da01c21a..887f26d1e 100644 --- a/src/test/prettyPrint/expressions.spec.ts +++ b/src/test/prettyPrint/expressions.spec.ts @@ -7,6 +7,10 @@ import { randomAstOpBinary, randomAstOpUnary, randomAstExpression, + randomAstInitOf, + randomAstNull, + randomAstStaticCall, + randomAstStructInstance, } from "../utils/expression/randomAst"; describe("Pretty Print Expressions", () => { @@ -21,6 +25,10 @@ describe("Pretty Print Expressions", () => { ], ["AstOpBinary", randomAstOpBinary(expression(), expression())], ["AstOpUnary", randomAstOpUnary(expression())], + ["AstNull", randomAstNull()], + ["AstInitOf", randomAstInitOf(expression())], + ["AstStaticCall", randomAstStaticCall(expression())], + ["AstStructInstance", randomAstStructInstance(expression())], ]; cases.forEach(([caseName, astGenerator]) => { diff --git a/src/test/utils/expression/randomAst.ts b/src/test/utils/expression/randomAst.ts index ed97c1467..75bdc527d 100644 --- a/src/test/utils/expression/randomAst.ts +++ b/src/test/utils/expression/randomAst.ts @@ -3,10 +3,16 @@ import { AstBoolean, AstConditional, AstExpression, + AstId, + AstInitOf, + AstNull, AstNumber, AstOpBinary, AstOpUnary, + AstStaticCall, AstString, + AstStructFieldInitializer, + AstStructInstance, } from "../../../grammar/ast"; import { dummySrcInfo } from "../../../grammar/src-info"; @@ -112,6 +118,72 @@ export function randomAstConditional( ); } +function randomAstId(): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("id"), + text: fc.string().filter((text) => /^[A-Za-z_]+$/.test(text)), + // Rules for text value are in src/grammar/grammar.ohm + }), + ); +} + +export function randomAstNull(): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("null"), + }), + ); +} + +export function randomAstInitOf( + expression: fc.Arbitrary, +): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("init_of"), + contract: randomAstId(), + args: fc.array(expression), + }), + ); +} + +export function randomAstStaticCall( + expression: fc.Arbitrary, +): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("static_call"), + function: randomAstId(), + args: fc.array(expression), + }), + ); +} + +function randomAstStructFieldInitializer( + expression: fc.Arbitrary, +): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("struct_field_initializer"), + field: randomAstId(), + initializer: expression, + }), + ); +} + +export function randomAstStructInstance( + expression: fc.Arbitrary, +): fc.Arbitrary { + return dummyAstNode( + fc.record({ + kind: fc.constant("struct_instance"), + type: randomAstId(), + args: fc.array(randomAstStructFieldInitializer(expression)), + }), + ); +} + export function randomAstExpression( maxShrinks: number, ): fc.Arbitrary {