From a35acaa8e0678b990fae57f48183616ac118604a Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Thu, 26 Mar 2026 12:52:49 +0000 Subject: [PATCH 1/2] test(allexport): add regression tests for set -a behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #793 — feature already implemented, adding test coverage for: - Basic allexport to subprocess - Allexport with source - Long option form (set -o allexport) - Not retroactive - For loop variable --- crates/bashkit/tests/allexport_tests.rs | 174 ++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 crates/bashkit/tests/allexport_tests.rs diff --git a/crates/bashkit/tests/allexport_tests.rs b/crates/bashkit/tests/allexport_tests.rs new file mode 100644 index 00000000..4a54a22f --- /dev/null +++ b/crates/bashkit/tests/allexport_tests.rs @@ -0,0 +1,174 @@ +//! Tests for `set -a` (allexport) behavior. +//! +//! When allexport is enabled, every variable assignment should also mark +//! the variable as exported (visible to child scripts via env). + +use bashkit::Bash; +use std::path::Path; + +/// Basic allexport: variables assigned while set -a is active are exported +#[tokio::test] +async fn allexport_basic_export_to_subprocess() { + let mut bash = Bash::new(); + let fs = bash.fs(); + + fs.write_file( + Path::new("/check.sh"), + b"#!/bin/bash\necho \"FOO=${FOO:-unset}\"\necho \"BAZ=${BAZ:-unset}\"\necho \"AFTER=${AFTER:-unset}\"", + ) + .await + .unwrap(); + fs.chmod(Path::new("/check.sh"), 0o755).await.unwrap(); + + let result = bash + .exec( + r#" +set -a +FOO="bar" +BAZ="qux" +set +a +AFTER="not-exported" +/check.sh +"#, + ) + .await + .unwrap(); + + let lines: Vec<&str> = result.stdout.trim().lines().collect(); + assert_eq!(lines[0], "FOO=bar"); + assert_eq!(lines[1], "BAZ=qux"); + assert_eq!(lines[2], "AFTER=unset"); +} + +/// allexport with source: sourced env files get exported +#[tokio::test] +async fn allexport_with_source() { + let mut bash = Bash::new(); + let fs = bash.fs(); + + fs.write_file( + Path::new("/vars.env"), + b"DB_HOST=localhost\nDB_PORT=5432", + ) + .await + .unwrap(); + + fs.write_file( + Path::new("/check.sh"), + b"#!/bin/bash\necho \"host=${DB_HOST:-unset}\"\necho \"port=${DB_PORT:-unset}\"", + ) + .await + .unwrap(); + fs.chmod(Path::new("/check.sh"), 0o755).await.unwrap(); + + let result = bash + .exec( + r#" +set -a +source /vars.env +set +a +/check.sh +"#, + ) + .await + .unwrap(); + + let lines: Vec<&str> = result.stdout.trim().lines().collect(); + assert_eq!(lines[0], "host=localhost"); + assert_eq!(lines[1], "port=5432"); +} + +/// set -o allexport / set +o allexport work as aliases +#[tokio::test] +async fn allexport_long_option_form() { + let mut bash = Bash::new(); + let fs = bash.fs(); + + fs.write_file( + Path::new("/check.sh"), + b"#!/bin/bash\necho \"X=${X:-unset}\"\necho \"Y=${Y:-unset}\"", + ) + .await + .unwrap(); + fs.chmod(Path::new("/check.sh"), 0o755).await.unwrap(); + + let result = bash + .exec( + r#" +set -o allexport +X="hello" +set +o allexport +Y="world" +/check.sh +"#, + ) + .await + .unwrap(); + + let lines: Vec<&str> = result.stdout.trim().lines().collect(); + assert_eq!(lines[0], "X=hello"); + assert_eq!(lines[1], "Y=unset"); +} + +/// Variables assigned before set -a are not retroactively exported +#[tokio::test] +async fn allexport_not_retroactive() { + let mut bash = Bash::new(); + let fs = bash.fs(); + + fs.write_file( + Path::new("/check.sh"), + b"#!/bin/bash\necho \"BEFORE=${BEFORE:-unset}\"\necho \"DURING=${DURING:-unset}\"", + ) + .await + .unwrap(); + fs.chmod(Path::new("/check.sh"), 0o755).await.unwrap(); + + let result = bash + .exec( + r#" +BEFORE="exists" +set -a +DURING="new" +set +a +/check.sh +"#, + ) + .await + .unwrap(); + + let lines: Vec<&str> = result.stdout.trim().lines().collect(); + assert_eq!(lines[0], "BEFORE=unset"); + assert_eq!(lines[1], "DURING=new"); +} + +/// allexport applies to for loop variable +#[tokio::test] +async fn allexport_for_loop_variable() { + let mut bash = Bash::new(); + let fs = bash.fs(); + + fs.write_file( + Path::new("/check.sh"), + b"#!/bin/bash\necho \"item=${item:-unset}\"", + ) + .await + .unwrap(); + fs.chmod(Path::new("/check.sh"), 0o755).await.unwrap(); + + let result = bash + .exec( + r#" +set -a +for item in alpha beta; do + : +done +set +a +/check.sh +"#, + ) + .await + .unwrap(); + + assert_eq!(result.stdout.trim(), "item=beta"); +} From 8feb2436d802feabcb5625a9a0d917e1ebabe323 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Thu, 26 Mar 2026 12:54:03 +0000 Subject: [PATCH 2/2] style: format allexport_tests.rs --- crates/bashkit/tests/allexport_tests.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/bashkit/tests/allexport_tests.rs b/crates/bashkit/tests/allexport_tests.rs index 4a54a22f..a5e8162e 100644 --- a/crates/bashkit/tests/allexport_tests.rs +++ b/crates/bashkit/tests/allexport_tests.rs @@ -46,12 +46,9 @@ async fn allexport_with_source() { let mut bash = Bash::new(); let fs = bash.fs(); - fs.write_file( - Path::new("/vars.env"), - b"DB_HOST=localhost\nDB_PORT=5432", - ) - .await - .unwrap(); + fs.write_file(Path::new("/vars.env"), b"DB_HOST=localhost\nDB_PORT=5432") + .await + .unwrap(); fs.write_file( Path::new("/check.sh"),