UNPKG

hardhat

Version:

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

286 lines (247 loc) 8.28 kB
import type { RunOptions } from "./runner.js"; import type { TestEvent } from "./types.js"; import type { NewTaskActionFunction } from "../../../types/tasks.js"; import type { Artifact as EdrArtifact, BuildInfoAndOutput, ObservabilityConfig, SolidityTestRunnerConfigArgs, TracingConfigWithBuffers, } from "@nomicfoundation/edr"; import { finished } from "node:stream/promises"; import { assertHardhatInvariant, HardhatError, } from "@nomicfoundation/hardhat-errors"; import { resolveFromRoot } from "@nomicfoundation/hardhat-utils/path"; import { createNonClosingWriter } from "@nomicfoundation/hardhat-utils/stream"; import { getFullyQualifiedName } from "../../../utils/contract-names.js"; import { HardhatRuntimeEnvironmentImplementation } from "../../core/hre.js"; import { isSupportedChainType } from "../../edr/chain-type.js"; import { ArtifactManagerImplementation } from "../artifacts/artifact-manager.js"; import { markTestRunStart as initCoverage, markTestWorkerDone as saveCoverageData, markTestRunDone as reportCoverage, } from "../coverage/helpers.js"; import { markTestRunStart as initGasStats, markTestWorkerDone as saveGasStatsData, markTestRunDone as reportGasStats, } from "../gas-analytics/helpers.js"; import { edrGasReportToHardhatGasMeasurements } from "../network-manager/edr/utils/convert-to-edr.js"; import { getEdrArtifacts, getBuildInfos } from "./edr-artifacts.js"; import { isTestSuiteArtifact, warnDeprecatedTestFail, solidityTestConfigToRunOptions, solidityTestConfigToSolidityTestRunnerConfigArgs, } from "./helpers.js"; import { testReporter } from "./reporter.js"; import { run } from "./runner.js"; interface TestActionArguments { testFiles: string[]; chainType: string; grep?: string; noCompile: boolean; verbosity: number; testSummaryIndex: number; } const runSolidityTests: NewTaskActionFunction<TestActionArguments> = async ( { testFiles, chainType, grep, noCompile, verbosity, testSummaryIndex }, hre, ) => { assertHardhatInvariant( hre instanceof HardhatRuntimeEnvironmentImplementation, "Expected HRE to be an instance of HardhatRuntimeEnvironmentImplementation", ); // Set an environment variable that plugins can use to detect when a process is running tests process.env.HH_TEST = "true"; // 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", }, ); } // Run the build task for contract files if needed if (noCompile !== true) { await hre.tasks.getTask("build").run({ noTests: true, }); } // Run the build task for test files const { testRootPaths }: { testRootPaths: string[] } = await hre.tasks .getTask("build") .run({ files: testFiles, noContracts: true, }); console.log(); // EDR needs all artifacts (contracts + tests) const edrArtifacts: Array<{ edrAtifact: EdrArtifact; userSourceName: string; }> = []; const buildInfos: BuildInfoAndOutput[] = []; for (const scope of ["contracts", "tests"] as const) { const artifactsDir = await hre.solidity.getArtifactsDirectory(scope); const artifactManager = new ArtifactManagerImplementation(artifactsDir); edrArtifacts.push(...(await getEdrArtifacts(artifactManager))); buildInfos.push(...(await getBuildInfos(artifactManager))); } const sourceNameToUserSourceName = new Map( edrArtifacts.map(({ userSourceName, edrAtifact }) => [ edrAtifact.id.source, userSourceName, ]), ); edrArtifacts.forEach(({ userSourceName, edrAtifact }) => { if ( testRootPaths.includes( resolveFromRoot(hre.config.paths.root, userSourceName), ) && isTestSuiteArtifact(edrAtifact) ) { warnDeprecatedTestFail(edrAtifact, sourceNameToUserSourceName); } }); const testSuiteIds = edrArtifacts .filter(({ userSourceName }) => testRootPaths.includes( resolveFromRoot(hre.config.paths.root, userSourceName), ), ) .filter(({ edrAtifact }) => isTestSuiteArtifact(edrAtifact)) .map(({ edrAtifact }) => edrAtifact.id); console.log("Running Solidity tests"); console.log(); let includesFailures = false; let includesErrors = false; const solidityTestConfig = hre.config.test.solidity; let observabilityConfig: ObservabilityConfig | undefined; if (hre.globalOptions.coverage) { observabilityConfig = { codeCoverage: { onCollectedCoverageCallback: async (coverageData: Uint8Array[]) => { const tags = coverageData.map((tag) => Buffer.from(tag).toString("hex"), ); await hre._coverage.addData(tags); }, }, }; } const config: SolidityTestRunnerConfigArgs = await solidityTestConfigToSolidityTestRunnerConfigArgs({ chainType, projectRoot: hre.config.paths.root, config: solidityTestConfig, verbosity, observability: observabilityConfig, testPattern: grep, generateGasReport: hre.globalOptions.gasStats, }); const tracingConfig: TracingConfigWithBuffers = { buildInfos, ignoreContracts: false, }; const options: RunOptions = solidityTestConfigToRunOptions(solidityTestConfig); await initCoverage("solidity"); await initGasStats("solidity"); const runStream = run( chainType, edrArtifacts.map(({ edrAtifact }) => edrAtifact), testSuiteIds, config, tracingConfig, sourceNameToUserSourceName, options, ); let failed = 0; let passed = 0; let skipped = 0; let failureOutput = ""; const testReporterStream = runStream .on("data", (event: TestEvent) => { if (event.type === "suite:done") { 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, ); for (const measurement of gasMeasurements) { hre._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 saveCoverageData("solidity"); await saveGasStatsData("solidity"); // this may print coverage and gas statistics reports await reportCoverage("solidity"); await reportGasStats("solidity"); if (includesFailures || includesErrors) { process.exitCode = 1; } console.log(); return { failed, passed, skipped, todo: 0, failureOutput, }; }; export default runSolidityTests;