UNPKG

@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
"use strict"; /** * 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;