Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go: Add runnables for Go #12003

Merged
merged 16 commits into from
May 26, 2024
Merged

go: Add runnables for Go #12003

merged 16 commits into from
May 26, 2024

Conversation

anilsenay
Copy link
Contributor

@anilsenay anilsenay commented May 17, 2024

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.

Release Notes:

  • Added Runnables/Tasks for:
    • Run test functions which start with "Test"
    • Run subtests
    • Run benchmark tests
    • Run main function

resim

Screen.Recording.2024-05-21.at.23.16.15.mov

Copy link

cla-bot bot commented May 17, 2024

We require contributors to sign our Contributor License Agreement, and we don't have @anilsenay on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@anilsenay
Copy link
Contributor Author

@cla-bot check

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label May 17, 2024
Copy link

cla-bot bot commented May 17, 2024

The cla-bot has been summoned, and re-checked this pull request!

@anilsenay anilsenay changed the title Add runnables for Go go: Add runnables for Go May 18, 2024
@SomeoneToIgnore SomeoneToIgnore removed their assignment May 21, 2024
let snapshot = location.buffer.read(cx).snapshot();
let point_range = location.range.to_point(&snapshot);
let end = Point::new(point_range.start.row + 1, 0);
let line = snapshot.text_for_range(point_range.start..end).peek();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense for build_context function to get access to context built up so far; that way, it could inspect variables set by the captures and you wouldn't have to extract function name from current line, which in my opinion isn't ideal. You really had no better tool at your disposal though.
Tomorrow I'll wire through the changes that will allow us to simplify that function a bit.

Copy link
Contributor Author

@anilsenay anilsenay May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@osiewicz Unfortunately, I haven't found a better way to get the line. As you said, It would be better if I could just access viabuild_context more easily. I would appreciate your assistance 🙏

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries at all! It's not like there was a better way either.
I've opened a PR in #12134; once that lands,
you'll be able to capture the first parameter (that's a string) as a hidden variable, process it (stripping "Run" prefix) and then save it as the SUBTASK_NAME.

@d1y
Copy link
Contributor

d1y commented May 22, 2024

@mrnugget #12110 Are these two duplicates?

@aexvir
Copy link
Contributor

aexvir commented May 22, 2024

@mrnugget #12110 Are these two duplicates?

they do seem to overlap, yep, although this one adds support for subtests (and benchmarks) so I hope it can be rebased and merged still
most of the tests I usually run have subtests so I find it extremely convenient to be able to run specific cases

@mrnugget
Copy link
Member

Woops, sorry! Didn't see this one here, I'll try to help with the rebase.

@anilsenay
Copy link
Contributor Author

Woops, sorry! Didn't see this one here, I'll try to help with the rebase.

Ahaha its okey 🙏 I haven't rebase it to avoid overlap your code. You may review my code and change as you like while doing rebase.

@mrnugget
Copy link
Member

Alright, I rebased and combined the code. Clicking on subtests works, but there's something fishy going on that stops them from showing up in the modal. They only show up when on first character of the line that contains t.Run. @osiewicz is looking into it.

@anilsenay
Copy link
Contributor Author

Alright, I rebased and combined the code. Clicking on subtests works, but there's something fishy going on that stops them from showing up in the modal. They only show up when on first character of the line that contains t.Run. @osiewicz is looking into it.

Thank you @mrnugget 🎉

Alright, he would also look for a better way to get the selected line. Then I'll leave it to him. I'll also look at what I can do.

@mrnugget
Copy link
Member

Yeah, I also pushed a commit to use the whole line.

Comment on lines 490 to 492
let line = snapshot.text_for_range(start..end).peek();

let go_subtest_variable = extract_subtest_name(line.unwrap_or(""))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think peek() does not return the whole line. I debugged line and it has not contain all line content. I tried to use collect and then got the whole line. Could you take a look for this issue?

let line = snapshot.text_for_range(start..end).collect::<String>();

let go_subtest_variable = extract_subtest_name(line.as_str())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does for me.

[crates/languages/src/go.rs:492:9] &line = Some(
    "\tt.Run(\"Foobar\", func(t *testing.T) {\n",
)
[crates/languages/src/go.rs:492:9] &line = Some(
    "\t\tfmt.Println(\"subtest\")\n",
)

