From 9721ff47d6d22c9072cb00d7d4cbd78318979ce8 Mon Sep 17 00:00:00 2001 From: galargh Date: Sat, 26 Oct 2024 22:37:18 +0200 Subject: [PATCH] feat: compile solidity test sources only --- v-next/example-project/hardhat.config.ts | 5 + .../builtin-plugins/solidity-test/helpers.ts | 144 +++++++++++------- .../builtin-plugins/solidity-test/index.ts | 24 +-- .../solidity-test/task-action.ts | 52 ++++--- .../solidity/build-system/build-system.ts | 4 +- .../src/types/solidity/build-system.ts | 6 +- .../builtin-plugins/solidity-test/helpers.ts | 98 ------------ .../solidity-test/test-fixtures/Test.t.sol | 43 ------ 8 files changed, 144 insertions(+), 232 deletions(-) delete mode 100644 v-next/hardhat/test/internal/builtin-plugins/solidity-test/helpers.ts delete mode 100644 v-next/hardhat/test/internal/builtin-plugins/solidity-test/test-fixtures/Test.t.sol diff --git a/v-next/example-project/hardhat.config.ts b/v-next/example-project/hardhat.config.ts index 2b8fed92b4..8e0d93ae1b 100644 --- a/v-next/example-project/hardhat.config.ts +++ b/v-next/example-project/hardhat.config.ts @@ -148,6 +148,11 @@ const config: HardhatUserConfig = { tests: { mocha: "test/mocha", nodeTest: "test/node", + solidity: [ + "contracts/Counter.sol", + "contracts/Counter.t.sol", + "contracts/WithForge.t.sol", + ], }, }, solidity: { diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity-test/helpers.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity-test/helpers.ts index a5f71e45ce..b76049617a 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity-test/helpers.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity-test/helpers.ts @@ -1,66 +1,106 @@ -import type { ArtifactsManager } from "../../../types/artifacts.js"; -import type { Artifact } from "@ignored/edr"; +import type { + Artifact as HardhatArtifact, + BuildInfo, +} from "../../../types/artifacts.js"; +import type { + BuildOptions, + SolidityBuildSystem, +} from "../../../types/solidity/build-system.js"; +import type { + ArtifactId as EdrArtifactId, + Artifact as EdrArtifact, +} from "@ignored/edr"; + +import path from "node:path"; import { HardhatError } from "@ignored/hardhat-vnext-errors"; -import { exists } from "@ignored/hardhat-vnext-utils/fs"; -import { resolveFromRoot } from "@ignored/hardhat-vnext-utils/path"; - -export async function getArtifacts( - hardhatArtifacts: ArtifactsManager, -): Promise { - const fqns = await hardhatArtifacts.getAllFullyQualifiedNames(); - const artifacts: Artifact[] = []; - - for (const fqn of fqns) { - const hardhatArtifact = await hardhatArtifacts.readArtifact(fqn); - const buildInfo = await hardhatArtifacts.getBuildInfo(fqn); - - if (buildInfo === undefined) { - throw new HardhatError( - HardhatError.ERRORS.SOLIDITY_TESTS.BUILD_INFO_NOT_FOUND_FOR_CONTRACT, - { - fqn, - }, - ); - } +import { readJsonFile } from "@ignored/hardhat-vnext-utils/fs"; + +import { FileBuildResultType } from "../../../types/solidity/build-system.js"; +import { shouldMergeCompilationJobs } from "../solidity/build-profiles.js"; - const id = { - name: hardhatArtifact.contractName, - solcVersion: buildInfo.solcVersion, - source: hardhatArtifact.sourceName, - }; +export interface TestCompileResult { + artifacts: EdrArtifact[]; + testSuiteIds: EdrArtifactId[]; +} - const contract = { - abi: JSON.stringify(hardhatArtifact.abi), - bytecode: hardhatArtifact.bytecode, - deployedBytecode: hardhatArtifact.deployedBytecode, - }; +export async function testCompile( + solidity: SolidityBuildSystem, + rootFilePaths: string[], + testFilePaths: string[], + projectRoot: string, + artifactsPath: string, + buildProfile: string, +): Promise { + const buildOptions: BuildOptions = { + force: false, + buildProfile, + mergeCompilationJobs: shouldMergeCompilationJobs(buildProfile), + quiet: false, + }; - const artifact = { id, contract }; - artifacts.push(artifact); - } + const results = await solidity.build(rootFilePaths, buildOptions); - return artifacts; -} + if ("reason" in results) { + throw new HardhatError( + HardhatError.ERRORS.SOLIDITY.COMPILATION_JOB_CREATION_ERROR, + { + reason: results.formattedReason, + rootFilePath: results.rootFilePath, + buildProfile: results.buildProfile, + }, + ); + } -export async function isTestArtifact( - root: string, - artifact: Artifact, -): Promise { - const { source } = artifact.id; + const sucessful = [...results.values()].every( + ({ type }) => + type === FileBuildResultType.CACHE_HIT || + type === FileBuildResultType.BUILD_SUCCESS, + ); - if (!source.endsWith(".t.sol")) { - return false; + if (!sucessful) { + throw new HardhatError(HardhatError.ERRORS.SOLIDITY.BUILD_FAILED); } - // NOTE: We also check whether the file exists in the workspace to filter out - // the artifacts from node modules. - const sourcePath = resolveFromRoot(root, source); - const sourceExists = await exists(sourcePath); + const testCompileResult: TestCompileResult = { + artifacts: [], + testSuiteIds: [], + }; + + for (const [filePath, result] of results.entries()) { + if ( + result.type === FileBuildResultType.BUILD_SUCCESS || + result.type === FileBuildResultType.CACHE_HIT + ) { + for (const artifactPath of result.contractArtifactsGenerated) { + const artifact: HardhatArtifact = await readJsonFile(artifactPath); + const buildInfo: BuildInfo = await readJsonFile( + path.join(artifactsPath, "build-info", `${result.buildId}.json`), + ); + + const id = { + name: artifact.contractName, + solcVersion: buildInfo.solcVersion, + source: artifact.sourceName, + }; - if (!sourceExists) { - return false; + const contract = { + abi: JSON.stringify(artifact.abi), + bytecode: artifact.bytecode, + deployedBytecode: artifact.deployedBytecode, + }; + + testCompileResult.artifacts.push({ + id, + contract, + }); + + if (testFilePaths.includes(path.join(projectRoot, filePath))) { + testCompileResult.testSuiteIds.push(id); + } + } + } } - return true; + return testCompileResult; } diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity-test/index.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity-test/index.ts index cd05bb892d..87cd55d162 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity-test/index.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity-test/index.ts @@ -3,18 +3,20 @@ import type { HardhatPlugin } from "../../../types/plugins.js"; import { ArgumentType } from "../../../types/arguments.js"; import { task } from "../../core/config.js"; +import "./type-extensions.js"; + const hardhatPlugin: HardhatPlugin = { id: "builtin:solidity-tests", dependencies: [ async () => { - const { default: artifactsPlugin } = await import( - "../artifacts/index.js" + const { default: solidityBuiltinPlugin } = await import( + "../solidity/index.js" ); - return artifactsPlugin; + return solidityBuiltinPlugin; }, async () => { - const { default: solidityPlugin } = await import("../solidity/index.js"); - return solidityPlugin; + const { default: testBuiltinPlugin } = await import("../test/index.js"); + return testBuiltinPlugin; }, ], hookHandlers: { @@ -36,18 +38,6 @@ const hardhatPlugin: HardhatPlugin = { }) .build(), ], - dependencies: [ - async () => { - const { default: solidityBuiltinPlugin } = await import( - "../solidity/index.js" - ); - return solidityBuiltinPlugin; - }, - async () => { - const { default: testBuiltinPlugin } = await import("../test/index.js"); - return testBuiltinPlugin; - }, - ], }; export default hardhatPlugin; diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity-test/task-action.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity-test/task-action.ts index 02b8e14019..048370f5dc 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity-test/task-action.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity-test/task-action.ts @@ -4,9 +4,14 @@ import type { NewTaskActionFunction } from "../../../types/tasks.js"; import { finished } from "node:stream/promises"; +import { + getAllFilesMatching, + isDirectory, +} from "@ignored/hardhat-vnext-utils/fs"; +import { resolveFromRoot } from "@ignored/hardhat-vnext-utils/path"; import { createNonClosingWriter } from "@ignored/hardhat-vnext-utils/stream"; -import { getArtifacts, isTestArtifact } from "./helpers.js"; +import { testCompile } from "./helpers.js"; import { testReporter } from "./reporter.js"; import { run } from "./runner.js"; @@ -16,28 +21,37 @@ interface TestActionArguments { } const runSolidityTests: NewTaskActionFunction = async ( - { timeout, noCompile }, + { timeout }, hre, ) => { - if (!noCompile) { - await hre.tasks.getTask("compile").run({}); - console.log(); - } - - const artifacts = await getArtifacts(hre.artifacts); - const testSuiteIds = ( + const rootFilePaths = ( await Promise.all( - artifacts.map(async (artifact) => { - if (await isTestArtifact(hre.config.paths.root, artifact)) { - return artifact.id; - } - }), + hre.config.paths.tests.solidity + .map((p) => resolveFromRoot(hre.config.paths.root, p)) + .map(async (p) => { + if (await isDirectory(p)) { + return getAllFilesMatching(p, (f) => f.endsWith(".sol")); + } else if (p.endsWith(".sol") === true) { + return [p]; + } else { + return []; + } + }), ) - ).filter((artifact) => artifact !== undefined); - - if (testSuiteIds.length === 0) { - return; - } + ).flat(1); + + const testFilePaths = rootFilePaths.filter((p) => { + return p.endsWith(".t.sol"); + }); + + const { artifacts, testSuiteIds } = await testCompile( + hre.solidity, + rootFilePaths, + testFilePaths, + hre.config.paths.root, + hre.config.paths.artifacts, + hre.globalOptions.buildProfile, + ); console.log("Running Solidity tests"); console.log(); diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/build-system.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/build-system.ts index 349a39a8db..d4e17181c9 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/build-system.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/build-system.ts @@ -67,7 +67,7 @@ export interface SolidityBuildSystemOptions { export class SolidityBuildSystemImplementation implements SolidityBuildSystem { readonly #options: SolidityBuildSystemOptions; - readonly #defaultConcurrenty = Math.max(os.cpus().length - 1, 1); + readonly #defaultConcurrency = Math.max(os.cpus().length - 1, 1); #downloadedCompilers = false; constructor(options: SolidityBuildSystemOptions) { @@ -118,7 +118,7 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem { compilationJobs, (compilationJob) => this.runCompilationJob(compilationJob), { - concurrency: options?.concurrency ?? this.#defaultConcurrenty, + concurrency: options?.concurrency ?? this.#defaultConcurrency, // An error when running the compiler is not a compilation failure, but // a fatal failure trying to run it, so we just throw on the first error stopOnError: true, diff --git a/v-next/hardhat/src/types/solidity/build-system.ts b/v-next/hardhat/src/types/solidity/build-system.ts index 821622e61e..f10aaa5b4f 100644 --- a/v-next/hardhat/src/types/solidity/build-system.ts +++ b/v-next/hardhat/src/types/solidity/build-system.ts @@ -149,7 +149,11 @@ export interface CacheHitFileBuildResult { type: FileBuildResultType.CACHE_HIT; // TODO: Should we remove this? It is a buildId of an already existing build // info. + // NOTE: The buildId and contractArtifactsGenerated are useful when one uses + // the build system programatically and wants to find out what artifacts + // were generated for a given file, and with what configuration. buildId: string; + contractArtifactsGenerated: string[]; } export interface SuccessfulFileBuildResult { @@ -265,7 +269,7 @@ export interface SolidityBuildSystem { * This method should only be used after a complete build has succeeded, as * it relies on the build system to have generated all the necessary artifact * files. - + * @param rootFilePaths All the root files of the project. */ cleanupArtifacts(rootFilePaths: string[]): Promise; diff --git a/v-next/hardhat/test/internal/builtin-plugins/solidity-test/helpers.ts b/v-next/hardhat/test/internal/builtin-plugins/solidity-test/helpers.ts deleted file mode 100644 index 245bf895ec..0000000000 --- a/v-next/hardhat/test/internal/builtin-plugins/solidity-test/helpers.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { Artifact } from "@ignored/edr"; - -import assert from "node:assert/strict"; -import { describe, it } from "node:test"; - -import { isTestArtifact } from "../../../../src/internal/builtin-plugins/solidity-test/helpers.js"; - -const testCases = [ - { - contract: "Abstract", - expected: true, - }, - { - contract: "NoTest", - expected: true, - }, - { - contract: "PublicTest", - expected: true, - }, - { - contract: "ExternalTest", - expected: true, - }, - { - contract: "PrivateTest", - expected: true, - }, - { - contract: "InternalTest", - expected: true, - }, - { - contract: "PublicInvariant", - expected: true, - }, - { - contract: "ExternalInvariant", - expected: true, - }, - { - contract: "PrivateInvariant", - expected: true, - }, - { - contract: "InternalInvariant", - expected: true, - }, -]; - -describe("isTestArtifact", () => { - for (const { contract, expected } of testCases) { - it(`should return ${expected} for the ${contract} contract`, async () => { - const artifact: Artifact = { - id: { - name: contract, - source: `test-fixtures/Test.t.sol`, - solcVersion: "0.8.20", - }, - contract: { - abi: "", - }, - }; - const actual = await isTestArtifact(import.meta.dirname, artifact); - assert.equal(actual, expected); - }); - } - - it("should return false if a file does not exist", async () => { - const artifact: Artifact = { - id: { - name: "Contract", - source: `test-fixtures/NonExistent.t.sol`, - solcVersion: "0.8.20", - }, - contract: { - abi: "", - }, - }; - const actual = await isTestArtifact(import.meta.dirname, artifact); - assert.equal(actual, false); - }); - - it("should return false if the file has the wrong extension", async () => { - const artifact: Artifact = { - id: { - name: "Contract", - source: `test-fixtures/WrongExtension.sol`, - solcVersion: "0.8.20", - }, - contract: { - abi: "", - }, - }; - const actual = await isTestArtifact(import.meta.dirname, artifact); - assert.equal(actual, false); - }); -}); diff --git a/v-next/hardhat/test/internal/builtin-plugins/solidity-test/test-fixtures/Test.t.sol b/v-next/hardhat/test/internal/builtin-plugins/solidity-test/test-fixtures/Test.t.sol deleted file mode 100644 index 025f6a7e35..0000000000 --- a/v-next/hardhat/test/internal/builtin-plugins/solidity-test/test-fixtures/Test.t.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.20; - -abstract contract Abstract { - function testPublic() public {} - function testExternal() external {} - function invariantPublic() public {} - function invariantExternal() external {} -} - -contract NoTest {} - -contract PublicTest { - function test() public {} -} - -contract ExternalTest { - function test() external {} -} - -contract PrivateTest { - function test() private {} -} - -contract InternalTest { - function test() internal {} -} - -contract PublicInvariant { - function invariant() public {} -} - -contract ExternalInvariant { - function invariant() external {} -} - -contract PrivateInvariant { - function invariant() private {} -} - -contract InternalInvariant { - function invariant() internal {} -}