use crate::bakefile::Bakefile;
use std::{collections::HashSet, convert::AsRef};
pub fn compute<'a>(bakefile: &'a Bakefile, tasks: &[&'a str]) -> Vec<&'a str> {
let mut roots: Vec<&'a str> = tasks.to_vec();
roots.sort();
let mut visited: HashSet<&'a str> = HashSet::new();
let mut schedule: Vec<&'a str> = vec![];
for root in roots {
let mut frontier: Vec<(&'a str, bool)> = vec![(root, true)];
let mut topological_sort: Vec<&'a str> = vec![];
while !frontier.is_empty() {
let (task, new) = frontier.pop().unwrap();
if new {
if visited.contains(task) {
continue;
}
visited.insert(task);
frontier.push((task, false));
let mut dependencies: Vec<&'a str> = bakefile.tasks[task]
.dependencies
.iter()
.map(AsRef::as_ref)
.collect();
dependencies.sort();
dependencies.reverse();
frontier.extend(
dependencies
.into_iter()
.map(|dependency| (dependency, true)),
);
} else {
topological_sort.push(task);
}
}
schedule.extend(topological_sort);
}
schedule
}
#[cfg(test)]
mod tests {
use crate::bakefile::{Bakefile, Task, DEFAULT_LOCATION, DEFAULT_USER};
use crate::schedule::compute;
use std::{collections::HashMap, path::Path};
fn task_with_dependencies(dependencies: Vec<String>) -> Task {
Task {
dependencies,
cache: true,
environment: HashMap::new(),
input_paths: vec![],
output_paths: vec![],
location: Path::new(DEFAULT_LOCATION).to_owned(),
user: DEFAULT_USER.to_owned(),
command: None,
}
}
fn empty_task() -> Task {
task_with_dependencies(vec![])
}
#[test]
fn schedule_empty() {
let bakefile = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks: HashMap::new(),
};
let actual: Vec<&str> = compute(&bakefile, &[]);
let expected: Vec<&str> = vec![];
assert_eq!(actual, expected);
}
#[test]
fn schedule_single() {
let mut tasks = HashMap::new();
tasks.insert("foo".to_owned(), empty_task());
let bakefile = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks,
};
let actual: Vec<&str> = compute(&bakefile, &["foo"]);
let expected: Vec<&str> = vec!["foo"];
assert_eq!(actual, expected);
}
#[test]
fn schedule_linear() {
let mut tasks = HashMap::new();
tasks.insert("foo".to_owned(), empty_task());
tasks.insert(
"bar".to_owned(),
task_with_dependencies(vec!["foo".to_owned()]),
);
tasks.insert(
"baz".to_owned(),
task_with_dependencies(vec!["bar".to_owned()]),
);
let bakefile = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks,
};
let actual: Vec<&str> = compute(&bakefile, &["baz"]);
let expected: Vec<&str> = vec!["foo", "bar", "baz"];
assert_eq!(actual, expected);
}
#[test]
fn schedule_diamond() {
let mut tasks = HashMap::new();
tasks.insert("foo".to_owned(), empty_task());
tasks.insert(
"bar".to_owned(),
task_with_dependencies(vec!["foo".to_owned()]),
);
tasks.insert(
"baz".to_owned(),
task_with_dependencies(vec!["foo".to_owned()]),
);
tasks.insert(
"qux".to_owned(),
task_with_dependencies(vec!["bar".to_owned(), "baz".to_owned()]),
);
let bakefile = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks,
};
let actual: Vec<&str> = compute(&bakefile, &["qux"]);
let expected: Vec<&str> = vec!["foo", "bar", "baz", "qux"];
assert_eq!(actual, expected);
}
#[test]
fn schedule_lexicographical_tie_breaking() {
let mut tasks = HashMap::new();
tasks.insert("foo".to_owned(), empty_task());
tasks.insert("bar".to_owned(), empty_task());
tasks.insert("baz".to_owned(), empty_task());
let bakefile = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks,
};
let actual: Vec<&str> = compute(&bakefile, &["foo", "bar", "baz"]);
let expected: Vec<&str> = vec!["bar", "baz", "foo"];
assert_eq!(actual, expected);
}
#[test]
fn schedule_dependency_duplicates() {
let mut tasks1 = HashMap::new();
tasks1.insert("foo".to_owned(), empty_task());
tasks1.insert("bar".to_owned(), empty_task());
tasks1.insert(
"baz".to_owned(),
task_with_dependencies(vec![
"foo".to_owned(),
"bar".to_owned(),
"foo".to_owned(),
]),
);
let mut tasks2 = HashMap::new();
tasks2.insert("foo".to_owned(), empty_task());
tasks2.insert("bar".to_owned(), empty_task());
tasks2.insert(
"baz".to_owned(),
task_with_dependencies(vec![
"bar".to_owned(),
"foo".to_owned(),
"bar".to_owned(),
]),
);
let bakefile1 = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks: tasks1,
};
let bakefile2 = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks: tasks2,
};
let first: Vec<&str> = compute(&bakefile1, &["baz"]);
let second: Vec<&str> = compute(&bakefile2, &["baz"]);
assert_eq!(first, second);
}
#[test]
fn schedule_input_duplicates() {
let mut tasks = HashMap::new();
tasks.insert("foo".to_owned(), empty_task());
tasks.insert("bar".to_owned(), empty_task());
tasks.insert("baz".to_owned(), empty_task());
let bakefile = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks,
};
let first: Vec<&str> = compute(&bakefile, &["baz", "bar", "baz"]);
let second: Vec<&str> = compute(&bakefile, &["bar", "baz", "bar"]);
assert_eq!(first, second);
}
#[test]
fn schedule_dependency_order() {
let mut tasks1 = HashMap::new();
tasks1.insert("foo".to_owned(), empty_task());
tasks1.insert("bar".to_owned(), empty_task());
tasks1.insert("baz".to_owned(), empty_task());
tasks1.insert(
"qux".to_owned(),
task_with_dependencies(vec![
"foo".to_owned(),
"bar".to_owned(),
"baz".to_owned(),
]),
);
let mut tasks2 = HashMap::new();
tasks2.insert("foo".to_owned(), empty_task());
tasks2.insert("bar".to_owned(), empty_task());
tasks2.insert("baz".to_owned(), empty_task());
tasks2.insert(
"qux".to_owned(),
task_with_dependencies(vec![
"baz".to_owned(),
"bar".to_owned(),
"foo".to_owned(),
]),
);
let bakefile1 = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks: tasks1,
};
let bakefile2 = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks: tasks2,
};
let first: Vec<&str> = compute(&bakefile1, &["baz"]);
let second: Vec<&str> = compute(&bakefile2, &["baz"]);
assert_eq!(first, second);
}
#[test]
fn schedule_input_order() {
let mut tasks = HashMap::new();
tasks.insert("foo".to_owned(), empty_task());
tasks.insert("bar".to_owned(), empty_task());
tasks.insert("baz".to_owned(), empty_task());
let bakefile = Bakefile {
image: "encom:os-12".to_owned(),
default: None,
tasks,
};
let first: Vec<&str> = compute(&bakefile, &["foo", "bar", "baz"]);
let second: Vec<&str> = compute(&bakefile, &["baz", "bar", "foo"]);
assert_eq!(first, second);
}
}