Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add results to commands #179

Merged
merged 10 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 69 additions & 42 deletions src/_modules/Commands.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ 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";
import {removeSelector} from "../_utils/removeSelector.sol";

/// @dev Struct used to hold command parameters. Useful for creating commands that can be run
/// multiple times
Expand All @@ -11,6 +13,10 @@ struct Command {
}

struct CommandResult {
Result _inner;
}

struct CommandOutput {
int32 exitCode;
bytes stdout;
bytes stderr;
Expand Down Expand Up @@ -163,12 +169,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);

result.exitCode = ffiResult.exit_code;
result.stdout = ffiResult.stdout;
result.stderr = ffiResult.stderr;
result.command = Command(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 Error(string memory message) {
return CommandError.notExecuted(message);
} catch (bytes memory message) {
return CommandError.notExecuted(abi.decode(removeSelector(message), (string)));
}
}

function run(string[1] memory inputs) internal returns (CommandResult memory) {
Expand Down Expand Up @@ -251,41 +265,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);
_inputs[0] = inputs[0];
Expand Down Expand Up @@ -425,5 +404,53 @@ library commands {
}
}

library CommandError {
bytes32 constant NOT_EXECUTED = keccak256("COMMAND_NOT_EXECUTED");

function notExecuted(string memory reason) public pure returns (CommandResult memory) {
string memory message = string.concat("The command was not executed: \"", reason, "\"");
return CommandResult(Error(NOT_EXECUTED, message).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) {
return abi.decode(self._inner.unwrap(), (CommandOutput));
}

/// @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)
{
return abi.decode(self._inner.expect(customError), (CommandOutput));
}

function toValue(CommandResult memory self) internal pure returns (CommandOutput memory) {
return abi.decode(self._inner.toValue(), (CommandOutput));
}

function toError(CommandResult memory self) internal pure returns (Error memory) {
return self._inner.toError();
}
}

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;
8 changes: 5 additions & 3 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 @@ -170,10 +170,12 @@ library LibRequestBuilder {
CommandResult memory result = req.toCommand().run();

if (result.isError()) {
return RequestError.commandFailed();
return ResponseResult(result.toError().toResult());
}

(uint256 status, bytes memory _body) = abi.decode(result.stdout, (uint256, bytes));
CommandOutput memory cmdOutput = result.toValue();

(uint256 status, bytes memory _body) = abi.decode(cmdOutput.stdout, (uint256, bytes));

return Ok(Response({url: req.url, status: status, body: _body}));
}
Expand Down
16 changes: 16 additions & 0 deletions src/_utils/removeSelector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.13 <0.9.0;

function removeSelector(bytes memory data) pure returns (bytes memory) {
require(data.length >= 4, "Input data is too short");

// Create a new bytes variable to store the result
bytes memory result = new bytes(data.length - 4);

// Copy the remaining data (excluding the first 4 bytes) into the result
for (uint256 i = 4; i < data.length; i++) {
result[i - 4] = data[i];
}

return result;
}
1 change: 1 addition & 0 deletions src/script.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {fmt} from "./_modules/Fmt.sol";
import {bound} from "./_utils/bound.sol";
import {format} from "./_utils/format.sol";
import {println} from "./_utils/println.sol";
import {removeSelector} from "./_utils/removeSelector.sol";
import {huff, Huffc} from "./_modules/Huff.sol";
import {fe, Fe} from "./_modules/Fe.sol";

Expand Down
3 changes: 2 additions & 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 All @@ -25,6 +25,7 @@ import {format} from "./_utils/format.sol";
import {println} from "./_utils/println.sol";
import {bound} from "./_utils/bound.sol";
import {formatError} from "./_utils/formatError.sol";
import {removeSelector} from "./_utils/removeSelector.sol";
import {Ok} from "./_modules/Result.sol";

// @dev Main entry point to Vulcan tests
Expand Down
25 changes: 11 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,16 @@ 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 was not executed: \"Failed to execute command: No such file or directory (os error 2)\"");

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);
}
}