1- use super :: ExecutorConfig ;
2- use crate :: executor:: ExecutionContext ;
3- use crate :: executor:: Executor ;
4- use crate :: system:: SystemInfo ;
5- use rstest_reuse:: { self , * } ;
6- use shell_quote:: { Bash , QuoteRefExt } ;
7- use tempfile:: TempDir ;
8- use tokio:: sync:: { OnceCell , Semaphore , SemaphorePermit } ;
9-
10- const TESTS : [ & str ; 6 ] = [
11- // Simple echo command
12- "echo 'Hello, World!'" ,
13- // Multi-line commands without semicolons
14- "echo \" Working\"
1+ // Shared test helpers. On non-linux platforms, the only consumer is the `walltime` mod, which is
2+ // gated behind `GITHUB_ACTIONS`. Apply the same gate here so dead-code lints don't fire on macOS
3+ // without the env var. On linux, valgrind always uses these helpers, so no gate is needed.
4+ #[ cfg_attr( not( target_os = "linux" ) , test_with:: env( GITHUB_ACTIONS ) ) ]
5+ mod helpers {
6+ pub use crate :: executor:: { ExecutionContext , Executor , ExecutorConfig } ;
7+ pub use crate :: system:: SystemInfo ;
8+ pub use rstest_reuse:: { self , * } ;
9+ pub use shell_quote:: { Bash , QuoteRefExt } ;
10+ pub use tempfile:: TempDir ;
11+ pub use tokio:: sync:: { OnceCell , Semaphore , SemaphorePermit } ;
12+
13+ pub const TESTS : [ & str ; 6 ] = [
14+ // Simple echo command
15+ "echo 'Hello, World!'" ,
16+ // Multi-line commands without semicolons
17+ "echo \" Working\"
1518echo \" with\"
1619echo \" multiple lines\" " ,
17- // Multi-line commands with semicolons
18- "echo \" Working\" ;
20+ // Multi-line commands with semicolons
21+ "echo \" Working\" ;
1922echo \" with\" ;
2023echo \" multiple lines\" ;" ,
21- // Directory change and validation
22- "cd /tmp
24+ // Directory change and validation
25+ "cd /tmp
2326# Check that the directory is actually changed
2427if [ $(basename $(pwd)) != \" tmp\" ]; then
2528 exit 1
2629fi" ,
27- // Quote escaping test
28- "#!/bin/bash
30+ // Quote escaping test
31+ "#!/bin/bash
2932VALUE=\" He said \\ \" Hello 'world'\\ \" & echo \\ $HOME\"
3033if [ \" $VALUE\" = \" He said \\ \" Hello 'world'\\ \" & echo \\ $HOME\" ]; then
3134 echo \" Quote test passed\"
3235else
3336 echo \" ERROR: Quote handling failed\"
3437 exit 1
3538fi" ,
36- // Command substitution test
37- "#!/bin/bash
39+ // Command substitution test
40+ "#!/bin/bash
3841RESULT=$(echo \" test 'nested' \\ \" quotes\\ \" here\" )
3942COUNT=$(echo \" $RESULT\" | wc -w)
4043if [ \" $COUNT\" -eq \" 4\" ]; then
@@ -43,112 +46,115 @@ else
4346 echo \" ERROR: Expected 4 words, got $COUNT\"
4447 exit 1
4548fi" ,
46- ] ;
49+ ] ;
4750
48- fn env_var_validation_script ( env : & str , expected : & str ) -> String {
49- let expected: String = expected. quoted ( Bash ) ;
50- format ! (
51- r#"
51+ pub fn env_var_validation_script ( env : & str , expected : & str ) -> String {
52+ let expected: String = expected. quoted ( Bash ) ;
53+ format ! (
54+ r#"
5255if [ "${env}" != {expected} ]; then
5356 echo "FAIL: Environment variable not set correctly"
5457 echo "Got: '${env}'"
5558 exit 1
5659fi
5760"#
58- )
59- }
60-
61- const ENV_TESTS : [ ( & str , & str ) ; 8 ] = [
62- // Mixed quotes, backticks, and shell metacharacters
63- (
64- "quotes_and_escapes" ,
65- r#""'He said "Hello 'world' `date`" & echo "done" with \\n\\t\\"# ,
66- ) ,
67- // Multiline content with tabs and trailing whitespace
68- (
69- "multiline_and_whitespace" ,
70- "Line 1\n Line 2\t Tabbed\n \t " ,
71- ) ,
72- // Shell metacharacters: pipes, redirects, operators
73- (
74- "shell_metacharacters" ,
75- r#"*.txt | grep "test" && echo "found" || echo "error" ; ls > /tmp/out"# ,
76- ) ,
77- // Variable expansion and command substitution
78- (
79- "variables_and_commands" ,
80- r#"$HOME ${PATH} $((1+1)) $(echo "embedded") VAR="value with spaces""# ,
81- ) ,
82- // Unicode characters and ANSI escape sequences
83- (
84- "unicode_and_special" ,
85- "🚀 café naïve\u{200b} hidden\x1b [31mRed\x1b [0m\x01 \x02 " ,
86- ) ,
87- // Complex mix of quoting styles with shell operators
88- (
89- "complex_mixed" ,
90- r#"start'single'middle"double"end $VAR | cmd && echo "done" || fail"# ,
91- ) ,
92- // Empty string edge case
93- ( "empty" , "" ) ,
94- // Whitespace-only content
95- ( "space_only" , " " ) ,
96- ] ;
97-
98- #[ template]
99- #[ rstest:: rstest]
100- #[ case( TESTS [ 0 ] ) ]
101- #[ case( TESTS [ 1 ] ) ]
102- #[ case( TESTS [ 2 ] ) ]
103- #[ case( TESTS [ 3 ] ) ]
104- #[ case( TESTS [ 4 ] ) ]
105- #[ case( TESTS [ 5 ] ) ]
106- fn test_cases ( #[ case] cmd : & str ) { }
107-
108- #[ template]
109- #[ rstest:: rstest]
110- #[ case( ENV_TESTS [ 0 ] ) ]
111- #[ case( ENV_TESTS [ 1 ] ) ]
112- #[ case( ENV_TESTS [ 2 ] ) ]
113- #[ case( ENV_TESTS [ 3 ] ) ]
114- #[ case( ENV_TESTS [ 4 ] ) ]
115- #[ case( ENV_TESTS [ 5 ] ) ]
116- #[ case( ENV_TESTS [ 6 ] ) ]
117- #[ case( ENV_TESTS [ 7 ] ) ]
118- fn env_test_cases ( #[ case] env_case : ( & str , & str ) ) { }
119-
120- async fn create_test_setup ( config : ExecutorConfig ) -> ( ExecutionContext , TempDir ) {
121- let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
122-
123- let mut config = config;
124-
125- // Provide a test token so authentication doesn't fail
126- if config. token . is_none ( ) {
127- config. token = Some ( "test-token" . to_string ( ) ) ;
61+ )
12862 }
12963
130- let profile_folder = temp_dir. path ( ) . to_path_buf ( ) ;
131- let execution_context = ExecutionContext :: new ( config, profile_folder) ;
64+ pub const ENV_TESTS : [ ( & str , & str ) ; 8 ] = [
65+ // Mixed quotes, backticks, and shell metacharacters
66+ (
67+ "quotes_and_escapes" ,
68+ r#""'He said "Hello 'world' `date`" & echo "done" with \\n\\t\\"# ,
69+ ) ,
70+ // Multiline content with tabs and trailing whitespace
71+ (
72+ "multiline_and_whitespace" ,
73+ "Line 1\n Line 2\t Tabbed\n \t " ,
74+ ) ,
75+ // Shell metacharacters: pipes, redirects, operators
76+ (
77+ "shell_metacharacters" ,
78+ r#"*.txt | grep "test" && echo "found" || echo "error" ; ls > /tmp/out"# ,
79+ ) ,
80+ // Variable expansion and command substitution
81+ (
82+ "variables_and_commands" ,
83+ r#"$HOME ${PATH} $((1+1)) $(echo "embedded") VAR="value with spaces""# ,
84+ ) ,
85+ // Unicode characters and ANSI escape sequences
86+ (
87+ "unicode_and_special" ,
88+ "🚀 café naïve\u{200b} hidden\x1b [31mRed\x1b [0m\x01 \x02 " ,
89+ ) ,
90+ // Complex mix of quoting styles with shell operators
91+ (
92+ "complex_mixed" ,
93+ r#"start'single'middle"double"end $VAR | cmd && echo "done" || fail"# ,
94+ ) ,
95+ // Empty string edge case
96+ ( "empty" , "" ) ,
97+ // Whitespace-only content
98+ ( "space_only" , " " ) ,
99+ ] ;
132100
133- ( execution_context, temp_dir)
134- }
101+ #[ template]
102+ #[ rstest:: rstest]
103+ #[ case( TESTS [ 0 ] ) ]
104+ #[ case( TESTS [ 1 ] ) ]
105+ #[ case( TESTS [ 2 ] ) ]
106+ #[ case( TESTS [ 3 ] ) ]
107+ #[ case( TESTS [ 4 ] ) ]
108+ #[ case( TESTS [ 5 ] ) ]
109+ pub fn test_cases ( #[ case] cmd : & str ) { }
110+
111+ #[ template]
112+ #[ rstest:: rstest]
113+ #[ case( ENV_TESTS [ 0 ] ) ]
114+ #[ case( ENV_TESTS [ 1 ] ) ]
115+ #[ case( ENV_TESTS [ 2 ] ) ]
116+ #[ case( ENV_TESTS [ 3 ] ) ]
117+ #[ case( ENV_TESTS [ 4 ] ) ]
118+ #[ case( ENV_TESTS [ 5 ] ) ]
119+ #[ case( ENV_TESTS [ 6 ] ) ]
120+ #[ case( ENV_TESTS [ 7 ] ) ]
121+ pub fn env_test_cases ( #[ case] env_case : ( & str , & str ) ) { }
122+
123+ pub async fn create_test_setup ( config : ExecutorConfig ) -> ( ExecutionContext , TempDir ) {
124+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
125+
126+ let mut config = config;
127+
128+ // Provide a test token so authentication doesn't fail
129+ if config. token . is_none ( ) {
130+ config. token = Some ( "test-token" . to_string ( ) ) ;
131+ }
135132
136- // Uprobes set by memtrack, lead to crashes in valgrind because they work by setting breakpoints on the first
137- // instruction. Valgrind doesn't rethrow those breakpoint exceptions, which makes the test crash.
138- //
139- // Therefore, we can only execute either valgrind or memtrack at any time, and not both at the same time.
140- static BPF_INSTRUMENTATION_LOCK : OnceCell < Semaphore > = OnceCell :: const_new ( ) ;
133+ let profile_folder = temp_dir. path ( ) . to_path_buf ( ) ;
134+ let execution_context = ExecutionContext :: new ( config, profile_folder) ;
141135
142- async fn acquire_bpf_instrumentation_lock ( ) -> SemaphorePermit < ' static > {
143- let semaphore = BPF_INSTRUMENTATION_LOCK
144- . get_or_init ( || async { Semaphore :: new ( 1 ) } )
145- . await ;
146- semaphore. acquire ( ) . await . unwrap ( )
136+ ( execution_context, temp_dir)
137+ }
138+
139+ // Uprobes set by memtrack, lead to crashes in valgrind because they work by setting breakpoints on the first
140+ // instruction. Valgrind doesn't rethrow those breakpoint exceptions, which makes the test crash.
141+ //
142+ // Therefore, we can only execute either valgrind or memtrack at any time, and not both at the same time.
143+ #[ cfg( target_os = "linux" ) ]
144+ pub static BPF_INSTRUMENTATION_LOCK : OnceCell < Semaphore > = OnceCell :: const_new ( ) ;
145+
146+ #[ cfg( target_os = "linux" ) ]
147+ pub async fn acquire_bpf_instrumentation_lock ( ) -> SemaphorePermit < ' static > {
148+ let semaphore = BPF_INSTRUMENTATION_LOCK
149+ . get_or_init ( || async { Semaphore :: new ( 1 ) } )
150+ . await ;
151+ semaphore. acquire ( ) . await . unwrap ( )
152+ }
147153}
148154
149155#[ cfg( target_os = "linux" ) ]
150156mod valgrind {
151- use super :: * ;
157+ use super :: helpers :: * ;
152158 use crate :: executor:: valgrind:: executor:: ValgrindExecutor ;
153159
154160 async fn get_valgrind_executor ( ) -> ( SemaphorePermit < ' static > , & ' static ValgrindExecutor ) {
@@ -209,7 +215,7 @@ mod valgrind {
209215
210216#[ test_with:: env( GITHUB_ACTIONS ) ]
211217mod walltime {
212- use super :: * ;
218+ use super :: helpers :: * ;
213219 use crate :: executor:: wall_time:: executor:: WallTimeExecutor ;
214220
215221 async fn get_walltime_executor ( ) -> ( SemaphorePermit < ' static > , WallTimeExecutor ) {
381387#[ cfg( target_os = "linux" ) ]
382388#[ test_with:: env( GITHUB_ACTIONS ) ]
383389mod memory {
384- use super :: * ;
390+ use super :: helpers :: * ;
385391 use crate :: executor:: memory:: executor:: MemoryExecutor ;
386392
387393 async fn get_memory_executor ( ) -> (
0 commit comments