;;======================================================================
;; Copyright 2006-2013, Matthew Welland.
;;
;; This file is part of Megatest.
;;
;; Megatest is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; Megatest is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Megatest. If not, see <http://www.gnu.org/licenses/>.
;;
;;======================================================================
;; summarize test in to a file test-summary.html in the test directory
;;
(define (tests:summarize-test run-id test-id)
(let* ((test-dat (rmt:get-test-info-by-id run-id test-id))
(out-dir (db:test-get-rundir test-dat))
(out-file (conc out-dir "/test-summary.html")))
;; first verify we are able to write the output file
(if (not (file-writable? out-dir))
(debug:print 0 *default-log-port* "ERROR: cannot write test-summary.html to " out-dir)
(let* (;; (steps-dat (rmt:get-steps-for-test run-id test-id))
(test-name (db:test-get-testname test-dat))
(item-path (db:test-get-item-path test-dat))
(full-name (db:test-make-full-name test-name item-path))
(oup (open-output-file out-file))
(status (db:test-get-status test-dat))
(color (common:get-color-from-status status))
(logf (db:test-get-final_logf test-dat))
(steps-dat (tests:get-compressed-steps run-id test-id)))
;; (dcommon:get-compressed-steps #f 1 30045)
;; (#("wasting_time" "23:36:13" "23:36:21" "0" "8.0s" "wasting_time.log"))
(s:output-new
oup
(s:html
(s:title "Summary for " full-name)
(s:body
(s:h2 "Summary for " full-name)
(s:table 'cellspacing "0" 'border "1"
(s:tr (s:td "run id") (s:td (db:test-get-run_id test-dat))
(s:td "test id") (s:td (db:test-get-id test-dat)))
(s:tr (s:td "testname") (s:td test-name)
(s:td "itempath") (s:td item-path))
(s:tr (s:td "state") (s:td (db:test-get-state test-dat))
(s:td "status") (s:td (s:a 'href logf (s:font 'color color status))))
(s:tr (s:td "TestDate") (s:td (seconds->work-week/day-time
(db:test-get-event_time test-dat)))
(s:td "Duration") (s:td (seconds->hr-min-sec (db:test-get-run_duration test-dat)))))
(s:h3 "Log files")
(s:table
'cellspacing "0" 'border "1"
(s:tr (s:td "Final log")(s:td (s:a 'href logf logf))))
(s:table
'cellspacing "0" 'border "1"
(s:tr (s:td "Step Name")(s:td "Start")(s:td "End")(s:td "Status")(s:td "Duration")(s:td "Log File"))
(map (lambda (step-dat)
(s:tr (s:td (tdb:steps-table-get-stepname step-dat))
(s:td (tdb:steps-table-get-start step-dat))
(s:td (tdb:steps-table-get-end step-dat))
(s:td (tdb:steps-table-get-status step-dat))
(s:td (tdb:steps-table-get-runtime step-dat))
(s:td (let ((step-log (tdb:steps-table-get-log-file step-dat)))
(s:a 'href step-log step-log)))))
steps-dat))
)))
(close-output-port oup)))))
;; for each test:
;;
(define (tests:filter-non-runnable run-id testkeynames testrecordshash)
(let ((runnables '()))
(for-each
(lambda (testkeyname)
(let* ((test-record (hash-table-ref testrecordshash testkeyname))
(test-name (tests:testqueue-get-testname test-record))
(itemdat (tests:testqueue-get-itemdat test-record))
(item-path (tests:testqueue-get-item_path test-record))
(waitons (tests:testqueue-get-waitons test-record))
(keep-test #t)
(test-id (rmt:get-test-id run-id test-name item-path))
(tdat (rmt:get-testinfo-state-status run-id test-id))) ;; (cdb:get-test-info-by-id *runremote* test-id)))
(if tdat
(begin
;; Look at the test state and status
(if (or (and (member (db:test-get-status tdat)
'("PASS" "WARN" "WAIVED" "CHECK" "SKIP"))
(equal? (db:test-get-state tdat) "COMPLETED"))
(member (db:test-get-state tdat)
'("INCOMPLETE" "KILLED")))
(set! keep-test #f))
;; examine waitons for any fails. If it is FAIL or INCOMPLETE then eliminate this test
;; from the runnable list
(if keep-test
(for-each (lambda (waiton)
;; for now we are waiting only on the parent test
(let* ((parent-test-id (rmt:get-test-id run-id waiton ""))
(wtdat (rmt:get-testinfo-state-status run-id test-id))) ;; (cdb:get-test-info-by-id *runremote* test-id)))
(if (or (and (equal? (db:test-get-state wtdat) "COMPLETED")
(member (db:test-get-status wtdat) '("FAIL" "ABORT")))
(member (db:test-get-status wtdat) '("KILLED"))
(member (db:test-get-state wtdat) '("INCOMPETE")))
;; (if (or (member (db:test-get-status wtdat)
;; '("FAIL" "KILLED"))
;; (member (db:test-get-state wtdat)
;; '("INCOMPETE")))
(set! keep-test #f)))) ;; no point in running this one again
waitons))))
(if keep-test (set! runnables (cons testkeyname runnables)))))
testkeynames)
runnables))
;;======================================================================
;; html output from server
;;======================================================================
(define (tests:dynamic-dboard page)
;(define (tests:create-html-tree o)
(let* (
;(page "1")
(linktree (common:get-linktree))
(area-name (common:get-testsuite-name))
(keys (rmt:get-keys))
(numkeys (length keys))
(targtweaked (make-list numkeys "%"))
(target-patt (string-join targtweaked "/"))
(total-runs (rmt:get-num-runs "%"))
(pg-size 10)
(pg (if (equal? page #f)
0
(- (string->number page) 1)))
(get-prev-links (lambda (pg linktree)
(debug:print-info 0 *default-log-port* "val: " (- 1 pg))
(let* ((link (if (not (eq? pg 0))
(s:a "<<prev " 'href (conc "dashboard?page=" pg ))
(s:a "" 'href (conc "dashboard?page=" pg)))))
link)))
(get-next-links (lambda (pg linktree total-runs)
(debug:print-info 0 *default-log-port* "val: " pg)
(debug:print-info 0 *default-log-port* "val: " total-runs " size" pg-size)
(let* ((link (if (> total-runs (+ 10 (* pg pg-size)))
(s:a "next>> " 'href (conc "dashboard?page=" (+ pg 2) ))
(s:a "" 'href (conc "dashboard?page=" pg )))))
link)))
(html-body (tests:dashboard-body pg pg-size keys numkeys total-runs linktree area-name get-prev-links get-next-links #t "%" target-patt))) ;; update tis function
html-body))
(define (tests:create-html-summary outf)
(let* ((lockfile (conc outf ".lock"))
(linktree (common:get-linktree))
(keys (rmt:get-keys))
(area-name (common:get-testsuite-name))
(run-patt (or (args:get-arg "-run-patt")
(args:get-arg "-runname")
"%"))
(target (or (args:get-arg "-target-patt")
(args:get-arg "-target")
"%"))
(targlist (string-split target "/"))
(numkeys (length keys))
(numtarg (length targlist))
(targtweaked (if (> numkeys numtarg)
(append targlist (make-list (- numkeys numtarg) "%"))
targlist))
(target-patt (string-join targtweaked "/")))
(if (common:simple-file-lock lockfile)
(begin
(let* (;(runsdat1 (rmt:get-runs run-patt #f #f (map (lambda (x)(list x "%")) keys)))
(runsdat (rmt:get-runs-by-patt keys run-patt target-patt #f #f #f 0))
(runs (vector-ref runsdat 1))
(header (vector-ref runsdat 0))
(oup (open-output-file (or outf (conc linktree "/targets.html"))))
(target-hash (test:create-target-hash runs header (length keys))))
(test:create-target-html target-hash oup area-name linktree)
(test:create-run-html runs area-name linktree (length keys) header))
(common:simple-file-release-lock lockfile))
#f)))
(define (test:get-test-hash test-data)
(let ((resh (make-hash-table)))
(map (lambda (test)
(let* ((test-name (vector-ref test 2))
(test-html-path (if (file-exists? (conc (vector-ref test 10) "/test-summary.html"))
(conc (vector-ref test 10) "/test-summary.html" )
(conc (vector-ref test 10) "/" (vector-ref test 13))))
(test-item (vector-ref test 11))
(test-status (vector-ref test 4)))
(if (not (hash-table-ref/default resh test-item #f))
(hash-table-set! resh test-item (make-hash-table)))
(hash-table-set! (hash-table-ref/default resh test-item #f) test-name (list test-status test-html-path))))
test-data)
resh))
(define (test:get-data->b-keys ordered-data a-keys)
(delete-duplicates
(sort (apply
append
(map (lambda (sub-key)
(let ((subdat (hash-table-ref ordered-data sub-key)))
(hash-table-keys subdat)))
a-keys))
string>=?)))
(define (test:create-run-html runs area-name linktree numkeys header)
(map (lambda (run)
(let* ((target (string-join (take (vector->list run) numkeys) "/"))
(run-name (db:get-value-by-header run header "runname"))
(run-time (seconds->work-week/day-time (db:get-value-by-header run header "event_time")))
(oup (if (file-exists? (conc linktree "/" target "/" run-name))
(open-output-file (conc linktree "/" target "/" run-name "/run.html"))
#f))
(run-id (db:get-value-by-header run header "id"))
(test-data (rmt:get-tests-for-run
run-id
"%" ;; testnamepatt
'() ;; states
'() ;; statuses
#f ;; offset
#f ;; num-to-get
#f ;; hide/not-hide
#f ;; sort-by
#f ;; sort-order
#f ;; 'shortlist ;; qrytype
0 ;; last update
#f))
(item-test-hash (test:get-test-hash test-data))
(items (hash-table-keys item-test-hash))
(test-names (test:get-data->b-keys item-test-hash items)))
(if oup
(begin
(s:output-new
oup
(s:html tests:css-jscript-block (tests:css-jscript-block-cond #f)
(s:title "Runs View " run-name)
(s:body
(s:h1 "Runs View " )
(s:h3 "Target" target)
(s:p
(s:b "Run name" ) run-name)
(s:p
(s:b "Run Date" ) run-time)
(s:table 'border 1 'cellspacing 0
(s:tr
(s:th "Items")
(map (lambda (test)
(s:th test))
test-names))
(map (lambda (item)
(let* ((test-hash (hash-table-ref/default item-test-hash item #f)))
(if test-hash
(begin
(s:tr
(s:td 'class "test" item)
(map (lambda (test)
(let* ((test-details (hash-table-ref/default test-hash test #f))
(status (if test-details
(car test-details)))
(link (if test-details
(string-substitute (conc linktree "/" target "/" run-name "/") "" (cadr test-details) "-"))))
(if test-details
(s:td 'class status
(s:a 'class "link" 'href link status ))
(s:td ""))))
test-names))))))
(sort items string<=?))))))
(close-output-port oup))
(debug:print-info 0 "Skip: Dirctory structure " linktree "/" target "/" run-name " does not exist. Megatest will not create run.html"))))
runs))
(define (test:create-target-hash runs header numkeys)
(let ((resh (make-hash-table)))
(for-each
(lambda (run)
(let* ((run-name (db:get-value-by-header run header "runname"))
(target (string-join (take (vector->list run) numkeys) "/"))
(run-list (hash-table-ref/default resh target #f)))
(if (not run-list)
(hash-table-set! resh target (list run-name))
(hash-table-set! resh target (cons run-name run-list)))))
runs)
resh))
(define (test:get-max-run-cnt target-hash targets)
(let* ((cnt 0 ))
(map (lambda (target)
(let* ((runs (hash-table-ref/default target-hash target #f))
(run-length (if runs
(length runs)
0)))
(if (< cnt run-length)
(set! cnt run-length))))
targets)
cnt))
(define (test:pad-runs target-hash targets max-row-length)
(map (lambda (target)
(let loop ((run-list (hash-table-ref/default target-hash target #f)))
(if (< (length run-list) max-row-length)
(begin
(hash-table-set! target-hash target (cons "" run-list))
(loop (hash-table-ref/default target-hash target #f) )))))
targets)
target-hash)
(define (test:create-target-html target-hash oup area-name linktree)
(let* ((targets (hash-table-keys target-hash))
(max-row-length (test:get-max-run-cnt target-hash targets))
(pad-runs-hash (test:pad-runs target-hash targets max-row-length)))
(s:output-new
oup
(s:html tests:css-jscript-block (tests:css-jscript-block-cond #f)
(s:title "Target View " area-name)
(s:body
(s:h1 "Target View " area-name)
(s:table 'id "LinkedList1" 'border "1" 'cellspacing 0
(s:tr 'class "something"
(s:th "Target")
(s:th 'colspan max-row-length "Runs"))
(let* ((tbl (map (lambda (target)
(s:tr
(s:td 'class "test" target)
(let* ((runs (hash-table-ref/default target-hash target #f))
(rest-row (map (lambda (run)
(if (equal? run "")
(s:td run)
(if (file-exists?(conc linktree "/" target "/" run ))
(begin
(s:td
(s:a 'href (conc target "/" run "/run.html") run))))))
(reverse runs))))
rest-row)))
targets)))
tbl)))))
(close-output-port oup)))
(define (tests:create-html-tree-old outf)
(let* ((lockfile (conc outf ".lock"))
(runs-to-process '()))
(if (common:simple-file-lock lockfile)
(let* ((linktree (common:get-linktree))
(oup (open-output-file (or outf (conc linktree "/runs-index.html"))))
(area-name (common:get-testsuite-name))
(keys (rmt:get-keys))
(numkeys (length keys))
(runsdat (rmt:get-runs "%" #f #f (map (lambda (x)(list x "%")) keys)))
(header (vector-ref runsdat 0))
(runs (vector-ref runsdat 1))
(runtreedat (map (lambda (x)
(tests:run-record->test-path x numkeys))
runs))
(runs-htree (common:list->htree runtreedat)))
(set! runs-to-process runs)
(s:output-new
oup
(s:html tests:css-jscript-block
(s:title "Summary for " area-name)
(s:body 'onload "addEvents();"
(s:h1 "Summary for " area-name)
;; top list
(s:ul 'id "LinkedList1" 'class "LinkedList"
(s:li
"Runs"
(common:htree->html runs-htree
'()
(lambda (x p)
(let* ((targ-path (string-intersperse p "/"))
(full-path (conc linktree "/" targ-path))
(run-name (car (reverse p))))
(if (and (common:file-exists? full-path)
(directory? full-path)
(file-writable? full-path))
(s:a run-name 'href (conc targ-path "/run-summary.html"))
(begin
(debug:print 0 *default-log-port* "INFO: Can't create " targ-path "/run-summary.html")
(conc run-name " (Not able to create summary at " targ-path ")")))))))))))
(close-output-port oup)
(common:simple-file-release-lock lockfile)
(for-each
(lambda (run)
(let* ((test-subpath (tests:run-record->test-path run numkeys))
(run-id (db:get-value-by-header run header "id"))
(run-dir (tests:run-record->test-path run numkeys))
(test-dats (rmt:get-tests-for-run
run-id
"%/" ;; testnamepatt
'() ;; states
'() ;; statuses
#f ;; offset
#f ;; num-to-get
#f ;; hide/not-hide
#f ;; sort-by
#f ;; sort-order
#f ;; 'shortlist ;; qrytype
0 ;; last update
#f))
(tests-tree-dat (map (lambda (test-dat)
;; (tests:run-record->test-path x numkeys))
(let* ((test-name (db:test-get-testname test-dat))
(item-path (db:test-get-item-path test-dat))
(full-name (db:test-make-full-name test-name item-path))
(path-parts (string-split full-name)))
path-parts))
test-dats))
(tests-htree (common:list->htree tests-tree-dat))
(html-dir (conc linktree "/" (string-intersperse run-dir "/")))
(html-path (conc html-dir "/run-summary.html"))
(oup (if (and (common:file-exists? html-dir)
(directory? html-dir)
(file-writable? html-dir))
(open-output-file html-path)
#f)))
;; (print "run-dir: " run-dir ", tests-tree-dat: " tests-tree-dat)
(if oup
(begin
(s:output-new
oup
(s:html tests:css-jscript-block
(s:title "Summary for " area-name)
(s:body 'onload "addEvents();"
(s:h1 "Summary for " (string-intersperse run-dir "/"))
;; top list
(s:ul 'id "LinkedList1" 'class "LinkedList"
(s:li
"Tests"
(common:htree->html tests-htree
'()
(lambda (x p)
(let* ((targ-path (string-intersperse p "/"))
(test-name (car p))
(item-path ;; (if (> (length p) 2) ;; test-name + run-name
(string-intersperse p "/"))
(full-targ (conc html-dir "/" targ-path))
(std-file (conc full-targ "/test-summary.html"))
(alt-file (conc full-targ "/megatest-rollup-" test-name ".html"))
(html-file (if (common:file-exists? alt-file)
alt-file
std-file))
(run-name (car (reverse p))))
(if (and (not (common:file-exists? full-targ))
(directory? full-targ)
(file-writable? full-targ))
(tests:summarize-test
run-id
(rmt:get-test-id run-id test-name item-path)))
(if (common:file-exists? full-targ)
(s:a run-name 'href html-file)
(begin
(debug:print 0 *default-log-port* "ERROR: can't access " full-targ)
(conc "No summary for " run-name)))))
))))))
(close-output-port oup)))))
runs)
#t)
#f)))
;;======================================================================
;; web pages
;;======================================================================
(define (http-transport:html-test-log $)
(let* ((run-id ($ 'runid))
(test-item ($ 'testname))
(parts (string-split test-item ":"))
(test-name (car parts))
(item-name (if (equal? (length parts) 1)
""
(cadr parts))))
;(print $)
(tests:get-test-log run-id test-name item-name)))
(define (http-transport:html-dboard $)
(let* ((page ($ 'page))
(oup (open-output-string))
(bdy "--------------------------")
(ret (tests:dynamic-dboard page)))
(s:output-new oup ret)
(close-output-port oup)
(set! bdy (get-output-string oup))
(conc "<h1>Dashboard</h1>" bdy "<br/> <br/> " )))
(define (http-transport:main-page)
(let ((linkpath (root-path)))
(conc "<head><h1>" (pathname-strip-directory *toppath*) "</h1></head>"
"<body>"
"Run area: " *toppath*
"<h2>Server Stats</h2>"
(http-transport:stats-table)
"<hr>"
(http-transport:runs linkpath)
"<hr>"
;; (http-transport:run-stats)
"</body>"
)))
(define (http-transport:stats-table)
(mutex-lock! *heartbeat-mutex*)
(let ((res
(conc "<table>"
;; "<tr><td>Max cached queries</td> <td>" *max-cache-size* "</td></tr>"
"<tr><td>Number of cached writes</td> <td>" *number-of-writes* "</td></tr>"
"<tr><td>Average cached write time</td> <td>" (if (eq? *number-of-writes* 0)
"n/a (no writes)"
(/ *writes-total-delay*
*number-of-writes*))
" ms</td></tr>"
"<tr><td>Number non-cached queries</td> <td>" *number-non-write-queries* "</td></tr>"
;; "<tr><td>Average non-cached time</td> <td>" (if (eq? *number-non-write-queries* 0)
;; "n/a (no queries)"
;; (/ *total-non-write-delay*
;; *number-non-write-queries*))
" ms</td></tr>"
"<tr><td>Last access</td><td>" (seconds->time-string *db-last-access*) "</td></tr>"
"</table>")))
(mutex-unlock! *heartbeat-mutex*)
res))
(define (http-transport:runs linkpath)
(conc "<h3>Runs</h3>"
(string-intersperse
(let ((files (map pathname-strip-directory (glob (conc linkpath "/*")))))
(map (lambda (p)
(conc "<a href=\"" p "\">" p "</a><br>"))
files))
" ")))
#;(define (http-transport:run-stats)
(let ((stats (open-run-close db:get-running-stats #f)))
(conc "<table>"
(string-intersperse
(map (lambda (stat)
(conc "<tr><td>" (car stat) "</td><td>" (cadr stat) "</td></tr>"))
stats)
" ")
"</table>")))
(define (tests:get-test-log run-id test-name item-name)
(let* ((test-data (rmt:get-tests-for-run
(string->number run-id)
test-name ;; testnamepatt
'() ;; states
'() ;; statuses
#f ;; offset
#f ;; num-to-get
#f ;; hide/not-hide
#f ;; sort-by
#f ;; sort-order
#f ;; 'shortlist ;; qrytype
0 ;; last update
#f))
(path "")
(found 0))
(debug:print-info 0 *default-log-port* "found: " found )
(let loop ((hed (car test-data))
(tal (cdr test-data)))
(debug:print-info 0 *default-log-port* "item: " (vector-ref hed 11) (vector-ref hed 10) "/" (vector-ref hed 13))
(if (equal? (vector-ref hed 11) item-name)
(begin
(set! found 1)
(set! path (conc (vector-ref hed 10) "/" (vector-ref hed 13)))))
(if (and (not (null? tal)) (equal? found 0))
(loop (car tal)(cdr tal))))
(if (equal? path "")
"<H2>Data not found</H2>"
(string-join (tests:readlines path) "\n"))))
;;===============================================
;; Java script
;;===============================================
(define (http-transport:show-jquery)
(let* ((data (tests:readlines *java-script-lib*)))
(string-join data "\n")))