@tonkite/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
277 lines (276 loc) • 11.4 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 node_crypto_1 = require("node:crypto");
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 testkit_1 = require("@tonkite/testkit");
const tolk_debug_symbols_1 = require("@scaleton/tolk-debug-symbols");
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');
}
const runTest = async ({ testPath, config, globalConfig, }) => {
for (const setupFile of config.setupFiles) {
try {
require(setupFile);
}
catch (error) {
throw new Error(`Setup file "${setupFile}" failed: ${error}`);
}
}
const entrypointFileName = testPath.replace(config.rootDir + '/', '');
const tolkCompilerConfig = {
entrypointFileName,
fsReadCallback: (path) => {
const content = fs.readFileSync(path).toString();
// NOTE: It's required to have either onInternalMessage() or main() method.
if (path == entrypointFileName) {
const disableMain = !!content.match(/(^|\n)\s*\/\/\s+@no-main/);
if (!disableMain) {
return content + '\n\n' + 'fun main() {}';
}
}
return content;
},
withStackComments: true,
};
const result = await (0, tolk_js_1.runTolkCompiler)(tolkCompilerConfig);
if (result.status !== 'ok') {
throw result.message;
}
const symbols = await (0, tolk_debug_symbols_1.collectDebugSymbols)(tolkCompilerConfig);
const testSourceCode = result.sourcesSnapshot.find(({ filename }) => filename === entrypointFileName);
if (!testSourceCode) {
throw new Error(`Expected behaviour: ${entrypointFileName} not found in a snapshot.`);
}
let numFailingTests = 0;
let numPassingTests = 0;
let numPendingTests = 0;
let numTodoTests = 0;
const testResults = [];
const testCases = await (0, source_code_1.extractGetMethods)(testSourceCode.contents);
// common setup
const executor = await testkit_1.TestKit.createExecutor();
const code = core_1.Cell.fromBase64(result.codeBoc64);
const data = (0, core_1.beginCell)().endCell();
const DEFAULT_ADDRESS = new core_1.Address(0, (0, node_crypto_1.randomBytes)(32));
const DEFAULT_RANDOM_SEED = (0, node_crypto_1.randomBytes)(32);
const DEFAULT_BALANCE = (0, core_1.toNano)('1');
const DEFAULT_GAS_LIMIT = 10_000;
const DEFAULT_UNIX_TIME = Math.floor(Date.now() / 1000);
const testNamePattern = globalConfig.testNamePattern &&
new RegExp(globalConfig.testNamePattern, 'i');
for (const testCase of testCases) {
let annotations = {};
let start = 0;
let end = 0;
const testCaseName = testCase.methodName
.replace(/^test_/, '')
.replace(/_/g, ' ');
try {
annotations = testCase.docBlock
? (0, annotations_1.extractAnnotationsFromDocBlock)(testCase.docBlock)
: {};
const isTest = testCase.methodName.startsWith('test_') || annotations.test;
if (!isTest) {
continue;
}
const skip = annotations.skip ||
(testNamePattern && !testNamePattern.test(testCase.methodName));
if (skip) {
testResults.push({
duration: end - start,
failureDetails: [],
failureMessages: [],
numPassingAsserts: 0,
status: 'pending',
ancestorTitles: annotations.scope ? [annotations.scope] : [],
title: testCaseName,
fullName: testCaseName,
});
numPendingTests += 1;
continue;
}
if (annotations.todo) {
testResults.push({
duration: end - start,
failureDetails: [],
failureMessages: [],
numPassingAsserts: 0,
status: 'todo',
ancestorTitles: annotations.scope ? [annotations.scope] : [],
title: testCaseName,
fullName: testCaseName,
});
numTodoTests += 1;
continue;
}
const invokeTest = async () => {
start = Date.now();
const { output } = await executor.runGetMethod({
code,
data,
methodId: testCase.methodId ?? (0, core_1.getMethodId)(testCase.methodName),
unixTime: annotations.unixTime ?? DEFAULT_UNIX_TIME,
balance: annotations.balance ?? DEFAULT_BALANCE,
stack: [],
address: DEFAULT_ADDRESS,
randomSeed: DEFAULT_RANDOM_SEED,
verbosity: 'full_location_stack_verbose',
config: sandbox_1.defaultConfig,
gasLimit: BigInt(annotations.gasLimit ?? DEFAULT_GAS_LIMIT),
debugEnabled: false,
});
end = Date.now();
if (!output.success) {
throw `Execution failed: ${output.error}`;
}
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}).`);
}
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}`);
}
}
};
await testkit_1.TestKitContext.runInContext({
test: {
executionId: process.env.TESTKIT_RUN_ID ?? null,
testPath: entrypointFileName,
ancestors: annotations.scope ? [annotations.scope] : [],
lifecycle: 'test',
testName: testCaseName,
},
symbols,
method: testCase.methodName ?? null,
annotations: {
exitCode: annotations.exitCode ?? null,
balance: annotations.balance?.toString() ?? null,
gasLimit: annotations.gasLimit ?? null,
unixTime: annotations.unixTime ?? null,
},
}, () => invokeTest());
testResults.push({
duration: end - start,
failureDetails: [],
failureMessages: [],
numPassingAsserts: 1,
status: 'passed',
ancestorTitles: annotations.scope ? [annotations.scope] : [],
title: testCaseName,
fullName: testCaseName,
});
numPassingTests += 1;
}
catch (error) {
const failureMessage = typeof error === 'string'
? error
: error instanceof Error
? error.message
: `${error}`;
testResults.push({
duration: end - start,
failureDetails: [],
failureMessages: [failureMessage],
numPassingAsserts: 0,
status: 'failed',
ancestorTitles: annotations.scope ? [annotations.scope] : [],
title: testCaseName,
fullName: testCaseName,
});
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,
};
};
module.exports = runTest;