go: Add support for running sub-tests in table tests (#35657)
One killer feature for the Go runner is to execute individual subtests within a table-test easily. Goland has had this feature forever, while in VSCode this has been notably missing. https://github.com/user-attachments/assets/363417a2-d1b1-43ca-8377-08ce062d6104 Release Notes: - Added support to run Go table-test subtests.
This commit is contained in:
parent
cc5eb24066
commit
b61b71405d
2 changed files with 439 additions and 6 deletions
|
@ -487,6 +487,8 @@ const GO_MODULE_ROOT_TASK_VARIABLE: VariableName =
|
|||
VariableName::Custom(Cow::Borrowed("GO_MODULE_ROOT"));
|
||||
const GO_SUBTEST_NAME_TASK_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("GO_SUBTEST_NAME"));
|
||||
const GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("GO_TABLE_TEST_CASE_NAME"));
|
||||
|
||||
impl ContextProvider for GoContextProvider {
|
||||
fn build_context(
|
||||
|
@ -545,10 +547,19 @@ impl ContextProvider for GoContextProvider {
|
|||
let go_subtest_variable = extract_subtest_name(_subtest_name.unwrap_or(""))
|
||||
.map(|subtest_name| (GO_SUBTEST_NAME_TASK_VARIABLE.clone(), subtest_name));
|
||||
|
||||
let table_test_case_name = variables.get(&VariableName::Custom(Cow::Borrowed(
|
||||
"_table_test_case_name",
|
||||
)));
|
||||
|
||||
let go_table_test_case_variable = table_test_case_name
|
||||
.and_then(extract_subtest_name)
|
||||
.map(|case_name| (GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE.clone(), case_name));
|
||||
|
||||
Task::ready(Ok(TaskVariables::from_iter(
|
||||
[
|
||||
go_package_variable,
|
||||
go_subtest_variable,
|
||||
go_table_test_case_variable,
|
||||
go_module_root_variable,
|
||||
]
|
||||
.into_iter()
|
||||
|
@ -570,6 +581,28 @@ impl ContextProvider for GoContextProvider {
|
|||
let module_cwd = Some(GO_MODULE_ROOT_TASK_VARIABLE.template_value());
|
||||
|
||||
Task::ready(Some(TaskTemplates(vec![
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
"go test {} -v -run {}/{}",
|
||||
GO_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
VariableName::Symbol.template_value(),
|
||||
GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE.template_value(),
|
||||
),
|
||||
command: "go".into(),
|
||||
args: vec![
|
||||
"test".into(),
|
||||
"-v".into(),
|
||||
"-run".into(),
|
||||
format!(
|
||||
"\\^{}\\$/\\^{}\\$",
|
||||
VariableName::Symbol.template_value(),
|
||||
GO_TABLE_TEST_CASE_NAME_TASK_VARIABLE.template_value(),
|
||||
),
|
||||
],
|
||||
cwd: package_cwd.clone(),
|
||||
tags: vec!["go-table-test-case".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
"go test {} -run {}",
|
||||
|
@ -842,10 +875,21 @@ mod tests {
|
|||
.collect()
|
||||
});
|
||||
|
||||
let tag_strings: Vec<String> = runnables
|
||||
.iter()
|
||||
.flat_map(|r| &r.runnable.tags)
|
||||
.map(|tag| tag.0.to_string())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
runnables.len() == 2,
|
||||
"Should find test function and subtest with double quotes, found: {}",
|
||||
runnables.len()
|
||||
tag_strings.contains(&"go-test".to_string()),
|
||||
"Should find go-test tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
assert!(
|
||||
tag_strings.contains(&"go-subtest".to_string()),
|
||||
"Should find go-subtest tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
|
||||
let buffer = cx.new(|cx| {
|
||||
|
@ -860,10 +904,299 @@ mod tests {
|
|||
.collect()
|
||||
});
|
||||
|
||||
let tag_strings: Vec<String> = runnables
|
||||
.iter()
|
||||
.flat_map(|r| &r.runnable.tags)
|
||||
.map(|tag| tag.0.to_string())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
runnables.len() == 2,
|
||||
"Should find test function and subtest with backticks, found: {}",
|
||||
runnables.len()
|
||||
tag_strings.contains(&"go-test".to_string()),
|
||||
"Should find go-test tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
assert!(
|
||||
tag_strings.contains(&"go-subtest".to_string()),
|
||||
"Should find go-subtest tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_slice_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExample(t *testing.T) {
|
||||
_ = "some random string"
|
||||
|
||||
testCases := []struct{
|
||||
name string
|
||||
anotherStr string
|
||||
}{
|
||||
{
|
||||
name: "test case 1",
|
||||
anotherStr: "foo",
|
||||
},
|
||||
{
|
||||
name: "test case 2",
|
||||
anotherStr: "bar",
|
||||
},
|
||||
}
|
||||
|
||||
notATableTest := []struct{
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "some string",
|
||||
},
|
||||
{
|
||||
name: "some other string",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// test code here
|
||||
})
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let buffer =
|
||||
cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot.runnable_ranges(0..table_test.len()).collect()
|
||||
});
|
||||
|
||||
let tag_strings: Vec<String> = runnables
|
||||
.iter()
|
||||
.flat_map(|r| &r.runnable.tags)
|
||||
.map(|tag| tag.0.to_string())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
tag_strings.contains(&"go-test".to_string()),
|
||||
"Should find go-test tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
assert!(
|
||||
tag_strings.contains(&"go-table-test-case".to_string()),
|
||||
"Should find go-table-test-case tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
|
||||
let go_test_count = tag_strings.iter().filter(|&tag| tag == "go-test").count();
|
||||
let go_table_test_count = tag_strings
|
||||
.iter()
|
||||
.filter(|&tag| tag == "go-table-test-case")
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
go_test_count == 1,
|
||||
"Should find exactly 1 go-test, found: {}",
|
||||
go_test_count
|
||||
);
|
||||
assert!(
|
||||
go_table_test_count == 2,
|
||||
"Should find exactly 2 go-table-test-case, found: {}",
|
||||
go_table_test_count
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_slice_ignored(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
||||
func Example() {
|
||||
_ = "some random string"
|
||||
|
||||
notATableTest := []struct{
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "some string",
|
||||
},
|
||||
{
|
||||
name: "some other string",
|
||||
},
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let buffer =
|
||||
cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot.runnable_ranges(0..table_test.len()).collect()
|
||||
});
|
||||
|
||||
let tag_strings: Vec<String> = runnables
|
||||
.iter()
|
||||
.flat_map(|r| &r.runnable.tags)
|
||||
.map(|tag| tag.0.to_string())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
!tag_strings.contains(&"go-test".to_string()),
|
||||
"Should find go-test tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
assert!(
|
||||
!tag_strings.contains(&"go-table-test-case".to_string()),
|
||||
"Should find go-table-test-case tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_map_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExample(t *testing.T) {
|
||||
_ = "some random string"
|
||||
|
||||
testCases := map[string]struct {
|
||||
someStr string
|
||||
fail bool
|
||||
}{
|
||||
"test failure": {
|
||||
someStr: "foo",
|
||||
fail: true,
|
||||
},
|
||||
"test success": {
|
||||
someStr: "bar",
|
||||
fail: false,
|
||||
},
|
||||
}
|
||||
|
||||
notATableTest := map[string]struct {
|
||||
someStr string
|
||||
}{
|
||||
"some string": {
|
||||
someStr: "foo",
|
||||
},
|
||||
"some other string": {
|
||||
someStr: "bar",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// test code here
|
||||
})
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let buffer =
|
||||
cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot.runnable_ranges(0..table_test.len()).collect()
|
||||
});
|
||||
|
||||
let tag_strings: Vec<String> = runnables
|
||||
.iter()
|
||||
.flat_map(|r| &r.runnable.tags)
|
||||
.map(|tag| tag.0.to_string())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
tag_strings.contains(&"go-test".to_string()),
|
||||
"Should find go-test tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
assert!(
|
||||
tag_strings.contains(&"go-table-test-case".to_string()),
|
||||
"Should find go-table-test-case tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
|
||||
let go_test_count = tag_strings.iter().filter(|&tag| tag == "go-test").count();
|
||||
let go_table_test_count = tag_strings
|
||||
.iter()
|
||||
.filter(|&tag| tag == "go-table-test-case")
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
go_test_count == 1,
|
||||
"Should find exactly 1 go-test, found: {}",
|
||||
go_test_count
|
||||
);
|
||||
assert!(
|
||||
go_table_test_count == 2,
|
||||
"Should find exactly 2 go-table-test-case, found: {}",
|
||||
go_table_test_count
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_map_ignored(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
||||
func Example() {
|
||||
_ = "some random string"
|
||||
|
||||
notATableTest := map[string]struct {
|
||||
someStr string
|
||||
}{
|
||||
"some string": {
|
||||
someStr: "foo",
|
||||
},
|
||||
"some other string": {
|
||||
someStr: "bar",
|
||||
},
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let buffer =
|
||||
cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot.runnable_ranges(0..table_test.len()).collect()
|
||||
});
|
||||
|
||||
let tag_strings: Vec<String> = runnables
|
||||
.iter()
|
||||
.flat_map(|r| &r.runnable.tags)
|
||||
.map(|tag| tag.0.to_string())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
!tag_strings.contains(&"go-test".to_string()),
|
||||
"Should find go-test tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
assert!(
|
||||
!tag_strings.contains(&"go-table-test-case".to_string()),
|
||||
"Should find go-table-test-case tag, found: {:?}",
|
||||
tag_strings
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -91,3 +91,103 @@
|
|||
) @_
|
||||
(#set! tag go-main)
|
||||
)
|
||||
|
||||
; Table test cases - slice and map
|
||||
(
|
||||
(short_var_declaration
|
||||
left: (expression_list (identifier) @_collection_var)
|
||||
right: (expression_list
|
||||
(composite_literal
|
||||
type: [
|
||||
(slice_type)
|
||||
(map_type
|
||||
key: (type_identifier) @_key_type
|
||||
(#eq? @_key_type "string")
|
||||
)
|
||||
]
|
||||
body: (literal_value
|
||||
[
|
||||
(literal_element
|
||||
(literal_value
|
||||
(keyed_element
|
||||
(literal_element
|
||||
(identifier) @_field_name
|
||||
)
|
||||
(literal_element
|
||||
[
|
||||
(interpreted_string_literal) @run @_table_test_case_name
|
||||
(raw_string_literal) @run @_table_test_case_name
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(keyed_element
|
||||
(literal_element
|
||||
[
|
||||
(interpreted_string_literal) @run @_table_test_case_name
|
||||
(raw_string_literal) @run @_table_test_case_name
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(for_statement
|
||||
(range_clause
|
||||
left: (expression_list
|
||||
[
|
||||
(
|
||||
(identifier)
|
||||
(identifier) @_loop_var
|
||||
)
|
||||
(identifier) @_loop_var
|
||||
]
|
||||
)
|
||||
right: (identifier) @_range_var
|
||||
(#eq? @_range_var @_collection_var)
|
||||
)
|
||||
body: (block
|
||||
(expression_statement
|
||||
(call_expression
|
||||
function: (selector_expression
|
||||
operand: (identifier) @_t_var
|
||||
field: (field_identifier) @_run_method
|
||||
(#eq? @_run_method "Run")
|
||||
)
|
||||
arguments: (argument_list
|
||||
.
|
||||
[
|
||||
(selector_expression
|
||||
operand: (identifier) @_tc_var
|
||||
(#eq? @_tc_var @_loop_var)
|
||||
field: (field_identifier) @_field_check
|
||||
(#eq? @_field_check @_field_name)
|
||||
)
|
||||
(identifier) @_arg_var
|
||||
(#eq? @_arg_var @_loop_var)
|
||||
]
|
||||
.
|
||||
(func_literal
|
||||
parameters: (parameter_list
|
||||
(parameter_declaration
|
||||
type: (pointer_type
|
||||
(qualified_type
|
||||
package: (package_identifier) @_pkg
|
||||
name: (type_identifier) @_type
|
||||
(#eq? @_pkg "testing")
|
||||
(#eq? @_type "T")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
) @_
|
||||
(#set! tag go-table-test-case)
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue