Skip to content

Commit

Permalink
feat: compile solidity test sources only
Browse files Browse the repository at this point in the history
  • Loading branch information
galargh committed Oct 28, 2024
1 parent d72a140 commit b0bdb71
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 72 deletions.
168 changes: 116 additions & 52 deletions v-next/hardhat/src/internal/builtin-plugins/solidity-test/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,130 @@
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 { 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<Artifact[]> {
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 {
getAllFilesMatching,
readJsonFile,
} from "@ignored/hardhat-vnext-utils/fs";

const id = {
name: hardhatArtifact.contractName,
solcVersion: buildInfo.solcVersion,
source: hardhatArtifact.sourceName,
};
import {
FileBuildResultType,
type BuildOptions,
type SolidityBuildSystem,
} from "../../../types/solidity/build-system.js";
import { shouldMergeCompilationJobs } from "../solidity/build-profiles.js";
import { buildDependencyGraph } from "../solidity/build-system/dependency-graph-building.js";
import {
isNpmRootPath,
npmModuleToNpmRootPath,
} from "../solidity/build-system/root-paths-utils.js";

const contract = {
abi: JSON.stringify(hardhatArtifact.abi),
bytecode: hardhatArtifact.bytecode,
deployedBytecode: hardhatArtifact.deployedBytecode,
};
export interface TestCompileOptions {
projectRoot: string;
artifactsPath: string;
dependenciesToCompile: string[];
remappings: string[];
buildProfile: string;
}

const artifact = { id, contract };
artifacts.push(artifact);
}
export async function testCompile(
solidity: SolidityBuildSystem,
testFilePaths: string[],
options: TestCompileOptions,
): Promise<EdrArtifact[]> {
const { dependencyGraph } = await buildDependencyGraph(
testFilePaths.toSorted(), // We sort them to have a deterministic order
options.projectRoot,
options.remappings,
);

return artifacts;
}
const localFilesToCompile = [...dependencyGraph.getAllFiles()].map(
({ fsPath }) => fsPath,
);

const dependenciesToCompile = options.dependenciesToCompile.map(
npmModuleToNpmRootPath,
);

const rootFilePaths = [...localFilesToCompile, ...dependenciesToCompile];

const buildOptions: BuildOptions = {
force: false,
buildProfile: options.buildProfile,
mergeCompilationJobs: shouldMergeCompilationJobs(options.buildProfile),
quiet: false,
};

const results = await solidity.build(rootFilePaths, buildOptions);

export async function isTestArtifact(
root: string,
artifact: Artifact,
): Promise<boolean> {
const { source } = artifact.id;
if ("reason" in results) {
throw new HardhatError(
HardhatError.ERRORS.SOLIDITY.COMPILATION_JOB_CREATION_ERROR,
{
reason: results.formattedReason,
rootFilePath: results.rootFilePath,
buildProfile: results.buildProfile,
},
);
}

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 artifacts: EdrArtifact[] = [];

for (const rootFilePath of rootFilePaths) {
const sourceName: string = isNpmRootPath(rootFilePath)
? rootFilePath.replace(/^npm:/, "")
: path.relative(options.projectRoot, rootFilePath);

if (!sourceExists) {
return false;
const fileFolder = path.join(options.artifactsPath, sourceName);

const contractArtifactPaths = await getAllFilesMatching(fileFolder, (f) =>
f.endsWith(".json"),
);

for (const contractArtifactPath of contractArtifactPaths) {
const contractArtifact: HardhatArtifact =
await readJsonFile(contractArtifactPath);
const buildInfo: BuildInfo = await readJsonFile(
path.join(
options.artifactsPath,
"build-info",
`${contractArtifact.buildInfoId}.json`,
),
);

const id = {
name: contractArtifact.contractName,
solcVersion: buildInfo.solcVersion,
source: contractArtifact.sourceName,
};

const contract = {
abi: JSON.stringify(contractArtifact.abi),
bytecode: contractArtifact.bytecode,
deployedBytecode: contractArtifact.deployedBytecode,
};

artifacts.push({
id,
contract,
});
}
}

return true;
return artifacts;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ import { task } from "../../core/config.js";
const hardhatPlugin: HardhatPlugin = {
id: "builtin:solidity-tests",
dependencies: [
async () => {
const { default: artifactsPlugin } = await import(
"../artifacts/index.js"
);
return artifactsPlugin;
},
async () => {
const { default: solidityPlugin } = await import("../solidity/index.js");
return solidityPlugin;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,46 @@ import type { RunOptions } from "./runner.js";
import type { TestEvent } from "./types.js";
import type { NewTaskActionFunction } from "../../../types/tasks.js";

import path from "node:path";
import { finished } from "node:stream/promises";

import { getArtifacts, isTestArtifact } from "./helpers.js";
import { getAllFilesMatching } from "@ignored/hardhat-vnext-utils/fs";
import { resolveFromRoot } from "@ignored/hardhat-vnext-utils/path";

import { isNpmRootPath } from "../solidity/build-system/root-paths-utils.js";

import { testCompile } from "./helpers.js";
import { testReporter } from "./reporter.js";
import { run } from "./runner.js";

const runSolidityTests: NewTaskActionFunction = async ({ timeout }, hre) => {
await hre.tasks.getTask("compile").run({ quiet: false });
console.log("\nCompiling Solidity test sources...\n");

console.log("\nRunning Solidity tests...\n");

const artifacts = await getArtifacts(hre.artifacts);
const testSuiteIds = (
const testFilePaths = (
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((p) => getAllFilesMatching(p, (f) => f.endsWith(".t.sol"))),
)
).filter((artifact) => artifact !== undefined);
).flat(1);

const artifacts = await testCompile(hre.solidity, testFilePaths, {
projectRoot: hre.config.paths.root,
artifactsPath: hre.config.paths.artifacts,
dependenciesToCompile: hre.config.solidity.dependenciesToCompile,
remappings: hre.config.solidity.remappings,
buildProfile: hre.globalOptions.buildProfile,
});
const testSuiteIds = artifacts
.map(({ id }) => id)
.filter(({ source }) => {
return !isNpmRootPath(source);
})
.filter(({ source }) => {
return testFilePaths.includes(path.join(hre.config.paths.root, source));
});

console.log("\nRunning Solidity tests...\n");

const config = {
projectRoot: hre.config.paths.root,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -114,7 +114,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,
Expand Down

0 comments on commit b0bdb71

Please sign in to comment.