@kstasi/jest-tolk
Version:
<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tonkite/tonkite/main/assets/logo-dark.svg"> <img alt="tonkite logo" src="https://raw.githubusercontent.com/tonkite/tonkite/main/a
252 lines (251 loc) • 10 kB
JavaScript
;
/**
* Copyright 2024 Scaleton Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const test_result_1 = require("@jest/test-result");
const tolk_js_1 = require("@ton/tolk-js");
const fs = __importStar(require("node:fs"));
const sandbox_1 = require("@ton/sandbox");
const core_1 = require("@ton/core");
const jest_message_util_1 = require("jest-message-util");
const chalk_1 = __importDefault(require("chalk"));
const assert = __importStar(require("node:assert"));
const annotations_1 = require("./annotations");
const source_code_1 = require("./source-code");
const fuzz_1 = require("./fuzz");
const utils_1 = require("./utils");
const fixture_1 = require("./fixture");
const runTest = async ({ testPath, config, globalConfig, }) => {
const entrypointFileName = testPath.replace(config.rootDir + '/', '');
const result = await compileTest(entrypointFileName);
if (result.status !== 'ok') {
throw result.message;
}
const testSourceCode = findTestSourceCode(result, entrypointFileName);
const testCases = await (0, source_code_1.extractGetMethods)(testSourceCode.contents);
const fixtureGetters = filterFixtureGetters(testCases);
const { executor, code, data } = await setupExecutor(result.codeBoc64);
const fixtures = await (0, fixture_1.extractFixtures)(executor, code, data, fixtureGetters);
const testNamePattern = globalConfig.testNamePattern &&
new RegExp(globalConfig.testNamePattern, 'i');
let numFailingTests = 0;
let numPassingTests = 0;
let numPendingTests = 0;
let numTodoTests = 0;
const testResults = [];
for (const testCase of testCases) {
const annotations = testCase.docBlock
? (0, annotations_1.extractAnnotationsFromDocBlock)(testCase.docBlock)
: {};
const isTest = testCase.methodName.startsWith('test_') || annotations.test;
if (!isTest) {
continue;
}
const testCaseName = testCase.methodName
.replace(/^test_/, '')
.replace(/_/g, ' ');
if (annotations.skip ||
(testNamePattern && !testNamePattern.test(testCase.methodName))) {
testResults.push(createTestResult('pending', testCaseName, annotations, 0));
numPendingTests += 1;
continue;
}
if (annotations.todo) {
testResults.push(createTestResult('todo', testCaseName, annotations, 0));
numTodoTests += 1;
continue;
}
try {
const start = Date.now();
const { output, input, debugLogs } = await runSingleTestCase(executor, code, data, testCase.methodId ?? (0, core_1.getMethodId)(testCase.methodName), annotations, fixtures, testCase.argTypes);
const end = Date.now();
validateTestOutput(input, output, annotations, debugLogs);
testResults.push(createTestResult('passed', testCaseName, annotations, end - start, undefined, output.success ? output.gas_used : undefined));
numPassingTests += 1;
}
catch (error) {
testResults.push(createTestResult('failed', testCaseName, annotations, 0, error));
numFailingTests += 1;
}
}
return {
...(0, test_result_1.createEmptyTestResult)(),
failureMessage: (0, jest_message_util_1.formatResultsErrors)(testResults, config, globalConfig, testPath),
numFailingTests,
numPassingTests,
numPendingTests,
numTodoTests,
testResults,
testFilePath: testPath,
};
};
async function compileTest(entrypointFileName) {
return await (0, tolk_js_1.runTolkCompiler)({
entrypointFileName,
fsReadCallback: (path) => readFileContent(path, entrypointFileName),
withStackComments: true,
});
}
function readFileContent(path, entrypointFileName) {
const content = fs.readFileSync(path).toString();
if (path === entrypointFileName &&
!content.match(/(^|\n)\s*\/\/\s+@no-main/)) {
return content + '\n\n' + 'fun main() {}';
}
return content;
}
function findTestSourceCode(result, entrypointFileName) {
const testSourceCode = result.sourcesSnapshot.find(({ filename }) => filename === entrypointFileName);
if (!testSourceCode) {
throw new Error(`Expected behaviour: ${entrypointFileName} not found in a snapshot.`);
}
return testSourceCode;
}
function filterFixtureGetters(getMethods) {
return getMethods.filter((method) => method.methodName.startsWith('fixture_'));
}
async function setupExecutor(codeBoc64) {
const executor = await sandbox_1.Executor.create();
const code = core_1.Cell.fromBase64(codeBoc64);
const data = (0, core_1.beginCell)().endCell();
return { executor, code, data };
}
async function runSingleTestCase(executor, code, data, methodId, annotations, fixtures, argTypes) {
return annotations.fuzz
? await (0, fuzz_1.executeFuzzTest)(executor, code, data, methodId, annotations, fixtures, argTypes ?? [])
: await (0, utils_1.runGetMethodWithDefaults)({
executor,
code,
data,
methodId,
unixTime: annotations.unixTime,
balance: annotations.balance,
stack: undefined,
gasLimit: annotations.gasLimit,
});
}
function validateTestOutput(input, output, annotations, debugLogs) {
if (!output.success) {
throw `Execution failed: ${output.error}`;
}
const inputString = input.length
? `\n\nInput: ${convertInputToString(input)}`
: '';
const debugString = debugLogs ? `\n\nDebug: ${debugLogs}` : '';
if (annotations.exitCode) {
assert.equal(output.vm_exit_code, annotations.exitCode, `Test case has thrown an error code ${output.vm_exit_code} (expected ${annotations.exitCode}).${inputString}${debugString}`);
}
else if (output.vm_exit_code !== 0) {
const stackTrace = extractStackTrace(output.vm_log);
throw new Error(`Test case has failed with an error code ${output.vm_exit_code}.\n\n[...]\n${stackTrace}${inputString}${debugString}`);
}
}
function convertInputToString(input) {
return input
.map((item) => {
if (item.type === 'int') {
return item.value.toString();
}
else if (['slice', 'cell', 'builder'].includes(item.type)) {
// @ts-ignore
return item.cell.toString();
}
else {
return '';
}
})
.join(', ');
}
function extractStackTrace(vmLogs) {
return vmLogs
.split(/\n/)
.filter((line) => {
if (line.startsWith('gas remaining:'))
return false;
if (line.startsWith('stack:'))
return false;
if (line.startsWith('code cell hash:'))
return false;
return true;
})
.map((line) => {
let matches;
if ((matches = line.match(/^execute ([^ $]+)(.*)/))) {
return chalk_1.default.cyan(matches[1]) + chalk_1.default.dim(matches[2]);
}
if ((matches = line.match(/^(handling exception code \d+:\s*)(.*)/))) {
return matches[1] + chalk_1.default.bold(matches[2]);
}
return line;
})
.slice(-8)
.join('\n');
}
function createTestResult(status, title, annotations, duration, error, gasUsed) {
const failureMessage = error
? typeof error === 'string'
? error
: error instanceof Error
? error.message
: `${error}`
: '';
return {
duration,
failureDetails: [],
failureMessages: failureMessage ? [failureMessage] : [],
numPassingAsserts: status === 'passed' ? 1 : 0,
status,
ancestorTitles: annotations.scope ? [annotations.scope] : [],
title: title + (gasUsed ? ` (gas: ${gasUsed})` : ''),
fullName: title,
};
}
module.exports = runTest;