Skip to content

Commit d38ca77

Browse files
committed
Highlight failure log source lines in VCS websites if possible.
1 parent 96beb45 commit d38ca77

File tree

1 file changed

+126
-46
lines changed

1 file changed

+126
-46
lines changed

html-failure-report.lisp

Lines changed: 126 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,77 @@ the string is returned unchanged."
8282
(when substituted
8383
(return substituted)))))
8484

85+
(defun reconstitute (string patterns)
86+
(let ((result (maybe-reconstitute string patterns)))
87+
(and (not (eq result string))
88+
result)))
89+
90+
;;; Linking to VCS sources from log lines
91+
92+
(defun parse-vcs-source-log-line (log-line)
93+
"Return a plist of info about log-line."
94+
(when (and (search "dist/build-cache/" log-line)
95+
(not (search ".cache/common-lisp" log-line)))
96+
(ppcre:register-groups-bind (project-name full-path)
97+
("dist/build-cache/(.*?)/(.*$)" log-line)
98+
(let* ((pos 0)
99+
(second-slash
100+
(dotimes (i 2 pos)
101+
(setf pos (position #\/ full-path :start (1+ pos)))))
102+
(end (and second-slash (position #\& full-path :start second-slash))))
103+
(let ((source (find-source project-name)))
104+
(when (and source second-slash)
105+
(let* ((path (subseq full-path (1+ second-slash) end))
106+
(start (search path log-line))
107+
(line-number nil))
108+
(ppcre:register-groups-bind ((#'parse-integer log-line-number)) ("Line: (\\d+)" log-line)
109+
(setf line-number log-line-number))
110+
(list :source source
111+
:path path
112+
:line-number line-number
113+
:path-bounds (cons start (+ start (length path)))))))))))
114+
115+
(defparameter *location-base-substitutions*
116+
'(("(https://github.com/.*?/.*?)\\.git" 0 "/blob/")
117+
("(https://.*gitlab.*)\\.git$" 0 "/blob/")
118+
("(https://bitbucket.org/.*?)\\.git$" 0 "/src/")
119+
("(http://dwim.hu/live/.*$)" 0)))
120+
121+
(defun location-base (location)
122+
(reconstitute location *location-base-substitutions*))
123+
124+
(defun source-branch (source)
125+
(typecase source
126+
(tagged-mixin
127+
(tag-data source))
128+
(git-source
129+
"master")
130+
(t
131+
nil)))
132+
133+
(defun source-file-link-base (source)
134+
(let ((base (location-base (location source))))
135+
(when base
136+
(format nil "~A~@[~A/~]" base (source-branch source)))))
137+
138+
(defun source-file-link (source path line-number)
139+
(let ((base (source-file-link-base source)))
140+
(when base
141+
(format nil "~A~A~@[#L~A~]" base path line-number))))
142+
143+
(defun link-subseq (line link bounds)
144+
(destructuring-bind (start . end)
145+
bounds
146+
(concatenate 'string
147+
(subseq line 0 start)
148+
"<a href='"
149+
link
150+
"'>"
151+
(subseq line start end)
152+
"</a>"
153+
(subseq line end))))
154+
155+
85156
;;; Posting to S3
86157

87158
(defun report-publishing-enabled-p ()
@@ -90,8 +161,8 @@ the string is returned unchanged."
90161
(defun content-type (file)
91162
(cond ((equalp (pathname-type file) "css")
92163
"text/css")
93-
((equalp (pathname-type file) "rss")
94-
"application/rss+xml")
164+
((equalp (pathname-type file) "rss")
165+
"application/rss+xml")
95166
(t
96167
"text/html")))
97168

@@ -124,9 +195,9 @@ the string is returned unchanged."
124195
(defgeneric full-failure-report-url (object)
125196
(:method (object)
126197
(format nil "http://~A/~A~A"
127-
*failtail-bucket*
128-
(report-prefix)
129-
(failure-report-url object))))
198+
*failtail-bucket*
199+
(report-prefix)
200+
(failure-report-url object))))
130201
(defgeneric failure-report-html-file (base object))
131202

132203
(defgeneric stylesheet-path (object))
@@ -161,8 +232,10 @@ the string is returned unchanged."
161232

162233
(defmethod new-failure-p ((object failing-system))
163234
(let* ((dist (ql-dist:find-dist "quicklisp"))
164-
(system (ql-dist:find-system-in-dist (system-name object) dist)))
165-
(not (not system))))
235+
(existing-system
236+
(ql-dist:find-system-in-dist (system-name object) dist)))
237+
(or (not (not existing-system))
238+
(< (days-old (source object)) 30))))
166239

167240
(defmethod failure-data ((source upstream-source))
168241
(let ((result '()))
@@ -260,61 +333,61 @@ source is found that matches the filename, return nil."
260333
;; reason), so don't try to make a failing-source in that case.
261334
(let ((source (find-source source-name)))
262335
(when source
263-
(make-instance 'failing-system
264-
:source (find-source source-name)
265-
:failure-log-file failure-file
266-
:system-file-name system-file-name
267-
:system-name (decode-string-from-filesystem
268-
failing-system))))))
336+
(make-instance 'failing-system
337+
:source (find-source source-name)
338+
:failure-log-file failure-file
339+
:system-file-name system-file-name
340+
:system-name (decode-string-from-filesystem
341+
failing-system))))))
269342

270343
(defun failing-source-log-files ()
271344
(let* ((base (translate-logical-pathname "quicklisp-controller:dist;build-artifacts;"))
272-
(fail-wild (merge-pathnames "**/fail_*_*_*.txt" base)))
345+
(fail-wild (merge-pathnames "**/fail_*_*_*.txt" base)))
273346
(directory fail-wild)))
274347

