Skip to content

Commit 3bae99a

Browse files
committed
DRY tests with macro
1 parent 82728be commit 3bae99a

4 files changed

Lines changed: 219 additions & 198 deletions

File tree

tests/linux_integration.rs

Lines changed: 56 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
mod common;
22
mod system_integration;
33

4+
#[macro_use]
5+
mod platform_test_macro;
6+
47
#[cfg(target_os = "linux")]
58
mod tests {
69
use super::*;
710
use crate::system_integration::JailTestPlatform;
8-
use serial_test::serial;
911

1012
/// Linux-specific platform implementation
1113
struct LinuxPlatform;
@@ -31,73 +33,13 @@ mod tests {
3133
}
3234
}
3335

34-
#[test]
35-
#[serial] // Network namespaces are global state, must run sequentially
36-
fn test_jail_allows_matching_requests() {
37-
system_integration::test_jail_allows_matching_requests::<LinuxPlatform>();
38-
}
39-
40-
#[test]
41-
#[serial] // Network namespaces are global state, must run sequentially
42-
fn test_jail_denies_non_matching_requests() {
43-
system_integration::test_jail_denies_non_matching_requests::<LinuxPlatform>();
44-
}
45-
46-
#[test]
47-
#[serial] // Network namespaces are global state, must run sequentially
48-
fn test_jail_method_specific_rules() {
49-
system_integration::test_jail_method_specific_rules::<LinuxPlatform>();
50-
}
51-
52-
#[test]
53-
#[serial] // Network namespaces are global state, must run sequentially
54-
fn test_jail_log_only_mode() {
55-
system_integration::test_jail_log_only_mode::<LinuxPlatform>();
56-
}
57-
58-
#[test]
59-
#[serial] // Network namespaces are global state, must run sequentially
60-
fn test_jail_dry_run_mode() {
61-
system_integration::test_jail_dry_run_mode::<LinuxPlatform>();
62-
}
63-
64-
#[test]
65-
fn test_jail_requires_command() {
66-
system_integration::test_jail_requires_command::<LinuxPlatform>();
67-
}
68-
69-
#[test]
70-
#[serial] // Network namespaces are global state, must run sequentially
71-
fn test_jail_exit_code_propagation() {
72-
system_integration::test_jail_exit_code_propagation::<LinuxPlatform>();
73-
}
74-
75-
#[test]
76-
#[serial] // Network namespaces are global state, must run sequentially
77-
fn test_native_jail_blocks_https() {
78-
system_integration::test_native_jail_blocks_https::<LinuxPlatform>();
79-
}
80-
81-
#[test]
82-
#[serial] // Network namespaces are global state, must run sequentially
83-
fn test_native_jail_allows_https() {
84-
system_integration::test_native_jail_allows_https::<LinuxPlatform>();
85-
}
86-
87-
#[test]
88-
#[serial] // Network namespaces are global state, must run sequentially
89-
fn test_jail_https_connect_denied() {
90-
system_integration::test_jail_https_connect_denied::<LinuxPlatform>();
91-
}
36+
// Generate all the shared platform tests
37+
platform_tests!(LinuxPlatform);
9238

93-
// Linux with network namespaces supports HTTPS CONNECT
94-
#[test]
95-
#[serial] // Network namespaces are global state, must run sequentially
96-
fn test_jail_https_connect_allowed() {
97-
system_integration::test_jail_https_connect_allowed::<LinuxPlatform>();
98-
}
39+
// Linux-specific tests below
40+
use serial_test::serial;
9941

