UNPKG

@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
"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 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;