From 8b2029b18148b64dd6dd1f7ae336da3988810aff Mon Sep 17 00:00:00 2001 From: nicholaslyang Date: Mon, 22 Apr 2024 10:39:07 -0400 Subject: [PATCH] Added tests --- crates/turborepo-lib/src/engine/mod.rs | 125 +++++++++++++++++++++--- crates/turborepo-lib/src/run/task_id.rs | 12 ++- 2 files changed, 125 insertions(+), 12 deletions(-) diff --git a/crates/turborepo-lib/src/engine/mod.rs b/crates/turborepo-lib/src/engine/mod.rs index ff601888b745c..40f6539685263 100644 --- a/crates/turborepo-lib/src/engine/mod.rs +++ b/crates/turborepo-lib/src/engine/mod.rs @@ -213,19 +213,20 @@ impl Engine { /// mode to re-run the non-persistent tasks. pub fn create_engine_without_persistent_tasks(&self) -> Engine { let new_graph = self.task_graph.filter_map( - |node_idx, node| { - if let TaskNode::Task(task) = &self.task_graph[node_idx] { + |node_idx, node| match &self.task_graph[node_idx] { + TaskNode::Task(task) => { let def = self .task_definitions .get(task) .expect("task should have definition"); if !def.persistent { - return Some(node.clone()); + Some(node.clone()) + } else { + None } } - - None + TaskNode::Root => Some(node.clone()), }, |_, _| Some(()), ); @@ -263,19 +264,20 @@ impl Engine { /// Creates an `Engine` that is only the persistent tasks. pub fn create_engine_for_persistent_tasks(&self) -> Engine { let mut new_graph = self.task_graph.filter_map( - |node_idx, node| { - if let TaskNode::Task(task) = &self.task_graph[node_idx] { + |node_idx, node| match &self.task_graph[node_idx] { + TaskNode::Task(task) => { let def = self .task_definitions .get(task) .expect("task should have definition"); if def.persistent { - return Some(node.clone()); + Some(node.clone()) + } else { + None } } - - None + TaskNode::Root => Some(node.clone()), }, |_, _| Some(()), ); @@ -566,6 +568,7 @@ mod test { }; use super::*; + use crate::run::task_id::TaskName; struct DummyDiscovery<'a>(&'a TempDir); @@ -585,7 +588,11 @@ mod test { let scripts = if had_build { BTreeMap::from_iter( - [("build".to_string(), "echo built!".to_string())].into_iter(), + [ + ("build".to_string(), "echo built!".to_string()), + ("dev".to_string(), "echo running dev!".to_string()), + ] + .into_iter(), ) } else { BTreeMap::default() @@ -671,4 +678,100 @@ mod test { // if our limit is greater, then it should pass engine.validate(&graph, 4, false).expect("ok"); } + + #[tokio::test] + async fn test_prune_persistent_tasks() { + // Verifies that we can prune the `Engine` to include only the persistent tasks + // or only the non-persistent tasks. + + let mut engine = Engine::new(); + + // add two packages with a persistent build task + for package in ["a", "b"] { + let build_task_id = TaskId::new(package, "build"); + let dev_task_id = TaskId::new(package, "dev"); + + engine.get_index(&build_task_id); + engine.add_definition(build_task_id.clone(), TaskDefinition::default()); + + engine.get_index(&dev_task_id); + engine.add_definition( + dev_task_id, + TaskDefinition { + persistent: true, + task_dependencies: vec![Spanned::new(TaskName::from(build_task_id))], + ..Default::default() + }, + ); + } + + let engine = engine.seal(); + + let persistent_tasks_engine = engine.create_engine_for_persistent_tasks(); + for node in persistent_tasks_engine.tasks() { + if let TaskNode::Task(task_id) = node { + let def = persistent_tasks_engine + .task_definition(task_id) + .expect("task should have definition"); + assert!(def.persistent, "task should be persistent"); + } + } + + let non_persistent_tasks_engine = engine.create_engine_without_persistent_tasks(); + for node in non_persistent_tasks_engine.tasks() { + if let TaskNode::Task(task_id) = node { + let def = non_persistent_tasks_engine + .task_definition(task_id) + .expect("task should have definition"); + assert!(!def.persistent, "task should not be persistent"); + } + } + } + + #[tokio::test] + async fn test_get_subgraph_for_package() { + // Verifies that we can prune the `Engine` to include only the persistent tasks + // or only the non-persistent tasks. + + let mut engine = Engine::new(); + + // Add two tasks in package `a` + let a_build_task_id = TaskId::new("a", "build"); + let a_dev_task_id = TaskId::new("a", "dev"); + + let a_build_idx = engine.get_index(&a_build_task_id); + engine.add_definition(a_build_task_id.clone(), TaskDefinition::default()); + + engine.get_index(&a_dev_task_id); + engine.add_definition(a_dev_task_id.clone(), TaskDefinition::default()); + + // Add two tasks in package `b` where the `build` task depends + // on the `build` task from package `a` + let b_build_task_id = TaskId::new("b", "build"); + let b_dev_task_id = TaskId::new("b", "dev"); + + let b_build_idx = engine.get_index(&b_build_task_id); + engine.add_definition( + b_build_task_id.clone(), + TaskDefinition { + task_dependencies: vec![Spanned::new(TaskName::from(a_build_task_id.clone()))], + ..Default::default() + }, + ); + + engine.get_index(&b_dev_task_id); + engine.add_definition(b_dev_task_id.clone(), TaskDefinition::default()); + engine.task_graph.add_edge(b_build_idx, a_build_idx, ()); + + let engine = engine.seal(); + let subgraph = engine.create_engine_for_subgraph(&PackageName::from("a")); + + // Verify that the subgraph only contains tasks from package `a` and the `build` + // task from package `b` + let tasks: Vec<_> = subgraph.tasks().collect(); + assert_eq!(tasks.len(), 3); + assert!(tasks.contains(&&TaskNode::Task(a_build_task_id))); + assert!(tasks.contains(&&TaskNode::Task(a_dev_task_id))); + assert!(tasks.contains(&&TaskNode::Task(b_build_task_id))); + } } diff --git a/crates/turborepo-lib/src/run/task_id.rs b/crates/turborepo-lib/src/run/task_id.rs index b56e32d91b8b6..f3fe276bd5040 100644 --- a/crates/turborepo-lib/src/run/task_id.rs +++ b/crates/turborepo-lib/src/run/task_id.rs @@ -13,7 +13,7 @@ pub struct TaskId<'a> { task: Cow<'a, str>, } -/// A task name as it appears in in a `turbo.json` it might be for all +/// A task name as it appears in a `turbo.json` it might be for all /// workspaces or just one. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] #[serde(try_from = "String", into = "String")] @@ -22,6 +22,16 @@ pub struct TaskName<'a> { task: Cow<'a, str>, } +impl<'a> From> for TaskName<'a> { + fn from(value: TaskId<'a>) -> Self { + let TaskId { package, task } = value; + TaskName { + package: Some(package), + task, + } + } +} + #[derive(Debug, thiserror::Error)] #[error("No workspace found in task id '{input}'")] pub struct TaskIdError<'a> {