Skip to content

Commit

Permalink
feat!: add results to commands
Browse files Browse the repository at this point in the history
Add results to the commands module similar to the requests experimental
module.
  • Loading branch information
gnkz committed Sep 4, 2023
1 parent 4fcd985 commit a6828d7
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 59 deletions.
111 changes: 72 additions & 39 deletions src/_modules/Commands.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -11,6 +12,10 @@ struct Command {
}

struct CommandResult {
Result _inner;
}

struct CommandOutput {
int32 exitCode;
bytes stdout;
bytes stderr;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
6 changes: 4 additions & 2 deletions src/_modules/experimental/Request.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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}));
}
Expand Down
2 changes: 1 addition & 1 deletion src/test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
24 changes: 10 additions & 14 deletions test/_modules/Commands.t.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 {
Expand All @@ -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();
}
Expand All @@ -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 <COMMAND>\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);

Expand Down
1 change: 1 addition & 0 deletions test/_modules/Fe.t.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
6 changes: 4 additions & 2 deletions test/_modules/Forks.t.sol
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion test/_modules/Huff.t.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
}
}

0 comments on commit a6828d7

Please sign in to comment.