mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-01-15 11:53:12 +00:00
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
This commit is contained in:
parent
109f9461b3
commit
cccec6574c
@ -1,6 +1,5 @@
|
||||
import std/httpclient
|
||||
import std/strutils
|
||||
import std/sequtils
|
||||
|
||||
from pkg/libp2p import Cid, `$`, init
|
||||
import pkg/chronicles
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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) =
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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..<nodeConfigs.clients.numNodes:
|
||||
echo "[multinodes.setup] starting client node ", i
|
||||
let node = await startClientNode()
|
||||
running.add RunningNode(
|
||||
role: Role.Client,
|
||||
node: node
|
||||
)
|
||||
echo "[multinodes.setup] added running client node ", i
|
||||
if i == 0:
|
||||
echo "[multinodes.setup] getting client 0 bootstrap spr"
|
||||
bootstrap = CodexProcess(node).client.info()["spr"].getStr()
|
||||
echo "[multinodes.setup] got client 0 bootstrap spr: ", bootstrap
|
||||
|
||||
if not nodeConfigs.providers.isNil:
|
||||
for i in 0..<nodeConfigs.providers.numNodes:
|
||||
echo "[multinodes.setup] starting provider node ", i
|
||||
let node = await startProviderNode()
|
||||
running.add RunningNode(
|
||||
role: Role.Provider,
|
||||
node: node
|
||||
)
|
||||
echo "[multinodes.setup] added running provider node ", i
|
||||
|
||||
if not nodeConfigs.validators.isNil:
|
||||
for i in 0..<nodeConfigs.validators.numNodes:
|
||||
@ -241,12 +254,21 @@ template multinodesuite*(name: string, body: untyped) =
|
||||
role: Role.Validator,
|
||||
node: node
|
||||
)
|
||||
echo "[multinodes.setup] added running validator node ", i
|
||||
|
||||
teardown:
|
||||
for r in running:
|
||||
await r.node.stop() # also stops rest client
|
||||
r.node.removeDataDir()
|
||||
for nodes in @[validators(), clients(), providers()]:
|
||||
for node in nodes:
|
||||
await node.stop() # also stops rest client
|
||||
node.removeDataDir()
|
||||
|
||||
# if hardhat was started in the test, kill the node
|
||||
# otherwise revert the snapshot taken in the test setup
|
||||
let hardhat = hardhat()
|
||||
if not hardhat.isNil:
|
||||
await hardhat.stop()
|
||||
else:
|
||||
discard await send(ethProvider, "evm_revert", @[snapshot])
|
||||
|
||||
running = @[]
|
||||
|
||||
body
|
||||
|
||||
@ -3,11 +3,7 @@ import pkg/questionable/results
|
||||
import pkg/confutils
|
||||
import pkg/chronicles
|
||||
import pkg/libp2p
|
||||
import pkg/stew/byteutils
|
||||
import std/osproc
|
||||
import std/os
|
||||
import std/sequtils
|
||||
import std/streams
|
||||
import std/strutils
|
||||
import codex/conf
|
||||
import codex/utils/exceptions
|
||||
@ -40,24 +36,35 @@ method startedOutput(node: NodeProcess): string {.base.} =
|
||||
method processOptions(node: NodeProcess): set[AsyncProcessOption] {.base.} =
|
||||
raiseAssert "[processOptions] not implemented"
|
||||
|
||||
method outputLineEndings(node: NodeProcess): string {.base.} =
|
||||
raiseAssert "[outputLineEndings] not implemented"
|
||||
|
||||
method onOutputLineCaptured(node: NodeProcess, line: string) {.base.} =
|
||||
raiseAssert "[onOutputLineCaptured] not implemented"
|
||||
|
||||
method start(node: NodeProcess) {.base, async.} =
|
||||
method start*(node: NodeProcess) {.base, async.} =
|
||||
logScope:
|
||||
nodeName = node.name
|
||||
|
||||
trace "starting node", args = node.arguments
|
||||
let poptions = node.processOptions + {AsyncProcessOption.StdErrToStdOut}
|
||||
trace "starting node",
|
||||
args = node.arguments,
|
||||
executable = node.executable,
|
||||
workingDir = node.workingDir,
|
||||
processOptions = poptions
|
||||
|
||||
node.process = await startProcess(
|
||||
node.executable,
|
||||
node.workingDir,
|
||||
node.arguments,
|
||||
options = node.processOptions,
|
||||
stdoutHandle = AsyncProcess.Pipe
|
||||
)
|
||||
try:
|
||||
node.process = await startProcess(
|
||||
node.executable,
|
||||
node.workingDir,
|
||||
node.arguments,
|
||||
options = poptions,
|
||||
stdoutHandle = AsyncProcess.Pipe
|
||||
)
|
||||
except CatchableError as e:
|
||||
error "failed to start node process", error = e.msg
|
||||
|
||||
proc captureOutput*(
|
||||
proc captureOutput(
|
||||
node: NodeProcess,
|
||||
output: string,
|
||||
started: Future[void]
|
||||
@ -68,20 +75,23 @@ proc captureOutput*(
|
||||
|
||||
trace "waiting for output", output
|
||||
|
||||
let stream = node.process.stdOutStream
|
||||
let stream = node.process.stdoutStream
|
||||
|
||||
try:
|
||||
while(let line = await stream.readLine(0, "\n"); line != ""):
|
||||
if node.debug:
|
||||
# would be nice if chronicles could parse and display with colors
|
||||
echo line
|
||||
while node.process.running.option == some true:
|
||||
while(let line = await stream.readLine(0, node.outputLineEndings); line != ""):
|
||||
if node.debug:
|
||||
# would be nice if chronicles could parse and display with colors
|
||||
echo line
|
||||
|
||||
if not started.isNil and not started.finished and line.contains(output):
|
||||
started.complete()
|
||||
if not started.isNil and not started.finished and line.contains(output):
|
||||
started.complete()
|
||||
|
||||
node.onOutputLineCaptured(line)
|
||||
node.onOutputLineCaptured(line)
|
||||
|
||||
await sleepAsync(1.millis)
|
||||
await sleepAsync(1.millis)
|
||||
|
||||
except AsyncStreamReadError as e:
|
||||
error "error reading output stream", error = e.msgDetail
|
||||
|
||||
@ -110,18 +120,20 @@ method stop*(node: NodeProcess) {.base, async.} =
|
||||
await node.trackedFutures.cancelTracked()
|
||||
if node.process != nil:
|
||||
try:
|
||||
if err =? node.process.terminate().errorOption:
|
||||
error "failed to terminate node process", errorCode = err
|
||||
discard await node.process.waitForExit(timeout=5.seconds)
|
||||
# close process' streams
|
||||
trace "waiting for node process to exit"
|
||||
let exitCode = await node.process.waitForExit(ZeroDuration)
|
||||
if exitCode > 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.} =
|
||||
|
||||
@ -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/<start_datetime> <suite_name>/<test_name>/<node_role>_<node_idx>.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/<start_datetime> <suite_name>/<test_name>/<node_role>_<node_idx>.log
|
||||
.withLogTopics("marketplace", "sales", "reservations", "node", "proving", "clock"),
|
||||
):
|
||||
|
||||
@ -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/<start_datetime> <suite_name>/<test_name>/<node_role>_<node_idx>.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/<start_datetime> <suite_name>/<test_name>/<node_role>_<node_idx>.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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user