use std::collections::{HashMap, HashSet, VecDeque};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct CommandJob {
pub executable: String,
pub arguments: Vec<String>,
pub working_dir: Option<PathBuf>,
pub environment: HashMap<String, String>,
pub inputs: Vec<PathBuf>,
pub outputs: Vec<PathBuf>,
pub description: String,
}
impl CommandJob {
pub fn new(executable: &str, description: &str) -> Self {
Self {
executable: executable.to_string(),
arguments: Vec::new(),
working_dir: None,
environment: HashMap::new(),
inputs: Vec::new(),
outputs: Vec::new(),
description: description.to_string(),
}
}
pub fn arg(mut self, arg: &str) -> Self {
self.arguments.push(arg.to_string());
self
}
pub fn args(mut self, args: &[&str]) -> Self {
for a in args {
self.arguments.push(a.to_string());
}
self
}
pub fn input(mut self, path: &Path) -> Self {
self.inputs.push(path.to_path_buf());
self
}
pub fn output(mut self, path: &Path) -> Self {
self.outputs.push(path.to_path_buf());
self
}
pub fn working_dir(mut self, dir: &Path) -> Self {
self.working_dir = Some(dir.to_path_buf());
self
}
pub fn command_line(&self) -> String {
let mut cmd = self.executable.clone();
for arg in &self.arguments {
cmd.push(' ');
cmd.push_str(arg);
}
cmd
}
}
#[derive(Debug, Clone)]
pub struct PipedJob {
pub commands: Vec<CommandJob>,
pub inputs: Vec<PathBuf>,
pub output: Option<PathBuf>,
pub description: String,
}
impl PipedJob {
pub fn new(description: &str) -> Self {
Self {
commands: Vec::new(),
inputs: Vec::new(),
output: None,
description: description.to_string(),
}
}
pub fn add_command(mut self, cmd: CommandJob) -> Self {
self.commands.push(cmd);
self
}
pub fn output(mut self, path: &Path) -> Self {
self.output = Some(path.to_path_buf());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JobStatus {
Pending,
Running,
Completed,
Failed(String),
Skipped,
}
#[derive(Debug, Clone)]
pub struct JobResult {
pub job_id: usize,
pub status: JobStatus,
pub duration: Duration,
pub stdout: String,
pub stderr: String,
pub exit_code: Option<i32>,
}
#[derive(Debug, Clone)]
pub struct JobList {
jobs: Vec<CommandJob>,
dependencies: HashMap<usize, HashSet<usize>>,
reverse_deps: HashMap<usize, HashSet<usize>>,
results: HashMap<usize, JobResult>,
total_jobs: usize,
}
impl JobList {
pub fn new() -> Self {
Self {
jobs: Vec::new(),
dependencies: HashMap::new(),
reverse_deps: HashMap::new(),
results: HashMap::new(),
total_jobs: 0,
}
}
pub fn add_job(&mut self, job: CommandJob) -> usize {
let id = self.jobs.len();
self.jobs.push(job);
self.total_jobs = id + 1;
id
}
pub fn add_dependency(&mut self, id: usize, dep_id: usize) {
self.dependencies.entry(id).or_default().insert(dep_id);
self.reverse_deps.entry(dep_id).or_default().insert(id);
}
pub fn len(&self) -> usize {
self.total_jobs
}
pub fn is_empty(&self) -> bool {
self.jobs.is_empty()
}
pub fn get_job(&self, id: usize) -> Option<&CommandJob> {
self.jobs.get(id)
}
pub fn dependencies_of(&self, id: usize) -> Option<&HashSet<usize>> {
self.dependencies.get(&id)
}
pub fn topological_order(&self) -> Vec<usize> {
let mut in_degree: HashMap<usize, usize> = HashMap::new();
let mut queue: VecDeque<usize> = VecDeque::new();
let mut order = Vec::new();
for i in 0..self.total_jobs {
let deps = self.dependencies_of(i).map(|d| d.len()).unwrap_or(0);
in_degree.insert(i, deps);
if deps == 0 {
queue.push_back(i);
}
}
while let Some(node) = queue.pop_front() {
order.push(node);
if let Some(rev_deps) = self.reverse_deps.get(&node) {
for &dep in rev_deps {
if let Some(deg) = in_degree.get_mut(&dep) {
*deg = deg.saturating_sub(1);
if *deg == 0 {
queue.push_back(dep);
}
}
}
}
}
if order.len() < self.total_jobs {
for i in 0..self.total_jobs {
if !order.contains(&i) {
order.push(i);
}
}
}
order
}
pub fn ready_jobs(&self, completed: &HashSet<usize>) -> Vec<usize> {
(0..self.total_jobs)
.filter(|&i| !completed.contains(&i))
.filter(|&i| {
self.dependencies_of(i)
.map(|deps| deps.iter().all(|d| completed.contains(d)))
.unwrap_or(true)
})
.collect()
}
}
impl Default for JobList {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct TempFileManager {
temp_dir: PathBuf,
files: Vec<PathBuf>,
prefix: String,
counter: u64,
}
impl TempFileManager {
pub fn new() -> Self {
let temp_dir = std::env::temp_dir().join("llvm_native_clang");
let _ = fs::create_dir_all(&temp_dir);
Self {
temp_dir,
files: Vec::new(),
prefix: "clang_tmp".to_string(),
counter: 0,
}
}
pub fn create_temp_file(&mut self, extension: &str) -> PathBuf {
self.counter += 1;
let filename = format!("{}_{:08x}.{}", self.prefix, self.counter, extension);
let path = self.temp_dir.join(&filename);
self.files.push(path.clone());
path
}
pub fn create_pp_file(&mut self) -> PathBuf {
self.create_temp_file("i")
}
pub fn create_asm_file(&mut self) -> PathBuf {
self.create_temp_file("s")
}
pub fn create_obj_file(&mut self) -> PathBuf {
self.create_temp_file("o")
}
pub fn create_ir_file(&mut self) -> PathBuf {
self.create_temp_file("ll")
}
pub fn create_bc_file(&mut self) -> PathBuf {
self.create_temp_file("bc")
}
pub fn cleanup(&self) -> Result<(), String> {
for file in &self.files {
if file.exists() {
fs::remove_file(file)
.map_err(|e| format!("Cannot remove {}: {}", file.display(), e))?;
}
}
Ok(())
}
pub fn file_count(&self) -> usize {
self.files.len()
}
pub fn temp_dir(&self) -> &Path {
&self.temp_dir
}
pub fn cleanup_all(&self) -> Result<(), String> {
if self.temp_dir.exists() {
fs::remove_dir_all(&self.temp_dir)
.map_err(|e| format!("Cannot remove temp dir: {}", e))?;
}
Ok(())
}
}
impl Default for TempFileManager {
fn default() -> Self {
Self::new()
}
}
impl Drop for TempFileManager {
fn drop(&mut self) {
let _ = self.cleanup();
}
}
#[derive(Debug)]
pub struct JobCache {
cache: HashMap<String, JobCacheEntry>,
enabled: bool,
}
#[derive(Debug, Clone)]
struct JobCacheEntry {
input_hash: String,
result: JobResult,
timestamp: Instant,
}
impl JobCache {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn compute_input_hash(job: &CommandJob) -> String {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
job.executable.hash(&mut hasher);
for arg in &job.arguments {
arg.hash(&mut hasher);
}
for input in &job.inputs {
input.to_string_lossy().hash(&mut hasher);
if let Ok(meta) = fs::metadata(input) {
if let Ok(modified) = meta.modified() {
modified.hash(&mut hasher);
}
}
if let Ok(meta) = fs::metadata(input) {
meta.len().hash(&mut hasher);
}
}
format!("{:x}", hasher.finish())
}
pub fn check(&self, job: &CommandJob) -> Option<&JobResult> {
if !self.enabled {
return None;
}
let hash = Self::compute_input_hash(job);
if let Some(entry) = self.cache.get(&hash) {
let outputs_exist = job.outputs.iter().all(|o| o.exists());
if outputs_exist {
return Some(&entry.result);
}
}
None
}
pub fn store(&mut self, job: &CommandJob, result: JobResult) {
if !self.enabled {
return;
}
let hash = Self::compute_input_hash(job);
let entry = JobCacheEntry {
input_hash: hash.clone(),
result,
timestamp: Instant::now(),
};
self.cache.insert(hash, entry);
}
pub fn clear(&mut self) {
self.cache.clear();
}
pub fn len(&self) -> usize {
self.cache.len()
}
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
}
impl Default for JobCache {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum JobPriority {
Low = 0,
Normal = 1,
High = 2,
Critical = 3,
}
#[derive(Debug, Clone)]
pub struct SchedulerConfig {
pub max_parallel: usize,
pub fail_fast: bool,
pub use_priority: bool,
pub show_progress: bool,
pub verbose: bool,
}
impl Default for SchedulerConfig {
fn default() -> Self {
Self {
max_parallel: 4,
fail_fast: true,
use_priority: false,
show_progress: true,
verbose: false,
}
}
}
pub struct JobScheduler {
config: SchedulerConfig,
jobs: JobList,
priorities: HashMap<usize, JobPriority>,
cache: JobCache,
completed: HashSet<usize>,
failed: HashSet<usize>,
running: HashSet<usize>,
start_time: Option<Instant>,
}
impl JobScheduler {
pub fn new(config: SchedulerConfig) -> Self {
Self {
config,
jobs: JobList::new(),
priorities: HashMap::new(),
cache: JobCache::new(),
completed: HashSet::new(),
failed: HashSet::new(),
running: HashSet::new(),
start_time: None,
}
}
pub fn add_job(&mut self, job: CommandJob, priority: JobPriority) -> usize {
let id = self.jobs.add_job(job);
self.priorities.insert(id, priority);
id
}
pub fn add_dependency(&mut self, id: usize, dep_id: usize) {
self.jobs.add_dependency(id, dep_id);
}
pub fn execute_sequential(&mut self) -> Vec<JobResult> {
self.start_time = Some(Instant::now());
let order = self.jobs.topological_order();
let total = order.len();
let mut results = Vec::new();
for (idx, &job_id) in order.iter().enumerate() {
if self.config.fail_fast && !self.failed.is_empty() {
break;
}
if self.config.show_progress {
eprintln!(
"[{}/{}] {}",
idx + 1,
total,
self.jobs
.get_job(job_id)
.map(|j| j.description.as_str())
.unwrap_or("unknown")
);
}
let result = self.execute_job(job_id);
let success =
result.status == JobStatus::Completed || result.status == JobStatus::Skipped;
if success {
self.completed.insert(job_id);
} else {
self.failed.insert(job_id);
}
results.push(result);
}
results
}
pub fn execute_parallel(&mut self) -> Vec<JobResult> {
self.start_time = Some(Instant::now());
let mut results = Vec::new();
let total = self.jobs.len();
let order = self.jobs.topological_order();
for (idx, &job_id) in order.iter().enumerate() {
if self.config.fail_fast && !self.failed.is_empty() {
break;
}
if self.config.show_progress {
eprintln!(
"[{}/{}] {}",
idx + 1,
total,
self.jobs
.get_job(job_id)
.map(|j| j.description.as_str())
.unwrap_or("unknown")
);
}
self.running.insert(job_id);
let result = self.execute_job(job_id);
self.running.remove(&job_id);
let success =
result.status == JobStatus::Completed || result.status == JobStatus::Skipped;
if success {
self.completed.insert(job_id);
} else {
self.failed.insert(job_id);
}
results.push(result);
}
results
}
fn execute_job(&mut self, job_id: usize) -> JobResult {
let job = match self.jobs.get_job(job_id) {
Some(j) => j.clone(),
None => {
return JobResult {
job_id,
status: JobStatus::Failed("Job not found".to_string()),
duration: Duration::ZERO,
stdout: String::new(),
stderr: "Job not found".to_string(),
exit_code: Some(1),
};
}
};
if let Some(cached) = self.cache.check(&job) {
let mut result = cached.clone();
result.status = JobStatus::Skipped;
result.duration = Duration::ZERO;
return result;
}
let start = Instant::now();
let (status, stdout, stderr, exit_code) = self.simulate_execute(&job);
let result = JobResult {
job_id,
status,
duration: start.elapsed(),
stdout,
stderr,
exit_code,
};
if result.status == JobStatus::Completed {
self.cache.store(&job, result.clone());
}
result
}
fn simulate_execute(&self, job: &CommandJob) -> (JobStatus, String, String, Option<i32>) {
for input in &job.inputs {
if !input.exists() {
return (
JobStatus::Failed(format!("Input file not found: {}", input.display())),
String::new(),
format!("Error: input '{}' does not exist", input.display()),
Some(1),
);
}
}
let stdout = format!("Compiled: {}\n", job.description);
let stderr = String::new();
for output in &job.outputs {
if let Some(parent) = output.parent() {
let _ = fs::create_dir_all(parent);
}
let _ = fs::write(output, "compiled");
}
(JobStatus::Completed, stdout, stderr, Some(0))
}
pub fn statistics(&self) -> SchedulerStats {
SchedulerStats {
total_jobs: self.jobs.len(),
completed: self.completed.len(),
failed: self.failed.len(),
skipped: self
.jobs
.len()
.saturating_sub(self.completed.len() + self.failed.len()),
elapsed: self.start_time.map(|t| t.elapsed()).unwrap_or_default(),
cache_hits: 0, }
}
pub fn job_list(&self) -> &JobList {
&self.jobs
}
pub fn print_progress(&self) {
let stats = self.statistics();
eprintln!(
"Progress: {}/{} jobs completed, {} failed, {} remaining",
stats.completed,
stats.total_jobs,
stats.failed,
stats.total_jobs - stats.completed - stats.failed
);
}
}
#[derive(Debug, Clone)]
pub struct SchedulerStats {
pub total_jobs: usize,
pub completed: usize,
pub failed: usize,
pub skipped: usize,
pub elapsed: Duration,
pub cache_hits: usize,
}
#[derive(Debug, Clone)]
pub struct DependencyTracker {
deps: HashMap<PathBuf, HashSet<PathBuf>>,
reverse_deps: HashMap<PathBuf, HashSet<PathBuf>>,
}
impl DependencyTracker {
pub fn new() -> Self {
Self {
deps: HashMap::new(),
reverse_deps: HashMap::new(),
}
}
pub fn record(&mut self, output: &Path, inputs: &[PathBuf]) {
let input_set: HashSet<PathBuf> = inputs.iter().cloned().collect();
self.deps.insert(output.to_path_buf(), input_set.clone());
for input in &input_set {
self.reverse_deps
.entry(input.clone())
.or_default()
.insert(output.to_path_buf());
}
}
pub fn inputs_of(&self, output: &Path) -> Option<&HashSet<PathBuf>> {
self.deps.get(output)
}
pub fn dependents_of(&self, input: &Path) -> Option<&HashSet<PathBuf>> {
self.reverse_deps.get(input)
}
pub fn is_outdated(&self, output: &Path) -> bool {
if let Some(inputs) = self.inputs_of(output) {
if !output.exists() {
return true;
}
let out_mtime = fs::metadata(output).ok().and_then(|m| m.modified().ok());
for input in inputs {
if input.exists() {
if let (Some(out_time), Some(in_time)) = (
out_mtime,
fs::metadata(input).ok().and_then(|m| m.modified().ok()),
) {
if in_time > out_time {
return true;
}
}
}
}
}
false
}
pub fn write_make_depfile(&self, path: &Path) -> Result<(), String> {
let mut content = String::new();
for (output, inputs) in &self.deps {
let inputs_str: Vec<String> = inputs.iter().map(|p| p.display().to_string()).collect();
content.push_str(&format!("{}: {}\n", output.display(), inputs_str.join(" ")));
}
fs::write(path, content).map_err(|e| format!("Cannot write depfile: {}", e))
}
pub fn clear(&mut self) {
self.deps.clear();
self.reverse_deps.clear();
}
}
impl Default for DependencyTracker {
fn default() -> Self {
Self::new()
}
}
pub struct DriverJobOrchestrator {
scheduler: JobScheduler,
temp_files: TempFileManager,
dep_tracker: DependencyTracker,
cache: JobCache,
source_files: Vec<PathBuf>,
output_dir: PathBuf,
}
impl DriverJobOrchestrator {
pub fn new(output_dir: &Path) -> Self {
let config = SchedulerConfig::default();
Self {
scheduler: JobScheduler::new(config),
temp_files: TempFileManager::new(),
dep_tracker: DependencyTracker::new(),
cache: JobCache::new(),
source_files: Vec::new(),
output_dir: output_dir.to_path_buf(),
}
}
pub fn with_config(output_dir: &Path, config: SchedulerConfig) -> Self {
Self {
scheduler: JobScheduler::new(config),
temp_files: TempFileManager::new(),
dep_tracker: DependencyTracker::new(),
cache: JobCache::new(),
source_files: Vec::new(),
output_dir: output_dir.to_path_buf(),
}
}
pub fn add_source(&mut self, path: &Path) {
self.source_files.push(path.to_path_buf());
}
pub fn setup_compilation(&mut self) {
let mut pp_jobs = Vec::new();
for source in &self.source_files.clone() {
let pp_output = self.temp_files.create_pp_file();
let pp_job = CommandJob::new("clang", &format!("Preprocess {}", source.display()))
.arg("-E")
.arg(&source.to_string_lossy())
.arg("-o")
.arg(&pp_output.to_string_lossy())
.input(source)
.output(&pp_output);
let pp_id = self.scheduler.add_job(pp_job, JobPriority::High);
pp_jobs.push((pp_id, pp_output));
}
let mut obj_ids = Vec::new();
for (pp_id, pp_output) in pp_jobs {
let source_name = pp_output
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("output");
let obj_output = self.output_dir.join(format!("{}.o", source_name));
let compile_job = CommandJob::new("clang", &format!("Compile {}", source_name))
.arg("-c")
.arg(&pp_output.to_string_lossy())
.arg("-o")
.arg(&obj_output.to_string_lossy())
.input(&pp_output)
.output(&obj_output);
let compile_id = self.scheduler.add_job(compile_job, JobPriority::Normal);
self.scheduler.add_dependency(compile_id, pp_id);
self.dep_tracker.record(&obj_output, &[pp_output.clone()]);
obj_ids.push(compile_id);
}
if obj_ids.len() > 1 {
let exe_output = self.output_dir.join("a.out");
let mut link_job = CommandJob::new("clang", "Link executable")
.arg("-o")
.arg(&exe_output.to_string_lossy());
for obj_id in &obj_ids {
if let Some(job) = self.scheduler.job_list().get_job(*obj_id) {
for out in &job.outputs {
link_job = link_job.arg(&out.to_string_lossy());
}
}
}
let link_id = self.scheduler.add_job(link_job, JobPriority::Critical);
for obj_id in obj_ids {
self.scheduler.add_dependency(link_id, obj_id);
}
}
}
pub fn execute(&mut self) -> Vec<JobResult> {
self.scheduler.execute_sequential()
}
pub fn execute_parallel(&mut self) -> Vec<JobResult> {
self.scheduler.execute_parallel()
}
pub fn statistics(&self) -> SchedulerStats {
self.scheduler.statistics()
}
pub fn print_summary(&self, results: &[JobResult]) {
let stats = self.statistics();
println!("\n╔══════════════════════════════════════════════════════════════╗");
println!("║ Compilation Job Summary ║");
println!("╠══════════════════════════════════════════════════════════════╣");
println!(
"║ Total jobs: {:4} ║",
stats.total_jobs
);
println!(
"║ Completed: {:4} ║",
stats.completed
);
println!(
"║ Failed: {:4} ║",
stats.failed
);
println!(
"║ Skipped: {:4} ║",
stats.skipped
);
println!(
"║ Elapsed: {:6} ms ║",
stats.elapsed.as_millis()
);
println!("╚══════════════════════════════════════════════════════════════╝\n");
for result in results {
match &result.status {
JobStatus::Completed => println!(
" ✓ Job {} completed in {}ms",
result.job_id,
result.duration.as_millis()
),
JobStatus::Skipped => {
println!(" ↷ Job {} skipped (cached)", result.job_id)
}
JobStatus::Failed(msg) => {
println!(" ✗ Job {} failed: {}", result.job_id, msg)
}
_ => {}
}
}
}
pub fn cleanup(&self) -> Result<(), String> {
self.temp_files.cleanup_all()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn test_command_job_new() {
let job = CommandJob::new("cc", "compile test.c");
assert_eq!(job.executable, "cc");
assert_eq!(job.description, "compile test.c");
assert!(job.arguments.is_empty());
}
#[test]
fn test_command_job_builder() {
let job = CommandJob::new("cc", "compile")
.arg("-c")
.arg("test.c")
.arg("-o")
.arg("test.o")
.input(Path::new("test.c"))
.output(Path::new("test.o"));
assert_eq!(job.arguments.len(), 4);
assert_eq!(job.inputs.len(), 1);
assert_eq!(job.outputs.len(), 1);
assert!(job.command_line().contains("-c"));
}
#[test]
fn test_command_job_args_builder() {
let job = CommandJob::new("gcc", "compile").args(&["-Wall", "-O2", "-std=c11"]);
assert_eq!(job.arguments.len(), 3);
assert!(job.command_line().contains("-Wall"));
}
#[test]
fn test_job_list_new() {
let list = JobList::new();
assert!(list.is_empty());
assert_eq!(list.len(), 0);
}
#[test]
fn test_job_list_add() {
let mut list = JobList::new();
let id = list.add_job(CommandJob::new("cc", "test"));
assert_eq!(id, 0);
assert_eq!(list.len(), 1);
assert!(list.get_job(0).is_some());
}
#[test]
fn test_job_list_dependencies() {
let mut list = JobList::new();
let id1 = list.add_job(CommandJob::new("cpp", "preprocess"));
let id2 = list.add_job(CommandJob::new("cc", "compile"));
list.add_dependency(id2, id1);
assert!(list.dependencies_of(id2).is_some());
assert!(list.dependencies_of(id2).unwrap().contains(&id1));
assert!(list.dependencies_of(id1).is_none());
}
#[test]
fn test_job_list_topological_order() {
let mut list = JobList::new();
let a = list.add_job(CommandJob::new("cmd", "A"));
let b = list.add_job(CommandJob::new("cmd", "B"));
let c = list.add_job(CommandJob::new("cmd", "C"));
list.add_dependency(b, a);
list.add_dependency(c, b);
let order = list.topological_order();
assert_eq!(order.len(), 3);
let pos_a = order.iter().position(|&x| x == a).unwrap();
let pos_b = order.iter().position(|&x| x == b).unwrap();
let pos_c = order.iter().position(|&x| x == c).unwrap();
assert!(pos_a < pos_b);
assert!(pos_b < pos_c);
}
#[test]
fn test_job_list_ready_jobs() {
let mut list = JobList::new();
let a = list.add_job(CommandJob::new("cmd", "A"));
let b = list.add_job(CommandJob::new("cmd", "B"));
list.add_dependency(b, a);
let mut completed = HashSet::new();
let ready = list.ready_jobs(&completed);
assert_eq!(ready, vec![0]);
completed.insert(a);
let ready = list.ready_jobs(&completed);
assert_eq!(ready, vec![1]);
}
#[test]
fn test_piped_job_new() {
let job = PipedJob::new("preprocess | compile");
assert!(job.commands.is_empty());
}
#[test]
fn test_piped_job_with_commands() {
let job = PipedJob::new("pipe")
.add_command(CommandJob::new("clang", "preprocess").arg("-E"))
.add_command(CommandJob::new("clang", "compile").arg("-c"))
.output(Path::new("output.o"));
assert_eq!(job.commands.len(), 2);
assert_eq!(job.output, Some(PathBuf::from("output.o")));
}
#[test]
fn test_temp_file_manager() {
let mut tm = TempFileManager::new();
let path = tm.create_temp_file("test");
assert!(path.exists() || path.parent().map_or(false, |p| p.exists()));
}
#[test]
fn test_temp_file_manager_types() {
let mut tm = TempFileManager::new();
let pp = tm.create_pp_file();
let asm = tm.create_asm_file();
let obj = tm.create_obj_file();
let ir = tm.create_ir_file();
let bc = tm.create_bc_file();
assert!(pp.ends_with(".i"));
assert!(asm.ends_with(".s"));
assert!(obj.ends_with(".o"));
assert!(ir.ends_with(".ll"));
assert!(bc.ends_with(".bc"));
}
#[test]
fn test_job_cache_new() {
let cache = JobCache::new();
assert!(cache.is_empty());
}
#[test]
fn test_job_cache_disabled() {
let mut cache = JobCache::new();
cache.set_enabled(false);
let job = CommandJob::new("cc", "test").input(Path::new("nonexistent.c"));
assert!(cache.check(&job).is_none());
}
#[test]
fn test_job_cache_hash() {
let job1 = CommandJob::new("cc", "test").arg("-c").arg("test.c");
let job2 = CommandJob::new("cc", "test").arg("-c").arg("test.c");
let hash1 = JobCache::compute_input_hash(&job1);
let hash2 = JobCache::compute_input_hash(&job2);
assert_eq!(hash1, hash2);
}
#[test]
fn test_job_cache_different_jobs() {
let job1 = CommandJob::new("cc", "test").arg("a.c");
let job2 = CommandJob::new("cc", "test").arg("b.c");
let hash1 = JobCache::compute_input_hash(&job1);
let hash2 = JobCache::compute_input_hash(&job2);
assert_ne!(hash1, hash2);
}
#[test]
fn test_scheduler_config_default() {
let config = SchedulerConfig::default();
assert_eq!(config.max_parallel, 4);
assert!(config.fail_fast);
assert!(config.show_progress);
}
#[test]
fn test_scheduler_new() {
let config = SchedulerConfig::default();
let scheduler = JobScheduler::new(config);
assert_eq!(scheduler.job_list().len(), 0);
}
#[test]
fn test_scheduler_add_job() {
let config = SchedulerConfig::default();
let mut scheduler = JobScheduler::new(config);
let job = CommandJob::new("cc", "test");
let id = scheduler.add_job(job, JobPriority::Normal);
assert_eq!(id, 0);
assert_eq!(scheduler.job_list().len(), 1);
}
#[test]
fn test_scheduler_execute_simple() {
let config = SchedulerConfig {
show_progress: false,
..Default::default()
};
let mut scheduler = JobScheduler::new(config);
let tmp = std::env::temp_dir().join("test_input.c");
let _ = fs::write(&tmp, "int main() { return 0; }");
let obj = std::env::temp_dir().join("test_output.o");
let _ = fs::remove_file(&obj);
let job = CommandJob::new("cc", "compile test.c")
.arg("-c")
.arg(&tmp.to_string_lossy().to_string())
.arg("-o")
.arg(&obj.to_string_lossy().to_string())
.input(&tmp)
.output(&obj);
scheduler.add_job(job, JobPriority::Normal);
let results = scheduler.execute_sequential();
assert_eq!(results.len(), 1);
assert!(results[0].status == JobStatus::Completed);
let _ = fs::remove_file(&tmp);
let _ = fs::remove_file(&obj);
}
#[test]
fn test_scheduler_dependency_chain() {
let config = SchedulerConfig {
show_progress: false,
..Default::default()
};
let mut scheduler = JobScheduler::new(config);
let tmp = std::env::temp_dir().join("test_dep_input.c");
let _ = fs::write(&tmp, "int x;");
let obj1 = std::env::temp_dir().join("test_dep_1.o");
let obj2 = std::env::temp_dir().join("test_dep_2.o");
let _ = fs::remove_file(&obj1);
let _ = fs::remove_file(&obj2);
let job1 = CommandJob::new("cc", "step 1").input(&tmp).output(&obj1);
let job2 = CommandJob::new("cc", "step 2").input(&obj1).output(&obj2);
let id1 = scheduler.add_job(job1, JobPriority::Normal);
let id2 = scheduler.add_job(job2, JobPriority::Normal);
scheduler.add_dependency(id2, id1);
let results = scheduler.execute_sequential();
assert_eq!(results.len(), 2);
let _ = fs::remove_file(&tmp);
let _ = fs::remove_file(&obj1);
let _ = fs::remove_file(&obj2);
}
#[test]
fn test_scheduler_statistics() {
let config = SchedulerConfig {
show_progress: false,
..Default::default()
};
let mut scheduler = JobScheduler::new(config);
let tmp = std::env::temp_dir().join("test_stats_input.c");
let _ = fs::write(&tmp, "void f() {}");
let obj = std::env::temp_dir().join("test_stats_output.o");
let _ = fs::remove_file(&obj);
let job = CommandJob::new("cc", "compile").input(&tmp).output(&obj);
scheduler.add_job(job, JobPriority::Normal);
let _ = scheduler.execute_sequential();
let stats = scheduler.statistics();
assert_eq!(stats.total_jobs, 1);
assert_eq!(stats.completed, 1);
assert_eq!(stats.failed, 0);
let _ = fs::remove_file(&tmp);
let _ = fs::remove_file(&obj);
}
#[test]
fn test_dependency_tracker_new() {
let tracker = DependencyTracker::new();
assert!(tracker.inputs_of(Path::new("test.o")).is_none());
}
#[test]
fn test_dependency_tracker_record() {
let mut tracker = DependencyTracker::new();
let output = PathBuf::from("test.o");
let inputs = vec![PathBuf::from("test.c"), PathBuf::from("test.h")];
tracker.record(&output, &inputs);
let deps = tracker.inputs_of(&output).unwrap();
assert!(deps.contains(&PathBuf::from("test.c")));
assert!(deps.contains(&PathBuf::from("test.h")));
}
#[test]
fn test_dependency_tracker_dependents() {
let mut tracker = DependencyTracker::new();
tracker.record(&PathBuf::from("test.o"), &[PathBuf::from("test.c")]);
let dependents = tracker.dependents_of(&PathBuf::from("test.c")).unwrap();
assert!(dependents.contains(&PathBuf::from("test.o")));
}
#[test]
fn test_driver_job_orchestrator() {
let tmp_dir = std::env::temp_dir().join("llvm_test_orch");
let _ = fs::create_dir_all(&tmp_dir);
let mut orch = DriverJobOrchestrator::new(&tmp_dir);
let src = tmp_dir.join("test.c");
let _ = fs::write(&src, "int main() { return 0; }");
orch.add_source(&src);
orch.setup_compilation();
assert!(orch.scheduler.job_list().len() > 0);
let results = orch.execute();
assert!(!results.is_empty());
let _ = fs::remove_dir_all(&tmp_dir);
}
#[test]
fn test_job_status_enum() {
assert_eq!(JobStatus::Pending, JobStatus::Pending);
assert_eq!(JobStatus::Completed, JobStatus::Completed);
assert_ne!(JobStatus::Completed, JobStatus::Failed("err".to_string()));
let failed = JobStatus::Failed("error message".to_string());
match failed {
JobStatus::Failed(msg) => assert!(msg.contains("error")),
_ => panic!("Expected Failed status"),
}
}
#[test]
fn test_job_priority_ordering() {
assert!(JobPriority::Critical > JobPriority::High);
assert!(JobPriority::High > JobPriority::Normal);
assert!(JobPriority::Normal > JobPriority::Low);
}
#[test]
fn test_cache_store_and_check() {
let mut cache = JobCache::new();
let tmp = std::env::temp_dir().join("test_cache_input.c");
let _ = fs::write(&tmp, "int x;");
let obj = std::env::temp_dir().join("test_cache_output.o");
let _ = fs::remove_file(&obj);
let job = CommandJob::new("cc", "test").input(&tmp).output(&obj);
let result = JobResult {
job_id: 0,
status: JobStatus::Completed,
duration: Duration::from_millis(100),
stdout: "ok".to_string(),
stderr: String::new(),
exit_code: Some(0),
};
cache.store(&job, result);
assert_eq!(cache.len(), 1);
let cached = cache.check(&job);
assert!(cached.is_some());
assert_eq!(cached.unwrap().stdout, "ok");
let _ = fs::remove_file(&tmp);
let _ = fs::remove_file(&obj);
}
#[test]
fn test_scheduler_parallel_execution() {
let config = SchedulerConfig {
max_parallel: 2,
show_progress: false,
..Default::default()
};
let mut scheduler = JobScheduler::new(config);
let tmp = std::env::temp_dir().join("test_par_input.c");
let _ = fs::write(&tmp, "int x;");
for i in 0..3 {
let obj = std::env::temp_dir().join(format!("test_par_{}.o", i));
let _ = fs::remove_file(&obj);
let job = CommandJob::new("cc", &format!("job {}", i))
.input(&tmp)
.output(&obj);
scheduler.add_job(job, JobPriority::Normal);
}
let results = scheduler.execute_parallel();
assert_eq!(results.len(), 3);
let _ = fs::remove_file(&tmp);
}
#[test]
fn test_command_job_working_dir() {
let job = CommandJob::new("make", "build").working_dir(Path::new("/tmp"));
assert_eq!(job.working_dir, Some(PathBuf::from("/tmp")));
}
#[test]
fn test_command_job_environment() {
let mut job = CommandJob::new("cc", "compile");
job.environment
.insert("CC".to_string(), "clang".to_string());
assert_eq!(job.environment.get("CC"), Some(&"clang".to_string()));
}
#[test]
fn test_job_list_large() {
let mut list = JobList::new();
for i in 0..100 {
list.add_job(CommandJob::new("cc", &format!("job {}", i)));
}
assert_eq!(list.len(), 100);
let order = list.topological_order();
assert_eq!(order.len(), 100);
}
}