Skip to content

Commit

Permalink
test: add isolate mode JS integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
agostbiro committed Sep 9, 2024
1 parent 17413be commit 850c33c
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.24;

import "./test.sol";
import "./Vm.sol";

contract Target {
uint256 public slot0;

function expandMemory(uint256 n) public pure returns (uint256) {
uint256[] memory arr = new uint256[](n);

for (uint256 i = 0; i < n; i++) {
arr[i] = i;
}

return arr.length;
}

function setValue(uint256 value) public {
slot0 = value;
}

function resetValue() public {
slot0 = 0;
}

fallback() external {}
}

abstract contract LastCallGasFixture is DSTest {
Vm constant vm = Vm(HEVM_ADDRESS);
Target public target;

struct Gas {
uint64 gasTotalUsed;
uint64 gasMemoryUsed;
int64 gasRefunded;
}

function testRevertNoCachedLastCallGas() public {
vm.expectRevert();
vm.lastCallGas();
}

function _setup() internal {
// Cannot be set in `setUp` due to `testRevertNoCachedLastCallGas`
// relying on no calls being made before `lastCallGas` is called.
target = new Target();
}

function _performCall() internal returns (bool success) {
(success,) = address(target).call("");
}

function _performExpandMemory() internal view {
target.expandMemory(1000);
}

function _performRefund() internal {
target.setValue(1);
target.resetValue();
}

function _assertGas(Vm.Gas memory lhs, Gas memory rhs) internal {
assertGt(lhs.gasLimit, 0);
assertGt(lhs.gasRemaining, 0);
assertEq(lhs.gasTotalUsed, rhs.gasTotalUsed);
assertEq(lhs.gasMemoryUsed, rhs.gasMemoryUsed);
assertEq(lhs.gasRefunded, rhs.gasRefunded);
}
}

// Test that `lastCallGas` works correctly in isolate mode.
contract LastCallGasIsolatedTest is LastCallGasFixture {
function testRecordLastCallGas() public {
_setup();
_performCall();
_assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21065, gasMemoryUsed: 0, gasRefunded: 0}));

_performCall();
_assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21065, gasMemoryUsed: 0, gasRefunded: 0}));

_performCall();
_assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21065, gasMemoryUsed: 0, gasRefunded: 0}));
}

function testRecordGasMemory() public {
_setup();
_performExpandMemory();
_assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 129820, gasMemoryUsed: 4994, gasRefunded: 0}));
}

function testRecordGasRefund() public {
_setup();
_performRefund();
_assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21400, gasMemoryUsed: 0, gasRefunded: 4800}));
}
}
98 changes: 98 additions & 0 deletions js/integration-tests/solidity-tests/test/testContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
Artifact,
ArtifactId,
FuzzConfigArgs,
InvariantConfigArgs,
type SolidityTestRunnerConfigArgs,
} from "@nomicfoundation/edr";
import {
buildSolidityTestsInput,
runAllSolidityTests,
} from "@nomicfoundation/edr-helpers";
import hre from "hardhat";

export class TestContext {
readonly rpcUrl = process.env.ALCHEMY_URL;
readonly rpcCachePath: string = "./edr-cache";
readonly fuzzFailuresPersistDir: string = "./edr-cache/fuzz";
readonly invariantFailuresPersistDir: string = "./edr-cache/invariant";
readonly artifacts: Artifact[];
readonly testSuiteIds: ArtifactId[];

constructor(artifacts: Artifact[], testSuiteIds: ArtifactId[]) {
this.artifacts = artifacts;
this.testSuiteIds = testSuiteIds;
}

static async setup(): Promise<TestContext> {
const results = await buildSolidityTestsInput(hre.artifacts);
return new TestContext(results.artifacts, results.testSuiteIds);
}

defaultConfig(): SolidityTestRunnerConfigArgs {
return {
projectRoot: hre.config.paths.root,
rpcCachePath: this.rpcCachePath,
};
}

fuzzConfig(
config?: Omit<FuzzConfigArgs, "failurePersistDir">
): FuzzConfigArgs {
return {
failurePersistDir: this.fuzzFailuresPersistDir,
...config,
};
}

invariantConfig(
config?: Omit<InvariantConfigArgs, "failurePersistDir">
): InvariantConfigArgs {
return {
failurePersistDir: this.invariantFailuresPersistDir,
...config,
};
}

async runTestsWithStats(
contractName: string,
config?: Omit<SolidityTestRunnerConfigArgs, "projectRoot">
): Promise<SolidityTestsRunResult> {
let totalTests = 0;
let failedTests = 0;

const suiteResults = await runAllSolidityTests(
this.artifacts,
this.matchingTest(contractName),
{
...this.defaultConfig(),
...config,
}
);

for (const suiteResult of suiteResults) {
for (const testResult of suiteResult.testResults) {
let failed = testResult.status === "Failure";
totalTests++;
if (failed) {
failedTests++;
}
}
}
return { totalTests, failedTests };
}

matchingTest(contractName: string): ArtifactId[] {
return this.matchingTests(new Set([contractName]));
}
matchingTests(testContractNames: Set<string>): ArtifactId[] {
return this.testSuiteIds.filter((testSuiteId) => {
return testContractNames.has(testSuiteId.name);
});
}
}

