From cccec6574c7d7be8e134e0f5246972b6fc2ad459 Mon Sep 17 00:00:00 2001 From: Eric <5089238+emizzle@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:14:28 +1100 Subject: [PATCH] fixes to get hardhat process launching at the test-level - removed ethersuite as the base testing suite for multinodesuite because the need to launch hardhat before ethersuite setup and kill hardhat after ethersuite teardown was required. Removing ethersuite allowed for checking if a connection to hardhat was established. If no connection established, hardhat would need to be started via the test config. If there was a connection established, a snapshot is taken during test setup and reverted during teardown. - modified the way the hardhat process was launched, because evaluating a command using chronos startProcess would wait for the command to complete before access to the output stream was given. Instead, we now launch the hardhat executable from node_modules/.bin/hardhat. - modify the way the processes are exited by killing them immediately. - fix warnings --- tests/integration/codexclient.nim | 1 - tests/integration/codexprocess.nim | 3 ++ tests/integration/hardhatprocess.nim | 52 ++++++++++++++----- tests/integration/marketplacesuite.nim | 1 - tests/integration/multinodes.nim | 70 +++++++++++++++++--------- tests/integration/nodeprocess.nim | 68 ++++++++++++++----------- tests/integration/testIntegration.nim | 8 +-- tests/integration/testproofs.nim | 21 ++++---- 8 files changed, 143 insertions(+), 81 deletions(-) diff --git a/tests/integration/codexclient.nim b/tests/integration/codexclient.nim index e3409b82..68235237 100644 --- a/tests/integration/codexclient.nim +++ b/tests/integration/codexclient.nim @@ -1,6 +1,5 @@ import std/httpclient import std/strutils -import std/sequtils from pkg/libp2p import Cid, `$`, init import pkg/chronicles diff --git a/tests/integration/codexprocess.nim b/tests/integration/codexprocess.nim index 91a79c99..05fda4c3 100644 --- a/tests/integration/codexprocess.nim +++ b/tests/integration/codexprocess.nim @@ -33,6 +33,9 @@ method startedOutput(node: CodexProcess): string = method processOptions(node: CodexProcess): set[AsyncProcessOption] = return {AsyncProcessOption.StdErrToStdOut} +method outputLineEndings(node: CodexProcess): string = + return "\n" + method onOutputLineCaptured(node: CodexProcess, line: string) = discard diff --git a/tests/integration/hardhatprocess.nim b/tests/integration/hardhatprocess.nim index 67219902..e99276cd 100644 --- a/tests/integration/hardhatprocess.nim +++ b/tests/integration/hardhatprocess.nim @@ -4,12 +4,10 @@ import pkg/confutils import pkg/chronicles import pkg/chronos import pkg/stew/io2 -import std/osproc import std/os import std/sets -import std/streams +import std/sequtils import std/strutils -import std/sugar import pkg/codex/conf import pkg/codex/utils/trackedfutures import ./codexclient @@ -30,13 +28,16 @@ method workingDir(node: HardhatProcess): string = return currentSourcePath() / ".." / ".." / ".." / "vendor" / "codex-contracts-eth" method executable(node: HardhatProcess): string = - return "npm start" + return "node_modules" / ".bin" / "hardhat" method startedOutput(node: HardhatProcess): string = return "Started HTTP and WebSocket JSON-RPC server at" method processOptions(node: HardhatProcess): set[AsyncProcessOption] = - return {AsyncProcessOption.EvalCommand, AsyncProcessOption.StdErrToStdOut} + return {} + +method outputLineEndings(node: HardhatProcess): string = + return "\n" proc openLogFile(node: HardhatProcess, logFilePath: string): IoHandle = let logFileHandle = openFile( @@ -53,11 +54,31 @@ proc openLogFile(node: HardhatProcess, logFilePath: string): IoHandle = return fileHandle +method start*(node: HardhatProcess) {.async.} = + + let poptions = node.processOptions + {AsyncProcessOption.StdErrToStdOut} + trace "starting node", + args = node.arguments, + executable = node.executable, + workingDir = node.workingDir, + processOptions = poptions + + try: + node.process = await startProcess( + node.executable, + node.workingDir, + @["node", "--export", "deployment-localhost.json"].concat(node.arguments), + options = poptions, + stdoutHandle = AsyncProcess.Pipe + ) + except CatchableError as e: + error "failed to start node process", error = e.msg + proc startNode*( _: type HardhatProcess, - args: seq[string] = @[], + args: seq[string], debug: string | bool = false, - name: string = "hardhat" + name: string ): Future[HardhatProcess] {.async.} = var logFilePath = "" @@ -70,14 +91,20 @@ proc startNode*( arguments.add arg trace "starting hardhat node", arguments - echo ">>> starting hardhat node with args: ", arguments - let node = await NodeProcess.startNode(arguments, debug, "hardhat") - let hardhat = HardhatProcess(node) + ## Starts a Hardhat Node with the specified arguments. + ## Set debug to 'true' to see output of the node. + let hardhat = HardhatProcess( + arguments: arguments, + debug: ($debug != "false"), + trackedFutures: TrackedFutures.new(), + name: "hardhat" + ) + + await hardhat.start() if logFilePath != "": hardhat.logFile = some hardhat.openLogFile(logFilePath) - # let hardhat = HardhatProcess() return hardhat method onOutputLineCaptured(node: HardhatProcess, line: string) = @@ -91,9 +118,10 @@ method onOutputLineCaptured(node: HardhatProcess, line: string) = method stop*(node: HardhatProcess) {.async.} = # terminate the process - procCall NodeProcess(node).stop() + await procCall NodeProcess(node).stop() if logFile =? node.logFile: + trace "closing hardhat log file" discard logFile.closeFile() method removeDataDir*(node: HardhatProcess) = diff --git a/tests/integration/marketplacesuite.nim b/tests/integration/marketplacesuite.nim index d7cff91e..9d1d44b9 100644 --- a/tests/integration/marketplacesuite.nim +++ b/tests/integration/marketplacesuite.nim @@ -90,7 +90,6 @@ template marketplacesuite*(name: string, body: untyped) = discard setup: - echo "[marketplacesuite.setup] setup start" marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner()) let tokenAddress = await marketplace.token() token = Erc20Token.new(tokenAddress, ethProvider.getSigner()) diff --git a/tests/integration/multinodes.nim b/tests/integration/multinodes.nim index 8ca02a53..9f1e5117 100644 --- a/tests/integration/multinodes.nim +++ b/tests/integration/multinodes.nim @@ -4,13 +4,15 @@ import std/strutils import std/sugar import std/times import pkg/chronicles -import ../ethertest +import pkg/ethers +import pkg/asynctest import ./hardhatprocess import ./codexprocess import ./hardhatconfig import ./codexconfig -export ethertest +export asynctest +export ethers except `%` export hardhatprocess export codexprocess export hardhatconfig @@ -44,25 +46,28 @@ proc nextFreePort(startPort: int): Future[int] {.async.} = "lsof -ti:" var port = startPort while true: + trace "checking if port is free", port let portInUse = await execCommandEx(cmd & $port) if portInUse.stdOutput == "": - echo "port ", port, " is free" + trace "port is free", port return port else: inc port template multinodesuite*(name: string, body: untyped) = - ethersuite name: + asyncchecksuite name: var running: seq[RunningNode] var bootstrap: string let starttime = now().format("yyyy-MM-dd'_'HH:mm:ss") var currentTestName = "" var nodeConfigs: NodeConfigs + var ethProvider {.inject, used.}: JsonRpcProvider + var accounts {.inject, used.}: seq[Address] + var snapshot: JsonNode template test(tname, startNodeConfigs, tbody) = - echo "[multinodes] inside test template, tname: ", tname, ", startNodeConfigs: ", startNodeConfigs currentTestName = tname nodeConfigs = startNodeConfigs test tname: @@ -101,11 +106,11 @@ template multinodesuite*(name: string, body: untyped) = if config.logFile: let updatedLogFile = getLogFile(role, none int) args.add "--log-file=" & updatedLogFile - echo ">>> [multinodes] starting hardhat node with args: ", args + let node = await HardhatProcess.startNode(args, config.debugEnabled, "hardhat") await node.waitUntilStarted() - debug "started new hardhat node" + trace "hardhat node started" return node proc newCodexProcess(roleIdx: int, @@ -148,25 +153,30 @@ template multinodesuite*(name: string, body: untyped) = "--eth-account=" & $accounts[nodeIdx]]) let node = await CodexProcess.startNode(args, conf.debugEnabled, $role & $roleIdx) - echo "[multinodes.newCodexProcess] waiting until ", role, " node started" await node.waitUntilStarted() - echo "[multinodes.newCodexProcess] ", role, " NODE STARTED" + trace "node started", nodeName = $role & $roleIdx return node - proc clients(): seq[CodexProcess] {.used.} = + proc hardhat: HardhatProcess = + for r in running: + if r.role == Role.Hardhat: + return HardhatProcess(r.node) + return nil + + proc clients: seq[CodexProcess] {.used.} = return collect: for r in running: if r.role == Role.Client: CodexProcess(r.node) - proc providers(): seq[CodexProcess] {.used.} = + proc providers: seq[CodexProcess] {.used.} = return collect: for r in running: if r.role == Role.Provider: CodexProcess(r.node) - proc validators(): seq[CodexProcess] {.used.} = + proc validators: seq[CodexProcess] {.used.} = return collect: for r in running: if r.role == Role.Validator: @@ -204,35 +214,38 @@ template multinodesuite*(name: string, body: untyped) = return await newCodexProcess(validatorIdx, config, Role.Validator) setup: - echo "[multinodes.setup] setup start" if not nodeConfigs.hardhat.isNil: - echo "[multinodes.setup] starting hardhat node " let node = await startHardhatNode() running.add RunningNode(role: Role.Hardhat, node: node) + try: + ethProvider = JsonRpcProvider.new("ws://localhost:8545") + # if hardhat was NOT started by the test, take a snapshot so it can be + # reverted in the test teardown + if nodeConfigs.hardhat.isNil: + snapshot = await send(ethProvider, "evm_snapshot") + accounts = await ethProvider.listAccounts() + except CatchableError as e: + fatal "failed to connect to hardhat", error = e.msg + raiseAssert "Hardhat not running. Run hardhat manually before executing tests, or include a HardhatConfig in the test setup." + if not nodeConfigs.clients.isNil: for i in 0.. 0: + error "failed to exit process, check for zombies", exitCode + + trace "closing node process' streams" await node.process.closeWait() - except AsyncTimeoutError as e: - error "waiting for process exit timed out", error = e.msgDetail except CatchableError as e: error "error stopping node process", error = e.msg + finally: node.process = nil + trace "node stopped" proc waitUntilStarted*(node: NodeProcess) {.async.} = diff --git a/tests/integration/testIntegration.nim b/tests/integration/testIntegration.nim index 497c9dd1..2287fd59 100644 --- a/tests/integration/testIntegration.nim +++ b/tests/integration/testIntegration.nim @@ -20,7 +20,7 @@ import ./marketplacesuite # You can also pass a string in same format like for the `--log-level` parameter # to enable custom logging levels for specific topics like: debug2 = "INFO; TRACE: marketplace" -twonodessuite "Integration tests", debug1 = true, debug2 = true: +twonodessuite "Integration tests", debug1 = false, debug2 = false: setup: # Our Hardhat configuration does use automine, which means that time tracked by `ethProvider.currentTime()` is not # advanced until blocks are mined and that happens only when transaction is submitted. @@ -232,20 +232,20 @@ marketplacesuite "Marketplace payouts": test "expired request partially pays out for stored time", NodeConfigs( - # Uncomment to start Hardhat automatically, mainly so logs can be inspected locally + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally # hardhat: HardhatConfig().withLogFile() clients: CodexConfig() .nodes(1) - .debug() # uncomment to enable console log output.debug() + # .debug() # uncomment to enable console log output.debug() .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log .withLogTopics("node", "erasure"), providers: CodexConfig() .nodes(1) - .debug() # uncomment to enable console log output + # .debug() # uncomment to enable console log output .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log .withLogTopics("marketplace", "sales", "reservations", "node", "proving", "clock"), ): diff --git a/tests/integration/testproofs.nim b/tests/integration/testproofs.nim index 0f1c88f5..db447c1d 100644 --- a/tests/integration/testproofs.nim +++ b/tests/integration/testproofs.nim @@ -16,20 +16,20 @@ logScope: marketplacesuite "Hosts submit regular proofs": test "hosts submit periodic proofs for slots they fill", NodeConfigs( - # Uncomment to start Hardhat automatically, mainly so logs can be inspected locally - # hardhat: HardhatConfig().debug().withLogFile(), + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally + # hardhat: HardhatConfig().withLogFile(), clients: CodexConfig() .nodes(1) - .debug() # uncomment to enable console log output + # .debug() # uncomment to enable console log output .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log .withLogTopics("node"), providers: CodexConfig() .nodes(1) - .debug() # uncomment to enable console log output + # .debug() # uncomment to enable console log output .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log .withLogTopics("marketplace", "sales", "reservations", "node"), ): @@ -56,7 +56,6 @@ marketplacesuite "Hosts submit regular proofs": await subscription.unsubscribe() - marketplacesuite "Simulate invalid proofs": # TODO: these are very loose tests in that they are not testing EXACTLY how @@ -65,8 +64,8 @@ marketplacesuite "Simulate invalid proofs": # proofs are being marked as missed by the validator. test "slot is freed after too many invalid proofs submitted", NodeConfigs( - # Uncomment to start Hardhat automatically, mainly so logs can be inspected locally - # hardhat: HardhatConfig().debug().withLogFile(), + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally + # hardhat: HardhatConfig().withLogFile(), clients: CodexConfig() @@ -117,8 +116,8 @@ marketplacesuite "Simulate invalid proofs": await subscription.unsubscribe() test "slot is not freed when not enough invalid proofs submitted", NodeConfigs( - # Uncomment to start Hardhat automatically, mainly so logs can be inspected locally - # hardhat: HardhatConfig().debug().withLogFile(), + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally + # hardhat: HardhatConfig().withLogFile(), clients: CodexConfig() @@ -170,8 +169,8 @@ marketplacesuite "Simulate invalid proofs": await subscription.unsubscribe() test "host that submits invalid proofs is paid out less", NodeConfigs( - # Uncomment to start Hardhat automatically, mainly so logs can be inspected locally - # hardhat: HardhatConfig().debug().withLogFile(), + # Uncomment to start Hardhat automatically, typically so logs can be inspected locally + # hardhat: HardhatConfig().withLogFile(), clients: CodexConfig()