@anilsenay
Copy link
Contributor Author

Yes, that's exactly the problem. It is because we're parsing the current line to look for name of subtest, but it may actually reside on a different line.

Honestly though, that's totally not your fault. It is a systemic problem (so to speak); right now we always use your current cursor position to run task context evaluation, but here it's not gonna cut it, as your context is sourced elsewhere. E.g: If you're in the middle of a test, then that test is your context. Then there's also a matter of nesting. Even though you're in a _sub_test, we should also be grabbing the context from the func TestSomething as well.

Got it, thank you for explanation 🙏
I was focusing on rendering run indicators, i wasn't aware of this modal problem until you mention it, lol.
I guess this is beyond me but I'll try to help if I figure out something via suggestion or something.

@thnt
Copy link

thnt commented May 23, 2024

Since MR #12110 has been merged, I'm reporting here:
Because #10224 has been resolved, I expect this also works for nested folder without go.work

I also want to able to configure custom test args, ex:

{
 "languages": {
    "Go": {
      "test_args": ["-v", "-cover", "-failfast"]
    }
  }
}

@anilsenay
Copy link
Contributor Author

Since MR #12110 has been merged, I'm reporting here: Because #10224 has been resolved, I expect this also works for nested folder without go.work

I also want to able to configure custom test args, ex:

{
 "languages": {
    "Go": {
      "test_args": ["-v", "-cover", "-failfast"]
    }
  }
}

Oh right, our way to set GO_PACKAGE_TASK_VARIABLE is not working for submodules. I think we should fix it.
Some solutions came in mind:

  1. Finding go module name of running test(from go.mod) and run test in that directory (i think it can be done with cwd) as {module_name}/{the_package_name_of_test}

for example, in a working directory /a and /b is different modules which has different go.mod file. When running a test such as /a/pkg/boo_test.go,

  • cwd should be /a,
  • command should be go test module-a/pkg -run TestXxx or path can be used without module name: go test ./pkg -run TestXxx

I guess vscode-go extension doing similar like this. In addition, module names are listed in the Testing tab. So getting module name of a go file may be useful for us even later.

  1. Easily, using file's absolute path instead of GO_PACKAGE_TASK_VARIABLE like go test /Users/user/my-app/a/b/foo_test.go - run TestXxx

@mrnugget @osiewicz


I agree that configurable test args could be helpful. For example I changed test timeout argument in vscode. I can be done by injecting user's custom arg list to task's args list maybe. Do you have any opinion or future plans for this suggestion @mrnugget @osiewicz ?

@osiewicz
Copy link
Contributor

osiewicz commented May 23, 2024

First off, if the user has a task in their tasks.json that's bound to your tag, that's gonna take the precedence over language-provided tasks, so there's already a way for users to override tasks from language.
I think this is a valid use case though; we need a way for user to configure language tasks.
I imagine that we could have a section in settings.json where an user could set their own value for a specific task variable. Then, in build_context you could set that task variable only if there's no user-supplied value.

osiewicz added a commit that referenced this pull request May 24, 2024
In #12003 we found ourselves in need for precise region tracking in which a given runnable has an effect in order to grab variables from it.
This PR makes it so that in task modal all task variables from queries overlapping current cursor position.
However, in the process of working on that I've found that we cannot always use a top-level capture to represent the full match range of
runnable (which has been my assumption up to this point). Tree-sitter captures cannot capture sibling groups; we did just that in Rust queries.

Thankfully, none of the extensions are affected as in them, a capture is always attached to single node. This PR adds annotations to them nonetheless; we'll be able to get rid of top-level captures in extension runnables.scm once this PR is in stable version of Zed.
osiewicz added a commit that referenced this pull request May 24, 2024
In #12003 we found ourselves in need for precise region tracking in which a given runnable has an effect in order to grab variables from it.
This PR makes it so that in task modal all task variables from queries overlapping current cursor position.
However, in the process of working on that I've found that we cannot always use a top-level capture to represent the full match range of
runnable (which has been my assumption up to this point). Tree-sitter captures cannot capture sibling groups; we did just that in Rust queries.

Thankfully, none of the extensions are affected as in them, a capture is always attached to single node. This PR adds annotations to them nonetheless; we'll be able to get rid of top-level captures in extension runnables.scm once this PR is in stable version of Zed.
@osiewicz
Copy link
Contributor

