AdamNiederer / cov

An emacs extension for displaying coverage data on your code
GNU General Public License v3.0
76 stars 16 forks source link

lcov parser fails with an error #42

Closed malta895 closed 2 years ago

malta895 commented 2 years ago

I am using Emacs version: 27.1, with Spacemacs.

I'm trying to get cov to work on a javascript project, the coverage report file is located at:

project-root/coverage/lcov.info

To find the lcov.info relatively from the root of the project, I set:

  (setq cov-lcov-patterns '(
                            (lambda (file-dir file-name)(concat (vc-root-dir) "coverage/lcov.info"))
                            ))

When I visit any .js file in my project and activate cov-mode, the function cov--lcov-parse fails with the error wrong type argument stringp nil, thrown by the function file-name-directory at this line, where it seems that the variable buffer-file-name is found nil.

I can see from the debugger that the path to the lcov.info file is correct.

I also tried by setting the absolute cov-lcov-file-name, with the same outcome.

Did I get something wrong in my configuration, or is it a possible bug?

sergiolib commented 2 years ago

I can confirm this issue.

Removing the expand-file-name call makes the function work when the lcov contains just absolute paths. The problem is that the parsing occurs in a new buffer and the call expects this buffer to have a file associated, something that causes the argument to return nil, which is not a string and thus the error we're getting.

One solution I see is working in terms of the default-directory variable, which is defined at that point. However, there are no guarantees that the relative paths and the default directory match. They would match if the coverage collector is called from the current buffer, which for the project I am working on is not the case.

If your lcov has absolute paths, the ugly workaround I have and works is evaluating the following expression:

(defun cov--lcov-parse (&optional buffer)
  "Parse lcov trace file in BUFFER.
Read from `current-buffer' if BUFFER is nil.
Return a list `((FILE . ((LINE-NUM EXEC-COUNT) ...)) ...)'."
  (let ((data (make-hash-table :test 'equal))  ; the collected data
        filelines                              ; data for the current SourceFile
        sourcefile)                            ; the current SourceFile
    (with-current-buffer (or buffer (current-buffer))
      (save-excursion
        (save-match-data
          (save-restriction
            (widen)
            (goto-char (point-min))
            (while (not (eobp))
              (unless (looking-at cov--lcov-prefix-re)
                (error "Unable to parse lcov data from %s: %s"
                       (or (buffer-file-name) (buffer-name))
                       (buffer-substring (line-beginning-position) (line-end-position))))
              (goto-char (match-end 0))
              (pcase (match-string 1)
                ;; Each SF signals the start of a new SourceFile.
                ("SF" (if (or sourcefile filelines)
                          (error "lcov parse error, SF with no preceeding end_of_record %s:%d"
                                 (buffer-file-name) (line-number-at-pos (point)))
                        ;; SF always hold an absolute path
                        (setq sourcefile (file-truename
                                           (buffer-substring (point) (line-end-position))))
                        (setq filelines
                              (or (gethash sourcefile data)
                                  (puthash sourcefile (make-hash-table :test 'eql) data)))))
                ;; DA:<line-num>,<exec-count>[,...]
                ("DA" (if (looking-at (rx (group-n 1 (1+ digit)) ?,
                                          (group-n 2 (1+ digit))
                                          (optional ?, (group (* any)))))
                          (let ((lineno (string-to-number (match-string 1)))
                                (count (string-to-number (match-string 2))))
                            (puthash lineno (+ (gethash lineno filelines 0) count) filelines))
                        (error "lcov parse error, bad DA line %s:%d"
                               (buffer-file-name) (line-number-at-pos (point)))))
                ;; End of coverage data for a source file, push
                ;; current file information to `data'
                ("end_of_record" (setq sourcefile nil filelines nil)))
              (forward-line 1))))))
    ;; TODO: Make it possible - or even mandatory - to use hashes instead of lists
    (cl-loop for sf being the hash-keys of data using (hash-values lines)
             collect (cons sf
                           (cl-loop for lineno being the hash-keys of lines
                                    using (hash-values count)
                                    collect (list lineno count))))))
snogge commented 2 years ago

Right, the coverage data is read into a temporary buffer to be parsed. The buffer file name is not set. Definitely a bug.