mirror of
https://github.com/codex-storage/nim-codex.git
synced 2025-02-10 03:55:30 +00:00
ensure timeouts work
Work around the AsyncProcess timeout not working correctly
This commit is contained in:
parent
f49a55e724
commit
aebe655575
@ -40,7 +40,6 @@ type
|
|||||||
hardhatPortLock: AsyncLock
|
hardhatPortLock: AsyncLock
|
||||||
hardhatProcessLock: AsyncLock
|
hardhatProcessLock: AsyncLock
|
||||||
testTimeout: Duration # individual test timeout
|
testTimeout: Duration # individual test timeout
|
||||||
futContinuousUpdates: Future[void].Raising([])
|
|
||||||
|
|
||||||
IntegrationTestConfig* = object
|
IntegrationTestConfig* = object
|
||||||
startHardhat: bool
|
startHardhat: bool
|
||||||
@ -64,6 +63,7 @@ type
|
|||||||
output: ?!CommandExResponse
|
output: ?!CommandExResponse
|
||||||
testId: string # when used in datadir path, prevents data dir clashes
|
testId: string # when used in datadir path, prevents data dir clashes
|
||||||
status: IntegrationTestStatus
|
status: IntegrationTestStatus
|
||||||
|
command: string
|
||||||
|
|
||||||
TestManagerError* = object of CatchableError
|
TestManagerError* = object of CatchableError
|
||||||
|
|
||||||
@ -214,8 +214,8 @@ proc printOutputMarker(
|
|||||||
|
|
||||||
proc printResult(
|
proc printResult(
|
||||||
test: IntegrationTest,
|
test: IntegrationTest,
|
||||||
printStdOut = false,
|
printStdOut = test.manager.debugCodexNodes,
|
||||||
printStdErr = false) =
|
printStdErr = test.manager.debugTestHarness) =
|
||||||
|
|
||||||
case test.status:
|
case test.status:
|
||||||
of IntegrationTestStatus.New:
|
of IntegrationTestStatus.New:
|
||||||
@ -249,6 +249,13 @@ proc printResult(
|
|||||||
test.printResult(fgRed)
|
test.printResult(fgRed)
|
||||||
|
|
||||||
of IntegrationTestStatus.Timeout:
|
of IntegrationTestStatus.Timeout:
|
||||||
|
if printStdOut and
|
||||||
|
output =? test.output:
|
||||||
|
test.printOutputMarker(MarkerPosition.Start,
|
||||||
|
"codex node output (stdout)")
|
||||||
|
echo output.stdOutput
|
||||||
|
test.printOutputMarker(MarkerPosition.Finish,
|
||||||
|
"codex node output (stdout)")
|
||||||
test.printResult(fgYellow)
|
test.printResult(fgYellow)
|
||||||
|
|
||||||
of IntegrationTestStatus.Ok:
|
of IntegrationTestStatus.Ok:
|
||||||
@ -325,54 +332,74 @@ proc buildCommand(
|
|||||||
", testFile: " & testFile &
|
", testFile: " & testFile &
|
||||||
", error: " & parent.msg,
|
", error: " & parent.msg,
|
||||||
parent
|
parent
|
||||||
|
proc setup(
|
||||||
|
test: IntegrationTest): Future[?Hardhat] {.async: (raises: [CancelledError, TestManagerError]).} =
|
||||||
|
|
||||||
proc runTest(
|
var hardhat = Hardhat.none
|
||||||
manager: TestManager,
|
var hardhatPort = int.none
|
||||||
config: IntegrationTestConfig) {.async: (raises: [CancelledError]).} =
|
|
||||||
|
if test.config.startHardhat:
|
||||||
|
let hh = await test.startHardhat()
|
||||||
|
hardhat = some hh
|
||||||
|
hardhatPort = some hh.port
|
||||||
|
test.manager.hardhats.add hh
|
||||||
|
|
||||||
|
test.command = await test.buildCommand(hardhatPort)
|
||||||
|
|
||||||
|
return hardhat
|
||||||
|
|
||||||
|
proc teardown(
|
||||||
|
test: IntegrationTest,
|
||||||
|
hardhat: ?Hardhat) {.async: (raises: [CancelledError]).} =
|
||||||
|
|
||||||
|
if test.config.startHardhat and hardhat =? hardhat:
|
||||||
|
try:
|
||||||
|
trace "Stopping hardhat", name = test.config.name
|
||||||
|
await hardhat.process.stop()
|
||||||
|
except CatchableError as e:
|
||||||
|
warn "Failed to stop hardhat node, continuing",
|
||||||
|
error = e.msg, test = test.config.name
|
||||||
|
|
||||||
|
if test.manager.debugHardhat:
|
||||||
|
test.printOutputMarker(MarkerPosition.Start, "Hardhat stdout")
|
||||||
|
for line in hardhat.output:
|
||||||
|
echo line
|
||||||
|
test.printOutputMarker(MarkerPosition.Finish, "Hardhat stdout")
|
||||||
|
|
||||||
|
test.manager.hardhats.keepItIf( it != hardhat )
|
||||||
|
|
||||||
|
proc start(test: IntegrationTest) {.async: (raises: []).} =
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
config
|
config = test.config
|
||||||
|
|
||||||
trace "Running test"
|
trace "Running test"
|
||||||
|
|
||||||
var test = IntegrationTest(
|
|
||||||
manager: manager,
|
|
||||||
config: config,
|
|
||||||
testId: $ uint16.example
|
|
||||||
)
|
|
||||||
|
|
||||||
test.timeStart = some Moment.now()
|
test.timeStart = some Moment.now()
|
||||||
test.status = IntegrationTestStatus.Running
|
test.status = IntegrationTestStatus.Running
|
||||||
manager.tests.add test
|
|
||||||
|
|
||||||
var hardhat: Hardhat
|
var hardhat = none Hardhat
|
||||||
var hardhatPort = int.none
|
|
||||||
var command: string
|
|
||||||
try:
|
try:
|
||||||
if config.startHardhat:
|
|
||||||
hardhat = await test.startHardhat()
|
try:
|
||||||
hardhatPort = hardhat.port.some
|
hardhat = await test.setup()
|
||||||
manager.hardhats.add hardhat
|
|
||||||
command = await test.buildCommand(hardhatPort)
|
|
||||||
except TestManagerError as e:
|
except TestManagerError as e:
|
||||||
error "Failed to start hardhat and build command", error = e.msg
|
error "Failed to start hardhat and build command", error = e.msg
|
||||||
test.timeEnd = some Moment.now()
|
test.timeEnd = some Moment.now()
|
||||||
test.status = IntegrationTestStatus.Error
|
test.status = IntegrationTestStatus.Error
|
||||||
test.output = CommandExResponse.failure(e)
|
test.output = CommandExResponse.failure(e)
|
||||||
test.printResult(printStdOut = manager.debugHardhat or manager.debugCodexNodes,
|
|
||||||
printStdErr = manager.debugTestHarness)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
trace "Starting parallel integration test", command
|
trace "Starting parallel integration test", command = test.command
|
||||||
test.printStart()
|
test.printStart()
|
||||||
test.process = execCommandEx(
|
test.process = execCommandEx(
|
||||||
command = command,
|
command = test.command,
|
||||||
timeout = manager.testTimeout
|
timeout = test.manager.testTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
let output = await test.process # waits on waitForExit
|
let output = await test.process # waits on waitForExit
|
||||||
test.output = success(output)
|
test.output = success(output)
|
||||||
|
|
||||||
@ -381,45 +408,26 @@ proc runTest(
|
|||||||
else:
|
else:
|
||||||
test.status = IntegrationTestStatus.Ok
|
test.status = IntegrationTestStatus.Ok
|
||||||
|
|
||||||
except CancelledError as e:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
except AsyncProcessTimeoutError as e:
|
except AsyncProcessTimeoutError as e:
|
||||||
test.timeEnd = some Moment.now()
|
test.timeEnd = some Moment.now()
|
||||||
error "Test timed out", name = config.name, duration = test.duration
|
error "Test timed out", name = test.config.name, duration = test.duration
|
||||||
test.output = CommandExResponse.failure(e)
|
test.output = CommandExResponse.failure(e)
|
||||||
test.status = IntegrationTestStatus.Timeout
|
test.status = IntegrationTestStatus.Timeout
|
||||||
test.printResult(printStdOut = manager.debugCodexNodes,
|
|
||||||
printStdErr = manager.debugTestHarness)
|
|
||||||
|
|
||||||
except AsyncProcessError as e:
|
except AsyncProcessError as e:
|
||||||
test.timeEnd = some Moment.now()
|
test.timeEnd = some Moment.now()
|
||||||
error "Test failed to complete", name = config.name, duration = test.duration
|
error "Test failed to complete", name = test.config.name, duration = test.duration
|
||||||
test.output = CommandExResponse.failure(e)
|
test.output = CommandExResponse.failure(e)
|
||||||
test.status = IntegrationTestStatus.Error
|
test.status = IntegrationTestStatus.Error
|
||||||
test.printResult(printStdOut = manager.debugCodexNodes,
|
|
||||||
printStdErr = manager.debugTestHarness)
|
|
||||||
|
|
||||||
if config.startHardhat and not hardhat.isNil:
|
await test.teardown(hardhat)
|
||||||
try:
|
|
||||||
trace "Stopping hardhat", name = config.name
|
|
||||||
await hardhat.process.stop()
|
|
||||||
except CatchableError as e:
|
|
||||||
warn "Failed to stop hardhat node, continuing",
|
|
||||||
error = e.msg, test = test.config.name
|
|
||||||
|
|
||||||
if manager.debugHardhat:
|
except CancelledError:
|
||||||
test.printOutputMarker(MarkerPosition.Start, "Hardhat stdout")
|
discard # start is asyncSpawned, do not propagate
|
||||||
for line in hardhat.output:
|
|
||||||
echo line
|
|
||||||
test.printOutputMarker(MarkerPosition.Finish, "Hardhat stdout")
|
|
||||||
|
|
||||||
manager.hardhats.keepItIf( it != hardhat )
|
|
||||||
|
|
||||||
test.timeEnd = some Moment.now()
|
test.timeEnd = some Moment.now()
|
||||||
info "Test completed", name = config.name, duration = test.duration
|
if test.status == IntegrationTestStatus.Ok:
|
||||||
test.printResult(printStdOut = manager.debugCodexNodes,
|
info "Test completed", name = test.config.name, duration = test.duration
|
||||||
printStdErr = manager.debugTestHarness)
|
|
||||||
|
|
||||||
proc continuallyShowUpdates(manager: TestManager) {.async: (raises: []).} =
|
proc continuallyShowUpdates(manager: TestManager) {.async: (raises: []).} =
|
||||||
try:
|
try:
|
||||||
@ -436,15 +444,47 @@ proc continuallyShowUpdates(manager: TestManager) {.async: (raises: []).} =
|
|||||||
&"Integration tests status after {manager.duration}"
|
&"Integration tests status after {manager.duration}"
|
||||||
|
|
||||||
for test in manager.tests:
|
for test in manager.tests:
|
||||||
test.printResult()
|
test.printResult(false, false)
|
||||||
|
|
||||||
|
if manager.tests.len > 0:
|
||||||
|
echo ""
|
||||||
|
|
||||||
await sleepAsync(sleepDuration)
|
await sleepAsync(sleepDuration)
|
||||||
|
|
||||||
except CancelledError as e:
|
except CancelledError as e:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
proc untilTimeout(fut: Future[void], timeout: Duration): Future[bool] {.async: (raises: [CancelledError]).} =
|
||||||
|
# workaround for withTimeout, which did not work correctly
|
||||||
|
try:
|
||||||
|
let timer = sleepAsync(timeout)
|
||||||
|
return (await race(fut, timer)) == fut
|
||||||
|
except ValueError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc run(test: IntegrationTest) {.async: (raises: []).} =
|
||||||
|
try:
|
||||||
|
let futStart = test.start()
|
||||||
|
let completedBeforeTimeout = await futStart.untilTimeout(test.manager.testTimeout)
|
||||||
|
if not completedBeforeTimeout:
|
||||||
|
test.timeEnd = some Moment.now()
|
||||||
|
error "Test timed out", name = test.config.name, duration = test.duration
|
||||||
|
let e = newException(AsyncProcessTimeoutError,
|
||||||
|
"Test did not complete before elapsed timeout")
|
||||||
|
test.output = CommandExResponse.failure(e)
|
||||||
|
test.status = IntegrationTestStatus.Timeout
|
||||||
|
|
||||||
|
if not futStart.finished:
|
||||||
|
await futStart.cancelAndWait()
|
||||||
|
|
||||||
|
test.printResult()
|
||||||
|
|
||||||
|
|
||||||
|
except CancelledError:
|
||||||
|
discard # do not propagate due to asyncSpawn
|
||||||
|
|
||||||
proc runTests(manager: TestManager) {.async: (raises: [CancelledError]).} =
|
proc runTests(manager: TestManager) {.async: (raises: [CancelledError]).} =
|
||||||
var testFutures: seq[Future[void].Raising([CancelledError])]
|
var testFutures: seq[Future[void]]
|
||||||
|
|
||||||
manager.timeStart = some Moment.now()
|
manager.timeStart = some Moment.now()
|
||||||
|
|
||||||
@ -452,7 +492,17 @@ proc runTests(manager: TestManager) {.async: (raises: [CancelledError]).} =
|
|||||||
"\n[Integration Test Manager] Starting parallel integration tests"
|
"\n[Integration Test Manager] Starting parallel integration tests"
|
||||||
|
|
||||||
for config in manager.configs:
|
for config in manager.configs:
|
||||||
testFutures.add manager.runTest(config)
|
|
||||||
|
var test = IntegrationTest(
|
||||||
|
manager: manager,
|
||||||
|
config: config,
|
||||||
|
testId: $ uint16.example
|
||||||
|
)
|
||||||
|
manager.tests.add test
|
||||||
|
|
||||||
|
let futRun = test.run()
|
||||||
|
testFutures.add futRun
|
||||||
|
asyncSpawn futRun
|
||||||
|
|
||||||
await allFutures testFutures
|
await allFutures testFutures
|
||||||
|
|
||||||
@ -476,10 +526,22 @@ proc withBorder(
|
|||||||
proc printResult(manager: TestManager) {.raises: [TestManagerError].}=
|
proc printResult(manager: TestManager) {.raises: [TestManagerError].}=
|
||||||
var successes = 0
|
var successes = 0
|
||||||
var totalDurationSerial: Duration
|
var totalDurationSerial: Duration
|
||||||
|
let showSummary = manager.debugCodexNodes or manager.debugHardhat or manager.debugTestHarness
|
||||||
|
|
||||||
|
if showSummary:
|
||||||
|
echo ""
|
||||||
|
echoStyled styleBright, styleUnderscore, bgWhite, fgBlack,
|
||||||
|
&"INTEGRATION TESTS RESULT"
|
||||||
|
|
||||||
for test in manager.tests:
|
for test in manager.tests:
|
||||||
totalDurationSerial += test.duration
|
totalDurationSerial += test.duration
|
||||||
if test.status == IntegrationTestStatus.Ok:
|
if test.status == IntegrationTestStatus.Ok:
|
||||||
inc successes
|
inc successes
|
||||||
|
# because debug output can really make things hard to read, show a nice
|
||||||
|
# summary of test results
|
||||||
|
if showSummary:
|
||||||
|
test.printResult(false, false)
|
||||||
|
|
||||||
# estimated time saved as serial execution with a single hardhat instance
|
# estimated time saved as serial execution with a single hardhat instance
|
||||||
# incurs less overhead
|
# incurs less overhead
|
||||||
let relativeTimeSaved = ((totalDurationSerial - manager.duration).nanos * 100) div
|
let relativeTimeSaved = ((totalDurationSerial - manager.duration).nanos * 100) div
|
||||||
@ -489,6 +551,7 @@ proc printResult(manager: TestManager) {.raises: [TestManagerError].}=
|
|||||||
else:
|
else:
|
||||||
fgGreen
|
fgGreen
|
||||||
|
|
||||||
|
|
||||||
echo "\n▢=====================================================================▢"
|
echo "\n▢=====================================================================▢"
|
||||||
echoStyled "| ", styleBright, styleUnderscore, "INTEGRATION TEST SUMMARY", resetStyle, "".withBorder(Align.Right, 43, {Border.Right})
|
echoStyled "| ", styleBright, styleUnderscore, "INTEGRATION TEST SUMMARY", resetStyle, "".withBorder(Align.Right, 43, {Border.Right})
|
||||||
echo "".withBorder()
|
echo "".withBorder()
|
||||||
@ -498,9 +561,12 @@ proc printResult(manager: TestManager) {.raises: [TestManagerError].}=
|
|||||||
echo "▢=====================================================================▢"
|
echo "▢=====================================================================▢"
|
||||||
|
|
||||||
proc start*(manager: TestManager) {.async: (raises: [CancelledError, TestManagerError]).} =
|
proc start*(manager: TestManager) {.async: (raises: [CancelledError, TestManagerError]).} =
|
||||||
manager.futContinuousUpdates = manager.continuallyShowUpdates()
|
let futContinuousUpdates = manager.continuallyShowUpdates()
|
||||||
asyncSpawn manager.futContinuousUpdates
|
asyncSpawn futContinuousUpdates
|
||||||
|
|
||||||
await manager.runTests()
|
await manager.runTests()
|
||||||
|
await futContinuousUpdates.cancelAndWait()
|
||||||
|
|
||||||
manager.printResult()
|
manager.printResult()
|
||||||
|
|
||||||
proc stop*(manager: TestManager) {.async: (raises: [CancelledError]).} =
|
proc stop*(manager: TestManager) {.async: (raises: [CancelledError]).} =
|
||||||
@ -513,5 +579,3 @@ proc stop*(manager: TestManager) {.async: (raises: [CancelledError]).} =
|
|||||||
await hardhat.process.stop()
|
await hardhat.process.stop()
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
trace "failed to stop hardhat node", error = e.msg
|
trace "failed to stop hardhat node", error = e.msg
|
||||||
|
|
||||||
await manager.futContinuousUpdates.cancelAndWait()
|
|
Loading…
x
Reference in New Issue
Block a user