From f74fb230ff3a8b8f4456790033925710337e246e Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Sat, 11 Jul 2026 06:09:49 -0400 Subject: [PATCH 1/5] netcat: add virtual TCP and UDP workflows --- Formula/netcat.rb | 145 ++++++++++++++++++ .../kandelo_formula_support.rb | 30 ++++ .../run-virtual-network-pairs.ts | 140 +++++++++++++++++ .../test/kandelo_formula_support_test.rb | 17 ++ README.md | 1 + 5 files changed, 333 insertions(+) create mode 100644 Formula/netcat.rb create mode 100644 Kandelo/formula_support/run-virtual-network-pairs.ts diff --git a/Formula/netcat.rb b/Formula/netcat.rb new file mode 100644 index 0000000..e23d792 --- /dev/null +++ b/Formula/netcat.rb @@ -0,0 +1,145 @@ +require_relative "../Kandelo/formula_support/kandelo_formula_support" + +class Netcat < Formula + include KandeloFormulaSupport + + desc "GNU TCP and UDP networking utility for Kandelo" + homepage "https://netcat.sourceforge.net/" + url "https://downloads.sourceforge.net/project/netcat/netcat/0.7.1/netcat-0.7.1.tar.gz" + sha256 "30719c9a4ffbcf15676b8f528233ccc54ee6cba96cb4590975f5fd60c68a066f" + license "GPL-2.0-or-later" + + skip_clean "bin/netcat" + + patch :DATA + + def install + kandelo_require_arch!("wasm32") + + kandelo_wasm_build do |root| + ENV["CFLAGS"] = "-O2 -gline-tables-only -fdebug-compilation-dir=." + ENV["ac_cv_func_malloc_0_nonnull"] = "yes" + ENV["ac_cv_func_realloc_0_nonnull"] = "yes" + ENV["ac_cv_func_gethostbyname"] = "yes" + ENV["ac_cv_func_getservbyname"] = "yes" + ENV["ac_cv_func_getaddrinfo"] = "yes" + ENV["ac_cv_func_inet_pton"] = "yes" + ENV["ac_cv_func_select"] = "yes" + ENV["ac_cv_header_resolv_h"] = "no" + ENV["ac_cv_lib_resolv_main"] = "no" + ENV["gl_cv_func_gettimeofday_clobber"] = "no" + + configure_args = kandelo_std_configure_args + # Upstream's 2004 config.sub predates arm64 Darwin. This identifies the + # build machine only; the SDK wrapper supplies the Wasm target triplet. + configure_args << "--build=arm-apple-darwin" if OS.mac? && Hardware::CPU.arm? + system kandelo_configure, *configure_args, + "--disable-nls", + "--without-included-gettext" + system "make", "-j#{ENV.make_jobs}" + + instrumented = buildpath/"src/netcat.instrumented" + system "#{root}/scripts/run-wasm-fork-instrument.sh", buildpath/"src/netcat", "-o", instrumented + artifact_guards = "#{root}/scripts/wasm-artifact-guards.sh" + system "bash", "-c", <<~SH + set -euo pipefail + . #{artifact_guards.shellescape} + expected_abi=$(wasm_current_abi_version #{root.to_s.shellescape}) + artifact_abi=$(wasm_extract_abi_version #{instrumented.to_s.shellescape}) + if [ -z "$expected_abi" ] || [ "$artifact_abi" != "$expected_abi" ]; then + echo "ERROR: Netcat ABI $artifact_abi does not match Kandelo ABI $expected_abi" >&2 + exit 1 + fi + wasm_require_no_legacy_asyncify #{instrumented.to_s.shellescape} + if ! wasm_has_complete_fork_instrumentation #{instrumented.to_s.shellescape}; then + echo "ERROR: Netcat has incomplete fork instrumentation" >&2 + exit 1 + fi + SH + end + + kandelo_install_bin(buildpath/"src", "netcat.instrumented", "netcat") + bin.install_symlink "netcat" => "nc" + man1.install "doc/netcat.1" + end + + test do + version_output = kandelo_run_wasm(bin/"netcat", ["--version"]) + assert_match(/netcat \(The GNU Netcat\) 0\.7\.1/, version_output) + + help_output = kandelo_run_wasm(bin/"nc", ["--help"], preserve_argv0: true) + assert_includes help_output, "-l, --listen" + assert_includes help_output, "-u, --udp" + assert_includes help_output, "-c, --close" + + pair_output = kandelo_run_virtual_network_pairs( + bin/"netcat", + [ + { + name: "tcp", + transport: "tcp", + serverArgs: %w[nc -n -l -p 25125 -w 3], + clientArgs: %w[nc -n -c 10.88.0.2 25125], + serverStdin: "", + clientStdin: "from-tcp\n", + expectedServerStdout: "from-tcp\n", + }, + { + name: "udp", + transport: "udp", + serverArgs: %w[nc -n -c -u -l -p 25126 -w 3], + clientArgs: %w[nc -n -u -c 10.88.0.2 25126], + serverStdin: "", + clientStdin: "from-udp\n", + expectedServerStdout: "from-udp\n", + }, + ], + ) + assert_includes pair_output, '"tcp"' + assert_includes pair_output, '"udp"' + + binary = File.binread(bin/"netcat") + refute_includes binary, prefix.to_s + refute_match %r{/Users/[^/]+/}, binary + end +end + +__END__ +diff --git a/src/netcat.c b/src/netcat.c +index 8fd6b51..3c19d64 100644 +--- a/src/netcat.c ++++ b/src/netcat.c +@@ -494,2 +494,5 @@ int main(int argc, char *argv[]) + if (netcat_mode == NETCAT_LISTEN) { ++ /* A completed listen loop is successful; upstream leaves glob_ret at ++ its EXIT_FAILURE initializer. */ ++ glob_ret = EXIT_SUCCESS; + if (opt_exec) { +diff --git a/src/core.c b/src/core.c +index 7e6f3dd..158720a 100644 +--- a/src/core.c ++++ b/src/core.c +@@ -81,7 +81,11 @@ static int core_udp_listen(nc_sock_t *ncsock) + static int core_udp_listen(nc_sock_t *ncsock) + { + int ret, *sockbuf, sock, sock_max, timeout = ncsock->timeout; +- bool need_udphelper = TRUE; ++ /* GNU netcat's fallback enumerates interfaces with SIOCGIFCONF and binds ++ one socket per address. Kandelo exposes ordinary INADDR_ANY UDP bind, ++ but not that non-POSIX interface-enumeration ioctl. Select upstream's ++ single-socket path. */ ++ bool need_udphelper = FALSE; + #ifdef USE_PKTINFO + int sockopt = 1; + #endif +diff --git a/src/netcat.h b/src/netcat.h +index 88a2974..3ee95f0 100644 +--- a/src/netcat.h ++++ b/src/netcat.h +@@ -94 +94,5 @@ +-# define USE_PKTINFO ++/* Kandelo's current POSIX socket layer exposes the constants via libc headers, ++ but does not implement IP_PKTINFO ancillary data. Keep GNU netcat on its ++ portable UDP path until the kernel supports the sockopt and recvmsg control ++ messages. */ ++/* # define USE_PKTINFO */ diff --git a/Kandelo/formula_support/kandelo_formula_support.rb b/Kandelo/formula_support/kandelo_formula_support.rb index 234278b..e04c00d 100644 --- a/Kandelo/formula_support/kandelo_formula_support.rb +++ b/Kandelo/formula_support/kandelo_formula_support.rb @@ -712,6 +712,36 @@ def kandelo_run_framebuffer_wasm( shell_output("cd #{Shellwords.escape(root)} && #{command} < /dev/null") end + # Run paired programs on isolated Kandelo machines joined by the host's + # LocalVirtualNetwork. This exercises bind/listen/connect and datagram paths + # that a single-process formula runner cannot cover. + def kandelo_run_virtual_network_pairs(bin_path, cases) + root = kandelo_require_root! + if (node = ENV.fetch("HOMEBREW_KANDELO_NODE", nil)).to_s != "" + ENV.prepend_path "PATH", File.dirname(node) + end + + wasm_path = Pathname(bin_path) + if wasm_path.extname != ".wasm" + staged_wasm = testpath/"#{wasm_path.basename}.wasm" + File.binwrite(staged_wasm, File.binread(wasm_path)) + wasm_path = staged_wasm + end + + config = JSON.generate({ cases: cases }) + runner = Pathname(__dir__)/"run-virtual-network-pairs.ts" + command = "cd #{Shellwords.escape(root)} && " + command << "KANDELO_FORMULA_VIRTUAL_PAIRS_JSON=#{Shellwords.escape(config)} " + command << "node --experimental-wasm-exnref --import tsx/esm " + command << "#{Shellwords.escape(runner.to_s)} #{Shellwords.escape(root)} " + command << Shellwords.escape(wasm_path.to_s) + command << " 2>&1" + + output = shell_output(command) + kandelo_record_node_execution!(wasm_path, [], launcher: "kandelo_run_virtual_network_pairs") + output + end + def kandelo_record_node_execution!(wasm_path, argv, launcher: "kandelo_run_wasm") receipt = ENV.fetch("HOMEBREW_KANDELO_NODE_RECEIPT_PATH", nil) return if receipt.to_s.empty? diff --git a/Kandelo/formula_support/run-virtual-network-pairs.ts b/Kandelo/formula_support/run-virtual-network-pairs.ts new file mode 100644 index 0000000..a7a45c3 --- /dev/null +++ b/Kandelo/formula_support/run-virtual-network-pairs.ts @@ -0,0 +1,140 @@ +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; + +interface PairCase { + name: string; + transport: "tcp" | "udp"; + serverArgs: string[]; + clientArgs: string[]; + serverStdin: string; + clientStdin: string; + expectedServerStdout: string; + timeoutMs?: number; +} + +interface PairConfig { + cases: PairCase[]; +} + +async function main(): Promise { + const [root, programPath] = process.argv.slice(2); + if (!root || !programPath) { + throw new Error("usage: run-virtual-network-pairs.ts KANDELO_ROOT PROGRAM"); + } + + const config = JSON.parse( + process.env.KANDELO_FORMULA_VIRTUAL_PAIRS_JSON ?? "{}", + ) as PairConfig; + if (!Array.isArray(config.cases) || config.cases.length === 0) { + throw new Error("KANDELO_FORMULA_VIRTUAL_PAIRS_JSON must contain cases"); + } + + const moduleUrl = (path: string) => pathToFileURL(join(root, path)).href; + const [ + { LocalVirtualNetwork }, + { NodePlatformIO }, + { runCentralizedProgram }, + ] = await Promise.all([ + import(moduleUrl("host/src/networking/virtual-network.ts")), + import(moduleUrl("host/src/platform/node.ts")), + import(moduleUrl("host/test/centralized-test-helper.ts")), + ]); + + const summaries: Record = {}; + for (const pair of config.cases) { + const network = new LocalVirtualNetwork(); + const serverIO = new NodePlatformIO(); + const clientIO = new NodePlatformIO(); + serverIO.network = network.attachMachine({ + id: `${pair.name}-server`, + address: [10, 88, 0, 2], + hostnames: [`${pair.name}-server`], + }); + clientIO.network = network.attachMachine({ + id: `${pair.name}-client`, + address: [10, 88, 0, 3], + hostnames: [`${pair.name}-client`], + }); + + let resolveServerReady!: () => void; + const serverReady = new Promise((resolve) => { + resolveServerReady = resolve; + }); + if (pair.transport === "tcp") { + const listenTcp = serverIO.network.listenTcp?.bind(serverIO.network); + if (!listenTcp) + throw new Error("virtual network has no TCP listener support"); + serverIO.network.listenTcp = (listenerId, addr, port, target) => { + const status = listenTcp(listenerId, addr, port, target); + if (status === 0) resolveServerReady(); + return status; + }; + } else if (pair.transport === "udp") { + const bindUdp = serverIO.network.bindUdp?.bind(serverIO.network); + if (!bindUdp) throw new Error("virtual network has no UDP bind support"); + serverIO.network.bindUdp = (endpointId, addr, port, target) => { + const status = bindUdp(endpointId, addr, port, target); + if (status === 0) resolveServerReady(); + return status; + }; + } else { + throw new Error( + `${pair.name} has unsupported transport ${String(pair.transport)}`, + ); + } + + const timeout = pair.timeoutMs ?? 10_000; + const serverRun = runCentralizedProgram({ + programPath, + argv: pair.serverArgs, + io: serverIO, + stdin: pair.serverStdin, + timeout, + }); + await Promise.race([ + serverReady, + serverRun.then((result) => { + throw new Error( + `${pair.name} server exited before ${pair.transport} readiness: ` + + JSON.stringify({ + status: result.exitCode, + stdout: result.stdout, + stderr: result.stderr, + }), + ); + }), + ]); + const clientRun = runCentralizedProgram({ + programPath, + argv: pair.clientArgs, + io: clientIO, + stdin: pair.clientStdin, + timeout, + }); + const [server, client] = await Promise.all([serverRun, clientRun]); + const summary = { + serverStatus: server.exitCode, + clientStatus: client.exitCode, + serverStdout: server.stdout, + serverStderr: server.stderr, + clientStderr: client.stderr, + }; + if ( + server.exitCode !== 0 || + client.exitCode !== 0 || + server.stdout !== pair.expectedServerStdout || + server.stderr !== "" || + client.stderr !== "" + ) { + throw new Error(`${pair.name} failed: ${JSON.stringify(summary)}`); + } + summaries[pair.name] = summary; + } + + process.stdout.write(`${JSON.stringify(summaries)}\n`); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/Kandelo/formula_support/test/kandelo_formula_support_test.rb b/Kandelo/formula_support/test/kandelo_formula_support_test.rb index 38f07a2..fc662b6 100644 --- a/Kandelo/formula_support/test/kandelo_formula_support_test.rb +++ b/Kandelo/formula_support/test/kandelo_formula_support_test.rb @@ -801,6 +801,23 @@ def test_pty_execution_rejects_an_empty_guest_argv0 assert_includes error.message, "guest argv0 must be a nonempty normalized absolute path" end + def test_virtual_network_pairs_use_tap_owned_runner + harness = Harness.new + output = harness.kandelo_run_virtual_network_pairs( + "program.wasm", + [{ name: "tcp", transport: "tcp", serverArgs: ["nc", "-l"], clientArgs: ["nc", "host"] }], + ) + + assert_equal "runtime-ok\n", output + assert_includes harness.command, "run-virtual-network-pairs.ts" + assert_includes harness.command, "KANDELO_FORMULA_VIRTUAL_PAIRS_JSON=" + assert_includes harness.command, "serverArgs" + assert_includes harness.command, "clientArgs" + assert_includes harness.command, "transport" + assert_includes harness.command, "program.wasm" + assert_equal "kandelo_run_virtual_network_pairs", harness.recorded_launcher + end + private def artifact_validation_harness(dir, harness_class = Harness) diff --git a/README.md b/README.md index 1d2c102..c4b50c6 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Current migration controls and pilots include: Kandelo's musl `catopen` and `catgets` implementation. - `ctags`, Universal Ctags' maintained tag generator, `readtags` query client, and optscript interpreter with complete C and C++ workflows. +- `netcat`, GNU TCP and UDP client/listener workflows across virtual Kandelo machines. The SDK is not yet a Homebrew dependency. Trusted builds supply an `HOMEBREW_KANDELO_ROOT` checkout containing the SDK, sysroot, kernel, and Node From 77ef566390407dc026354fd53130b7628bd2fd83 Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Sat, 11 Jul 2026 06:14:26 -0400 Subject: [PATCH 2/5] netcat: declare artifact guard tools --- Formula/netcat.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Formula/netcat.rb b/Formula/netcat.rb index e23d792..0f02f47 100644 --- a/Formula/netcat.rb +++ b/Formula/netcat.rb @@ -9,6 +9,9 @@ class Netcat < Formula sha256 "30719c9a4ffbcf15676b8f528233ccc54ee6cba96cb4590975f5fd60c68a066f" license "GPL-2.0-or-later" + depends_on "binaryen" => :build + depends_on "wabt" => :build + skip_clean "bin/netcat" patch :DATA From 0b17a69b6db2a10252bed2ea6b1f2eb027ae7059 Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Sat, 11 Jul 2026 06:30:46 -0400 Subject: [PATCH 3/5] netcat: make virtual network command mutable --- Kandelo/formula_support/kandelo_formula_support.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kandelo/formula_support/kandelo_formula_support.rb b/Kandelo/formula_support/kandelo_formula_support.rb index e04c00d..5215230 100644 --- a/Kandelo/formula_support/kandelo_formula_support.rb +++ b/Kandelo/formula_support/kandelo_formula_support.rb @@ -730,7 +730,7 @@ def kandelo_run_virtual_network_pairs(bin_path, cases) config = JSON.generate({ cases: cases }) runner = Pathname(__dir__)/"run-virtual-network-pairs.ts" - command = "cd #{Shellwords.escape(root)} && " + command = +"cd #{Shellwords.escape(root)} && " command << "KANDELO_FORMULA_VIRTUAL_PAIRS_JSON=#{Shellwords.escape(config)} " command << "node --experimental-wasm-exnref --import tsx/esm " command << "#{Shellwords.escape(runner.to_s)} #{Shellwords.escape(root)} " From 67bac20ebbc13804819e82bd13d512ffffa6d0ac Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Sat, 11 Jul 2026 18:17:08 -0400 Subject: [PATCH 4/5] netcat: avoid redundant command unfreeze --- Kandelo/formula_support/kandelo_formula_support.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kandelo/formula_support/kandelo_formula_support.rb b/Kandelo/formula_support/kandelo_formula_support.rb index 5215230..e04c00d 100644 --- a/Kandelo/formula_support/kandelo_formula_support.rb +++ b/Kandelo/formula_support/kandelo_formula_support.rb @@ -730,7 +730,7 @@ def kandelo_run_virtual_network_pairs(bin_path, cases) config = JSON.generate({ cases: cases }) runner = Pathname(__dir__)/"run-virtual-network-pairs.ts" - command = +"cd #{Shellwords.escape(root)} && " + command = "cd #{Shellwords.escape(root)} && " command << "KANDELO_FORMULA_VIRTUAL_PAIRS_JSON=#{Shellwords.escape(config)} " command << "node --experimental-wasm-exnref --import tsx/esm " command << "#{Shellwords.escape(runner.to_s)} #{Shellwords.escape(root)} " From 3cf0374c26f9784b05eaabc06b50f9d06c057c3f Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Sun, 12 Jul 2026 08:26:37 -0400 Subject: [PATCH 5/5] netcat: enforce the non-forking artifact contract --- Formula/netcat.rb | 44 ++++++------------- .../kandelo_formula_support.rb | 4 ++ .../test/kandelo_formula_support_test.rb | 37 ++++++++++------ 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/Formula/netcat.rb b/Formula/netcat.rb index 0f02f47..3e5a4e5 100644 --- a/Formula/netcat.rb +++ b/Formula/netcat.rb @@ -1,4 +1,4 @@ -require_relative "../Kandelo/formula_support/kandelo_formula_support" +require (Tap.fetch("automattic", "kandelo-homebrew").path/"Kandelo/formula_support/kandelo_formula_support").to_s class Netcat < Formula include KandeloFormulaSupport @@ -20,17 +20,19 @@ def install kandelo_require_arch!("wasm32") kandelo_wasm_build do |root| - ENV["CFLAGS"] = "-O2 -gline-tables-only -fdebug-compilation-dir=." - ENV["ac_cv_func_malloc_0_nonnull"] = "yes" - ENV["ac_cv_func_realloc_0_nonnull"] = "yes" - ENV["ac_cv_func_gethostbyname"] = "yes" - ENV["ac_cv_func_getservbyname"] = "yes" - ENV["ac_cv_func_getaddrinfo"] = "yes" - ENV["ac_cv_func_inet_pton"] = "yes" - ENV["ac_cv_func_select"] = "yes" + prefix_maps = [ + "-ffile-prefix-map=#{buildpath}=/usr/src/netcat", + "-fdebug-prefix-map=#{buildpath}=/usr/src/netcat", + "-fmacro-prefix-map=#{buildpath}=/usr/src/netcat", + "-ffile-prefix-map=#{root}=/usr/src/kandelo", + "-fdebug-prefix-map=#{root}=/usr/src/kandelo", + "-fmacro-prefix-map=#{root}=/usr/src/kandelo", + ] + ENV["CFLAGS"] = ["-O2", "-gline-tables-only", "-fdebug-compilation-dir=.", *prefix_maps].join(" ") + # The SDK site owns target function facts. GNU Netcat's optional + # resolver-library probes are package-specific and libresolv is absent. ENV["ac_cv_header_resolv_h"] = "no" ENV["ac_cv_lib_resolv_main"] = "no" - ENV["gl_cv_func_gettimeofday_clobber"] = "no" configure_args = kandelo_std_configure_args # Upstream's 2004 config.sub predates arm64 Darwin. This identifies the @@ -40,28 +42,10 @@ def install "--disable-nls", "--without-included-gettext" system "make", "-j#{ENV.make_jobs}" - - instrumented = buildpath/"src/netcat.instrumented" - system "#{root}/scripts/run-wasm-fork-instrument.sh", buildpath/"src/netcat", "-o", instrumented - artifact_guards = "#{root}/scripts/wasm-artifact-guards.sh" - system "bash", "-c", <<~SH - set -euo pipefail - . #{artifact_guards.shellescape} - expected_abi=$(wasm_current_abi_version #{root.to_s.shellescape}) - artifact_abi=$(wasm_extract_abi_version #{instrumented.to_s.shellescape}) - if [ -z "$expected_abi" ] || [ "$artifact_abi" != "$expected_abi" ]; then - echo "ERROR: Netcat ABI $artifact_abi does not match Kandelo ABI $expected_abi" >&2 - exit 1 - fi - wasm_require_no_legacy_asyncify #{instrumented.to_s.shellescape} - if ! wasm_has_complete_fork_instrumentation #{instrumented.to_s.shellescape}; then - echo "ERROR: Netcat has incomplete fork instrumentation" >&2 - exit 1 - fi - SH + kandelo_validate_wasm_artifact(buildpath/"src/netcat", fork: :forbidden) end - kandelo_install_bin(buildpath/"src", "netcat.instrumented", "netcat") + kandelo_install_bin(buildpath/"src", "netcat", "netcat") bin.install_symlink "netcat" => "nc" man1.install "doc/netcat.1" end diff --git a/Kandelo/formula_support/kandelo_formula_support.rb b/Kandelo/formula_support/kandelo_formula_support.rb index e04c00d..760a62a 100644 --- a/Kandelo/formula_support/kandelo_formula_support.rb +++ b/Kandelo/formula_support/kandelo_formula_support.rb @@ -729,6 +729,10 @@ def kandelo_run_virtual_network_pairs(bin_path, cases) end config = JSON.generate({ cases: cases }) + # Compiled host output shadows TypeScript source under tsx. Network-pair + # tests must exercise the checkout supplied by HOMEBREW_KANDELO_ROOT. + FileUtils.rm_rf(Pathname(root)/"host/dist") + runner = Pathname(__dir__)/"run-virtual-network-pairs.ts" command = "cd #{Shellwords.escape(root)} && " command << "KANDELO_FORMULA_VIRTUAL_PAIRS_JSON=#{Shellwords.escape(config)} " diff --git a/Kandelo/formula_support/test/kandelo_formula_support_test.rb b/Kandelo/formula_support/test/kandelo_formula_support_test.rb index fc662b6..566da60 100644 --- a/Kandelo/formula_support/test/kandelo_formula_support_test.rb +++ b/Kandelo/formula_support/test/kandelo_formula_support_test.rb @@ -801,21 +801,30 @@ def test_pty_execution_rejects_an_empty_guest_argv0 assert_includes error.message, "guest argv0 must be a nonempty normalized absolute path" end - def test_virtual_network_pairs_use_tap_owned_runner - harness = Harness.new - output = harness.kandelo_run_virtual_network_pairs( - "program.wasm", - [{ name: "tcp", transport: "tcp", serverArgs: ["nc", "-l"], clientArgs: ["nc", "host"] }], - ) + def test_virtual_network_pairs_use_tap_owned_runner_and_remove_stale_host_dist + Dir.mktmpdir("kandelo-formula-support") do |dir| + root = Pathname(dir)/"kandelo root" + host_dist = root/"host/dist" + host_dist.mkpath + (host_dist/"stale.js").binwrite("stale") - assert_equal "runtime-ok\n", output - assert_includes harness.command, "run-virtual-network-pairs.ts" - assert_includes harness.command, "KANDELO_FORMULA_VIRTUAL_PAIRS_JSON=" - assert_includes harness.command, "serverArgs" - assert_includes harness.command, "clientArgs" - assert_includes harness.command, "transport" - assert_includes harness.command, "program.wasm" - assert_equal "kandelo_run_virtual_network_pairs", harness.recorded_launcher + harness = Harness.new + harness.root_path = root.to_s + output = harness.kandelo_run_virtual_network_pairs( + "program.wasm", + [{ name: "tcp", transport: "tcp", serverArgs: ["nc", "-l"], clientArgs: ["nc", "host"] }], + ) + + assert_equal "runtime-ok\n", output + assert_includes harness.command, "run-virtual-network-pairs.ts" + assert_includes harness.command, "KANDELO_FORMULA_VIRTUAL_PAIRS_JSON=" + assert_includes harness.command, "serverArgs" + assert_includes harness.command, "clientArgs" + assert_includes harness.command, "transport" + assert_includes harness.command, "program.wasm" + assert_equal "kandelo_run_virtual_network_pairs", harness.recorded_launcher + refute_path_exists host_dist + end end private