From 30964967dba2bf58601454b3dd3533555604d4a2 Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Wed, 18 Oct 2023 14:43:27 -0700 Subject: [PATCH] Add code coverage --- .github/workflows/coverage.yml | 53 +++++++++++++++++++++ .gitignore | 1 + .luacov | 9 ++++ Makefile | 39 ++++++++++++++-- flake.lock | 51 +++++++++++++++++++++ flake.nix | 66 +++++++++++++++++++++++++-- nix/cluacov.nix | 15 ++++++ nix/luacov-reporter-lcov.nix | 17 +++++++ nix/luacov.nix | 25 ++++++++++ nix/mkCheck.nix | 40 ++++++++-------- nix/patches/luacov-reporter-lcov.diff | 12 +++++ nix/patches/luacov.diff | 35 ++++++++++++++ scripts/mini_test_init.lua | 5 ++ 13 files changed, 342 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/coverage.yml create mode 100644 .gitignore create mode 100644 .luacov create mode 100644 nix/cluacov.nix create mode 100644 nix/luacov-reporter-lcov.nix create mode 100644 nix/luacov.nix create mode 100644 nix/patches/luacov-reporter-lcov.diff create mode 100644 nix/patches/luacov.diff diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..7ccb25a --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,53 @@ +--- +on: + pull_request: + +name: Code coverage + +permissions: + issues: write + pull-requests: write + +jobs: + coverage-report: + name: Generate test coverage report + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + + - uses: cachix/install-nix-action@v22 + with: + extra_nix_config: | + extra-experimental-features = nix-command flakes + accept-flake-config = true + + - name: Fetch code coverage report + id: fetch-coverage + run: | + COVERAGE_PATH=$(nix build '.#coverage' --print-build-logs --print-out-paths --no-link) + echo "coverage-path=$COVERAGE_PATH" >> "$GITHUB_OUTPUT" + + - name: Read coverage summary + id: coverage-summary + uses: juliangruber/read-file-action@v1 + with: + path: ${{ steps.fetch-coverage.outputs.coverage-path }}/coverage-summary.txt + + - name: Comment with coverage information + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + Code coverage: + ``` + ${{ steps.coverage-summary.outputs.content }} + ``` + + - uses: actions/upload-artifact@v3 + with: + name: coverage-report + path: ${{ steps.fetch-coverage.outputs.coverage-path }}/coverage-report diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.luacov b/.luacov new file mode 100644 index 0000000..2bc369d --- /dev/null +++ b/.luacov @@ -0,0 +1,9 @@ +return { + modules = { + ["broot"] = "lua/broot/init.lua", + ["broot.*"] = "lua", + }, + statsfile = "target/coverage.stats", + reportfile = "target/coverage.lcov", + tick = true, +} diff --git a/Makefile b/Makefile index cb0a214..1b0f88e 100644 --- a/Makefile +++ b/Makefile @@ -5,13 +5,44 @@ ifdef file NVIM_COMMAND = "lua MiniTest.run_file([["$(file)"]])" endif -# Run all test files -.PHONY: test -test: - nvim --headless --noplugin -u ./scripts/mini_test_init.lua -c $(NVIM_COMMAND) +ifdef coverage +export COVERAGE = 1 +endif + +# Clean out temporary files or other artifacts. +.PHONY: clean +clean: + rm -rf target # Format code .PHONY: format format: stylua . alejandra . + +# Run all test files, or the file specified by `file=`. +.PHONY: test +test: target + nvim --headless --noplugin -u ./scripts/mini_test_init.lua -c $(NVIM_COMMAND) +ifdef COVERAGE + make target/coverage-report +endif + +# Generate an HTML coverage report and fail if there are untested lines. +target/coverage-report target/coverage-summary.txt: target target/coverage.lcov + genhtml target/coverage.lcov \ + --no-function-coverage \ + --no-branch-coverage \ + --output-directory target/coverage-report + lcov --summary target/coverage.lcov \ + --fail-under-lines 100 \ + | tee target/coverage-summary.txt + lcov --list target/coverage.lcov \ + | tee --append target/coverage-summary.txt + +target/coverage.lcov: target/coverage.stats + luacov -r lcov + +# Create the `target` directory. +target: + mkdir -p target diff --git a/flake.lock b/flake.lock index c4b7bb1..3bae70f 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,53 @@ { "nodes": { + "cluacov": { + "flake": false, + "locked": { + "lastModified": 1525526952, + "narHash": "sha256-+kWkLQgre80drX8QTnu15RozO4H32tboSALUTEcegsg=", + "owner": "mpeterv", + "repo": "cluacov", + "rev": "ca8e019e1f61b7f3f2c2d37f2728a741dc167cc5", + "type": "github" + }, + "original": { + "owner": "mpeterv", + "repo": "cluacov", + "type": "github" + } + }, + "luacov": { + "flake": false, + "locked": { + "lastModified": 1695190830, + "narHash": "sha256-uxm1HQd5kV1pOZ0qMWARgLUh+UqHB4J9ztXi9WdGGKU=", + "owner": "lunarmodules", + "repo": "luacov", + "rev": "716c408cf4489dfb516b59021042839b8f4132d6", + "type": "github" + }, + "original": { + "owner": "lunarmodules", + "repo": "luacov", + "type": "github" + } + }, + "luacov-reporter-lcov": { + "flake": false, + "locked": { + "lastModified": 1572605922, + "narHash": "sha256-o+9E+pMVpWpd4M0gBnU0g9OA7UZjNbuqFa6WG2j73nE=", + "owner": "daurnimator", + "repo": "luacov-reporter-lcov", + "rev": "4d881ddb4eeec5ac0cd7d4b7679e3e59f9ac5745", + "type": "github" + }, + "original": { + "owner": "daurnimator", + "repo": "luacov-reporter-lcov", + "type": "github" + } + }, "lualscheck": { "flake": false, "locked": { @@ -65,6 +113,9 @@ }, "root": { "inputs": { + "cluacov": "cluacov", + "luacov": "luacov", + "luacov-reporter-lcov": "luacov-reporter-lcov", "lualscheck": "lualscheck", "mini-nvim": "mini-nvim", "neodev": "neodev", diff --git a/flake.nix b/flake.nix index ca98efa..a34f4ae 100644 --- a/flake.nix +++ b/flake.nix @@ -3,22 +3,44 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; + + # Neovim test framework. mini-nvim = { url = "github:echasnovski/mini.nvim"; flake = false; }; + + # Vimhelp->HTML generation. Used for the GitHub pages site. vimhelp = { url = "github:c4rlo/vimhelp"; flake = false; }; + + # Tool for running `lua-language-server` in check mode for type-checking. lualscheck = { url = "github:9999years/lualscheck"; flake = false; }; + + # Neovim Lua API type stubs. neodev = { url = "github:folke/neodev.nvim"; flake = false; }; + + # Lua code coverage. + luacov = { + url = "github:lunarmodules/luacov"; + flake = false; + }; + cluacov = { + url = "github:mpeterv/cluacov"; + flake = false; + }; + luacov-reporter-lcov = { + url = "github:daurnimator/luacov-reporter-lcov"; + flake = false; + }; }; nixConfig = { @@ -34,6 +56,9 @@ vimhelp, lualscheck, neodev, + luacov, + cluacov, + luacov-reporter-lcov, }: let forAllSystems = function: nixpkgs.lib.genAttrs [ @@ -54,6 +79,7 @@ mkCheck = pkgs.callPackage ./nix/mkCheck.nix { luarc = self.packages.${pkgs.system}.luarc; }; + luaPkgs = pkgs.lua5_1.pkgs; in { # mini.nvim unit tests. tests = mkCheck { @@ -73,6 +99,27 @@ ''; }; + luacov = self.checks.${pkgs.system}.tests.override (old: { + name = "luacov"; + + nativeCheckInputs = + (old.nativeCheckInputs or []) + ++ [ + self.packages.${pkgs.system}.luacov + self.packages.${pkgs.system}.cluacov + pkgs.lcov + ]; + + COVERAGE = true; + + installPhase = '' + mkdir $out + cp target/coverage.lcov $out/ + cp target/coverage-summary.txt $out/ + cp --recursive target/coverage-report $out/ + ''; + }); + # Check diagnostics and type annotations with `lua-language-server`. luals = mkCheck { name = "lua-language-server"; @@ -116,7 +163,7 @@ luacheck = mkCheck { name = "luacheck"; - nativeCheckInputs = [pkgs.lua5_1.pkgs.luacheck]; + nativeCheckInputs = [luaPkgs.luacheck]; checkPhase = '' luacheck . @@ -135,6 +182,20 @@ }; luarc = pkgs.callPackage ./nix/luarc.nix {inherit neodev;}; + + luacov = pkgs.callPackage ./nix/luacov.nix { + inherit luacov; + luacov-reporter-lcov = self.packages.${pkgs.system}.luacov-reporter-lcov; + }; + cluacov = pkgs.callPackage ./nix/cluacov.nix { + inherit cluacov; + luacov = self.packages.${pkgs.system}.luacov; + }; + luacov-reporter-lcov = pkgs.callPackage ./nix/luacov-reporter-lcov.nix { + inherit luacov-reporter-lcov; + }; + + coverage = self.checks.${pkgs.system}.luacov; }); devShells = forAllSystems (pkgs: { @@ -142,9 +203,6 @@ MINI_NVIM = "${mini-nvim}"; inputsFrom = builtins.attrValues self.checks.${pkgs.system}; - packages = [ - pkgs.lua5_1.pkgs.luacheck - ]; shellHook = '' ${self.packages.${pkgs.system}.luarc.link-to-cwd} diff --git a/nix/cluacov.nix b/nix/cluacov.nix new file mode 100644 index 0000000..9c491b7 --- /dev/null +++ b/nix/cluacov.nix @@ -0,0 +1,15 @@ +{ + lua5_1, + luacov, + cluacov, +}: +lua5_1.pkgs.buildLuarocksPackage { + pname = "cluacov"; + version = "scm-1"; + src = cluacov; + + propagatedBuildInputs = [ + lua5_1 + luacov + ]; +} diff --git a/nix/luacov-reporter-lcov.nix b/nix/luacov-reporter-lcov.nix new file mode 100644 index 0000000..ecb412d --- /dev/null +++ b/nix/luacov-reporter-lcov.nix @@ -0,0 +1,17 @@ +{ + lua5_1, + luacov-reporter-lcov, +}: +lua5_1.pkgs.buildLuarocksPackage { + pname = "luacov-reporter-lcov"; + version = "scm-0"; + src = luacov-reporter-lcov; + + patches = [ + ./patches/luacov-reporter-lcov.diff + ]; + + propagatedBuildInputs = [ + lua5_1 + ]; +} diff --git a/nix/luacov.nix b/nix/luacov.nix new file mode 100644 index 0000000..1f88d49 --- /dev/null +++ b/nix/luacov.nix @@ -0,0 +1,25 @@ +{ + lua5_1, + luacov, + luacov-reporter-lcov, +}: +lua5_1.pkgs.buildLuarocksPackage { + pname = "luacov"; + version = "scm-1"; + src = luacov; + + propagatedBuildInputs = [ + lua5_1 + luacov-reporter-lcov + ]; + + patches = [ + ./patches/luacov.diff + ]; + + # Tell `luacov` where to find its assets. + preConfigure = '' + substituteInPlace src/luacov/reporter/html.lua \ + --subst-var-by assetDir "$out/$rocksSubdir/$pname/$version" + ''; +} diff --git a/nix/mkCheck.nix b/nix/mkCheck.nix index 707d9c1..c448d73 100644 --- a/nix/mkCheck.nix +++ b/nix/mkCheck.nix @@ -1,25 +1,29 @@ { + lib, stdenv, luarc, -}: args @ {name, ...}: let - args' = builtins.removeAttrs args ["name"]; -in - stdenv.mkDerivation ({ - name = "broot.nvim-${name}"; +}: let + mkCheck = args @ {name, ...}: let + args' = builtins.removeAttrs args ["name"]; + in + stdenv.mkDerivation ({ + name = "broot.nvim-${name}"; - src = ../.; + src = ../.; - dontConfigure = true; - dontBuild = true; - doCheck = true; + dontConfigure = true; + dontBuild = true; + doCheck = true; - postPatch = '' - export HOME=$(pwd) - ${luarc.link-to-cwd} - ''; + postPatch = '' + export HOME=$(pwd) + ${luarc.link-to-cwd} + ''; - installPhase = '' - touch $out - ''; - } - // args') + installPhase = '' + touch $out + ''; + } + // args'); +in + args: lib.makeOverridable mkCheck args diff --git a/nix/patches/luacov-reporter-lcov.diff b/nix/patches/luacov-reporter-lcov.diff new file mode 100644 index 0000000..5366588 --- /dev/null +++ b/nix/patches/luacov-reporter-lcov.diff @@ -0,0 +1,12 @@ +diff --git a/luacov-reporter-lcov-scm-0.rockspec b/luacov-reporter-lcov-scm-0.rockspec +index c257373..c4e808e 100644 +--- a/luacov-reporter-lcov-scm-0.rockspec ++++ b/luacov-reporter-lcov-scm-0.rockspec +@@ -13,7 +13,6 @@ source = { + + dependencies = { + "lua >= 5.1"; +- "luacov"; + } + + build = { diff --git a/nix/patches/luacov.diff b/nix/patches/luacov.diff new file mode 100644 index 0000000..944d868 --- /dev/null +++ b/nix/patches/luacov.diff @@ -0,0 +1,35 @@ +The `datafile` package (https://github.com/hishamhm/datafile/) doesn't play +nicely with Nix. + +We remove it and tell `luacov` where to find the asset files directly. + +diff --git a/luacov-scm-1.rockspec b/luacov-scm-1.rockspec +index a8bf6d7..ecefad4 100644 +--- a/luacov-scm-1.rockspec ++++ b/luacov-scm-1.rockspec +@@ -18,7 +18,6 @@ effectiveness of a test suite. + } + dependencies = { + "lua >= 5.1", +- "datafile", + } + build = { + type = "builtin", +diff --git a/src/luacov/reporter/html.lua b/src/luacov/reporter/html.lua +index 26b49a3..6db32d3 100644 +--- a/src/luacov/reporter/html.lua ++++ b/src/luacov/reporter/html.lua +@@ -1,4 +1,3 @@ +-local datafile = require("datafile") + local luacov_reporter = require("luacov.reporter") + + local reporter = {} +@@ -32,7 +31,7 @@ do + + -- Returns contents of a file or nil + error message. + local function read_asset(name) +- local f, open_err = datafile.open("src/luacov/reporter/html/static/" .. name, "rb") ++ local f, open_err = io.open("@assetDir@/src/luacov/reporter/html/static/" .. name, "rb") + + if not f then + error(unprefix(open_err, name .. ": ")) diff --git a/scripts/mini_test_init.lua b/scripts/mini_test_init.lua index c6d5535..ce21240 100644 --- a/scripts/mini_test_init.lua +++ b/scripts/mini_test_init.lua @@ -12,6 +12,11 @@ vim.opt.runtimepath:append("," .. vim.fn.join({ cwd, cwd .. "/scripts", mini_nvi -- Set up 'mini.test' require("mini.test").setup() +-- Set up coverage if requested. +if os.getenv "COVERAGE" ~= nil then + require "luacov" +end + require("broot").setup { config_files = { vim.fn.fnamemodify("tests/data/conf.toml", ":p"),