#!/usr/bin/env nu # Compare curl transfers with native curl and check standard WASI imports. use std/assert def guest [component: path, root: path, args: list, input: string = ''] { $input | ^wasmtime run -S http -S cli-exit-with-code=y --dir $'($root)::/' --env HOME=/ $component ...$args | complete } def compare [name: string, component: path, root: path, url: string, args: list, input: string = '', stderr: bool = false] { let native = ($input | ^curl ...$args $url | complete) let wasi = (guest $component $root ($args | append $url) $input) assert equal $wasi.exit_code $native.exit_code $name assert equal $wasi.stdout $native.stdout $name if $stderr { assert equal $wasi.stderr $native.stderr $name } print $'($name): PASS' } def main [component: path = '/tmp/p1-curl/target/wasm32-wasip2/release/curl.wasm'] { let component = ($component | path expand) mkdir /tmp/p1-curl let root = (^mktemp -d -p /tmp/p1-curl comparison.XXXXXX | str trim) let manifest = ($env.FILE_PWD | path join tests/server/Cargo.toml) let build = (with-env {CARGO_BUILD_JOBS: '2', CARGO_TARGET_DIR: '/tmp/p1-curl/server-target'} { ^cargo build --manifest-path $manifest --locked } | complete) if $build.exit_code != 0 { error make {msg: ($build | to json)} } let readiness = ($root | path join port) let server = job spawn { ^/tmp/p1-curl/server-target/debug/curl-test-server $readiness out> ($root | path join server.out) err> ($root | path join server.err) } try { let deadline = (date now) + 5sec while not ($readiness | path exists) { if (date now) >= $deadline { error make {msg: $'Fixture did not start. Inspect ($root)/server.err'} } } let port = (open --raw $readiness | str trim) run-tests $component $root $port } catch {|failure| job kill $server error make {msg: $failure.msg} } job kill $server print 'Fixture stopped: PASS' } def run-tests [component: path, root: path, port: string] { let base = $'http://127.0.0.1:($port)' let wit = (^wasm-tools component wit $component | complete) assert equal $wit.exit_code 0 let imports = ($wit.stdout | lines | str trim | where {|line| $line | str starts-with 'import '}) assert (not ($imports | is-empty)) assert ($imports | all {|line| $line | str starts-with 'import wasi:'}) print 'Imports: PASS (WASI only)' print ($imports | str join (char newline)) let cases = [ {name: GET, path: /echo, args: [-sS -A Agent]} {name: method, path: /echo, args: [-sS -A Agent -X DELETE]} {name: headers, path: /echo, args: [-sS -A Agent -H 'X-Test: one' -H 'X-Test: two' -H 'Accept: custom']} {name: suppress-header, path: /echo, args: [-sS -A Agent -H 'Accept:']} {name: data, path: /echo, args: [-sS -A Agent -d a=1 --data b=2]} {name: empty-data, path: /echo, args: [-sS -A Agent -d '' -d x]} {name: raw, path: /echo, args: [-sS -A Agent --data-raw '@literal']} {name: mixed-data, path: /echo, args: [-sS -A Agent --data-raw a -d b --data-binary c]} {name: explicit-GET-data, path: /echo, args: [-sS -A Agent -X GET -d text]} {name: JSON, path: /echo, args: [-sS -A Agent --json '{"a":1}']} {name: JSON-headers, path: /echo, args: [-sS -A Agent --json '{}' -H 'Content-Type: custom']} {name: basic, path: /echo, args: [-sS -A Agent -u 'user:pass']} {name: redirect-no-follow, path: /redirect, args: [-sS -A Agent]} {name: redirect, path: /redirect, args: [-sS -A Agent -L]} {name: redirect-POST-GET, path: /303, args: [-sS -A Agent -L -d x]} {name: redirect-preserve-POST, path: /307, args: [-sS -A Agent -L -d x]} {name: redirect-limit, path: /loop, args: [-s -L --max-redirs 1]} {name: fail, path: /404, args: [-sS -f]} {name: fail-body, path: /404, args: [-sS --fail-with-body]} {name: silent-fail, path: /404, args: [-s -f]} {name: fail-headers-size, path: /404, args: [-sS -fi -w '%{size_download}']} {name: write-out, path: /plain, args: [-sS -w '%{http_code}|%{url_effective}|%{content_type}|%{size_download}\n']} {name: head, path: /plain, args: [-sS -I]} {name: include, path: /plain, args: [-sS -i]} {name: ignored-insecure, path: /plain, args: [-sS -k]} {name: timeout, path: /slow, args: [-s -m 0.01]} ] for case in $cases { compare $case.name $component $root $'($base)($case.path)' $case.args '' ($case.name in [fail fail-body silent-fail]) } let text = $"line1\nline2\r\n" $text | save ($root | path join input) for flag in [-d --data-binary --json] { let native = (^curl -sS -A Agent $flag $'@($root)/input' $'($base)/echo' | complete) let wasi = (guest $component $root [-sS -A Agent $flag '@/input' $'($base)/echo']) assert equal $wasi.exit_code $native.exit_code assert equal $wasi.stdout $native.stdout print $'($flag) @file: PASS' compare $'($flag) @-' $component $root $'($base)/echo' [-sS -A Agent $flag '@-'] $text } let native = (^curl -sS -o ($root | path join native.out) $'($base)/plain' | complete) let wasi = (guest $component $root [-sS -o /guest.out $'($base)/plain']) assert equal $wasi.exit_code $native.exit_code assert equal $wasi.stdout $native.stdout assert equal (open --raw ($root | path join guest.out)) (open --raw ($root | path join native.out)) print '-o: PASS' let wasi = (guest $component $root [-sS -O $'($base)/plain']) assert equal $wasi.exit_code 0 assert equal (open --raw ($root | path join plain)) $"hello\n" print '-O: PASS' let native = (^curl --no-such-option | complete) let wasi = (guest $component $root [--no-such-option]) assert equal $wasi.exit_code 2 assert equal $wasi.stderr $native.stderr print 'Unknown option: PASS' let verbose = (guest $component $root [-sS -v -H 'X-Test: visible' $'($base)/plain']) assert equal $verbose.exit_code 0 assert ($verbose.stderr | str contains '> X-Test: visible') assert ($verbose.stderr | str contains '< HTTP/1.1 200 OK') assert (not ($verbose.stderr | str contains 'Authorization')) print 'Verbose request and response headers: PASS' let https = (guest $component $root [-sS -w '%{http_code}' https://example.com]) assert equal $https.exit_code 0 assert ($https.stdout | str contains 'Example Domain') assert ($https.stdout | str ends-with '200') print 'Public HTTPS GET: PASS' print $'Fixture retained: ($root)' }