├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── luarocks.yml │ ├── makefile.yml │ ├── release.yml │ └── static-analysis.yml ├── .gitignore ├── .luacheckrc ├── .luarc.json ├── .releaserc.json ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── ROADMAP.md ├── git-hooks └── pre-commit ├── lua └── neotest-java │ ├── build_tool │ ├── gradle.lua │ ├── init.lua │ └── maven.lua │ ├── command │ ├── binaries.lua │ ├── jdtls.lua │ ├── junit_command_builder.lua │ └── run.lua │ ├── context_holder.lua │ ├── core │ ├── dir_filter.lua │ ├── file_checker.lua │ ├── positions_discoverer.lua │ ├── result_builder.lua │ ├── root_finder.lua │ └── spec_builder │ │ ├── compiler │ │ ├── init.lua │ │ └── jdtls.lua │ │ └── init.lua │ ├── init.lua │ ├── logger.lua │ ├── types │ ├── ignore_path_patterns.lua │ ├── junit_result.lua │ ├── module.lua │ ├── patterns.lua │ ├── project.lua │ └── test_class_patterns.lua │ └── util │ ├── compatible_path.lua │ ├── detect_project_type.lua │ ├── find_module_by_filepath.lua │ ├── flat_map.lua │ ├── just_take_the_dependency.lua │ ├── random_port.lua │ ├── read_file.lua │ ├── read_xml_tag.lua │ ├── resolve_qualified_name.lua │ ├── should_ignore_path.lua │ ├── there_is_wrapper_in.lua │ ├── timer.lua │ └── write_file.lua ├── plugin └── neotest-java.lua ├── scripts └── test └── tests ├── core ├── dir_filter_spec.lua ├── file_checker_spec.lua ├── positions_discoverer_spec.lua ├── result_builder_spec.lua └── root_finder_spec.lua ├── fixtures ├── SomeNestedTest.java ├── Test.java ├── build-example.gradle ├── gradle-groovy-demo │ ├── .gitignore │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── demo │ │ │ │ └── DemoApplication.java │ │ └── resources │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ ├── EmptySourceTest.java │ │ ├── ExampleTest.java │ │ ├── NestedTests.java │ │ ├── ParameterizedTests.java │ │ ├── PrintingTest.java │ │ ├── SingleMethodFailingTest.java │ │ └── demo │ │ └── DemoApplicationTests.java ├── gradle-kotlin-demo │ ├── .gitignore │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── demo │ │ │ │ └── DemoApplication.java │ │ └── resources │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── demo │ │ └── DemoApplicationTests.java └── maven-demo │ ├── .gitignore │ ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── demo │ │ │ └── DemoApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── example │ ├── EmptySourceTest.java │ ├── ErroneousTest.java │ ├── ExampleTest.java │ ├── NestedTests.java │ ├── ParameterizedMethodTest.java │ ├── SingleMethodFailingTest.java │ └── demo │ ├── DemoApplicationTests.java │ └── RepositoryIT.java ├── init_spec.lua ├── testrc.vim ├── types └── project_spec.lua └── util ├── compatible_path_spec.lua ├── find_module_from_filepath_spec.lua ├── just_take_the_dependency_spec.lua └── resolve_qualified_name_spec.lua /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: rcasia 7 | 8 | --- 9 | 10 | **NeoVim Version** 11 | Output of `nvim --version` 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. 15 | 16 | Steps to reproduce the behavior: 17 | 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | Please provide example test files to reproduce. 24 | 25 | **Expected behavior** 26 | A clear and concise description of what you expected to happen. 27 | 28 | **Logs** 29 | 30 | 1. Wipe the `neotest.log` file in `stdpath("log")` or `stdpath("data")`. 31 | 2. Wipe the `neotest-java.log` file in `stdpath("log")` or `stdpath("data")`. 32 | 3. Set `log_level = vim.log.levels.DEBUG` in your neotest setup config. 33 | 4. Reproduce the issue. 34 | 5. Provide the new logs. 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION 🌈' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | template: | 4 | ## Changes 5 | $CHANGES 6 | categories: 7 | - title: '🚀 Features' 8 | labels: 9 | - 'feature' 10 | - 'enhancement' 11 | - title: '🐛 Bug Fixes' 12 | labels: 13 | - 'fix' 14 | - 'bugfix' 15 | - 'bug' 16 | - title: '🧰 Maintenance' 17 | label: 'chore' 18 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 19 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 20 | version-resolver: 21 | major: 22 | labels: 23 | - 'major' 24 | minor: 25 | labels: 26 | - 'minor' 27 | patch: 28 | labels: 29 | - 'patch' 30 | default: patch 31 | -------------------------------------------------------------------------------- /.github/workflows/luarocks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Push to Luarocks 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | release: 9 | types: 10 | - created 11 | tags: 12 | - '*' 13 | pull_request: # Tests installing the generated rockspec on PR without uploading 14 | workflow_dispatch: 15 | 16 | jobs: 17 | luarocks-upload: 18 | runs-on: ubuntu-22.04 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 # Required to count the commits 23 | - name: Get Version 24 | run: echo "LUAROCKS_VERSION=$(git describe --abbrev=0 --tags)" >> $GITHUB_ENV 25 | 26 | # Needed to install the tree-sitter parser dependency 27 | - name: Install C/C++ Compiler 28 | uses: rlalik/setup-cpp-compiler@master 29 | with: 30 | compiler: clang-latest 31 | - name: Install tree-sitter CLI 32 | uses: baptiste0928/cargo-install@v3 33 | with: 34 | crate: tree-sitter-cli 35 | 36 | - name: LuaRocks Upload 37 | uses: nvim-neorocks/luarocks-tag-release@v7 38 | env: 39 | LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} 40 | with: 41 | version: ${{ env.LUAROCKS_VERSION }} 42 | dependencies: | 43 | neotest 44 | plenary.nvim 45 | nvim-nio 46 | tree-sitter-java 47 | nvim-dap 48 | nvim-dap-ui 49 | 50 | -------------------------------------------------------------------------------- /.github/workflows/makefile.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | schedule: 5 | - cron: "00 00 * * 4" # every friday at 00:00 6 | push: 7 | branches: ["main"] 8 | pull_request: 9 | branches: ["main"] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@v4 18 | 19 | - uses: rhysd/action-setup-vim@v1 20 | with: 21 | neovim: true 22 | 23 | - name: Run tests 24 | run: make test 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: release 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | - name: Release 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 24 | run: npx semantic-release 25 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Static Analysis 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | luacheck: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Luacheck 14 | uses: lunarmodules/luacheck@v1.2.0 15 | 16 | stylua: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Stylua 20 | uses: JohnnyMorganz/stylua-action@v4 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | version: latest # NOTE: we recommend pinning to a specific version in case of formatting changes 24 | # CLI arguments 25 | args: --check . 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.vim 2 | /luarocks 3 | /lua_modules 4 | /.luarocks 5 | 6 | deps 7 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | ignore = { 2 | "631", -- max_line_length 3 | } 4 | read_globals = { 5 | "vim", 6 | "describe", 7 | "it", 8 | "assert" 9 | } 10 | exclude_files = { 11 | "**/fun/**" 12 | } 13 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "diagnostics.globals": [ 3 | "vim" 4 | ] 5 | } -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@semantic-release/commit-analyzer", 4 | "@semantic-release/release-notes-generator", 5 | [ 6 | "@semantic-release/github", 7 | { 8 | "successComment": false 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing to neotest-java 3 | 4 | If you are here it means you are interested in contributing. Thank you for your help! :confetti_ball: 5 | 6 | ## :inbox_tray: Pull Requests 7 | If you are considering to open a pull request for a little change, feel free do it straight forward. 8 | For larger changes, it is a good idea to open an issue first describing the feature idea or bug. 9 | 10 | For the PRs to succeed and be merged you are expected to: 11 | * have read this doc 12 | * run the formatter before every commit 13 | * to avoid duplicated work, open a draft PR as soon as you get to work 14 | 15 | ### :electric_plug: Set up 16 | 17 | #### Required dependencies for contributors 18 | You will need to have installed: 19 | * Java JDK 17 or 21 20 | * This list of commands available in your terminal: 21 | * `stylua` 22 | * `make` 23 | * `git` 24 | 25 | #### First build 26 | The first step to setup your enviroment is to type: 27 | ```bash 28 | make 29 | ``` 30 | This command will: 31 | 1. clone neotest, plenary and nvim-treesitter 32 | 2. install the java parser for nvim-treesitter 33 | 3. build the java projects (it is expected to have some failing test) 34 | 4. run neotest-java tests 35 | 36 | #### Running tests 37 | 38 | A. Using [neotest-plenary](https://github.com/nvim-neotest/neotest-plenary). 39 | 40 | 41 | > Note: For the moment, you will need to specify the path to `.../neotest-java/tests/testrc.vim` 42 | in the neotest-plenary configuration. See nvim-neotest/neotest-plenary#13 43 | 44 | B. From terminal 45 | 46 | * Run all tests (for lua changes): 47 | ```bash 48 | make test 49 | ``` 50 | * Run all tests (for lua and java changes): 51 | ```bash 52 | make clean && make 53 | ``` 54 | * Run a test file: 55 | ```bash 56 | ./scripts/test [path-to-test-file] 57 | ``` 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ricardo Casía 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | 3 | mvn = ./tests/fixtures/maven-demo/mvnw 4 | gradle_groovy = ./tests/fixtures/gradle-groovy-demo/gradlew 5 | gradle_kotlin = ./tests/fixtures/gradle-kotlin-demo/gradlew 6 | 7 | all: hooks test 8 | 9 | hooks: 10 | cp -f ./git-hooks/* .git/hooks/ 11 | 12 | test: install 13 | ./scripts/test 14 | 15 | test-fail-fast: install 16 | ./scripts/test --fail-fast 17 | 18 | prepare-demo: 19 | # it is expected to fail because there are failing tests 20 | -$(mvn) -f tests/fixtures/maven-demo/pom.xml clean verify --fail-at-end -Dtest="*" 21 | -$(gradle_groovy) -p tests/fixtures/gradle-groovy-demo clean test --continue 22 | -$(gradle_kotlin) -p tests/fixtures/gradle-kotlin-demo clean test --continue 23 | 24 | install: deps/plenary.nvim deps/nvim-treesitter deps/nvim-treesitter/parser/java.so deps/nvim-treesitter/parser/groovy.so deps/neotest deps/nvim-nio 25 | 26 | deps/plenary.nvim: 27 | mkdir -p deps 28 | git clone --depth 1 https://github.com/nvim-lua/plenary.nvim.git $@ 29 | 30 | deps/nvim-treesitter: 31 | mkdir -p deps 32 | git clone --depth 1 https://github.com/nvim-treesitter/nvim-treesitter.git $@ 33 | 34 | deps/neotest: 35 | mkdir -p deps 36 | git clone --depth 1 https://github.com/nvim-neotest/neotest $@ 37 | 38 | deps/nvim-nio: 39 | mkdir -p deps 40 | git clone --depth 1 https://github.com/nvim-neotest/nvim-nio $@ 41 | 42 | deps/nvim-treesitter/parser/groovy.so: deps/nvim-treesitter 43 | nvim --headless -u tests/testrc.vim -c "TSInstallSync groovy | quit" 44 | 45 | deps/nvim-treesitter/parser/java.so: deps/nvim-treesitter 46 | nvim --headless -u tests/testrc.vim -c "TSInstallSync java | quit" 47 | 48 | 49 | clean: 50 | rm -rf deps/plenary.nvim deps/nvim-treesitter deps/neotest 51 | $(mvn) -f tests/fixtures/maven-demo/pom.xml clean 52 | $(gradle_groovy) -p tests/fixtures/gradle-groovy-demo clean 53 | $(gradle_kotlin) -p tests/fixtures/gradle-kotlin-demo clean 54 | 55 | validate: 56 | stylua --check . 57 | 58 | format: 59 | stylua . 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

neotest-java

5 |

Neotest adapter for Java, using JUnit.

