Skip to content

Commit

Permalink
test: parser/printer fuzz tests for expressions (tact-lang#1248)
Browse files Browse the repository at this point in the history
  • Loading branch information
xpyctumo authored and sansx committed Feb 10, 2025
1 parent f2c3b59 commit 11122f3
Show file tree
Hide file tree
Showing 11 changed files with 558 additions and 8 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ src/func/funcfiftlib.js
**/grammar.ohm*.js
src/grammar/next/grammar.ts
jest.setup.js
jest.globalSetup.js
jest.teardown.js
/docs
version.build.ts
Expand Down
20 changes: 20 additions & 0 deletions dev-docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,23 @@ The project contains special TypeScript files with the `.build.ts` extension tha
A typical example is [test/contracts.build.ts](https://github.com/tact-lang/tact/blob/132fe4ad7f030671d28740313b9d573fd8829684/src/test/contracts.build.ts) which builds contract tests.

When adding new build or test scripts, make sure to use the `.build.ts` extension to keep them separate from the main compiler code.

## Random AST Expression Generation

To generate and inspect random Abstract Syntax Trees (ASTs), you can use the `yarn random-ast` command. This command generates a specified number of random Abstract Syntax Trees (ASTs) and pretty-prints them.

Note: At the moment only Ast Expression will be generated

### Usage

`yarn random-ast <count>`

Where `<count>` is the number of random expressions to generate.

### Example

`yarn random-ast 42`

This will produce 42 random expressions and pretty-print them in the terminal.

The implementation can be found in [random-ast.ts](../src/ast/random-ast.ts).
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module.exports = {
testEnvironment: "node",
testPathIgnorePatterns: ["/node_modules/", "/dist/"],
maxWorkers: "8",
globalSetup: "./jest.setup.js",
globalSetup: "./jest.globalSetup.js",
setupFiles: ["./jest.setup.js"],
globalTeardown: "./jest.teardown.js",
snapshotSerializers: ["@tact-lang/ton-jest/serializers"],
};
8 changes: 8 additions & 0 deletions jest.globalSetup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const coverage = require("@tact-lang/coverage");

module.exports = async () => {
if (process.env.COVERAGE === "true") {
coverage.beginCoverage();
}
};
54 changes: 49 additions & 5 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
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);
}
},
});
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"prepack": "pinst --disable",
"postpack": "pinst --enable",
"next-version": "ts-node version.build.ts",
"random-ast": "ts-node ./src/ast/random-ast.ts",
"top10": "find . -type f -exec du -h {} + | sort -rh | head -n 10"
},
"files": [
Expand All @@ -55,7 +56,8 @@
"!**/*.build.*",
"!**/*.spec.js",
"!**/*.spec.d.ts",
"!**/*.tsbuildinfo"
"!**/*.tsbuildinfo",
"!**/*.infra.ts"
],
"main": "./dist/index.js",
"bin": {
Expand Down Expand Up @@ -86,6 +88,7 @@
"@ton/sandbox": "^0.24.0",
"@ton/test-utils": "^0.5.0",
"@tonstudio/pgen": "^0.0.1",
"@types/diff": "^7.0.0",
"@types/glob": "^8.1.0",
"@types/jest": "^29.5.12",
"@types/json-bigint": "^1.0.4",
Expand All @@ -96,7 +99,9 @@
"chalk": "4.1.2",
"cross-env": "^7.0.3",
"cspell": "^8.8.3",
"diff": "^7.0.0",
"eslint": "^8.57.0",
"fast-check": "^3.23.2",
"glob": "^8.1.0",
"husky": "^9.1.5",
"jest": "^29.3.1",
Expand Down
1 change: 1 addition & 0 deletions spell/cspell-list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,4 @@ workchains
xffff
xtwitter
привет
letrec
34 changes: 34 additions & 0 deletions src/ast/fuzz.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import fc from "fast-check";
import { getParser } from "../grammar";
import { eqExpressions, getAstFactory } from "../ast/ast-helpers";
import { diffAstObjects, randomAstExpression } from "./random.infra";
import { prettyPrint } from "./ast-printer";

describe("Pretty Print Expressions", () => {
const maxDepth = 4;
const parser = getParser(getAstFactory(), "new");

it(`should parse AstExpression`, () => {
fc.assert(
fc.property(randomAstExpression(maxDepth), (generatedAst) => {
const prettyBefore = prettyPrint(generatedAst);

const parsedAst = parser.parseExpression(prettyBefore);
const prettyAfter = prettyPrint(parsedAst);

expect(prettyBefore).toBe(prettyAfter);
const actual = eqExpressions(generatedAst, parsedAst);
if (!actual) {
diffAstObjects(
generatedAst,
parsedAst,
prettyBefore,
prettyAfter,
);
}
expect(actual).toBe(true);
}),
{ seed: 1, numRuns: 5000 },
);
});
});
22 changes: 22 additions & 0 deletions src/ast/random-ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env ts-node
import fc from "fast-check";
import { randomAstExpression } from "./random.infra";
import { prettyPrint } from "./ast-printer";

const args = process.argv.slice(2);
if (args.length !== 1) {
console.error("Usage: yarn random-ast <count>");
process.exit(1);
}

const count = parseInt(args[0] ?? "", 10);
if (isNaN(count) || count <= 0) {
console.error("Error: Count must be a positive integer");
process.exit(1);
}

fc.sample(randomAstExpression(4), count).forEach((expression, index) => {
console.log(`Expression ${index + 1}:`);
console.log(prettyPrint(expression));
console.log("-".repeat(80));
});
Loading

0 comments on commit 11122f3

Please sign in to comment.