如何在Nushell测试脚本中将闭包的stdout和stderr重定向至变量?
Nushell测试脚本:将闭包的stdout/stderr捕获到变量替代临时文件
问题描述
我正在为一款CLI编写Nushell测试脚本,核心需求是仅在测试函数执行失败时,打印其stdout、stderr以及Nushell的错误信息。目前的实现是将输出写入临时文件后再读取,效率较低,希望能直接将闭包的stdout和stderr重定向到变量,但尝试了多种方法均未成功。
当前实现
def run_tests [functions, passed, total] -> int { mut passed = $passed for pair in ($functions | enumerate) { print $"test ($pair.index): running" --no-newline try { do $pair.item.func out+err> /tmp/test-output # FIXME: 临时文件方案效率低 print $"\rtest ($pair.index): success" $passed += 1 } catch { print $"\rtest ($pair.index): failure" print (open /tmp/test-output) print $in } } return $passed }
注:
$functions是包含func列的表格,func列存储了带有断言、可能执行失败的Nushell命令及外部命令的闭包。
已尝试的方法
let result = do $pair.item.func:直接打印闭包输出,不符合“仅失败时打印”的需求complete:仅支持外部命令,对Nushell闭包无效do $pair.item.func out+err> $result:语法不支持向变量重定向,仅能写入文件
测试用例示例
let functions = [ [name, func]; [start-stop-label, { log-timer start test log-timer stop log-timer get logs assert (log-timer get logs | str contains test) } ], [overwrite-options, { touch ~/log-1.csv log-timer config set --log-file-path log-1.csv --row-formatter v1-0 let config = log-timer config get | from json assert ($config.log_file_path == ('~/log-1.csv' | path expand)) assert ($config.row_formatter == V1_0) log-timer config set --row-formatter v2-0 let config = log-timer config get | from json assert ($config.log_file_path == ('~/log-1.csv' | path expand)) assert ($config.row_formatter == V2_0) touch ~/log-2.csv log-timer config set --log-file-path ~/log-2.csv let config = log-timer config get | from json assert ($config.log_file_path == ('~/log-2.csv' | path expand)) assert ($config.row_formatter == V2_0) },], [set-default, { log-timer config set-default --confirm let current = log-timer config get | from json let default = log-timer config get-default | from json assert ($current == $default) }], ]
期望输出(start-stop-label测试失败时)
test 1: failure started activity 'test' stopped activity 'test' Error: × Assertion failed. ╭─[entry #4:1:8] 1 │ assert (log-timer get logs | str contains test) · ──┬── · ╰── It is not true. ╰──── test 2: success test 3: running
解决方案
方案1:单次执行捕获输出与错误
利用Nushell的错误捕获机制,在捕获错误的同时收集闭包的输出,无需临时文件:
def run_tests [functions, passed, total] -> int { mut passed = $passed for pair in ($functions | enumerate) { print $"test ($pair.index): running" --no-newline let capture_result = ( try { # 执行闭包,若成功则记录状态 do $pair.item.func { status: "success", output: "" } } catch { # 执行闭包并捕获所有输出 let captured_output = (do $pair.item.func out+err> stdout) | str collect { status: "failure", output: $captured_output, error: $in } } ) if $capture_result.status == "success" { print $"\rtest ($pair.index): success" $passed += 1 } else { print $"\rtest ($pair.index): failure" print $capture_result.output print $capture_result.error } } return $passed }
方案2:后台任务捕获输出(无重复执行)
如果测试闭包有副作用,不适合重复执行,可以用后台任务捕获输出并同步获取执行状态:
def run_tests [functions, passed, total] -> int { mut passed = $passed for pair in ($functions | enumerate) { print $"test ($pair.index): running" --no-newline # 启动后台任务执行闭包,捕获所有输出 let job = (do $pair.item.func out+err> stdout) | start wait $job let captured_output = ($job | get stdout) | str collect let exit_code = $job | get exit_code if $exit_code == 0 { print $"\rtest ($pair.index): success" $passed += 1 } else { # 重新捕获Nushell的错误信息(若需要完整错误栈) try { do $pair.item.func } catch { print $"\rtest ($pair.index): failure" print $captured_output print $in } } } return $passed }
注:方案2中若不需要完整的Nushell错误栈,可直接通过
exit_code判断并打印捕获的输出,无需再次执行闭包。
内容的提问来源于stack exchange,提问作者Ask Sødal
相关产品推荐
相关产品推荐

