Skip to main content

lib_unknown/
dyntest.rs

1//! 动态编译运行 harness(仅开发期使用):将代码片段生成临时 Cargo 工程并编译执行,支持超时、批量与缓存(`DnyRun` / `BatchRunner`)。
2//!
3//! 需要同时启用 `"dyntest"` 与 `"std"` 特性。传入代码将被编译执行,**仅可在隔离沙箱中使用**。
4#![allow(unused)]
5use std::collections::HashMap;
6use std::fmt;
7use std::fs;
8use std::io::{Read, Write};
9use std::path::PathBuf;
10use std::process::{Command, Output, Stdio};
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::thread;
14use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
15
16use crate::rand;
17
18/// 动态测试运行的系统级错误(区别于被测程序自身的失败)。
19///
20/// # Feature Requirement
21///
22/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
23#[derive(Debug, Clone, PartialEq)]
24pub enum SystemError {
25    /// 无系统级错误;`ok == false` 时表示被测程序自身返回非零退出码。
26    None,
27    /// 执行超时并已被终止,载荷为超时阈值。
28    Timeout(Duration),
29    /// `cargo build` 失败。
30    CompileFailed,
31    /// 子进程无法启动,载荷为系统错误描述。
32    ProcessSpawnFailed(String),
33}
34
35/// 单次动态测试的结果,`Display` 输出彩色摘要(超长输出自动截断)。
36///
37/// # Feature Requirement
38///
39/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
40///
41/// # Examples
42///
43/// ```rust,no_run
44/// use lib_unknown::dyntest::{BatchRunner, DnyTask};
45///
46/// let task = DnyTask::new("hello", r#"fn main() { println!("hi"); }"#);
47/// let results = BatchRunner::new().run([task]);
48/// assert_eq!(results.len(), 1);
49/// ```
50#[derive(Debug, Clone)]
51pub struct DnyResult {
52    /// 被测程序标准输出全文。
53    pub stdout: String,
54    /// 被测程序标准错误全文(已过滤 `Compiling` / `Finished` 行)。
55    pub stderr: String,
56    /// 被测程序退出码;系统级失败时为 `-2`,默认构造时为 `-1`。
57    pub exit_code: i32,
58    /// 编译耗时。
59    pub build_duration: Duration,
60    /// 运行耗时。
61    pub run_duration: Duration,
62    /// 被测程序是否以退出码 0 正常结束。
63    pub ok: bool,
64    /// 系统级错误,无则为 [`SystemError::None`]。
65    pub system_err: SystemError,
66}
67
68impl Default for DnyResult {
69    fn default() -> Self {
70        Self {
71            stdout: String::new(),
72            stderr: String::new(),
73            exit_code: -1,
74            build_duration: Duration::ZERO,
75            run_duration: Duration::ZERO,
76            ok: false,
77            system_err: SystemError::None,
78        }
79    }
80}
81
82fn truncate_output(text: &str, max_lines: usize) -> String {
83    const MAX_LINE_CHARS: usize = 300;
84
85    let text = text.trim();
86    if text.is_empty() {
87        return String::new();
88    }
89
90    let truncate_line = |line: &str| -> String {
91        if line.len() <= MAX_LINE_CHARS {
92            return line.to_string();
93        }
94
95        let mut chars = line.chars();
96        let head: String = chars.by_ref().take(MAX_LINE_CHARS / 2).collect();
97
98        if chars.next().is_none() {
99            return head;
100        }
101
102        let tail: String = line
103            .chars()
104            .rev()
105            .take(MAX_LINE_CHARS / 2)
106            .collect::<Vec<char>>()
107            .into_iter()
108            .rev()
109            .collect();
110
111        format!("{} ... [单行超长截断] ... {}", head, tail)
112    };
113
114    let lines: Vec<&str> = text.lines().collect();
115
116    if lines.len() <= max_lines {
117        return lines
118            .into_iter()
119            .map(truncate_line)
120            .collect::<Vec<_>>()
121            .join("\n");
122    }
123
124    let half = max_lines / 2;
125    let head = lines[..half]
126        .iter()
127        .copied()
128        .map(truncate_line)
129        .collect::<Vec<_>>()
130        .join("\n");
131    let tail = lines[lines.len() - half..]
132        .iter()
133        .copied()
134        .map(truncate_line)
135        .collect::<Vec<_>>()
136        .join("\n");
137
138    format!(
139        "{}\n\n... [已截断 {} 行] ...\n\n{}",
140        head,
141        lines.len() - max_lines,
142        tail
143    )
144}
145
146impl fmt::Display for DnyResult {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        let green = "\x1b[32m";
149        let red = "\x1b[31m";
150        let yellow = "\x1b[33m";
151        let cyan = "\x1b[36m";
152        let reset = "\x1b[0m";
153
154        let (status_color, status_text) = match &self.system_err {
155            SystemError::None if self.ok => (green, "SUCCESS"),
156            SystemError::None => (red, "FAILED (Runtime Error)"),
157            SystemError::Timeout(_) => (yellow, "TIMEOUT (TLE)"),
158            SystemError::CompileFailed => (red, "COMPILE ERROR (CE)"),
159            SystemError::ProcessSpawnFailed(_) => (red, "SYSTEM ERROR"),
160        };
161
162        writeln!(f, "{status_color}=== [Result: {}] ==={reset}", status_text)?;
163        writeln!(
164            f,
165            "> Build: {cyan}{:?}{reset} | Run: {cyan}{:?}{reset}",
166            self.build_duration, self.run_duration
167        )?;
168        writeln!(f, "> Exit Code: {status_color}{}{reset}", self.exit_code)?;
169
170        let stdout_trimmed = self.stdout.trim();
171        if !stdout_trimmed.is_empty() {
172            writeln!(
173                f,
174                "{}--- Stdout (size: {} bytes) ---{}",
175                yellow,
176                self.stdout.len(),
177                reset
178            )?;
179            writeln!(f, "{}", truncate_output(stdout_trimmed, 20))?;
180        }
181
182        let clean_stderr = self
183            .stderr
184            .lines()
185            .filter(|l| !l.trim().starts_with("Compiling "))
186            .filter(|l| !l.trim().starts_with("Finished "))
187            .collect::<Vec<_>>()
188            .join("\n");
189
190        let stderr_trimmed = clean_stderr.trim();
191        if !stderr_trimmed.is_empty() {
192            writeln!(
193                f,
194                "{}--- Stderr (size: {} bytes) ---{}",
195                red,
196                clean_stderr.len(),
197                reset
198            )?;
199            writeln!(f, "{}", truncate_output(stderr_trimmed, 40))?;
200        }
201
202        writeln!(f, "{status_color}=========================={reset}")?;
203        Ok(())
204    }
205}
206
207/// 单个动态测试任务:`main.rs` 内容、依赖与运行配置的 Builder。
208///
209/// # Feature Requirement
210///
211/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
212///
213/// # Examples
214///
215/// ```rust
216/// use lib_unknown::dyntest::DnyTask;
217///
218/// let task = DnyTask::new("hello", r#"fn main() { println!("hi"); }"#)
219///     .with_deps(r#"serde = "1""#)
220///     .with_env("RUST_BACKTRACE", "1")
221///     .with_release(false);
222/// assert_eq!(task.tag, "hello");
223/// ```
224#[derive(Clone)]
225pub struct DnyTask {
226    /// 任务标识,用于结果回调用例区分;`From` 元组缺省时为 `"unnamed_task"`。
227    pub tag: String,
228    /// 待编译运行的 `main.rs` 源码。
229    pub main_code: String,
230    /// 追加到临时工程 `[dependencies]` 的依赖声明片段。
231    pub deps: String,
232    /// 注入子进程的环境变量。
233    pub envs: HashMap<String, String>,
234    /// 写入临时工程 `.cargo/config.toml` 的配置,无则为 `None`。
235    pub cargo_config: Option<String>,
236    /// 是否以 `--release` 编译运行。
237    pub is_release: bool,
238}
239
240impl DnyTask {
241    /// 以标识与 `main.rs` 源码创建任务,其余配置取默认值。
242    ///
243    /// # Feature Requirement
244    ///
245    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
246    ///
247    /// # Examples
248    ///
249    /// ```rust
250    /// use lib_unknown::dyntest::DnyTask;
251    ///
252    /// let task = DnyTask::new("t", "fn main() {}");
253    /// assert_eq!(task.tag, "t");
254    /// ```
255    pub fn new(tag: impl Into<String>, code: impl Into<String>) -> Self {
256        Self {
257            tag: tag.into(),
258            main_code: code.into(),
259            deps: String::new(),
260            envs: HashMap::new(),
261            cargo_config: None,
262            is_release: false,
263        }
264    }
265    /// 设置 `[dependencies]` 依赖声明片段。
266    ///
267    /// # Feature Requirement
268    ///
269    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
270    pub fn with_deps(mut self, deps: impl Into<String>) -> Self {
271        self.deps = deps.into();
272        self
273    }
274    /// 注入单个环境变量。
275    ///
276    /// # Feature Requirement
277    ///
278    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
279    pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
280        self.envs.insert(key.into(), value.into());
281        self
282    }
283    /// 批量注入环境变量。
284    ///
285    /// # Feature Requirement
286    ///
287    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
288    pub fn with_envs<I, K, V>(mut self, envs: I) -> Self
289    where
290        I: IntoIterator<Item = (K, V)>,
291        K: Into<String>,
292        V: Into<String>,
293    {
294        for (k, v) in envs {
295            self.envs.insert(k.into(), v.into());
296        }
297        self
298    }
299    /// 设置写入临时工程 `.cargo/config.toml` 的配置。
300    ///
301    /// # Feature Requirement
302    ///
303    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
304    pub fn with_cargo_config(mut self, config: impl Into<String>) -> Self {
305        self.cargo_config = Some(config.into());
306        self
307    }
308    /// 开启或关闭 release 模式。
309    ///
310    /// # Feature Requirement
311    ///
312    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
313    pub fn with_release(mut self, release: bool) -> Self {
314        self.is_release = release;
315        self
316    }
317}
318
319impl From<(String, String, Option<String>)> for DnyTask {
320    fn from(tuple: (String, String, Option<String>)) -> Self {
321        Self {
322            main_code: tuple.0,
323            deps: tuple.1,
324            tag: tuple.2.unwrap_or_else(|| "unnamed_task".to_string()),
325            envs: HashMap::new(),
326            cargo_config: None,
327            is_release: false, // [新增]
328        }
329    }
330}
331
332/// 批量运行器:串行执行多个 [`DnyTask`],可选超时、仅编译、依赖缓存与逐任务回调。
333///
334/// # Feature Requirement
335///
336/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
337///
338/// # Examples
339///
340/// ```rust,no_run
341/// use lib_unknown::dyntest::{BatchRunner, DnyTask};
342///
343/// let tasks = [DnyTask::new("a", "fn main() {}"), DnyTask::new("b", "fn main() {}")];
344/// let results = BatchRunner::new().only_build(true).run(tasks);
345/// assert_eq!(results.len(), 2);
346/// ```
347pub struct BatchRunner {
348    timeout: Option<Duration>,
349    only_build: bool,
350    use_cache: bool,
351    on_result: Option<Box<dyn FnMut(String, DnyResult)>>,
352}
353
354impl Default for BatchRunner {
355    fn default() -> Self {
356        Self::new()
357    }
358}
359
360impl BatchRunner {
361    /// 以默认配置(无超时、编译并运行、不缓存、无回调)创建运行器。
362    ///
363    /// # Feature Requirement
364    ///
365    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
366    pub fn new() -> Self {
367        Self {
368            timeout: None,
369            only_build: false,
370            use_cache: false,
371            on_result: None,
372        }
373    }
374
375    /// 设置单个任务编译 + 运行的总超时,超时后子进程被终止,结果记为 [`SystemError::Timeout`]。
376    ///
377    /// # Feature Requirement
378    ///
379    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
380    pub fn timeout(mut self, d: Duration) -> Self {
381        self.timeout = Some(d);
382        self
383    }
384    /// 仅编译不运行。
385    ///
386    /// # Feature Requirement
387    ///
388    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
389    pub fn only_build(mut self, b: bool) -> Self {
390        self.only_build = b;
391        self
392    }
393    /// 启用首任务依赖预热缓存(先空跑一次编译,后续任务复用编译缓存)。
394    ///
395    /// # Feature Requirement
396    ///
397    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
398    pub fn use_cache(mut self, c: bool) -> Self {
399        self.use_cache = c;
400        self
401    }
402    /// 设置每个任务完成后的回调(参数为任务标识与结果)。
403    ///
404    /// # Feature Requirement
405    ///
406    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
407    pub fn on_result<F>(mut self, f: F) -> Self
408    where
409        F: FnMut(String, DnyResult) + 'static,
410    {
411        self.on_result = Some(Box::new(f));
412        self
413    }
414
415    pub(crate) fn with_boxed_callback(
416        mut self,
417        cb: Option<Box<dyn FnMut(String, DnyResult)>>,
418    ) -> Self {
419        self.on_result = cb;
420        self
421    }
422
423    /// 串行执行全部任务,返回按输入顺序的 `(标识, 结果)` 列表;空输入直接返回空列表。
424    ///
425    /// # Feature Requirement
426    ///
427    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
428    ///
429    /// # Examples
430    ///
431    /// ```rust,no_run
432    /// use lib_unknown::dyntest::{BatchRunner, DnyTask};
433    ///
434    /// let results = BatchRunner::new().run([DnyTask::new("a", "fn main() {}")]);
435    /// assert!(results[0].1.ok);
436    /// ```
437    ///
438    /// # Panics
439    ///
440    /// - 任务工程目录创建/文件写入失败(磁盘不可写、权限不足)时 panic,源于内部 `DnyRun` 初始化。
441    pub fn run<I, T>(mut self, tasks: I) -> Vec<(String, DnyResult)>
442    where
443        I: IntoIterator<Item = T>,
444        T: Into<DnyTask>,
445    {
446        let tasks: Vec<DnyTask> = tasks.into_iter().map(|t| t.into()).collect();
447
448        if tasks.is_empty() {
449            return vec![];
450        }
451
452        if self.use_cache {
453            self.run_with_cache(tasks)
454        } else {
455            self.run_standard(tasks)
456        }
457    }
458
459    fn run_standard(&mut self, tasks: Vec<DnyTask>) -> Vec<(String, DnyResult)> {
460        let mut results = Vec::with_capacity(tasks.len());
461        for (index, task) in tasks.into_iter().enumerate() {
462            let tag = if task.tag == "unnamed_task" {
463                format!("task_{}", index)
464            } else {
465                task.tag
466            };
467
468            let mut runner = DnyRun::new(&task.main_code, &task.deps);
469            runner.envs(task.envs.clone());
470            runner.release(task.is_release);
471
472            if let Some(config) = task.cargo_config {
473                runner.cargo_config(config);
474            }
475
476            let res = if self.only_build {
477                runner.build(self.timeout)
478            } else {
479                runner.run(self.timeout)
480            };
481
482            if let Some(ref mut callback) = self.on_result {
483                callback(tag.clone(), res.clone());
484            }
485            results.push((tag, res));
486        }
487        results
488    }
489
490    fn run_with_cache(&mut self, tasks: Vec<DnyTask>) -> Vec<(String, DnyResult)> {
491        let mut results = Vec::with_capacity(tasks.len());
492        let first_deps = &tasks[0].deps;
493
494        let mut runner = DnyRun::new("fn main() {}", first_deps);
495        runner.release(tasks[0].is_release);
496
497        if let Some(config) = &tasks[0].cargo_config {
498            runner.cargo_config(config.clone());
499        }
500
501        let init_res = runner.build(self.timeout);
502        if let Some(ref mut callback) = self.on_result {
503            callback("init_cache".to_string(), init_res.clone());
504        }
505        results.push(("init_cache".to_string(), init_res));
506
507        for (index, task) in tasks.into_iter().enumerate() {
508            let tag = if task.tag == "unnamed_task" {
509                format!("task_{}", index)
510            } else {
511                task.tag
512            };
513
514            runner.reset_deps(&task.deps);
515            runner.reset_main_code(&task.main_code);
516
517            runner.clear_envs();
518            runner.envs(task.envs.clone());
519            runner.release(task.is_release); // [新增]
520
521            if let Some(config) = task.cargo_config {
522                runner.cargo_config(config);
523            } else {
524                runner.clear_cargo_config();
525            }
526
527            let res = if self.only_build {
528                runner.build(self.timeout)
529            } else {
530                runner.run(self.timeout)
531            };
532
533            if let Some(ref mut callback) = self.on_result {
534                callback(tag.clone(), res.clone());
535            }
536            results.push((tag, res));
537        }
538        results
539    }
540}
541
542/// 运行单个动态测试任务的便捷入口(等价于单任务 `BatchRunner`)。
543///
544/// # Feature Requirement
545///
546/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
547///
548/// # Examples
549///
550/// ```rust,no_run
551/// use lib_unknown::dyntest::dny_run;
552///
553/// let res = dny_run("fn main() {}", "", None, true);
554/// assert!(res.ok);
555/// ```
556///
557/// # Panics
558///
559/// - 任务工程目录创建/文件写入失败时 panic,源于内部 `DnyRun` 初始化。
560pub fn dny_run(
561    main_code: &str,
562    deps: &str,
563    timeout: Option<Duration>,
564    only_build: bool,
565) -> DnyResult {
566    let dny_runner = DnyRun::new(main_code, deps);
567    if only_build {
568        dny_runner.build(timeout)
569    } else {
570        dny_runner.run(timeout)
571    }
572}
573
574/// 批量运行的便捷入口(不使用依赖预热缓存)。
575///
576/// # Feature Requirement
577///
578/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
579///
580/// # Examples
581///
582/// ```rust,no_run
583/// use lib_unknown::dyntest::{DnyTask, dny_run_batch};
584///
585/// let results = dny_run_batch([DnyTask::new("a", "fn main() {}")], None, None, None);
586/// assert_eq!(results.len(), 1);
587/// ```
588///
589/// # Panics
590///
591/// - 任务工程目录创建/文件写入失败时 panic,源于内部 `DnyRun` 初始化。
592pub fn dny_run_batch<I, T>(
593    task: I,
594    task_timeout: Option<Duration>,
595    only_build: Option<bool>,
596    on_result: Option<Box<dyn FnMut(String, DnyResult)>>,
597) -> Vec<(String, DnyResult)>
598where
599    I: IntoIterator<Item = T>,
600    T: Into<DnyTask>,
601{
602    let mut runner = BatchRunner::new()
603        .only_build(only_build.unwrap_or(false))
604        .use_cache(false)
605        .with_boxed_callback(on_result);
606
607    if let Some(t) = task_timeout {
608        runner = runner.timeout(t);
609    }
610    runner.run(task)
611}
612
613/// 批量运行的便捷入口(启用首任务依赖预热缓存,首个结果为 `"init_cache"`)。
614///
615/// # Feature Requirement
616///
617/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
618///
619/// # Examples
620///
621/// ```rust,no_run
622/// use lib_unknown::dyntest::{DnyTask, dny_run_batch_use_cache};
623///
624/// let results = dny_run_batch_use_cache([DnyTask::new("a", "fn main() {}")], None, None, None);
625/// assert_eq!(results[0].0, "init_cache");
626/// ```
627///
628/// # Panics
629///
630/// - 任务工程目录创建/文件写入失败时 panic,源于内部 `DnyRun` 初始化。
631pub fn dny_run_batch_use_cache<I, T>(
632    task: I,
633    task_timeout: Option<Duration>,
634    only_build: Option<bool>,
635    on_result: Option<Box<dyn FnMut(String, DnyResult)>>,
636) -> Vec<(String, DnyResult)>
637where
638    I: IntoIterator<Item = T>,
639    T: Into<DnyTask>,
640{
641    let mut runner = BatchRunner::new()
642        .only_build(only_build.unwrap_or(false))
643        .use_cache(true)
644        .with_boxed_callback(on_result);
645
646    if let Some(t) = task_timeout {
647        runner = runner.timeout(t);
648    }
649    runner.run(task)
650}
651
652/// 单个临时 Cargo 工程的句柄:在 `target/dyn_tests/dny_<id>/` 下生成工程并编译执行,`Drop` 时释放心跳锁。
653///
654/// # Feature Requirement
655///
656/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
657pub struct DnyRun {
658    /// 待编译运行的 `main.rs` 源码。
659    pub main_code: String,
660    /// `[dependencies]` 依赖声明片段。
661    pub deps: String,
662    dir: PathBuf,
663    project_name: String,
664    lock: DynTestLock,
665    envs: HashMap<String, String>,
666    cargo_config: Option<String>,
667    /// 是否以 `--release` 编译运行。
668    pub is_release: bool,
669}
670
671impl DnyRun {
672    /// 在 `target/dyn_tests` 下创建以纳秒时间戳 + 随机数命名的临时工程并同步文件。
673    ///
674    /// # Feature Requirement
675    ///
676    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
677    ///
678    /// # Examples
679    ///
680    /// ```rust,no_run
681    /// use lib_unknown::dyntest::DnyRun;
682    ///
683    /// let runner = DnyRun::new("fn main() {}", "");
684    /// let res = runner.build(None);
685    /// assert!(res.ok);
686    /// ```
687    ///
688    /// # Panics
689    ///
690    /// - 系统时钟早于 UNIX 纪元、无法获取当前目录或工程目录/文件创建失败时 panic。
691    pub fn new(main_code: &str, deps: &str) -> Self {
692        let id = format!(
693            "{}_{}",
694            SystemTime::now()
695                .duration_since(UNIX_EPOCH)
696                .unwrap()
697                .as_nanos(),
698            rand::random::<u32>()
699        );
700        let project_name = format!("dyn_test_{}", id);
701
702        let mut dir = std::env::current_dir().unwrap();
703        dir.push("target");
704        dir.push("dyn_tests");
705        dir.push(format!("dny_{}", id));
706
707        let src_dir = dir.join("src");
708        fs::create_dir_all(&src_dir).expect("无法创建共享测试目录");
709
710        let instance = Self {
711            main_code: main_code.to_string(),
712            deps: deps.to_string(),
713            dir,
714            project_name: project_name.clone(),
715            lock: DynTestLock::new(&project_name),
716            envs: HashMap::new(),
717            cargo_config: None,
718            is_release: false,
719        };
720        instance.sync_files();
721        instance
722    }
723
724    /// 设置是否以 `--release` 编译运行。
725    ///
726    /// # Feature Requirement
727    ///
728    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
729    pub fn release(&mut self, release: bool) -> &mut Self {
730        self.is_release = release;
731        self
732    }
733
734    /// 注入单个子进程环境变量。
735    ///
736    /// # Feature Requirement
737    ///
738    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
739    pub fn env(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
740        self.envs.insert(key.into(), value.into());
741        self
742    }
743
744    /// 批量注入子进程环境变量。
745    ///
746    /// # Feature Requirement
747    ///
748    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
749    pub fn envs<I, K, V>(&mut self, envs: I) -> &mut Self
750    where
751        I: IntoIterator<Item = (K, V)>,
752        K: Into<String>,
753        V: Into<String>,
754    {
755        for (k, v) in envs {
756            self.envs.insert(k.into(), v.into());
757        }
758        self
759    }
760
761    /// 清空已注入的环境变量。
762    ///
763    /// # Feature Requirement
764    ///
765    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
766    pub fn clear_envs(&mut self) -> &mut Self {
767        self.envs.clear();
768        self
769    }
770
771    /// 设置 `.cargo/config.toml` 内容并立即同步到工程目录。
772    ///
773    /// # Feature Requirement
774    ///
775    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
776    ///
777    /// # Panics
778    ///
779    /// - `.cargo` 目录或配置文件写入失败时 panic。
780    pub fn cargo_config(&mut self, config: impl Into<String>) -> &mut Self {
781        self.cargo_config = Some(config.into());
782        self.sync_cargo_config();
783        self
784    }
785
786    /// 清除 cargo 配置(内存值置空并删除已写入的配置文件,忽略删除失败)。
787    ///
788    /// # Feature Requirement
789    ///
790    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
791    pub fn clear_cargo_config(&mut self) -> &mut Self {
792        self.cargo_config = None;
793        let config_path = self.dir.join(".cargo").join("config.toml");
794        if config_path.exists() {
795            let _ = fs::remove_file(config_path);
796        }
797        self
798    }
799
800    fn sync_cargo_config(&self) {
801        if let Some(config_content) = &self.cargo_config {
802            let cargo_dir = self.dir.join(".cargo");
803            if !cargo_dir.exists() {
804                fs::create_dir_all(&cargo_dir).expect("无法创建 .cargo 目录");
805            }
806            fs::write(cargo_dir.join("config.toml"), config_content)
807                .expect("写入 .cargo/config.toml 失败");
808        }
809    }
810
811    /// 将当前 `Cargo.toml` / `main.rs` / cargo 配置同步到工程目录。
812    ///
813    /// # Feature Requirement
814    ///
815    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
816    ///
817    /// # Panics
818    ///
819    /// - 文件写入失败(磁盘不可写、权限不足)时 panic。
820    pub fn sync_files(&self) {
821        let cargo_toml = format!(
822            r#"[package]
823name = "{}"
824version = "0.1.0"
825edition = "2021"
826
827[dependencies]
828{}
829"#,
830            self.project_name, self.deps
831        );
832        fs::write(self.dir.join("Cargo.toml"), cargo_toml).expect("写入 Cargo.toml 失败");
833        fs::write(self.dir.join("src").join("main.rs"), &self.main_code)
834            .expect("写入 main.rs 失败");
835
836        self.sync_cargo_config();
837    }
838
839    fn execute_cmd(
840        cmd: &mut Command,
841        timeout: Option<Duration>,
842    ) -> Result<(Output, Duration), SystemError> {
843        let start_time = Instant::now();
844
845        let mut child = cmd
846            .stdout(Stdio::piped())
847            .stderr(Stdio::piped())
848            .spawn()
849            .map_err(|e| SystemError::ProcessSpawnFailed(e.to_string()))?;
850
851        let mut stdout_pipe = child.stdout.take().expect("无法获取 stdout");
852        let mut stderr_pipe = child.stderr.take().expect("无法获取 stderr");
853
854        let stdout_handle = thread::spawn(move || {
855            let mut buf = Vec::new();
856            let _ = stdout_pipe.read_to_end(&mut buf);
857            buf
858        });
859        let stderr_handle = thread::spawn(move || {
860            let mut buf = Vec::new();
861            let _ = stderr_pipe.read_to_end(&mut buf);
862            buf
863        });
864
865        match timeout {
866            None => {
867                let status = child
868                    .wait()
869                    .map_err(|e| SystemError::ProcessSpawnFailed(e.to_string()))?;
870                let stdout = stdout_handle.join().unwrap_or_default();
871                let stderr = stderr_handle.join().unwrap_or_default();
872                Ok((
873                    Output {
874                        status,
875                        stdout,
876                        stderr,
877                    },
878                    start_time.elapsed(),
879                ))
880            }
881            Some(limit) => {
882                let check_interval = Duration::from_millis(10);
883                loop {
884                    if let Ok(Some(status)) = child.try_wait() {
885                        let stdout = stdout_handle.join().unwrap_or_default();
886                        let stderr = stderr_handle.join().unwrap_or_default();
887                        return Ok((
888                            Output {
889                                status,
890                                stdout,
891                                stderr,
892                            },
893                            start_time.elapsed(),
894                        ));
895                    }
896
897                    if start_time.elapsed() >= limit {
898                        let _ = child.kill();
899                        let _ = child.wait();
900                        return Err(SystemError::Timeout(limit));
901                    }
902                    thread::sleep(check_interval);
903                }
904            }
905        }
906    }
907
908    /// 执行 `cargo build`(`is_release` 为真时加 `--release`),超时则终止并记为 [`SystemError::Timeout`];自身不 panic,失败均体现在返回的 [`DnyResult`] 中。
909    ///
910    /// # Feature Requirement
911    ///
912    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
913    ///
914    /// # Examples
915    ///
916    /// ```rust,no_run
917    /// use lib_unknown::dyntest::DnyRun;
918    ///
919    /// let res = DnyRun::new("fn main() {}", "").build(None);
920    /// assert!(res.ok);
921    /// ```
922    pub fn build(&self, timeout: Option<Duration>) -> DnyResult {
923        let mut result = DnyResult::default();
924        let mut cmd = Command::new("cargo");
925        cmd.arg("build").arg("-q").current_dir(&self.dir);
926
927        if self.is_release {
928            cmd.arg("--release");
929        }
930
931        cmd.envs(&self.envs);
932
933        match Self::execute_cmd(&mut cmd, timeout) {
934            Ok((output, duration)) => {
935                result.build_duration = duration;
936                result.stdout = String::from_utf8_lossy(&output.stdout).to_string();
937                result.stderr = String::from_utf8_lossy(&output.stderr).to_string();
938                result.exit_code = output.status.code().unwrap_or(-1);
939                result.ok = output.status.success();
940                if !result.ok {
941                    result.system_err = SystemError::CompileFailed;
942                }
943            }
944            Err(sys_err) => {
945                result.system_err = sys_err;
946                result.exit_code = -2;
947            }
948        }
949        result
950    }
951
952    /// 先编译后直接运行产物;编译失败时直接返回编译结果,不启动进程。
953    ///
954    /// # Feature Requirement
955    ///
956    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
957    ///
958    /// # Examples
959    ///
960    /// ```rust,no_run
961    /// use lib_unknown::dyntest::DnyRun;
962    ///
963    /// let res = DnyRun::new(r#"fn main() { println!("hi"); }"#, "").run(None);
964    /// assert!(res.ok);
965    /// ```
966    pub fn run(&self, timeout: Option<Duration>) -> DnyResult {
967        let build_result = self.build(timeout);
968        if !build_result.ok {
969            return build_result;
970        }
971        self.run_no_build(timeout, Some(build_result))
972    }
973
974    /// 不重新编译,直接运行已有产物(产物缺失时返回 [`SystemError::ProcessSpawnFailed`] 结果)。
975    /// `build_result` 用于透传编译耗时,无则置零。
976    ///
977    /// # Feature Requirement
978    ///
979    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
980    pub fn run_no_build(
981        &self,
982        timeout: Option<Duration>,
983        build_result: Option<DnyResult>,
984    ) -> DnyResult {
985        let mut target_dir = self.dir.join("target");
986
987        if let Some(cfg_str) = &self.cargo_config {
988            let mut in_build_section = false;
989
990            for line in cfg_str.lines() {
991                let line = line.trim();
992
993                if line.is_empty() || line.starts_with('#') {
994                    continue;
995                }
996
997                if line.starts_with('[') {
998                    in_build_section = line == "[build]";
999                    continue;
1000                }
1001
1002                if in_build_section
1003                    && line.starts_with("target")
1004                    && let Some((_, val)) = line.split_once('=')
1005                {
1006                    let val_no_comment = val.split('#').next().unwrap_or(val);
1007                    let target_val = val_no_comment
1008                        .trim()
1009                        .trim_matches(|c| c == '"' || c == '\'');
1010                    target_dir = target_dir.join(target_val);
1011                    break;
1012                }
1013            }
1014        }
1015
1016        let exe_name = if cfg!(windows) {
1017            format!("{}.exe", self.project_name)
1018        } else {
1019            self.project_name.clone()
1020        };
1021
1022        let profile_dir = if self.is_release { "release" } else { "debug" };
1023        let bin_path = target_dir.join(profile_dir).join(&exe_name);
1024
1025        let build_result = build_result.unwrap_or_default();
1026        let mut result = DnyResult {
1027            build_duration: build_result.build_duration,
1028            ..Default::default()
1029        };
1030
1031        if !bin_path.exists() {
1032            result.system_err = SystemError::ProcessSpawnFailed(format!(
1033                "测试可执行文件未找到。\n预期路径: {}\n请检查编译是否真的成功,或者 target/profile 逻辑是否正确。",
1034                bin_path.display()
1035            ));
1036            result.exit_code = -2;
1037            return result;
1038        }
1039
1040        let mut cmd = Command::new(&bin_path);
1041        cmd.envs(&self.envs);
1042
1043        match Self::execute_cmd(&mut cmd, timeout) {
1044            Ok((output, duration)) => {
1045                result.run_duration = duration;
1046                result.stdout = String::from_utf8_lossy(&output.stdout).into_owned();
1047                result.stderr = String::from_utf8_lossy(&output.stderr).into_owned();
1048                result.exit_code = output.status.code().unwrap_or(-1);
1049                result.ok = output.status.success();
1050            }
1051            Err(sys_err) => {
1052                result.system_err = sys_err;
1053                result.exit_code = -2;
1054            }
1055        }
1056
1057        result
1058    }
1059
1060    /// 替换 `main.rs` 内容并同步到工程目录(供缓存复用时复写任务代码)。
1061    ///
1062    /// # Feature Requirement
1063    ///
1064    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
1065    ///
1066    /// # Panics
1067    ///
1068    /// - 文件写入失败时 panic。
1069    pub fn reset_main_code(&mut self, main_code: &str) {
1070        self.main_code = main_code.to_string();
1071        self.sync_files();
1072    }
1073    /// 替换依赖声明并同步到工程目录。
1074    ///
1075    /// # Feature Requirement
1076    ///
1077    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
1078    ///
1079    /// # Panics
1080    ///
1081    /// - 文件写入失败时 panic。
1082    pub fn reset_deps(&mut self, deps: &str) {
1083        self.deps = deps.to_string();
1084        self.sync_files();
1085    }
1086}
1087
1088/// 工程心跳锁:在 `target/dyn_tests/lock/` 下创建锁文件并由后台线程每 500ms 续写,`Drop` 时停线程删文件,用于识别僵尸工程。
1089///
1090/// # Feature Requirement
1091///
1092/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
1093pub struct DynTestLock {
1094    lock_path: PathBuf,
1095    stop_signal: Arc<AtomicBool>,
1096    thread_handle: Option<thread::JoinHandle<()>>,
1097}
1098
1099impl DynTestLock {
1100    /// 创建锁文件并启动心跳线程。
1101    ///
1102    /// # Feature Requirement
1103    ///
1104    /// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
1105    ///
1106    /// # Panics
1107    ///
1108    /// - 无法获取当前目录、锁目录/锁文件创建失败,或心跳线程无法启动时 panic。
1109    pub fn new(id: &str) -> Self {
1110        let mut lock_path = std::env::current_dir().unwrap();
1111        lock_path.push("target");
1112        lock_path.push("dyn_tests");
1113        lock_path.push("lock");
1114
1115        fs::create_dir_all(&lock_path).expect("无法创建锁目录");
1116        let lock_file = lock_path.join(format!("lock_{}", id));
1117        fs::File::create(&lock_file).expect("无法创建锁文件");
1118
1119        let stop_signal = Arc::new(AtomicBool::new(false));
1120        let thread_stop_signal = stop_signal.clone();
1121        let thread_lock_file = lock_file.clone();
1122
1123        let thread_handle = thread::spawn(move || {
1124            let mut counter = 0u64;
1125            let heartbeat_interval = Duration::from_millis(500);
1126            while !thread_stop_signal.load(Ordering::Relaxed) {
1127                if let Ok(mut file) = fs::File::create(&thread_lock_file) {
1128                    let _ = write!(file, "heartbeat: {}", counter);
1129                }
1130                counter += 1;
1131                for _ in 0..10 {
1132                    if thread_stop_signal.load(Ordering::Relaxed) {
1133                        break;
1134                    }
1135                    thread::sleep(heartbeat_interval / 10);
1136                }
1137            }
1138        });
1139
1140        Self {
1141            lock_path: lock_file,
1142            stop_signal,
1143            thread_handle: Some(thread_handle),
1144        }
1145    }
1146}
1147
1148impl Drop for DynTestLock {
1149    fn drop(&mut self) {
1150        self.stop_signal.store(true, Ordering::Relaxed);
1151        if let Some(handle) = self.thread_handle.take() {
1152            let _ = handle.join();
1153        }
1154        if self.lock_path.exists() {
1155            let _ = fs::remove_file(&self.lock_path);
1156        }
1157    }
1158}
1159
1160/// 清理全部动态测试工程:等待活跃锁释放(心跳停滞超 1500ms 的视为僵尸并清除其锁),再删除 `target/dyn_tests` 目录。
1161///
1162/// # Feature Requirement
1163///
1164/// 需要同时启用 `"dyntest"` 与 `"std"` 特性。
1165///
1166/// # Examples
1167///
1168/// ```rust,no_run
1169/// use lib_unknown::dyntest::clear_dny_project;
1170///
1171/// clear_dny_project(None);
1172/// ```
1173///
1174/// # Panics
1175///
1176/// - 无法获取当前目录时 panic;超时仅打印警告,不 panic。
1177pub fn clear_dny_project(timeout: Option<Duration>) {
1178    thread::sleep(Duration::from_secs_f32(0.3));
1179    let base_dir = std::env::current_dir()
1180        .unwrap()
1181        .join("target")
1182        .join("dyn_tests");
1183    let lock_dir = base_dir.join("lock");
1184    let start_time = Instant::now();
1185    let zombie_threshold = Duration::from_millis(1500);
1186
1187    if lock_dir.exists() {
1188        loop {
1189            let mut active_locks = 0;
1190            if let Ok(entries) = fs::read_dir(&lock_dir) {
1191                for entry in entries.flatten() {
1192                    let path = entry.path();
1193                    if !path.is_file() {
1194                        continue;
1195                    }
1196
1197                    let is_zombie = match fs::metadata(&path).and_then(|m| m.modified()) {
1198                        Ok(modified_time) => {
1199                            modified_time.elapsed().unwrap_or(Duration::ZERO) > zombie_threshold
1200                        }
1201                        Err(_) => true,
1202                    };
1203
1204                    if is_zombie {
1205                        let _ = fs::remove_file(&path);
1206                    } else {
1207                        active_locks += 1;
1208                    }
1209                }
1210            }
1211
1212            if active_locks == 0 {
1213                break;
1214            }
1215            if let Some(limit) = timeout
1216                && start_time.elapsed() >= limit
1217            {
1218                eprintln!("警告: 清除动态测试环境超时!");
1219                break;
1220            }
1221            thread::sleep(Duration::from_millis(100));
1222        }
1223    }
1224
1225    if base_dir.exists() {
1226        let _ = fs::remove_dir_all(base_dir);
1227    }
1228}