275348
(defun failing-systems ()
276349
(remove nil
277-
(mapcar #'parse-failure-file-name
278-
(failing-source-log-files))))
350+
(mapcar #'parse-failure-file-name
351+
(failing-source-log-files))))
279352

280353
(defun failure-log-failure-report ()
281354
"Scan the failure log files of all projects to produce a failure report."
282355
(let ((systems (make-hash-table :test 'equal)))
283356
(flet ((fsource (source)
284-
(or (gethash (name source) systems)
285-
(setf (gethash (name source) systems)
286-
(make-instance 'failing-source
287-
:failure-data nil
288-
:source source)))))
357+
(or (gethash (name source) systems)
358+
(setf (gethash (name source) systems)
359+
(make-instance 'failing-source
360+
:failure-data nil
361+
:source source)))))
289362
(let ((table (make-hash-table :test 'eq))
290-
(systems (failing-systems))
291-
(report (make-instance 'failure-report
292-
:failure-data '())))
293-
(dolist (system systems)
294-
(let ((key (fsource (source system))))
295-
(push system (gethash key table))))
296-
(maphash (lambda (failing-source failing-systems)
297-
(setf (failure-data failing-source) failing-systems)
298-
(push failing-source (failure-data report)))
299-
table)
300-
report))))
363+
(systems (failing-systems))
364+
(report (make-instance 'failure-report
365+
:failure-data '())))
366+
(dolist (system systems)
367+
(let ((key (fsource (source system))))
368+
(push system (gethash key table))))
369+
(maphash (lambda (failing-source failing-systems)
370+
(setf (failure-data failing-source) failing-systems)
371+
(push failing-source (failure-data report)))
372+
table)
373+
report))))
301374

302375
(defmethod failure-data ((object (eql t)))
303376
(failure-log-failure-report))
304377

305378
(defparameter *log-lines-that-are-boring*
306379
(mapcar 'ppcre:create-scanner
307-
'("^WARNING:")))
380+
'("^WARNING:")))
308381

309382
(defparameter *log-lines-to-highlight*
310383
(mapcar 'ppcre:create-scanner
311384
'("^; caught (WARNING|ERROR):"
312385
" READ error during"
313-
"^Backtrace for"
386+
"^Backtrace for"
314387
"^Unhandled")))
315388

316389
(defparameter *failure-log-reconstitution-patterns*
317-
'(("(The ANSI Standard, Section )([0-9.]*)"
390+
'(("(^.*The ANSI Standard, Section )([0-9.]*)"
318391
0 "<a href='http://l1sp.org/cl/" 1 "'>" 1 "</a>")))
319392

320393
(defun failure-log-reconstitute-line (line)
@@ -363,18 +436,25 @@ source is found that matches the filename, return nil."
363436
(loop for line = (read-line log-stream nil)
364437
while line
365438
do
366-
(setf line (cl-who:escape-string line))
367-
(cond ((highlighted-log-line-p line)
368-
(write-string "<strong>" stream)
369-
(write-string line stream)
370-
(write-string "</strong>" stream)
371-
(terpri stream))
372-
((boring-log-line-p line)
373-
(format stream "<span class='boring'>~A~%</span>" line))
374-
(t
375-
(write-line line stream)))))
439+
(setf line (failure-log-reconstitute-line (cl-who:escape-string line)))
440+
(let ((upstream-info (parse-vcs-source-log-line line)))
441+
(when upstream-info
442+
(destructuring-bind (&key source path path-bounds line-number &allow-other-keys)
443+
upstream-info
444+
(let ((link (source-file-link source path line-number)))
445+
(when link
446+
(setf line (link-subseq line link path-bounds)))))))
447+
(cond ((highlighted-log-line-p line)
448+
(write-string "<strong>" stream)
449+
(write-string line stream)
450+
(write-string "</strong>" stream)
451+
(terpri stream))
452+
((boring-log-line-p line)
453+
(format stream "<span class='boring'>~A~%</span>" line))
454+
(t
455+
(write-line line stream))))
376456
(format stream "</pre>")
377-
(format stream "</div>~%"))
457+
(format stream "</div>~%")))
378458

379459
(defmethod write-html-failure-report-content ((source failing-source) stream)
380460
(dolist (system (failure-data source))

0 commit comments

Comments
 (0)