6 | 7 | 8 | 9 | 10 | 11 | LuaRocks 12 | 13 | 14 | 15 | GitHub Repo stars 16 | 17 |
18 | 19 | ![image](https://github.com/user-attachments/assets/d1d77980-faab-4110-9b7c-ae6911a3d42c) 20 | 21 | ## ⭐ Features 22 | 23 | - ✅ Maven and Gradle projects 24 | - ✅ Multimodule projects 25 | - ✅ Integrated with [`nvim-dap`](https://github.com/mfussenegger/nvim-dap) for test debugging. 26 | 27 | > Check [ROADMAP.md](./ROADMAP.md) to see what's coming! 28 | 29 | ## :wrench: Installation 30 | 31 | ##### Install in 3 steps :athletic_shoe: 32 | 33 | 1. Make sure you have installed nvim-treesitter parsers. Use `:TSInstall java` 34 | 2. Add neotest-java to your config: 35 | 36 |
37 | LazyVim distro installation example 38 | 39 | ```lua 40 | return { 41 | { 42 | "rcasia/neotest-java", 43 | ft = "java", 44 | dependencies = { 45 | "mfussenegger/nvim-jdtls", 46 | "mfussenegger/nvim-dap", -- for the debugger 47 | "rcarriga/nvim-dap-ui", -- recommended 48 | "theHamsta/nvim-dap-virtual-text", -- recommended 49 | }, 50 | init = function() 51 | -- override the default keymaps. 52 | -- needed until neotest-java is integrated in LazyVim 53 | local keys = require("lazyvim.plugins.lsp.keymaps").get() 54 | -- run test file 55 | keys[#keys + 1] = {"tt", function() require("neotest").run.run(vim.fn.expand("%")) end, mode = "n" } 56 | -- run nearest test 57 | keys[#keys + 1] = {"tr", function() require("neotest").run.run() end, mode = "n" } 58 | -- debug test file 59 | keys[#keys + 1] = {"tD", function() require("neotest").run.run({ strategy = "dap" }) end, mode = "n" } 60 | -- debug nearest test 61 | keys[#keys + 1] = {"td", function() require("neotest").run.run({ vim.fn.expand("%"), strategy = "dap" }) end, mode = "n" } 62 | end, 63 | }, 64 | { 65 | "nvim-neotest/neotest", 66 | dependencies = { 67 | "nvim-neotest/nvim-nio", 68 | "nvim-lua/plenary.nvim", 69 | "antoinemadec/FixCursorHold.nvim", 70 | "nvim-treesitter/nvim-treesitter" 71 | }, 72 | opts = { 73 | adapters = { 74 | ["neotest-java"] = { 75 | -- config here 76 | }, 77 | }, 78 | }, 79 | }, 80 | } 81 | ``` 82 | 83 |
84 |
85 | lazy.nvim plugin manager example 86 | 87 | ```lua 88 | return { 89 | { 90 | "rcasia/neotest-java", 91 | ft = "java", 92 | dependencies = { 93 | "mfussenegger/nvim-jdtls", 94 | "mfussenegger/nvim-dap", -- for the debugger 95 | "rcarriga/nvim-dap-ui", -- recommended 96 | "theHamsta/nvim-dap-virtual-text", -- recommended 97 | }, 98 | }, 99 | { 100 | "nvim-neotest/neotest", 101 | dependencies = { 102 | "nvim-neotest/nvim-nio", 103 | "nvim-lua/plenary.nvim", 104 | "antoinemadec/FixCursorHold.nvim", 105 | "nvim-treesitter/nvim-treesitter" 106 | }, 107 | opts = { 108 | adapters = { 109 | require("neotest-java")({ 110 | -- config here 111 | }), 112 | }, 113 | }, 114 | }, 115 | } 116 | ``` 117 | 118 |
119 | 120 | 3. Run `:NeotestJava setup` 121 | 122 | > [!NOTE] 123 | > It will download the JUnit standalone jar from 124 | > 125 | 126 | ## :gear: Configuration 127 | 128 | ```lua 129 | { 130 | junit_jar = nil, -- default: stdpath("data") .. /nvim/neotest-java/junit-platform-console-standalone-[version].jar 131 | incremental_build = true 132 | } 133 | ``` 134 | 135 | ## :octocat: Contributing 136 | 137 | Feel free to contribute to this project by creating issues for bug 138 | reports, feature requests, or suggestions. 139 | 140 | You can also submit pull requests for any enhancements, bug fixes, or new features. 141 | 142 | Your contributions are greatly appreciated. See [CONTRIBUTING.md](https://github.com/rcasia/neotest-java/blob/main/CONTRIBUTING.md) 143 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | 2 | # ROADMAP 3 | 4 | The goal of this project is to achieve: 5 | 6 | - a smooth testing flow 7 | - quick test runs 8 | - being a reliable tool 9 | 10 | v1.0.0 11 | 12 | - [ ] Smooth feedback when there is a compilation error 13 | - [x] Compile with nvim-jdtls when available 14 | - [ ] Select and run certain testcases of parameterized tests 15 | - [ ] Integrate with [nvim-coverage](https://github.com/andythigpen/nvim-coverage) 16 | 17 | extra: 18 | 19 | - [ ] for maintainers: add luacov -> add more tests -> merciless refactor 20 | -------------------------------------------------------------------------------- /git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | make validate 4 | -------------------------------------------------------------------------------- /lua/neotest-java/build_tool/gradle.lua: -------------------------------------------------------------------------------- 1 | local compatible_path = require("neotest-java.util.compatible_path") 2 | local PROJECT_FILENAME = "build.gradle" 3 | 4 | ---@class neotest-java.GradleBuildTool : neotest-java.BuildTool 5 | local gradle = {} 6 | 7 | gradle.get_output_dir = function(root) 8 | root = root and root or "." 9 | return compatible_path(root .. "/bin") 10 | end 11 | 12 | function gradle.get_project_filename() 13 | return PROJECT_FILENAME 14 | end 15 | ---@type neotest-java.BuildTool 16 | return gradle 17 | -------------------------------------------------------------------------------- /lua/neotest-java/build_tool/init.lua: -------------------------------------------------------------------------------- 1 | local maven = require("neotest-java.build_tool.maven") 2 | local gradle = require("neotest-java.build_tool.gradle") 3 | local log = require("neotest-java.logger") 4 | local nio = require("nio") 5 | local Job = require("plenary.job") 6 | local lib = require("neotest.lib") 7 | 8 | ---@class neotest-java.BuildTool 9 | ---@field get_output_dir fun(root?: string): string 10 | ---@field get_project_filename fun(): string 11 | ---@field get_module_dependencies fun(root: string): table 12 | 13 | ---@type table 14 | local build_tools = { gradle = gradle, maven = maven } 15 | 16 | --- will determine the build tool to use 17 | ---@return neotest-java.BuildTool 18 | build_tools.get = function(project_type) 19 | if not build_tools[project_type] then 20 | error("unknown project type: " .. project_type) 21 | end 22 | return build_tools[project_type] 23 | end 24 | 25 | ---@param command string 26 | ---@param args string[] 27 | ---@return nio.control.Event 28 | build_tools.launch_debug_test = function(command, args) 29 | lib.notify("Running debug test", vim.log.levels.INFO) 30 | log.trace("run_debug_test function") 31 | 32 | local test_command_started_listening = nio.control.event() 33 | local terminated_command_event = nio.control.event() 34 | 35 | local stderr = {} 36 | local job = Job:new({ 37 | command = command, 38 | args = args, 39 | on_stderr = function(_, data) 40 | stderr[#stderr + 1] = data 41 | end, 42 | on_stdout = function(_, data) 43 | if string.find(data, "Listening") then 44 | test_command_started_listening.set() 45 | end 46 | end, 47 | on_exit = function(_, code) 48 | terminated_command_event.set() 49 | 50 | log.debug("command exited with code: ", code) 51 | if code ~= 0 then 52 | log.error("command exited with code: ", code) 53 | log.error("stderr: ", table.concat(stderr, "\n")) 54 | end 55 | end, 56 | }) 57 | log.debug("starting job with command: ", command, " ", table.concat(args, " ")) 58 | job:start() 59 | test_command_started_listening.wait() 60 | 61 | return terminated_command_event 62 | end 63 | 64 | return build_tools 65 | -------------------------------------------------------------------------------- /lua/neotest-java/build_tool/maven.lua: -------------------------------------------------------------------------------- 1 | local compatible_path = require("neotest-java.util.compatible_path") 2 | 3 | local PROJECT_FILE = "pom.xml" 4 | 5 | ---@class neotest-java.MavenBuildTool : neotest-java.BuildTool 6 | local maven = {} 7 | 8 | maven.get_output_dir = function(root) 9 | root = root and root or "." 10 | -- TODO: read from pom.xml 11 | return compatible_path(root .. "/target/classes") 12 | end 13 | 14 | function maven.get_project_filename() 15 | return PROJECT_FILE 16 | end 17 | 18 | ---@type neotest-java.BuildTool 19 | return maven 20 | -------------------------------------------------------------------------------- /lua/neotest-java/command/binaries.lua: -------------------------------------------------------------------------------- 1 | local jdtls = require("neotest-java.command.jdtls") 2 | local compatible_path = require("neotest-java.util.compatible_path") 3 | local logger = require("neotest-java.logger") 4 | 5 | local binaries = { 6 | 7 | java = function() 8 | local ok, jdtls_java_home = pcall(jdtls.get_java_home) 9 | 10 | if ok then 11 | return compatible_path(jdtls_java_home .. "/bin/java") 12 | end 13 | 14 | logger.warn("JAVA_HOME setting not found in jdtls. Using defualt binary: java") 15 | return "java" 16 | end, 17 | 18 | javac = function() 19 | local ok, jdtls_java_home = pcall(jdtls.get_java_home) 20 | 21 | if ok then 22 | return compatible_path(jdtls_java_home .. "/bin/javac") 23 | end 24 | 25 | logger.warn("JAVA_HOME setting not found in jdtls. Using default: javac") 26 | return "javac" 27 | end, 28 | } 29 | 30 | return binaries 31 | -------------------------------------------------------------------------------- /lua/neotest-java/command/jdtls.lua: -------------------------------------------------------------------------------- 1 | local nio = require("nio") 2 | local write_file = require("neotest-java.util.write_file") 3 | local compatible_path = require("neotest-java.util.compatible_path") 4 | 5 | local M = {} 6 | 7 | M.get_java_home = function() 8 | local bufnr = vim.api.nvim_get_current_buf() 9 | local uri = vim.uri_from_bufnr(bufnr) 10 | local future = nio.control.future() 11 | 12 | local setting = "org.eclipse.jdt.ls.core.vm.location" 13 | local cmd = { 14 | command = "java.project.getSettings", 15 | arguments = { uri, { setting } }, 16 | } 17 | require("jdtls.util").execute_command(cmd, function(err1, resp) 18 | assert(not err1, vim.inspect(err1)) 19 | future.set(resp) 20 | end, bufnr) 21 | 22 | local java_exec = future.wait() 23 | 24 | return java_exec[setting] 25 | end 26 | 27 | ---@param additional_classpath_entries string[] 28 | M.get_classpath = function(additional_classpath_entries) 29 | additional_classpath_entries = additional_classpath_entries or {} 30 | 31 | local classpaths = {} 32 | 33 | local bufnr = vim.api.nvim_get_current_buf() 34 | local uri = vim.uri_from_bufnr(bufnr) 35 | local runtime_classpath_future = nio.control.future() 36 | local test_classpath_future = nio.control.future() 37 | 38 | ---@param future nio.control.Future 39 | for scope, future in pairs({ ["runtime"] = runtime_classpath_future, ["test"] = test_classpath_future }) do 40 | local options = vim.json.encode({ scope = scope }) 41 | local cmd = { 42 | command = "java.project.getClasspaths", 43 | arguments = { uri, options }, 44 | } 45 | require("jdtls.util").execute_command(cmd, function(err1, resp) 46 | assert(not err1, vim.inspect(err1)) 47 | 48 | future.set(resp.classpaths) 49 | end, bufnr) 50 | end 51 | local runtime_classpaths = runtime_classpath_future.wait() 52 | local test_classpaths = test_classpath_future.wait() 53 | 54 | for _, v in ipairs(additional_classpath_entries) do 55 | classpaths[#classpaths + 1] = v 56 | end 57 | for _, v in ipairs(runtime_classpaths) do 58 | classpaths[#classpaths + 1] = v 59 | end 60 | for _, v in ipairs(test_classpaths) do 61 | classpaths[#classpaths + 1] = v 62 | end 63 | 64 | return classpaths 65 | end 66 | 67 | M.get_classpath_file_argument = function(report_dir, additional_classpath_entries) 68 | local classpath = table.concat(M.get_classpath(additional_classpath_entries), ":") 69 | local temp_file = compatible_path(report_dir .. "/.cp") 70 | write_file(temp_file, ("-cp %s"):format(classpath)) 71 | 72 | return ("@%s"):format(temp_file) 73 | end 74 | 75 | return M 76 | -------------------------------------------------------------------------------- /lua/neotest-java/command/junit_command_builder.lua: -------------------------------------------------------------------------------- 1 | local binaries = require("neotest-java.command.binaries") 2 | local java = binaries.java 3 | local logger = require("neotest-java.logger") 4 | 5 | --- @class CommandBuilder 6 | local CommandBuilder = { 7 | 8 | --- @return CommandBuilder 9 | new = function(self, config, project_type) 10 | self.__index = self 11 | self._junit_jar = config.junit_jar 12 | self._project_type = project_type 13 | return setmetatable({}, self) 14 | end, 15 | 16 | ---@param qualified_name string example: com.example.ExampleTest 17 | ---@param node_name? string example: shouldNotFail 18 | ---@return CommandBuilder 19 | test_reference = function(self, qualified_name, node_name, type) 20 | self._test_references = self._test_references or {} 21 | 22 | if type == "dir" then 23 | qualified_name = qualified_name:match("(.+)%..+") 24 | end 25 | 26 | local method_name 27 | if type == "test" then 28 | method_name = node_name 29 | end 30 | 31 | self._test_references[#self._test_references + 1] = { 32 | qualified_name = qualified_name, 33 | method_name = method_name, 34 | type = type, 35 | } 36 | 37 | return self 38 | end, 39 | 40 | get_referenced_classes = function(self) 41 | local classes = {} 42 | for _, v in ipairs(self._test_references) do 43 | local class_package = self:_create_method_qualified_reference(v.qualified_name) 44 | classes[#classes + 1] = class_package 45 | end 46 | return classes 47 | end, 48 | 49 | get_referenced_methods = function(self) 50 | local methods = {} 51 | for _, v in ipairs(self._test_references) do 52 | local method = self:_create_method_qualified_reference(v.qualified_name, v.method_name) 53 | methods[#methods + 1] = method 54 | end 55 | return methods 56 | end, 57 | 58 | _create_method_qualified_reference = function(_, qualified_name, method_name) 59 | if method_name == nil then 60 | return qualified_name 61 | end 62 | 63 | return qualified_name .. "#" .. method_name 64 | end, 65 | get_referenced_method_names = function(self) 66 | local method_names = {} 67 | for _, v in ipairs(self._test_references) do 68 | method_names[#method_names + 1] = v.method_name 69 | end 70 | return method_names 71 | end, 72 | 73 | reports_dir = function(self, reports_dir) 74 | self._reports_dir = reports_dir 75 | end, 76 | 77 | basedir = function(self, basedir) 78 | logger.debug("assigned basedir: " .. basedir) 79 | self._basedir = basedir 80 | end, 81 | 82 | classpath_file_arg = function(self, classpath_file_arg) 83 | self._classpath_file_arg = classpath_file_arg 84 | end, 85 | 86 | --- @param port? number 87 | --- @return { command: string, args: string[] } 88 | build_junit = function(self, port) 89 | assert(self._test_references, "test_references cannot be nil") 90 | assert(self._basedir, "basedir cannot be nil") 91 | assert(self._classpath_file_arg, "classpath_file_arg cannot be nil") 92 | 93 | local selectors = {} 94 | for _, v in ipairs(self._test_references) do 95 | if v.type == "test" then 96 | table.insert(selectors, "-m=" .. v.qualified_name .. "#" .. v.method_name) 97 | elseif v.type == "file" then 98 | table.insert(selectors, "-c=" .. v.qualified_name) 99 | elseif v.type == "dir" then 100 | selectors = "-p=" .. v.qualified_name 101 | end 102 | end 103 | assert(#selectors ~= 0, "junit command has to have a selector") 104 | 105 | local junit_command = { 106 | command = java(), 107 | args = { 108 | "-jar", 109 | self._junit_jar, 110 | "execute", 111 | ("%s"):format(self._classpath_file_arg), 112 | "--reports-dir=" .. self._reports_dir, 113 | "--fail-if-no-tests", 114 | "--disable-banner", 115 | "--details=testfeed", 116 | "--config=junit.platform.output.capture.stdout=true", 117 | }, 118 | } 119 | -- add selectors 120 | for _, v in ipairs(selectors) do 121 | table.insert(junit_command.args, v) 122 | end 123 | 124 | -- add debug arguments if debug port is specified 125 | if port then 126 | table.insert(junit_command.args, 1, "-Xdebug") 127 | table.insert( 128 | junit_command.args, 129 | 1, 130 | "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0.0.0.0:" .. port 131 | ) 132 | end 133 | 134 | return junit_command 135 | end, 136 | 137 | --- @param port? number 138 | --- @return { command: string, args: string[] } 139 | build_to_string = function(self, port) 140 | local c = self:build_junit(port) 141 | return c.command .. " " .. table.concat(c.args, " ") 142 | end, 143 | } 144 | 145 | return CommandBuilder 146 | -------------------------------------------------------------------------------- /lua/neotest-java/command/run.lua: -------------------------------------------------------------------------------- 1 | local process = require("neotest.lib.process") 2 | 3 | local function string_to_table(str) 4 | local result_table = {} 5 | for word in str:gmatch("%S+") do 6 | table.insert(result_table, word) 7 | end 8 | return result_table 9 | end 10 | 11 | ---@param command string | string[] 12 | ---@param output_file? string 13 | local function run(command, output_file) 14 | if type(command) == "string" then 15 | command = string_to_table(command) 16 | end 17 | 18 | local exit_code, res = process.run(command, { stdout = true, stderr = true }) 19 | 20 | assert( 21 | exit_code == 0, 22 | "error while running command " .. table.concat(command, " ") .. " exit code: " .. exit_code .. " " .. res.stderr 23 | ) 24 | 25 | if output_file then 26 | local file = io.open(output_file, "w") or error("Unable to open file for writing: " .. output_file) 27 | file:write(res.stdout) 28 | file:close() 29 | end 30 | 31 | return res.stdout 32 | end 33 | 34 | return run 35 | -------------------------------------------------------------------------------- /lua/neotest-java/context_holder.lua: -------------------------------------------------------------------------------- 1 | local log = require("neotest-java.logger") 2 | local compatible_path = require("neotest-java.util.compatible_path") 3 | 4 | local DEFAULT_VERSION = "1.10.1" 5 | 6 | ---@type neotest-java.ConfigOpts 7 | local default_config = { 8 | junit_jar = compatible_path( 9 | ("%s/neotest-java/junit-platform-console-standalone-%s.jar"):format(vim.fn.stdpath("data"), DEFAULT_VERSION) 10 | ), 11 | incremental_build = true, 12 | default_version = DEFAULT_VERSION, 13 | } 14 | 15 | ---@type neotest-java.Context 16 | local context = { root = nil, config = default_config } 17 | 18 | ---@class neotest-java.ContextHolder 19 | return { 20 | -- 21 | get_context = function() 22 | return context 23 | end, 24 | config = function() 25 | return context.config 26 | end, 27 | set_opts = function(opts) 28 | context.config = vim.tbl_extend("force", context.config, opts) 29 | log.debug("Config updated: ", context.config) 30 | end, 31 | set_root = function(root) 32 | context.root = root 33 | end, 34 | } 35 | 36 | ---@class neotest-java.ConfigOpts 37 | ---@field junit_jar string 38 | ---@field incremental_build boolean 39 | ---@field default_version string 40 | 41 | ---@class neotest-java.Context 42 | ---@field config neotest-java.ConfigOpts 43 | ---@field root string|nil 44 | -------------------------------------------------------------------------------- /lua/neotest-java/core/dir_filter.lua: -------------------------------------------------------------------------------- 1 | local DirFilter = { 2 | -- List of directories to exclude from search 3 | excluded_directories = { 4 | "target", 5 | "build", 6 | "out", 7 | "bin", 8 | "resources", 9 | "main", 10 | }, 11 | } 12 | 13 | ---Filter directories when searching for test files 14 | ---@async 15 | ---@param name string Name of directory 16 | ---@param rel_path string Path to directory, relative to root 17 | ---@param root string Root directory of project 18 | ---@return boolean 19 | function DirFilter.filter_dir(name, rel_path, root) -- luacheck: ignore 212 unused argument 20 | for _, v in ipairs(DirFilter.excluded_directories) do 21 | if string.find(rel_path, "test") then 22 | return true 23 | end 24 | 25 | if string.find(rel_path, v) then 26 | return false 27 | end 28 | end 29 | 30 | return true 31 | end 32 | 33 | return DirFilter 34 | -------------------------------------------------------------------------------- /lua/neotest-java/core/file_checker.lua: -------------------------------------------------------------------------------- 1 | local JAVA_TEST_FILE_PATTERNS = require("neotest-java.types.patterns").JAVA_TEST_FILE_PATTERNS 2 | 3 | local FileChecker = {} 4 | 5 | ---@async 6 | ---@param file_path string 7 | ---@return boolean 8 | function FileChecker.is_test_file(file_path) 9 | if string.find(file_path, "/main/") then 10 | return false 11 | end 12 | for _, pattern in ipairs(JAVA_TEST_FILE_PATTERNS) do 13 | if string.find(file_path, pattern) then 14 | return true 15 | end 16 | end 17 | return false 18 | end 19 | 20 | return FileChecker 21 | -------------------------------------------------------------------------------- /lua/neotest-java/core/positions_discoverer.lua: -------------------------------------------------------------------------------- 1 | local lib = require("neotest.lib") 2 | 3 | local PositionsDiscoverer = {} 4 | 5 | ---Given a file path, parse all the tests within it. 6 | ---@async 7 | ---@param file_path string Absolute file path 8 | ---@return neotest.Tree | nil 9 | function PositionsDiscoverer.discover_positions(file_path) 10 | local annotations = { "Test", "ParameterizedTest", "TestFactory", "CartesianTest" } 11 | local a = vim.iter(annotations) 12 | :map(function(v) 13 | return string.format([["%s"]], v) 14 | end) 15 | :join(" ") 16 | 17 | local query = [[ 18 | 19 | ;; Test class 20 | (class_declaration 21 | name: (identifier) @namespace.name 22 | ) @namespace.definition 23 | 24 | ;; annotated functions 25 | (method_declaration 26 | (modifiers 27 | (marker_annotation 28 | name: (identifier) @annotation 29 | (#any-of? @annotation ]] .. a .. [[) 30 | ) 31 | ) 32 | name: (identifier) @test.name 33 | ) @test.definition 34 | 35 | ]] 36 | 37 | return lib.treesitter.parse_positions(file_path, query, { nested_namespaces = true }) 38 | end 39 | 40 | return PositionsDiscoverer 41 | -------------------------------------------------------------------------------- /lua/neotest-java/core/result_builder.lua: -------------------------------------------------------------------------------- 1 | local xml = require("neotest.lib.xml") 2 | local flat_map = require("neotest-java.util.flat_map") 3 | local resolve_qualified_name = require("neotest-java.util.resolve_qualified_name") 4 | local log = require("neotest-java.logger") 5 | local lib = require("neotest.lib") 6 | local JunitResult = require("neotest-java.types.junit_result") 7 | local SKIPPED = JunitResult.SKIPPED 8 | 9 | local REPORT_FILE_NAMES_PATTERN = "TEST-.+%.xml$" 10 | 11 | --- @param classname string name of class 12 | --- @param testname string name of test 13 | --- @return string unique_key based on classname and testname 14 | local build_unique_key = function(classname, testname) 15 | -- replace all $ for :: 16 | classname = classname:gsub("%$", "::") 17 | 18 | return classname .. "::" .. testname 19 | end 20 | 21 | local function is_parameterized_test(testcases, name) 22 | -- regex to match the name with some parameters and index at the end 23 | -- example: subtractAMinusBEqualsC(int, int, int)[1] 24 | local regex = name .. "[%(%.%{]?.*%[%d+%]$" 25 | 26 | for k, _ in pairs(testcases) do 27 | if string.match(k, regex) then 28 | return true 29 | end 30 | end 31 | 32 | return false 33 | end 34 | 35 | ---@return neotest-java.JunitResult[] 36 | local function extract_parameterized_tests(testcases, name) 37 | -- regex to match the name with some parameters and index at the end 38 | -- example: subtractAMinusBEqualsC(int, int, int)[1] 39 | local regex = name .. "[%(%.%{]?.*%[%d+%]$" 40 | 41 | local tests = {} 42 | for k, v in pairs(testcases) do 43 | if string.match(k, regex) then 44 | tests[#tests + 1] = JunitResult:new(v) 45 | end 46 | end 47 | 48 | return tests 49 | end 50 | 51 | local ResultBuilder = {} 52 | 53 | ---@async 54 | ---@param spec neotest.RunSpec 55 | ---@param result neotest.StrategyResult 56 | ---@param tree neotest.Tree 57 | ---@param scan fun(dir: string, opts: table): string[] 58 | ---@param read_file fun(filepath: string): string 59 | ---@return table 60 | function ResultBuilder.build_results(spec, result, tree, scan, read_file) -- luacheck: ignore 212 unused argument 61 | scan = scan or require("plenary.scandir").scan_dir 62 | read_file = read_file or require("neotest-java.util.read_file") 63 | 64 | -- if the test command failed, return an error 65 | if result.code ~= 0 and result.code ~= 1 then 66 | local node = tree:data() 67 | return { [node.id] = JunitResult.ERROR(node.id, result.output) } 68 | end 69 | 70 | -- wait for the debug test to finish 71 | if spec.context.strategy == "dap" then 72 | spec.context.terminated_command_event.wait() 73 | end 74 | 75 | ---@type table 76 | local results = {} 77 | 78 | local testcases = {} 79 | ---@type table 80 | local testcases_junit = {} 81 | 82 | local report_filepaths = scan(spec.context.reports_dir, { 83 | search_pattern = REPORT_FILE_NAMES_PATTERN, 84 | }) 85 | log.debug("Found report files: ", report_filepaths) 86 | 87 | assert(#report_filepaths ~= 0, "no report file could be generated") 88 | 89 | local testcases_in_xml = flat_map(function(filepath) 90 | local ok, data = pcall(function() 91 | return read_file(filepath) 92 | end) 93 | if not ok then 94 | lib.notify("Error reading file: " .. filepath) 95 | return {} 96 | end 97 | 98 | local xml_data = xml.parse(data) 99 | 100 | local testcases_in_xml = xml_data.testsuite.testcase 101 | if not vim.isarray(testcases_in_xml) then 102 | testcases_in_xml = { testcases_in_xml } 103 | end 104 | return testcases_in_xml 105 | end, report_filepaths) 106 | 107 | for _, testcase in ipairs(testcases_in_xml) do 108 | local jresult = JunitResult:new(testcase) 109 | local name = jresult:name() 110 | local classname = jresult:classname() 111 | 112 | name = name:gsub("%(.*%)", "") 113 | local unique_key = build_unique_key(classname, name) 114 | testcases[unique_key] = testcase 115 | testcases_junit[unique_key] = jresult 116 | end 117 | 118 | for _, node in tree:iter() do 119 | local is_test = node.type == "test" 120 | local is_parameterized = is_parameterized_test(testcases, node.name) 121 | 122 | if not is_test then 123 | goto continue 124 | end 125 | 126 | local qualified_name = resolve_qualified_name(node.path) 127 | 128 | -- node.id contains something like: "com/example/MyTest.java::MyTest::MyInnerTest::test" 129 | local pattern = "::([^:]+)" -- will match all the nested classes and test method name 130 | local nested_classes = vim.iter(node.id:gmatch(pattern)):totable() 131 | 132 | local inner_classes = vim 133 | .iter(nested_classes) 134 | -- 135 | :skip(1) -- skip the base class 136 | :rskip(1) -- skip the test method name 137 | :join("::") 138 | 139 | if inner_classes ~= "" then 140 | qualified_name = qualified_name .. "::" .. inner_classes 141 | end 142 | 143 | local unique_key = build_unique_key(qualified_name, node.name) 144 | 145 | if is_parameterized then 146 | local jtestcases = extract_parameterized_tests(testcases, unique_key) 147 | results[node.id] = JunitResult.merge_results(jtestcases) 148 | else 149 | local jtestcase = testcases_junit[unique_key] 150 | 151 | if not jtestcase then 152 | results[node.id] = SKIPPED(node.id) 153 | else 154 | results[node.id] = jtestcase:result() 155 | end 156 | end 157 | 158 | ::continue:: 159 | end 160 | 161 | return results 162 | end 163 | 164 | return ResultBuilder 165 | -------------------------------------------------------------------------------- /lua/neotest-java/core/root_finder.lua: -------------------------------------------------------------------------------- 1 | local log = require("neotest-java.logger") 2 | 3 | local RootFinder = {} 4 | 5 | ---Find the project root directory given a current directory to work from. 6 | ---Should no root be found, the adapter can still be used in a non-project context if a test file matches. 7 | ---@async 8 | ---@param dir string @Directory to treat as cwd 9 | ---@param matcher fun(pattern: string): fun(dir: string): string | nil 10 | ---@return string | nil @Absolute root dir of test suite 11 | function RootFinder.find_root(dir, matcher) 12 | local patterns = { 13 | "pom.xml", 14 | "settings.gradle", 15 | "settings.gradle.kts", 16 | ".git", 17 | } 18 | 19 | for _, m in ipairs(patterns) do 20 | local root = matcher(m)(dir) 21 | 22 | if root then 23 | log.debug("Found root: " .. root) 24 | return root 25 | end 26 | end 27 | 28 | log.debug("No root found for " .. dir .. ".") 29 | return nil 30 | end 31 | 32 | return RootFinder 33 | -------------------------------------------------------------------------------- /lua/neotest-java/core/spec_builder/compiler/init.lua: -------------------------------------------------------------------------------- 1 | local jdtls_compiler = require("neotest-java.core.spec_builder.compiler.jdtls") 2 | 3 | ---@class NeotestJavaCompiler.Opts 4 | ---@field cwd string 5 | ---@field classpath_file_dir string 6 | ---@field compile_mode string 7 | 8 | --- Interface for Java compilers 9 | ---@class NeotestJavaCompiler 10 | local NeotestJavaCompiler = {} 11 | 12 | ---@param opts NeotestJavaCompiler.Opts 13 | ---@return string classpath_file_arg 14 | function NeotestJavaCompiler.compile(opts) end 15 | 16 | ---@type table 17 | local compilers = { 18 | jdtls = jdtls_compiler, 19 | } 20 | 21 | return compilers 22 | -------------------------------------------------------------------------------- /lua/neotest-java/core/spec_builder/compiler/jdtls.lua: -------------------------------------------------------------------------------- 1 | local logger = require("neotest-java.logger") 2 | local nio = require("nio") 3 | local _jdtls = require("neotest-java.command.jdtls") 4 | local scan = require("plenary.scandir") 5 | 6 | ---@type NeotestJavaCompiler 7 | local jdtls_compiler = { 8 | compile = function(args) 9 | -- check there is an active java client 10 | local clients = vim.lsp.get_clients({ name = "jdtls" }) 11 | local client = assert(clients and clients[1], "there is no jdtls client attached.") 12 | 13 | logger.debug(("compilation in %s mode"):format(args.compile_mode)) 14 | nio.run(function(_) 15 | nio.scheduler() 16 | local bufnr = 0 --TODO: set bufnr to the java file being compiled 17 | client:request( 18 | "java/buildWorkspace", 19 | { forceRebuild = args.compile_mode == "full" }, 20 | function(err, result, ctx) 21 | if err then 22 | logger.error("compilation failed: " .. vim.inspect(err)) 23 | end 24 | end, 25 | bufnr 26 | ) 27 | end):wait() 28 | logger.debug("compilation complete!") 29 | 30 | local resources = scan.scan_dir(args.cwd, { 31 | only_dirs = true, 32 | search_pattern = "test/resources$", 33 | }) 34 | 35 | return _jdtls.get_classpath_file_argument(args.classpath_file_dir, resources) 36 | end, 37 | } 38 | 39 | return jdtls_compiler 40 | -------------------------------------------------------------------------------- /lua/neotest-java/core/spec_builder/init.lua: -------------------------------------------------------------------------------- 1 | ---@module "neotest" 2 | 3 | local CommandBuilder = require("neotest-java.command.junit_command_builder") 4 | local resolve_qualfied_name = require("neotest-java.util.resolve_qualified_name") 5 | local logger = require("neotest-java.logger") 6 | local random_port = require("neotest-java.util.random_port") 7 | local build_tools = require("neotest-java.build_tool") 8 | local nio = require("nio") 9 | local path = require("plenary.path") 10 | local compatible_path = require("neotest-java.util.compatible_path") 11 | local Project = require("neotest-java.types.project") 12 | local ch = require("neotest-java.context_holder") 13 | local find_module_by_filepath = require("neotest-java.util.find_module_by_filepath") 14 | local compilers = require("neotest-java.core.spec_builder.compiler") 15 | 16 | local SpecBuilder = {} 17 | 18 | ---@param args neotest.RunArgs 19 | ---@param project_type string 20 | ---@param config neotest-java.ConfigOpts 21 | ---@return nil | neotest.RunSpec | neotest.RunSpec[] 22 | function SpecBuilder.build_spec(args, project_type, config) 23 | if args.strategy == "dap" then 24 | local ok_dap, _ = pcall(require, "dap") 25 | assert(ok_dap, "neotest-java requires nvim-dap to run debug tests") 26 | end 27 | 28 | local command = CommandBuilder:new(config, project_type) 29 | local tree = args.tree 30 | local position = tree:data() 31 | local root = assert(ch:get_context().root) 32 | local absolute_path = position.path 33 | local project = assert(Project.from_root_dir(root), "project not detected correctly") 34 | local modules = project:get_modules() 35 | 36 | -- make sure we are in root_dir 37 | nio.fn.chdir(root) 38 | 39 | -- make sure outputDir is created to operate in it 40 | local output_dir = build_tools.get(project_type).get_output_dir() 41 | local output_dir_parent = compatible_path(path:new(output_dir):parent().filename) 42 | 43 | vim.uv.fs_mkdir(output_dir_parent, 493) 44 | vim.uv.fs_mkdir(output_dir, 493) 45 | 46 | -- JUNIT REPORT DIRECTORY 47 | local reports_dir = 48 | compatible_path(string.format("%s/junit-reports/%s", output_dir, nio.fn.strftime("%d%m%y%H%M%S"))) 49 | command:reports_dir(compatible_path(reports_dir)) 50 | 51 | local module_dirs = vim.iter(modules) 52 | :map(function(mod) 53 | return mod.base_dir 54 | end) 55 | :totable() 56 | local base_dir = assert(find_module_by_filepath(module_dirs, position.path), "module base_dir not found") 57 | command:basedir(base_dir) 58 | 59 | -- TEST SELECTORS 60 | if position.type == "dir" then 61 | for _, child in tree:iter() do 62 | if child.type == "file" then 63 | command:test_reference(resolve_qualfied_name(child.path), child.name, "file") 64 | end 65 | end 66 | elseif position.type == "namespace" then 67 | for _, child in tree:iter() do 68 | if child.type == "test" then 69 | command:test_reference(resolve_qualfied_name(child.path), child.name, "test") 70 | end 71 | end 72 | elseif position.type == "file" then 73 | command:test_reference(resolve_qualfied_name(absolute_path), position.name, "file") 74 | elseif position.type == "test" then 75 | -- note: parameterized tests are not being discovered by the junit standalone, so we run tests per file 76 | command:test_reference(resolve_qualfied_name(absolute_path), position.name, "file") 77 | end 78 | 79 | -- COMPILATION STEP 80 | local compile_mode = ch.config().incremental_build and "incremental" or "full" 81 | local classpath_file_arg = 82 | compilers.jdtls.compile({ cwd = base_dir, classpath_file_dir = output_dir, compile_mode = compile_mode }) 83 | command:classpath_file_arg(classpath_file_arg) 84 | 85 | -- DAP STRATEGY 86 | if args.strategy == "dap" then 87 | local port = random_port() 88 | 89 | -- PREPARE DEBUG TEST COMMAND 90 | local junit = command:build_junit(port) 91 | logger.debug("junit debug command: ", junit.command, " ", table.concat(junit.args, " ")) 92 | local terminated_command_event = build_tools.launch_debug_test(junit.command, junit.args) 93 | 94 | return { 95 | strategy = { 96 | type = "java", 97 | request = "attach", 98 | name = ("neotest-java (on port %s)"):format(port), 99 | port = port, 100 | }, 101 | cwd = root, 102 | symbol = position.name, 103 | context = { 104 | strategy = args.strategy, 105 | reports_dir = reports_dir, 106 | terminated_command_event = terminated_command_event, 107 | }, 108 | } 109 | end 110 | 111 | -- NORMAL STRATEGY 112 | logger.info("junit command: ", command:build_to_string()) 113 | return { 114 | command = command:build_to_string(), 115 | cwd = root, 116 | symbol = position.name, 117 | context = { reports_dir = reports_dir }, 118 | } 119 | end 120 | 121 | return SpecBuilder 122 | -------------------------------------------------------------------------------- /lua/neotest-java/init.lua: -------------------------------------------------------------------------------- 1 | local File = require("neotest.lib.file") 2 | 3 | local file_checker = require("neotest-java.core.file_checker") 4 | local root_finder = require("neotest-java.core.root_finder") 5 | local dir_filter = require("neotest-java.core.dir_filter") 6 | local position_discoverer = require("neotest-java.core.positions_discoverer") 7 | local spec_builder = require("neotest-java.core.spec_builder") 8 | local result_builder = require("neotest-java.core.result_builder") 9 | local log = require("neotest-java.logger") 10 | local ch = require("neotest-java.context_holder") 11 | local lib = require("neotest.lib") 12 | 13 | local junit_version = ch.config().default_version 14 | 15 | local detect_project_type = require("neotest-java.util.detect_project_type") 16 | 17 | local check_junit_jar = function(filepath) 18 | local exists, _ = File.exists(filepath) 19 | assert( 20 | exists, 21 | ([[ 22 | Junit Platform Console Standalone jar not found at %s 23 | Please run the following command to download it: NeotestJava setup 24 | Or alternatively, download it from https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/%s/junit-platform-console-standalone-%s.jar 25 | ]]):format(filepath, junit_version, junit_version) 26 | ) 27 | end 28 | 29 | ---@type neotest.Adapter 30 | local NeotestJavaAdapter = { 31 | name = "neotest-java", 32 | filter_dir = dir_filter.filter_dir, 33 | is_test_file = file_checker.is_test_file, 34 | discover_positions = position_discoverer.discover_positions, 35 | results = result_builder.build_results, 36 | root = function(dir) 37 | local root = root_finder.find_root(dir, lib.files.match_root_pattern) 38 | if root then 39 | ch.set_root(root) 40 | end 41 | return root 42 | end, 43 | build_spec = function(args) 44 | check_junit_jar(ch.get_context().config.junit_jar) 45 | 46 | -- TODO: find a way to avoid to make this steps every time 47 | 48 | -- find root 49 | local root = ch.get_context().root or root_finder.find_root(vim.fn.getcwd()) 50 | assert(root, "root directory not found") 51 | 52 | -- detect project type 53 | local project_type = detect_project_type(root) 54 | 55 | -- build spec 56 | return spec_builder.build_spec(args, project_type, ch.get_context().config) 57 | end, 58 | }; 59 | 60 | -- on init 61 | (function() 62 | log.info("neotest-java adapter initialized") 63 | 64 | -- create data directory if it doesn't exist 65 | local data_dir = vim.fn.stdpath("data") .. "/neotest-java" 66 | vim.uv.fs_mkdir(data_dir, 493) 67 | end)() 68 | 69 | setmetatable(NeotestJavaAdapter, { 70 | __call = function(_, opts) 71 | opts = opts or {} 72 | 73 | ch.set_opts(opts) 74 | 75 | return NeotestJavaAdapter 76 | end, 77 | }) 78 | 79 | return NeotestJavaAdapter 80 | -------------------------------------------------------------------------------- /lua/neotest-java/logger.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/nvim-neotest/neotest/blob/f30bab1faef13d47f3905e065215c96a42d075ad/lua/neotest/logging.lua 2 | local config = require("neotest.config") 3 | local utils = require("neotest.utils") 4 | local loggers = {} 5 | 6 | local log_date_format = "%FT%H:%M:%SZ%z" 7 | 8 | ---@class neotest.Logger 9 | ---@field trace function 10 | ---@field debug function 11 | ---@field info function 12 | ---@field warn function 13 | ---@field error function 14 | local Logger = {} 15 | 16 | local LARGE = 1e9 17 | 18 | ---@return neotest.Logger 19 | function Logger.new(filename, opts) 20 | opts = opts or {} 21 | local logger = loggers[filename] 22 | if logger then 23 | return logger 24 | end 25 | logger = {} 26 | setmetatable(logger, { __index = Logger }) 27 | loggers[filename] = logger 28 | local path_sep = (function() 29 | if jit then 30 | local os = string.lower(jit.os) 31 | if os == "linux" or os == "osx" or os == "bsd" then 32 | return "/" 33 | else 34 | return "\\" 35 | end 36 | else 37 | return package.config:sub(1, 1) 38 | end 39 | end)() 40 | 41 | local function path_join(...) 42 | return table.concat(utils.tbl_flatten({ ... }), path_sep) 43 | end 44 | 45 | logger._level = opts.level or config.log_level 46 | local ok, logpath = pcall(vim.fn.stdpath, "log") 47 | if not ok then 48 | logpath = vim.fn.stdpath("cache") 49 | end 50 | logger._filename = path_join(logpath, filename .. ".log") 51 | 52 | vim.fn.mkdir(logpath, "p") 53 | local logfile = assert(io.open(logger._filename, "a+")) 54 | 55 | local log_info = vim.loop.fs_stat(logger._filename) 56 | if log_info and log_info.size > LARGE then 57 | local warn_msg = 58 | string.format("Neotest log is large (%d MB): %s", log_info.size / (1000 * 1000), logger._filename) 59 | vim.notify(warn_msg, vim.log.levels.WARN) 60 | end 61 | 62 | for level, levelnr in pairs(vim.log.levels) do 63 | logger[level:lower()] = function(...) 64 | local argc = select("#", ...) 65 | if levelnr < logger._level then 66 | return false 67 | end 68 | if argc == 0 then 69 | return true 70 | end 71 | local info = debug.getinfo(2, "Sl") 72 | local fileinfo = string.format("%s:%s", info.short_src, info.currentline) 73 | local parts = { 74 | table.concat({ level, "|", os.date(log_date_format), "|", fileinfo, "|" }, " "), 75 | } 76 | if _G._NEOTEST_IS_CHILD then 77 | table.insert(parts, "CHILD |") 78 | end 79 | for i = 1, argc do 80 | local arg = select(i, ...) 81 | if arg == nil then 82 | table.insert(parts, "") 83 | elseif type(arg) == "string" then 84 | table.insert(parts, arg) 85 | elseif type(arg) == "table" and arg.__tostring then 86 | table.insert(parts, arg.__tostring(arg)) 87 | else 88 | table.insert(parts, vim.inspect(arg)) 89 | end 90 | end 91 | logfile:write(table.concat(parts, " "), "\n") 92 | logfile:flush() 93 | end 94 | end 95 | return logger 96 | end 97 | 98 | function Logger:set_level(level) 99 | self._level = assert( 100 | type(level) == "number" and level or vim.log.levels[tostring(level):upper()], 101 | string.format("Log level must be one of (trace, debug, info, warn, error), got: %q", level) 102 | ) 103 | end 104 | 105 | function Logger:get_filename() 106 | return self._filename 107 | end 108 | 109 | return Logger.new("neotest-java", { level = vim.log.levels.TRACE }) 110 | -------------------------------------------------------------------------------- /lua/neotest-java/types/ignore_path_patterns.lua: -------------------------------------------------------------------------------- 1 | local IGNORE_PATH_PATTERNS = { 2 | "^build[/\\]", -- build directory at the root 3 | "[/\\]build[/\\]", -- build directory in subdirectories 4 | "^target[/\\]", -- target directory at the root (Maven) 5 | "[/\\]target[/\\]", -- target directory in subdirectories 6 | "^%.gradle[/\\]", -- .gradle directory at the root 7 | "[/\\]%.gradle[/\\]", -- .gradle directory in subdirectories 8 | "^%.mvn[/\\]", -- .mvn directory at the root 9 | "[/\\]%.mvn[/\\]", -- .mvn directory in subdirectories 10 | "%.class$", -- compiled Java class files 11 | "^%.settings[/\\]", -- .settings directory (Eclipse) 12 | "[/\\]%.settings[/\\]", 13 | "^%.project$", -- Eclipse project file 14 | "^%.classpath$", -- Eclipse classpath file 15 | "^gradle%.app%.settings$", -- Gradle app settings file 16 | "^gradlew$", -- Gradle wrapper script (Unix) 17 | "^gradlew%.bat$", -- Gradle wrapper script (Windows) 18 | "^gradle/wrapper[/\\]", -- Gradle wrapper directory 19 | "[/\\]%.idea[/\\]", -- IntelliJ IDEA directory 20 | "%.iml$", -- IntelliJ IDEA module files 21 | "[/\\]%.vscode[/\\]", -- Visual Studio Code directory 22 | "%.log$", -- Log files 23 | "^out[/\\]", -- Output directory 24 | "[/\\]out[/\\]", 25 | "[/\\]node_modules[/\\]", -- Node.js modules directory (if applicable) 26 | "^node_modules[/\\]", 27 | "%.jar$", -- JAR files 28 | "%.war$", -- WAR files 29 | "^%.DS_Store$", -- macOS file 30 | "^%.classpath$", -- Eclipse classpath file 31 | } 32 | 33 | return IGNORE_PATH_PATTERNS 34 | -------------------------------------------------------------------------------- /lua/neotest-java/types/junit_result.lua: -------------------------------------------------------------------------------- 1 | local nio = require("nio") 2 | 3 | local FAILED = require("neotest.types").ResultStatus.failed 4 | local PASSED = require("neotest.types").ResultStatus.passed 5 | local SKIPPED = require("neotest.types").ResultStatus.skipped 6 | 7 | local LINE_SEPARATOR = "=================================\n" 8 | local NEW_LINE = "\n" 9 | 10 | ---@param data string | string[] | table 11 | ---@return string | nil filepath 12 | local function create_file_with_content(data) 13 | if not data then 14 | return nil 15 | end 16 | 17 | if type(data) == "table" then 18 | if #data == 0 then 19 | return nil 20 | end 21 | data = vim.iter(vim.tbl_values(data)):flatten(math.huge):totable() 22 | data = table.concat(data, LINE_SEPARATOR) 23 | end 24 | 25 | -- Generate a unique temporary file name 26 | local filepath = nio.fn.tempname() 27 | 28 | nio.run(function() 29 | -- Open the file in write mode 30 | local file = assert(io.open(filepath, "w")) 31 | 32 | file:write(data) 33 | 34 | -- Close the file 35 | file:close() 36 | end) 37 | 38 | -- Return the path to the file 39 | return filepath 40 | end 41 | 42 | ---@class neotest-java.JunitResult 43 | ---@field testcase table 44 | local JunitResult = {} 45 | 46 | ---@return neotest.Result 47 | function JunitResult.SKIPPED(id) 48 | return { 49 | status = SKIPPED, 50 | output = create_file_with_content({ id, "This test was not executed." }), 51 | } 52 | end 53 | 54 | ---@return neotest.Result 55 | function JunitResult.ERROR(id, output) 56 | return { 57 | status = "failed", 58 | output = output or create_file_with_content({ id, "This test execution had an unexpected error." }), 59 | } 60 | end 61 | 62 | function JunitResult:new(testcase) 63 | self.__index = self 64 | return setmetatable({ testcase = testcase }, self) 65 | end 66 | 67 | function JunitResult:id() 68 | return self:name() 69 | end 70 | 71 | ---@return string 72 | function JunitResult:name() 73 | return self.testcase._attr.name 74 | end 75 | 76 | ---@return string 77 | function JunitResult:classname() 78 | return self.testcase._attr.classname 79 | end 80 | 81 | ---@return neotest.ResultStatus 82 | ---@return string | nil failure_message a short output 83 | ---@return string | nil failure_output a more detailed output 84 | function JunitResult:status() 85 | local failed = self.testcase.failure or self.testcase.error 86 | -- This is not parsed correctly by the library 87 | -- 88 | -- it breaks in the first '>' 89 | -- so it does not detect message attribute sometimes 90 | if failed and not failed._attr then 91 | return FAILED, failed[1], failed[2] 92 | end 93 | if failed and failed._attr then 94 | return FAILED, failed._attr.message, failed[1] 95 | end 96 | return PASSED 97 | end 98 | 99 | ---@param with_name_prefix? boolean 100 | ---@return neotest.Error[] | nil 101 | function JunitResult:errors(with_name_prefix) 102 | with_name_prefix = with_name_prefix or false 103 | local status, failure_message, failure_output = self:status() 104 | local filename = string.match(self:classname(), "[%.]?([%a%$_][%a%d%$_]+)$") .. ".java" 105 | local line_searchpattern = string.gsub(filename, "%.", "%%.") .. ":(%d+)%)" 106 | 107 | local line 108 | if failure_output then 109 | line = string.match(failure_output, line_searchpattern) 110 | -- NOTE: errors array is expecting lines properties to be 0 index based 111 | line = line and line - 1 or nil 112 | end 113 | 114 | if status == PASSED then 115 | return nil 116 | end 117 | if with_name_prefix then 118 | failure_message = self:name() .. " -> " .. failure_message 119 | end 120 | return { { message = failure_message, line = line } } 121 | end 122 | 123 | ---@return string[] 124 | function JunitResult:output() 125 | local system_out = self.testcase["system-out"] or {} 126 | if type(system_out) == "string" then 127 | system_out = { system_out } 128 | end 129 | 130 | local status, _, failure_output = self:status() 131 | if status == FAILED then 132 | system_out[#system_out + 1] = failure_output 133 | else -- PASSED 134 | system_out[#system_out + 1] = "Test passed" .. NEW_LINE 135 | end 136 | 137 | return system_out 138 | end 139 | 140 | --- Convert neotest-java.JunitResult to neotest.Result 141 | --- Each time this function is called, it will create a temporary file with the output content 142 | ---@return neotest.Result 143 | function JunitResult:result() 144 | local status, failure_message = self:status() 145 | return { 146 | status = status, 147 | short = failure_message, 148 | errors = self:errors(), 149 | output = create_file_with_content(self:output()), 150 | } 151 | end 152 | 153 | ---@param results neotest-java.JunitResult[] 154 | ---@return neotest.Result 155 | function JunitResult.merge_results(results) 156 | table.sort(results, function(a, b) 157 | return a:name() < b:name() 158 | end) 159 | 160 | local status = vim.iter(results):any(function(result) 161 | return result:status() == FAILED 162 | end) and FAILED or PASSED 163 | 164 | local output = vim.iter(results) 165 | :map(function(result) 166 | return result:output() 167 | end) 168 | :flatten(math.huge) 169 | :totable() 170 | 171 | if status == PASSED then 172 | return { status = status, output = create_file_with_content(output) } 173 | end 174 | 175 | local errors = vim.iter(results) 176 | :map(function(result) 177 | return result:errors(true) 178 | end) 179 | :flatten() 180 | :totable() 181 | 182 | local short = vim.iter(results) 183 | :filter(function(result) 184 | return result:status() == FAILED 185 | end) 186 | :map(function(result) 187 | return result:errors(), result:name() 188 | end) 189 | :map(function(error, name) 190 | return name .. " -> " .. error[1].message 191 | end) 192 | :fold(nil, function(a, b) 193 | if not a then 194 | return b 195 | end 196 | return a .. NEW_LINE .. b 197 | end) 198 | 199 | return { status = status, errors = errors, short = short, output = create_file_with_content(output) } 200 | end 201 | 202 | return JunitResult 203 | -------------------------------------------------------------------------------- /lua/neotest-java/types/module.lua: -------------------------------------------------------------------------------- 1 | ---@class neotest-java.Module 2 | ---@field base_dir string 3 | ---@field _build_tool neotest-java.BuildTool 4 | ---@field name string 5 | ---@field module_dependencies string[] 6 | local Module = {} 7 | Module.__index = Module 8 | 9 | ---@param base_dir string 10 | ---@param build_tool neotest-java.BuildTool 11 | ---@return neotest-java.Module 12 | function Module.new(base_dir, build_tool) 13 | local self = setmetatable({}, Module) 14 | self.base_dir = base_dir 15 | self.name = base_dir:match("([^/]+)$") 16 | self._build_tool = build_tool 17 | return self 18 | end 19 | 20 | function Module:get_output_dir() 21 | return self._build_tool.get_output_dir(self.base_dir) 22 | end 23 | 24 | ---@return string[] 25 | function Module:get_module_dependencies() 26 | return self.module_dependencies or {} 27 | end 28 | 29 | return Module 30 | -------------------------------------------------------------------------------- /lua/neotest-java/types/patterns.lua: -------------------------------------------------------------------------------- 1 | local TEST_CLASS_PATTERNS = { 2 | "Test$", 3 | "Tests$", 4 | "Spec$", 5 | "IT$", 6 | } 7 | 8 | local JAVA_TEST_FILE_PATTERNS = { 9 | "Test%.java$", 10 | "Tests%.java$", 11 | "Spec%.java$", 12 | "IT%.java$", 13 | } 14 | 15 | return { 16 | TEST_CLASS_PATTERNS = TEST_CLASS_PATTERNS, 17 | JAVA_TEST_FILE_PATTERNS = JAVA_TEST_FILE_PATTERNS, 18 | } 19 | -------------------------------------------------------------------------------- /lua/neotest-java/types/project.lua: -------------------------------------------------------------------------------- 1 | local build_tools = require("neotest-java.build_tool") 2 | local detect_project_type = require("neotest-java.util.detect_project_type") 3 | local Module = require("neotest-java.types.module") 4 | local scan = require("plenary.scandir") 5 | local logger = require("neotest-java.logger") 6 | local should_ignore_path = require("neotest-java.util.should_ignore_path") 7 | local compatible_path = require("neotest-java.util.compatible_path") 8 | local Path = require("plenary.path") 9 | 10 | ---@class neotest-java.Project 11 | ---@field root_dir string 12 | ---@field build_tool neotest-java.BuildTool 13 | local Project = {} 14 | Project.__index = Project 15 | 16 | ---@param root_dir string 17 | ---@return neotest-java.Project 18 | function Project.from_root_dir(root_dir) 19 | local self = setmetatable({}, Project) 20 | self.root_dir = root_dir 21 | self.build_tool = build_tools.get(detect_project_type(root_dir)) 22 | return self 23 | end 24 | 25 | ---@return neotest-java.Module[] 26 | function Project:get_modules() 27 | local project_file = self.build_tool.get_project_filename() 28 | logger.debug("Searching for project files: ", project_file) 29 | logger.debug("Root directory: ", self.root_dir) 30 | 31 | -- NOTE: flag respect_gitignore does not work with "build.gradle" 32 | local dirs = scan.scan_dir(self.root_dir, { 33 | search_pattern = function(path) 34 | return not should_ignore_path(path) and path:find(self.build_tool.get_project_filename()) 35 | end, 36 | respect_gitignore = false, 37 | }) 38 | 39 | assert(dirs and #dirs > 0, "should find at least 1 module") 40 | 41 | logger.debug("Found project directories: ", dirs) 42 | 43 | ---@type table 44 | local modules = {} 45 | for _, dir in ipairs(dirs) do 46 | local base_dir = compatible_path(Path:new(dir):parent().filename) 47 | local mod = Module.new(base_dir, self.build_tool) 48 | modules[#modules + 1] = mod 49 | end 50 | 51 | local base_dirs = {} 52 | for _, mod in ipairs(modules) do 53 | base_dirs[#base_dirs + 1] = mod.base_dir 54 | end 55 | logger.debug("modules: ", base_dirs) 56 | 57 | return modules 58 | end 59 | 60 | return Project 61 | -------------------------------------------------------------------------------- /lua/neotest-java/types/test_class_patterns.lua: -------------------------------------------------------------------------------- 1 | local TEST_CLASS_PATTERNS = { 2 | "Test$", 3 | "Tests$", 4 | "Spec$", 5 | "IT$", 6 | } 7 | 8 | return TEST_CLASS_PATTERNS 9 | -------------------------------------------------------------------------------- /lua/neotest-java/util/compatible_path.lua: -------------------------------------------------------------------------------- 1 | ---@param path string 2 | ---@return string compatible_path 3 | local function compatible_path(path) 4 | -- check if the system is windows 5 | local has_win = vim.fn.has("win64") == 1 or vim.fn.has("win32") == 1 6 | -- set path separator based on the system 7 | local sep = has_win and "\\" or "/" 8 | 9 | -- preserve relative path prefix if present 10 | local relative_prefix = "" 11 | if path:sub(1, 2) == "./" or path:sub(1, 2) == ".\\" then 12 | relative_prefix = "." .. sep 13 | end 14 | 15 | -- replace separators with the system's separator 16 | path = path:gsub("%./", sep):gsub("%.\\", sep):gsub("/", sep):gsub("\\", sep) 17 | 18 | -- normalize the path and remove duplicate separators 19 | return (relative_prefix .. vim.fs.normalize(path)):gsub(sep .. "+", sep) 20 | end 21 | 22 | return compatible_path 23 | -------------------------------------------------------------------------------- /lua/neotest-java/util/detect_project_type.lua: -------------------------------------------------------------------------------- 1 | local compatible_path = require("neotest-java.util.compatible_path") 2 | --- @param project_root_path string 3 | --- @return string "gradle" | "maven" | "unknown" 4 | local function detect_project_type(project_root_path) 5 | local gradle_kotlin_settings_file = compatible_path(project_root_path .. "/settings.gradle.kts") 6 | local gradle_groovy_settings_file = compatible_path(project_root_path .. "/settings.gradle") 7 | local maven_build_file = compatible_path(project_root_path .. "/pom.xml") 8 | 9 | if 10 | vim.fn.filereadable(gradle_groovy_settings_file) == 1 11 | or vim.fn.filereadable(gradle_kotlin_settings_file) == 1 12 | then 13 | return "gradle" 14 | elseif vim.fn.filereadable(maven_build_file) == 1 then 15 | return "maven" 16 | end 17 | 18 | return "unknown" 19 | end 20 | 21 | return detect_project_type 22 | -------------------------------------------------------------------------------- /lua/neotest-java/util/find_module_by_filepath.lua: -------------------------------------------------------------------------------- 1 | local logger = require("neotest-java.logger") 2 | 3 | ---@param filepath string 4 | ---@param module_dirs string[] 5 | ---@return string | nil module_dir 6 | local find_module_by_filepath = function(module_dirs, filepath) 7 | logger.debug("module_dirs", module_dirs) 8 | logger.debug("filepath", filepath) 9 | if not filepath or filepath == "" then 10 | return nil 11 | end 12 | 13 | -- Normalize paths to use forward slashes for Windows compatibility 14 | filepath = filepath:gsub("\\", "/") 15 | local normalized_module_dirs = vim.tbl_map(function(dir) 16 | return dir:gsub("\\", "/") 17 | end, module_dirs) 18 | 19 | local matches = {} 20 | 21 | for _, module_dir in ipairs(normalized_module_dirs) do 22 | -- Escape any special characters in module_dir for pattern matching 23 | local escaped_module_dir = module_dir:gsub("([^%w])", "%%%1") 24 | 25 | -- Build patterns to search for the module directory in the filepath 26 | local patterns = { 27 | "[%./]" .. escaped_module_dir .. "[%./]", -- Module in the middle 28 | "^" .. escaped_module_dir .. "[%./]", -- Module at the start 29 | "[%./]" .. escaped_module_dir .. "$", -- Module at the end 30 | "^" .. escaped_module_dir .. "$", -- Module is the entire path 31 | } 32 | 33 | for _, pattern in ipairs(patterns) do 34 | if filepath:find(pattern) then 35 | table.insert(matches, module_dir) 36 | break -- If we found a match for this module_dir, no need to check other patterns 37 | end 38 | end 39 | end 40 | 41 | -- Select the longest match from all the matches 42 | local longest_match = nil 43 | for _, match in ipairs(matches) do 44 | if not longest_match or #match > #longest_match then 45 | longest_match = match 46 | end 47 | end 48 | 49 | return longest_match 50 | end 51 | 52 | return find_module_by_filepath 53 | -------------------------------------------------------------------------------- /lua/neotest-java/util/flat_map.lua: -------------------------------------------------------------------------------- 1 | local function flat_map(func, t) 2 | local result = {} 3 | for _, v in ipairs(t) do 4 | local mapped = func(v) 5 | if type(mapped) == "table" then 6 | vim.list_extend(result, mapped) 7 | else 8 | table.insert(result, mapped) 9 | end 10 | end 11 | return result 12 | end 13 | 14 | return flat_map 15 | -------------------------------------------------------------------------------- /lua/neotest-java/util/just_take_the_dependency.lua: -------------------------------------------------------------------------------- 1 | local function take_just_the_dependency(line) 2 | -- Pattern to match groupId, artifactId, and version 3 | local pattern = "([%a][%w_%-%.]*):([%a][%w_%-%.]*)(.-):([%d][%w_%-%.]*)" 4 | local _, new_version = line:match("(%d+%.%d+%.%d+) %-+> (%d+%.%d+%.%d+)") 5 | local groupId, artifactId, _, version = line:match(pattern) 6 | version = new_version or version 7 | if groupId and artifactId and version then 8 | return groupId .. ":" .. artifactId .. ":" .. version 9 | else 10 | return nil 11 | end 12 | end 13 | 14 | return take_just_the_dependency 15 | -------------------------------------------------------------------------------- /lua/neotest-java/util/random_port.lua: -------------------------------------------------------------------------------- 1 | local uv = vim.uv 2 | 3 | ---@return number port 4 | local random_port = function() 5 | local server = assert(uv.new_tcp()) 6 | assert(server:bind("127.0.0.1", 0)) 7 | local port = server:getsockname().port 8 | server:close() 9 | return port 10 | end 11 | 12 | return random_port 13 | -------------------------------------------------------------------------------- /lua/neotest-java/util/read_file.lua: -------------------------------------------------------------------------------- 1 | local File = require("neotest.lib.file") 2 | local compatible_path = require("neotest-java.util.compatible_path") 3 | 4 | ---@param path string 5 | local function read_file(path) 6 | return File.read(compatible_path(path)) 7 | end 8 | 9 | return read_file 10 | -------------------------------------------------------------------------------- /lua/neotest-java/util/read_xml_tag.lua: -------------------------------------------------------------------------------- 1 | local memo = require("neotest.lib.func_util.memoize") 2 | local file = require("neotest.lib.file") 3 | local xml = require("neotest.lib.xml") 4 | 5 | ---@param filepath string 6 | ---@param selector string ex: project.build.sourceDirectory 7 | ---@return string | nil 8 | local cache = {} 9 | local read_xml_tag = memo(function(filepath, selector) 10 | local content = file.read(filepath) 11 | local parsed = xml.parse(content) 12 | 13 | for tag in string.gmatch(selector, "[^%.]+") do 14 | if not parsed[tag] then 15 | return nil 16 | end 17 | parsed = parsed[tag] 18 | end 19 | 20 | if type(parsed) == "table" then 21 | return nil 22 | end 23 | 24 | return parsed 25 | end, cache) 26 | 27 | return read_xml_tag 28 | -------------------------------------------------------------------------------- /lua/neotest-java/util/resolve_qualified_name.lua: -------------------------------------------------------------------------------- 1 | local read_file = require("neotest-java.util.read_file") 2 | local TEST_CLASS_PATTERNS = require("neotest-java.types.test_class_patterns") 3 | 4 | local CLASSNAME_QUERY = [[ 5 | ((class_declaration (identifier) @target)) 6 | ]] 7 | 8 | local PACKAGE_QUERY = [[ 9 | 10 | ((package_declaration (scoped_identifier) @package.name)) 11 | 12 | ((package_declaration (identifier) @package.name)) 13 | 14 | ]] 15 | 16 | local function resolve_qualified_name(filename) 17 | ---@param raw_query string 18 | ---@param content string 19 | ---@return string[] 20 | local function find_in_text(raw_query, content) 21 | local query = vim.treesitter.query.parse("java", raw_query) 22 | 23 | local lang_tree = vim.treesitter.get_string_parser(content, "java") 24 | local root = lang_tree:parse()[1]:root() 25 | 26 | local result = {} 27 | for _, node, _ in query:iter_captures(root, content, 0, -1) do --luacheck: ignore 512 loop is executed at most once 28 | result[#result + 1] = vim.treesitter.get_node_text(node, content) 29 | end 30 | return result 31 | end 32 | 33 | -- read the file 34 | local ok, content = pcall(function() 35 | return read_file(filename) 36 | end) 37 | if not ok then 38 | error(string.format("file does not exist: %s", filename)) 39 | end 40 | 41 | local package_lines = find_in_text(PACKAGE_QUERY, content) 42 | 43 | local package_line = (package_lines and package_lines[1]) and (package_lines[1] .. ".") or "" 44 | local names = find_in_text(CLASSNAME_QUERY, content) 45 | 46 | -- as there can be different class names 47 | -- searches for the one the mathces the test class patterns 48 | local name = nil 49 | for _, _name in ipairs(names) do 50 | for _, pattern in ipairs(TEST_CLASS_PATTERNS) do 51 | if _name:find(pattern) then 52 | name = _name 53 | break 54 | end 55 | end 56 | end 57 | assert(name, "test class name not found") -- should not happen 58 | 59 | return package_line .. name 60 | end 61 | 62 | return resolve_qualified_name 63 | -------------------------------------------------------------------------------- /lua/neotest-java/util/should_ignore_path.lua: -------------------------------------------------------------------------------- 1 | local ignore_patterns = require("neotest-java.types.ignore_path_patterns") 2 | 3 | -- Function to determine if a path should be ignored in Maven or Gradle projects 4 | local function should_ignore_path(path) 5 | -- Normalize the path separators to '/' 6 | local normalized_path = path:gsub("\\", "/") 7 | 8 | for _, pattern in ipairs(ignore_patterns) do 9 | if normalized_path:match(pattern) then 10 | return true -- Path should be ignored 11 | end 12 | end 13 | return false -- Path should not be ignored 14 | end 15 | 16 | return should_ignore_path 17 | -------------------------------------------------------------------------------- /lua/neotest-java/util/there_is_wrapper_in.lua: -------------------------------------------------------------------------------- 1 | --- @param path string 2 | --- @return boolean 3 | local function there_is_wrapper_in(path) 4 | local gradle_wrapper = path .. "/gradlew" 5 | local maven_wrapper = path .. "/mvnw" 6 | return vim.fn.filereadable(gradle_wrapper) == 1 or vim.fn.filereadable(maven_wrapper) == 1 7 | end 8 | 9 | return there_is_wrapper_in 10 | -------------------------------------------------------------------------------- /lua/neotest-java/util/timer.lua: -------------------------------------------------------------------------------- 1 | ---@class neotest-java.Timer 2 | ---@field startTime number 3 | ---@field endTime number 4 | local Timer = {} 5 | Timer.__index = Timer 6 | 7 | -- Method to stop the timer and return the elapsed time 8 | function Timer:stop() 9 | if self.startTime then 10 | self.endTime = os.clock() * 1000 11 | return math.ceil(self.endTime - self.startTime) 12 | else 13 | return nil, "Error: The timer has not been started." 14 | end 15 | end 16 | 17 | -- Method to get the elapsed time without stopping the timer 18 | function Timer:getElapsedTime() 19 | if self.startTime then 20 | if self.endTime then 21 | return self.endTime - self.startTime 22 | else 23 | return math.ceil((os.clock() * 1000) - self.startTime) 24 | end 25 | else 26 | return nil, "Error: The timer has not been started." 27 | end 28 | end 29 | 30 | -- Method to create and start a new timer 31 | function Timer.start() 32 | local instance = setmetatable({}, Timer) 33 | instance.startTime = os.clock() * 1000 34 | instance.endTime = nil 35 | return instance 36 | end 37 | 38 | ---@type neotest-java.Timer 39 | return Timer 40 | -------------------------------------------------------------------------------- /lua/neotest-java/util/write_file.lua: -------------------------------------------------------------------------------- 1 | local nio = require("nio") 2 | local compatible_path = require("neotest-java.util.compatible_path") 3 | local Path = require("plenary.path") 4 | local logger = require("neotest-java.logger") 5 | 6 | local BUFFER_SIZE = 500 7 | 8 | ---@param filepath string 9 | ---@param content string 10 | local function write_file(filepath, content) 11 | -- for os compatibibility 12 | local _filepath = compatible_path(filepath) 13 | -- create parent directories if they don't exist 14 | nio.fn.mkdir(Path:new(_filepath):parent():absolute(), "p") 15 | 16 | logger.debug("writing to file: ", _filepath) 17 | 18 | local file = io.open(_filepath, "w") or error("Could not open file for writing: " .. _filepath) 19 | local pointer = 1 20 | for i = BUFFER_SIZE, #content, BUFFER_SIZE do 21 | file:write(content:sub(pointer, i)) 22 | pointer = i + 1 23 | end 24 | if pointer <= #content then 25 | file:write(content:sub(pointer)) 26 | end 27 | 28 | file:close() 29 | end 30 | 31 | return write_file 32 | -------------------------------------------------------------------------------- /plugin/neotest-java.lua: -------------------------------------------------------------------------------- 1 | local exists = require("neotest.lib.file").exists 2 | local job = require("plenary.job") 3 | local logger = require("neotest.logging") 4 | local lib = require("neotest.lib") 5 | local ch = require("neotest-java.context_holder") 6 | local junit_version = ch.config().default_version 7 | 8 | local options = { 9 | setup = function() 10 | local filepath = ("%s/neotest-java/junit-platform-console-standalone-%s.jar"):format( 11 | vim.fn.stdpath("data"), 12 | junit_version 13 | ) 14 | if exists(filepath) then 15 | lib.notify("Already setup!") 16 | return 17 | end 18 | 19 | -- Download Junit Standalone Jar 20 | local stderr = {} 21 | job 22 | :new({ 23 | command = "curl", 24 | args = { 25 | "--output", 26 | filepath, 27 | ("https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/%s/junit-platform-console-standalone-%s.jar"):format( 28 | junit_version, 29 | junit_version 30 | ), 31 | "--create-dirs", 32 | }, 33 | on_stderr = function(_, data) 34 | table.insert(stderr, data) 35 | end, 36 | on_exit = function(_, code) 37 | if code == 0 then 38 | lib.notify("Downloaded Junit Standalone successfully!") 39 | else 40 | local output = table.concat(stderr, "\n") 41 | lib.notify(string.format("Error while downloading: \n %s", output), "error") 42 | logger.error(output) 43 | end 44 | end, 45 | }) 46 | :start() 47 | end, 48 | } 49 | 50 | vim.api.nvim_create_user_command("NeotestJava", function(info) 51 | local fun = options[info.args] or error("Invalid option") 52 | fun() 53 | end, { 54 | desc = "Setup neotest-java", 55 | nargs = 1, 56 | complete = function() 57 | -- keys from options 58 | return vim.tbl_keys(options) 59 | end, 60 | }) 61 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Adapted from https://github.com/nvim-neotest/neotest/blob/master/scripts/test 3 | 4 | tempfile=$(mktemp) 5 | 6 | 7 | if [[ -n $1 ]] && [[ $1 == "--fail-fast" ]]; then 8 | echo "Running tests with --fail-fast" 9 | nvim --headless --noplugin -u tests/testrc.vim \ 10 | -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/testrc.vim', sequential = true, keep_going = false}" | tee "${tempfile}" 11 | elif [[ -n $1 ]]; then 12 | nvim --headless --noplugin -u tests/testrc.vim -c "PlenaryBustedFile $1" | tee "${tempfile}" 13 | else 14 | nvim --headless --noplugin -u tests/testrc.vim \ 15 | -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/testrc.vim'}" | tee "${tempfile}" 16 | fi 17 | 18 | # Plenary doesn't emit exit code 1 when tests have errors during setup 19 | errors=$(sed 's/\x1b\[[0-9;]*m//g' "${tempfile}" | awk '/(Errors|Failed) :/ {print $3}' | grep -v '0') 20 | 21 | rm "${tempfile}" 22 | 23 | if [[ -n $errors ]]; then 24 | echo "Tests failed" 25 | exit 1 26 | fi 27 | 28 | exit 0 29 | 30 | -------------------------------------------------------------------------------- /tests/core/dir_filter_spec.lua: -------------------------------------------------------------------------------- 1 | local plugin = require("neotest-java") 2 | 3 | describe("DirFilter", function() 4 | it("should filter out excluded directories", function() 5 | local relative_paths = { 6 | "src/main/java/com/example/", 7 | "build/classes/com/example/Example.class", 8 | "out/classes/com/example/Example.class", 9 | "bin/classes/com/example/Example.class", 10 | "resources/classes/com/example/Example.class", 11 | "target/classes/com/example/Example.class", 12 | "src/main/java/com/example/Example.java", 13 | } 14 | 15 | local root = "/home/user/project" 16 | 17 | local name = "java" 18 | 19 | -- then 20 | for _, path in ipairs(relative_paths) do 21 | local result = plugin.filter_dir(name, path, root) 22 | 23 | -- print path when test fails 24 | if result then 25 | print("Expected to filter out: " .. path) 26 | end 27 | 28 | assert.is_false(result) 29 | end 30 | end) 31 | 32 | it("should not filter out directories test directories", function() 33 | local relative_paths = { 34 | "src/test/java/com/example/", 35 | "src/test/java/com/example/Example.java", 36 | "src/test/java/com/example/ExampleTest.java", 37 | "src/test/java/com/example/resources/ExampleSpec.java", 38 | "src/test/java/com/example/domain/resources/ExampleSpec.java", 39 | } 40 | 41 | local root = "/home/user/project" 42 | 43 | local name = "java" 44 | 45 | -- then 46 | for _, path in ipairs(relative_paths) do 47 | local result = plugin.filter_dir(name, path, root) 48 | 49 | -- print path when test fails 50 | if not result then 51 | print("Expected to not filter out: " .. path) 52 | end 53 | assert.is_true(result) 54 | end 55 | end) 56 | end) 57 | -------------------------------------------------------------------------------- /tests/core/file_checker_spec.lua: -------------------------------------------------------------------------------- 1 | local plugin = require("neotest-java") 2 | local it = require("nio").tests.it -- async 3 | 4 | describe("file_checker", function() 5 | it("should return true for test files", function() 6 | local test_files = { 7 | "src/test/java/neotest/NeotestTest.java", 8 | "src/test/java/neotest/RepositoryTests.java", 9 | "src/test/java/neotest/NeotestIT.java", 10 | "src/test/java/neotest/ProductAceptanceTests.java", 11 | "src/test/java/neotest/domain/ProductAceptanceTests.java", 12 | } 13 | 14 | for _, file_path in ipairs(test_files) do 15 | assert.is_true(plugin.is_test_file(file_path)) 16 | end 17 | end) 18 | 19 | it("should return false for a java non-test file", function() 20 | local non_test_files = { 21 | "src/test/java/neotest/Configuration.java", 22 | "src/test/java/neotest/TestRepository.java", 23 | "src/test/java/neotest/Neotest.java", 24 | } 25 | for _, file_path in ipairs(non_test_files) do 26 | assert.is_false(plugin.is_test_file(file_path)) 27 | end 28 | end) 29 | 30 | it("should return false for every class inside main folder", function() 31 | local non_test_files = { 32 | "src/main/java/neotest/NeotestTest.java", 33 | } 34 | for _, file_path in ipairs(non_test_files) do 35 | assert.is_false(plugin.is_test_file(file_path)) 36 | end 37 | end) 38 | end) 39 | -------------------------------------------------------------------------------- /tests/core/positions_discoverer_spec.lua: -------------------------------------------------------------------------------- 1 | local _ = require("vim.treesitter") -- NOTE: needed for loading treesitter upfront for the tests 2 | local async = require("nio").tests 3 | local plugin = require("neotest-java") 4 | 5 | local current_dir = vim.fn.expand("%:p:h") .. "/" 6 | 7 | describe("PositionsDiscoverer", function() 8 | async.it("should discover test method names", function() 9 | -- given 10 | local file_path = current_dir .. "tests/fixtures/Test.java" 11 | 12 | -- when 13 | local actual = plugin.discover_positions(file_path) 14 | 15 | -- then 16 | local actual_list = actual:to_list() 17 | 18 | assert.equals("shouldFindThis1", actual_list[2][2][1].name) 19 | assert.equals("shouldFindThis2", actual_list[2][3][1].name) 20 | assert.equals("shouldFindThis3", actual_list[2][4][1].name) 21 | assert.equals("shouldFindThis4", actual_list[2][5][1].name) 22 | 23 | -- should find 4 tests 24 | local actual_count = #actual:children()[1]:children() 25 | assert.equals(4, actual_count) 26 | end) 27 | 28 | async.it("should discover nested tests", function() 29 | -- given 30 | local file_path = current_dir .. "tests/fixtures/SomeNestedTest.java" 31 | 32 | -- when 33 | local actual = plugin.discover_positions(file_path) 34 | 35 | -- then 36 | local test_name = actual:to_list()[2][2][2][2][1].name 37 | assert.equals(test_name, "someTest") 38 | 39 | local another_outer_test_name = actual:to_list()[2][2][3][1].name 40 | assert.equals(another_outer_test_name, "oneMoreOuterTest") 41 | end) 42 | end) 43 | -------------------------------------------------------------------------------- /tests/core/result_builder_spec.lua: -------------------------------------------------------------------------------- 1 | local _ = require("vim.treesitter") -- NOTE: needed for loading treesitter upfront for the tests 2 | local async = require("nio").tests 3 | local plugin = require("neotest-java") 4 | local result_builder = require("neotest-java.core.result_builder") 5 | local tempname_fn = require("nio").fn.tempname 6 | 7 | local current_dir = vim.fn.fnamemodify(vim.fn.expand("%:p:h"), ":p") 8 | local TEMPNAME = "/tmp/tempname-1234" 9 | local MAVEN_REPORTS_DIR = vim.loop.cwd() .. "/tests/fixtures/maven-demo/target/surefire-reports/" 10 | 11 | local SUCCESSFUL_RESULT = { 12 | code = 0, 13 | output = "output", 14 | } 15 | 16 | local DEFAULT_SPEC = { 17 | cwd = vim.loop.cwd() .. "/tests/fixtures/maven-demo", 18 | context = { 19 | reports_dir = MAVEN_REPORTS_DIR, 20 | }, 21 | } 22 | 23 | local tempfiles = {} 24 | 25 | ---@param content string 26 | ---@return string filepath 27 | local function create_tempfile_with_test(content) 28 | local path = vim.fn.tempname() .. ".java" 29 | table.insert(tempfiles, path) 30 | local file = io.open(path, "w") 31 | file:write(content) 32 | file:close() 33 | return path 34 | end 35 | 36 | describe("ResultBuilder", function() 37 | async.before_each(function() 38 | -- mock the tempname function to return a fixed value 39 | require("nio").fn.tempname = function() 40 | return TEMPNAME 41 | end 42 | end) 43 | 44 | async.after_each(function() 45 | require("nio").fn.tempname = tempname_fn 46 | 47 | -- remove all temp files 48 | for _, path in ipairs(tempfiles) do 49 | os.remove(path) 50 | end 51 | end) 52 | 53 | async.it("throws error when no report files found", function() 54 | --given 55 | local scan_dir = function() 56 | return {} 57 | end 58 | 59 | local file_path = current_dir .. "tests/fixtures/maven-demo/src/test/java/com/example/ExampleTest.java" 60 | local tree = plugin.discover_positions(file_path) 61 | 62 | --when 63 | local _, err = pcall(result_builder.build_results, DEFAULT_SPEC, SUCCESSFUL_RESULT, tree, scan_dir) 64 | 65 | -- then 66 | assert.match("no report file could be generated", err) 67 | end) 68 | 69 | async.it("ignores report file when cannot be read", function() 70 | --given 71 | local scan_dir = function() 72 | return { "TEST-someTest.xml" } 73 | end 74 | local read_file = function() 75 | error("cannot read file") 76 | end 77 | 78 | local file_path = current_dir .. "tests/fixtures/maven-demo/src/test/java/com/example/ExampleTest.java" 79 | local tree = plugin.discover_positions(file_path) 80 | 81 | local expected = { 82 | [current_dir .. "tests/fixtures/maven-demo/src/test/java/com/example/ExampleTest.java::ExampleTest::shouldFail"] = { 83 | status = "skipped", 84 | output = TEMPNAME, 85 | }, 86 | [current_dir .. "tests/fixtures/maven-demo/src/test/java/com/example/ExampleTest.java::ExampleTest::shouldNotFail"] = { 87 | status = "skipped", 88 | output = TEMPNAME, 89 | }, 90 | } 91 | --when 92 | local results = result_builder.build_results(DEFAULT_SPEC, SUCCESSFUL_RESULT, tree, scan_dir, read_file) 93 | 94 | -- then 95 | assert.are.same(expected, results) 96 | end) 97 | 98 | async.it("should build results from report", function() 99 | --given 100 | local file_content = [[ 101 | package com.example; 102 | import org.junit.jupiter.api.Test; 103 | 104 | import static org.junit.jupiter.api.Assertions.assertTrue; 105 | 106 | public class ExampleTest { 107 | @Test 108 | void shouldNotFail() { 109 | assertTrue(true); 110 | } 111 | 112 | @Test 113 | void shouldFail() { 114 | assertTrue(false); 115 | } 116 | } 117 | ]] 118 | 119 | local report_file = [[ 120 | 121 | 122 | 123 | OUTPUT TEXT 124 | 125 | 126 | 127 | 128 | ]] 129 | 130 | local file_path = create_tempfile_with_test(file_content) 131 | local tree = plugin.discover_positions(file_path) 132 | local scan_dir = function() 133 | return { file_path } 134 | end 135 | local read_file = function() 136 | return report_file 137 | end 138 | 139 | local expected = { 140 | [file_path .. "::ExampleTest::shouldFail"] = { 141 | -- errors = { { line = 13, message = "expected: but was: " } }, 142 | errors = { { message = "expected: but was: " } }, 143 | short = "expected: but was: ", 144 | status = "failed", 145 | output = TEMPNAME, 146 | }, 147 | [file_path .. "::ExampleTest::shouldNotFail"] = { 148 | status = "passed", 149 | output = TEMPNAME, 150 | }, 151 | } 152 | 153 | --when 154 | local results = result_builder.build_results(DEFAULT_SPEC, SUCCESSFUL_RESULT, tree, scan_dir, read_file) 155 | 156 | --then 157 | assert.are.same(expected, results) 158 | end) 159 | 160 | async.it("builds the results for a test that has an error at start", function() 161 | --given 162 | local file_content = [[ 163 | 164 | package com.example; 165 | 166 | import static org.junit.jupiter.api.Assertions.assertEquals; 167 | 168 | import org.junit.jupiter.api.Test; 169 | import org.springframework.beans.factory.annotation.Value; 170 | import org.springframework.boot.test.context.SpringBootTest; 171 | 172 | import com.example.demo.DemoApplication; 173 | 174 | @SpringBootTest(classes = { DemoApplication.class }) 175 | public class ErroneousTest { 176 | @Value("${foo.property}") String requiredProperty; 177 | 178 | @Test 179 | void shouldFailOnError(){ 180 | assertEquals("test", requiredProperty); 181 | } 182 | } 183 | 184 | ]] 185 | local report_file = [[ 186 | 187 | 188 | 189 | ERROR OUTPUT TEXT 190 | 191 | 192 | SYSTEM OUTPUT TEXT 193 | 194 | 195 | 196 | ]] 197 | 198 | local file_path = create_tempfile_with_test(file_content) 199 | local tree = plugin.discover_positions(file_path) 200 | local scan_dir = function() 201 | return { file_path } 202 | end 203 | local read_file = function() 204 | return report_file 205 | end 206 | 207 | local expected = { 208 | [file_path .. "::ErroneousTest::shouldFailOnError"] = { 209 | errors = { 210 | { 211 | message = "Error creating bean with name 'com.example.ErroneousTest': Injection of autowired dependencies failed", 212 | }, 213 | }, 214 | output = TEMPNAME, 215 | short = "Error creating bean with name 'com.example.ErroneousTest': Injection of autowired dependencies failed", 216 | status = "failed", 217 | }, 218 | } 219 | 220 | --when 221 | local results = result_builder.build_results(DEFAULT_SPEC, SUCCESSFUL_RESULT, tree, scan_dir, read_file) 222 | 223 | --then 224 | assert.are.same(expected, results) 225 | end) 226 | 227 | async.it("builds the results for integrations tests", function() 228 | --given 229 | local file_content = [[ 230 | 231 | package com.example.demo; 232 | 233 | import org.junit.jupiter.api.Test; 234 | import org.springframework.boot.test.context.SpringBootTest; 235 | 236 | @SpringBootTest 237 | class RepositoryIT { 238 | 239 | @Test 240 | void shouldWorkProperly() { 241 | } 242 | 243 | } 244 | 245 | ]] 246 | 247 | local report_file = [[ 248 | 249 | 250 | 251 | SYSTEM OUTPUT TEXT 252 | 253 | 254 | 255 | ]] 256 | 257 | local file_path = create_tempfile_with_test(file_content) 258 | local expected = { 259 | [file_path .. "::RepositoryIT::shouldWorkProperly"] = { 260 | status = "passed", 261 | output = TEMPNAME, 262 | }, 263 | } 264 | 265 | local tree = plugin.discover_positions(file_path) 266 | local scan_dir = function() 267 | return { file_path } 268 | end 269 | local read_file = function() 270 | return report_file 271 | end 272 | 273 | --when 274 | local results = result_builder.build_results(DEFAULT_SPEC, SUCCESSFUL_RESULT, tree, scan_dir, read_file) 275 | 276 | --then 277 | assert.are.same(expected, results) 278 | end) 279 | 280 | async.it("builds results for parameterized test with @CsvSource", function() 281 | --given 282 | local file_content = [[ 283 | package com.example; 284 | 285 | import org.junit.jupiter.params.ParameterizedTest; 286 | import org.junit.jupiter.params.provider.CsvSource; 287 | 288 | import static org.junit.jupiter.api.Assertions.assertTrue; 289 | 290 | public class ParameterizedMethodTest { 291 | 292 | @ParameterizedTest 293 | @CsvSource({ 294 | "1,1,2", 295 | "1,2,3", 296 | "2,3,5", 297 | "15,15,30" 298 | }) 299 | void parameterizedMethodShouldNotFail(Integer a, Integer b, Integer result) { 300 | assertTrue(a + b == result); 301 | } 302 | 303 | @ParameterizedTest 304 | @CsvSource({ 305 | "1,2", 306 | "3,4", 307 | "4,4" 308 | }) 309 | void parameterizedMethodShouldFail(Integer a, Integer b) { 310 | assertTrue(a == b); 311 | } 312 | } 313 | 314 | ]] 315 | 316 | local report_file = [[ 317 | 318 | 319 | 320 | FAILURE OUTPUT 321 | 322 | 323 | 324 | 325 | FAILURE OUTPUT 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | ]] 335 | 336 | local file_path = create_tempfile_with_test(file_content) 337 | 338 | local tree = plugin.discover_positions(file_path) 339 | local scan_dir = function() 340 | return { file_path } 341 | end 342 | local read_file = function() 343 | return report_file 344 | end 345 | 346 | local expected = { 347 | [file_path .. "::ParameterizedMethodTest::parameterizedMethodShouldFail"] = { 348 | errors = { 349 | { 350 | -- line = 27, 351 | message = "parameterizedMethodShouldFail(Integer, Integer)[1] -> expected: but was: ", 352 | }, 353 | { 354 | -- line = 27, 355 | message = "parameterizedMethodShouldFail(Integer, Integer)[2] -> expected: but was: ", 356 | }, 357 | }, 358 | short = "parameterizedMethodShouldFail(Integer, Integer)[1] -> expected: but was: \nparameterizedMethodShouldFail(Integer, Integer)[2] -> expected: but was: ", 359 | status = "failed", 360 | output = TEMPNAME, 361 | }, 362 | [file_path .. "::ParameterizedMethodTest::parameterizedMethodShouldNotFail"] = { 363 | status = "passed", 364 | output = TEMPNAME, 365 | }, 366 | } 367 | --when 368 | local results = result_builder.build_results(DEFAULT_SPEC, SUCCESSFUL_RESULT, tree, scan_dir, read_file) 369 | 370 | --then 371 | assert.are.same(expected, results) 372 | end) 373 | 374 | async.it("builds the results for parameterized with @EmptySource test", function() 375 | --given 376 | local file_content = [[ 377 | package com.example; 378 | 379 | import org.junit.jupiter.params.ParameterizedTest; 380 | import org.junit.jupiter.params.provider.EmptySource; 381 | 382 | import static org.junit.jupiter.api.Assertions.assertTrue; 383 | 384 | import org.apache.logging.log4j.util.Strings; 385 | 386 | import static org.junit.jupiter.api.Assertions.assertFalse; 387 | 388 | public class EmptySourceTest { 389 | 390 | @ParameterizedTest 391 | @EmptySource 392 | void emptySourceShouldPass(String input) { 393 | assertTrue(Strings.isBlank(input)); 394 | } 395 | 396 | @ParameterizedTest 397 | @EmptySource 398 | void emptySourceShouldFail(String input) { 399 | assertFalse(Strings.isBlank(input)); 400 | } 401 | } 402 | ]] 403 | 404 | local report_file = [[ 405 | 406 | 407 | 408 | FAILURE OUTPUT 409 | 410 | 411 | 412 | 413 | ]] 414 | 415 | local file_path = create_tempfile_with_test(file_content) 416 | 417 | local tree = plugin.discover_positions(file_path) 418 | local scan_dir = function() 419 | return { file_path } 420 | end 421 | local read_file = function() 422 | return report_file 423 | end 424 | 425 | local expected = { 426 | [file_path .. "::EmptySourceTest::emptySourceShouldFail"] = { 427 | errors = { 428 | { 429 | -- line = 22, 430 | message = "emptySourceShouldFail(String)[1] -> expected: but was: ", 431 | }, 432 | }, 433 | short = "emptySourceShouldFail(String)[1] -> expected: but was: ", 434 | status = "failed", 435 | output = TEMPNAME, 436 | }, 437 | [file_path .. "::EmptySourceTest::emptySourceShouldPass"] = { 438 | status = "passed", 439 | output = TEMPNAME, 440 | }, 441 | } 442 | --when 443 | local results = result_builder.build_results(DEFAULT_SPEC, SUCCESSFUL_RESULT, tree, scan_dir, read_file) 444 | 445 | --then 446 | assert.are.same(expected, results) 447 | end) 448 | 449 | async.it("should build results for nested tests", function() 450 | local file_content = [[ 451 | package com.example; 452 | 453 | import org.junit.jupiter.api.Nested; 454 | import org.junit.jupiter.api.Test; 455 | 456 | class NestedTest { 457 | 458 | @Test 459 | void plainTest() { 460 | 461 | } 462 | 463 | @Nested 464 | class Level2 { 465 | 466 | @Test 467 | void nestedTest() { 468 | 469 | } 470 | } 471 | 472 | } 473 | ]] 474 | 475 | local report_file = [[ 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | ]] 485 | 486 | local file_path = create_tempfile_with_test(file_content) 487 | 488 | local tree = plugin.discover_positions(file_path) 489 | local scan_dir = function() 490 | return { file_path } 491 | end 492 | local read_file = function() 493 | return report_file 494 | end 495 | 496 | local expected = { 497 | [file_path .. "::NestedTest::Level2::nestedTest"] = { 498 | status = "passed", 499 | output = TEMPNAME, 500 | }, 501 | [file_path .. "::NestedTest::plainTest"] = { 502 | status = "passed", 503 | output = TEMPNAME, 504 | }, 505 | } 506 | 507 | --when 508 | local results = result_builder.build_results(DEFAULT_SPEC, SUCCESSFUL_RESULT, tree, scan_dir, read_file) 509 | 510 | --then 511 | assert.are.same(expected, results) 512 | end) 513 | end) 514 | -------------------------------------------------------------------------------- /tests/core/root_finder_spec.lua: -------------------------------------------------------------------------------- 1 | local root_finder = require("neotest-java.core.root_finder") 2 | 3 | describe("RootFinder", function() 4 | it("should find the root when matcher matches", function() 5 | -- given 6 | local function matcher() 7 | return function(dir) 8 | return dir 9 | end 10 | end 11 | local dir = "example/dir" 12 | 13 | -- when 14 | local actualRoot = root_finder.find_root(dir, matcher) 15 | 16 | -- then 17 | assert.are.same(dir, actualRoot) 18 | end) 19 | 20 | it("should not find the root when matcher does not match", function() 21 | -- given 22 | local function matcher() 23 | return function() 24 | return nil 25 | end 26 | end 27 | local dir = "example/dir" 28 | 29 | -- when 30 | local actualRoot = root_finder.find_root(dir, matcher) 31 | 32 | -- then 33 | assert.is_nil(actualRoot) 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /tests/fixtures/SomeNestedTest.java: -------------------------------------------------------------------------------- 1 | 2 | public class SomeTest { 3 | public static class SomeNestedTest { 4 | public static class AnotherNestedTest { 5 | @Test 6 | public void someTest() { 7 | assertEquals(1 + 1, 2); 8 | } 9 | } 10 | 11 | @Test 12 | public void oneMoreOuterTest() { 13 | assertEquals(1 + 1, 2); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/fixtures/Test.java: -------------------------------------------------------------------------------- 1 | 2 | class Test { 3 | 4 | @Test 5 | public void shouldFindThis1() { 6 | assertThat(1).isEqualTo(1); 7 | } 8 | 9 | @ParameterizedTest 10 | @ValueSource(ints = {1, 2, 3}) 11 | public void shouldFindThis2(int i) { 12 | assertThat(i).isGreaterThan(0); 13 | } 14 | 15 | @Test 16 | public void shouldFindThis3() { 17 | assertThat(1).isEqualTo(1); 18 | } 19 | 20 | @ParameterizedTest 21 | @MethodSource("provideStringsForIsBlank") 22 | public void shouldFindThis4() { 23 | assertThat(1).isEqualTo(1); 24 | } 25 | 26 | private void assertThat(int i) { 27 | // do nothing 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /tests/fixtures/build-example.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | ext { 4 | springBootVersion = '2.1.3.RELEASE' 5 | } 6 | repositories { 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 11 | classpath "io.spring.gradle:dependency-management-plugin:1.0.6.RELEASE" 12 | } 13 | } 14 | 15 | allprojects { 16 | group 'com.sungjun' 17 | version '1.0-SNAPSHOT' 18 | } 19 | 20 | subprojects { 21 | apply plugin: 'java' 22 | apply plugin: 'org.springframework.boot' 23 | apply plugin: 'io.spring.dependency-management' 24 | 25 | sourceCompatibility = 1.8 26 | 27 | repositories { 28 | mavenCentral() 29 | } 30 | 31 | dependencies { 32 | testCompile group: 'junit', name: 'junit', version: '4.12' 33 | } 34 | } 35 | 36 | project(':sample-api') { 37 | dependencies { 38 | compile project(':sample-common') 39 | } 40 | } 41 | 42 | project(':sample-admin') { 43 | dependencies { 44 | compile project(':sample-common') 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.3' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'com.example' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter' 20 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 21 | } 22 | 23 | tasks.named('test') { 24 | useJUnitPlatform() 25 | } 26 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcasia/neotest-java/3cc0bceb0b0957e918939ba95434197e6b3775e7/tests/fixtures/gradle-groovy-demo/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'demo' 2 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/test/java/com/example/EmptySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.EmptySource; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import org.apache.logging.log4j.util.Strings; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | 12 | public class EmptySourceTest { 13 | 14 | @ParameterizedTest 15 | @EmptySource 16 | void emptySourceShouldPass(String input) { 17 | assertTrue(Strings.isBlank(input)); 18 | } 19 | 20 | @ParameterizedTest 21 | @EmptySource 22 | void emptySourceShouldFail(String input) { 23 | assertFalse(Strings.isBlank(input)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/test/java/com/example/ExampleTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | public class ExampleTest { 8 | @Test 9 | void shouldNotFail() { 10 | assertTrue(true); 11 | } 12 | 13 | @Test 14 | void shouldFail() { 15 | assertTrue(false); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/test/java/com/example/NestedTests.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | public class NestedTests { 8 | 9 | public static class SomeNestedTest { 10 | 11 | @Test 12 | void shouldSucceed() { 13 | assertEquals(2, 1 + 1); 14 | } 15 | 16 | @Test 17 | void shouldFail() { 18 | assertEquals(3, 1 + 1); 19 | } 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/test/java/com/example/ParameterizedTests.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.CsvSource; 7 | 8 | public class ParameterizedTests{ 9 | 10 | @ParameterizedTest 11 | @CsvSource({ 12 | "1, 2, 3", 13 | "2, 3, 5", 14 | "3, 4, 7" 15 | }) 16 | void shouldPass(int a, int b, int c) { 17 | assertTrue(a + b == c); 18 | } 19 | 20 | @ParameterizedTest 21 | @CsvSource({ 22 | "1, 2, 3", 23 | "2, 3, 5", 24 | "3, 4, 7" 25 | }) 26 | void shouldPass2(int a, int b, int c) { 27 | assertTrue(a + b == c); 28 | } 29 | 30 | @ParameterizedTest 31 | @CsvSource({ 32 | "1, 2, 2", 33 | "2, 3, 3", 34 | "3, 4, 4" 35 | }) 36 | void shouldFail(int a, int b, int c) { 37 | assertTrue(a + b == c); 38 | } 39 | 40 | @ParameterizedTest 41 | @CsvSource({ 42 | "1, 1, 2", 43 | "2, 3, 3", 44 | "3, 4, 4" 45 | }) 46 | void shouldFail2(int a, int b, int c) { 47 | assertTrue(a + b == c); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/test/java/com/example/PrintingTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | public class PrintingTest { 6 | 7 | @Test 8 | void justPrintingOut() { 9 | System.out.println("some literal"); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/test/java/com/example/SingleMethodFailingTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | public class SingleMethodFailingTest { 8 | @Test 9 | void shouldFail() { 10 | assertTrue(false); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-groovy-demo/src/test/java/com/example/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DemoApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | id("org.springframework.boot") version "3.1.6" 4 | id("io.spring.dependency-management") version "1.1.4" 5 | } 6 | 7 | group = "com.example" 8 | version = "0.0.1-SNAPSHOT" 9 | 10 | java { 11 | sourceCompatibility = JavaVersion.VERSION_17 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation("org.springframework.boot:spring-boot-starter") 20 | testImplementation("org.springframework.boot:spring-boot-starter-test") 21 | } 22 | 23 | tasks.withType { 24 | useJUnitPlatform() 25 | } 26 | 27 | tasks.bootBuildImage { 28 | builder.set("paketobuildpacks/builder-jammy-base:latest") 29 | } 30 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcasia/neotest-java/3cc0bceb0b0957e918939ba95434197e6b3775e7/tests/fixtures/gradle-kotlin-demo/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "demo" 2 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/fixtures/gradle-kotlin-demo/src/test/java/com/example/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DemoApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcasia/neotest-java/3cc0bceb0b0957e918939ba95434197e6b3775e7/tests/fixtures/maven-demo/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.1.3 9 | 10 | 11 | com.example 12 | demo 13 | 0.0.1-SNAPSHOT 14 | demo 15 | Demo project for Spring Boot 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-maven-plugin 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/test/java/com/example/EmptySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.EmptySource; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import org.apache.logging.log4j.util.Strings; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | 12 | public class EmptySourceTest { 13 | 14 | @ParameterizedTest 15 | @EmptySource 16 | void emptySourceShouldPass(String input) { 17 | assertTrue(Strings.isBlank(input)); 18 | } 19 | 20 | @ParameterizedTest 21 | @EmptySource 22 | void emptySourceShouldFail(String input) { 23 | assertFalse(Strings.isBlank(input)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/test/java/com/example/ErroneousTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | 9 | import com.example.demo.DemoApplication; 10 | 11 | @SpringBootTest(classes = { DemoApplication.class }) 12 | public class ErroneousTest { 13 | @Value("${foo.property}") String requiredProperty; 14 | 15 | @Test 16 | void shouldFailOnError(){ 17 | assertEquals("test", requiredProperty); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/test/java/com/example/ExampleTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | import org.junit.jupiter.api.Test; 3 | 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | public class ExampleTest { 7 | @Test 8 | void shouldNotFail() { 9 | assertTrue(true); 10 | } 11 | 12 | @Test 13 | void shouldFail() { 14 | assertTrue(false); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/test/java/com/example/NestedTests.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | public class NestedTests { 8 | 9 | public static class SomeNestedTest { 10 | 11 | @Test 12 | void shouldSucceed() { 13 | assertEquals(2, 1 + 1); 14 | } 15 | 16 | @Test 17 | void shouldFail() { 18 | assertEquals(3, 1 + 1); 19 | } 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/test/java/com/example/ParameterizedMethodTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.junit.jupiter.params.ParameterizedTest; 4 | import org.junit.jupiter.params.provider.CsvSource; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | public class ParameterizedMethodTest { 9 | 10 | @ParameterizedTest 11 | @CsvSource({ 12 | "1,1,2", 13 | "1,2,3", 14 | "2,3,5", 15 | "15,15,30" 16 | }) 17 | void parameterizedMethodShouldNotFail(Integer a, Integer b, Integer result) { 18 | assertTrue(a + b == result); 19 | } 20 | 21 | @ParameterizedTest 22 | @CsvSource({ 23 | "1,2", 24 | "3,4", 25 | "4,4" 26 | }) 27 | void parameterizedMethodShouldFail(Integer a, Integer b) { 28 | assertTrue(a == b); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/test/java/com/example/SingleMethodFailingTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | public class SingleMethodFailingTest { 8 | @Test 9 | void shouldFail() { 10 | assertTrue(false); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/test/java/com/example/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DemoApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/maven-demo/src/test/java/com/example/demo/RepositoryIT.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class RepositoryIT { 8 | 9 | @Test 10 | void shouldWorkProperly() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/init_spec.lua: -------------------------------------------------------------------------------- 1 | describe("NeotestJava plugin", function() 2 | local default_config = { 3 | default_version = "1.10.1", 4 | junit_jar = vim.fn.stdpath("data") .. "/neotest-java/junit-platform-console-standalone-1.10.1.jar", 5 | incremental_build = true, 6 | } 7 | 8 | it("should init default configuration", function() 9 | -- when 10 | require("neotest-java") 11 | 12 | -- then 13 | assert.are.same(default_config, require("neotest-java.context_holder").get_context().config) 14 | end) 15 | 16 | it("should init with empty configuration", function() 17 | -- when 18 | require("neotest-java")({}) 19 | 20 | -- then 21 | assert.are.same(default_config, require("neotest-java.context_holder").get_context().config) 22 | end) 23 | end) 24 | -------------------------------------------------------------------------------- /tests/testrc.vim: -------------------------------------------------------------------------------- 1 | set runtimepath+=. 2 | set runtimepath+=./deps/plenary.nvim 3 | set runtimepath+=./deps/neotest 4 | set runtimepath+=./deps/nvim-treesitter 5 | set runtimepath+=./deps/nvim-nio 6 | runtime! plugin/plenary.vim 7 | 8 | -------------------------------------------------------------------------------- /tests/types/project_spec.lua: -------------------------------------------------------------------------------- 1 | local async = require("nio").tests 2 | local Project = require("neotest-java.types.project") 3 | 4 | describe("project", function() 5 | local testscases = { 6 | { 7 | input = "./tests/fixtures/maven-demo", 8 | expected = { 9 | "maven-demo", 10 | }, 11 | }, 12 | { 13 | input = "./tests/fixtures/gradle-groovy-demo", 14 | expected = { 15 | "gradle-groovy-demo", 16 | }, 17 | }, 18 | } 19 | for _, testcase in ipairs(testscases) do 20 | async.it("should get modules: " .. testcase.input, function() 21 | local project = Project.from_root_dir(testcase.input) 22 | local results = {} 23 | for _, mod in ipairs(project:get_modules()) do 24 | results[#results + 1] = mod.name 25 | end 26 | assert.same(testcase.expected, results) 27 | end) 28 | end 29 | end) 30 | -------------------------------------------------------------------------------- /tests/util/compatible_path_spec.lua: -------------------------------------------------------------------------------- 1 | local compatible_path = require("neotest-java.util.compatible_path") 2 | 3 | describe("compatible_path", function() 4 | local original_vim_fn_has 5 | 6 | before_each(function() 7 | -- save original function 8 | original_vim_fn_has = vim.fn.has -- luacheck: ignore 122 Setting a read-only field of a global variable 9 | end) 10 | 11 | after_each(function() 12 | -- reset 13 | vim.fn.has = original_vim_fn_has -- luacheck: ignore 122 Setting a read-only field of a global variable 14 | end) 15 | 16 | -- should create compatible path for unix 17 | do 18 | local testcases = { 19 | { 20 | description = "basic case", 21 | input = "\\PP\\expense-app-v3\\expenses-domain\\target", 22 | expected = "/PP/expense-app-v3/expenses-domain/target", 23 | }, 24 | { 25 | description = "relative path", 26 | input = "expenses-domain\\target", 27 | expected = "expenses-domain/target", 28 | }, 29 | { 30 | description = "relative path with dot", 31 | input = ".\\expenses-domain\\target", 32 | expected = "./expenses-domain/target", 33 | }, 34 | { 35 | description = "with a dot between file separators", 36 | input = "\\PP\\expense-app-v3\\expenses-domain\\.\\target", 37 | expected = "/PP/expense-app-v3/expenses-domain/target", 38 | }, 39 | { 40 | description = "mixed file separators", 41 | input = "\\PP\\expense-app-v3/expenses-domain\\./target", 42 | expected = "/PP/expense-app-v3/expenses-domain/target", 43 | }, 44 | } 45 | 46 | for _, case in ipairs(testcases) do 47 | it(("(unix) case: %s -> %s"):format(case.description, case.input), function() 48 | assert.same(case.expected, compatible_path(case.input), case.description) 49 | end) 50 | end 51 | end 52 | 53 | -- should create compatible path for windows 54 | do 55 | local testcases = { 56 | { 57 | description = "basic case for win", 58 | input = "/PP/expense-app-v3/expenses-domain/target", 59 | expected = "\\PP\\expense-app-v3\\expenses-domain\\target", 60 | }, 61 | { 62 | description = "relative path", 63 | input = "expenses-domain/target", 64 | expected = "expenses-domain\\target", 65 | }, 66 | { 67 | description = "relative path with dot", 68 | input = "./expenses-domain/target", 69 | expected = ".\\expenses-domain\\target", 70 | }, 71 | { 72 | description = "with a dot between file separators", 73 | input = "/PP/expense-app-v3/expenses-domain/./target", 74 | expected = "\\PP\\expense-app-v3\\expenses-domain\\target", 75 | }, 76 | { 77 | description = "mixed file separators", 78 | input = "/PP/expense-app-v3\\expenses-domain\\./target", 79 | expected = "\\PP\\expense-app-v3\\expenses-domain\\target", 80 | }, 81 | } 82 | 83 | for _, case in ipairs(testcases) do 84 | -- mock vim.fn.has to simulate to be on windows 85 | vim.fn.has = function(arg) -- luacheck: ignore 122 Setting a read-only field of a global variable 86 | if arg == "win64" or arg == "win32" then 87 | return 1 88 | else 89 | return 0 90 | end 91 | end 92 | it(("(windows) case: %s -> %s "):format(case.description, case.input), function() 93 | local result = compatible_path(case.input) 94 | assert.same(case.expected, result, case.description) 95 | end) 96 | end 97 | end 98 | end) 99 | -------------------------------------------------------------------------------- /tests/util/find_module_from_filepath_spec.lua: -------------------------------------------------------------------------------- 1 | -- Test file 2 | 3 | local u = require("neotest-java.util.find_module_by_filepath") 4 | 5 | describe("find_module_by_filepath function", function() 6 | local test_cases = { 7 | { 8 | description = "Standard case with module in the path", 9 | module_dirs = { "rest-module", "grpc-module", "infrastructure-module", "domain-module" }, 10 | filepath = "./code/domain-module/src/test/org/app/Test.java", 11 | expected = "domain-module", 12 | }, 13 | { 14 | description = "Standard case with module in the path for windows", 15 | module_dirs = { "rest-module", "grpc-module", "infrastructure-module", "domain-module" }, 16 | filepath = ".\\code\\domain-module\\src\\test\\org\\app\\Test.java", 17 | expected = "domain-module", 18 | }, 19 | { 20 | description = "Module not present in the path", 21 | module_dirs = { "rest-module", "grpc-module", "infrastructure-module", "domain-module" }, 22 | filepath = "./code/unknown-module/src/test/org/app/Test.java", 23 | expected = nil, 24 | }, 25 | { 26 | description = "Filepath contains module name as substring", 27 | module_dirs = { "rest", "grpc", "infrastructure", "domain" }, 28 | filepath = "./code/restful/src/test/org/app/Test.java", 29 | expected = nil, 30 | }, 31 | { 32 | description = "Module name at the start of the path", 33 | module_dirs = { "rest", "grpc", "infrastructure", "domain" }, 34 | filepath = "rest/src/test/org/app/Test.java", 35 | expected = "rest", 36 | }, 37 | { 38 | description = "Module name at the end of the path", 39 | module_dirs = { "rest", "grpc", "infrastructure", "domain" }, 40 | filepath = "./code/src/test/org/app/domain", 41 | expected = "domain", 42 | }, 43 | { 44 | description = "Module name with special characters", 45 | module_dirs = { "rest-module", "grpc-module", "infrastructure-module", "domain-module" }, 46 | filepath = "./code/infrastructure-module/src/test/org/app/Test.java", 47 | expected = "infrastructure-module", 48 | }, 49 | { 50 | description = "Multiple modules with similar names", 51 | module_dirs = { "module", "module-test", "module-prod" }, 52 | filepath = "./code/module-test/src/test/org/app/Test.java", 53 | expected = "module-test", 54 | }, 55 | { 56 | description = "Empty module_dirs list", 57 | module_dirs = {}, 58 | filepath = "./code/domain-module/src/test/org/app/Test.java", 59 | expected = nil, 60 | }, 61 | { 62 | description = "Empty filepath", 63 | module_dirs = { "rest-module", "grpc-module", "infrastructure-module", "domain-module" }, 64 | filepath = "", 65 | expected = nil, 66 | }, 67 | { 68 | description = "Module name with regex special characters", 69 | module_dirs = { "rest+module", "grpc-module", "infrastructure-module", "domain-module" }, 70 | filepath = "./code/rest+module/src/test/org/app/Test.java", 71 | expected = "rest+module", 72 | }, 73 | { 74 | description = "Real example that was failing", 75 | module_dirs = { 76 | "./neovim-java", 77 | "./neovim-java/api-explorer", 78 | "./neovim-java/core-rpc", 79 | "./neovim-java/handler-annotations", 80 | "./neovim-java/neovim-api", 81 | "./neovim-java/neovim-notifications", 82 | "./neovim-java/neovim-rx-api", 83 | "./neovim-java/plugin-host", 84 | "./neovim-java/plugins-common-host", 85 | "./neovim-java/reactive-core-rpc", 86 | "./neovim-java/rplugin-example", 87 | "./neovim-java/rplugin-example", 88 | "./neovim-java/rplugin-hosted-example", 89 | "./neovim-java/testing-helpers", 90 | "./neovim-java/unix-socket-connection", 91 | }, 92 | filepath = "./neovim-java/core-rpc/src/test/java/com/ensarsarajcic/neovim/java/corerpc/client/AsyncRpcSenderTest.java", 93 | expected = "./neovim-java/core-rpc", 94 | }, 95 | } 96 | 97 | for _, case in ipairs(test_cases) do 98 | it(case.description, function() 99 | assert.same(case.expected, u(case.module_dirs, case.filepath)) 100 | end) 101 | end 102 | end) 103 | -------------------------------------------------------------------------------- /tests/util/just_take_the_dependency_spec.lua: -------------------------------------------------------------------------------- 1 | local take_just_the_dependency = require("neotest-java.util.just_take_the_dependency") 2 | 3 | describe("take_just_the_dependency", function() 4 | local test_cases = { 5 | { 6 | input = "------javax.servlet:javax.servlet-api:jar:3.1.0:provided------", 7 | expected = "javax.servlet:javax.servlet-api:3.1.0", 8 | }, 9 | { 10 | input = "[INFO]-----com.google.errorprone:error_prone_annotations:jar:2.28.0:compile", 11 | expected = "com.google.errorprone:error_prone_annotations:2.28.0", 12 | }, 13 | { 14 | input = "[INFO]-org.hamcrest:hamcrest:jar:2.2:test", 15 | expected = "org.hamcrest:hamcrest:2.2", 16 | }, 17 | { 18 | input = "org.hamcrest:hamcrest:jar:dto:2.2:test", 19 | expected = "org.hamcrest:hamcrest:2.2", 20 | }, 21 | { 22 | input = "[INFO] +- org.hamcrest:hamcrest:jar:2.2:test", 23 | expected = "org.hamcrest:hamcrest:2.2", 24 | }, 25 | { 26 | input = "2021-07-28T12:34:56Z", 27 | expected = nil, 28 | }, 29 | { 30 | input = "GMT+1", 31 | expected = nil, 32 | }, 33 | { 34 | input = "UTC-5:00", 35 | expected = nil, 36 | }, 37 | { 38 | input = "[INFO] | +- junit:junit:jar:4.12:compile", 39 | expected = "junit:junit:4.12", 40 | }, 41 | { 42 | input = " junit:junit:4.12 (*)", 43 | expected = "junit:junit:4.12", 44 | }, 45 | { 46 | input = "| +--- org.springframework.boot:spring-boot-starter:3.1.0", 47 | expected = "org.springframework.boot:spring-boot-starter:3.1.0", 48 | }, 49 | { 50 | input = "+--- org.junit.platform:junit-platform-launcher:1.9.2 -> 1.9.3", 51 | expected = "org.junit.platform:junit-platform-launcher:1.9.3", 52 | }, 53 | { 54 | input = " | \\--- org.junit.platform:junit-platform-launcher:1.9.2.RELEASE (*)", 55 | expected = "org.junit.platform:junit-platform-launcher:1.9.2.RELEASE", 56 | }, 57 | } 58 | 59 | for _, case in ipairs(test_cases) do 60 | it(case.input, function() 61 | local result = take_just_the_dependency(case.input) 62 | assert.are.same(case.expected, result) 63 | end) 64 | end 65 | end) 66 | -------------------------------------------------------------------------------- /tests/util/resolve_qualified_name_spec.lua: -------------------------------------------------------------------------------- 1 | local _ = require("vim.treesitter") -- NOTE: needed for loading treesitter upfront for the tests 2 | local a = require("nio").tests 3 | local resolve_qualified_name = require("neotest-java.util.resolve_qualified_name") 4 | 5 | describe("resolve_qualified_name", function() 6 | local tmp_files 7 | 8 | before_each(function() 9 | tmp_files = {} 10 | end) 11 | 12 | after_each(function() 13 | -- clear temporary files 14 | for _, file in ipairs(tmp_files) do 15 | os.remove(file) 16 | end 17 | end) 18 | 19 | ---@param content string 20 | ---@return string filename 21 | local function create_tmp_file(content) 22 | local tmp_file = os.tmpname() 23 | table.insert(tmp_files, tmp_file) 24 | local file = assert(io.open(tmp_file, "w")) 25 | file:write(content) 26 | file:close() 27 | return tmp_file 28 | end 29 | 30 | local testcases = { 31 | { 32 | input = [[ 33 | package com.example; 34 | 35 | class ExampleTest {} 36 | 37 | ]], 38 | expected = "com.example.ExampleTest", 39 | }, 40 | { 41 | input = [[ 42 | package com.example; 43 | 44 | class SomeConfig {} 45 | class ExampleTest {} 46 | 47 | ]], 48 | expected = "com.example.ExampleTest", 49 | }, 50 | { 51 | input = [[ 52 | class ExampleTest {} 53 | ]], 54 | expected = "ExampleTest", 55 | }, 56 | } 57 | 58 | for _, case in ipairs(testcases) do 59 | a.it("should resolve the qualified name of a file", function() 60 | local result = resolve_qualified_name(create_tmp_file(case.input)) 61 | 62 | assert.are.same(case.expected, result) 63 | end) 64 | end 65 | 66 | a.it("should error when file does not exist", function() 67 | local bad_example_filepath = "some-fake-filename" 68 | assert.has_error(function() 69 | resolve_qualified_name(bad_example_filepath) 70 | end, string.format("file does not exist: %s", bad_example_filepath)) 71 | end) 72 | 73 | a.it("should error when class name is not found", function() 74 | local bad_example_filepath = create_tmp_file([[ 75 | package com.example; 76 | 77 | class Configuration { } 78 | 79 | ]]) 80 | assert.has_error(function() 81 | resolve_qualified_name(bad_example_filepath) 82 | end, "test class name not found") 83 | end) 84 | end) 85 | --------------------------------------------------------------------------------