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 <mrnugget@gmail.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
Anıl Şenay 2024-05-26 16:16:52 +03:00 committed by GitHub
parent 5665cad250
commit ddb551c794
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 155 additions and 23 deletions

View file

@ -39,6 +39,9 @@ impl GoLspAdapter {
lazy_static! { lazy_static! {
static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap(); 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)] #[async_trait(?Send)]
@ -443,6 +446,8 @@ fn adjust_runs(
pub(crate) struct GoContextProvider; pub(crate) struct GoContextProvider;
const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE")); 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 { impl ContextProvider for GoContextProvider {
fn build_context( fn build_context(
@ -457,11 +462,10 @@ impl ContextProvider for GoContextProvider {
.file() .file()
.and_then(|file| Some(file.as_local()?.abs_path(cx))); .and_then(|file| Some(file.as_local()?.abs_path(cx)));
Ok( let go_package_variable = local_abs_path
if let Some(buffer_dir) = local_abs_path
.as_deref() .as_deref()
.and_then(|local_abs_path| local_abs_path.parent()) .and_then(|local_abs_path| local_abs_path.parent())
{ .map(|buffer_dir| {
// Prefer the relative form `./my-nested-package/is-here` over // Prefer the relative form `./my-nested-package/is-here` over
// absolute path, because it's more readable in the modal, but // absolute path, because it's more readable in the modal, but
// the absolute path also works. // the absolute path also works.
@ -477,14 +481,19 @@ impl ContextProvider for GoContextProvider {
}) })
.unwrap_or_else(|| format!("{}", buffer_dir.to_string_lossy())); .unwrap_or_else(|| format!("{}", buffer_dir.to_string_lossy()));
TaskVariables::from_iter(Some(( (GO_PACKAGE_TASK_VARIABLE.clone(), package_name.to_string())
GO_PACKAGE_TASK_VARIABLE.clone(), });
package_name.to_string(),
))) let _subtest_name = variables.get(&VariableName::Custom(Cow::Borrowed("_subtest_name")));
} else {
TaskVariables::default() 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<TaskTemplates> { fn associated_tasks(&self) -> Option<TaskTemplates> {
@ -517,6 +526,46 @@ impl ContextProvider for GoContextProvider {
args: vec!["test".into(), "./...".into()], args: vec!["test".into(), "./...".into()],
..TaskTemplate::default() ..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 { TaskTemplate {
label: format!("go run {}", GO_PACKAGE_TASK_VARIABLE.template_value(),), label: format!("go run {}", GO_PACKAGE_TASK_VARIABLE.template_value(),),
command: "go".into(), command: "go".into(),
@ -528,6 +577,18 @@ impl ContextProvider for GoContextProvider {
} }
} }
fn extract_subtest_name(input: &str) -> Option<String> {
let replaced_spaces = input.trim_matches('"').replace(' ', "_");
Some(
GO_ESCAPE_SUBTEST_NAME_REGEX
.replace_all(&replaced_spaces, |caps: &regex::Captures| {
format!("\\{}", &caps[0])
})
.to_string(),
)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -1,3 +1,4 @@
; Functions names start with `Test`
( (
( (
(function_declaration name: (_) @run (function_declaration name: (_) @run
@ -6,6 +7,52 @@
(#set! tag go-test) (#set! tag go-test)
) )
; `t.Run`
(
(
(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 (function_declaration name: (_) @run

View file

@ -113,6 +113,7 @@ pub fn init(
vec![Arc::new(go::GoLspAdapter)], vec![Arc::new(go::GoLspAdapter)],
GoContextProvider GoContextProvider
); );
language!( language!(
"json", "json",
vec![Arc::new(json::JsonLspAdapter::new( vec![Arc::new(json::JsonLspAdapter::new(

View file

@ -679,4 +679,27 @@ mod tests {
expected.sort_by_key(|var| var.to_string()); expected.sort_by_key(|var| var.to_string());
assert_eq!(resolved_variables, expected) 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());
}
} }