command-stream 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/rust/src/ansi.rs +194 -0
- package/rust/src/events.rs +305 -0
- package/rust/src/lib.rs +71 -60
- package/rust/src/macros.rs +165 -0
- package/rust/src/pipeline.rs +411 -0
- package/rust/src/quote.rs +161 -0
- package/rust/src/state.rs +333 -0
- package/rust/src/stream.rs +369 -0
- package/rust/src/trace.rs +152 -0
- package/rust/src/utils.rs +53 -158
- package/rust/tests/events.rs +207 -0
- package/rust/tests/macros.rs +77 -0
- package/rust/tests/pipeline.rs +93 -0
- package/rust/tests/state.rs +207 -0
- package/rust/tests/stream.rs +102 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
//! Streaming and async iteration support
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides async streaming capabilities similar to JavaScript's
|
|
4
|
+
//! async iterators and stream handling in `$.stream-utils.mjs`.
|
|
5
|
+
//!
|
|
6
|
+
//! ## Usage
|
|
7
|
+
//!
|
|
8
|
+
//! ```rust,no_run
|
|
9
|
+
//! use command_stream::{StreamingRunner, OutputChunk};
|
|
10
|
+
//!
|
|
11
|
+
//! #[tokio::main]
|
|
12
|
+
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
13
|
+
//! let runner = StreamingRunner::new("yes hello");
|
|
14
|
+
//!
|
|
15
|
+
//! // Stream output as it arrives
|
|
16
|
+
//! let mut stream = runner.stream();
|
|
17
|
+
//! let mut count = 0;
|
|
18
|
+
//! while let Some(chunk) = stream.next().await {
|
|
19
|
+
//! match chunk {
|
|
20
|
+
//! OutputChunk::Stdout(data) => {
|
|
21
|
+
//! print!("{}", String::from_utf8_lossy(&data));
|
|
22
|
+
//! count += 1;
|
|
23
|
+
//! if count >= 5 {
|
|
24
|
+
//! break;
|
|
25
|
+
//! }
|
|
26
|
+
//! }
|
|
27
|
+
//! OutputChunk::Stderr(data) => {
|
|
28
|
+
//! eprint!("{}", String::from_utf8_lossy(&data));
|
|
29
|
+
//! }
|
|
30
|
+
//! OutputChunk::Exit(code) => {
|
|
31
|
+
//! println!("Process exited with code: {}", code);
|
|
32
|
+
//! break;
|
|
33
|
+
//! }
|
|
34
|
+
//! }
|
|
35
|
+
//! }
|
|
36
|
+
//!
|
|
37
|
+
//! Ok(())
|
|
38
|
+
//! }
|
|
39
|
+
//! ```
|
|
40
|
+
|
|
41
|
+
use std::collections::HashMap;
|
|
42
|
+
use std::path::PathBuf;
|
|
43
|
+
use std::process::Stdio;
|
|
44
|
+
use tokio::io::BufReader;
|
|
45
|
+
use tokio::process::Command;
|
|
46
|
+
use tokio::sync::mpsc;
|
|
47
|
+
|
|
48
|
+
use crate::trace::trace_lazy;
|
|
49
|
+
use crate::{CommandResult, Result};
|
|
50
|
+
|
|
51
|
+
/// A chunk of output from a streaming process
|
|
52
|
+
#[derive(Debug, Clone)]
|
|
53
|
+
pub enum OutputChunk {
|
|
54
|
+
/// Stdout data
|
|
55
|
+
Stdout(Vec<u8>),
|
|
56
|
+
/// Stderr data
|
|
57
|
+
Stderr(Vec<u8>),
|
|
58
|
+
/// Process exit code
|
|
59
|
+
Exit(i32),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// A streaming process runner that allows async iteration over output
|
|
63
|
+
pub struct StreamingRunner {
|
|
64
|
+
command: String,
|
|
65
|
+
cwd: Option<PathBuf>,
|
|
66
|
+
env: Option<HashMap<String, String>>,
|
|
67
|
+
stdin_content: Option<String>,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl StreamingRunner {
|
|
71
|
+
/// Create a new streaming runner
|
|
72
|
+
pub fn new(command: impl Into<String>) -> Self {
|
|
73
|
+
StreamingRunner {
|
|
74
|
+
command: command.into(),
|
|
75
|
+
cwd: None,
|
|
76
|
+
env: None,
|
|
77
|
+
stdin_content: None,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Set the working directory
|
|
82
|
+
pub fn cwd(mut self, path: impl Into<PathBuf>) -> Self {
|
|
83
|
+
self.cwd = Some(path.into());
|
|
84
|
+
self
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Set environment variables
|
|
88
|
+
pub fn env(mut self, env: HashMap<String, String>) -> Self {
|
|
89
|
+
self.env = Some(env);
|
|
90
|
+
self
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Set stdin content
|
|
94
|
+
pub fn stdin(mut self, content: impl Into<String>) -> Self {
|
|
95
|
+
self.stdin_content = Some(content.into());
|
|
96
|
+
self
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Start the process and return a stream of output chunks
|
|
100
|
+
pub fn stream(mut self) -> OutputStream {
|
|
101
|
+
let (tx, rx) = mpsc::channel(1024);
|
|
102
|
+
|
|
103
|
+
// Spawn the process handling task
|
|
104
|
+
let command = self.command.clone();
|
|
105
|
+
let cwd = self.cwd.take();
|
|
106
|
+
let env = self.env.take();
|
|
107
|
+
let stdin_content = self.stdin_content.take();
|
|
108
|
+
|
|
109
|
+
tokio::spawn(async move {
|
|
110
|
+
if let Err(e) = run_streaming_process(command, cwd, env, stdin_content, tx.clone()).await {
|
|
111
|
+
trace_lazy("StreamingRunner", || format!("Error: {}", e));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
OutputStream { rx }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Run to completion and collect all output
|
|
119
|
+
pub async fn collect(self) -> Result<CommandResult> {
|
|
120
|
+
let mut stdout = Vec::new();
|
|
121
|
+
let mut stderr = Vec::new();
|
|
122
|
+
let mut exit_code = 0;
|
|
123
|
+
|
|
124
|
+
let mut stream = self.stream();
|
|
125
|
+
while let Some(chunk) = stream.rx.recv().await {
|
|
126
|
+
match chunk {
|
|
127
|
+
OutputChunk::Stdout(data) => stdout.extend(data),
|
|
128
|
+
OutputChunk::Stderr(data) => stderr.extend(data),
|
|
129
|
+
OutputChunk::Exit(code) => exit_code = code,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Ok(CommandResult {
|
|
134
|
+
stdout: String::from_utf8_lossy(&stdout).to_string(),
|
|
135
|
+
stderr: String::from_utf8_lossy(&stderr).to_string(),
|
|
136
|
+
code: exit_code,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Stream of output chunks from a process
|
|
142
|
+
pub struct OutputStream {
|
|
143
|
+
rx: mpsc::Receiver<OutputChunk>,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
impl OutputStream {
|
|
147
|
+
/// Receive the next chunk
|
|
148
|
+
pub async fn next(&mut self) -> Option<OutputChunk> {
|
|
149
|
+
self.rx.recv().await
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Collect all remaining output into vectors
|
|
153
|
+
pub async fn collect(mut self) -> (Vec<u8>, Vec<u8>, i32) {
|
|
154
|
+
let mut stdout = Vec::new();
|
|
155
|
+
let mut stderr = Vec::new();
|
|
156
|
+
let mut exit_code = 0;
|
|
157
|
+
|
|
158
|
+
while let Some(chunk) = self.rx.recv().await {
|
|
159
|
+
match chunk {
|
|
160
|
+
OutputChunk::Stdout(data) => stdout.extend(data),
|
|
161
|
+
OutputChunk::Stderr(data) => stderr.extend(data),
|
|
162
|
+
OutputChunk::Exit(code) => exit_code = code,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
(stdout, stderr, exit_code)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// Collect stdout only, discarding stderr
|
|
170
|
+
pub async fn collect_stdout(mut self) -> Vec<u8> {
|
|
171
|
+
let mut stdout = Vec::new();
|
|
172
|
+
|
|
173
|
+
while let Some(chunk) = self.rx.recv().await {
|
|
174
|
+
if let OutputChunk::Stdout(data) = chunk {
|
|
175
|
+
stdout.extend(data);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
stdout
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// Run a streaming process and send output to the channel
|
|
184
|
+
async fn run_streaming_process(
|
|
185
|
+
command: String,
|
|
186
|
+
cwd: Option<PathBuf>,
|
|
187
|
+
env: Option<HashMap<String, String>>,
|
|
188
|
+
stdin_content: Option<String>,
|
|
189
|
+
tx: mpsc::Sender<OutputChunk>,
|
|
190
|
+
) -> Result<()> {
|
|
191
|
+
trace_lazy("StreamingRunner", || format!("Starting: {}", command));
|
|
192
|
+
|
|
193
|
+
let shell = find_available_shell();
|
|
194
|
+
let mut cmd = Command::new(&shell.cmd);
|
|
195
|
+
for arg in &shell.args {
|
|
196
|
+
cmd.arg(arg);
|
|
197
|
+
}
|
|
198
|
+
cmd.arg(&command);
|
|
199
|
+
|
|
200
|
+
// Configure stdio
|
|
201
|
+
if stdin_content.is_some() {
|
|
202
|
+
cmd.stdin(Stdio::piped());
|
|
203
|
+
} else {
|
|
204
|
+
cmd.stdin(Stdio::null());
|
|
205
|
+
}
|
|
206
|
+
cmd.stdout(Stdio::piped());
|
|
207
|
+
cmd.stderr(Stdio::piped());
|
|
208
|
+
|
|
209
|
+
// Set working directory
|
|
210
|
+
if let Some(ref cwd) = cwd {
|
|
211
|
+
cmd.current_dir(cwd);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Set environment
|
|
215
|
+
if let Some(ref env_vars) = env {
|
|
216
|
+
for (key, value) in env_vars {
|
|
217
|
+
cmd.env(key, value);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Spawn the process
|
|
222
|
+
let mut child = cmd.spawn()?;
|
|
223
|
+
|
|
224
|
+
// Write stdin if needed
|
|
225
|
+
if let Some(content) = stdin_content {
|
|
226
|
+
if let Some(mut stdin) = child.stdin.take() {
|
|
227
|
+
use tokio::io::AsyncWriteExt;
|
|
228
|
+
let _ = stdin.write_all(content.as_bytes()).await;
|
|
229
|
+
let _ = stdin.shutdown().await;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Spawn stdout reader
|
|
234
|
+
let stdout = child.stdout.take();
|
|
235
|
+
let tx_stdout = tx.clone();
|
|
236
|
+
let stdout_handle = if let Some(stdout) = stdout {
|
|
237
|
+
Some(tokio::spawn(async move {
|
|
238
|
+
let mut reader = BufReader::new(stdout);
|
|
239
|
+
let mut buf = vec![0u8; 8192];
|
|
240
|
+
loop {
|
|
241
|
+
use tokio::io::AsyncReadExt;
|
|
242
|
+
match reader.read(&mut buf).await {
|
|
243
|
+
Ok(0) => break,
|
|
244
|
+
Ok(n) => {
|
|
245
|
+
if tx_stdout.send(OutputChunk::Stdout(buf[..n].to_vec())).await.is_err() {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
Err(_) => break,
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}))
|
|
253
|
+
} else {
|
|
254
|
+
None
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Spawn stderr reader
|
|
258
|
+
let stderr = child.stderr.take();
|
|
259
|
+
let tx_stderr = tx.clone();
|
|
260
|
+
let stderr_handle = if let Some(stderr) = stderr {
|
|
261
|
+
Some(tokio::spawn(async move {
|
|
262
|
+
let mut reader = BufReader::new(stderr);
|
|
263
|
+
let mut buf = vec![0u8; 8192];
|
|
264
|
+
loop {
|
|
265
|
+
use tokio::io::AsyncReadExt;
|
|
266
|
+
match reader.read(&mut buf).await {
|
|
267
|
+
Ok(0) => break,
|
|
268
|
+
Ok(n) => {
|
|
269
|
+
if tx_stderr.send(OutputChunk::Stderr(buf[..n].to_vec())).await.is_err() {
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
Err(_) => break,
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}))
|
|
277
|
+
} else {
|
|
278
|
+
None
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Wait for readers to complete
|
|
282
|
+
if let Some(handle) = stdout_handle {
|
|
283
|
+
let _ = handle.await;
|
|
284
|
+
}
|
|
285
|
+
if let Some(handle) = stderr_handle {
|
|
286
|
+
let _ = handle.await;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Wait for process to exit
|
|
290
|
+
let status = child.wait().await?;
|
|
291
|
+
let code = status.code().unwrap_or(-1);
|
|
292
|
+
|
|
293
|
+
// Send exit code
|
|
294
|
+
let _ = tx.send(OutputChunk::Exit(code)).await;
|
|
295
|
+
|
|
296
|
+
trace_lazy("StreamingRunner", || format!("Exited with code: {}", code));
|
|
297
|
+
|
|
298
|
+
Ok(())
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// Shell configuration
|
|
302
|
+
#[derive(Debug, Clone)]
|
|
303
|
+
struct ShellConfig {
|
|
304
|
+
cmd: String,
|
|
305
|
+
args: Vec<String>,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/// Find an available shell
|
|
309
|
+
fn find_available_shell() -> ShellConfig {
|
|
310
|
+
let is_windows = cfg!(windows);
|
|
311
|
+
|
|
312
|
+
if is_windows {
|
|
313
|
+
ShellConfig {
|
|
314
|
+
cmd: "cmd.exe".to_string(),
|
|
315
|
+
args: vec!["/c".to_string()],
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
let shells = [
|
|
319
|
+
("/bin/sh", "-c"),
|
|
320
|
+
("/usr/bin/sh", "-c"),
|
|
321
|
+
("/bin/bash", "-c"),
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
for (cmd, arg) in shells {
|
|
325
|
+
if std::path::Path::new(cmd).exists() {
|
|
326
|
+
return ShellConfig {
|
|
327
|
+
cmd: cmd.to_string(),
|
|
328
|
+
args: vec![arg.to_string()],
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
ShellConfig {
|
|
334
|
+
cmd: "/bin/sh".to_string(),
|
|
335
|
+
args: vec!["-c".to_string()],
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// Async iterator trait for output streams
|
|
341
|
+
#[async_trait::async_trait]
|
|
342
|
+
pub trait AsyncIterator {
|
|
343
|
+
type Item;
|
|
344
|
+
|
|
345
|
+
/// Get the next item from the iterator
|
|
346
|
+
async fn next(&mut self) -> Option<Self::Item>;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
#[async_trait::async_trait]
|
|
350
|
+
impl AsyncIterator for OutputStream {
|
|
351
|
+
type Item = OutputChunk;
|
|
352
|
+
|
|
353
|
+
async fn next(&mut self) -> Option<Self::Item> {
|
|
354
|
+
self.rx.recv().await
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// Extension trait to convert ProcessRunner into a stream
|
|
359
|
+
pub trait IntoStream {
|
|
360
|
+
/// Convert into an output stream
|
|
361
|
+
fn into_stream(self) -> OutputStream;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
impl IntoStream for crate::ProcessRunner {
|
|
365
|
+
fn into_stream(self) -> OutputStream {
|
|
366
|
+
let streaming = StreamingRunner::new(self.command().to_string());
|
|
367
|
+
streaming.stream()
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//! Trace/logging utilities for command-stream
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides verbose logging functionality that can be controlled
|
|
4
|
+
//! via environment variables for debugging and development purposes.
|
|
5
|
+
|
|
6
|
+
use std::env;
|
|
7
|
+
|
|
8
|
+
/// Check if tracing is enabled via environment variables
|
|
9
|
+
///
|
|
10
|
+
/// Tracing can be controlled via:
|
|
11
|
+
/// - COMMAND_STREAM_TRACE=true/false (explicit control)
|
|
12
|
+
/// - COMMAND_STREAM_VERBOSE=true (enables tracing unless TRACE=false)
|
|
13
|
+
pub fn is_trace_enabled() -> bool {
|
|
14
|
+
let trace_env = env::var("COMMAND_STREAM_TRACE").ok();
|
|
15
|
+
let verbose_env = env::var("COMMAND_STREAM_VERBOSE")
|
|
16
|
+
.map(|v| v == "true")
|
|
17
|
+
.unwrap_or(false);
|
|
18
|
+
|
|
19
|
+
match trace_env.as_deref() {
|
|
20
|
+
Some("false") => false,
|
|
21
|
+
Some("true") => true,
|
|
22
|
+
_ => verbose_env,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Trace function for verbose logging
|
|
27
|
+
///
|
|
28
|
+
/// Outputs trace messages to stderr when tracing is enabled.
|
|
29
|
+
/// Messages are prefixed with timestamp and category.
|
|
30
|
+
///
|
|
31
|
+
/// # Examples
|
|
32
|
+
///
|
|
33
|
+
/// ```
|
|
34
|
+
/// use command_stream::trace::trace;
|
|
35
|
+
///
|
|
36
|
+
/// trace("ProcessRunner", "Starting command execution");
|
|
37
|
+
/// ```
|
|
38
|
+
pub fn trace(category: &str, message: &str) {
|
|
39
|
+
if !is_trace_enabled() {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let timestamp = chrono::Utc::now().to_rfc3339();
|
|
44
|
+
eprintln!("[TRACE {}] [{}] {}", timestamp, category, message);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Trace function with lazy message evaluation
|
|
48
|
+
///
|
|
49
|
+
/// Only evaluates the message function if tracing is enabled.
|
|
50
|
+
/// This is useful for expensive message formatting that should
|
|
51
|
+
/// be avoided when tracing is disabled.
|
|
52
|
+
///
|
|
53
|
+
/// # Examples
|
|
54
|
+
///
|
|
55
|
+
/// ```
|
|
56
|
+
/// use command_stream::trace::trace_lazy;
|
|
57
|
+
///
|
|
58
|
+
/// trace_lazy("ProcessRunner", || {
|
|
59
|
+
/// format!("Expensive computation result: {}", 42)
|
|
60
|
+
/// });
|
|
61
|
+
/// ```
|
|
62
|
+
pub fn trace_lazy<F>(category: &str, message_fn: F)
|
|
63
|
+
where
|
|
64
|
+
F: FnOnce() -> String,
|
|
65
|
+
{
|
|
66
|
+
if !is_trace_enabled() {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
trace(category, &message_fn());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#[cfg(test)]
|
|
74
|
+
mod tests {
|
|
75
|
+
use super::*;
|
|
76
|
+
use std::env;
|
|
77
|
+
use std::sync::Mutex;
|
|
78
|
+
|
|
79
|
+
// Use a mutex to serialize tests that modify environment variables
|
|
80
|
+
// This prevents race conditions when tests run in parallel
|
|
81
|
+
static ENV_MUTEX: Mutex<()> = Mutex::new(());
|
|
82
|
+
|
|
83
|
+
/// Helper to save and restore environment variables during tests
|
|
84
|
+
struct EnvGuard {
|
|
85
|
+
trace_value: Option<String>,
|
|
86
|
+
verbose_value: Option<String>,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl EnvGuard {
|
|
90
|
+
fn new() -> Self {
|
|
91
|
+
EnvGuard {
|
|
92
|
+
trace_value: env::var("COMMAND_STREAM_TRACE").ok(),
|
|
93
|
+
verbose_value: env::var("COMMAND_STREAM_VERBOSE").ok(),
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl Drop for EnvGuard {
|
|
99
|
+
fn drop(&mut self) {
|
|
100
|
+
// Restore original values
|
|
101
|
+
match &self.trace_value {
|
|
102
|
+
Some(v) => env::set_var("COMMAND_STREAM_TRACE", v),
|
|
103
|
+
None => env::remove_var("COMMAND_STREAM_TRACE"),
|
|
104
|
+
}
|
|
105
|
+
match &self.verbose_value {
|
|
106
|
+
Some(v) => env::set_var("COMMAND_STREAM_VERBOSE", v),
|
|
107
|
+
None => env::remove_var("COMMAND_STREAM_VERBOSE"),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[test]
|
|
113
|
+
fn test_trace_disabled_by_default() {
|
|
114
|
+
let _lock = ENV_MUTEX.lock().unwrap();
|
|
115
|
+
let _guard = EnvGuard::new();
|
|
116
|
+
|
|
117
|
+
// Clear env vars to test default behavior
|
|
118
|
+
env::remove_var("COMMAND_STREAM_TRACE");
|
|
119
|
+
env::remove_var("COMMAND_STREAM_VERBOSE");
|
|
120
|
+
assert!(!is_trace_enabled());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[test]
|
|
124
|
+
fn test_trace_enabled_by_verbose() {
|
|
125
|
+
let _lock = ENV_MUTEX.lock().unwrap();
|
|
126
|
+
let _guard = EnvGuard::new();
|
|
127
|
+
|
|
128
|
+
env::remove_var("COMMAND_STREAM_TRACE");
|
|
129
|
+
env::set_var("COMMAND_STREAM_VERBOSE", "true");
|
|
130
|
+
assert!(is_trace_enabled());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn test_trace_explicit_true() {
|
|
135
|
+
let _lock = ENV_MUTEX.lock().unwrap();
|
|
136
|
+
let _guard = EnvGuard::new();
|
|
137
|
+
|
|
138
|
+
env::remove_var("COMMAND_STREAM_VERBOSE");
|
|
139
|
+
env::set_var("COMMAND_STREAM_TRACE", "true");
|
|
140
|
+
assert!(is_trace_enabled());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn test_trace_explicit_false_overrides_verbose() {
|
|
145
|
+
let _lock = ENV_MUTEX.lock().unwrap();
|
|
146
|
+
let _guard = EnvGuard::new();
|
|
147
|
+
|
|
148
|
+
env::set_var("COMMAND_STREAM_TRACE", "false");
|
|
149
|
+
env::set_var("COMMAND_STREAM_VERBOSE", "true");
|
|
150
|
+
assert!(!is_trace_enabled());
|
|
151
|
+
}
|
|
152
|
+
}
|