Skip to content

Commit

Permalink
feat: enhance the solidity test artifacts discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
galargh committed Oct 16, 2024
1 parent ef2a5de commit 0bf17ba
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 25 deletions.
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions v-next/hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@
"typescript-eslint": "7.7.1"
},
"dependencies": {
"@nomicfoundation/solidity-analyzer": "^0.1.0",
"@ignored/hardhat-vnext-zod-utils": "workspace:^3.0.0-next.3",
"@ignored/hardhat-vnext-utils": "workspace:^3.0.0-next.3",
"@ignored/hardhat-vnext-errors": "workspace:^3.0.0-next.3",
"@ignored/edr": "0.6.2-alpha.0",
"@ignored/hardhat-vnext-errors": "workspace:^3.0.0-next.3",
"@ignored/hardhat-vnext-utils": "workspace:^3.0.0-next.3",
"@ignored/hardhat-vnext-zod-utils": "workspace:^3.0.0-next.3",
"@nomicfoundation/slang": "^0.18.2",
"@nomicfoundation/solidity-analyzer": "^0.1.0",
"@sentry/node": "^5.18.1",
"adm-zip": "^0.4.16",
"chalk": "^5.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import type {

import { runSolidityTests } from "@ignored/edr";
import { HardhatError } from "@ignored/hardhat-vnext-errors";
import { exists, readUtf8File } from "@ignored/hardhat-vnext-utils/fs";
import { resolveFromRoot } from "@ignored/hardhat-vnext-utils/path";
import { NonterminalKind, TerminalKind } from "@nomicfoundation/slang/cst";
import { Parser } from "@nomicfoundation/slang/parser";

/**
* Run all the given solidity tests and returns the whole results after finishing.
Expand Down Expand Up @@ -50,13 +54,11 @@ export async function runAllSolidityTests(
});
}

export async function buildSolidityTestsInput(
export async function getArtifacts(
hardhatArtifacts: ArtifactsManager,
isTestArtifact: (artifact: Artifact) => boolean = () => true,
): Promise<{ artifacts: Artifact[]; testSuiteIds: ArtifactId[] }> {
): Promise<Artifact[]> {
const fqns = await hardhatArtifacts.getAllFullyQualifiedNames();
const artifacts: Artifact[] = [];
const testSuiteIds: ArtifactId[] = [];

for (const fqn of fqns) {
const hardhatArtifact = await hardhatArtifacts.readArtifact(fqn);
Expand Down Expand Up @@ -85,10 +87,83 @@ export async function buildSolidityTestsInput(

const artifact = { id, contract };
artifacts.push(artifact);
if (isTestArtifact(artifact)) {
testSuiteIds.push(artifact.id);
}

return artifacts;
}

export async function isTestArtifact(
root: string,
artifact: Artifact,
): Promise<boolean> {
const { name, source, solcVersion } = artifact.id;

if (!source.endsWith(".t.sol")) {
return false;
}

const sourcePath = resolveFromRoot(root, source);
const sourceExists = await exists(sourcePath);

if (!sourceExists) {
return false;
}

const content = await readUtf8File(source);
const parser = Parser.create(solcVersion);
const cursor = parser
.parse(NonterminalKind.SourceUnit, content)
.createTreeCursor();

while (
cursor.goToNextNonterminalWithKind(NonterminalKind.ContractDefinition)
) {
const nameCursor = cursor.spawn();
if (!nameCursor.goToNextTerminalWithKind(TerminalKind.Identifier)) {
continue;
}
if (nameCursor.node.unparse() !== name) {
continue;
}

const abstractCursor = cursor.spawn();
if (abstractCursor.goToNextTerminalWithKind(TerminalKind.AbstractKeyword)) {
return false;
}

const functionCursor = cursor.spawn();

while (
functionCursor.goToNextNonterminalWithKind(
NonterminalKind.FunctionDefinition,
)
) {
const functionNameCursor = functionCursor.spawn();
if (
!functionNameCursor.goToNextTerminalWithKind(TerminalKind.Identifier)
) {
continue;
}

const functionName = functionNameCursor.node.unparse();
if (
functionName.startsWith("test") ||
functionName.startsWith("invariant")
) {
const publicCursor = functionCursor.spawn();
if (publicCursor.goToNextTerminalWithKind(TerminalKind.PublicKeyword)) {
return true;
}

const externalCursor = functionCursor.spawn();
if (
externalCursor.goToNextTerminalWithKind(TerminalKind.ExternalKeyword)
) {
return true;
}
}
}
}

return { artifacts, testSuiteIds };
return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import type { NewTaskActionFunction } from "../../../types/tasks.js";

import { spec } from "node:test/reporters";

import { buildSolidityTestsInput, runAllSolidityTests } from "./helpers.js";
import {
getArtifacts,
isTestArtifact,
runAllSolidityTests,
} from "./helpers.js";

const runSolidityTests: NewTaskActionFunction = async (_arguments, hre) => {
await hre.tasks.getTask("compile").run({ quiet: false });
Expand All @@ -16,19 +20,16 @@ const runSolidityTests: NewTaskActionFunction = async (_arguments, hre) => {
let totalTests = 0;
let failedTests = 0;

const { artifacts, testSuiteIds } = await buildSolidityTestsInput(
hre.artifacts,
(artifact) => {
const sourceName = artifact.id.source;
const isTestArtifact =
sourceName.endsWith(".t.sol") &&
sourceName.startsWith("contracts/") &&
!sourceName.startsWith("contracts/forge-std/") &&
!sourceName.startsWith("contracts/ds-test/");

return isTestArtifact;
},
);
const artifacts = await getArtifacts(hre.artifacts);
const testSuiteIds = (
await Promise.all(
artifacts.map(async (artifact) => {
if (await isTestArtifact(hre.config.paths.root, artifact)) {
return artifact.id;
}
}),
)
).filter((artifact) => artifact !== undefined);

const config = {
projectRoot: hre.config.paths.root,
Expand Down

0 comments on commit 0bf17ba

Please sign in to comment.