Skip to content

Contributing

🙏 PRs are welcome

Improvement suggestion PRs to this repo are very much welcome, and I encourage you to begin by reading the below paragraph on the adapter design and engage in the discussions in case the change is not trivial.

You can run tests, formatting and linting locally with make all. Install dependencies with make install. Have a look at the Makefile for more details. You can also use the neotest-plenary and neotest-golang adapters to run the tests of this repo within Neovim.

AST and tree-sitter

To figure out new tree-sitter queries (for detecting tests), the following commands are available in Neovim to aid you:

  • :Inspect to show the highlight groups under the cursor.
  • :InspectTree to show the parsed syntax tree (formerly known as "TSPlayground").
  • :EditQuery to open the Live Query Editor (Nvim 0.10+).

For example, open up a Go test file and then execute :InspectTree. A new window will appear which shows what the tree-sitter query syntax representation looks like for the Go test file.

Again, from the Go test file, execute :EditQuery to open up the query editor in a separate window. In the editor, you can now start creating your syntax query and play around. You can paste in queries from query.lua in the editor, to see how the query behaves and highlights parts of your Go test file.

General design of the adapter

Treesitter queries detect tests

Neotest leverages treesitter AST-parsing of source code to detect tests. This adapter supplies queries so to figure out what is considered a test.

From the result of these queries, a Neotest "position" tree is built (can be visualized through the "Neotest summary"). Each position in the tree represents either a dir, file or test type. Neotest also has a notion of a namespace position type, but this is ignored by default by this adapter (but leveraged to supply testify support).

Generating valid go test commands

The dir, file and test tree position types cannot be directly translated over to Go so to produce a valid go test command. Go primarily cares about a Go package's import path, test name regexp filters and the current working directory.

For example, these are all valid go test command:

Bash
# run all tests, recursing sub-packages, in the current working directory.
go test ./...

# run all tests in a given package 'x', by specifying the full import path
go test github.com/fredrikaverpil/neotest-golang/x

# run all tests in a given package 'x', recursing sub-packages
go test github.com/fredrikaverpil/neotest-golang/x/...

# run _some_ tests in a given package, based on a regexp filter
go test github.com/fredrikaverpil/neotest-golang -run "^(^TestFoo$|^TestBar$)$"

Note on go.mod

All the above commands must be run somewhere beneath the location of the go.mod file specifying the module name, which in this example is github.com/fredrikaverpil/neotest-golang.

I figured out that by executing go list -json ./... in the go.mod root location, the output provides valuable information about test files/folders and their corresponding Go package's import path. This data is key to being able to take the Neotest/treesitter position type and generate a valid go test command for it. In essence, this approach is what makes neotest-golang so robust.

Output processing

Neotest captures the stdout from the test execution command and writes it to disk as a temporary file. The adapter is responsible for reading the file(s) and reporting back status and output to the Neotest tree (and specifically the position in the tree which was executed). It is therefore crucial for outputting structured data, which in this case is done with go test -json.

One challenge here is that Go build errors are not part of the strucutured JSON output (although captured in the stdout) and needs to be looked for in other ways.

Another challenge is to properly populate statuses and errors into the corresponding Neotest tree position. This becomes increasingly difficult when you consider running tests in a recursive manner (e.g. go test -json ./...).

Errors are recorded and populated, per position type, along with its corresponding buffer's line number. Neotest can then show the errors inline as diagnostics.

I've taken an approach with this adapter where I record test outcome for each Neotest position type and populate it onto each of them, when applicable.

On some systems and terminals, there are great issues with the go test output. I've therefore made it possible to make the adapter rely on output saved directly to disk without going through stdout, by leveraging gotestsum.