Add support for detecting tests in source files, and implement it for Rust (#11195)

Continuing work from #10873 

Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Piotr Osiewicz
2024-05-05 16:32:48 +02:00
committed by GitHub
co-authored by Mikayla
parent 14c7782ce6
commit 5a71d8c7f1
29 changed files with 1147 additions and 605 deletions
+10 -15
View File
@@ -7655,17 +7655,15 @@ impl Project {
} else {
let fs = self.fs.clone();
let task_abs_path = abs_path.clone();
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
task_inventory.add_source(
TaskSourceKind::Worktree {
id: remote_worktree_id,
abs_path,
id_base: "local_tasks_for_worktree",
},
|cx| {
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
StaticSource::new(TrackedFile::new(tasks_file_rx, cx), cx)
},
StaticSource::new(TrackedFile::new(tasks_file_rx, cx)),
cx,
);
}
@@ -7677,23 +7675,20 @@ impl Project {
} else {
let fs = self.fs.clone();
let task_abs_path = abs_path.clone();
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
task_inventory.add_source(
TaskSourceKind::Worktree {
id: remote_worktree_id,
abs_path,
id_base: "local_vscode_tasks_for_worktree",
},
|cx| {
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
StaticSource::new(
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
tasks_file_rx,
cx,
),
StaticSource::new(
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
tasks_file_rx,
cx,
)
},
),
),
cx,
);
}
+29 -50
View File
@@ -14,7 +14,7 @@ use serde_json::json;
#[cfg(not(windows))]
use std::os;
use std::task::Poll;
use task::{TaskContext, TaskSource, TaskTemplate, TaskTemplates};
use task::{TaskContext, TaskTemplate, TaskTemplates};
use unindent::Unindent as _;
use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
use worktree::WorktreeModelHandle as _;
@@ -168,12 +168,11 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
let all_tasks = project
.update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
project.task_inventory().update(cx, |inventory, _| {
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),
&task_context,
cx,
);
old.extend(new);
old
@@ -215,13 +214,9 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
project.update(cx, |project, cx| {
let inventory = project.task_inventory();
inventory.update(cx, |inventory, cx| {
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),
&task_context,
cx,
);
inventory.update(cx, |inventory, _| {
let (mut old, new) =
inventory.used_and_current_resolved_tasks(None, Some(workree_id), &task_context);
old.extend(new);
let (_, resolved_task) = old
.into_iter()
@@ -231,41 +226,39 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
})
});
let tasks = serde_json::to_string(&TaskTemplates(vec![TaskTemplate {
label: "cargo check".to_string(),
command: "cargo".to_string(),
args: vec![
"check".to_string(),
"--all".to_string(),
"--all-targets".to_string(),
],
env: HashMap::from_iter(Some((
"RUSTFLAGS".to_string(),
"-Zunstable-options".to_string(),
))),
..TaskTemplate::default()
}]))
.unwrap();
let (tx, rx) = futures::channel::mpsc::unbounded();
let templates = cx.update(|cx| TrackedFile::new(rx, cx));
tx.unbounded_send(tasks).unwrap();
let source = StaticSource::new(templates);
cx.run_until_parked();
cx.update(|cx| {
let all_tasks = project
.update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json"));
inventory.add_source(
global_task_source_kind.clone(),
|cx| {
cx.new_model(|_| {
let source = TestTaskSource {
tasks: TaskTemplates(vec![TaskTemplate {
label: "cargo check".to_string(),
command: "cargo".to_string(),
args: vec![
"check".to_string(),
"--all".to_string(),
"--all-targets".to_string(),
],
env: HashMap::from_iter(Some((
"RUSTFLAGS".to_string(),
"-Zunstable-options".to_string(),
))),
..TaskTemplate::default()
}]),
};
Box::new(source) as Box<_>
})
},
cx,
);
inventory.add_source(global_task_source_kind.clone(), source, cx);
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),
&task_context,
cx,
);
old.extend(new);
old
@@ -317,20 +310,6 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
});
}
struct TestTaskSource {
tasks: TaskTemplates,
}
impl TaskSource for TestTaskSource {
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn tasks_to_schedule(&mut self, _: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates {
self.tasks.clone()
}
}
#[gpui::test]
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
init_test(cx);
+69 -152
View File
@@ -1,17 +1,18 @@
//! Project-wide storage of the tasks available, capable of updating itself from the sources set.
use std::{
any::TypeId,
cmp::{self, Reverse},
path::{Path, PathBuf},
sync::Arc,
};
use collections::{hash_map, HashMap, VecDeque};
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
use gpui::{AppContext, Context, Model, ModelContext};
use itertools::{Either, Itertools};
use language::Language;
use task::{ResolvedTask, TaskContext, TaskId, TaskSource, TaskTemplate, VariableName};
use task::{
static_source::StaticSource, ResolvedTask, TaskContext, TaskId, TaskTemplate, VariableName,
};
use util::{post_inc, NumericPrefixWithSuffix};
use worktree::WorktreeId;
@@ -22,14 +23,12 @@ pub struct Inventory {
}
struct SourceInInventory {
source: Model<Box<dyn TaskSource>>,
_subscription: Subscription,
type_id: TypeId,
source: StaticSource,
kind: TaskSourceKind,
}
/// Kind of a source the tasks are fetched from, used to display more source information in the UI.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TaskSourceKind {
/// bash-like commands spawned by users, not associated with any path
UserInput,
@@ -95,7 +94,7 @@ impl Inventory {
pub fn add_source(
&mut self,
kind: TaskSourceKind,
create_source: impl FnOnce(&mut ModelContext<Self>) -> Model<Box<dyn TaskSource>>,
source: StaticSource,
cx: &mut ModelContext<Self>,
) {
let abs_path = kind.abs_path();
@@ -106,16 +105,7 @@ impl Inventory {
}
}
let source = create_source(cx);
let type_id = source.read(cx).type_id();
let source = SourceInInventory {
_subscription: cx.observe(&source, |_, _, cx| {
cx.notify();
}),
source,
type_id,
kind,
};
let source = SourceInInventory { source, kind };
self.sources.push(source);
cx.notify();
}
@@ -136,31 +126,12 @@ impl Inventory {
self.sources.retain(|s| s.kind.worktree() != Some(worktree));
}
pub fn source<T: TaskSource>(&self) -> Option<(Model<Box<dyn TaskSource>>, TaskSourceKind)> {
let target_type_id = std::any::TypeId::of::<T>();
self.sources.iter().find_map(
|SourceInInventory {
type_id,
source,
kind,
..
}| {
if &target_type_id == type_id {
Some((source.clone(), kind.clone()))
} else {
None
}
},
)
}
/// Pulls its task sources relevant to the worktree and the language given,
/// returns all task templates with their source kinds, in no specific order.
pub fn list_tasks(
&self,
language: Option<Arc<Language>>,
worktree: Option<WorktreeId>,
cx: &mut AppContext,
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name(),
@@ -180,7 +151,7 @@ impl Inventory {
.flat_map(|source| {
source
.source
.update(cx, |source, cx| source.tasks_to_schedule(cx))
.tasks_to_schedule()
.0
.into_iter()
.map(|task| (&source.kind, task))
@@ -199,7 +170,6 @@ impl Inventory {
language: Option<Arc<Language>>,
worktree: Option<WorktreeId>,
task_context: &TaskContext,
cx: &mut AppContext,
) -> (
Vec<(TaskSourceKind, ResolvedTask)>,
Vec<(TaskSourceKind, ResolvedTask)>,
@@ -246,7 +216,7 @@ impl Inventory {
.flat_map(|source| {
source
.source
.update(cx, |source, cx| source.tasks_to_schedule(cx))
.tasks_to_schedule()
.0
.into_iter()
.map(|task| (&source.kind, task))
@@ -387,9 +357,12 @@ fn task_variables_preference(task: &ResolvedTask) -> Reverse<usize> {
#[cfg(test)]
mod test_inventory {
use gpui::{AppContext, Context as _, Model, ModelContext, TestAppContext};
use gpui::{AppContext, Model, TestAppContext};
use itertools::Itertools;
use task::{TaskContext, TaskId, TaskSource, TaskTemplate, TaskTemplates};
use task::{
static_source::{StaticSource, TrackedFile},
TaskContext, TaskTemplate, TaskTemplates,
};
use worktree::WorktreeId;
use crate::Inventory;
@@ -398,55 +371,28 @@ mod test_inventory {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestTask {
id: task::TaskId,
name: String,
}
pub struct StaticTestSource {
pub tasks: Vec<TestTask>,
}
impl StaticTestSource {
pub(super) fn new(
task_names: impl IntoIterator<Item = String>,
cx: &mut AppContext,
) -> Model<Box<dyn TaskSource>> {
cx.new_model(|_| {
Box::new(Self {
tasks: task_names
.into_iter()
.enumerate()
.map(|(i, name)| TestTask {
id: TaskId(format!("task_{i}_{name}")),
name,
})
.collect(),
}) as Box<dyn TaskSource>
})
}
}
impl TaskSource for StaticTestSource {
fn tasks_to_schedule(
&mut self,
_cx: &mut ModelContext<Box<dyn TaskSource>>,
) -> TaskTemplates {
TaskTemplates(
self.tasks
.clone()
.into_iter()
.map(|task| TaskTemplate {
label: task.name,
command: "test command".to_string(),
..TaskTemplate::default()
})
.collect(),
)
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
pub(super) fn static_test_source(
task_names: impl IntoIterator<Item = String>,
cx: &mut AppContext,
) -> StaticSource {
let tasks = TaskTemplates(
task_names
.into_iter()
.map(|name| TaskTemplate {
label: name,
command: "test command".to_owned(),
..TaskTemplate::default()
})
.collect(),
);
let (tx, rx) = futures::channel::mpsc::unbounded();
let file = TrackedFile::new(rx, cx);
tx.unbounded_send(serde_json::to_string(&tasks).unwrap())
.unwrap();
StaticSource::new(file)
}
pub(super) fn task_template_names(
@@ -454,9 +400,9 @@ mod test_inventory {
worktree: Option<WorktreeId>,
cx: &mut TestAppContext,
) -> Vec<String> {
inventory.update(cx, |inventory, cx| {
inventory.update(cx, |inventory, _| {
inventory
.list_tasks(None, worktree, cx)
.list_tasks(None, worktree)
.into_iter()
.map(|(_, task)| task.label)
.sorted()
@@ -469,13 +415,9 @@ mod test_inventory {
worktree: Option<WorktreeId>,
cx: &mut TestAppContext,
) -> Vec<String> {
inventory.update(cx, |inventory, cx| {
let (used, current) = inventory.used_and_current_resolved_tasks(
None,
worktree,
&TaskContext::default(),
cx,
);
inventory.update(cx, |inventory, _| {
let (used, current) =
inventory.used_and_current_resolved_tasks(None, worktree, &TaskContext::default());
used.into_iter()
.chain(current)
.map(|(_, task)| task.original_task().label.clone())
@@ -488,9 +430,9 @@ mod test_inventory {
task_name: &str,
cx: &mut TestAppContext,
) {
inventory.update(cx, |inventory, cx| {
inventory.update(cx, |inventory, _| {
let (task_source_kind, task) = inventory
.list_tasks(None, None, cx)
.list_tasks(None, None)
.into_iter()
.find(|(_, task)| task.label == task_name)
.unwrap_or_else(|| panic!("Failed to find task with name {task_name}"));
@@ -508,13 +450,9 @@ mod test_inventory {
worktree: Option<WorktreeId>,
cx: &mut TestAppContext,
) -> Vec<(TaskSourceKind, String)> {
inventory.update(cx, |inventory, cx| {
let (used, current) = inventory.used_and_current_resolved_tasks(
None,
worktree,
&TaskContext::default(),
cx,
);
inventory.update(cx, |inventory, _| {
let (used, current) =
inventory.used_and_current_resolved_tasks(None, worktree, &TaskContext::default());
let mut all = used;
all.extend(current);
all.into_iter()
@@ -549,27 +487,25 @@ mod tests {
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
|cx| StaticTestSource::new(vec!["3_task".to_string()], cx),
static_test_source(vec!["3_task".to_string()], cx),
cx,
);
});
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
|cx| {
StaticTestSource::new(
vec![
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
],
cx,
)
},
static_test_source(
vec![
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
],
cx,
),
cx,
);
});
cx.run_until_parked();
let expected_initial_state = [
"1_a_task".to_string(),
"1_task".to_string(),
@@ -622,12 +558,11 @@ mod tests {
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
|cx| {
StaticTestSource::new(vec!["10_hello".to_string(), "11_hello".to_string()], cx)
},
static_test_source(vec!["10_hello".to_string(), "11_hello".to_string()], cx),
cx,
);
});
cx.run_until_parked();
let expected_updated_state = [
"10_hello".to_string(),
"11_hello".to_string(),
@@ -680,15 +615,11 @@ mod tests {
let worktree_path_1 = Path::new("worktree_path_1");
let worktree_2 = WorktreeId::from_usize(2);
let worktree_path_2 = Path::new("worktree_path_2");
inventory_with_statics.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
|cx| {
StaticTestSource::new(
vec!["user_input".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(vec!["user_input".to_string(), common_name.to_string()], cx),
cx,
);
inventory.add_source(
@@ -696,12 +627,10 @@ mod tests {
id_base: "test source",
abs_path: path_1.to_path_buf(),
},
|cx| {
StaticTestSource::new(
vec!["static_source_1".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(
vec!["static_source_1".to_string(), common_name.to_string()],
cx,
),
cx,
);
inventory.add_source(
@@ -709,12 +638,10 @@ mod tests {
id_base: "test source",
abs_path: path_2.to_path_buf(),
},
|cx| {
StaticTestSource::new(
vec!["static_source_2".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(
vec!["static_source_2".to_string(), common_name.to_string()],
cx,
),
cx,
);
inventory.add_source(
@@ -723,12 +650,7 @@ mod tests {
abs_path: worktree_path_1.to_path_buf(),
id_base: "test_source",
},
|cx| {
StaticTestSource::new(
vec!["worktree_1".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(vec!["worktree_1".to_string(), common_name.to_string()], cx),
cx,
);
inventory.add_source(
@@ -737,16 +659,11 @@ mod tests {
abs_path: worktree_path_2.to_path_buf(),
id_base: "test_source",
},
|cx| {
StaticTestSource::new(
vec!["worktree_2".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(vec!["worktree_2".to_string(), common_name.to_string()], cx),
cx,
);
});
cx.run_until_parked();
let worktree_independent_tasks = vec![
(
TaskSourceKind::AbsPath {