interface SolidityTestsRunResult {
totalTests: number;
failedTests: number;
}
108 changes: 27 additions & 81 deletions js/integration-tests/solidity-tests/test/unit.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,26 @@
import type {
Artifact,
ArtifactId,
SolidityTestRunnerConfigArgs,
} from "@nomicfoundation/edr";
import {
buildSolidityTestsInput,
runAllSolidityTests,
} from "@nomicfoundation/edr-helpers";
import { assert } from "chai";
import hre from "hardhat";
import { TestContext } from "./testContext";

describe("Unit tests", () => {
const alchemyUrl = process.env.ALCHEMY_URL;
const rpcCachePath = "./edr-cache";
let artifacts: Artifact[], testSuiteIds: ArtifactId[];
let testContext: TestContext;

before(async () => {
const results = await buildSolidityTestsInput(hre.artifacts);
artifacts = results.artifacts;
testSuiteIds = results.testSuiteIds;
testContext = await TestContext.setup();
});

function matchingTest(contractName: string): ArtifactId[] {
return matchingTests(new Set([contractName]));
}

function matchingTests(testContractNames: Set<string>): ArtifactId[] {
return testSuiteIds.filter((testSuiteId) => {
return testContractNames.has(testSuiteId.name);
});
}

it("SuccessAndFailure", async function () {
const { totalTests, failedTests } = await runTestsWithStats(
artifacts,
matchingTest("SuccessAndFailureTest"),
{
projectRoot: hre.config.paths.root,
}
const { totalTests, failedTests } = await testContext.runTestsWithStats(
"SuccessAndFailureTest"
);

assert.equal(failedTests, 1);
assert.equal(totalTests, 2);
});

it("ContractEnvironment", async function () {
const { totalTests, failedTests } = await runTestsWithStats(
artifacts,
matchingTest("ContractEnvironmentTest"),
const { totalTests, failedTests } = await testContext.runTestsWithStats(
"ContractEnvironmentTest",
{
projectRoot: hre.config.paths.root,
sender: Buffer.from("976EA74026E726554dB657fA54763abd0C3a0aa9", "hex"),
chainId: 12n,
blockNumber: 23n,
Expand All @@ -61,18 +32,27 @@ describe("Unit tests", () => {
assert.equal(totalTests, 1);
});

it("LastCallGasIsolated", async function () {
const { totalTests, failedTests } = await testContext.runTestsWithStats(
"LastCallGasIsolatedTest",
{
isolate: true,
}
);

assert.equal(failedTests, 0);
assert.equal(totalTests, 4);
});

it("GlobalFork", async function () {
if (alchemyUrl === undefined) {
if (testContext.rpcUrl === undefined) {
this.skip();
}

const { totalTests, failedTests } = await runTestsWithStats(
artifacts,
matchingTest("GlobalForkTest"),
const { totalTests, failedTests } = await testContext.runTestsWithStats(
"GlobalForkTest",
{
projectRoot: hre.config.paths.root,
rpcCachePath,
ethRpcUrl: alchemyUrl,
ethRpcUrl: testContext.rpcUrl,
forkBlockNumber: 20_000_000n,
}
);
Expand All @@ -82,18 +62,15 @@ describe("Unit tests", () => {
});

it("ForkCheatcode", async function () {
if (alchemyUrl === undefined) {
if (testContext.rpcUrl === undefined) {
this.skip();
}

const { totalTests, failedTests } = await runTestsWithStats(
artifacts,
matchingTest("ForkCheatcodeTest"),
const { totalTests, failedTests } = await testContext.runTestsWithStats(
"ForkCheatcodeTest",
{
projectRoot: hre.config.paths.root,
rpcCachePath,
rpcEndpoints: {
alchemyMainnet: alchemyUrl,
alchemyMainnet: testContext.rpcUrl,
},
}
);
Expand All @@ -102,34 +79,3 @@ describe("Unit tests", () => {
assert.equal(totalTests, 1);
});
});

interface SolidityTestsRunResult {
totalTests: number;
failedTests: number;
}

async function runTestsWithStats(
artifacts: Artifact[],
testSuiteIds: ArtifactId[],
config: SolidityTestRunnerConfigArgs
): Promise<SolidityTestsRunResult> {
let totalTests = 0;
let failedTests = 0;

const suiteResults = await runAllSolidityTests(
artifacts,
testSuiteIds,
config
);

for (const suiteResult of suiteResults) {
for (const testResult of suiteResult.testResults) {
let failed = testResult.status === "Failure";
totalTests++;
if (failed) {
failedTests++;
}
}
}
return { totalTests, failedTests };
}

0 comments on commit 850c33c

Please sign in to comment.