parabuild/
parabuilder.rs

1use crate::cuda_utils::get_cuda_mig_device_uuids;
2use crate::filesystem_utils::{
3    copy_dir, copy_dir_with_ignore, copy_dir_with_rsync, is_command_installed,
4    wait_until_file_ready,
5};
6use crate::handlebars_helper::*;
7use chrono::Local;
8use crossbeam_channel::{unbounded, Receiver, Sender};
9use handlebars::Handlebars;
10use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
11use serde_json::{json, Value as JsonValue};
12use std::borrow::Cow;
13use std::collections::HashSet;
14use std::env;
15use std::error::Error;
16use std::io::Write;
17use std::path::{Path, PathBuf};
18use std::process::Command;
19use std::sync::OnceLock;
20use std::sync::{
21    atomic::{AtomicBool, Ordering},
22    Arc,
23};
24use std::thread::JoinHandle;
25use std::time::{Duration, Instant};
26use tempfile::tempdir;
27use uuid::Uuid;
28
29/// Method you want to when there is a compilation error
30#[derive(PartialEq, Copy, Clone)]
31pub enum CompliationErrorHandlingMethod {
32    /// Just ignore this data
33    Ignore,
34    /// Collect this data into `compile_error_datas` returned by `run()`
35    Collect,
36    /// Panic when there is a compilation error
37    Panic,
38}
39
40/// Method you want to run the your `run_bash_script`
41#[derive(PartialEq, Copy, Clone)]
42pub enum RunMethod {
43    /// Just compile, do not run
44    No,
45    /// Compile and run in the same thread/workspace
46    InPlace,
47    /// Compile and run in different threads/workspaces, `usize` is the number of threads to run
48    OutOfPlace(usize),
49    /// After compile, run in a `usize` thread/workspace
50    Exclusive(usize),
51}
52
53static CUDA_DEVICE_UUIDS: OnceLock<Vec<String>> = OnceLock::new();
54
55fn get_cuda_device_uuid(id: usize) -> Option<String> {
56    let cuda_device_uuids = CUDA_DEVICE_UUIDS.get_or_init(|| get_cuda_mig_device_uuids());
57    if id < cuda_device_uuids.len() {
58        Some(cuda_device_uuids[id].clone())
59    } else {
60        None
61    }
62}
63
64/// The main body of building system
65pub struct Parabuilder {
66    project_path: PathBuf,
67    workspaces_path: PathBuf,
68    template_file: PathBuf,
69    target_files: Vec<PathBuf>,
70    target_files_base: Vec<String>,
71    init_bash_script: String,
72    compile_bash_script: String,
73    run_bash_script: String,
74    build_workers: usize,
75    run_method: RunMethod,
76    temp_target_path_dir: PathBuf,
77    run_func_data: RunFunc,
78    data_queue_receiver: Option<Receiver<(usize, JsonValue)>>,
79    compilation_error_handling_method: CompliationErrorHandlingMethod,
80    auto_gather_array_data: bool,
81    in_place_template: bool,
82    disable_progress_bar: bool,
83    mpb: MultiProgress,
84    no_cache: bool,
85    without_rsync: bool,
86    enable_cppflags: bool,
87    autosave_interval: u64,
88    autosave_dir: PathBuf,
89    continue_from_start_time: Option<String>,
90}
91
92fn run_func_data_pre_(
93    workspace_path: &PathBuf,
94    run_script: &str,
95    data: &JsonValue,
96    _: &mut JsonValue,
97) -> Result<(bool, JsonValue), Box<dyn Error>> {
98    let workspace_id = workspace_path
99        .file_name()
100        .unwrap()
101        .to_str()
102        .unwrap()
103        .split('_')
104        .last()
105        .unwrap();
106    let mut output = Command::new("bash");
107    output
108        .arg("-c")
109        .arg(run_script)
110        .env("PARABUILD_ID", workspace_id);
111    if let Some(mig_uuid) = get_cuda_device_uuid(workspace_id.parse().unwrap()) {
112        output.env("CUDA_VISIBLE_DEVICES", mig_uuid);
113    }
114    output.current_dir(&workspace_path);
115    let output = output.output().unwrap();
116    let stdout = String::from_utf8(output.stdout).unwrap();
117    let stderr = String::from_utf8(output.stderr).unwrap();
118    let this_data = json! {
119        {
120            "status": match output.status.code() {
121                Some(code) => code,
122                None => -1
123            },
124            "stdout": stdout,
125            "stderr": stderr,
126            "data": data
127        }
128    };
129    Ok((output.status.success(), this_data))
130}
131
132fn run_func_data_post_(
133    this_data: JsonValue,
134    run_data: &mut JsonValue,
135) -> Result<JsonValue, Box<dyn Error>> {
136    if run_data.is_null() {
137        *run_data = JsonValue::Array(vec![this_data.clone()]);
138    } else {
139        run_data.as_array_mut().unwrap().push(this_data.clone());
140    }
141    Ok(this_data)
142}
143
144fn run_func_data_panic_on_error(
145    workspace_path: &PathBuf,
146    run_script: &str,
147    data: &JsonValue,
148    run_data: &mut JsonValue,
149    stop_flag: &Arc<AtomicBool>,
150) -> Result<JsonValue, Box<dyn Error>> {
151    let (success, this_data) = run_func_data_pre_(workspace_path, run_script, data, run_data)?;
152    if !success {
153        Err(format!("stderr: {}", this_data["stderr"]).as_str())?;
154    }
155    if stop_flag.load(Ordering::Relaxed) {
156        Ok(JsonValue::Null)
157    } else {
158        run_func_data_post_(this_data, run_data)
159    }
160}
161
162fn run_func_data_ignore_on_error(
163    workspace_path: &PathBuf,
164    run_script: &str,
165    data: &JsonValue,
166    run_data: &mut JsonValue,
167    stop_flag: &Arc<AtomicBool>,
168) -> Result<JsonValue, Box<dyn Error>> {
169    let (_, this_data) = run_func_data_pre_(workspace_path, run_script, data, run_data)?;
170    if stop_flag.load(Ordering::Relaxed) {
171        Ok(JsonValue::Null)
172    } else {
173        run_func_data_post_(this_data, run_data)
174    }
175}
176
177type RunFunc = fn(
178    &PathBuf,
179    &str,
180    &JsonValue,
181    &mut JsonValue,
182    &Arc<AtomicBool>,
183) -> Result<JsonValue, Box<dyn Error>>;
184
185/// Default run function that panics when there is an error
186pub const PANIC_ON_ERROR_DEFAULT_RUN_FUNC: RunFunc = run_func_data_panic_on_error;
187
188/// Default run function that ignores when there is an error
189pub const IGNORE_ON_ERROR_DEFAULT_RUN_FUNC: RunFunc = run_func_data_ignore_on_error;
190
191impl Parabuilder {
192    pub const TEMP_TARGET_PATH_DIR: &'static str = "targets";
193
194    pub fn new<P, Q, R, S>(
195        project_path: P,
196        workspaces_path: Q,
197        template_file: R,
198        target_files: &[S],
199    ) -> Self
200    where
201        P: AsRef<Path>,
202        Q: AsRef<Path>,
203        R: AsRef<Path>,
204        S: AsRef<Path>,
205    {
206        let project_path = project_path.as_ref().to_path_buf();
207        let workspaces_path = workspaces_path.as_ref().to_path_buf();
208        let template_file = template_file.as_ref().to_path_buf();
209        let target_files: Vec<PathBuf> = target_files
210            .into_iter()
211            .map(|target_file| target_file.as_ref().to_path_buf())
212            .collect();
213        let target_files_base = target_files
214            .iter()
215            .map(|target_file| {
216                target_file
217                    .file_name()
218                    .unwrap()
219                    .to_string_lossy()
220                    .to_string()
221            })
222            .collect();
223
224        let default_run_bash_script = if target_files.len() > 0 {
225            format!(
226                r#"
227                ./{}
228                "#,
229                target_files[0].to_string_lossy()
230            )
231        } else {
232            "".to_string()
233        };
234
235        let temp_target_path_dir = workspaces_path.join(Self::TEMP_TARGET_PATH_DIR);
236        let init_bash_script = r#"
237        cmake -B build -S . -DPARABUILD=ON
238        "#;
239        let compile_bash_script = r#"
240        cmake --build build --target all -- -B
241        "#;
242        let build_workers = 1;
243        Self {
244            project_path,
245            workspaces_path,
246            template_file,
247            target_files,
248            target_files_base,
249            init_bash_script: init_bash_script.to_string(),
250            compile_bash_script: compile_bash_script.to_string(),
251            run_bash_script: default_run_bash_script,
252            build_workers,
253            run_method: RunMethod::Exclusive(1),
254            temp_target_path_dir,
255            run_func_data: IGNORE_ON_ERROR_DEFAULT_RUN_FUNC,
256            data_queue_receiver: None,
257            compilation_error_handling_method: CompliationErrorHandlingMethod::Collect,
258            auto_gather_array_data: true,
259            in_place_template: false,
260            disable_progress_bar: false,
261            mpb: MultiProgress::new(),
262            no_cache: false,
263            without_rsync: false,
264            enable_cppflags: false,
265            autosave_interval: 0,
266            autosave_dir: PathBuf::from(".parabuild/autosave"),
267            continue_from_start_time: None,
268        }
269    }
270
271    pub fn init_bash_script(mut self, init_bash_script: &str) -> Self {
272        self.init_bash_script = init_bash_script.to_string();
273        self
274    }
275
276    pub fn compile_bash_script(mut self, compile_bash_script: &str) -> Self {
277        self.compile_bash_script = compile_bash_script.to_string();
278        self
279    }
280
281    pub fn run_bash_script(mut self, run_bash_script: &str) -> Self {
282        self.run_bash_script = run_bash_script.to_string();
283        self
284    }
285
286    pub fn build_workers(mut self, build_workers: usize) -> Self {
287        self.build_workers = build_workers;
288        self
289    }
290
291    pub fn run_workers(mut self, run_workers: isize) -> Self {
292        if run_workers > 0 {
293            self.run_method = RunMethod::OutOfPlace(run_workers as usize);
294        } else if run_workers == 0 {
295            self.run_method = RunMethod::No;
296        } else if run_workers < 0 {
297            self.run_method = RunMethod::Exclusive(-run_workers as usize);
298        }
299        self
300    }
301
302    pub fn run_workers_exclusive(mut self, run_workers: isize) -> Self {
303        self.run_method = RunMethod::Exclusive(run_workers as usize);
304        self
305    }
306
307    pub fn run_method(mut self, run_method: RunMethod) -> Self {
308        self.run_method = run_method;
309        self
310    }
311
312    pub fn run_func(mut self, run_func: RunFunc) -> Self {
313        self.run_func_data = run_func;
314        self
315    }
316
317    pub fn compilation_error_handling_method(
318        mut self,
319        compilation_error_handling_method: CompliationErrorHandlingMethod,
320    ) -> Self {
321        self.compilation_error_handling_method = compilation_error_handling_method;
322        self
323    }
324
325    pub fn auto_gather_array_data(mut self, auto_gather_array_data: bool) -> Self {
326        self.auto_gather_array_data = auto_gather_array_data;
327        self
328    }
329
330    pub fn in_place_template(mut self, in_place_template: bool) -> Self {
331        self.in_place_template = in_place_template;
332        self
333    }
334
335    pub fn disable_progress_bar(mut self, disable_progress_bar: bool) -> Self {
336        self.disable_progress_bar = disable_progress_bar;
337        self
338    }
339
340    pub fn no_cache(mut self, no_cache: bool) -> Self {
341        self.no_cache = no_cache;
342        self
343    }
344
345    pub fn without_rsync(mut self, without_rsync: bool) -> Self {
346        self.without_rsync = without_rsync;
347        self
348    }
349
350    pub fn enable_cppflags(mut self, enable_cppflags: bool) -> Self {
351        self.enable_cppflags = enable_cppflags;
352        self
353    }
354
355    pub fn autosave_interval(mut self, autosave_interval: u64) -> Self {
356        self.autosave_interval = autosave_interval;
357        self
358    }
359
360    pub fn autosave_dir<S: AsRef<Path>>(mut self, autosave_dir: S) -> Self {
361        self.autosave_dir = autosave_dir.as_ref().to_path_buf();
362        self
363    }
364
365    /// Set datas to be rendered into the template
366    pub fn set_datas(&mut self, datas: Vec<JsonValue>) -> Result<(), Box<dyn Error>> {
367        if self.data_queue_receiver.is_some() {
368            return Err("Data queue receiver is already initialized".into());
369        }
370        let (data_queue_sender, data_queue_receiver) = unbounded();
371        self.data_queue_receiver = Some(data_queue_receiver);
372        for id_data in datas.into_iter().enumerate() {
373            data_queue_sender.send(id_data).unwrap();
374        }
375        Ok(())
376    }
377
378    /// Set datas to be rendered into the template
379    pub fn set_datas_with_processed_data_ids_set(
380        &mut self,
381        datas: Vec<JsonValue>,
382        processed_data_ids_set: HashSet<usize>,
383    ) -> Result<(), Box<dyn Error>> {
384        if self.data_queue_receiver.is_some() {
385            return Err("Data queue receiver is already initialized".into());
386        }
387        let (data_queue_sender, data_queue_receiver) = unbounded();
388        self.data_queue_receiver = Some(data_queue_receiver);
389        for id_data in datas.into_iter().enumerate() {
390            if !processed_data_ids_set.contains(&id_data.0) {
391                data_queue_sender.send(id_data).unwrap();
392            }
393        }
394        Ok(())
395    }
396
397    pub fn get_data_queue_sender(&mut self) -> Result<Sender<(usize, JsonValue)>, Box<dyn Error>> {
398        if self.data_queue_receiver.is_some() {
399            return Err("Data queue receiver is already initialized".into());
400        }
401        let (data_queue_sender, data_queue_receiver) = unbounded();
402        self.data_queue_receiver = Some(data_queue_receiver);
403        Ok(data_queue_sender)
404    }
405
406    /// Initialize workspaces
407    pub fn init_workspace(&self) -> Result<(), Box<dyn Error>> {
408        if !is_command_installed("rsync") {
409            if !self.without_rsync {
410                return Err("rsync is not installed, set `without_rsync` to true to ignore".into());
411            }
412        }
413        let out_of_place_run_workers = match self.run_method {
414            RunMethod::OutOfPlace(run_workers) => run_workers,
415            RunMethod::Exclusive(run_workers) => run_workers,
416            _ => 0,
417        };
418        let workspaces_path = if self.workspaces_path.is_absolute() {
419            self.workspaces_path.clone()
420        } else {
421            env::current_dir().unwrap().join(&self.workspaces_path)
422        };
423        if self.no_cache {
424            if self.workspaces_path.exists() {
425                std::fs::remove_dir_all(&self.workspaces_path).unwrap();
426            }
427        }
428        std::fs::create_dir_all(&workspaces_path).unwrap();
429        let mut project_path = self.project_path.clone();
430        let move_to_temp_dir = workspaces_path
431            .starts_with(std::fs::canonicalize(&self.project_path).unwrap())
432            && self.without_rsync;
433        let mut build_handles = vec![];
434        if move_to_temp_dir {
435            self.add_spinner("copying to temp dir");
436            project_path = tempdir().unwrap().into_path();
437            copy_dir_with_ignore(&self.project_path, &project_path).unwrap();
438        }
439        for (i, destination) in (0..self.build_workers).map(|i| (i, format!("workspace_{}", i))) {
440            let source = project_path.clone();
441            let destination = self.workspaces_path.join(destination);
442            let init_bash_script = self.init_bash_script.clone();
443            let mpb = self.mpb.clone();
444            let disable_progress_bar = self.disable_progress_bar;
445            let without_rsync = self.without_rsync;
446            let handle = std::thread::spawn(move || {
447                let sp = Self::add_spinner2(
448                    disable_progress_bar,
449                    &mpb,
450                    format!("init workspace {}: copying", i),
451                );
452                if move_to_temp_dir {
453                    copy_dir(&source, &destination).unwrap();
454                } else {
455                    if without_rsync {
456                        copy_dir_with_ignore(&source, &destination).unwrap();
457                    } else {
458                        copy_dir_with_rsync(&source, &destination).unwrap();
459                    }
460                }
461                sp.set_message(format!("init workspace {}: init", i));
462                Command::new("bash")
463                    .arg("-c")
464                    .arg(&init_bash_script)
465                    .current_dir(&destination)
466                    .output()
467                    .unwrap();
468            });
469            build_handles.push(handle);
470        }
471        let mut run_handles = vec![];
472        if out_of_place_run_workers > 0 {
473            // only compile to executable when run_workers = 0
474            std::fs::create_dir_all(self.workspaces_path.join(Self::TEMP_TARGET_PATH_DIR)).unwrap();
475            for (i, destination) in
476                (0..out_of_place_run_workers).map(|i| (i, format!("workspace_exe_{}", i)))
477            {
478                let source = project_path.clone();
479                let destination = self.workspaces_path.join(destination);
480                let init_bash_script = self.init_bash_script.clone();
481                // let compile_bash_script = self.compile_bash_script.clone();
482                // let in_place_template = self.in_place_template;
483                let mpb = self.mpb.clone();
484                let disable_progress_bar = self.disable_progress_bar;
485                let without_rsync = self.without_rsync;
486                let handle = std::thread::spawn(move || {
487                    let sp = Self::add_spinner2(
488                        disable_progress_bar,
489                        &mpb,
490                        format!("init workspace_run {}: copying", i),
491                    );
492                    if move_to_temp_dir {
493                        copy_dir(&source, &destination).unwrap();
494                    } else {
495                        if without_rsync {
496                            copy_dir_with_ignore(&source, &destination).unwrap();
497                        } else {
498                            copy_dir_with_rsync(&source, &destination).unwrap();
499                        }
500                    }
501                    sp.set_message(format!("init workspace_run {}: init", i));
502                    match Command::new("bash")
503                        .arg("-c")
504                        .arg(&init_bash_script)
505                        .current_dir(&destination)
506                        .output()
507                    {
508                        Ok(output) => {
509                            if !output.status.success() {
510                                panic!(
511                                    "Init bash script failed in workspace_run_{}: {:?}",
512                                    i, output
513                                );
514                            }
515                        }
516                        Err(e) => {
517                            panic!("Init bash script failed in workspace_run_{}: {:?}", i, e);
518                        }
519                    }
520                    // assert!(output.status.success());
521                    // let output = Command::new("bash")
522                    //     .arg("-c")
523                    //     .arg(&compile_bash_script)
524                    //     .current_dir(&destination)
525                    //     .output()
526                    //     .unwrap();
527                    // assert!(output.status.success());
528                });
529                run_handles.push(handle);
530            }
531        }
532
533        for handle in build_handles {
534            handle.join().unwrap();
535        }
536        for handle in run_handles {
537            handle.join().unwrap();
538        }
539
540        std::fs::create_dir_all(&self.temp_target_path_dir).unwrap();
541
542        Ok(())
543    }
544
545    fn latest_folder<P: AsRef<Path>>(dir: P) -> Option<PathBuf> {
546        if !dir.as_ref().exists() {
547            return None;
548        }
549        let mut latest = None;
550        let mut latest_time = None;
551        for entry in std::fs::read_dir(dir).unwrap() {
552            let entry = entry.unwrap();
553            let path = entry.path();
554            let metadata = entry.metadata().unwrap();
555            if metadata.is_dir() {
556                let time = metadata.modified().unwrap();
557                if latest_time.is_none() || time > latest_time.unwrap() {
558                    latest = Some(path);
559                    latest_time = Some(time);
560                }
561            }
562        }
563        latest
564    }
565
566    /// Load autosave data (run_datas, compile_error_datas, processed_data_ids)
567    pub fn autosave_load(&mut self, start_time: String) -> (JsonValue, Vec<JsonValue>, Vec<usize>) {
568        let autosave_dir = if start_time.is_empty() {
569            let latest = Self::latest_folder(&self.autosave_dir);
570            if latest.is_none() {
571                panic!("No autosave data found, consider running without --continue");
572            }
573            latest.unwrap()
574        } else {
575            self.autosave_dir.join(start_time)
576        };
577        println!("Loading autosave data from {:?}", autosave_dir);
578        self.continue_from_start_time = Some(
579            autosave_dir
580                .file_name()
581                .unwrap()
582                .to_str()
583                .unwrap()
584                .to_string(),
585        );
586        let datas = std::fs::read_dir(&autosave_dir).unwrap().into_iter().fold(
587            (vec![], vec![], vec![]),
588            |(mut run_datas_array, mut compile_error_datas_array, mut processed_data_ids_array),
589             entry| {
590                let entry = entry.unwrap();
591                let path = entry.path();
592                let run_datas_file = path.join("run_datas.json");
593                let compile_error_datas_file = path.join("compile_error_datas.json");
594                let processed_data_ids_file = path.join("processed_data_ids.json");
595                let run_datas: JsonValue =
596                    serde_json::from_reader(std::fs::File::open(&run_datas_file).unwrap()).unwrap();
597                let compile_error_datas: Vec<JsonValue> = serde_json::from_reader(
598                    std::fs::File::open(&compile_error_datas_file).unwrap(),
599                )
600                .unwrap();
601                let processed_data_ids: Vec<usize> =
602                    serde_json::from_reader(std::fs::File::open(&processed_data_ids_file).unwrap())
603                        .unwrap();
604                run_datas_array.push(run_datas);
605                compile_error_datas_array.extend(compile_error_datas);
606                processed_data_ids_array.extend(processed_data_ids);
607                (
608                    run_datas_array,
609                    compile_error_datas_array,
610                    processed_data_ids_array,
611                )
612            },
613        );
614        self.gather_data(datas.0, datas.1, datas.2).unwrap()
615    }
616
617    /// Save autosave data
618    fn autosave_save<P: AsRef<Path>>(
619        autosave_dir: P,
620        start_time: &str,
621        run_datas: &JsonValue,
622        compile_error_datas: &Vec<JsonValue>,
623        processed_data_ids: &Vec<usize>,
624        workspace_id: Uuid,
625    ) {
626        // 包含当前时间的文件名
627        let autosave_dir = autosave_dir
628            .as_ref()
629            .to_path_buf()
630            .join(start_time)
631            .join(workspace_id.to_string());
632        if !autosave_dir.exists() {
633            std::fs::create_dir_all(&autosave_dir).expect("Failed to create autosave dir");
634        }
635        let run_datas_file = autosave_dir.join("run_datas.json");
636        let run_datas_file1 = autosave_dir.join("run_datas.json.1");
637        let compile_error_datas_file = autosave_dir.join("compile_error_datas.json");
638        let compile_error_datas_file1 = autosave_dir.join("compile_error_datas.json.1");
639        let processed_data_ids_file = autosave_dir.join("processed_data_ids.json");
640        let processed_data_ids_file1 = autosave_dir.join("processed_data_ids.json.1");
641        if run_datas_file.exists() {
642            std::fs::rename(&run_datas_file, &run_datas_file1).unwrap();
643        }
644        if compile_error_datas_file.exists() {
645            std::fs::rename(&compile_error_datas_file, &compile_error_datas_file1).unwrap();
646        }
647        if processed_data_ids_file.exists() {
648            std::fs::rename(&processed_data_ids_file, &processed_data_ids_file1).unwrap();
649        }
650        let run_datas_file = std::fs::File::create(&run_datas_file).unwrap();
651        let compile_error_datas_file = std::fs::File::create(&compile_error_datas_file).unwrap();
652        let processed_data_ids_file = std::fs::File::create(&processed_data_ids_file).unwrap();
653        serde_json::to_writer(run_datas_file, &run_datas).unwrap();
654        serde_json::to_writer(compile_error_datas_file, &compile_error_datas).unwrap();
655        serde_json::to_writer(processed_data_ids_file, &processed_data_ids).unwrap();
656    }
657
658    /// run the build system
659    pub fn run(&self) -> Result<(JsonValue, Vec<JsonValue>, Vec<usize>), Box<dyn Error>> {
660        let start_time = if let Some(start_time) = &self.continue_from_start_time {
661            start_time.clone()
662        } else {
663            Local::now().format("%Y-%m-%d_%H-%M-%S").to_string()
664        };
665        println!("Start from: {}", start_time);
666        if !self.data_queue_receiver.is_some() {
667            return Err("Data queue receiver is not initialized".into());
668        }
669        if !is_command_installed("bash") {
670            return Err("bash is not installed".into());
671        }
672        if !is_command_installed("lsof") {
673            return Err("lsof is not installed, which may lead to strange problems that are difficult to reproduce".into());
674        }
675        let mut build_handles = vec![];
676        let mut run_handles = Vec::new();
677        let (executable_queue_sender, executable_queue_receiver) = unbounded();
678        let data_size = self.data_queue_receiver.as_ref().unwrap().len() as u64;
679        let build_pb = self.add_progress_bar("Building", data_size, "All builds done");
680        let run_pb = if !matches!(self.run_method, RunMethod::No) {
681            if matches!(self.run_method, RunMethod::Exclusive(_)) {
682                self.add_progress_bar("Waiting to run (exclusive)", data_size, "All runs done")
683            } else {
684                self.add_progress_bar("Running", data_size, "All runs done")
685            }
686        } else {
687            ProgressBar::hidden()
688        };
689        let stop_flag = Arc::new(AtomicBool::new(false));
690        if !cfg!(test) {
691            ctrlc::set_handler({
692                let stop_flag = Arc::clone(&stop_flag);
693                move || {
694                    println!("Ctrl-C received, stopping...");
695                    stop_flag.store(true, Ordering::Relaxed);
696                }
697            })
698            .expect("Error setting Ctrl-C handler");
699        }
700        build_pb.tick();
701        run_pb.tick();
702        let spawn_build_workers = || {
703            for i in 0..self.build_workers {
704                let workspace_path = self.workspaces_path.join(format!("workspace_{}", i));
705                let build_handle = self.build_worker(
706                    workspace_path,
707                    executable_queue_sender.clone(),
708                    build_pb.clone(),
709                    run_pb.clone(),
710                    Arc::clone(&stop_flag),
711                    start_time.clone(),
712                );
713                build_handles.push(build_handle);
714            }
715            drop(executable_queue_sender);
716        };
717        let spawn_run_workers = || {
718            let run_workers = match self.run_method {
719                RunMethod::OutOfPlace(run_workers) => run_workers,
720                RunMethod::Exclusive(run_workers) => run_workers,
721                _ => 0,
722            };
723            for i in 0..run_workers {
724                let workspace_path = self.workspaces_path.join(format!("workspace_exe_{}", i));
725                let run_handle = self.run_worker(
726                    workspace_path,
727                    executable_queue_receiver.clone(),
728                    run_pb.clone(),
729                    Arc::clone(&stop_flag),
730                    start_time.clone(),
731                );
732                run_handles.push(run_handle);
733            }
734            drop(executable_queue_receiver);
735        };
736        let gather_build_handlers =
737            |build_handles: Vec<JoinHandle<(JsonValue, Vec<JsonValue>, Vec<usize>)>>| {
738                build_handles.into_iter().fold(
739                    (vec![], vec![], vec![]),
740                    |(
741                        mut run_datas_array,
742                        mut compile_error_datas_array,
743                        mut processed_data_ids_array,
744                    ),
745                     handle| {
746                        let (run_datas, compile_error_datas, processed_data_ids) =
747                            handle.join().unwrap();
748                        run_datas_array.push(run_datas);
749                        compile_error_datas_array.extend(compile_error_datas);
750                        processed_data_ids_array.extend(processed_data_ids);
751                        (
752                            run_datas_array,
753                            compile_error_datas_array,
754                            processed_data_ids_array,
755                        )
756                    },
757                )
758            };
759        let gather_run_handlers = |run_handles: Vec<JoinHandle<(JsonValue, Vec<usize>)>>| {
760            run_handles.into_iter().fold(
761                (vec![], vec![]),
762                |(mut run_datas_array, mut processed_data_ids_array), handle| {
763                    let (run_datas, processed_data_ids) = handle.join().unwrap();
764                    run_datas_array.push(run_datas);
765                    processed_data_ids_array.extend(processed_data_ids);
766                    (run_datas_array, processed_data_ids_array)
767                },
768            )
769        };
770        spawn_build_workers();
771        drop(build_pb);
772        match self.run_method {
773            RunMethod::No | RunMethod::InPlace => {
774                let (run_datas, compile_error_datas, processed_data_ids) =
775                    gather_build_handlers(build_handles);
776                self.gather_data(run_datas, compile_error_datas, processed_data_ids)
777            }
778            RunMethod::Exclusive(_) => {
779                let (_, compile_error_datas, mut processed_data_ids) =
780                    gather_build_handlers(build_handles);
781                run_pb.set_message("Running");
782                spawn_run_workers();
783                let (run_datas, run_processed_data_ids) = gather_run_handlers(run_handles);
784                processed_data_ids.extend(run_processed_data_ids);
785                self.gather_data(run_datas, compile_error_datas, processed_data_ids)
786            }
787            RunMethod::OutOfPlace(_) => {
788                spawn_run_workers();
789                let (_, compile_error_datas, mut processed_data_ids) =
790                    gather_build_handlers(build_handles);
791                let (run_datas, run_processed_data_ids) = gather_run_handlers(run_handles);
792                processed_data_ids.extend(run_processed_data_ids);
793                self.gather_data(run_datas, compile_error_datas, processed_data_ids)
794            }
795        }
796    }
797
798    fn build_worker(
799        &self,
800        workspace_path: PathBuf,
801        executable_queue_sender: Sender<(usize, JsonValue)>,
802        build_pb: ProgressBar,
803        run_pb: ProgressBar,
804        stop_flag: Arc<AtomicBool>,
805        start_time: String,
806    ) -> std::thread::JoinHandle<(JsonValue, Vec<JsonValue>, Vec<usize>)> {
807        let template_path = self.project_path.join(&self.template_file);
808        let targets_path: Vec<PathBuf> = self
809            .target_files
810            .iter()
811            .map(|target_file| workspace_path.join(target_file).to_path_buf())
812            .collect();
813        let compile_bash_script = self.compile_bash_script.clone();
814        let template_output_file = if self.in_place_template {
815            self.template_file.clone()
816        } else {
817            self.template_file.with_extension("")
818        };
819        let target_files_base = self.target_files_base.clone();
820        let temp_target_path_dir = self.temp_target_path_dir.clone();
821        let data_queue_receiver = self.data_queue_receiver.as_ref().unwrap().clone();
822        let run_method = self.run_method;
823        let run_func = self.run_func_data;
824        let compilation_error_handling_method = self.compilation_error_handling_method;
825
826        let template_output_path = workspace_path.join(&template_output_file);
827        let mut handlebars = Handlebars::new();
828        if template_path.exists() && template_path.is_file() {
829            handlebars.register_helper("default", Box::new(default_value_helper));
830            handlebars
831                .register_template_string("tpl", std::fs::read_to_string(&template_path).unwrap())
832                .unwrap();
833        }
834        let mut run_data = JsonValue::Null;
835        let mut compile_error_datas = Vec::new();
836        let run_bash_script = self.run_bash_script.clone();
837        let enable_cppflags = self.enable_cppflags;
838        let disable_progress_bar = self.disable_progress_bar;
839        let mpb = self.mpb.clone();
840        let autosave_dir = self.autosave_dir.clone();
841        let autosave_interval = self.autosave_interval;
842        std::thread::spawn(move || {
843            let uuid = Uuid::new_v4();
844            let mut processed_data_ids = Vec::new();
845            let sp = Self::add_spinner2(
846                disable_progress_bar || !matches!(run_method, RunMethod::InPlace),
847                &mpb,
848                serde_json::to_string_pretty(&JsonValue::Null).unwrap(),
849            );
850            let mut autosave_last_time = Instant::now();
851            for (i, data) in data_queue_receiver.iter() {
852                let mut cppflags_val = "-DPARABUILD=ON ".to_string();
853                if enable_cppflags {
854                    /* {"key":value} => -Dkey=value*/
855                    for (key, value) in data.as_object().unwrap().iter() {
856                        cppflags_val.push_str(&format!("-D{}={} ", key, value));
857                    }
858                }
859                if handlebars.has_template("tpl") {
860                    let mut template_output = std::fs::File::create(&template_output_path)
861                        .expect(format!("Failed to create {:?}", template_output_path).as_str());
862                    handlebars
863                        .render_to_write("tpl", &data, &template_output)
864                        .expect(format!("Failed to render {:?}", template_output_path).as_str());
865                    template_output.flush().unwrap();
866                }
867                let mut output = Command::new("bash");
868                let mut output = output
869                    .arg("-c")
870                    .arg(&compile_bash_script)
871                    .current_dir(&workspace_path);
872                if enable_cppflags {
873                    output = output.env("CPPFLAGS", cppflags_val);
874                }
875                let output = output.output();
876                build_pb.inc(1);
877                if output.is_err() || output.is_ok() && !output.as_ref().unwrap().status.success() {
878                    if stop_flag.load(Ordering::Relaxed) {
879                        // current data should be saved, ignore here
880                    } else {
881                        processed_data_ids.push(i);
882                        if compilation_error_handling_method
883                            == CompliationErrorHandlingMethod::Panic
884                        {
885                            if let Ok(output) = output {
886                                panic!(
887                                    "Compilation script failed in data: {:?} with output: {:?}",
888                                    data, output
889                                );
890                            } else {
891                                panic!("Compilation script failed in data: {:?}", data);
892                            }
893                        } else {
894                            if !matches!(run_method, RunMethod::No) {
895                                run_pb.inc(1);
896                            }
897                            match compilation_error_handling_method {
898                                CompliationErrorHandlingMethod::Collect => {
899                                    compile_error_datas.push(data.clone());
900                                    continue;
901                                }
902                                CompliationErrorHandlingMethod::Ignore => {
903                                    continue;
904                                }
905                                CompliationErrorHandlingMethod::Panic => {
906                                    panic!("Compilation script failed in data: {:?}", data);
907                                }
908                            }
909                        }
910                    }
911                }
912                if stop_flag.load(Ordering::Relaxed) {
913                    Self::autosave_save(
914                        &autosave_dir,
915                        &start_time,
916                        &run_data,
917                        &compile_error_datas,
918                        &processed_data_ids,
919                        uuid,
920                    );
921                    break;
922                }
923                match run_method {
924                    RunMethod::InPlace => {
925                        // run
926                        let last_data = run_func(
927                            &std::fs::canonicalize(&workspace_path).unwrap(),
928                            &run_bash_script,
929                            &data,
930                            &mut run_data,
931                            &stop_flag,
932                        )
933                        .unwrap();
934                        sp.set_message(serde_json::to_string_pretty(&last_data).unwrap());
935                        run_pb.inc(1);
936                    }
937                    RunMethod::No | RunMethod::Exclusive(_) | RunMethod::OutOfPlace(_) => {
938                        for (target_path, target_file_base) in
939                            targets_path.iter().zip(target_files_base.iter())
940                        {
941                            let to_target_executable_path_file =
942                                format!("{}_{}", &target_file_base, i);
943                            let to_target_executable_path =
944                                temp_target_path_dir.join(&to_target_executable_path_file);
945                            std::fs::copy(&target_path, &to_target_executable_path).unwrap();
946                        }
947                        match run_method {
948                            RunMethod::No => {
949                                let to_metadata_path =
950                                    temp_target_path_dir.join(format!("data_{}.json", i));
951                                std::fs::write(&to_metadata_path, data.to_string()).unwrap();
952                            }
953                            RunMethod::OutOfPlace(_) | RunMethod::Exclusive(_) => {
954                                executable_queue_sender.send((i, data.clone())).unwrap();
955                            }
956                            _ => panic!("Unexpected run method"),
957                        }
958                    }
959                }
960                if stop_flag.load(Ordering::Relaxed) {
961                    Self::autosave_save(
962                        &autosave_dir,
963                        &start_time,
964                        &run_data,
965                        &compile_error_datas,
966                        &processed_data_ids,
967                        uuid,
968                    );
969                    break;
970                }
971                match run_method {
972                    RunMethod::InPlace | RunMethod::No => {
973                        processed_data_ids.push(i);
974                    }
975                    _ => {}
976                }
977                if autosave_interval > 0
978                    && autosave_last_time.elapsed().as_secs() > autosave_interval
979                {
980                    Self::autosave_save(
981                        &autosave_dir,
982                        &start_time,
983                        &run_data,
984                        &compile_error_datas,
985                        &processed_data_ids,
986                        uuid,
987                    );
988                    autosave_last_time = Instant::now();
989                }
990            }
991            (run_data, compile_error_datas, processed_data_ids)
992        })
993    }
994
995    fn run_worker(
996        &self,
997        workspace_path: PathBuf,
998        executable_queue_receiver: Receiver<(usize, JsonValue)>,
999        run_pb: ProgressBar,
1000        stop_flag: Arc<AtomicBool>,
1001        start_time: String,
1002    ) -> std::thread::JoinHandle<(JsonValue, Vec<usize>)> {
1003        let uuid = Uuid::new_v4();
1004        let targets_path: Vec<PathBuf> = self
1005            .target_files
1006            .iter()
1007            .map(|target_file| workspace_path.join(target_file).to_path_buf())
1008            .collect();
1009        let target_files_base = self.target_files_base.clone();
1010        let run_func = self.run_func_data;
1011        let mut run_data = JsonValue::Null;
1012        let disable_progress_bar = self.disable_progress_bar;
1013        let mpb = self.mpb.clone();
1014        let run_bash_script = self.run_bash_script.clone();
1015        let temp_target_path_dir = self.temp_target_path_dir.clone();
1016        let autosave_dir = self.autosave_dir.clone();
1017        let autosave_interval = self.autosave_interval;
1018        std::thread::spawn(move || {
1019            let mut processed_data_ids = Vec::new();
1020            let mut autosave_last_time = Instant::now();
1021            let sp = Self::add_spinner2(
1022                disable_progress_bar,
1023                &mpb,
1024                serde_json::to_string_pretty(&JsonValue::Null).unwrap(),
1025            );
1026            for (i, data) in executable_queue_receiver.iter() {
1027                for (target_path, target_file_base) in
1028                    targets_path.iter().zip(target_files_base.iter())
1029                {
1030                    let to_target_path_file = format!("{}_{}", &target_file_base, i);
1031                    let to_target_executable_path = temp_target_path_dir.join(&to_target_path_file);
1032                    std::fs::rename(&to_target_executable_path, &target_path).unwrap();
1033                }
1034                for target_path in targets_path.iter() {
1035                    wait_until_file_ready(&target_path).unwrap();
1036                }
1037                let last_data = run_func(
1038                    &std::fs::canonicalize(&workspace_path).unwrap(),
1039                    &run_bash_script,
1040                    &data,
1041                    &mut run_data,
1042                    &stop_flag,
1043                )
1044                .unwrap();
1045                if stop_flag.load(Ordering::Relaxed) {
1046                    Self::autosave_save(
1047                        &autosave_dir,
1048                        &start_time,
1049                        &run_data,
1050                        &vec![],
1051                        &processed_data_ids,
1052                        uuid,
1053                    );
1054                    break;
1055                }
1056                sp.set_message(serde_json::to_string_pretty(&last_data).unwrap());
1057                run_pb.inc(1);
1058                processed_data_ids.push(i);
1059                if autosave_interval > 0
1060                    && autosave_last_time.elapsed().as_secs() > autosave_interval
1061                {
1062                    Self::autosave_save(
1063                        &autosave_dir,
1064                        &start_time,
1065                        &run_data,
1066                        &vec![],
1067                        &processed_data_ids,
1068                        uuid,
1069                    );
1070                    autosave_last_time = Instant::now();
1071                }
1072            }
1073            (run_data, processed_data_ids)
1074        })
1075    }
1076
1077    pub fn gather_data(
1078        &self,
1079        run_data_array: Vec<JsonValue>,
1080        compile_error_datas: Vec<JsonValue>,
1081        processed_data_ids: Vec<usize>,
1082    ) -> Result<(JsonValue, Vec<JsonValue>, Vec<usize>), Box<dyn Error>> {
1083        let run_data_array: Vec<JsonValue> = run_data_array
1084            .into_iter()
1085            .filter(|item| !item.is_null())
1086            .collect();
1087        let run_datas = if self.run_method == RunMethod::No {
1088            JsonValue::Null
1089        } else if self.auto_gather_array_data && run_data_array.iter().all(|item| item.is_array()) {
1090            let mut run_data = Vec::new();
1091            for run_data_item in run_data_array {
1092                run_data.extend(run_data_item.as_array().unwrap().iter().cloned());
1093            }
1094            JsonValue::Array(run_data)
1095        } else {
1096            // just return array json
1097            JsonValue::Array(run_data_array)
1098        };
1099        Ok((run_datas, compile_error_datas, processed_data_ids))
1100    }
1101
1102    fn add_progress_bar<S: Into<String>, F: Into<Cow<'static, str>>>(
1103        &self,
1104        message: S,
1105        total: u64,
1106        finish_message: F,
1107    ) -> ProgressBar {
1108        if self.disable_progress_bar {
1109            return ProgressBar::hidden();
1110        }
1111        let sty = ProgressStyle::with_template(
1112            "[{elapsed_precise}  ETA: {eta_precise}] [{per_sec}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
1113        )
1114        .unwrap();
1115        self.mpb.add(
1116            ProgressBar::new(total)
1117                .with_message(message.into())
1118                .with_style(sty)
1119                .with_finish(ProgressFinish::AbandonWithMessage(finish_message.into())),
1120        )
1121    }
1122
1123    fn add_spinner<S: Into<String>>(&self, message: S) -> ProgressBar {
1124        if self.disable_progress_bar {
1125            return ProgressBar::hidden();
1126        }
1127        let sp = self
1128            .mpb
1129            .add(ProgressBar::new_spinner().with_message(message.into()));
1130        sp.enable_steady_tick(Duration::from_millis(100));
1131        sp
1132    }
1133
1134    fn add_spinner2<S: Into<String>>(
1135        disable_progress_bar: bool,
1136        mpb: &MultiProgress,
1137        message: S,
1138    ) -> ProgressBar {
1139        if disable_progress_bar {
1140            return ProgressBar::hidden();
1141        }
1142        let sp = mpb.add(ProgressBar::new_spinner().with_message(message.into()));
1143        sp.enable_steady_tick(Duration::from_millis(100));
1144        sp
1145    }
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150    use super::*;
1151    // use std::time::Instant;
1152    use serde_json::json;
1153
1154    const EXAMPLE_PROJECT: &str = crate::test_constants::EXAMPLE_CMAKE_PROJECT_PATH;
1155    const EXAMPLE_TEMPLATE_FILE: &str = "src/main.cpp.template";
1156    const EXAMPLE_IN_PLACE_TEMPLATE_FILE: &str = "src/main.cpp";
1157    const EXAMPLE_TARGET_EXECUTABLE_FILE: &str = "build/main";
1158    const EXAMPLE_INIT_BASH_SCRIPT: &str = r#"
1159        cmake -B build -S .
1160        "#;
1161    const EXAMPLE_IN_PLACE_INIT_BASH_SCRIPT: &str = r#"
1162        cmake -B build -S . -DPARABUILD=ON
1163        "#;
1164    const EXAMPLE_COMPILE_BASH_SCRIPT: &str = r#"
1165        cmake --build build --target all -- -B
1166        "#;
1167
1168    #[test]
1169    fn test_workspaces_under_project_path() {
1170        let example_project_path = std::fs::canonicalize(EXAMPLE_PROJECT).unwrap();
1171        let workspaces_path = PathBuf::from("workspaces_under_project_path");
1172        let parabuilder = Parabuilder::new(
1173            ".",
1174            &workspaces_path,
1175            example_project_path.join("src/main.cpp.template"),
1176            &["build/main"],
1177        )
1178        .init_bash_script(
1179            format!(
1180                r#"
1181                cmake -B build -S {}
1182                "#,
1183                EXAMPLE_PROJECT
1184            )
1185            .as_str(),
1186        )
1187        .compile_bash_script(
1188            r#"
1189            cmake --build build --target all -- -B
1190            "#,
1191        )
1192        .disable_progress_bar(true)
1193        .no_cache(true);
1194        parabuilder.init_workspace().unwrap();
1195        assert!(workspaces_path
1196            .join(format!(
1197                "workspace_0/{}/src/main.cpp.template",
1198                EXAMPLE_PROJECT
1199            ))
1200            .exists());
1201        assert!(workspaces_path.join("workspace_0/build").exists());
1202        std::fs::remove_dir_all(workspaces_path).unwrap();
1203    }
1204
1205    const SINGLETHREADED_N: i64 = 20;
1206    const MULTITHREADED_N: i64 = 100;
1207
1208    fn parabuild_tester(
1209        name: &str,
1210        size: i64,
1211        build_workers: usize,
1212        run_method: RunMethod,
1213        in_place_template: bool,
1214        is_makefile_project: bool,
1215    ) {
1216        let project = if !is_makefile_project {
1217            EXAMPLE_PROJECT
1218        } else {
1219            crate::test_constants::EXAMPLE_MAKEFILE_PROJECT_PATH
1220        };
1221        let mut datas = (1..=size)
1222            .map(|i| json!({"N": i}))
1223            .collect::<Vec<JsonValue>>();
1224        if size == 0 {
1225            datas.push(json!({}));
1226        }
1227        let error_data = json!({"N": "a"});
1228        datas.push(error_data.clone());
1229        let workspaces_path = PathBuf::from(format!("tests/workspaces_{}", name));
1230        let mut parabuilder = Parabuilder::new(
1231            project,
1232            &workspaces_path,
1233            if !is_makefile_project {
1234                if in_place_template {
1235                    EXAMPLE_IN_PLACE_TEMPLATE_FILE
1236                } else {
1237                    EXAMPLE_TEMPLATE_FILE
1238                }
1239            } else {
1240                ""
1241            },
1242            if !is_makefile_project {
1243                &[EXAMPLE_TARGET_EXECUTABLE_FILE]
1244            } else {
1245                &["main"]
1246            },
1247        )
1248        .init_bash_script(if !is_makefile_project {
1249            if in_place_template {
1250                EXAMPLE_IN_PLACE_INIT_BASH_SCRIPT
1251            } else {
1252                EXAMPLE_INIT_BASH_SCRIPT
1253            }
1254        } else {
1255            ""
1256        })
1257        .compile_bash_script(if !is_makefile_project {
1258            EXAMPLE_COMPILE_BASH_SCRIPT
1259        } else {
1260            "make -B"
1261        })
1262        .build_workers(build_workers)
1263        .run_method(run_method)
1264        .run_func(PANIC_ON_ERROR_DEFAULT_RUN_FUNC)
1265        .disable_progress_bar(true)
1266        .compilation_error_handling_method(CompliationErrorHandlingMethod::Collect)
1267        .in_place_template(in_place_template)
1268        .auto_gather_array_data(true)
1269        .enable_cppflags(is_makefile_project);
1270
1271        parabuilder.set_datas(datas).unwrap();
1272        parabuilder.init_workspace().unwrap();
1273        let (run_data, compile_error_datas, processed_data_ids) = parabuilder.run().unwrap();
1274        assert!(
1275            compile_error_datas == vec![error_data],
1276            "got: {:?} {:?}",
1277            run_data,
1278            compile_error_datas
1279        );
1280        assert!(
1281            processed_data_ids.len() == if size == 0 { 2 } else { size as usize + 1 }, // default
1282            "got: {:?}",
1283            processed_data_ids
1284        );
1285        if matches!(run_method, RunMethod::No) {
1286            assert!(run_data.is_null(), "got: {}", run_data);
1287            for i in 0..size {
1288                assert!(workspaces_path
1289                    .join(format!("{}/main_{}", Parabuilder::TEMP_TARGET_PATH_DIR, i))
1290                    .exists());
1291                assert!(workspaces_path
1292                    .join(format!(
1293                        "{}/data_{}.json",
1294                        Parabuilder::TEMP_TARGET_PATH_DIR,
1295                        i
1296                    ))
1297                    .exists());
1298            }
1299        } else {
1300            let ground_truth = if size > 0 {
1301                (1..=size).sum::<i64>()
1302            } else {
1303                42
1304            };
1305            assert!(run_data.is_array());
1306            let sum = run_data.as_array().unwrap().iter().fold(0, |acc, item| {
1307                acc + item["stdout"]
1308                    .as_str()
1309                    .unwrap()
1310                    .trim()
1311                    .parse::<i64>()
1312                    .unwrap()
1313            });
1314            assert!(
1315                sum == ground_truth,
1316                "expected: {}, got: {}, run_data: {}",
1317                ground_truth,
1318                sum,
1319                run_data
1320            );
1321        }
1322        std::fs::remove_dir_all(workspaces_path).unwrap();
1323    }
1324
1325    #[test]
1326    fn test_singlethreaded_parabuild_without_run() {
1327        parabuild_tester(
1328            "test_singlethreaded_parabuild_without_run",
1329            SINGLETHREADED_N,
1330            1,
1331            RunMethod::No,
1332            false,
1333            false,
1334        );
1335    }
1336
1337    #[test]
1338    fn test_singlethreaded_parabuild_in_place_run() {
1339        parabuild_tester(
1340            "test_singlethreaded_parabuild_in_place_run",
1341            SINGLETHREADED_N,
1342            1,
1343            RunMethod::InPlace,
1344            false,
1345            false,
1346        );
1347    }
1348
1349    #[test]
1350    fn test_multithreaded_parabuild_without_run() {
1351        parabuild_tester(
1352            "test_multithreaded_parabuild_without_run",
1353            MULTITHREADED_N,
1354            4,
1355            RunMethod::No,
1356            false,
1357            false,
1358        );
1359    }
1360
1361    #[test]
1362    fn test_multithreaded_parabuild_in_place_run() {
1363        parabuild_tester(
1364            "test_multithreaded_parabuild_in_place_run",
1365            MULTITHREADED_N,
1366            4,
1367            RunMethod::InPlace,
1368            false,
1369            false,
1370        );
1371    }
1372
1373    #[test]
1374    fn test_multithreaded_parabuild_out_of_place_single_run() {
1375        parabuild_tester(
1376            "test_multithreaded_parabuild_out_of_place_single_run",
1377            MULTITHREADED_N,
1378            4,
1379            RunMethod::OutOfPlace(1),
1380            false,
1381            false,
1382        );
1383    }
1384
1385    #[test]
1386    fn test_multithreaded_parabuild_out_of_place_run() {
1387        parabuild_tester(
1388            "test_multithreaded_parabuild_out_of_place_run",
1389            MULTITHREADED_N,
1390            4,
1391            RunMethod::OutOfPlace(2),
1392            false,
1393            false,
1394        );
1395    }
1396
1397    #[test]
1398    fn test_multithreaded_parabuild_exclusive_run() {
1399        parabuild_tester(
1400            "test_multithreaded_parabuild_exclusive_run",
1401            MULTITHREADED_N,
1402            4,
1403            RunMethod::Exclusive(2),
1404            false,
1405            false,
1406        );
1407    }
1408
1409    #[test]
1410    fn test_multithreaded_parabuild_out_of_place_run_in_place_template() {
1411        parabuild_tester(
1412            "test_multithreaded_parabuild_out_of_place_run_in_place_template",
1413            MULTITHREADED_N,
1414            4,
1415            RunMethod::OutOfPlace(2),
1416            true,
1417            false,
1418        );
1419    }
1420
1421    #[test]
1422    fn test_multithreaded_parabuild_out_of_place_run_default_template() {
1423        parabuild_tester(
1424            "test_multithreaded_parabuild_out_of_place_run_default_template",
1425            0,
1426            4,
1427            RunMethod::OutOfPlace(2),
1428            true,
1429            false,
1430        );
1431    }
1432
1433    #[test]
1434    fn test_multithreaded_parabuild_makefile_project() {
1435        parabuild_tester(
1436            "test_multithreaded_parabuild_makefile_project",
1437            10,
1438            4,
1439            RunMethod::OutOfPlace(2),
1440            true,
1441            true,
1442        );
1443    }
1444
1445    // #[test]
1446    // fn test_multithreaded_parabuild_out_of_place_run_in_place_template_heavy() {
1447    //     parabuild_tester(
1448    //         "test_multithreaded_parabuild_out_of_place_run_in_place_template",
1449    //         1000,
1450    //         20,
1451    //         RunMethod::OutOfPlace(4),
1452    //         true,
1453    //     );
1454    // }
1455}