[mirror] Make your go dev experience better github.com/olexsmir/gopher.nvim
neovim golang

tests: improve testing (#80)

* chore: setup mini.test

* chore(ci): setup new test runner, install plugin deps

* chore(ci): test only on stable and nightly releases

* test: iferr

* test: struct_tags

* test: impl

* test: gotests

authored by olexsmir.xyz and committed by GitHub da960189 0ed14a40

+3
.envrc
··· 1 + dotenv 2 + 3 + env_vars_required GOPHER_DIR
+7 -1
.github/workflows/linters.yml
··· 1 1 name: linters 2 - on: [push, pull_request] 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + - develop 8 + pull_request: 3 9 4 10 jobs: 5 11 linters:
+33 -23
.github/workflows/tests.yml
··· 1 1 name: tests 2 - on: [push, pull_request] 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + - develop 8 + pull_request: 3 9 4 10 jobs: 5 11 tests: 6 12 strategy: 7 13 matrix: 8 14 os: [ubuntu-latest] 9 - nvim_version: 15 + version: 16 + - v0.10.4 10 17 - nightly 11 - - v0.7.0 12 - - v0.7.2 13 - - v0.8.0 14 - - v0.8.1 15 - - v0.8.2 16 - - v0.8.3 17 - - v0.9.0 18 - - v0.9.1 19 - - v0.9.2 20 - - v0.9.4 21 - - v0.9.5 22 - - v0.10.0 23 18 runs-on: ${{ matrix.os }} 24 19 steps: 25 20 - name: Install Task ··· 28 23 version: 3.x 29 24 repo-token: ${{ secrets.GITHUB_TOKEN }} 30 25 26 + - name: Install Go 27 + uses: actions/setup-go@v5 28 + with: 29 + go-version: "1.24.0" 30 + check-latest: false 31 + 32 + - name: Install NeoVim 33 + uses: rhysd/action-setup-vim@v1 34 + with: 35 + neovim: true 36 + version: ${{ matrix.version }} 37 + 31 38 - uses: actions/checkout@v3 32 39 33 - - name: Install Neovim 34 - run: | 35 - mkdir -p /tmp/nvim 36 - wget -q https://github.com/neovim/neovim/releases/download/${{ matrix.nvim_version }}/nvim.appimage -O /tmp/nvim/nvim.appimage 37 - cd /tmp/nvim 38 - chmod a+x ./nvim.appimage 39 - ./nvim.appimage --appimage-extract 40 - echo "/tmp/nvim/squashfs-root/usr/bin/" >> $GITHUB_PATH 40 + - name: Cache .tests 41 + uses: actions/cache@v4 42 + with: 43 + path: | 44 + ${{ github.workspace }}/.tests 45 + ~/.cache/go-build 46 + ~/go/pkg/mod 47 + key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }} 48 + 49 + - name: Install Go bins 50 + run: nvim --headless -u "./scripts/minimal_init.lua" -c "GoInstallDeps" -c "qa!" 41 51 42 52 - name: Run Tests 43 53 run: | 44 54 nvim --version 45 - task test 55 + task tests
+1
.gitignore
··· 1 1 /playground/ 2 2 /.tests/ 3 + /.env
+3 -2
README.md
··· 10 10 11 11 ## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim)) 12 12 13 - Pre-dependency: 13 + Requirements: 14 14 15 - - [Go](https://github.com/golang/go) 15 + - **Neovim 0.10** or later 16 16 - `go` treesitter parser, install by `:TSInstall go` 17 + - [Go](https://github.com/golang/go) installed (tested on 1.23) 17 18 18 19 ```lua 19 20 {
+4 -8
Taskfile.yml
··· 26 26 cmds: 27 27 - stylua . 28 28 29 - test: 30 - desc: runs all tests 31 - aliases: [tests, spec] 29 + tests: 30 + desc: run all tests 32 31 cmds: 33 32 - | 34 33 nvim --headless \ 35 - -u ./scripts/minimal_init.lua \ 36 - -c "PlenaryBustedDirectory spec \ 37 - {minimal_init='./scripts/minimal_init.lua' \ 38 - ,sequential=true}" \ 39 - -c ":qa!" 34 + -u ./scripts/minimal_init.lua \ 35 + -c "lua MiniTest.run()" 40 36 41 37 docgen: 42 38 desc: generate vimhelp
-3
doc/gopher.nvim.txt
··· 116 116 } 117 117 < 118 118 119 - 120 119 ============================================================================== 121 120 ------------------------------------------------------------------------------ 122 121 *gopher.nvim-impl* ··· 147 146 } 148 147 < 149 148 150 - 151 149 ============================================================================== 152 150 ------------------------------------------------------------------------------ 153 151 *gopher.nvim-gotests* ··· 166 164 167 165 you can also specify the template to use for generating the tests. see |gopher.nvim-config| 168 166 more details about templates can be found at: https://github.com/cweill/gotests 169 - 170 167 171 168 ------------------------------------------------------------------------------ 172 169 *gopher.nvim-gotests-named*
-8
lua/gopher/_utils/init.lua
··· 23 23 log.debug(msg) 24 24 end 25 25 26 - -- safe require 27 - ---@param module string module name 28 - function utils.sreq(module) 29 - local ok, m = pcall(require, module) 30 - assert(ok, string.format("gopher.nvim dependency error: %s not installed", module)) 31 - return m 32 - end 33 - 34 26 return utils
+1 -1
lua/gopher/config.lua
··· 23 23 --minidoc_replace_start { 24 24 25 25 ---@tag gopher.nvim-config-defaults 26 - ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section):gsub(">", ">lua") 26 + ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) 27 27 --- 28 28 ---@class gopher.Config 29 29 local default_config = {
+3
nvim.toml
··· 1 1 [vim] 2 2 any = true 3 3 4 + [MiniTest] 5 + any = true 6 + 4 7 [describe] 5 8 any = true 6 9 [[describe.args]]
+20 -5
scripts/minimal_init.lua
··· 6 6 local function install_plug(plugin) 7 7 local name = plugin:match ".*/(.*)" 8 8 local package_root = root ".tests/site/pack/deps/start/" 9 - if not vim.loop.fs_stat(package_root .. name) then 9 + if not vim.uv.fs_stat(package_root .. name) then 10 10 print("Installing " .. plugin) 11 11 vim.fn.mkdir(package_root, "p") 12 12 vim.fn.system { ··· 18 18 } 19 19 end 20 20 end 21 + 22 + vim.env.XDG_CONFIG_HOME = root ".tests/config" 23 + vim.env.XDG_DATA_HOME = root ".tests/data" 24 + vim.env.XDG_STATE_HOME = root ".tests/state" 25 + vim.env.XDG_CACHE_HOME = root ".tests/cache" 21 26 22 27 vim.cmd [[set runtimepath=$VIMRUNTIME]] 23 28 vim.opt.runtimepath:append(root()) ··· 27 32 install_plug "nvim-lua/plenary.nvim" 28 33 install_plug "nvim-treesitter/nvim-treesitter" 29 34 install_plug "echasnovski/mini.doc" -- used for docs generation 35 + install_plug "echasnovski/mini.test" 30 36 31 - vim.env.XDG_CONFIG_HOME = root ".tests/config" 32 - vim.env.XDG_DATA_HOME = root ".tests/data" 33 - vim.env.XDG_STATE_HOME = root ".tests/state" 34 - vim.env.XDG_CACHE_HOME = root ".tests/cache" 37 + -- install go treesitter parse 38 + require("nvim-treesitter.install").ensure_installed_sync "go" 39 + 40 + -- setup mini.test only when running headless nvim 41 + if #vim.api.nvim_list_uis() == 0 then 42 + require("mini.test").setup { 43 + collect = { 44 + find_files = function() 45 + return vim.fn.globpath("spec", "**/*_test.lua", true, true) 46 + end, 47 + }, 48 + } 49 + end
+1
spec/fixtures/comment/package_input.go
··· 1 + package main
+2
spec/fixtures/comment/package_output.go
··· 1 + // Package main provides main 2 + package main
+9
spec/fixtures/iferr/iferr_input.go
··· 1 + package main 2 + 3 + func test() error { 4 + return nil 5 + } 6 + 7 + func main() { 8 + err := test() 9 + }
+12
spec/fixtures/iferr/iferr_output.go
··· 1 + package main 2 + 3 + func test() error { 4 + return nil 5 + } 6 + 7 + func main() { 8 + err := test() 9 + if err != nil { 10 + return 11 + } 12 + }
+3
spec/fixtures/impl/closer_input.go
··· 1 + package main 2 + 3 + type CloserTest struct{}
+8
spec/fixtures/impl/closer_output.go
··· 1 + package main 2 + 3 + type CloserTest2 struct{} 4 + 5 + func (closertest *CloserTest2) Close() error { 6 + panic("not implemented") // TODO: Implement 7 + } 8 +
+3
spec/fixtures/impl/reader_input.go
··· 1 + package main 2 + 3 + type Read struct{}
+8
spec/fixtures/impl/reader_output.go
··· 1 + package main 2 + 3 + func (r Read2) Read(p []byte) (n int, err error) { 4 + panic("not implemented") // TODO: Implement 5 + } 6 + 7 + 8 + type Read2 struct{}
+3
spec/fixtures/impl/writer_input.go
··· 1 + package main 2 + 3 + type WriterTest struct{}
+8
spec/fixtures/impl/writer_output.go
··· 1 + package main 2 + 3 + type WriterTest2 struct{} 4 + 5 + func (w *WriterTest2) Write(p []byte) (n int, err error) { 6 + panic("not implemented") // TODO: Implement 7 + } 8 +
+6 -6
spec/fixtures/tags/remove_output.go
··· 1 1 package main 2 2 3 3 type Test struct { 4 - ID int 5 - Name string 6 - Num int64 4 + ID int 5 + Name string 6 + Num int64 7 7 Another struct { 8 - First int 9 - Second string 10 - } 8 + First int 9 + Second string 10 + } 11 11 }
+5
spec/fixtures/tests/function_input.go
··· 1 + package fortest 2 + 3 + func Add(x, y int) int { 4 + return 2 + x + y 5 + }
+24
spec/fixtures/tests/function_output.go
··· 1 + package fortest 2 + 3 + import "testing" 4 + 5 + func TestAdd(t *testing.T) { 6 + type args struct { 7 + x int 8 + y int 9 + } 10 + tests := []struct { 11 + name string 12 + args args 13 + want int 14 + }{ 15 + // TODO: Add test cases. 16 + } 17 + for _, tt := range tests { 18 + t.Run(tt.name, func(t *testing.T) { 19 + if got := Add(tt.args.x, tt.args.y); got != tt.want { 20 + t.Errorf("Add() = %v, want %v", got, tt.want) 21 + } 22 + }) 23 + } 24 + }
+7
spec/fixtures/tests/method_input.go
··· 1 + package fortest 2 + 3 + type ForTest struct{} 4 + 5 + func (t *ForTest) Add(x, y int) int { 6 + return 2 + x + y 7 + }
+26
spec/fixtures/tests/method_output.go
··· 1 + package fortest 2 + 3 + import "testing" 4 + 5 + func TestForTest_Add(t *testing.T) { 6 + type args struct { 7 + x int 8 + y int 9 + } 10 + tests := []struct { 11 + name string 12 + tr *ForTest 13 + args args 14 + want int 15 + }{ 16 + // TODO: Add test cases. 17 + } 18 + for _, tt := range tests { 19 + t.Run(tt.name, func(t *testing.T) { 20 + tr := &ForTest{} 21 + if got := tr.Add(tt.args.x, tt.args.y); got != tt.want { 22 + t.Errorf("ForTest.Add() = %v, want %v", got, tt.want) 23 + } 24 + }) 25 + } 26 + }
+27
spec/integration/comment_test.lua
··· 1 + local t = require "spec.testutils" 2 + 3 + local child = MiniTest.new_child_neovim() 4 + local T = MiniTest.new_set { 5 + hooks = { 6 + post_once = child.stop, 7 + pre_case = function() 8 + MiniTest.skip "This module should be fixed first" 9 + child.restart { "-u", t.mininit_path } 10 + end, 11 + }, 12 + } 13 + T["comment"] = MiniTest.new_set {} 14 + 15 + T["comment"]["should add comment to package"] = function() end 16 + 17 + T["comment"]["should add comment to struct"] = function() end 18 + 19 + T["comment"]["should add comment to function"] = function() end 20 + 21 + T["comment"]["should add comment to method"] = function() end 22 + 23 + T["comment"]["should add comment to interface"] = function() end 24 + 25 + T["comment"]["otherwise should add // above cursor"] = function() end 26 + 27 + return T
+47
spec/integration/gotests_test.lua
··· 1 + local t = require "spec.testutils" 2 + 3 + local child = MiniTest.new_child_neovim() 4 + local T = MiniTest.new_set { 5 + hooks = { 6 + post_once = child.stop, 7 + pre_case = function() 8 + child.restart { "-u", t.mininit_path } 9 + end, 10 + }, 11 + } 12 + T["gotests"] = MiniTest.new_set {} 13 + 14 + --- NOTE: :GoTestAdd is the only place that has actual logic 15 + --- All other parts are handled `gotests` itself. 16 + 17 + ---@param fpath string 18 + ---@return string 19 + local function read_testfile(fpath) 20 + return t.readfile(fpath:gsub(".go", "_test.go")) 21 + end 22 + 23 + T["gotests"]["should add test for function under cursor"] = function() 24 + local tmp = t.tmpfile() 25 + local fixtures = t.get_fixtures "tests/function" 26 + t.writefile(tmp, fixtures.input) 27 + 28 + child.cmd("silent edit " .. tmp) 29 + child.fn.setpos(".", { child.fn.bufnr "%", 3, 6 }) 30 + child.cmd "GoTestAdd" 31 + 32 + t.eq(fixtures.output, read_testfile(tmp)) 33 + end 34 + 35 + T["gotests"]["should add test for method under cursor"] = function() 36 + local tmp = t.tmpfile() 37 + local fixtures = t.get_fixtures "tests/method" 38 + t.writefile(tmp, fixtures.input) 39 + 40 + child.cmd("silent edit " .. tmp) 41 + child.fn.setpos(".", { child.fn.bufnr "%", 5, 19 }) 42 + child.cmd "GoTestAdd" 43 + 44 + t.eq(fixtures.output, read_testfile(tmp)) 45 + end 46 + 47 + return T
+26
spec/integration/iferr_test.lua
··· 1 + local t = require "spec.testutils" 2 + 3 + local child = MiniTest.new_child_neovim() 4 + local T = MiniTest.new_set { 5 + hooks = { 6 + post_once = child.stop, 7 + pre_case = function() 8 + child.restart { "-u", t.mininit_path } 9 + end, 10 + }, 11 + } 12 + T["iferr"] = MiniTest.new_set {} 13 + T["iferr"]["works"] = function() 14 + local tmp = t.tmpfile() 15 + local fixtures = t.get_fixtures "iferr/iferr" 16 + t.writefile(tmp, fixtures.input) 17 + 18 + child.cmd("silent edit " .. tmp) 19 + child.fn.setpos(".", { child.fn.bufnr "%", 8, 2, 0 }) 20 + child.cmd "GoIfErr" 21 + child.cmd "write" 22 + 23 + t.eq(t.readfile(tmp), fixtures.output) 24 + end 25 + 26 + return T
+55
spec/integration/impl_test.lua
··· 1 + local t = require "spec.testutils" 2 + 3 + local child = MiniTest.new_child_neovim() 4 + local T = MiniTest.new_set { 5 + hooks = { 6 + post_once = child.stop, 7 + pre_case = function() 8 + child.restart { "-u", t.mininit_path } 9 + end, 10 + }, 11 + } 12 + T["impl"] = MiniTest.new_set {} 13 + T["impl"]["works w io.Writer"] = function() 14 + local tmp = t.tmpfile() 15 + local fixtures = t.get_fixtures "impl/writer" 16 + t.writefile(tmp, fixtures.input) 17 + 18 + child.cmd("silent edit " .. tmp) 19 + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 6 }) 20 + child.cmd "GoImpl w io.Writer" 21 + child.cmd "write" 22 + 23 + -- NOTE: since "impl" won't implement interface if it's already implemented i went with this hack 24 + local rhs = fixtures.output:gsub("Test2", "Test") 25 + t.eq(t.readfile(tmp), rhs) 26 + end 27 + 28 + T["impl"]["works r Read io.Reader"] = function() 29 + local tmp = t.tmpfile() 30 + local fixtures = t.get_fixtures "impl/reader" 31 + t.writefile(tmp, fixtures.input) 32 + 33 + child.cmd("silent edit " .. tmp) 34 + child.cmd "GoImpl r Read io.Reader" 35 + child.cmd "write" 36 + 37 + local rhs = fixtures.output:gsub("Read2", "Read") 38 + t.eq(t.readfile(tmp), rhs) 39 + end 40 + 41 + T["impl"]["works io.Closer"] = function() 42 + local tmp = t.tmpfile() 43 + local fixtures = t.get_fixtures "impl/closer" 44 + t.writefile(tmp, fixtures.input) 45 + 46 + child.cmd("silent edit " .. tmp) 47 + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 6 }) 48 + child.cmd "GoImpl io.Closer" 49 + child.cmd "write" 50 + 51 + local rhs = fixtures.output:gsub("Test2", "Test") 52 + t.eq(t.readfile(tmp), rhs) 53 + end 54 + 55 + return T
+37
spec/integration/struct_tags_test.lua
··· 1 + local t = require "spec.testutils" 2 + 3 + local child = MiniTest.new_child_neovim() 4 + local T = MiniTest.new_set { 5 + hooks = { 6 + post_once = child.stop, 7 + pre_case = function() 8 + child.restart { "-u", t.mininit_path } 9 + end, 10 + }, 11 + } 12 + T["struct_tags"] = MiniTest.new_set {} 13 + T["struct_tags"]["works add"] = function() 14 + local tmp = t.tmpfile() 15 + local fixtures = t.get_fixtures "tags/add" 16 + t.writefile(tmp, fixtures.input) 17 + 18 + child.cmd("silent edit " .. tmp) 19 + child.fn.setpos(".", { child.fn.bufnr "%", 3, 6, 0 }) 20 + child.cmd "GoTagAdd json" 21 + 22 + t.eq(t.readfile(tmp), fixtures.output) 23 + end 24 + 25 + T["struct_tags"]["works remove"] = function() 26 + local tmp = t.tmpfile() 27 + local fixtures = t.get_fixtures "tags/remove" 28 + t.writefile(tmp, fixtures.input) 29 + 30 + child.cmd("silent edit " .. tmp) 31 + child.fn.setpos(".", { child.fn.bufnr "%", 4, 6, 0 }) 32 + child.cmd "GoTagRm json" 33 + 34 + t.eq(t.readfile(tmp), fixtures.output) 35 + end 36 + 37 + return T
+43
spec/testutils.lua
··· 1 + local base_dir = vim.env.GOPHER_DIR or vim.fn.expand "%:p:h" 2 + 3 + ---@class gopher.TestUtils 4 + local testutils = {} 5 + 6 + testutils.mininit_path = vim.fs.joinpath(base_dir, "scripts", "minimal_init.lua") 7 + testutils.fixtures_dir = vim.fs.joinpath(base_dir, "spec/fixtures") 8 + 9 + ---@generic T 10 + ---@param a T 11 + ---@param b T 12 + ---@return boolean 13 + function testutils.eq(a, b) 14 + return MiniTest.expect.equality(a, b) 15 + end 16 + 17 + ---@return string 18 + function testutils.tmpfile() 19 + return vim.fn.tempname() .. ".go" 20 + end 21 + 22 + ---@param path string 23 + ---@return string 24 + function testutils.readfile(path) 25 + return vim.fn.join(vim.fn.readfile(path), "\n") 26 + end 27 + 28 + ---@param fpath string 29 + ---@param contents string 30 + function testutils.writefile(fpath, contents) 31 + vim.fn.writefile(vim.split(contents, "\n"), fpath) 32 + end 33 + 34 + ---@param fixture string 35 + ---@return {input: string, output: string} 36 + function testutils.get_fixtures(fixture) 37 + return { 38 + input = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_input.go"), 39 + output = testutils.readfile(vim.fs.joinpath(testutils.fixtures_dir, fixture) .. "_output.go"), 40 + } 41 + end 42 + 43 + return testutils
-29
spec/units/config_spec.lua
··· 1 - describe("gopher.config", function() 2 - it(".setup() should provide default when .setup() is not called", function() 3 - local c = require "gopher.config" 4 - 5 - assert.are.same(c.commands.go, "go") 6 - assert.are.same(c.commands.gomodifytags, "gomodifytags") 7 - assert.are.same(c.commands.gotests, "gotests") 8 - assert.are.same(c.commands.impl, "impl") 9 - assert.are.same(c.commands.iferr, "iferr") 10 - assert.are.same(c.commands.dlv, "dlv") 11 - end) 12 - 13 - it(".setup() should change options on users config", function() 14 - local c = require "gopher.config" 15 - c.setup { 16 - commands = { 17 - go = "go1.420", 18 - gomodifytags = "iDontUseRustBtw", 19 - }, 20 - } 21 - 22 - assert.are.same(c.commands.go, "go1.420") 23 - assert.are.same(c.commands.gomodifytags, "iDontUseRustBtw") 24 - assert.are.same(c.commands.gotests, "gotests") 25 - assert.are.same(c.commands.impl, "impl") 26 - assert.are.same(c.commands.iferr, "iferr") 27 - assert.are.same(c.commands.dlv, "dlv") 28 - end) 29 - end)
-15
spec/units/utils_spec.lua
··· 1 - describe("gopher._utils", function() 2 - local u = require "gopher._utils" 3 - 4 - describe(".sreq()", function() 5 - it("can require existing module", function() 6 - assert.are.same(require "gopher", u.sreq "gopher") 7 - end) 8 - 9 - it("cannot require non-existing module", function() 10 - assert.has.errors(function() 11 - u.sreq "iDontExistBtw" 12 - end) 13 - end) 14 - end) 15 - end)