Something went wrong. Try again.
Nix-based distributed command runner now.dev.br
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765#! /usr/bin/env nix#! nix shell . --command now{ runner, ... }:let inputs = import ./.tack; pkgs = import inputs.nixpkgs { }; now = import ./. { inherit pkgs; };
mkOptionDocumentation = level: module: { name, value }: let inherit (pkgs.lib) generators; inherit (pkgs.lib.strings) replicate; in '' ${replicate level "#"} ${module}.${name}
${value.description or ""}
${if value ? type then "_Type:_ `${value.type.name}`" else ""}
${ if value ? default then '' _Default:_
```nix ${generators.toPretty { multiline = true; allowPrettyValues = true; } value.default} ``` '' else "" } '';
mkModuleDocumentation = level: { name, value }: let inherit (builtins) concatStringsSep; inherit (pkgs.lib) attrsToList; inherit (pkgs.lib.strings) replicate; in '' ${replicate level "#"} ${name}
${value.meta.description or ""}
Available options:
${concatStringsSep "\n\n" ( map (mkOptionDocumentation (level + 1) name) ( builtins.filter ({ value, ... }: value ? description) (attrsToList value.options) ) )} '';
mkDocumentation = level: modules: let inherit (builtins) concatStringsSep; inherit (pkgs.lib) attrsToList; in concatStringsSep "\n\n" ( map (mkModuleDocumentation level) ( builtins.filter ({ value, ... }: value.meta.generateDocs or true) (attrsToList modules) ) );in{ jobs = {
# ============================================================ # Docs # ============================================================
serve-docs = { name = "Serve docs"; steps = [ { path = [ now pkgs.watchexec pkgs.zensical ]; run = '' trap 'kill 0' EXIT INT TERM watchexec -w now.nix -w now/modules -r now run generate-nix-docs --logging step & watchexec -w now.nix -w src -r now run generate-cli-docs --logging step & watchexec -w now.nix -w .tack/pins.lock.json -r now run generate-now-services-docs --logging step & zensical serve -f docs/zensical.toml ''; } ]; };
publish-docs = { name = "Build and publish docs"; needs = [ "generate-nix-docs" "generate-cli-docs" "generate-now-services-docs" ]; sandbox.enable = true; steps = [ { sandbox.writableDirectory = true; path = [ pkgs.zensical ]; run = "zensical build -f docs/zensical.toml"; } { sandbox = { useHome = [ ".ssh" ]; networkAccess = true; }; env.DOCS_HOST = runner.secret "DOCS_HOST"; path = [ pkgs.openssh pkgs.rsync ]; run = '' rsync --delete-after -acP docs/site/ $DOCS_HOST:''${DOCS_DIRECTORY:-www} ''; } ]; };
generate-nix-docs = { name = "Generate Nix docs"; sandbox.enable = true; steps = let adios = (import inputs.adios) // { evalId = ""; evalModule = _: { }; envType = let inherit (adios) types; in types.union [ types.string (types.struct "nowSecret" { }) (types.struct "nowDownload" { }) ]; }; in [ (runner.steps.upload { name = "docs-workflow"; deriv = pkgs.writeText "docs-workflow" ( mkDocumentation 2 { workflow = import ./now/modules/workflow.nix adios; } ); }) (runner.steps.upload { name = "docs-job"; deriv = pkgs.writeText "docs-job" ( mkDocumentation 2 { job = import ./now/modules/job.nix adios; } ); }) (runner.steps.upload { name = "docs-step"; deriv = pkgs.writeText "docs-step" ( mkDocumentation 2 { step = import ./now/modules/step.nix adios; } ); }) (runner.steps.upload { name = "docs-sandbox"; deriv = pkgs.writeText "docs-sandbox" ( mkDocumentation 2 { sandbox = import ./now/modules/sandbox.nix adios; } ); }) { sandbox.writableDirectory = true; env = { DOCS_WORKFLOW = runner.download "docs-workflow"; DOCS_JOB = runner.download "docs-job"; DOCS_STEP = runner.download "docs-step"; DOCS_SANDBOX = runner.download "docs-sandbox"; OUT = "docs/docs/options.md"; }; run = '' set -euo pipefail
cat > $OUT << EOF --- icon: lucide/square-menu ---
# Options reference
!!! note
This documentation is auto-generated from the workflow definitions.
$(cat $DOCS_WORKFLOW)
$(cat $DOCS_JOB)
$(cat $DOCS_STEP)
$(cat $DOCS_SANDBOX) EOF
echo "Updated Nix docs." ''; } ]; };
generate-cli-docs = { name = "Generate CLI docs"; sandbox.enable = true; steps = [ (runner.steps.upload { name = "docs-cli"; deriv = pkgs.runCommand "now-cli" { nativeBuildInputs = [ now pkgs.to-html ]; } '' mkdir $out to-html --no-prompt "now help" > $out/index.html to-html --no-prompt "now help init" > $out/init.html to-html --no-prompt "now help list" > $out/list.html to-html --no-prompt "now help eval" > $out/eval.html to-html --no-prompt "now help run" > $out/run.html ''; }) { sandbox.writableDirectory = true; env = { DOCS_CLI = runner.download "docs-cli"; OUT = "docs/docs/cli.md"; }; run = '' set -euo pipefail
cat > $OUT << EOF --- icon: lucide/terminal ---
# CLI reference
!!! note
This documentation is auto-generated from the command line.
## now
$(cat $DOCS_CLI/index.html)
## now init
$(cat $DOCS_CLI/init.html)
## now list
$(cat $DOCS_CLI/list.html)
## now eval
$(cat $DOCS_CLI/eval.html)
## now run
$(cat $DOCS_CLI/run.html) EOF
echo "Updated CLI docs." ''; } ]; };
generate-now-services-docs = { name = "Generate now-services docs"; sandbox.enable = true; steps = [ (runner.steps.upload { name = "docs-now-services"; deriv = let adios = import inputs.adios; services = adios.lib.importModules { directory = "${inputs.now-services}/services"; }; in pkgs.writeText "docs-now-services" (mkDocumentation 3 services); }) { sandbox.writableDirectory = true; env = { DOCS_NOW_SERVICES = runner.download "docs-now-services"; OUT = "docs/docs/services.md"; }; run = '' set -euo pipefail
cat > $OUT << EOF --- icon: lucide/circle-pile ---
# now-services
[\`now-services\`](https://codeberg.org/now-runner/now-services) is a pluggable collection of steps that let you run development services with \`now\`.
## Installation
=== "tack"
\`\`\`bash tack add now-services git+https://codeberg.org/now-runner/now-services.git --fetch \`\`\`
\`\`\`nix title="now.nix" let inputs = import ./.tack; services = import inputs.now-services; in { jobs.my_job.steps = [ (services.postgresql { }) ]; } \`\`\`
=== "npins"
\`\`\`bash npins add git https://codeberg.org/now-runner/now-services.git \`\`\`
\`\`\`nix title="now.nix" let sources = import ./npins; services = import sources.now-services; in { jobs.my_job.steps = [ (services.postgresql { }) ]; } \`\`\`
=== "Nix flake"
\`\`\`nix title="flake.nix" { inputs = { # ... now-services.url = "git+https://codeberg.org/now-runner/now-services.git"; };
outputs = { now-services, ... }@inputs: { now.jobs.my_job.steps = [ (now-services.postgresql { }) ]; }; } \`\`\`
## Available services
!!! note
This documentation is auto-generated from the service definitions.
$(cat $DOCS_NOW_SERVICES) EOF
echo "Updated now-services docs." ''; } ]; };
# ============================================================ # Tests # ============================================================
test = { name = "Finalize tests"; needs = [ "test-abort" "test-checkout" "test-conditionals" "test-cycle" "test-dotenv" "test-dry-run" "test-env" "test-error" "test-fail-fast" "test-fhs" "test-flake" "test-gcroot-dir" "test-glob" "test-graceful-termination" "test-init" "test-invalid-env" "test-invalid-output-var" "test-job-timeout" "test-jobs" "test-list" "test-local-only" "test-matrix" "test-missing-download" "test-missing-secret" "test-missing-var" "test-nix-config" "test-nixpkgs" "test-no-match" "test-orchestrate-services" "test-overlay" "test-path-lockdown" "test-sandbox" "test-script" "test-secret-reuse" "test-shell-args" "test-skip" "test-tempdir" "test-upload" "test-var-script" "test-vars" "test-workflow-timeout" ]; steps = [ { shell = pkgs.nushell; run = '' 'Good to go! ^u^' | ansi gradient -F rainbow ''; } ]; };
test-abort = { name = "Test --abort"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --abort --workflow .now/tests/abort.nix --all-jobs | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
for i in 0..5 { if not ($output | str contains $"=> ($i)") { print $"(ansi red_bold)ERROR:(ansi reset) Expected '=> ($i)' in output" exit 1 } }
if ($output | str contains "late ran") { print $"(ansi red_bold)ERROR:(ansi reset) 'late' job should have been aborted" exit 1 }
if not ($output | str contains "=== note: teardown still runs on error ===") { print $"(ansi red_bold)ERROR:(ansi reset) Expected teardown note in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-checkout = { name = "Test local checkout"; steps = [ { path = [ pkgs.git now ]; shell = pkgs.nushell; run = '' let tempdir = (mktemp -p ($env.TMPDIR? | default "/tmp") -d) git init -b main $tempdir 'Foo contents' | save ($tempdir | path join foo) 'Bar contents' | save ($tempdir | path join bar) 'bar' | save ($tempdir | path join .gitignore)
now run --cwdir $tempdir --all-jobs --workflow .now/tests/checkout.nix rm -rf $tempdir if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-conditionals = { name = "Test conditionals"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --all-jobs --workflow .now/tests/conditionals.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-cycle = { name = "Test cycle"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --abort --workflow .now/tests/cycle.nix --all-jobs | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "Cycle detected") { print $"(ansi red_bold)ERROR:(ansi reset) Expected a 'Cycle detected' error" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-dotenv = { name = "Test --env-file"; steps = [ { path = [ pkgs.git now ]; shell = pkgs.nushell; run = '' let tmp = mktemp r#'MY_VAR="This is a variable" MY_SECRET='This is a secret' '# | save --force $tmp
now run --env-file $tmp --workflow .now/tests/env.nix rm $tmp if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-dry-run = { name = "Test --dry-run"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' # Error job should pass. now run --dry-run --workflow .now/tests/error.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
# Malformed job (eg. cyclic graph) should fail. let result = (now run --dry-run --workflow .now/tests/cycle.nix --all-jobs | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "Cycle detected") { print $"(ansi red_bold)ERROR:(ansi reset) Expected a 'Cycle detected' error" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-env = { name = "Test environment"; steps = [ { env = { MY_VAR = "This is a variable"; MY_SECRET = "This is a secret"; }; path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/env.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-error = { name = "Test error exit status"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' # Ensure the test evaluates just fine now eval --workflow .now/tests/error.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed to evaluate" exit 1 }
let result = (now run --workflow .now/tests/error.nix --all-jobs | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "=== note: teardown still runs on error ===") { print $"(ansi red_bold)ERROR:(ansi reset) Expected teardown note in output" exit 1 }
if ($output | str contains "This shouldn't be printed at all!") { print $"(ansi red_bold)ERROR:(ansi reset) Steps after a failed step should not run" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-fail-fast = { name = "Test strategy.failFast"; steps = [ { name = "failFast = true"; path = [ now ]; shell = pkgs.nushell; run = '' if BUILDERS in $env { let result = (now run --workflow .now/tests/fail-fast/true.nix --builders $env.BUILDERS | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "Doing local work") { print $"(ansi red_bold)ERROR:(ansi reset) Expected local run in output" exit 1 } if not ($output | str contains "Doing remote work") { print $"(ansi red_bold)ERROR:(ansi reset) Expected remote run in output" exit 1 } if ($output | str contains "Done with remote work") { print $"(ansi red_bold)ERROR:(ansi reset) Remote run should've been aborted" exit 1 }
if not ($output | str contains "Local teardown") { print $"(ansi red_bold)ERROR:(ansi reset) Expected local teardown in output" exit 1 } if not ($output | str contains "Remote teardown") { print $"(ansi red_bold)ERROR:(ansi reset) Expected remote teardown in output" exit 1 }
if not ($output | str contains "Job 'job-remote' was aborted") { print $"(ansi red_bold)ERROR:(ansi reset) Expected abort message in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" } else { print $"(ansi yellow_bold)WARNING:(ansi reset) BUILDERS is unset; skipping" print "" print $"(ansi d)=== hint: to run this, pass an envvar like:(ansi reset)" print $"(ansi d)=== BUILDERS='ssh://localhost - - 1 1 now now -'(ansi reset)" } ''; } { name = "failFast = false"; path = [ now ]; shell = pkgs.nushell; run = '' if BUILDERS in $env { let result = (now run --workflow .now/tests/fail-fast/false.nix --builders $env.BUILDERS | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "Doing local work") { print $"(ansi red_bold)ERROR:(ansi reset) Expected local run in output" exit 1 } if not ($output | str contains "Doing remote work") { print $"(ansi red_bold)ERROR:(ansi reset) Expected remote run in output" exit 1 } if not ($output | str contains "Done with remote work") { print $"(ansi red_bold)ERROR:(ansi reset) Remote run shouldn't have been aborted" exit 1 }
if not ($output | str contains "Local teardown") { print $"(ansi red_bold)ERROR:(ansi reset) Expected local teardown in output" exit 1 } if not ($output | str contains "Remote teardown") { print $"(ansi red_bold)ERROR:(ansi reset) Expected remote teardown in output" exit 1 }
if ($output | str contains "Job 'job-remote' was aborted") { print $"(ansi red_bold)ERROR:(ansi reset) Unexpected abort message in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" } else { print $"(ansi yellow_bold)WARNING:(ansi reset) BUILDERS is unset; skipping" print "" print $"(ansi d)=== hint: to run this, pass an envvar like:(ansi reset)" print $"(ansi d)=== BUILDERS='ssh://localhost - - 1 1 now now -'(ansi reset)" } ''; } ]; };
test-fhs = { name = "Test FHS environment"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/fhs.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-flake = { name = "Test flake"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --flake .now/tests flake if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
now run --flake .now/tests#other-entrypoint example if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-gcroot-dir = { name = "Test --gcroot-dir"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let gcrootdir = (mktemp -p ($env.TMPDIR? | default "/tmp") -d) now run --gcroot-dir $gcrootdir -w .now/tests/gcroot-dir.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" rm -rf $gcrootdir exit 1 }
let files = (ls -a $gcrootdir) print $files rm -rf $gcrootdir
if ($files | where name =~ '-example-build\.drv$' | is-empty) { print $"(ansi red_bold)ERROR:(ansi reset) Missing build in gcroot directory" exit 1 }
if ($files | where name =~ '-example-upload\.drv$' | is-empty) { print $"(ansi red_bold)ERROR:(ansi reset) Missing upload in gcroot directory" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-glob = { name = "Test job globbing"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' for pattern in ["a/1*" "b/**/*" "c/f?o"] { now run $pattern --cores 1 --workflow .now/tests/glob.nix if $env.LAST_EXIT_CODE != 0 { print $"ERROR: now run failed for pattern '($pattern)'" exit 1 } }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-graceful-termination = { name = "Test graceful termination"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let dir = (mktemp --directory) let log = ($dir | path join "output.log")
let driver = r#' now run --workflow .now/tests/graceful-termination.nix > "$1" 2>&1 & pid=$!
# Wait for the step to be running tries=0 until grep -q "step: started" "$1" 2>/dev/null; do tries=$((tries + 1)) if [ "$tries" -gt 1800 ]; then kill -KILL "$pid" 2>/dev/null exit 124 fi sleep 0.1 done
kill -INT "$pid"
# Watchdog: fail instead of freezing if now hangs after SIGINT ( sleep 10; kill -KILL "$pid" 2>/dev/null ) >/dev/null 2>&1 & watchdog=$! wait "$pid" status=$? kill "$watchdog" 2>/dev/null exit "$status" '#
let result = (^sh -c $driver sh $log | complete) let output = (open $log --raw) rm -rf $dir print $output
if $result.exit_code == 124 { print $"(ansi red_bold)ERROR:(ansi reset) Step never started" exit 1 }
if $result.exit_code == 137 { print $"(ansi red_bold)ERROR:(ansi reset) now did not exit after SIGINT (killed by watchdog)" exit 1 }
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Aborted workflow shouldn't have succeeded!" exit 1 }
if ($output | str contains "Done.") { print $"(ansi red_bold)ERROR:(ansi reset) Aborted workflow reported 'Done.'" exit 1 }
if ($output | str contains "teardown: marker=missing") { print $"(ansi red_bold)ERROR:(ansi reset) Step never ran its SIGTERM handler" exit 1 }
if ($output | str contains "teardown: marker=cleaning") { print $"(ansi red_bold)ERROR:(ansi reset) Teardown started while the step was still cleaning up" exit 1 }
if not ($output | str contains "teardown: marker=finished") { print $"(ansi red_bold)ERROR:(ansi reset) Teardown did not run after abort" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-init = { name = "Test `now init`"; checkout = "none"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now init let result = (./now.nix run | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
if not ($output | str contains "Hello from now!") { print $"(ansi red_bold)ERROR:(ansi reset) Missing default message in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-invalid-env = { name = "Test POSIX env validation"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now eval -w .now/tests/invalid-env.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded" exit 1 }
if not ($output | str contains "environment variable '' does not have a valid POSIX variable name") { print $"(ansi red_bold)ERROR:(ansi reset) Missing error in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-invalid-output-var = { name = "Test POSIX step.outputVar validation"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now eval -w .now/tests/invalid-output-var.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded" exit 1 }
if not ($output | str contains "output environment variable '' is not a valid POSIX variable name") { print $"(ansi red_bold)ERROR:(ansi reset) Missing error in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-job-timeout = { name = "Test job timeout"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --workflow .now/tests/job-timeout.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "Done!") { print $"(ansi red_bold)ERROR:(ansi reset) Expected the first step to complete before the timeout" exit 1 }
if not ($output | str contains "=== note: teardown still runs on timeout ===") { print $"(ansi red_bold)ERROR:(ansi reset) Expected teardown note in output" exit 1 }
if not ($output | str contains "timed out after 3s") { print $"(ansi red_bold)ERROR:(ansi reset) Expected a timeout error" exit 1 }
if ($output | str contains "This shouldn't be printed at all!") { print $"(ansi red_bold)ERROR:(ansi reset) The step that timed out should not have completed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-jobs = { name = "Test job dependencies"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run b x --workflow .now/tests/jobs.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-list = { name = "Test `now list`"; steps = [ { path = [ now pkgs.graphviz ]; shell = pkgs.nushell; run = '' let job_list = now list --workflow .now/tests/jobs.nix | split words if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) `now list` failed" exit 1 }
if $job_list != [a b c x y] { print $"(ansi red_bold)ERROR:(ansi reset) Invalid job list" exit 1 }
now list --tree --workflow .now/tests/jobs.nix | dot -Tplain-ext if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) `now list --tree` failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-local-only = { name = "Test --local-only"; steps = [ { path = [ now ]; shell = pkgs.nushell; env.NIX_CONFIG = '' builders = ssh://localhost - - 1 1 now now - ''; run = '' let result = (now run --local-only --workflow .now/tests/local-only.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "No runners match for job 'remote-job'") { print $"(ansi red_bold)ERROR:(ansi reset) Error didn't match" exit 1 }
if ($output | str contains "This shouldn't run") { print $"(ansi red_bold)ERROR:(ansi reset) Remote step shouldn't run" exit 1 }
if ($output | str contains "Teardown shouldn't run") { print $"(ansi red_bold)ERROR:(ansi reset) Remote teardown shouldn't run" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-matrix = { name = "Test run matrix"; steps = [ { path = [ now ]; shell = pkgs.nushell; env.VAR_TO_PASS_TO_RUNNERS = "forty-two"; run = '' if BUILDERS in $env { now run --all-jobs --builders $env.BUILDERS --workflow .now/tests/matrix.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)"
} else { print $"(ansi yellow_bold)WARNING:(ansi reset) BUILDERS is unset; skipping" print "" print $"(ansi d)=== hint: to run this, pass an envvar like:(ansi reset)" print $"(ansi d)=== BUILDERS='ssh://localhost - - 1 1 now now -'(ansi reset)" } ''; } ]; };
test-missing-download = { name = "Test missing `runner.download`"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --workflow .now/tests/missing-download.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "No upload named 'NO_DOWNLOAD'") { print $"(ansi red_bold)ERROR:(ansi reset) Expected missing download error in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-missing-secret = { name = "Test missing `runner.secret`"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --workflow .now/tests/missing-secret.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "Secret environment variable 'NO_SECRET' is unset") { print $"(ansi red_bold)ERROR:(ansi reset) Expected missing secret error in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-missing-var = { name = "Test missing `runner.var`"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --workflow .now/tests/missing-var.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "Required environment variable 'NO_VAR' is unset") { print $"(ansi red_bold)ERROR:(ansi reset) Expected missing var error in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-nix-config = { name = "Test special steps' nixConfig"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/nix-config.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-nixpkgs = { name = "Test nixpkgs"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/nixpkgs.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-no-match = { name = "Test no matching job"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --workflow .now/tests/jobs.nix inexistent | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "No jobs matched 'inexistent'") { print $"(ansi red_bold)ERROR:(ansi reset) Expected match error in output" exit 1 }
if not ($output | str contains "Available options: a, b, c, x, y") { print $"(ansi red_bold)ERROR:(ansi reset) Expected suggestions in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-orchestrate-services = { name = "Test `now-services.orchestrate`"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let dir = (mktemp --directory) let log = ($dir | path join "output.log")
let driver = r#' cd .now/tests/orchestrate-services now run > "$1" 2>&1 & pid=$!
# Wait for the step to be running tries=0 until grep -q "Started main()" "$1" 2>/dev/null; do tries=$((tries + 1)) if [ "$tries" -gt 1800 ]; then kill -KILL "$pid" 2>/dev/null exit 124 fi sleep 0.1 done
kill -INT "$pid"
# Watchdog: fail instead of freezing if now hangs after SIGINT ( sleep 10; kill -KILL "$pid" 2>/dev/null ) >/dev/null 2>&1 & watchdog=$! wait "$pid" status=$? kill "$watchdog" 2>/dev/null exit "$status" '#
let result = (^sh -c $driver sh $log | complete) let output = (open $log --raw) rm -rf $dir print $output
if $result.exit_code == 124 { print $"(ansi red_bold)ERROR:(ansi reset) Step never started" exit 1 }
if $result.exit_code == 137 { print $"(ansi red_bold)ERROR:(ansi reset) now did not exit after SIGINT (killed by watchdog)" exit 1 }
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Aborted workflow shouldn't have succeeded!" exit 1 }
if ($output | str contains "Finished main()") { print $"(ansi red_bold)ERROR:(ansi reset) Aborted workflow reported 'Finished main()'" exit 1 }
if not ($output | str contains "Caught signal: SIGTERM") { print $"(ansi red_bold)ERROR:(ansi reset) Step never ran its SIGTERM handler" exit 1 }
if not ($output | str contains "SystemExit: 42") { print $"(ansi red_bold)ERROR:(ansi reset) Step didn't exit with custom status code" exit 1 }
if not ($output | str contains "Runner aborted") { print $"(ansi red_bold)ERROR:(ansi reset) Runner didn't abort" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-overlay = { name = "Test overlay"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/overlay.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-path-lockdown = { name = "Test path lockdown"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/path-lockdown.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-sandbox = { name = "Test sandboxing"; steps = [ { path = [ pkgs.git now ]; shell = pkgs.nushell; run = '' let cwdir = (mktemp -p ($env.TMPDIR? | default "/tmp") -d) git init -b main $cwdir mkdir ($cwdir | path join bar) 'Foo contents' | save ($cwdir | path join foo) 'Baz contents' | save ($cwdir | path join bar/baz)
now run --cwdir $cwdir --all-jobs --workflow .now/tests/sandbox.nix rm -rf $cwdir if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-script = { name = "Test script import"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/script.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-secret-reuse = { name = "Test invalid secret reuse"; steps = [ { path = [ now ]; shell = pkgs.nushell; env.SHARED_VAR = "supersecretpassword"; run = '' let result = (now eval -w .now/tests/secret-reuse.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded" exit 1 }
if not ($output | str contains "secret 'SHARED_VAR' cannot also be used as a regular variable") { print $"(ansi red_bold)ERROR:(ansi reset) Missing error in output" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-shell-args = { name = "Test step.shellArgs"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --workflow .now/tests/shell-args.nix shellArgs | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test should have succeeded!" exit 1 }
if not ($output | str contains "* * *") { print $"(ansi red_bold)ERROR:(ansi reset) Expected non-globbed string" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-skip = { name = "Test skip non-runnable jobs"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --builders "" --skip --all-jobs --workflow .now/tests/skip.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-tempdir = { name = "Test runner.steps.tempdir"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/tempdir.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-upload = { name = "Test uploads"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/upload.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-var-script = { name = "Test runner.var disallowed in scripts"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' let result = (now run --workflow .now/tests/var-script.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "cannot be used directly in") { print $"(ansi red_bold)ERROR:(ansi reset) Expected a 'cannot be used directly in' error" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-vars = { name = "Test envvars"; steps = [ { env = { TEST_FIRST_VAR = "first var"; TEST_FIRST_SECRET = "first secret"; TEST_SECOND_VAR = "second var"; TEST_SECOND_SECRET = "second secret"; }; path = [ now ]; shell = pkgs.nushell; run = '' now run --workflow .now/tests/vars.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; };
test-workflow-timeout = { name = "Test workflow timeout"; steps = [ { path = [ now ]; shell = pkgs.nushell; run = '' # Ensure workflow is cached now run --workflow .now/tests/workflow-timeout.nix if $env.LAST_EXIT_CODE != 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test failed" exit 1 }
let result = (now run --timeout 3s --workflow .now/tests/workflow-timeout.nix | complete) let output = $result.stdout + $result.stderr print $output
if $result.exit_code == 0 { print $"(ansi red_bold)ERROR:(ansi reset) Test shouldn't have succeeded!" exit 1 }
if not ($output | str contains "Done!") { print $"(ansi red_bold)ERROR:(ansi reset) Expected the first step to complete before the timeout" exit 1 }
if not ($output | str contains "=== note: teardown still runs on timeout ===") { print $"(ansi red_bold)ERROR:(ansi reset) Expected teardown note in output" exit 1 }
if not ($output | str contains "timed out after 3s") { print $"(ansi red_bold)ERROR:(ansi reset) Expected a timeout error" exit 1 }
if ($output | str contains "This shouldn't be printed at all!") { print $"(ansi red_bold)ERROR:(ansi reset) The step that timed out should not have completed" exit 1 }
print $"(ansi green)Test passed.(ansi reset)" ''; } ]; }; };}