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 Nov 7, 2024
1 parent 6dea780 commit c6e7ae9
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 229 deletions.
5 changes: 5 additions & 0 deletions v-next/example-project/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
148 changes: 98 additions & 50 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,114 @@
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 {
CompilationJobCreationError,
FailedFileBuildResult,
FileBuildResult,
} 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";
import { readJsonFile } from "@ignored/hardhat-vnext-utils/fs";

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 { FileBuildResultType } from "../../../types/solidity/build-system.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,
};
type SolidityBuildResults =
| Map<string, FileBuildResult>
| CompilationJobCreationError;
type SuccessfulSolidityBuildResults = Map<
string,
Exclude<FileBuildResult, FailedFileBuildResult>
>;

const artifact = { id, contract };
artifacts.push(artifact);
export function throwIfSolidityBuildFailed(
results: SolidityBuildResults,
): asserts results is SuccessfulSolidityBuildResults {
if ("reason" in results) {
throw new HardhatError(
HardhatError.ERRORS.SOLIDITY.COMPILATION_JOB_CREATION_ERROR,
{
reason: results.formattedReason,
rootFilePath: results.rootFilePath,
buildProfile: results.buildProfile,
},
);
}

return artifacts;
const sucessful = [...results.values()].every(
({ type }) =>
type === FileBuildResultType.CACHE_HIT ||
type === FileBuildResultType.BUILD_SUCCESS,
);

if (!sucessful) {
throw new HardhatError(HardhatError.ERRORS.SOLIDITY.BUILD_FAILED);
}
}

export async function isTestArtifact(
root: string,
artifact: Artifact,
): Promise<boolean> {
const { source } = artifact.id;
export async function getArtifacts(
results: SuccessfulSolidityBuildResults,
artifactsRootPath: string,
): Promise<EdrArtifact[]> {
const artifacts: EdrArtifact[] = [];

if (!source.endsWith(".t.sol")) {
return false;
}
for (const [source, 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(artifactsRootPath, "build-info", `${result.buildId}.json`),
);

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

// 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 contract = {
abi: JSON.stringify(artifact.abi),
bytecode: artifact.bytecode,
deployedBytecode: artifact.deployedBytecode,
};

if (!sourceExists) {
return false;
artifacts.push({
id,
contract,
});
}
}
}

return true;
return artifacts;
}

export async function getTestSuiteIds(
artifacts: EdrArtifact[],
rootFilePaths: string[],
projectRoot: string,
): Promise<EdrArtifactId[]> {
const testSources = rootFilePaths
.filter((p) => {
return p.endsWith(".t.sol");
})
.map((p) => path.relative(projectRoot, p));

return artifacts
.map(({ id }) => id)
.filter(({ source }) => testSources.includes(source));
}
24 changes: 7 additions & 17 deletions v-next/hardhat/src/internal/builtin-plugins/solidity-test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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;
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import type { RunOptions } from "./runner.js";
import type { TestEvent } from "./types.js";
import type { BuildOptions } from "../../../types/solidity/build-system.js";
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 { shouldMergeCompilationJobs } from "../solidity/build-profiles.js";

import {
getArtifacts,
getTestSuiteIds,
throwIfSolidityBuildFailed,
} from "./helpers.js";
import { testReporter } from "./reporter.js";
import { run } from "./runner.js";

Expand All @@ -16,28 +29,44 @@ interface TestActionArguments {
}

const runSolidityTests: NewTaskActionFunction<TestActionArguments> = 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);
).flat(1);

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

if (testSuiteIds.length === 0) {
return;
}
const results = await hre.solidity.build(rootFilePaths, buildOptions);

throwIfSolidityBuildFailed(results);

const artifacts = await getArtifacts(results, hre.config.paths.artifacts);
const testSuiteIds = await getTestSuiteIds(
artifacts,
rootFilePaths,
hre.config.paths.root,
);

console.log("Running Solidity tests");
console.log();
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 @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion v-next/hardhat/src/types/solidity/build-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<void>;
Expand Down
Loading

0 comments on commit c6e7ae9

Please sign in to comment.