From ddb551c794345538dbef112c5bcc2ae99f27b07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C4=B1l=20=C5=9Eenay?= Date: Sun, 26 May 2024 16:16:52 +0300 Subject: [PATCH] go: Add runnables for Go (#12003) Implemented runnables for specially for running tests for Go. I'm grateful for your feedback because this is my first experience with Rust and Zed codebase. ![resim](https://github.com/zed-industries/zed/assets/1047345/789b31da-554f-47cd-a08c-444eced104f4) https://github.com/zed-industries/zed/assets/1047345/ae1abd9e-3657-4322-9c28-02d0752b5ccd Release Notes: - Added Runnables/Tasks for: - Run test functions which start with "Test" - Run subtests - Run benchmark tests - Run main function --------- Co-authored-by: Thorsten Ball Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- crates/languages/src/go.rs | 87 +++++++++++++++++++++++---- crates/languages/src/go/runnables.scm | 67 ++++++++++++++++++--- crates/languages/src/lib.rs | 1 + crates/task/src/task_template.rs | 23 +++++++ 4 files changed, 155 insertions(+), 23 deletions(-) diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 8b35220234..30f9f35363 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -39,6 +39,9 @@ impl GoLspAdapter { lazy_static! { static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap(); + static ref GO_EXTRACT_SUBTEST_NAME_REGEX: Regex = + Regex::new(r#".*t\.Run\("([^"]*)".*"#).unwrap(); + static ref GO_ESCAPE_SUBTEST_NAME_REGEX: Regex = Regex::new(r#"[.*+?^${}()|\[\]\\]"#).unwrap(); } #[async_trait(?Send)] @@ -443,6 +446,8 @@ fn adjust_runs( pub(crate) struct GoContextProvider; const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE")); +const GO_SUBTEST_NAME_TASK_VARIABLE: VariableName = + VariableName::Custom(Cow::Borrowed("GO_SUBTEST_NAME")); impl ContextProvider for GoContextProvider { fn build_context( @@ -457,11 +462,10 @@ impl ContextProvider for GoContextProvider { .file() .and_then(|file| Some(file.as_local()?.abs_path(cx))); - Ok( - if let Some(buffer_dir) = local_abs_path - .as_deref() - .and_then(|local_abs_path| local_abs_path.parent()) - { + let go_package_variable = local_abs_path + .as_deref() + .and_then(|local_abs_path| local_abs_path.parent()) + .map(|buffer_dir| { // Prefer the relative form `./my-nested-package/is-here` over // absolute path, because it's more readable in the modal, but // the absolute path also works. @@ -477,14 +481,19 @@ impl ContextProvider for GoContextProvider { }) .unwrap_or_else(|| format!("{}", buffer_dir.to_string_lossy())); - TaskVariables::from_iter(Some(( - GO_PACKAGE_TASK_VARIABLE.clone(), - package_name.to_string(), - ))) - } else { - TaskVariables::default() - }, - ) + (GO_PACKAGE_TASK_VARIABLE.clone(), package_name.to_string()) + }); + + let _subtest_name = variables.get(&VariableName::Custom(Cow::Borrowed("_subtest_name"))); + + let go_subtest_variable = extract_subtest_name(_subtest_name.unwrap_or("")) + .map(|subtest_name| (GO_SUBTEST_NAME_TASK_VARIABLE.clone(), subtest_name)); + + Ok(TaskVariables::from_iter( + [go_package_variable, go_subtest_variable] + .into_iter() + .flatten(), + )) } fn associated_tasks(&self) -> Option { @@ -517,6 +526,46 @@ impl ContextProvider for GoContextProvider { args: vec!["test".into(), "./...".into()], ..TaskTemplate::default() }, + TaskTemplate { + label: format!( + "go test {} -run {}/{}", + GO_PACKAGE_TASK_VARIABLE.template_value(), + VariableName::Symbol.template_value(), + GO_SUBTEST_NAME_TASK_VARIABLE.template_value(), + ), + command: "go".into(), + args: vec![ + "test".into(), + GO_PACKAGE_TASK_VARIABLE.template_value(), + "-v".into(), + "-run".into(), + format!( + "^{}$/^{}$", + VariableName::Symbol.template_value(), + GO_SUBTEST_NAME_TASK_VARIABLE.template_value(), + ), + ], + tags: vec!["go-subtest".to_owned()], + ..TaskTemplate::default() + }, + TaskTemplate { + label: format!( + "go test {} -bench {}", + GO_PACKAGE_TASK_VARIABLE.template_value(), + VariableName::Symbol.template_value() + ), + command: "go".into(), + args: vec![ + "test".into(), + GO_PACKAGE_TASK_VARIABLE.template_value(), + "-benchmem".into(), + "-run=^$".into(), + "-bench".into(), + format!("^{}$", VariableName::Symbol.template_value()), + ], + tags: vec!["go-benchmark".to_owned()], + ..TaskTemplate::default() + }, TaskTemplate { label: format!("go run {}", GO_PACKAGE_TASK_VARIABLE.template_value(),), command: "go".into(), @@ -528,6 +577,18 @@ impl ContextProvider for GoContextProvider { } } +fn extract_subtest_name(input: &str) -> Option { + let replaced_spaces = input.trim_matches('"').replace(' ', "_"); + + Some( + GO_ESCAPE_SUBTEST_NAME_REGEX + .replace_all(&replaced_spaces, |caps: ®ex::Captures| { + format!("\\{}", &caps[0]) + }) + .to_string(), + ) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/languages/src/go/runnables.scm b/crates/languages/src/go/runnables.scm index d5d01879e2..d689116007 100644 --- a/crates/languages/src/go/runnables.scm +++ b/crates/languages/src/go/runnables.scm @@ -1,15 +1,62 @@ +; Functions names start with `Test` ( - ( - (function_declaration name: (_) @run - (#match? @run "^Test.*")) - ) @_ - (#set! tag go-test) + ( + (function_declaration name: (_) @run + (#match? @run "^Test.*")) + ) @_ + (#set! tag go-test) ) +; `t.Run` ( - ( - (function_declaration name: (_) @run - (#eq? @run "main")) - ) @_ - (#set! tag go-main) + ( + (call_expression + function: ( + selector_expression + field: _ @run @_name + (#eq? @_name "Run") + ) + arguments: ( + argument_list + . + (interpreted_string_literal) @_subtest_name + . + (func_literal + parameters: ( + parameter_list + (parameter_declaration + name: (identifier) @_param_name + type: (pointer_type + (qualified_type + package: (package_identifier) @_pkg + name: (type_identifier) @_type + (#eq? @_pkg "testing") + (#eq? @_type "T") + ) + ) + ) + ) + ) @_second_argument + ) + ) + ) @_ + (#set! tag go-subtest) +) + +; Functions names start with `Benchmark` +( + ( + (function_declaration name: (_) @run @_name + (#match? @_name "^Benchmark.+")) + ) @_ + (#set! tag go-benchmark) +) + +; go run +( + ( + (function_declaration name: (_) @run + (#eq? @run "main")) + ) @_ + (#set! tag go-main) ) diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index fadc182180..ec7a0d3889 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -113,6 +113,7 @@ pub fn init( vec![Arc::new(go::GoLspAdapter)], GoContextProvider ); + language!( "json", vec![Arc::new(json::JsonLspAdapter::new( diff --git a/crates/task/src/task_template.rs b/crates/task/src/task_template.rs index 2a48e48f3d..78643f14c1 100644 --- a/crates/task/src/task_template.rs +++ b/crates/task/src/task_template.rs @@ -679,4 +679,27 @@ mod tests { expected.sort_by_key(|var| var.to_string()); assert_eq!(resolved_variables, expected) } + + #[test] + fn substitute_funky_labels() { + let faulty_go_test = TaskTemplate { + label: format!( + "go test {}/{}", + VariableName::Symbol.template_value(), + VariableName::Symbol.template_value(), + ), + command: "go".into(), + args: vec![format!( + "^{}$/^{}$", + VariableName::Symbol.template_value(), + VariableName::Symbol.template_value() + )], + ..TaskTemplate::default() + }; + let mut context = TaskContext::default(); + context + .task_variables + .insert(VariableName::Symbol, "my-symbol".to_string()); + assert!(faulty_go_test.resolve_task("base", &context).is_some()); + } }