Skip to content

Commit b184ebd

Browse files
authored
fix: add support for inline isolate configuration (foundry-rs#9904)
* add test for inline isolate configuration * clean up docs * clarify * prefer config, add comment
1 parent 5af4630 commit b184ebd

File tree

2 files changed

+139
-5
lines changed

2 files changed

+139
-5
lines changed

crates/forge/src/multi_runner.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,18 +294,21 @@ pub struct TestRunnerConfig {
294294

295295
impl TestRunnerConfig {
296296
/// Reconfigures all fields using the given `config`.
297+
/// This is for example used to override the configuration with inline config.
297298
pub fn reconfigure_with(&mut self, config: Arc<Config>) {
298299
debug_assert!(!Arc::ptr_eq(&self.config, &config));
299300

300-
// TODO: self.evm_opts
301-
// TODO: self.env
302301
self.spec_id = config.evm_spec_id();
303302
self.sender = config.sender;
303+
self.odyssey = config.odyssey;
304+
self.isolation = config.isolate;
305+
306+
// Specific to Forge, not present in config.
307+
// TODO: self.evm_opts
308+
// TODO: self.env
304309
// self.coverage = N/A;
305310
// self.debug = N/A;
306311
// self.decode_internal = N/A;
307-
// self.isolation = N/A;
308-
self.odyssey = config.odyssey;
309312

310313
self.config = config;
311314
}

crates/forge/tests/cli/inline_config.rs

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
use std::{fs, path::Path};
2+
3+
use serde::{Deserialize, Deserializer};
4+
15
forgetest!(runs, |prj, cmd| {
26
prj.add_test(
37
"inline.sol",
@@ -201,7 +205,134 @@ Encountered a total of 1 failing tests, 0 tests succeeded
201205
"#]]);
202206
});
203207

204-
forgetest_init!(evm_version, |prj, cmd| {
208+
forgetest_init!(config_inline_isolate, |prj, cmd| {
209+
prj.wipe_contracts();
210+
prj.add_test(
211+
"inline.sol",
212+
r#"
213+
import {Test} from "forge-std/Test.sol";
214+
215+
contract Dummy {
216+
uint256 public number;
217+
218+
function setNumber(uint256 newNumber) public {
219+
number = newNumber;
220+
}
221+
}
222+
223+
contract FunctionConfig is Test {
224+
Dummy dummy;
225+
226+
function setUp() public {
227+
dummy = new Dummy();
228+
}
229+
230+
/// forge-config: default.isolate = true
231+
function test_isolate() public {
232+
vm.startSnapshotGas("testIsolatedFunction");
233+
dummy.setNumber(1);
234+
vm.stopSnapshotGas();
235+
}
236+
237+
function test_non_isolate() public {
238+
vm.startSnapshotGas("testNonIsolatedFunction");
239+
dummy.setNumber(2);
240+
vm.stopSnapshotGas();
241+
}
242+
}
243+
244+
/// forge-config: default.isolate = true
245+
contract ContractConfig is Test {
246+
Dummy dummy;
247+
248+
function setUp() public {
249+
dummy = new Dummy();
250+
}
251+
252+
function test_non_isolate() public {
253+
vm.startSnapshotGas("testIsolatedContract");
254+
dummy.setNumber(3);
255+
vm.stopSnapshotGas();
256+
}
257+
}
258+
"#,
259+
)
260+
.unwrap();
261+
262+
cmd.args(["test", "-j1"]).assert_success().stdout_eq(str![[r#"
263+
[COMPILING_FILES] with [SOLC_VERSION]
264+
[SOLC_VERSION] [ELAPSED]
265+
Compiler run successful!
266+
267+
Ran 1 test for test/inline.sol:ContractConfig
268+
[PASS] test_non_isolate() ([GAS])
269+
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]
270+
271+
Ran 2 tests for test/inline.sol:FunctionConfig
272+
[PASS] test_isolate() ([GAS])
273+
[PASS] test_non_isolate() ([GAS])
274+
Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]
275+
276+
Ran 2 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)
277+
278+
"#]]);
279+
280+
assert!(prj.root().join("snapshots/FunctionConfig.json").exists());
281+
assert!(prj.root().join("snapshots/ContractConfig.json").exists());
282+
283+
#[derive(Debug, Deserialize)]
284+
#[serde(rename_all = "camelCase")]
285+
struct FunctionConfig {
286+
#[serde(deserialize_with = "string_to_u64")]
287+
test_isolated_function: u64,
288+
289+
#[serde(deserialize_with = "string_to_u64")]
290+
test_non_isolated_function: u64,
291+
}
292+
293+
#[derive(Debug, Deserialize)]
294+
#[serde(rename_all = "camelCase")]
295+
struct ContractConfig {
296+
#[serde(deserialize_with = "string_to_u64")]
297+
test_isolated_contract: u64,
298+
}
299+
300+
fn string_to_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
301+
where
302+
D: Deserializer<'de>,
303+
{
304+
let s: serde_json::Value = Deserialize::deserialize(deserializer)?;
305+
match s {
306+
serde_json::Value::String(s) => s.parse::<u64>().map_err(serde::de::Error::custom),
307+
serde_json::Value::Number(n) if n.is_u64() => Ok(n.as_u64().unwrap()),
308+
_ => Err(serde::de::Error::custom("Expected a string or number")),
309+
}
310+
}
311+
312+
fn read_snapshot<T: for<'de> Deserialize<'de>>(path: &Path) -> T {
313+
let content = fs::read_to_string(path).expect("Failed to read file");
314+
serde_json::from_str(&content).expect("Failed to parse snapshot")
315+
}
316+
317+
let function_config: FunctionConfig =
318+
read_snapshot(&prj.root().join("snapshots/FunctionConfig.json"));
319+
let contract_config: ContractConfig =
320+
read_snapshot(&prj.root().join("snapshots/ContractConfig.json"));
321+
322+
// FunctionConfig {
323+
// test_isolated_function: 48926,
324+
// test_non_isolated_function: 27722,
325+
// }
326+
327+
// ContractConfig {
328+
// test_isolated_contract: 48926,
329+
// }
330+
331+
assert!(function_config.test_isolated_function > function_config.test_non_isolated_function);
332+
assert_eq!(function_config.test_isolated_function, contract_config.test_isolated_contract);
333+
});
334+
335+
forgetest_init!(config_inline_evm_version, |prj, cmd| {
205336
prj.wipe_contracts();
206337
prj.add_test(
207338
"inline.sol",

0 commit comments

Comments
 (0)