Ok, I've opened last PR (#12237) that should unblock work on this one.
I'd say we should focus on getting this merged and then - if you fancy it - we can tackle @thnt s request in a follow-up PR.

(#eq? @run "main"))
) @go-main
(call_expression function: (_) @run @_name
(#match? @_name "^t.Run.*"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to assume that the variable we're calling Run on is always going to be called t? I get that it's a convention, but is there some precedent in Go community for breaking it sometimes?

Copy link
Contributor

@osiewicz osiewicz May 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think we can rework this pattern altogether to simplify it a bit:
With the following query:


; `t.Run`
(
    (call_expression 
      function: (
      	selector_expression
      	field: _ @run @_name
        (#eq? @_name "Run")
      )
      arguments: (
      	argument_list
        .
        (interpreted_string_literal) @_subtest_name
       )
    )
    (#set! tag go-subtest)
)

In build_context the task_variables argument is gonna have the value of first argument (string literal) in _subtest_name key. You won't have to parse the line then.
So, tl;dr: in build_context you can get the value of this capture with:
variables.get(&VariableName::Custom(Cow::Borrowed("_subtest_name")));

You still need to escape the subtest name (I guess?) and set it in the context being returned from build_context (captures with _ prefix are removed after a call to build_context so that end users can't reference it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, its a convention and actually I did not want to cover an edge case for now 😄

It seems logical; I'm gonna try your new suggestion when I am free or maybe you gonna try it already.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chipping in to say it's a fairly strong convention and good starting point. I've seen some codebases use test helpers that break the t.Run pattern but VSCode and GoLand don't match those subtests either.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's fair. With that other query (#12003 (comment)) we don't even need to match on variable name though, as we'll capture just the name of the function being called; so the whole dispute about whether we should match t. is kinda moot. ;p

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would your example above match an unrelated Run() call like

client.Run(ctx) // should not match

t.Run("client returns OK", func(t testing.T) {...}) // should match

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it would not. We also expect the first argument to be a string literal. We could perhaps constrain it further to also require a second parameter that's (I guess?) an anonymous function taking a single param of type testing T

@anilsenay
Copy link
Contributor Author

Ok, I've opened last PR (#12237) that should unblock work on this one. I'd say we should focus on getting this merged and then - if you fancy it - we can tackle @thnt s request in a follow-up PR.

If you talked about submodule issue, I will open an issue for that not to be forgotten after this PR merged.

osiewicz added a commit that referenced this pull request May 24, 2024
…odal (#12237)

In #12003 we found ourselves in need for precise region tracking in
which a given runnable has an effect in order to grab variables from it.
This PR makes it so that in task modal all task variables from queries
overlapping current cursor position.
However, in the process of working on that I've found that we cannot
always use a top-level capture to represent the full match range of
runnable (which has been my assumption up to this point). Tree-sitter
captures cannot capture sibling groups; we did just that in Rust
queries.

Thankfully, none of the extensions are affected as in them, a capture is
always attached to single node. This PR adds annotations to them
nonetheless; we'll be able to get rid of top-level captures in extension
runnables.scm once this PR is in stable version of Zed.


Release Notes:

- N/A
Copy link
Contributor

@osiewicz osiewicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

Hide @second_argument from user

Co-authored-by: Piotr Osiewicz <[email protected]>
@osiewicz osiewicz merged commit ddb551c into zed-industries:main May 26, 2024
8 checks passed
notpeter pushed a commit to zed-extensions/ruby that referenced this pull request Oct 11, 2024
…odal (zed-industries/zed#12237)

In zed-industries/zed#12003 we found ourselves in need for precise region tracking in
which a given runnable has an effect in order to grab variables from it.
This PR makes it so that in task modal all task variables from queries
overlapping current cursor position.
However, in the process of working on that I've found that we cannot
always use a top-level capture to represent the full match range of
runnable (which has been my assumption up to this point). Tree-sitter
captures cannot capture sibling groups; we did just that in Rust
queries.

Thankfully, none of the extensions are affected as in them, a capture is
always attached to single node. This PR adds annotations to them
nonetheless; we'll be able to get rid of top-level captures in extension
runnables.scm once this PR is in stable version of Zed.


Release Notes:

- N/A
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla-signed The user has signed the Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants