mirror of
https://github.com/pnx/neotest-phpunit
synced 2026-06-16 03:54:55 +02:00
Merge pull request #2 from V13Axel/Dynamic_Configuration
Dynamic configuration
This commit is contained in:
commit
7e20c2ba56
6 changed files with 284 additions and 86 deletions
93
README.md
93
README.md
|
|
@ -3,45 +3,91 @@
|
|||
This plugin provides a [Pest](https://pestphp.com) adapter for the [Neotest](https://github.com/nvim-neotest/neotest) framework.
|
||||
|
||||
This is a fork of `neotest-pest` originally by [@theutz](https://github.com/theutz/neotest-pest), with some fixes and updates:
|
||||
|
||||
- Updated to work with [Pest](https://pestphp.com) 2.0
|
||||
- Support for (and automatic detection of) Laravel Sail
|
||||
- Note: This also moves junit output files into `storage/app/`
|
||||
- Parallel testing support
|
||||
|
||||
:warning: _Ive only focused on making this work for me. Please test against your Pest tests_ :warning:
|
||||
|
||||
## :package: Installation
|
||||
|
||||
Install the plugin using packer:
|
||||
Install the plugin using your favorite package manager.
|
||||
|
||||
Here's an example using lazy.nvim:
|
||||
|
||||
```lua
|
||||
use({
|
||||
'nvim-neotest/neotest',
|
||||
requires = {
|
||||
...,
|
||||
'V13Axel/neotest-pest',
|
||||
},
|
||||
config = function()
|
||||
require('neotest').setup({
|
||||
...,
|
||||
adapters = {
|
||||
require('neotest-pest'),
|
||||
}
|
||||
})
|
||||
end
|
||||
})
|
||||
{
|
||||
'nvim-neotest/neotest',
|
||||
dependencies = {
|
||||
...,
|
||||
'V13Axel/neotest-pest',
|
||||
},
|
||||
config = function()
|
||||
require('neotest').setup({
|
||||
...,
|
||||
adapters = {
|
||||
require('neotest-pest'),
|
||||
}
|
||||
})
|
||||
end
|
||||
}
|
||||
```
|
||||
|
||||
## :wrench: Configuration
|
||||
|
||||
The plugin may be configured as below:
|
||||
> [!TIP]
|
||||
> Any of these options can be set to a lua function that returns the desired result. For example, wanna run tests in parallel, one for each CPU core?
|
||||
> `parallel = function() return #vim.loop.cpu_info() end,`
|
||||
|
||||
```lua
|
||||
adapters = {
|
||||
require('neotest-pest')({
|
||||
pest_cmd = function()
|
||||
return "vendor/bin/pest"
|
||||
end
|
||||
}),
|
||||
require('neotest-pest')({
|
||||
-- Ignore these directories when looking for tests
|
||||
-- -- Default: { "vendor", "node_modules" }
|
||||
ignore_dirs = { "vendor", "node_modules" }
|
||||
|
||||
-- Ignore any projects containing "phpunit-only.tests"
|
||||
-- -- Default: {}
|
||||
root_ignore_files = { "phpunit-only.tests" },
|
||||
|
||||
-- Specify suffixes for files that should be considered tests
|
||||
-- -- Default: { "Test.php" }
|
||||
test_file_suffixes = { "Test.php", "_test.php", "PestTest.php" },
|
||||
|
||||
-- Sail not properly detected? Explicitly enable it.
|
||||
-- -- Default: function() that checks for sail presence
|
||||
sail_enabled = false,
|
||||
|
||||
-- Custom sail executable. Not running in Sail, but running bare Docker?
|
||||
-- Set `sail_enabled` = true and `sail_executable` to { "docker", "exec", "[somecontainer]" }
|
||||
-- -- Default: "vendor/bin/sail"
|
||||
sail_executable = "vendor/bin/sail",
|
||||
|
||||
-- Custom pest binary.
|
||||
-- -- Default: function that checks for sail presence
|
||||
pest_cmd = "vendor/bin/pest",
|
||||
|
||||
-- Run N tests in parallel, <=1 doesn't pass --parallel to pest at all
|
||||
-- -- Default: 0
|
||||
parallel = 16
|
||||
|
||||
-- Enable ["compact" output printer](https://pestphp.com/docs/optimizing-tests#content-compact-printer)
|
||||
-- -- Default: false
|
||||
compact = false,
|
||||
|
||||
-- Set a custom path for the results XML file, parsed by this adapter
|
||||
--
|
||||
------------------------------------------------------------------------------------
|
||||
-- NOTE: This must be a path accessible by both your test runner AND your editor! --
|
||||
------------------------------------------------------------------------------------
|
||||
--
|
||||
-- -- Default: function that checks for sail presence.
|
||||
-- -- - If no sail: Numbered file in randomized /tmp/ directory (using async.fn.tempname())
|
||||
-- -- - If sail: "storage/app/" .. os.date("junit-%Y%m%d-%H%M%S")
|
||||
results_path = function() "/some/accessible/path" end,
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -62,6 +108,7 @@ vim.keymap.set('n', '<leader>tn', function() require('neotest').run.run() end)
|
|||
To test a file run `lua require('neotest').run.run(vim.fn.expand('%'))`
|
||||
|
||||
Example - <leader>t(est)f(ile):
|
||||
|
||||
```lua
|
||||
vim.keymap.set('n', '<leader>tf', function() require('neotest').run.run(vim.fn.expand('%')) end)
|
||||
```
|
||||
|
|
@ -76,7 +123,7 @@ To test the full test suite run `lua require('neotest').run.run({ suite = true }
|
|||
|
||||
## :gift: Contributing
|
||||
|
||||
This fork is maintained by one guy, in my spare time and when I can get to it. Please raise a PR if you are interested in adding new functionality or fixing any bugs. When submitting a bug, please include an example test that I can test against.
|
||||
I'm just one guy, maintaining this in my spare time and when I can get to it. Please raise a PR if you are interested in adding new functionality or fixing any bugs. When submitting a bug, please include an example test that I can test against.
|
||||
|
||||
To trigger the tests for the adapter, run:
|
||||
|
||||
|
|
|
|||
87
lua/neotest-pest/config.lua
Normal file
87
lua/neotest-pest/config.lua
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
local logger = require('neotest.logging')
|
||||
local ok, async = pcall(require, "nio")
|
||||
if not ok then
|
||||
async = require("neotest.async")
|
||||
end
|
||||
|
||||
local is_callable = function(obj)
|
||||
return type(obj) == "function" or (type(obj) == "table" and obj.__call)
|
||||
end
|
||||
|
||||
local M = {
|
||||
env = {
|
||||
root_ignore_files = {},
|
||||
root_files = { "tests/Pest.php" },
|
||||
ignore_dirs = { "vendor", "node_modules" },
|
||||
test_file_suffixes = { "Test.php" },
|
||||
sail_executable = "vendor/bin/sail",
|
||||
parallel = 0,
|
||||
compact = false,
|
||||
},
|
||||
|
||||
_sail_error = false,
|
||||
_sail_enabled = false,
|
||||
}
|
||||
|
||||
function M.env.sail_enabled()
|
||||
-- Cache and short-circuit so we don't have to check the disk for
|
||||
-- vnedor/bin/sail every time. If the user removes the sail executable
|
||||
-- out from under us, that's their problem.
|
||||
if M._sail_enabled then
|
||||
return true
|
||||
end
|
||||
|
||||
return M.sail_available()
|
||||
end
|
||||
|
||||
function M.env.is_parallel()
|
||||
return M('parallel') > 0
|
||||
end
|
||||
|
||||
function M.env.pest_cmd()
|
||||
if M('sail_enabled') then
|
||||
return { "vendor/bin/sail", "bin", "pest" }
|
||||
end
|
||||
|
||||
return { "vendor/bin/pest" }
|
||||
end
|
||||
|
||||
function M.env.results_path()
|
||||
if M('sail_enabled') then
|
||||
return "storage/app/" .. os.date("junit-%Y%m%d-%H%M%S")
|
||||
end
|
||||
|
||||
return async.fn.tempname()
|
||||
end
|
||||
|
||||
function M.sail_error()
|
||||
return M._sail_error
|
||||
end
|
||||
|
||||
function M.sail_available()
|
||||
if vim.fn.filereadable(M('sail_executable')) == 1 then
|
||||
M._sail_enabled = true
|
||||
return true
|
||||
end
|
||||
|
||||
M._sail_error = true
|
||||
logger.debug("Sail executable not found")
|
||||
end
|
||||
|
||||
function M.merge(env)
|
||||
for key, value in pairs(env) do
|
||||
M.env[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(M, {
|
||||
__call = function(_, key)
|
||||
if is_callable(M.env[key] or nil) then
|
||||
return M.env[key]()
|
||||
end
|
||||
|
||||
return M.env[key] or {}
|
||||
end
|
||||
})
|
||||
|
||||
return M
|
||||
|
|
@ -1,7 +1,22 @@
|
|||
local lib = require('neotest.lib')
|
||||
local async = require('neotest.async')
|
||||
local logger = require('neotest.logging')
|
||||
local utils = require('neotest-pest.utils')
|
||||
local config = require('neotest-pest.config')
|
||||
local debug = logger.debug
|
||||
|
||||
local notify = function(msg, level)
|
||||
vim.notify(msg, level, {
|
||||
title = "neotest-pest",
|
||||
})
|
||||
end
|
||||
|
||||
local error = function(msg)
|
||||
notify(msg, "error")
|
||||
end
|
||||
|
||||
local info = function(msg)
|
||||
notify(msg, "info")
|
||||
end
|
||||
|
||||
---@class neotest.Adapter
|
||||
---@field name string
|
||||
|
|
@ -13,7 +28,37 @@ local NeotestAdapter = { name = "neotest-pest" }
|
|||
---@async
|
||||
---@param dir string @Directory to treat as cwd
|
||||
---@return string | nil @Absolute root dir of test suite
|
||||
NeotestAdapter.root = lib.files.match_root_pattern("tests/Pest.php")
|
||||
function NeotestAdapter.root(dir)
|
||||
local result = nil
|
||||
|
||||
debug("Finding root...")
|
||||
|
||||
for _, root_ignore_file in ipairs(config("root_ignore_files")) do
|
||||
debug("Checking root ignore file", root_ignore_file)
|
||||
|
||||
result = lib.files.match_root_pattern(root_ignore_file)(dir)
|
||||
|
||||
if result then
|
||||
debug("Ignoring root because file", root_ignore_file)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
for _, root_file in ipairs(config("root_files")) do
|
||||
debug("Checking root file", root_file)
|
||||
|
||||
result = lib.files.match_root_pattern(root_file)(dir)
|
||||
|
||||
if result then
|
||||
debug("Found root", result)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
debug("Root not found")
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---Filter directories when searching for test files
|
||||
---@async
|
||||
|
|
@ -22,14 +67,22 @@ NeotestAdapter.root = lib.files.match_root_pattern("tests/Pest.php")
|
|||
---@param root string Root directory of project
|
||||
---@return boolean True when matching
|
||||
function NeotestAdapter.filter_dir(name, rel_path, root)
|
||||
return vim.startswith(rel_path, "tests")
|
||||
for _, filter_dir in ipairs(config("ignore_dirs")) do
|
||||
if name == filter_dir then return false end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param file_path string
|
||||
---@return boolean
|
||||
function NeotestAdapter.is_test_file(file_path)
|
||||
return vim.endswith(file_path, "Test.php")
|
||||
for _, suffix in ipairs(config("test_file_suffixes")) do
|
||||
if vim.endswith(file_path, suffix) then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function NeotestAdapter.discover_positions(path)
|
||||
|
|
@ -52,57 +105,56 @@ function NeotestAdapter.discover_positions(path)
|
|||
})
|
||||
end
|
||||
|
||||
local function get_pest_cmd()
|
||||
local binary = "pest"
|
||||
|
||||
if vim.fn.filereadable("vendor/bin/pest") == 1 then
|
||||
binary = "vendor/bin/pest"
|
||||
end
|
||||
|
||||
return binary
|
||||
end
|
||||
|
||||
local is_callable = function(obj)
|
||||
return type(obj) == "function" or (type(obj) == "table" and obj.__call)
|
||||
end
|
||||
|
||||
---@param args neotest.RunArgs
|
||||
---@return neotest.RunSpec | nil
|
||||
function NeotestAdapter.build_spec(args)
|
||||
local position = args.tree:data()
|
||||
local results_path = "storage/app/" .. os.date("junit-%Y%m%d-%H%M%S")
|
||||
local results_path = config('results_path')
|
||||
|
||||
local binary = get_pest_cmd()
|
||||
debug("Building spec for:", position)
|
||||
debug("Results path:", results_path)
|
||||
|
||||
local command = {}
|
||||
local path = position.path;
|
||||
|
||||
if vim.fn.filereadable("vendor/bin/sail") == 1 then
|
||||
command = vim.tbl_flatten({
|
||||
"vendor/bin/sail", "bin", "pest",
|
||||
position.name ~= "tests" and ("/var/www/html" .. string.sub(position.path, string.len(vim.loop.cwd()) + 1)),
|
||||
"--log-junit=" .. results_path,
|
||||
})
|
||||
else
|
||||
command = vim.tbl_flatten({
|
||||
binary,
|
||||
position.name ~= "tests" and position.path,
|
||||
"--log-junit=" .. results_path,
|
||||
})
|
||||
if config('sail_enabled') then
|
||||
debug("Sail enabled, adjusting path")
|
||||
path = "/var/www/html" .. string.sub(position.path, string.len(vim.loop.cwd() or "") + 1)
|
||||
end
|
||||
|
||||
local command = vim.tbl_flatten({
|
||||
config('pest_cmd'),
|
||||
path,
|
||||
"--log-junit=" .. results_path,
|
||||
})
|
||||
|
||||
if position.type == "test" then
|
||||
local script_args = vim.tbl_flatten({
|
||||
command = vim.tbl_flatten({
|
||||
command,
|
||||
"--filter",
|
||||
position.name,
|
||||
})
|
||||
else
|
||||
debug("Position type:", position.type)
|
||||
end
|
||||
|
||||
if config('is_parallel') then
|
||||
command = vim.tbl_flatten({
|
||||
command,
|
||||
script_args,
|
||||
"--parallel",
|
||||
"--processes=" .. config('parallel'),
|
||||
})
|
||||
end
|
||||
|
||||
if config('compact') == true then
|
||||
info("Using compact output")
|
||||
command = vim.tbl_flatten({
|
||||
command,
|
||||
"--compact",
|
||||
})
|
||||
end
|
||||
|
||||
debug("Command:", command)
|
||||
|
||||
return {
|
||||
command = command,
|
||||
context = {
|
||||
|
|
@ -121,18 +173,21 @@ function NeotestAdapter.results(test, result, tree)
|
|||
|
||||
local ok, data = pcall(lib.files.read, output_file)
|
||||
if not ok then
|
||||
error("No test output file found! Should have been at: " .. output_file)
|
||||
logger.error("No test output file found:", output_file)
|
||||
return {}
|
||||
end
|
||||
|
||||
local ok, parsed_data = pcall(lib.xml.parse, data)
|
||||
if not ok then
|
||||
error("Failed to parse test output!")
|
||||
logger.error("Failed to parse test output:", output_file)
|
||||
return {}
|
||||
end
|
||||
|
||||
local ok, results = pcall(utils.get_test_results, parsed_data, output_file)
|
||||
if not ok then
|
||||
error("Could not get test results!")
|
||||
logger.error("Could not get test results", output_file)
|
||||
return {}
|
||||
end
|
||||
|
|
@ -142,13 +197,8 @@ end
|
|||
|
||||
setmetatable(NeotestAdapter, {
|
||||
__call = function(_, opts)
|
||||
if is_callable(opts.pest_cmd) then
|
||||
get_pest_cmd = opts.pest_cmd
|
||||
elseif opts.pest_cmd then
|
||||
get_pest_cmd = function()
|
||||
return opts.pest_cmd
|
||||
end
|
||||
end
|
||||
config.merge(opts or {})
|
||||
|
||||
return NeotestAdapter
|
||||
end,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ M.make_test_id = function(position)
|
|||
local path = string.sub(position.path, string.len(vim.loop.cwd()) + 2)
|
||||
|
||||
local id = path .. separator .. position.name
|
||||
logger.info("Path to test file:", { position.path })
|
||||
logger.info("Treesitter id:", { id })
|
||||
logger.debug("Path to test file:", { position.path })
|
||||
logger.debug("Treesitter id:", { id })
|
||||
|
||||
return id
|
||||
end
|
||||
|
|
@ -65,14 +65,14 @@ end
|
|||
---@param output_file string
|
||||
---@return table
|
||||
local function make_outputs(test, output_file)
|
||||
logger.info("Pre-output test:", test)
|
||||
logger.debug("Pre-output test:", test)
|
||||
local test_attr = test["_attr"] or test[1]["_attr"]
|
||||
local name = string.gsub(test_attr.name, "it (.*)", "%1")
|
||||
|
||||
-- Difference to neotest-phpunit as of PHPUnit 10:
|
||||
-- Pest's test IDs are in the format "path/to/test/file::test name"
|
||||
local test_id = string.gsub(test_attr.file, "(.*)::(.*)", "%1") .. separator .. name
|
||||
logger.info("Pest id:", { test_id })
|
||||
logger.debug("Pest id:", { test_id })
|
||||
|
||||
local test_output = {
|
||||
status = "passed",
|
||||
|
|
@ -83,7 +83,7 @@ local function make_outputs(test, output_file)
|
|||
local test_failed, errors, fails = errors_or_fails(test)
|
||||
|
||||
if test_failed then
|
||||
logger.info("test_failed:", { test_failed, errors, fails })
|
||||
logger.debug("test_failed:", { test_failed, errors, fails })
|
||||
test_output.status = "failed"
|
||||
|
||||
if #errors > 0 then
|
||||
|
|
|
|||
28
phpunit.xml
28
phpunit.xml
|
|
@ -1,18 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="src/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Test Suite">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">./app</directory>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" bootstrap="src/autoload.php" colors="true" cacheDirectory=".phpunit.cache">
|
||||
<testsuites>
|
||||
<testsuite name="Test Suite">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">./app</directory>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
|
|
|||
18
phpunit.xml.bak
Normal file
18
phpunit.xml.bak
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="src/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Test Suite">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">./app</directory>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
</phpunit>
|
||||
Loading…
Add table
Add a link
Reference in a new issue