diff --git a/src/_modules/Commands.sol b/src/_modules/Commands.sol index 59c4cf55..e3656b1d 100644 --- a/src/_modules/Commands.sol +++ b/src/_modules/Commands.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.13 <0.9.0; import {VmSafe} from "forge-std/Vm.sol"; import {vulcan} from "./Vulcan.sol"; +import {Result, ResultType, Ok, Error} from "./Result.sol"; /// @dev Struct used to hold command parameters. Useful for creating commands that can be run /// multiple times @@ -11,6 +12,10 @@ struct Command { } struct CommandResult { + Result _inner; +} + +struct CommandOutput { int32 exitCode; bytes stdout; bytes stderr; @@ -163,12 +168,20 @@ library commands { /// @param inputs An array of strings representing the parameters of the command. /// @return result The result of the command as a bytes array. function run(string[] memory inputs) internal returns (CommandResult memory result) { - VmSafe.FfiResult memory ffiResult = vulcan.hevm.tryFfi(inputs); + try vulcan.hevm.tryFfi(inputs) returns (VmSafe.FfiResult memory ffiResult) { + CommandOutput memory output; + + output.exitCode = ffiResult.exit_code; + output.stdout = ffiResult.stdout; + output.stderr = ffiResult.stderr; + output.command = Command(inputs); + + return Ok(output); + } catch { + return CommandError.commandFailed(); + } + - result.exitCode = ffiResult.exit_code; - result.stdout = ffiResult.stdout; - result.stderr = ffiResult.stderr; - result.command = Command(inputs); } function run(string[1] memory inputs) internal returns (CommandResult memory) { @@ -251,40 +264,6 @@ library commands { return _toDynamic(inputs).run(); } - /// @dev Checks if a `CommandResult` returned an `ok` exit code. - function isOk(CommandResult memory self) internal pure returns (bool) { - return self.exitCode == 0; - } - - /// @dev Checks if a `CommandResult` struct is an error. - function isError(CommandResult memory self) internal pure returns (bool) { - return !self.isOk(); - } - - /// @dev Returns the output of a `CommandResult` or reverts if the result was an error. - function unwrap(CommandResult memory self) internal pure returns (bytes memory) { - string memory error; - - if (self.isError()) { - error = string.concat("Failed to run command ", self.command.toString()); - - if (self.stderr.length > 0) { - error = string.concat(error, ":\n\n", string(self.stderr)); - } - } - - return expect(self, error); - } - - /// @dev Returns the output of a `CommandResult` or reverts if the result was an error. - /// @param customError The error message that will be used when reverting. - function expect(CommandResult memory self, string memory customError) internal pure returns (bytes memory) { - if (self.isError()) { - revert(customError); - } - - return self.stdout; - } function _toDynamic(string[1] memory inputs) private pure returns (string[] memory _inputs) { _inputs = new string[](1); @@ -425,5 +404,59 @@ library commands { } } +library CommandError { + bytes32 constant COMMAND_FAILED = keccak256("COMMAND_FAILED"); + + function commandFailed() public pure returns (CommandResult memory) { + return CommandResult(Error(COMMAND_FAILED, "The command failed to execute").toResult()); + } +} + +library LibCommandResult { + /// @dev Checks if a `CommandResult` returned an `ok` exit code. + function isOk(CommandResult memory self) internal pure returns (bool) { + return self._inner.isOk(); + } + + /// @dev Checks if a `CommandResult` struct is an error. + function isError(CommandResult memory self) internal pure returns (bool) { + return self._inner.isError(); + } + + /// @dev Returns the output of a `CommandResult` or reverts if the result was an error. + function unwrap(CommandResult memory self) internal pure returns (CommandOutput memory) { + string memory error; + + if (self.isError()) { + CommandOutput memory output = abi.decode(self._inner.unwrap(), (CommandOutput)); + + error = string.concat("Failed to run command ", output.command.toString()); + + if (output.stderr.length > 0) { + error = string.concat(error, ":\n\n", string(output.stderr)); + } + } + + return expect(self, error); + } + + /// @dev Returns the output of a `CommandResult` or reverts if the result was an error. + /// @param customError The error message that will be used when reverting. + function expect(CommandResult memory self, string memory customError) internal pure returns (CommandOutput memory) { + if (self.isError()) { + revert(customError); + } + + CommandOutput memory output = abi.decode(self._inner.unwrap(), (CommandOutput)); + + return output; + } +} + +function Ok(CommandOutput memory output) pure returns (CommandResult memory) { + return CommandResult(Ok(abi.encode(output))); +} + using commands for Command global; using commands for CommandResult global; +using LibCommandResult for CommandResult global; diff --git a/src/_modules/experimental/Request.sol b/src/_modules/experimental/Request.sol index 7e9367d5..6fdac225 100644 --- a/src/_modules/experimental/Request.sol +++ b/src/_modules/experimental/Request.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import {Command, CommandResult, commands} from "../Commands.sol"; +import {Command, CommandResult, CommandOutput, commands} from "../Commands.sol"; import {JsonObject, json as jsonModule, JsonResult, Ok} from "../Json.sol"; import {Result, Error, StringResult, Ok} from "../Result.sol"; @@ -173,7 +173,9 @@ library LibRequestBuilder { return RequestError.commandFailed(); } - (uint256 status, bytes memory _body) = abi.decode(result.stdout, (uint256, bytes)); + CommandOutput memory cmdOutput = abi.decode(result._inner.unwrap(), (CommandOutput)); + + (uint256 status, bytes memory _body) = abi.decode(cmdOutput.stdout, (uint256, bytes)); return Ok(Response({url: req.url, status: status, body: _body})); } diff --git a/src/test.sol b/src/test.sol index 18dd34d2..7f0f4499 100644 --- a/src/test.sol +++ b/src/test.sol @@ -5,7 +5,7 @@ import {console} from "./_modules/Console.sol"; import {vulcan, Log} from "./_modules/Vulcan.sol"; import {any} from "./_modules/Any.sol"; import {accounts} from "./_modules/Accounts.sol"; -import {commands, Command, CommandResult} from "./_modules/Commands.sol"; +import {commands, Command, CommandResult, CommandOutput} from "./_modules/Commands.sol"; import {ctx} from "./_modules/Context.sol"; import {env} from "./_modules/Env.sol"; import {events} from "./_modules/Events.sol"; diff --git a/test/_modules/Commands.t.sol b/test/_modules/Commands.t.sol index a54cfd31..7d9e1af9 100644 --- a/test/_modules/Commands.t.sol +++ b/test/_modules/Commands.t.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity >=0.8.13 <0.9.0; import {Test, expect, commands, Command, CommandResult, ctx} from "../../src/test.sol"; @@ -15,14 +16,15 @@ contract CommandsTest is Test { string[2] memory inputs = ["echo", "'Hello, World!'"]; Command memory cmd = commands.create(inputs[0]).arg(inputs[1]); - expect(string(cmd.run().stdout)).toEqual(inputs[1]); + expect(string(cmd.run().unwrap().stdout)).toEqual(inputs[1]); } function testItCanRunCommandsDirectly() external { string[2] memory inputs = ["echo", "'Hello, World!'"]; + CommandResult memory result = commands.run(inputs); - expect(string(result.stdout)).toEqual(inputs[1]); + expect(string(result.unwrap().stdout)).toEqual(inputs[1]); } function testCommandToString() external { @@ -38,13 +40,13 @@ contract CommandsTest is Test { } function testIsNotOk() external { - CommandResult memory result = commands.run(["forge", "--hlkfshjfhjas"]); + CommandResult memory result = commands.run(["nonexistentcommand", "--hlkfshjfhjas"]); expect(result.isOk()).toBeFalse(); } function testIsError() external { - CommandResult memory result = commands.run(["forge", "--hlkfshjfhjas"]); + CommandResult memory result = commands.run(["nonexistentcommand", "--hlkfshjfhjas"]); expect(result.isError()).toBeTrue(); } @@ -58,21 +60,15 @@ contract CommandsTest is Test { function testUnwrap() external { CommandResult memory result = commands.run(["echo", "'Hello World'"]); - bytes memory output = result.unwrap(); + bytes memory output = result.unwrap().stdout; expect(string(output)).toEqual("'Hello World'"); } function testUnwrapReverts() external { - CommandResult memory result = commands.run(["forge", "--hlkfshjfhjas"]); - - bytes memory expectedError = bytes( - string.concat( - "Failed to run command forge --hlkfshjfhjas:\n\n", - "error: unexpected argument '--hlkfshjfhjas' found\n\n", - "Usage: forge \n\n" "For more information, try '--help'.\n" - ) - ); + CommandResult memory result = commands.run(["nonexistentcommand", "--hlkfshjfhjas"]); + + bytes memory expectedError = bytes("The command failed to execute"); ctx.expectRevert(expectedError); diff --git a/test/_modules/Fe.t.sol b/test/_modules/Fe.t.sol index 35b9c4e7..05ec1ed7 100644 --- a/test/_modules/Fe.t.sol +++ b/test/_modules/Fe.t.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity >=0.8.13 <0.9.0; import {Test, expect, commands, Command, fe, Fe, fs, println, strings} from "../../src/test.sol"; diff --git a/test/_modules/Forks.t.sol b/test/_modules/Forks.t.sol index d7d09cfc..65870f93 100644 --- a/test/_modules/Forks.t.sol +++ b/test/_modules/Forks.t.sol @@ -1,6 +1,6 @@ pragma solidity >=0.8.13 <0.9.0; -import {Test, expect, commands, forks, Fork, CommandResult, println} from "../../src/test.sol"; +import {Test, expect, commands, forks, Fork, CommandResult, CommandOutput, println} from "../../src/test.sol"; import {Sender} from "../mocks/Sender.sol"; contract ForksTest is Test { @@ -13,7 +13,9 @@ contract ForksTest is Test { ["--silent", "-H", "Content-Type: application/json", "-X", "POST", "--data", data, ENDPOINT] ).run(); - if (res.stdout.length == 0) { + CommandOutput memory cmdOutput = res.unwrap(); + + if (cmdOutput.stdout.length == 0) { println("Skipping test because forking endpoint is not available"); return; } diff --git a/test/_modules/Huff.t.sol b/test/_modules/Huff.t.sol index f7673e39..3b4926ca 100644 --- a/test/_modules/Huff.t.sol +++ b/test/_modules/Huff.t.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity >=0.8.13 <0.9.0; import {Test, expect, Command, CommandResult, console, huff, Huffc} from "../../src/test.sol"; @@ -54,6 +55,6 @@ contract HuffTest is Test { function testCompile() external { CommandResult memory initcode = huff.create().setFilePath("./test/mocks/Getter.huff").compile(); - expect(initcode.stdout.length).toBeGreaterThan(0); + expect(initcode.unwrap().stdout.length).toBeGreaterThan(0); } }