UP | HOME

Setting up Directory Local lcov Configuration for cov.el

Table of Contents

Problem description

At my work, we have a convention for where test coverage files are supposed to be generated to integrate with code quality tooling in CI, typically `root/coverage/…`. I've been working on upping our code coverage for one of our repos in which we generate lcov data. The `lcov.info` file is out of band with the rest of the repo, and its SF (source file) descriptors are relative to the root of the project, not the position of the coverage file. I would like to be able to view this coverage information in line in Emacs.

I usually use the excellent cov.el package for this. I noticed that it provides a `cov-lcov-project-root` variable that seemed to be perfectly suited for my use case. I set the variable in my project's dir-locals.el file, but I was getting errors the cov.el could not find coverage info for any of my source files.

The problem

The cov–read-and-parse function is responsible for parsing the coverage file and building an in memory mapping from source files to coverage information. For lcov, it does this by concatenating the SF location from the lcov file to either the `cov-lcov-project-root` or the coverage file's position if the variable is not set. However, this work takes place inside a `with-temp-buffer` macro. This temp buffer does not inherit the original buffer's buffer local variables. Looking at the project's issues, I see that this has been raised in this issue, but has not been addressed by the project's maintainer. There are two ways for the consumer of the package to fix this.

Source fix

A simple update of the source code can fix this issue. Before:

(defun cov--read-and-parse (file-path format)
  "<docstring omitted for brevity>"
  (with-temp-buffer
    (insert-file-contents file-path)
    (setq-local cov-coverage-file file-path)
    (funcall (intern (concat "cov--"  (symbol-name format) "-parse")))))

After:

(defun cov--read-and-parse (file-path format)
  "<docstring omitted for brevity>"
  ;; NEW Accessing the `cov-lcov-project-root` variable will get the
  ;; buffer local value if set, otherwise it falls back to the global
  ;; value.
  (let ((lcov-project-root cov-lcov-project-root))
    (with-temp-buffer
      (insert-file-contents file-path)
      ;; NEW Copy the local value to the temp buffer
      (setq-local cov-lcov-project-root lcov-project-root)
      (setq-local cov-coverage-file file-path)
      (funcall (intern (concat "cov--"  (symbol-name format) "-parse"))))))

Personally, I prefer not to directly update the source of my third party packages to make updating easier in the future. Luckily, Emacs gives us the tools necessary to correct this behavior without forking the dependency.

Advice

We can use Emacs' advice functionality to set the global value of the variable temporarily before the function runs, then reset it back to its original value afterwards.

(defun my/set-lcov-project-root-globally (function &rest args)
  ;; The default-value function returns the global value for a variable, not the buffer local one
  (let ((original-value (default-value 'cov-lcov-project-root)))
    ;; Remember, accessing the variable will prefer the buffer local
    ;; value, otherwise fall back to the global value. setq sets the global value.
    (setq cov-lcov-project-root cov-lcov-project-root)
    (let ((result (apply function args)))
      (setq cov-lcov-project-root original-value)
      result)))

Date: 2026-01-29 08:52:15

Emacs 30.2 (Org mode 9.7.11)