100-
// Linux-specific test: verify namespace cleanup
42+
/// Linux-specific test: verify namespace cleanup
10143
#[test]
10244
#[serial]
10345
fn test_namespace_cleanup() {
@@ -112,11 +54,11 @@ mod tests {
11254
let initial_namespaces = String::from_utf8_lossy(&output.stdout);
11355
let initial_count = initial_namespaces
11456
.lines()
115-
.filter(|line| line.starts_with("httpjail_"))
57+
.filter(|line| line.contains("httpjail_"))
11658
.count();
11759

11860
// Run httpjail
119-
let mut cmd = system_integration::httpjail_cmd();
61+
let mut cmd = common::httpjail_cmd();
12062
cmd.arg("-r")
12163
.arg("allow: .*")
12264
.arg("--")
@@ -125,7 +67,7 @@ mod tests {
12567

12668
let _output = cmd.output().expect("Failed to execute httpjail");
12769

128-
// Check namespaces are cleaned up
70+
// Check namespace was cleaned up
12971
let output = std::process::Command::new("ip")
13072
.args(&["netns", "list"])
13173
.output()
@@ -134,87 +76,69 @@ mod tests {
13476
let final_namespaces = String::from_utf8_lossy(&output.stdout);
13577
let final_count = final_namespaces
13678
.lines()
137-
.filter(|line| line.starts_with("httpjail_"))
79+
.filter(|line| line.contains("httpjail_"))
13880
.count();
13981

14082
assert_eq!(
14183
initial_count, final_count,
142-
"Namespace was not cleaned up properly. Initial: {}, Final: {}",
84+
"Namespace not cleaned up properly. Initial: {}, Final: {}",
14385
initial_count, final_count
14486
);
14587
}
14688

147-
// Linux-specific test: verify concurrent execution
89+
/// Linux-specific test: verify concurrent namespace isolation
14890
#[test]
14991
#[serial]
15092
fn test_concurrent_namespace_isolation() {
15193
LinuxPlatform::require_privileges();
152-
153-
use std::sync::Arc;
154-
use std::sync::atomic::{AtomicUsize, Ordering};
94+
use std::process::Command;
15595
use std::thread;
96+
use std::time::Duration;
15697

157-
let success_count = Arc::new(AtomicUsize::new(0));
158-
let mut handles = vec![];
159-
160-
// Spawn multiple httpjail instances concurrently
161-
for i in 0..3 {
162-
let success_count = Arc::clone(&success_count);
163-
let handle = thread::spawn(move || {
164-
let mut cmd = system_integration::httpjail_cmd();
165-
cmd.arg("-r")
166-
.arg("allow: .*")
167-
.arg("--")
168-
.arg("sh")
169-
.arg("-c")
170-
.arg(&format!("echo 'Instance {}'", i));
171-
172-
match cmd.output() {
173-
Ok(output) if output.status.success() => {
174-
success_count.fetch_add(1, Ordering::SeqCst);
175-
}
176-
_ => {}
177-
}
178-
});
179-
handles.push(handle);
180-
}
181-
182-
// Wait for all threads
183-
for handle in handles {
184-
handle.join().unwrap();
185-
}
186-
187-
// All instances should succeed
188-
assert_eq!(
189-
success_count.load(Ordering::SeqCst),
190-
3,
191-
"Not all concurrent instances succeeded"
192-
);
193-
194-
// Verify all namespaces are cleaned up
195-
let output = std::process::Command::new("ip")
196-
.args(&["netns", "list"])
98+
// Start first httpjail instance that sleeps
99+
let mut child1 = common::httpjail_cmd()
100+
.arg("-r")
101+
.arg("allow: .*")
102+
.arg("--")
103+
.arg("sh")
104+
.arg("-c")
105+
.arg("echo 'Instance 1'; sleep 2; echo 'Instance 1 done'")
106+
.spawn()
107+
.expect("Failed to start first httpjail");
108+
109+
// Give it time to set up
110+
thread::sleep(Duration::from_millis(500));
111+
112+
// Start second httpjail instance
113+
let output2 = common::httpjail_cmd()
114+
.arg("-r")
115+
.arg("allow: .*")
116+
.arg("--")
117+
.arg("echo")
118+
.arg("Instance 2")
197119
.output()
198-
.expect("Failed to list namespaces");
120+
.expect("Failed to execute second httpjail");
199121

200-
let namespaces = String::from_utf8_lossy(&output.stdout);
201-
let httpjail_count = namespaces
202-
.lines()
203-
.filter(|line| line.starts_with("httpjail_"))
204-
.count();
122+
// Both should succeed without interference
123+
let output1 = child1
124+
.wait_with_output()
125+
.expect("Failed to wait for first httpjail");
205126

206-
assert_eq!(
207-
httpjail_count, 0,
208-
"Found {} httpjail namespaces still present after concurrent test",
209-
httpjail_count
127+
assert!(
128+
output1.status.success(),
129+
"First instance failed: {:?}",
130+
String::from_utf8_lossy(&output1.stderr)
131+
);
132+
assert!(
133+
output2.status.success(),
134+
"Second instance failed: {:?}",
135+
String::from_utf8_lossy(&output2.stderr)
210136
);
211-
}
212-
}
213137

214-
#[cfg(not(target_os = "linux"))]
215-
mod tests {
216-
#[test]
217-
fn test_platform_not_linux() {
218-
println!("Linux integration tests only run on Linux");
138+
// Verify both ran
139+
let stdout1 = String::from_utf8_lossy(&output1.stdout);
140+
let stdout2 = String::from_utf8_lossy(&output2.stdout);
141+
assert!(stdout1.contains("Instance 1"), "First instance didn't run");
142+
assert!(stdout2.contains("Instance 2"), "Second instance didn't run");
219143
}
220144
}

tests/macos_integration.rs

Lines changed: 17 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,41 @@
11
mod common;
22
mod system_integration;
33

4+
#[macro_use]
5+
mod platform_test_macro;
6+
47
#[cfg(target_os = "macos")]
58
mod tests {
69
use super::*;
7-
use serial_test::serial;
10+
use crate::system_integration::JailTestPlatform;
811

912
/// macOS-specific platform implementation
1013
struct MacOSPlatform;
1114

1215
impl system_integration::JailTestPlatform for MacOSPlatform {
1316
fn require_privileges() {
14-
common::require_sudo();
17+
// Check if running as root
18+
let uid = unsafe { libc::geteuid() };
19+
if uid != 0 {
20+
eprintln!("\n⚠️ Test requires root privileges.");
21+
eprintln!(" Run with: sudo cargo test --test macos_integration");
22+
eprintln!(" Or use the SUDO_ASKPASS helper:");
23+
eprintln!(" SUDO_ASKPASS=$(pwd)/askpass_macos.sh sudo -A cargo test\n");
24+
panic!("Test skipped: requires root privileges");
25+
}
1526
}
1627

1728
fn platform_name() -> &'static str {
1829
"macOS"
1930
}
2031

2132
fn supports_https_interception() -> bool {
22-
true // macOS supports transparent TLS interception
33+
true // macOS with PF supports transparent TLS interception
2334
}
2435
}
2536

26-
#[test]
27-
#[serial] // PF rules are global state, must run sequentially
28-
fn test_jail_allows_matching_requests() {
29-
system_integration::test_jail_allows_matching_requests::<MacOSPlatform>();
30-
}
31-
32-
#[test]
33-
#[serial] // PF rules are global state, must run sequentially
34-
fn test_jail_denies_non_matching_requests() {
35-
system_integration::test_jail_denies_non_matching_requests::<MacOSPlatform>();
36-
}
37-
38-
#[test]
39-
#[serial] // PF rules are global state, must run sequentially
40-
fn test_jail_method_specific_rules() {
41-
system_integration::test_jail_method_specific_rules::<MacOSPlatform>();
42-
}
37+
// Generate all the shared platform tests
38+
platform_tests!(MacOSPlatform);
4339

44-
#[test]
45-
#[serial] // PF rules are global state, must run sequentially
46-
fn test_jail_log_only_mode() {
47-
system_integration::test_jail_log_only_mode::<MacOSPlatform>();
48-
}
49-
50-
#[test]
51-
#[serial] // PF rules are global state, must run sequentially
52-
fn test_jail_dry_run_mode() {
53-
system_integration::test_jail_dry_run_mode::<MacOSPlatform>();
54-
}
55-
56-
#[test]
57-
fn test_jail_requires_command() {
58-
system_integration::test_jail_requires_command::<MacOSPlatform>();
59-
}
60-
61-
#[test]
62-
#[serial] // PF rules are global state, must run sequentially
63-
fn test_jail_exit_code_propagation() {
64-
system_integration::test_jail_exit_code_propagation::<MacOSPlatform>();
65-
}
66-
67-
#[test]
68-
#[serial] // PF rules are global state, must run sequentially
69-
fn test_native_jail_blocks_https() {
70-
system_integration::test_native_jail_blocks_https::<MacOSPlatform>();
71-
}
72-
73-
#[test]
74-
#[serial] // PF rules are global state, must run sequentially
75-
fn test_native_jail_allows_https() {
76-
system_integration::test_native_jail_allows_https::<MacOSPlatform>();
77-
}
78-
79-
#[test]
80-
#[serial] // PF rules are global state, must run sequentially
81-
fn test_jail_https_connect_denied() {
82-
system_integration::test_jail_https_connect_denied::<MacOSPlatform>();
83-
}
84-
85-
#[test]
86-
#[serial] // PF rules are global state, must run sequentially
87-
fn test_jail_https_connect_allowed() {
88-
system_integration::test_jail_https_connect_allowed::<MacOSPlatform>();
89-
}
40+
// macOS-specific tests can be added here if needed
9041
}

0 commit comments

Comments
 (0)