UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

261 lines 12 kB
import { finished } from "node:stream/promises"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; import { exists } from "@nomicfoundation/hardhat-utils/fs"; import { resolveFromRoot } from "@nomicfoundation/hardhat-utils/path"; import { createNonClosingWriter } from "@nomicfoundation/hardhat-utils/stream"; import { getFullyQualifiedName } from "../../../utils/contract-names.js"; import { errorResult, successfulResult } from "../../../utils/result.js"; import { isSupportedChainType } from "../../edr/chain-type.js"; import { ArtifactManagerImplementation } from "../artifacts/artifact-manager.js"; import { getCoverageManager } from "../coverage/helpers.js"; import { getGasAnalyticsManager } from "../gas-analytics/helpers/accessors.js"; import { edrGasReportToHardhatGasMeasurements } from "../network-manager/edr/utils/convert-to-edr.js"; import { buildEdrArtifactsWithMetadata, getBuildInfosAndOutputs, } from "./edr-artifacts.js"; import { isTestSuiteArtifact, warnDeprecatedTestFail, solidityTestConfigToSolidityTestRunnerConfigArgs, } from "./helpers.js"; import { getTestFunctionOverrides } from "./inline-config/index.js"; import { testReporter } from "./reporter.js"; import { run } from "./runner.js"; const runSolidityTests = async ({ testFiles, chainType, grep, noCompile, testSummaryIndex }, hre) => { // Set an environment variable that plugins can use to detect when a process is running tests process.env.HH_TEST = "true"; const verbosity = hre.globalOptions.verbosity; // NOTE: The resolution from CWD mimics what `build` does. It's important for // both tasks to be aligned. const resolvedTestFilesArgument = testFiles.map((f) => resolveFromRoot(process.cwd(), f)); await validateThatProvidedFilesAreTests(hre.solidity, testFiles, resolvedTestFilesArgument); // Sets the NODE_ENV environment variable to "test" so the code can detect that tests are running // This is done by other JS/TS test frameworks like vitest process.env.NODE_ENV ??= "test"; if (!isSupportedChainType(chainType)) { throw new HardhatError(HardhatError.ERRORS.CORE.ARGUMENTS.INVALID_VALUE_FOR_TYPE, { value: chainType, type: "ChainType", name: "chainType", }); } let testRootPathsToRun; let edrArtifactsWithMetadata; let allBuildInfosAndOutputs; if (hre.config.solidity.splitTestsCompilation) { if (noCompile !== true) { await hre.tasks.getTask("build").run({ noTests: true, }); } ({ testRootPaths: testRootPathsToRun } = await hre.tasks .getTask("build") .run({ files: testFiles, noContracts: true, })); console.log(); ({ edrArtifactsWithMetadata, allBuildInfosAndOutputs } = await loadArtifacts(hre.solidity, ["contracts", "tests"])); } else { if (noCompile !== true) { ({ testRootPaths: testRootPathsToRun } = await hre.tasks .getTask("build") .run({ files: testFiles, })); } else { if (resolvedTestFilesArgument.length > 0) { testRootPathsToRun = resolvedTestFilesArgument; } else { testRootPathsToRun = []; const allRoots = await hre.solidity.getRootFilePaths({ scope: "contracts", }); for (const root of allRoots) { if ((await hre.solidity.getScope(root)) === "tests") { testRootPathsToRun.push(root); } } } } console.log(); ({ edrArtifactsWithMetadata, allBuildInfosAndOutputs } = await loadArtifacts(hre.solidity, ["contracts"])); // When noCompile, validate selected test roots have compiled artifacts if (noCompile === true) { const compiledSources = new Set(edrArtifactsWithMetadata.map(({ userSourceName }) => resolveFromRoot(hre.config.paths.root, userSourceName))); const notCompiledFiles = []; for (const root of testRootPathsToRun) { if (!compiledSources.has(root)) { notCompiledFiles.push(root); } } if (notCompiledFiles.length > 0) { throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY_TESTS.SELECTED_TEST_FILES_NOT_COMPILED, { files: notCompiledFiles.map((f) => `- ${f}`).join("\n"), }); } } } const sourceNameToUserSourceName = new Map(edrArtifactsWithMetadata.map(({ userSourceName, edrArtifact }) => [ edrArtifact.id.source, userSourceName, ])); const testRootPathsSet = new Set(testRootPathsToRun); const testSuiteArtifacts = edrArtifactsWithMetadata .filter(({ userSourceName }) => testRootPathsSet.has(resolveFromRoot(hre.config.paths.root, userSourceName))) .filter(({ edrArtifact }) => isTestSuiteArtifact(edrArtifact)); for (const { edrArtifact } of testSuiteArtifacts) { warnDeprecatedTestFail(edrArtifact, sourceNameToUserSourceName); } const testSuiteIds = testSuiteArtifacts.map(({ edrArtifact }) => edrArtifact.id); console.log("Running Solidity tests"); console.log(); let includesFailures = false; let includesErrors = false; const solidityTestConfig = hre.config.test.solidity; let observabilityConfig; if (hre.globalOptions.coverage) { const coverage = getCoverageManager(hre); observabilityConfig = { codeCoverage: { onCollectedCoverageCallback: async (coverageData) => { const tags = coverageData.map((tag) => Buffer.from(tag).toString("hex")); await coverage.addData(tags); }, }, }; } // Extract hardfork from the selected network configuration let hardfork; if (hre.globalOptions.network !== undefined) { const networkName = hre.globalOptions.network; const networkConfig = hre.config.networks[networkName]; if (networkConfig !== undefined && networkConfig.type === "edr-simulated") { hardfork = networkConfig.hardfork; } } const testFunctionOverrides = getTestFunctionOverrides(testSuiteArtifacts, allBuildInfosAndOutputs); const testRunnerConfig = await solidityTestConfigToSolidityTestRunnerConfigArgs({ chainType, projectRoot: hre.config.paths.root, hardfork, config: solidityTestConfig, verbosity, observability: observabilityConfig, testPattern: grep, generateGasReport: hre.globalOptions.gasStats || hre.globalOptions.gasStatsJson !== undefined, testFunctionOverrides, }); const tracingConfig = { buildInfos: allBuildInfosAndOutputs.map(({ buildInfo, output }) => ({ buildInfo, output, })), ignoreContracts: false, }; await hre.hooks.runHandlerChain("test", "onTestRunStart", ["solidity"], async () => { }); const runStream = run(chainType, edrArtifactsWithMetadata.map(({ edrArtifact }) => edrArtifact), testSuiteIds, testRunnerConfig, tracingConfig, sourceNameToUserSourceName); let failed = 0; let passed = 0; let skipped = 0; let failureOutput = ""; const suiteResults = []; const testReporterStream = runStream .on("data", (event) => { if (event.type === "suite:done") { suiteResults.push(event.data); if (event.data.testResults.some(({ status }) => status === "Failure")) { includesFailures = true; } } else if (event.type === "run:done") { const { gasReport } = event.data; // Gas report may be undefined if gas analytics is disabled if (gasReport === undefined) { return; } const testContractFqns = testSuiteIds.map(({ name, source }) => getFullyQualifiedName(source, name)); // we can't use the onGasMeasurement hook here as it's async and stream // handlers are sync const gasMeasurements = edrGasReportToHardhatGasMeasurements(gasReport, testContractFqns); const gasAnalytics = getGasAnalyticsManager(hre); for (const measurement of gasMeasurements) { gasAnalytics.addGasMeasurement(measurement); } } }) .compose(async function* (source) { const reporter = testReporter(source, sourceNameToUserSourceName, verbosity, testSummaryIndex); for await (const value of reporter) { if (typeof value === "string") { yield value; } else { failed = value.failed; passed = value.passed; skipped = value.skipped; failureOutput = value.failureOutput; } } }); const outputStream = testReporterStream.pipe(createNonClosingWriter(process.stdout)); try { // NOTE: We're awaiting the original run stream to finish to catch any // errors produced by the runner. await finished(runStream); // We also await the output stream to finish, as we want to wait for it // to avoid returning before the whole output was generated. await finished(outputStream); } catch (error) { console.error(error); includesErrors = true; } await hre.hooks.runHandlerChain("test", "onTestWorkerDone", ["solidity"], async () => { }); await hre.hooks.runHandlerChain("test", "onTestRunDone", ["solidity"], async () => { }); console.log(); const result = { summary: { failed, passed, skipped, todo: 0, failureOutput }, suiteResults, }; return includesFailures || includesErrors ? errorResult(result) : successfulResult(result); }; /** * Validates that the test files provided by the user, resolved in this case, * are actually test files. * * @param solidity The solidity build system * @param testFiles The test files, as provided by the user * @param resolvedTestFilesArgument The resolved testFiles */ async function validateThatProvidedFilesAreTests(solidity, testFiles, resolvedTestFilesArgument) { const existsResults = await Promise.all(resolvedTestFilesArgument.map((rootPath) => exists(rootPath))); const missing = testFiles.filter((_, i) => !existsResults[i]); if (missing.length > 0) { throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY_TESTS.SELECTED_TEST_FILES_DO_NOT_EXIST, { files: missing.map((f) => `- ${f}`).join("\n"), }); } const scopes = await Promise.all(resolvedTestFilesArgument.map((rootPath) => solidity.getScope(rootPath))); const nonTests = testFiles.filter((_, i) => scopes[i] !== "tests"); if (nonTests.length > 0) { throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY_TESTS.SELECTED_FILES_ARE_NOT_SOLIDITY_TESTS, { files: nonTests.map((f) => `- ${f}`).join("\n"), }); } } async function loadArtifacts(solidity, scopes) { const edrArtifactsWithMetadata = []; const allBuildInfosAndOutputs = []; for (const scope of scopes) { const artifactsDir = await solidity.getArtifactsDirectory(scope); const artifactManager = new ArtifactManagerImplementation(artifactsDir); edrArtifactsWithMetadata.push(...(await buildEdrArtifactsWithMetadata(artifactManager))); allBuildInfosAndOutputs.push(...(await getBuildInfosAndOutputs(artifactManager))); } return { edrArtifactsWithMetadata, allBuildInfosAndOutputs }; } export default runSolidityTests; //# sourceMappingURL=task-action.js.map