From 3a4771516bd3416663b371bee2e0d516108aaa50 Mon Sep 17 00:00:00 2001 From: Ruben Garcia Date: Fri, 6 Mar 2026 08:04:54 +0100 Subject: [PATCH] refactor: remove pytest parametrize support, keep fixture features only Remove all modules related to pytest.mark.parametrize and pytest cache: - Delete call_spec.lua, nodeids.lua, marks.lua, strings.lua, tables.lua - Delete jump/param.lua and its tests - Remove Pytrize, PytrizeClear, PytrizeJump commands - Remove .pytest_cache and pytest.ini from root markers - Remove get_nodeids_path from paths.lua - Update rename test to use .git instead of .pytest_cache as root marker - Rewrite README to focus on fixture features only --- README.md | 94 ++----------- lua/pytrize/api.lua | 63 +-------- lua/pytrize/call_spec.lua | 225 ------------------------------- lua/pytrize/init.lua | 37 ++--- lua/pytrize/jump/init.lua | 2 - lua/pytrize/jump/param.lua | 128 ------------------ lua/pytrize/marks.lua | 28 ---- lua/pytrize/nodeids.lua | 80 ----------- lua/pytrize/paths.lua | 8 +- lua/pytrize/strings.lua | 20 --- lua/pytrize/tables.lua | 19 --- tests/pytrize/call_spec_spec.lua | 125 ----------------- tests/pytrize/nodeids_spec.lua | 32 ----- tests/pytrize/rename_spec.lua | 2 +- tests/pytrize/strings_spec.lua | 33 ----- tests/pytrize/tables_spec.lua | 31 ----- 16 files changed, 37 insertions(+), 890 deletions(-) delete mode 100644 lua/pytrize/call_spec.lua delete mode 100644 lua/pytrize/jump/param.lua delete mode 100644 lua/pytrize/marks.lua delete mode 100644 lua/pytrize/nodeids.lua delete mode 100644 lua/pytrize/strings.lua delete mode 100644 lua/pytrize/tables.lua delete mode 100644 tests/pytrize/call_spec_spec.lua delete mode 100644 tests/pytrize/nodeids_spec.lua delete mode 100644 tests/pytrize/strings_spec.lua delete mode 100644 tests/pytrize/tables_spec.lua diff --git a/README.md b/README.md index eff600b..b80db51 100644 --- a/README.md +++ b/README.md @@ -2,89 +2,41 @@ ## Short summary -Helps navigating `pytest.mark.parametrize` entries and fixtures by virtual text and jump to declaration commands, using `pytest`s cache and `treesitter`. - -![pytrize](https://user-images.githubusercontent.com/23341710/143510539-c025925c-0e4c-4990-83ab-1c0da076c0f8.gif) - -## What problems does this plugin solve? - -### Parametrize - -`pytest` is amazing! The only thing that bothers me from time to time is if there are many entries in the parametrization of the test. -If a test fails you might see for example: - -``` -test.py::test[None2-a1-b-c1-8] -``` - -Now you want to see what test case this actually corresponds to. -What I sometimes do is to go to the entries in `pytest.mark.parametrize` and count the entries until I'm at the right one. -But this is really not nice and easy to make a mistake, we should let the computer do this for us. - -Enter `pytrize`. - -### Fixture - -Another issue is knowing where a certain fixture is defined and what it does. +Helps navigating pytest fixtures by providing jump to declaration, rename, and find usages commands, using `treesitter`. ## What does the plugin do? -Several things: - -- Populates virtual text at the entries of `pytest.mark.parametrize` (see gif above), such that you can easily see which one is which. - Done by calling `Pytrize` (and `PytrizeClear` to clear them). - Alternatively `lua require('pytrize.api').set()` (and `lua require('pytrize.api').clear()`). -- Provides a command to jump to the corresponding entry in `pytest.mark.parametrize` based on the test-case id under the cursor (see gif above). - Done by calling `PytrizeJump`. - Alternatively `lua require('pytrize.api').jump()`. - See the [Input](#input)-section below for cases where the file-path is not available. -- Provides a command to jump to the declaration of the fixture under the cursor (by name), see [fixture](#jump-to-fixture) below. +- **Jump to fixture definition** — jump to the declaration of the fixture under the cursor. Done by calling `PytrizeJumpFixture`. Alternatively `lua require('pytrize.api').jump_fixture()`. -- Provides a command to rename the fixture under the cursor (by name), see [fixture](#rename-fixture) below. + See [Jump to fixture](#jump-to-fixture) below. +- **Rename fixture** — rename the fixture under the cursor across the project. Done by calling `PytrizeRenameFixture`. Alternatively `lua require('pytrize.api').rename_fixture()`. -- Provides a command to find all usages of the fixture under the cursor across the project, see [fixture usages](#fixture-usages) below. + See [Rename fixture](#rename-fixture) below. +- **Find fixture usages** — find all usages of the fixture under the cursor across the project. Done by calling `PytrizeFixtureUsages`. Alternatively `lua require('pytrize.api').fixture_usages()`. + See [Fixture usages](#fixture-usages) below. ## Installation -For example using [`packer`](https://github.com/wbthomason/packer.nvim): - -```lua -use { -- pytrize {{{ - 'sigfriedcub1990/nvim-pytrize.lua', - -- uncomment if you want to lazy load - -- cmd = {'Pytrize', 'PytrizeClear', 'PytrizeJump'}, - -- uncomment if you want to lazy load but not use the commands - -- module = 'pytrize', - config = function() - require("pytrize").setup({ - -- metrics = true, -- uncomment to log timing info for jump and rename - }) - end, -} -- }}} -``` - For example using [`lazy.nvim`](https://github.com/folke/lazy.nvim): ```lua { 'sigfriedcub1990/nvim-pytrize.lua', version = '*', - dependencies = { 'nvim-lua/plenary' }, ft = 'python', -- Load only for python files opts = { -- metrics = true, -- uncomment to log timing info for jump and rename + -- preferred_input = 'fzf-lua', -- uncomment to use fzf-lua for fixture usages }, -- uncomment if you want to lazy load - -- cmd = {'Pytrize', 'PytrizeClear', 'PytrizeJump'}, -} -- }}} + -- cmd = {'PytrizeJumpFixture', 'PytrizeRenameFixture', 'PytrizeFixtureUsages'}, +} ``` -Requires [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim). - ## Configuration `require("pytrize").setup` takes an optional table of settings which currently have the default values: @@ -94,23 +46,17 @@ Requires [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim). no_commands = false, highlight = 'LineNr', metrics = false, - preferred_input = 'fzf-lua', + preferred_input = nil, } ``` where: -- `no_commands` can be set to `true` and the commands `Pytrize` etc won't be declared. -- `highlight` defines the highlighting used for the virtual text. +- `no_commands` can be set to `true` and the user commands won't be declared. +- `highlight` defines the highlighting used for virtual text. - `metrics` when set to `true`, logs timing information via `vim.notify` after each jump-to-fixture and rename operation. Useful for understanding performance in large projects. The jump reports total time and index-build time; the rename reports total, grep, scoping (fixture resolution), and apply time. - `preferred_input` which method to use for displaying results (if installed). Currently `'fzf-lua'` is supported — when set, fixture usages are displayed in an [`fzf-lua`](https://github.com/ibhagwan/fzf-lua) picker instead of the quickfix list. When `nil` (the default), results go to the quickfix list. -## Details - -- `pytest`s cache is used to find the test-case ids (eg `test.py::test[None2-a1-b-c1-8]`) which means that the tests have to be run at least once. - Also old ids might confuse `pytrize`, but you can clear the cache with `pytest --cache-clear`. -- `treesitter` is used to find the correct entry in `pytest.mark.parametrize`. - ## Jump to fixture To jump to the declaration of a fixture under the cursor, do `PytrizeJumpFixture`: @@ -118,7 +64,7 @@ To jump to the declaration of a fixture under the cursor, do `PytrizeJumpFixture ## Rename fixture -To rename the fixture under the cursor, do `PytrizeRenameFixture`: +To rename the fixture under the cursor, do `PytrizeRenameFixture`. ## Fixture usages @@ -127,18 +73,6 @@ To find all usages of the fixture under the cursor, do `PytrizeFixtureUsages`. Results are loaded into Neovim's quickfix list and the quickfix window is opened automatically. Each entry shows the file, line, and the line content where the fixture is used — as a parameter, a body reference, or inside `@pytest.mark.usefixtures(...)`. The fixture definition itself is excluded from the results. -## Input - -In some cases the file-path is not printed by pytest, for example when a test fails when it might look something like: - -``` - -_________________________________ test[None2-a1-b-c1-9] _________________________________ -``` - -or similar. -If you trigger to jump to the declaration of the parameters in this case `pytrize` will find all files in the cache that matches this test-case id and if there is more than one ask you which one to jump to (via `vim.ui.select`). - ### fzf-lua When `preferred_input = 'fzf-lua'` is set and [`fzf-lua`](https://github.com/ibhagwan/fzf-lua) is installed, fixture usages (`PytrizeFixtureUsages`) are displayed in an fzf picker with a built-in previewer. Supported actions: diff --git a/lua/pytrize/api.lua b/lua/pytrize/api.lua index 0a5da72..819d71d 100644 --- a/lua/pytrize/api.lua +++ b/lua/pytrize/api.lua @@ -1,73 +1,24 @@ local M = {} ---- Clear pytrize virtual text from buffer. ----@param bufnr? integer Buffer number (0 or nil for current buffer) -M.clear = function(bufnr) - local marks = require("pytrize.marks") - marks.clear(bufnr or 0) -end - ---- Set pytrize virtual text for parametrize entries in buffer. ----@param bufnr? integer Buffer number (0 or nil for current buffer) -M.set = function(bufnr) - local cs = require("pytrize.call_spec") - local marks = require("pytrize.marks") - bufnr = bufnr or 0 - marks.clear(bufnr) - local call_specs_per_func = cs.get_calls(bufnr) - if call_specs_per_func == nil then - return - end - for _, call_specs in pairs(call_specs_per_func) do - for _, call_spec in ipairs(call_specs) do - for _, entry_spec in ipairs(call_spec.entries) do - local entry_row = entry_spec.node:start() - marks.set({ - bufnr = bufnr, - text = entry_spec.id, - row = entry_row, - }) - for _, item_spec in ipairs(entry_spec.items) do - local item_row = item_spec.node:start() - if item_row ~= entry_row then - marks.set({ - bufnr = bufnr, - text = item_spec.id, - row = item_spec.node:start(), - }) - end - end - end - end - end -end - ---- Jump to the parametrize entry declaration under cursor. -M.jump = function() - local jump = require("pytrize.jump") - - jump.to_param_declaration() -end - --- Jump to fixture definition under cursor. M.jump_fixture = function() - local jump = require("pytrize.jump") + local jump = require("pytrize.jump") - jump.to_fixture_declaration() + jump.to_fixture_declaration() end --- Rename fixture under cursor across the project. M.rename_fixture = function() - local rename = require("pytrize.rename") + local rename = require("pytrize.rename") - rename.rename_fixture() + rename.rename_fixture() end ---- Show all usages of fixture under cursor in quickfix list. +--- Show all usages of fixture under cursor. M.fixture_usages = function() - local usages = require("pytrize.usages") + local usages = require("pytrize.usages") - usages.show_usages() + usages.show_usages() end return M diff --git a/lua/pytrize/call_spec.lua b/lua/pytrize/call_spec.lua deleted file mode 100644 index fab2cf5..0000000 --- a/lua/pytrize/call_spec.lua +++ /dev/null @@ -1,225 +0,0 @@ -local M = {} - -local ts = vim.treesitter -local parse_query = vim.treesitter.query.parse - -local warn = require("pytrize.warn").warn -local tbls = require("pytrize.tables") - -local get_root = function(bufnr) - local ok, parser = pcall(ts.get_parser, bufnr, "python") - if not ok then - warn("Python treesitter parser not available") - return nil - end - local trees = parser:parse() - if not trees or #trees == 0 then - warn("Failed to parse buffer") - return nil - end - return trees[1]:root() -end - -local PARAM_QUERY = parse_query( - "python", - [[ - (decorated_definition ( - decorator ( - call - function: ((attribute) @param) - ) - )) - ]] -) - -local get_param_call_nodes = function(bufnr) - local tsroot = get_root(bufnr) - if tsroot == nil then - return {} - end - local nodes = {} - for _, node, _ in PARAM_QUERY:iter_captures(tsroot, bufnr) do - if ts.get_node_text(node, bufnr) == "pytest.mark.parametrize" then - table.insert(nodes, node:parent()) - end - end - return nodes -end - -local get_second_arg_node = function(call_node) - local arguments = call_node:field("arguments")[1] - if not arguments then - return nil - end - -- Use named children to avoid depending on raw child indices - local named = {} - for child in arguments:iter_children() do - if child:named() then - table.insert(named, child) - end - end - return named[2] -end - -local get_named_children = function(node) - local children = {} - for child in node:iter_children() do - if child:named() and child:type() ~= "comment" then - table.insert(children, child) - end - end - return children -end - -local list_entries = function(call_node) - local list = get_second_arg_node(call_node) - if not list or list:type() ~= "list" then - return {} - end - return get_named_children(list) -end - -local LITERALS = { - integer = true, - float = true, - none = true, - ["true"] = true, - ["false"] = true, -} - -local is_simple_literal = function(node) - return LITERALS[node:type()] ~= nil -end - -local get_item_id = function(entry_idx, item_node, param, bufnr) - if item_node:type() == "string" then - local str = ts.get_node_text(item_node, bufnr) - local quote = str:sub(1, 1) - str = vim.fn.trim(str, quote):gsub("\n", "\\n") - return str - elseif is_simple_literal(item_node) then - return ts.get_node_text(item_node, bufnr) - end - return string.format("%s%d", param, entry_idx) -end - -local get_entry = function(entry_idx, entry_node, params, bufnr) - if entry_node:type() ~= "tuple" then - if #params == 1 then - return { - { - id = get_item_id(entry_idx, entry_node, params[1], bufnr), - node = entry_node, - param = params[1], - idx = entry_idx, - }, - } - else - return { { - id = string.format("unknown (%d)", entry_idx), - node = entry_node, - } } - end - end - local items = {} - local item_nodes = get_named_children(entry_node) - if #params ~= #item_nodes then - -- TODO warn here? - -- warn(string.format( - -- 'number of items in entry tuple differ from number of params, %d items and %d params (line %d in %s)', - -- #item_nodes, - -- #params, - -- entry_node:start() + 1, - -- vim.fn.bufname(bufnr) - -- )) - return nil - end - for i, param in ipairs(params) do - table.insert(items, { - id = get_item_id(entry_idx, item_nodes[i], param, bufnr), - node = item_nodes[i], - param = param, - idx = entry_idx, - }) - end - return items -end - -local get_entries = function(call_node, params, bufnr) - local entries = {} - for entry_idx, entry_node in ipairs(list_entries(call_node)) do - local items = get_entry(entry_idx - 1, entry_node, params, bufnr) - if items ~= nil then - table.insert(entries, { - id = table.concat( - tbls.list_map(function(item) - return item.id - end, items), - "-" - ), - items = items, - node = entry_node, - }) - end - end - return entries -end - -M.get_calls = function(bufnr) - local calls = get_param_call_nodes(bufnr or 0) - local call_specs = {} - for _, call in ipairs(calls) do - -- Move to separate func, better way? - local decorated_definition = call:parent():parent() - if decorated_definition:type() ~= "decorated_definition" then - local row = call:start() - warn( - string.format( - "couldn't parse params (line %d)\n expected `decorated_definition`\n got `%s`", - row, - decorated_definition:type() - ) - ) - return - end - local func = decorated_definition:field("definition")[1] - local func_name = ts.get_node_text(func:field("name")[1], bufnr) - - local arguments = call:field("arguments")[1] - local first_named = nil - for child in arguments:iter_children() do - if child:named() then - first_named = child - break - end - end - local params_node = first_named - if not params_node or params_node:type() ~= "string" then - local row = call:start() - warn( - string.format( - "couldn't parse params (line %d)\n expected `string`\n got `%s`", - row, - params_node and params_node:type() or "nil" - ) - ) - return - end - local params_str = ts.get_node_text(params_node, bufnr) - params_str = params_str:sub(2, -2) - local params = vim.fn.split(params_str, [[,\s*]]) -- TODO avoid vim script? - local entries = get_entries(call, params, bufnr) - if call_specs[func_name] == nil then - call_specs[func_name] = {} - end - table.insert(call_specs[func_name], { - node = call, - entries = entries, - params = params, - func_name = func_name, - }) - end - return call_specs -end - -return M diff --git a/lua/pytrize/init.lua b/lua/pytrize/init.lua index 84b5d96..cd773e6 100644 --- a/lua/pytrize/init.lua +++ b/lua/pytrize/init.lua @@ -3,34 +3,25 @@ local M = {} local settings = require("pytrize.settings") local function setup_commands() - vim.api.nvim_create_user_command("Pytrize", function() - require("pytrize.api").set() - end, { desc = "Set pytrize virtual text for parametrize entries" }) - vim.api.nvim_create_user_command("PytrizeClear", function() - require("pytrize.api").clear() - end, { desc = "Clear pytrize virtual text" }) - vim.api.nvim_create_user_command("PytrizeJump", function() - require("pytrize.api").jump() - end, { desc = "Jump to parametrize entry under cursor" }) - vim.api.nvim_create_user_command("PytrizeJumpFixture", function() - require("pytrize.api").jump_fixture() - end, { desc = "Jump to fixture definition under cursor" }) - vim.api.nvim_create_user_command("PytrizeRenameFixture", function() - require("pytrize.api").rename_fixture() - end, { desc = "Rename fixture under cursor across project" }) - vim.api.nvim_create_user_command("PytrizeFixtureUsages", function() - require("pytrize.api").fixture_usages() - end, { desc = "Show fixture usages in quickfix list" }) + vim.api.nvim_create_user_command("PytrizeJumpFixture", function() + require("pytrize.api").jump_fixture() + end, { desc = "Jump to fixture definition under cursor" }) + vim.api.nvim_create_user_command("PytrizeRenameFixture", function() + require("pytrize.api").rename_fixture() + end, { desc = "Rename fixture under cursor across project" }) + vim.api.nvim_create_user_command("PytrizeFixtureUsages", function() + require("pytrize.api").fixture_usages() + end, { desc = "Show fixture usages in quickfix list" }) end --- Configure the pytrize plugin. ---@param opts? PytrizeSettings M.setup = function(opts) - opts = opts or {} - settings.update(opts) - if not settings.settings.no_commands then - setup_commands() - end + opts = opts or {} + settings.update(opts) + if not settings.settings.no_commands then + setup_commands() + end end return M diff --git a/lua/pytrize/jump/init.lua b/lua/pytrize/jump/init.lua index dfe228d..e070067 100644 --- a/lua/pytrize/jump/init.lua +++ b/lua/pytrize/jump/init.lua @@ -1,9 +1,7 @@ local M = {} -local param = require('pytrize.jump.param') local fixture = require('pytrize.jump.fixture') -M.to_param_declaration = param.to_declaration M.to_fixture_declaration = fixture.to_declaration return M diff --git a/lua/pytrize/jump/param.lua b/lua/pytrize/jump/param.lua deleted file mode 100644 index 709c3c5..0000000 --- a/lua/pytrize/jump/param.lua +++ /dev/null @@ -1,128 +0,0 @@ -local M = {} - -local cs = require('pytrize.call_spec') -local nids = require('pytrize.nodeids') -local tbls = require('pytrize.tables') -local paths = require('pytrize.paths') -local warn = require('pytrize.warn').warn -local open_file = require('pytrize.jump.util').open_file -local get_nodeids_path = require('pytrize.paths').get_nodeids_path - -local function query_file(func_name, callback) - local rootdir, _ = paths.split_at_root(vim.api.nvim_buf_get_name(0)) - if rootdir == nil then - return - end - local unique_files = {} - for file, file_nodeids in pairs(nids.get(rootdir)) do - if file_nodeids[func_name] ~= nil then - unique_files[file] = true - end - end - local files = {} - for file, _ in pairs(unique_files) do - if vim.fn.filereadable(file) == 1 then - table.insert(files, file) - end - end - if #files == 0 then - warn(string.format( - 'could not find the file for function `%s` when looking in %s, did you run the test?', - func_name, - get_nodeids_path(rootdir) - )) - elseif #files == 1 then - callback(files[1]) - else - vim.ui.select(files, { - prompt = 'Multiple files found for the nodeid under cursor, pick the correct one:', - }, callback) - -- prompt_files(files, callback) - end -end - -local function jump_to_nodeid_at_cursor(callback) - -- TODO how to handle col_num? - local line_num, col_num = unpack(vim.api.nvim_win_get_cursor(0)) - local line = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, 0)[1] - local i, _ = string.find(line, '%S*:?:?test_%w+%[.*') -- TODO how to check for zero or two :? - if i == nil then - warn("no nodeid under cursor") - return - end - local nodeid = nids.parse_raw(line:sub(i)) - local pattern_position = col_num + 1 - (i - 1) -- cursor relative to match - local param_position = pattern_position - nodeid.param_start_idx + 1 -- cursor relative to params - param_position = math.min(math.max(1, param_position), nodeid.params:len()) -- restrict it to be inside - if nodeid == nil then - warn("couldn't parse nodeid under cursor") - return - end - if nodeid.file == nil then - query_file(nodeid.func_name, function(file) - if file == nil then - return - end - nodeid.file = file - callback(nodeid, param_position) - end) - else - callback(nodeid, param_position) - end -end - -local startswith = function(str, sub) - return str:sub(1, sub:len()) == sub -end - -M.to_declaration = function() - jump_to_nodeid_at_cursor(function(nodeid, param_position) - local bufnr = 0 - local original_buffer = vim.api.nvim_buf_get_name(bufnr) - if vim.fn.filereadable(nodeid.file) == 0 then - warn(string.format('file `%s` is not readable', nodeid.file)) - return - end - open_file(nodeid.file) - local call_specs = cs.get_calls(bufnr)[nodeid.func_name] - if call_specs == nil then - open_file(original_buffer) - return - end - local params = nodeid.params - for _, call_spec in ipairs(tbls.reverse(call_specs)) do - for _, entry_spec in ipairs(call_spec.entries) do - if startswith(params, entry_spec.id:sub(1, params:len())) then - for _, item_spec in ipairs(entry_spec.items) do - if startswith(params, item_spec.id) then - if param_position <= item_spec.id:len() then - local row, col = item_spec.node:start() - vim.api.nvim_win_set_cursor(0, {row + 1, col}) - return - else - local shift = item_spec.id:len() + 2 - params = params:sub(shift) - param_position = param_position - (shift - 1) - end - end - end - end - end - end - warn(string.format( - 'could not find the id `%s` of `%s` in file `%s`', - nodeid.params, - nodeid.func_name, - nodeid.file - )) - if #call_specs > 0 then - -- at least jump to the last call spec - local row, col = call_specs[#call_specs].node:start() - vim.api.nvim_win_set_cursor(0, {row + 1, col}) - else - open_file(original_buffer) - end - end) -end - -return M diff --git a/lua/pytrize/marks.lua b/lua/pytrize/marks.lua deleted file mode 100644 index e6e9243..0000000 --- a/lua/pytrize/marks.lua +++ /dev/null @@ -1,28 +0,0 @@ -local M = {} - -local settings = require("pytrize.settings").settings - -local NS_ID = vim.api.nvim_create_namespace("pytrize") - ---- Clear all pytrize extmarks from a buffer. ----@param bufnr integer Buffer number (0 for current) -M.clear = function(bufnr) - if bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - vim.api.nvim_buf_clear_namespace(bufnr, NS_ID, 0, -1) -end - ---- Set a virtual text extmark. ----@param opts { bufnr: integer, row: integer, text: string } -M.set = function(opts) - local bufnr = opts.bufnr - if bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - vim.api.nvim_buf_set_extmark(bufnr, NS_ID, opts.row, 0, { - virt_text = { { opts.text, settings.highlight } }, - }) -end - -return M diff --git a/lua/pytrize/nodeids.lua b/lua/pytrize/nodeids.lua deleted file mode 100644 index 23626a5..0000000 --- a/lua/pytrize/nodeids.lua +++ /dev/null @@ -1,80 +0,0 @@ -local M = {} - -local split_once = require("pytrize.strings").split_once -local warn = require("pytrize.warn").warn -local get_nodeids_path = require("pytrize.paths").get_nodeids_path - -local function get_raw_nodeids(rootdir) - local nodeids_path = get_nodeids_path(rootdir) - - if vim.fn.filereadable(nodeids_path) ~= 1 then - warn(string.format("Nodeids file not found: %s\nHave you run pytest?", nodeids_path)) - return {} - end - - local ok_read, content = pcall(vim.fn.readfile, nodeids_path) - if not ok_read then - warn(string.format("Failed to read nodeids file: %s", nodeids_path)) - return {} - end - - local ok_json, result = pcall(vim.fn.json_decode, content) - if not ok_json then - warn(string.format("Failed to parse nodeids JSON: %s", nodeids_path)) - return {} - end - - return result -end - -M.parse_raw = function(raw_nodeid) - local file - local func_name - local rest - local param_start_idx - file, rest = split_once(raw_nodeid, "::", { plain = true }) - if rest == nil then - -- no file - file = nil - rest = raw_nodeid - param_start_idx = 0 - else - param_start_idx = file:len() + 2 - end - func_name, rest = split_once(rest, "[", { plain = true }) - if rest == nil then - return - end - param_start_idx = param_start_idx + func_name:len() + 1 - - -- local params, _ = split_once(rest, ']', {plain = true, right = true}) - return { - file = file, - func_name = func_name, - params = rest, - param_start_idx = param_start_idx + 1, - } -end - -M.get = function(rootdir) - local nodeids = {} - for _, raw_nodeid in ipairs(get_raw_nodeids(rootdir)) do - local nodeid = M.parse_raw(raw_nodeid) - if nodeid ~= nil then - if nodeid.file == nil then - warn("node id has no file") - return {} - end - if nodeids[nodeid.file] == nil then - nodeids[nodeid.file] = {} - end - if nodeids[nodeid.file][nodeid.func_name] == nil then - nodeids[nodeid.file][nodeid.func_name] = {} - end - table.insert(nodeids[nodeid.file][nodeid.func_name], nodeid.params) - end - end - return nodeids -end - -return M diff --git a/lua/pytrize/paths.lua b/lua/pytrize/paths.lua index ec0b48f..43990b9 100644 --- a/lua/pytrize/paths.lua +++ b/lua/pytrize/paths.lua @@ -3,11 +3,9 @@ local M = {} local warn = require("pytrize.warn").warn local root_markers = { - ".pytest_cache", "pyproject.toml", "setup.py", "setup.cfg", - "pytest.ini", "tox.ini", ".git", } @@ -40,7 +38,7 @@ M.split_at_root = function(file) return dir, join_path(rel_file_fragments) end end - warn("couldn't find the pytest root dir") + warn("couldn't find the project root dir") end M.get_conftest_chain = function(filepath, root_dir) @@ -70,8 +68,4 @@ M.get_conftest_chain = function(filepath, root_dir) return chain end -M.get_nodeids_path = function(rootdir) - return join_path({ rootdir, ".pytest_cache", "v", "cache", "nodeids" }) -end - return M diff --git a/lua/pytrize/strings.lua b/lua/pytrize/strings.lua deleted file mode 100644 index bbf4728..0000000 --- a/lua/pytrize/strings.lua +++ /dev/null @@ -1,20 +0,0 @@ -local M = {} - -M.split_once = function(str, sep, kwargs) - if kwargs.right then - kwargs.right = false - local second, first = M.split_once(str:reverse(), sep:reverse(), kwargs) - return first:reverse(), second:reverse() - end - local fragments = vim.split(str, sep, kwargs) - local first = table.remove(fragments, 1) - local second - if #fragments > 0 then - second = table.concat(fragments, sep) - else - second = nil - end - return first, second -end - -return M diff --git a/lua/pytrize/tables.lua b/lua/pytrize/tables.lua deleted file mode 100644 index d21b4df..0000000 --- a/lua/pytrize/tables.lua +++ /dev/null @@ -1,19 +0,0 @@ -local M = {} - -M.reverse = function(lst) - local reversed = {} - for _, entry in ipairs(lst) do - table.insert(reversed, 1, entry) - end - return reversed -end - -M.list_map = function(func, iterable) - local new = {} - for _, v in ipairs(iterable) do - table.insert(new, func(v)) - end - return new -end - -return M diff --git a/tests/pytrize/call_spec_spec.lua b/tests/pytrize/call_spec_spec.lua deleted file mode 100644 index 57f2b07..0000000 --- a/tests/pytrize/call_spec_spec.lua +++ /dev/null @@ -1,125 +0,0 @@ -local has_parser = pcall(function() - vim.treesitter.language.inspect("python") -end) - -if not has_parser then - describe("call_spec (skipped)", function() - it("SKIPPED: python treesitter parser not installed", function() - print("Skipping call_spec tests: python treesitter parser not available") - end) - end) - return -end - -local call_spec = require("pytrize.call_spec") - -local function create_python_buf(lines) - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - vim.api.nvim_set_option_value("filetype", "python", { buf = bufnr }) - - -- Force treesitter parse - local parser = vim.treesitter.get_parser(bufnr, "python") - parser:parse() - - return bufnr -end - -describe("call_spec.get_calls", function() - it("parses single-param parametrize with strings", function() - local bufnr = create_python_buf({ - "import pytest", - "", - '@pytest.mark.parametrize("name", [', - ' "alice",', - ' "bob",', - "])", - "def test_greet(name):", - " pass", - }) - - local result = call_spec.get_calls(bufnr) - assert.is_not_nil(result) - assert.is_not_nil(result["test_greet"]) - assert.are.equal(1, #result["test_greet"]) - - local spec = result["test_greet"][1] - assert.are.equal("test_greet", spec.func_name) - assert.are.same({ "name" }, spec.params) - assert.are.equal(2, #spec.entries) - assert.are.equal("alice", spec.entries[1].id) - assert.are.equal("bob", spec.entries[2].id) - - vim.api.nvim_buf_delete(bufnr, { force = true }) - end) - - it("parses multi-param parametrize with tuples", function() - local bufnr = create_python_buf({ - "import pytest", - "", - '@pytest.mark.parametrize("x, y", [', - " (1, 2),", - " (3, 4),", - "])", - "def test_add(x, y):", - " pass", - }) - - local result = call_spec.get_calls(bufnr) - assert.is_not_nil(result) - - local spec = result["test_add"][1] - assert.are.same({ "x", "y" }, spec.params) - assert.are.equal(2, #spec.entries) - assert.are.equal("1-2", spec.entries[1].id) - assert.are.equal("3-4", spec.entries[2].id) - - vim.api.nvim_buf_delete(bufnr, { force = true }) - end) - - it("parses mixed types: int, float, bool, None", function() - local bufnr = create_python_buf({ - "import pytest", - "", - '@pytest.mark.parametrize("val", [', - " 42,", - " 3.14,", - " True,", - " False,", - " None,", - "])", - "def test_types(val):", - " pass", - }) - - local result = call_spec.get_calls(bufnr) - assert.is_not_nil(result) - - local spec = result["test_types"][1] - assert.are.equal(5, #spec.entries) - assert.are.equal("42", spec.entries[1].id) - assert.are.equal("3.14", spec.entries[2].id) - assert.are.equal("True", spec.entries[3].id) - assert.are.equal("False", spec.entries[4].id) - assert.are.equal("None", spec.entries[5].id) - - vim.api.nvim_buf_delete(bufnr, { force = true }) - end) - - it("handles multiple decorators on one function", function() - local bufnr = create_python_buf({ - "import pytest", - "", - '@pytest.mark.parametrize("a", [1, 2])', - '@pytest.mark.parametrize("b", [3, 4])', - "def test_combo(a, b):", - " pass", - }) - - local result = call_spec.get_calls(bufnr) - assert.is_not_nil(result) - assert.are.equal(2, #result["test_combo"]) - - vim.api.nvim_buf_delete(bufnr, { force = true }) - end) -end) diff --git a/tests/pytrize/nodeids_spec.lua b/tests/pytrize/nodeids_spec.lua deleted file mode 100644 index 0cc0609..0000000 --- a/tests/pytrize/nodeids_spec.lua +++ /dev/null @@ -1,32 +0,0 @@ -local nodeids = require("pytrize.nodeids") - -describe("parse_raw", function() - it("parses a full nodeid with file, func, and params", function() - local result = nodeids.parse_raw("test_file.py::test_func[param1-param2]") - assert.are.equal("test_file.py", result.file) - assert.are.equal("test_func", result.func_name) - assert.are.equal("param1-param2]", result.params) - -- param_start_idx: "test_file.py::" = 14 chars, "test_func[" = 10 chars -> index 25 - assert.are.equal(25, result.param_start_idx) - end) - - it("parses nodeid without file", function() - local result = nodeids.parse_raw("test_func[param1]") - assert.is_nil(result.file) - assert.are.equal("test_func", result.func_name) - assert.are.equal("param1]", result.params) - assert.are.equal(11, result.param_start_idx) - end) - - it("returns nil when no params (no bracket)", function() - local result = nodeids.parse_raw("test_file.py::test_func") - assert.is_nil(result) - end) - - it("handles nested brackets in params", function() - local result = nodeids.parse_raw("test_file.py::test_func[param[0]-param[1]]") - assert.are.equal("test_file.py", result.file) - assert.are.equal("test_func", result.func_name) - assert.are.equal("param[0]-param[1]]", result.params) - end) -end) diff --git a/tests/pytrize/rename_spec.lua b/tests/pytrize/rename_spec.lua index 05b79bf..c6748a1 100644 --- a/tests/pytrize/rename_spec.lua +++ b/tests/pytrize/rename_spec.lua @@ -308,7 +308,7 @@ describe("rename - cross-file fixture scoping", function() end) it("renaming fixture in conftest only renames usage in file_a, not in file_b which has its own definition", function() - vim.fn.mkdir(tmp_root .. "/.pytest_cache", "p") + vim.fn.mkdir(tmp_root .. "/.git", "p") write_py(tmp_root .. "/conftest.py", { "import pytest", diff --git a/tests/pytrize/strings_spec.lua b/tests/pytrize/strings_spec.lua deleted file mode 100644 index 98fb0a6..0000000 --- a/tests/pytrize/strings_spec.lua +++ /dev/null @@ -1,33 +0,0 @@ -local strings = require("pytrize.strings") - -describe("split_once", function() - it("splits on first occurrence of separator", function() - local first, second = strings.split_once("a::b::c", "::", { plain = true }) - assert.are.equal("a", first) - assert.are.equal("b::c", second) - end) - - it("returns nil second when separator not found", function() - local first, second = strings.split_once("abc", "::", { plain = true }) - assert.are.equal("abc", first) - assert.is_nil(second) - end) - - it("splits from the right when right=true", function() - local first, second = strings.split_once("a-b-c", "-", { plain = true, right = true }) - assert.are.equal("a-b", first) - assert.are.equal("c", second) - end) - - it("handles separator at start", function() - local first, second = strings.split_once("::abc", "::", { plain = true }) - assert.are.equal("", first) - assert.are.equal("abc", second) - end) - - it("handles separator at end", function() - local first, second = strings.split_once("abc::", "::", { plain = true }) - assert.are.equal("abc", first) - assert.are.equal("", second) - end) -end) diff --git a/tests/pytrize/tables_spec.lua b/tests/pytrize/tables_spec.lua deleted file mode 100644 index 4d0a275..0000000 --- a/tests/pytrize/tables_spec.lua +++ /dev/null @@ -1,31 +0,0 @@ -local tables = require("pytrize.tables") - -describe("reverse", function() - it("reverses a list", function() - assert.are.same({ 3, 2, 1 }, tables.reverse({ 1, 2, 3 })) - end) - - it("handles empty list", function() - assert.are.same({}, tables.reverse({})) - end) - - it("handles single element", function() - assert.are.same({ 1 }, tables.reverse({ 1 })) - end) -end) - -describe("list_map", function() - it("applies function to each element", function() - local result = tables.list_map(function(x) return x * 2 end, { 1, 2, 3 }) - assert.are.same({ 2, 4, 6 }, result) - end) - - it("returns empty list for empty input", function() - assert.are.same({}, tables.list_map(function(x) return x end, {})) - end) - - it("works with identity function", function() - assert.are.same({ "a", "b" }, tables.list_map(function(x) return x end, { "a", "b" })) - end) -end) -