Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -1,12 +1,13 @@ PREFIX=. +CSCOPTS= SRCFILES = common.scm items.scm launch.scm \ ods.scm runconfig.scm server.scm configf.scm \ db.scm keys.scm margs.scm megatest-version.scm \ - process.scm runs.scm tasks.scm + process.scm runs.scm tasks.scm tests.scm GUISRCF = dashboard.scm dashboard-tests.scm dashboard-guimonitor.scm OFILES = $(SRCFILES:%.scm=%.o) GOFILES = $(GUISRCF:%.scm=%.o) @@ -14,25 +15,26 @@ HELPERS=$(addprefix $(PREFIX)/bin/,mt_laststep mt_runstep mt_ezstep) all : megatest dboard megatest: $(OFILES) megatest.o - csc $(OFILES) megatest.o -o megatest + csc $(CSCOPTS) $(OFILES) megatest.o -o megatest dboard : $(OFILES) $(GOFILES) csc $(OFILES) $(GOFILES) -o dboard # Special dependencies for the includes -db.o launch.o runs.o dashboard-tests.o dashboard-guimonitor.o monitor.o dashboard.o megatest.o : db_records.scm -runs.o dashboard.o dashboard-tests.o : run_records.scm -keys.o db.o runs.o launch.o megatest.o : key_records.scm -tasks.o dashboard-tasks.o : task_records.scm +tests.o db.o launch.o runs.o dashboard-tests.o dashboard-guimonitor.o monitor.o dashboard.o megatest.o : db_records.scm +tests.o runs.o dashboard.o dashboard-tests.o : run_records.scm +db.o ezsteps.o keys.o launch.o megatest.o monitor.o runs-for-ref.o runs.o tests.o : key_records.scm +tests.o tasks.o dashboard-tasks.o : task_records.scm +runs.o : test_records.scm $(OFILES) $(GOFILES) : common_records.scm %.o : %.scm - csc -c $< + csc $(CSCOPTS) -c $< $(PREFIX)/bin/megatest : megatest @echo Installing to PREFIX=$(PREFIX) cp megatest $(PREFIX)/bin/megatest Index: configf.scm ================================================================== --- configf.scm +++ configf.scm @@ -76,22 +76,27 @@ (read-config include-file res allow-system environ-patt: environ-patt) (loop (read-line inp) curr-section-name #f #f))) (section-rx ( x section-name ) (loop (read-line inp) section-name #f #f)) (key-sys-pr ( x key cmd ) (if allow-system (let ((alist (hash-table-ref/default res curr-section-name '())) - (val (let* ((cmdres (cmd-run->list cmd)) - (status (cadr cmdres)) - (res (car cmdres))) - (if (not (eq? status 0)) - (begin - (debug:print 0 "ERROR: problem with " inl ", return code " status) - (exit 1))) - (if (null? res) - "" - (string-intersperse res " "))))) + (val-proc (lambda () + (let* ((cmdres (cmd-run->list cmd)) + (status (cadr cmdres)) + (res (car cmdres))) + (if (not (eq? status 0)) + (begin + (debug:print 0 "ERROR: problem with " inl ", return code " status) + (exit 1))) + (if (null? res) + "" + (string-intersperse res " ")))))) (hash-table-set! res curr-section-name - (config:assoc-safe-add alist key val)) + (config:assoc-safe-add alist + key + (if (eq? allow-system 'return-procs) + val-proc + (val-proc)))) (loop (read-line inp) curr-section-name #f #f)) (loop (read-line inp) curr-section-name #f #f))) (key-val-pr ( x key val ) (let* ((alist (hash-table-ref/default res curr-section-name '())) (envar (and environ-patt (string-match (regexp environ-patt) curr-section-name))) (realval (if envar Index: dashboard.scm ================================================================== --- dashboard.scm +++ dashboard.scm @@ -453,11 +453,12 @@ ;; (print "Setting search for " x " to " val) (hash-table-set! *searchpatts* x val)) (define (mark-for-update) (set! *last-db-update-time* 0) - (set! *delayed-update* 1)) + (set! *delayed-update* 1) + ) (define (make-dashboard-buttons nruns ntests keynames) (let* ((nkeys (length keynames)) (runsvec (make-vector nruns)) (header (make-vector nruns)) Index: db.scm ================================================================== --- db.scm +++ db.scm @@ -38,12 +38,12 @@ (keystr (keys->keystr keys)) (fieldstr (keys->key/field keys))) (for-each (lambda (key) (let ((keyn (vector-ref key 0))) (if (member (string-downcase keyn) - (list "runname" "state" "status" "owner" "event_time" "comment" "fail_count" - "pass_count")) + (list "runname" "state" "status" "owner" "event_time" "comment" "fail_count" + "pass_count")) (begin (print "ERROR: your key cannot be named " keyn " as this conflicts with the same named field in the runs table") (system (conc "rm -f " dbpath)) (exit 1))))) keys) @@ -51,24 +51,24 @@ (sqlite3:execute db "CREATE TABLE IF NOT EXISTS keys (id INTEGER PRIMARY KEY, fieldname TEXT, fieldtype TEXT, CONSTRAINT keyconstraint UNIQUE (fieldname));") (for-each (lambda (key) (sqlite3:execute db "INSERT INTO keys (fieldname,fieldtype) VALUES (?,?);" (key:get-fieldname key)(key:get-fieldtype key))) keys) (sqlite3:execute db (conc - "CREATE TABLE IF NOT EXISTS runs (id INTEGER PRIMARY KEY, " - fieldstr (if havekeys "," "") - "runname TEXT," - "state TEXT DEFAULT ''," - "status TEXT DEFAULT ''," - "owner TEXT DEFAULT ''," - "event_time TIMESTAMP," - "comment TEXT DEFAULT ''," - "fail_count INTEGER DEFAULT 0," - "pass_count INTEGER DEFAULT 0," - "CONSTRAINT runsconstraint UNIQUE (runname" (if havekeys "," "") keystr "));")) + "CREATE TABLE IF NOT EXISTS runs (id INTEGER PRIMARY KEY, " + fieldstr (if havekeys "," "") + "runname TEXT," + "state TEXT DEFAULT ''," + "status TEXT DEFAULT ''," + "owner TEXT DEFAULT ''," + "event_time TIMESTAMP," + "comment TEXT DEFAULT ''," + "fail_count INTEGER DEFAULT 0," + "pass_count INTEGER DEFAULT 0," + "CONSTRAINT runsconstraint UNIQUE (runname" (if havekeys "," "") keystr "));")) (sqlite3:execute db (conc "CREATE INDEX runs_index ON runs (runname" (if havekeys "," "") keystr ");")) (sqlite3:execute db - "CREATE TABLE IF NOT EXISTS tests + "CREATE TABLE IF NOT EXISTS tests (id INTEGER PRIMARY KEY, run_id INTEGER, testname TEXT, host TEXT DEFAULT 'n/a', cpuload REAL DEFAULT -1, @@ -113,10 +113,11 @@ reviewed TIMESTAMP, iterated TEXT DEFAULT '', avg_runtime REAL, avg_disk REAL, tags TEXT DEFAULT '', + jobgroup TEXT DEFAULT 'default', CONSTRAINT test_meta_constraint UNIQUE (testname));") (sqlite3:execute db "CREATE TABLE IF NOT EXISTS test_data (id INTEGER PRIMARY KEY, test_id INTEGER, category TEXT DEFAULT '', variable TEXT, @@ -173,17 +174,17 @@ (sqlite3:execute db "DROP TABLE IF EXISTS metadat;") (sqlite3:execute db "CREATE TABLE IF NOT EXISTS metadat (id INTEGER, var TEXT, val TEXT, CONSTRAINT metadat_constraint UNIQUE (var));") (db:set-var db "MEGATEST_VERSION" 1.21) ;; set before, just in case the changes are already applied (sqlite3:execute db test-meta-def) - ;(for-each - ; (lambda (stmt) - ; (sqlite3:execute db stmt)) - ; (list - ; "ALTER TABLE tests ADD COLUMN first_err TEXT;" - ; "ALTER TABLE tests ADD COLUMN first_warn TEXT;" - ; )) + ;(for-each + ; (lambda (stmt) + ; (sqlite3:execute db stmt)) + ; (list + ; "ALTER TABLE tests ADD COLUMN first_err TEXT;" + ; "ALTER TABLE tests ADD COLUMN first_warn TEXT;" + ; )) (patch-db)) ((< mver 1.24) (db:set-var db "MEGATEST_VERSION" 1.24) (sqlite3:execute db "DROP TABLE IF EXISTS test_data;") (sqlite3:execute db "DROP TABLE IF EXISTS test_meta;") @@ -207,10 +208,13 @@ (patch-db)) ((< mver 1.29) (db:set-var db "MEGATEST_VERSION" 1.29) (sqlite3:execute db "ALTER TABLE test_steps ADD COLUMN logfile TEXT DEFAULT '';") (sqlite3:execute db "ALTER TABLE tests ADD COLUMN shortdir TEXT DEFAULT '';")) + ((< mver 1.36) + (db:set-var db "MEGATEST_VERSION" 1.36) + (sqlite3:execute db "ALTER TABLER test_meta ADD COLUMN jobgroup TEXT DEFAULT 'default';")) ((< mver megatest-version) (db:set-var db "MEGATEST_VERSION" megatest-version)))))) ;;====================================================================== ;; meta get and set vars @@ -256,11 +260,11 @@ (tal (cdr header)) (n 0)) (if (equal? hed field) (vector-ref row n) (if (null? tal) #f (loop (car tal)(cdr tal)(+ n 1))))))) - + ;;====================================================================== ;; R U N S ;;====================================================================== (define (runs:get-std-run-fields keys remfields) @@ -363,12 +367,12 @@ (let ((res '()) (states-str (conc "('" (string-intersperse states "','") "')")) (statuses-str (conc "('" (string-intersperse statuses "','") "')")) ) (sqlite3:for-each-row - (lambda (id run-id testname state status event-time host cpuload diskfree uname rundir item-path run-duration final-logf comment) - (set! res (cons (vector id run-id testname state status event-time host cpuload diskfree uname rundir item-path run-duration final-logf comment) res))) + (lambda (a . b) ;; id run-id testname state status event-time host cpuload diskfree uname rundir item-path run-duration final-logf comment) + (set! res (cons (apply vector a b) res))) ;; id run-id testname state status event-time host cpuload diskfree uname rundir item-path run-duration final-logf comment) res))) db (conc "SELECT id,run_id,testname,state,status,event_time,host,cpuload,diskfree,uname,rundir,item_path,run_duration,final_logf,comment " " FROM tests WHERE run_id=? AND testname like ? AND item_path LIKE ? " " AND NOT (state in " states-str " AND status IN " statuses-str ") " ;; " ORDER BY id DESC;" @@ -393,13 +397,13 @@ ;; use currstate = #f and or currstatus = #f to apply to any state or status respectively ;; WARNING: SQL injection risk (define (db:set-tests-state-status db run-id testnames currstate currstatus newstate newstatus) (for-each (lambda (testname) (let ((qry (conc "UPDATE tests SET state=?,status=? WHERE " - (if currstate (conc "state='" currstate "' AND ") "") - (if currstatus (conc "status='" currstatus "' AND ") "") - " run_id=? AND testname=? AND NOT (item_path='' AND testname in (SELECT DISTINCT testname FROM tests WHERE testname=? AND item_path != ''));"))) + (if currstate (conc "state='" currstate "' AND ") "") + (if currstatus (conc "status='" currstatus "' AND ") "") + " run_id=? AND testname=? AND NOT (item_path='' AND testname in (SELECT DISTINCT testname FROM tests WHERE testname=? AND item_path != ''));"))) ;;(debug:print 0 "QRY: " qry) (sqlite3:execute db qry run-id newstate newstatus testname testname))) testnames)) (define (db:delete-tests-in-state db run-id state) @@ -416,10 +420,23 @@ (lambda (count) (set! res count)) db "SELECT count(id) FROM tests WHERE state = 'RUNNING' OR state = 'LAUNCHED' OR state = 'REMOTEHOSTSTART';") res)) + +(define (db:get-count-tests-running-in-jobgroup db jobgroup) + (if (not jobgroup) + 0 ;; + (let ((res 0)) + (sqlite3:for-each-row + (lambda (count) + (set! res count)) + db + "SELECT count(id) FROM tests WHERE state = 'RUNNING' OR state = 'LAUNCHED' OR state = 'REMOTEHOSTSTART' + AND testname in (SELECT testname FROM test_meta WHERE jobgroup=?;" + jobgroup) + res))) ;; done with run when: ;; 0 tests in LAUNCHED, NOT_STARTED, REMOTEHOSTSTART, RUNNING (define (db:estimated-tests-remaining db run-id) (let ((res 0)) @@ -455,18 +472,18 @@ (define (db:test-set-comment db run-id testname item-path comment) (sqlite3:execute db "UPDATE tests SET comment=? WHERE run_id=? AND testname=? AND item_path=?;" - comment run-id testname item-path)) + comment run-id testname item-path)) ;; (define (db:test-set-rundir! db run-id testname item-path rundir) (sqlite3:execute db "UPDATE tests SET rundir=? WHERE run_id=? AND testname=? AND item_path=?;" - rundir run-id testname item-path)) + rundir run-id testname item-path)) ;;====================================================================== ;; Tests meta data ;;====================================================================== @@ -519,13 +536,13 @@ (if (and (or (not expected)(equal? expected "")) (or (not tol) (equal? expected "")) (or (not units) (equal? expected ""))) (let-values (((new-expected new-tol new-units)(db:get-prev-tol-for-test db test-id category variable))) - (set! expected new-expected) - (set! tol new-tol) - (set! units new-units))) + (set! expected new-expected) + (set! tol new-tol) + (set! units new-units))) (debug:print 4 "AFTER: category: " category " variable: " variable " value: " value ", expected: " expected " tol: " tol " units: " units " status: " status " comment: " comment) ;; calculate status if NOT specified (if (and (not status)(number? expected)(number? value)) ;; need expected and value to be numbers @@ -543,11 +560,11 @@ ((<=) (if (<= value expected) "pass" "fail")) (else (conc "ERROR: bad tol comparator " tol)))))) (debug:print 4 "AFTER2: category: " category " variable: " variable " value: " value ", expected: " expected " tol: " tol " units: " units " status: " status " comment: " comment) (sqlite3:execute db "INSERT OR REPLACE INTO test_data (test_id,category,variable,value,expected,tol,units,comment,status) VALUES (?,?,?,?,?,?,?,?,?);" - test-id category variable value expected tol units (if comment comment "") status))) + test-id category variable value expected tol units (if comment comment "") status))) csvlist))) ;; get a list of test_data records matching categorypatt (define (db:read-test-data db test-id categorypatt) (let ((res '())) @@ -571,11 +588,11 @@ (debug:print 4 lin) (db:csv->test-data db test-id lin) (loop (read-line)))))) ;; roll up the current results. (db:test-data-rollup db test-id))) - + ;; WARNING: Do NOT call this for the parent test on an iterated test ;; Roll up test_data pass/fail results ;; look at the test_data status field, ;; if all are pass (any case) and the test status is PASS or NULL or '' then set test status to PASS. ;; if one or more are fail (any case) then set test status to PASS, non "pass" or "fail" are ignored @@ -607,11 +624,11 @@ ;;====================================================================== ;; S T E P S ;;====================================================================== (define (db:step-get-time-as-string vec) - (seconds->time-string (db:step-get-event_time vec))) + (seconds->time-string (db:step-get-event_time vec))) ;; db-get-test-steps-for-run (define (db:get-steps-for-test db test-id) (let ((res '())) (sqlite3:for-each-row @@ -661,13 +678,13 @@ (seconds->hr-min-sec (- endt startt)) "-1"))) (if (> (string-length (db:step-get-logfile step)) 0) (vector-set! record 5 (db:step-get-logfile step)))) (else - (vector-set! record 2 (db:step-get-state step)) - (vector-set! record 3 (db:step-get-status step)) - (vector-set! record 4 (db:step-get-event_time step)))) + (vector-set! record 2 (db:step-get-state step)) + (vector-set! record 3 (db:step-get-status step)) + (vector-set! record 4 (db:step-get-event_time step)))) (hash-table-set! res (db:step-get-stepname step) record) (debug:print 6 "record(after) = " record "\nid: " (db:step-get-id step) "\nstepname: " (db:step-get-stepname step) "\nstate: " (db:step-get-state step) @@ -698,10 +715,56 @@ (set! result (cons waitontest-name result)))))) tests) (if (not ever-seen)(set! result (cons waitontest-name result))))) waiton) (delete-duplicates result)))) + +;; the new prereqs calculation, looks also at itempath if specified +;; all prereqs must be met: +;; if prereq test with itempath='' is COMPLETED and PASS, WARN, CHECK, or WAIVED then prereq is met +;; if prereq test with itempath=ref-item-path and COMPLETED with PASS, WARN, CHECK, or WAIVED then prereq is met +(define (db:get-prereqs-not-met db run-id waitons ref-item-path) + (if (or (not waitons) + (null? waitons)) + '() + (let* ((unmet-pre-reqs '()) + (result '())) + (for-each + (lambda (waitontest-name) + ;; by getting the tests with matching name we are looking only at the matching test + ;; and related sub items + (let ((tests (db-get-tests-for-run db run-id waitontest-name #f '() '())) + (ever-seen #f) + (parent-waiton-met #f) + (item-waiton-met #f)) + (for-each + (lambda (test) + ;; (if (equal? waitontest-name (db:test-get-testname test)) ;; by defintion this had better be true ... + (let* ((state (db:test-get-state test)) + (status (db:test-get-status test)) + (item-path (db:test-get-item-path test)) + (is-completed (equal? state "COMPLETED")) + (is-ok (member status '("PASS" "WARN" "CHECK" "WAIVED"))) + (same-itempath (equal? ref-item-path item-path))) + (set! ever-seen #t) + (cond + ;; case 1, non-item (parent test) is + ((and (equal? item-path "") ;; this is the parent test + is-completed + is-ok) + (set! parent-waiton-met #t)) + ((and same-itempath + is-completed + is-ok) + (set! item-waiton-met #t))))) + tests) + (if (not (or parent-waiton-met item-waiton-met)) + (set! result (cons waitontest-name result))) + ;; if the test is not found then clearly the waiton is not met... + (if (not ever-seen)(set! result (cons waitontest-name result))))) + waitons) + (delete-duplicates result)))) ;;====================================================================== ;; Extract ods file from the db ;;====================================================================== @@ -741,45 +804,45 @@ (debug:print 2 "Using " tempdir " for constructing the ods file. keyqry: " keyqry " keystr: " keysstr " with keys: " (map cadr keypatt-alist)) ;; "Expected Value" ;; "Value Found" ;; "Tolerance" (apply sqlite3:for-each-row - (lambda (test-id . b) - (set! test-ids (cons test-id test-ids)) ;; test-id is now testname - (set! results (append results ;; note, drop the test-id - (list - (if pathmod - (let* ((vb (apply vector b)) - (keyvals (let loop ((i 0) - (res '())) - (if (>= i numkeys) - res - (loop (+ i 1) - (append res (list (vector-ref vb (+ i 2)))))))) - (runname (vector-ref vb 1)) - (testname (vector-ref vb (+ 2 numkeys))) - (item-path (vector-ref vb (+ 3 numkeys))) - (final-log (vector-ref vb (+ 7 numkeys))) - (run-dir (vector-ref vb (+ 18 numkeys))) - (log-fpath (conc run-dir "/" final-log))) ;; (string-intersperse keyvals "/") "/" testname "/" item-path "/" - (debug:print 4 "log: " log-fpath " exists: " (file-exists? log-fpath)) - (vector-set! vb (+ 7 numkeys) (if (file-exists? log-fpath) - (let ((newpath (conc pathmod "/" - (string-intersperse keyvals "/") - "/" runname "/" testname "/" - (if (string=? item-path "") "" (conc "/" item-path)) - final-log))) - ;; for now throw away newpath and use the log-fpath conc'd with pathmod - (set! newpath (conc pathmod log-fpath)) - (if windows (string-translate newpath "/" "\\") newpath)) - (if (> *verbosity* 1) - (conc final-log " not-found") - ""))) - (vector->list vb)) - b))))) - db - (conc "SELECT + (lambda (test-id . b) + (set! test-ids (cons test-id test-ids)) ;; test-id is now testname + (set! results (append results ;; note, drop the test-id + (list + (if pathmod + (let* ((vb (apply vector b)) + (keyvals (let loop ((i 0) + (res '())) + (if (>= i numkeys) + res + (loop (+ i 1) + (append res (list (vector-ref vb (+ i 2)))))))) + (runname (vector-ref vb 1)) + (testname (vector-ref vb (+ 2 numkeys))) + (item-path (vector-ref vb (+ 3 numkeys))) + (final-log (vector-ref vb (+ 7 numkeys))) + (run-dir (vector-ref vb (+ 18 numkeys))) + (log-fpath (conc run-dir "/" final-log))) ;; (string-intersperse keyvals "/") "/" testname "/" item-path "/" + (debug:print 4 "log: " log-fpath " exists: " (file-exists? log-fpath)) + (vector-set! vb (+ 7 numkeys) (if (file-exists? log-fpath) + (let ((newpath (conc pathmod "/" + (string-intersperse keyvals "/") + "/" runname "/" testname "/" + (if (string=? item-path "") "" (conc "/" item-path)) + final-log))) + ;; for now throw away newpath and use the log-fpath conc'd with pathmod + (set! newpath (conc pathmod log-fpath)) + (if windows (string-translate newpath "/" "\\") newpath)) + (if (> *verbosity* 1) + (conc final-log " not-found") + ""))) + (vector->list vb)) + b))))) + db + (conc "SELECT t.testname,r.id,runname," keysstr ",t.testname, t.item_path,tm.description,t.state,t.status, final_logf,run_duration, strftime('%m/%d/%Y %H:%M:%S',datetime(t.event_time,'unixepoch'),'localtime'), tm.tags,r.owner,t.comment, @@ -787,11 +850,11 @@ tm.owner,reviewed, diskfree,uname,rundir, host,cpuload FROM tests AS t INNER JOIN runs AS r ON t.run_id=r.id INNER JOIN test_meta AS tm ON tm.testname=t.testname WHERE runname LIKE ? AND " keyqry ";") - runspatt (map cadr keypatt-alist)) + runspatt (map cadr keypatt-alist)) (set! results (list (cons "Runs" results))) ;; now, for each test, collect the test_data info and add a new sheet (for-each (lambda (test-id) (let ((test-data (list testdata-header)) Index: docs/megatest.lyx ================================================================== --- docs/megatest.lyx +++ docs/megatest.lyx @@ -285,12 +285,11 @@ \begin_layout Standard \begin_inset VSpace medskip \end_inset This document is believed to be acurate at the time of writing but as with - any opensource project the source code itself is the final arbiter of the - softwares behaviour. + any opensource project the source code itself is the reference. It is the responsibility of the end user to validate that the code will perform as they expect. The author assumes no responsibility for any inaccuracies that this document may contain. In no event will Matthew Welland be liable for direct, indirect, special, @@ -299,14 +298,14 @@ damages. \end_layout \begin_layout Standard -This document is a snapshot in time and the Megatest software has likely - been changed since publication. - This document and the product that it describes may be improved at any - time, without notice or obligation. +This document is a snapshot in time and Megatest software has likely changed + since publication. + This document and Megatest may be improved at any time, without notice + or obligation. \end_layout \begin_layout Standard \begin_inset Newpage newpage @@ -459,18 +458,17 @@ Megatest design philosophy \end_layout \begin_layout Standard Megatest is intended to provide the minimum needed resources to make writing - a suite of tests for software, design engineering or process control (via - owlfs for example) without being specialized for any specific problem space. + a suite of tests and implementing continuous build for software, design + engineering or process control (via owlfs for example) without being specialize +d for any specific problem space. Megatest in of itself does not know what constitutes a PASS or FAIL of a test. In most cases megatest is best used in conjunction with logpro or a similar tool to parse, analyze and decide on the test outcome. - A call to megatest can then be made to record the result. - \end_layout \begin_layout Subsection Megatest architecture \end_layout @@ -1675,10 +1673,23 @@ \end_layout \begin_layout Subsection Monitor logic \end_layout + +\begin_layout Standard +Note: The monitor is usable but incomplete as of Megatest v1.31. + Click on the +\begin_inset Quotes eld +\end_inset + +Monitor +\begin_inset Quotes erd +\end_inset + + button on the dashboard to start the monitor and give it a try. +\end_layout \begin_layout Standard \begin_inset Graphics filename monitor-state-diagram.svg @@ -1692,10 +1703,16 @@ \end_layout \begin_layout Subsection Configuration file Syntax \end_layout + +\begin_layout Standard +Note: whitespace is preserved including at the end of line. + Ensure your entries only have whitespace at the end of line when needed + to avoid problems. +\end_layout \begin_layout Subsubsection Sections \end_layout @@ -2008,11 +2025,11 @@ megatest.config \end_layout \begin_layout Standard \begin_inset Tabular - + @@ -2344,10 +2361,58 @@ \begin_layout Plain Layout \end_layout +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +[jobgroups] +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +string of letters, numbers and underscore +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +number +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +no +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +Control number of jobs allowed to concurrently run in categories. + See [jobgroup] in testconfig +\end_layout + \end_inset @@ -2743,11 +2808,11 @@ testconfig file \end_layout \begin_layout Standard \begin_inset Tabular - + @@ -2894,54 +2959,150 @@ \end_inset - + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +jobgroup +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout + +\end_layout + +\end_inset + + + + \begin_inset Text \begin_layout Plain Layout [items] \end_layout \end_inset - + \begin_inset Text \begin_layout Plain Layout any valid \end_layout \end_inset - + \begin_inset Text \begin_layout Plain Layout list of values \end_layout \end_inset - + +\begin_inset Text + +\begin_layout Plain Layout +no +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +The test will be repeated once for each item with the variable name set + to the value. + If there is more than one variable then the test will be run against all + unique combinations of the values +\end_layout + +\end_inset + + + + +\begin_inset Text + +\begin_layout Plain Layout +[eztests] +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +any valid +\end_layout + +\end_inset + + +\begin_inset Text + +\begin_layout Plain Layout +stepname command +\end_layout + +\end_inset + + \begin_inset Text \begin_layout Plain Layout no \end_layout \end_inset - + \begin_inset Text \begin_layout Plain Layout -The test will be repeated once for each item with the variable name set - to the value. - If there is more than one variable then the test will be run against all - unique combinations of the values +Use in addition to or instead of runscript for easy implementation of steps. + If .logpro exists it will be applied to the .log and + resulting exit code will be used to determine PASS/FAIL/WARN \end_layout \end_inset @@ -3870,11 +4031,11 @@ \begin_inset Text \begin_layout Plain Layout -If using logpro to asses the PASS/FAIL status of the step you specify the +If using logpro to acess the PASS/FAIL status of the step you specify the logpro file with this parameter. \end_layout \end_inset ADDED docs/monitor-state-diagram.svg Index: docs/monitor-state-diagram.svg ================================================================== --- /dev/null +++ docs/monitor-state-diagram.svg @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + New + processing + waiting + done + + + + + + + ADDED docs/test-launch-state-diagram.svg Index: docs/test-launch-state-diagram.svg ================================================================== --- /dev/null +++ docs/test-launch-state-diagram.svg @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + get test + + + (Tests list) + items list? + string + proc + list + #f + + all prerequisites met? + no + yes + + launch and drop test record + + + create recordsand add to tests list + + + + waiton prerequites met?(waitonbyitem prerequisitesdo not have to be met) + yes + no + + + + + + + Index: items.scm ================================================================== --- items.scm +++ items.scm @@ -117,10 +117,30 @@ '() #f))) res))) ;; Nope, not now, return null as of 6/6/2011 - +(define (check-valid-items class item) + (let ((valid-values (let ((s (config-lookup *configdat* "validvalues" class))) + (if s (string-split s) #f)))) + (if valid-values + (if (member item valid-values) + item #f) + item))) + +(define (items:get-items-from-config tconfig) + (let* (;; db is always at *toppath*/db/megatest.db + (items (hash-table-ref/default tconfig "items" '())) + (itemstable (hash-table-ref/default tconfig "itemstable" '()))) + (if (procedure? items) + (set! items (items))) + (if (procedure? itemstable) + (set! itemstable (itemstable))) + (if (or (not (null? items))(not (null? itemstable))) + (append (item-assoc->item-list items) + (item-table->item-list itemstable)) + '(())))) + ;; (pp (item-assoc->item-list itemdat)) Index: key_records.scm ================================================================== --- key_records.scm +++ key_records.scm @@ -19,7 +19,9 @@ (string-join (map (lambda (k)(conc (key:get-fieldname k) " " (key:get-fieldtype k))) (append keys additional)) ",")) (define-inline (item-list->path itemdat) - (string-intersperse (map cadr itemdat) "/")) + (if (list? itemdat) + (string-intersperse (map cadr itemdat) "/") + "")) Index: launch.scm ================================================================== --- launch.scm +++ launch.scm @@ -371,13 +371,17 @@ (key-vals (get-key-vals db run-id)) (key-str (string-intersperse key-vals "/")) (dfullp (conc disk-path "/" key-str "/" runname "/" testname item-path)) (toptest-path (conc disk-path "/" key-str "/" runname "/" testname)) - (runsdir (config-lookup *configdat* "setup" "runsdir")) - (lnkpath (conc (if runsdir runsdir (conc *toppath* "/runs")) - "/" key-str "/" runname item-path))) + (runsdir (let ((rd (config-lookup *configdat* "setup" "runsdir"))) + (if rd rd (conc *toppath* "/runs")))) + (lnkpath (conc runsdir "/" key-str "/" runname item-path))) + (if (not (file-exists? runsdir)) + (begin + (debug:print 0 "WARNING: runsdir did not exist! Creating it now at " runsdir) + (system (conc "mkdir -p " runsdir)))) ;; since this is an iterated test this is as good a place as any to ;; update the toptest record with its location rundir (if (not (equal? item-path "")) (db:test-set-rundir! db run-id testname "" toptest-path)) (debug:print 2 "Setting up test run area") @@ -384,15 +388,15 @@ (debug:print 2 " - creating run area in " dfullp) (system (conc "mkdir -p " dfullp)) (debug:print 2 " - creating link from " dfullp "/" testname " to " lnkpath) (system (conc "mkdir -p " lnkpath)) -;; I suspect this section was deleting test directories under some -;; wierd sitations + ;; I suspect this section was deleting test directories under some + ;; wierd sitations? This doesn't make sense - reenabling the rm -f -;; (if (file-exists? (conc lnkpath "/" testname)) -;; (system (conc "rm -f " lnkpath "/" testname))) + (if (file-exists? (conc lnkpath "/" testname)) + (system (conc "rm -f " lnkpath "/" testname))) (system (conc "ln -sf " dfullp " " lnkpath "/" testname)) (if (directory? dfullp) (begin (let* ((cmd (conc "rsync -av" (if (> *verbosity* 1) "" "q") " " test-path "/ " dfullp "/")) (status (system cmd))) Index: megatest-version.scm ================================================================== --- megatest-version.scm +++ megatest-version.scm @@ -1,7 +1,7 @@ ;; Always use two digit decimal ;; 1.01, 1.02...1.10,1.11 ... 1.99,2.00.. (declare (unit megatest-version)) -(define megatest-version 1.34) +(define megatest-version 1.36) Index: megatest.scm ================================================================== --- megatest.scm +++ megatest.scm @@ -324,15 +324,48 @@ ;; run all tests are are Not COMPLETED and PASS or CHECK (if (args:get-arg "-runall") (general-run-call "-runall" "run all tests" - (lambda (db keys keynames keyvallst) - (let* ((test-names (get-all-legal-tests))) ;; "PROD" is ignored for now - (debug:print 1 "INFO: Attempting to start the following tests...") - (debug:print 1 " " (string-intersperse test-names ",")) - (run-tests db test-names))))) + (lambda (db target runname keys keynames keyvallst) + (runs:run-tests db + target + runname + (args:get-arg "-testpatt") + (args:get-arg "-itempatt") + user + (make-hash-table))))) + +;;====================================================================== +;; run one test +;;====================================================================== + +;; 1. find the config file +;; 2. change to the test directory +;; 3. update the db with "test started" status, set running host +;; 4. process launch the test +;; - monitor the process, update stats in the db every 2^n minutes +;; 5. as the test proceeds internally it calls megatest as each step is +;; started and completed +;; - step started, timestamp +;; - step completed, exit status, timestamp +;; 6. test phone home +;; - if test run time > allowed run time then kill job +;; - if cannot access db > allowed disconnect time then kill job + +(if (args:get-arg "-runtests") + (general-run-call + "-runtests" + "run a test" + (lambda (db target runname keys keynames keyvallst) + (runs:run-tests db + target + runname + (args:get-arg "-runtests") + (args:get-arg "-itempatt") + user + (make-hash-table))))) ;;====================================================================== ;; Rollup into a run ;;====================================================================== (if (args:get-arg "-rollup") @@ -359,35 +392,10 @@ (runspatt (args:get-arg ":runname")) (pathmod (args:get-arg "-pathmod")) (keyvalalist (keys->alist keys "%"))) (db:extract-ods-file db outputfile keyvalalist (if runspatt runspatt "%") pathmod))))) -;;====================================================================== -;; run one test -;;====================================================================== - -;; 1. find the config file -;; 2. change to the test directory -;; 3. update the db with "test started" status, set running host -;; 4. process launch the test -;; - monitor the process, update stats in the db every 2^n minutes -;; 5. as the test proceeds internally it calls megatest as each step is -;; started and completed -;; - step started, timestamp -;; - step completed, exit status, timestamp -;; 6. test phone home -;; - if test run time > allowed run time then kill job -;; - if cannot access db > allowed disconnect time then kill job - -(if (args:get-arg "-runtests") - (general-run-call - "-runtests" - "run a test" - (lambda (db keys keynames keyvallst) - (let ((test-names (string-split (args:get-arg "-runtests") ","))) - (run-tests db test-names))))) - ;;====================================================================== ;; execute the test ;; - gets called on remote host ;; - receives info from the -execute param ;; - passes info to steps via MT_CMDINFO env var (future is to use a dot file) ADDED monitor.scm Index: monitor.scm ================================================================== --- /dev/null +++ monitor.scm @@ -0,0 +1,25 @@ +;; Copyright 2006-2011, Matthew Welland. +;; +;; This program is made available under the GNU GPL version 2.0 or +;; greater. See the accompanying file COPYING for details. +;; +;; This program is distributed WITHOUT ANY WARRANTY; without even the +;; implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +;; PURPOSE. + +;; strftime('%m/%d/%Y %H:%M:%S','now','localtime') + +(use sqlite3 srfi-1 posix regex regex-case srfi-69 dot-locking) +(import (prefix sqlite3 sqlite3:)) + +(declare (unit runs)) +(declare (uses db)) +(declare (uses common)) +(declare (uses items)) +(declare (uses runconfig)) + +(include "common_records.scm") +(include "key_records.scm") +(include "db_records.scm") +(include "run_records.scm") + Index: runs.scm ================================================================== --- runs.scm +++ runs.scm @@ -16,48 +16,17 @@ (declare (unit runs)) (declare (uses db)) (declare (uses common)) (declare (uses items)) (declare (uses runconfig)) +(declare (uses tests)) (include "common_records.scm") (include "key_records.scm") (include "db_records.scm") (include "run_records.scm") - -;; register a test run with the db -(define (register-run db keys) ;; test-name) - (let* ((keystr (keys->keystr keys)) - (comma (if (> (length keys) 0) "," "")) - (andstr (if (> (length keys) 0) " AND " "")) - (valslots (keys->valslots keys)) ;; ?,?,? ... - (keyvallst (keys->vallist keys)) ;; extracts the values from remainder of (argv) - (runname (get-with-default ":runname" #f)) - (state (get-with-default ":state" "no")) - (status (get-with-default ":status" "n/a")) - (allvals (append (list runname state status user) keyvallst)) - (qryvals (append (list runname) keyvallst)) - (key=?str (string-intersperse (map (lambda (k)(conc (key:get-fieldname k) "=?")) keys) " AND "))) - (debug:print 3 "keys: " keys " allvals: " allvals " keyvallst: " keyvallst) - (debug:print 2 "NOTE: using key " (string-intersperse keyvallst "/") " for this run") - (if (and runname (null? (filter (lambda (x)(not x)) keyvallst))) ;; there must be a better way to "apply and" - (let ((res #f)) - (apply sqlite3:execute db (conc "INSERT OR IGNORE INTO runs (runname,state,status,owner,event_time" comma keystr ") VALUES (?,?,?,?,strftime('%s','now')" comma valslots ");") - allvals) - (apply sqlite3:for-each-row - (lambda (id) - (set! res id)) - db - (let ((qry (conc "SELECT id FROM runs WHERE (runname=? " andstr key=?str ");"))) - ;(debug:print 4 "qry: " qry) - qry) - qryvals) - (sqlite3:execute db "UPDATE runs SET state=?,status=? WHERE id=?;" state status res) - res) - (begin - (debug:print 0 "ERROR: Called without all necessary keys") - #f)))) +(include "test_records.scm") ;; runs:get-runs-by-patt ;; get runs by list of criteria ;; register a test run with the db ;; @@ -87,298 +56,10 @@ db (conc "SELECT " keystr " FROM runs WHERE runname like ? " key-patt ";") runnamepatt) (vector header res))) -(define (register-test db run-id test-name item-path) - (let ((item-paths (if (equal? item-path "") - (list item-path) - (list item-path "")))) - (for-each - (lambda (pth) - (sqlite3:execute db "INSERT OR IGNORE INTO tests (run_id,testname,event_time,item_path,state,status) VALUES (?,?,strftime('%s','now'),?,'NOT_STARTED','n/a');" - run-id - test-name - pth - ;; (conc "," (string-intersperse tags ",") ",") - )) - item-paths ))) - -;; get the previous record for when this test was run where all keys match but runname -;; returns #f if no such test found, returns a single test record if found -(define (test:get-previous-test-run-record db run-id test-name item-path) - (let* ((keys (db:get-keys db)) - (selstr (string-intersperse (map (lambda (x)(vector-ref x 0)) keys) ",")) - (qrystr (string-intersperse (map (lambda (x)(conc (vector-ref x 0) "=?")) keys) " AND ")) - (keyvals #f)) - ;; first look up the key values from the run selected by run-id - (sqlite3:for-each-row - (lambda (a . b) - (set! keyvals (cons a b))) - db - (conc "SELECT " selstr " FROM runs WHERE id=? ORDER BY event_time DESC;") run-id) - (if (not keyvals) - #f - (let ((prev-run-ids '())) - (apply sqlite3:for-each-row - (lambda (id) - (set! prev-run-ids (cons id prev-run-ids))) - db - (conc "SELECT id FROM runs WHERE " qrystr " AND id != ?;") (append keyvals (list run-id))) - ;; for each run starting with the most recent look to see if there is a matching test - ;; if found then return that matching test record - (debug:print 4 "selstr: " selstr ", qrystr: " qrystr ", keyvals: " keyvals ", previous run ids found: " prev-run-ids) - (if (null? prev-run-ids) #f - (let loop ((hed (car prev-run-ids)) - (tal (cdr prev-run-ids))) - (let ((results (db-get-tests-for-run db hed test-name item-path '() '()))) - (debug:print 4 "Got tests for run-id " run-id ", test-name " test-name ", item-path " item-path ": " results) - (if (and (null? results) - (not (null? tal))) - (loop (car tal)(cdr tal)) - (if (null? results) #f - (car results)))))))))) - -;; get the previous records for when these tests were run where all keys match but runname -;; NB// Merge this with test:get-previous-test-run-records? This one looks for all matching tests -;; can use wildcards. -(define (test:get-matching-previous-test-run-records db run-id test-name item-path) - (let* ((keys (db:get-keys db)) - (selstr (string-intersperse (map (lambda (x)(vector-ref x 0)) keys) ",")) - (qrystr (string-intersperse (map (lambda (x)(conc (vector-ref x 0) "=?")) keys) " AND ")) - (keyvals #f) - (tests-hash (make-hash-table))) - ;; first look up the key values from the run selected by run-id - (sqlite3:for-each-row - (lambda (a . b) - (set! keyvals (cons a b))) - db - (conc "SELECT " selstr " FROM runs WHERE id=? ORDER BY event_time DESC;") run-id) - (if (not keyvals) - '() - (let ((prev-run-ids '())) - (apply sqlite3:for-each-row - (lambda (id) - (set! prev-run-ids (cons id prev-run-ids))) - db - (conc "SELECT id FROM runs WHERE " qrystr " AND id != ?;") (append keyvals (list run-id))) - ;; collect all matching tests for the runs then - ;; extract the most recent test and return that. - (debug:print 4 "selstr: " selstr ", qrystr: " qrystr ", keyvals: " keyvals - ", previous run ids found: " prev-run-ids) - (if (null? prev-run-ids) '() ;; no previous runs? return null - (let loop ((hed (car prev-run-ids)) - (tal (cdr prev-run-ids))) - (let ((results (db-get-tests-for-run db hed test-name item-path '() '()))) - (debug:print 4 "Got tests for run-id " run-id ", test-name " test-name - ", item-path " item-path " results: " (intersperse results "\n")) - ;; Keep only the youngest of any test/item combination - (for-each - (lambda (testdat) - (let* ((full-testname (conc (db:test-get-testname testdat) "/" (db:test-get-item-path testdat))) - (stored-test (hash-table-ref/default tests-hash full-testname #f))) - (if (or (not stored-test) - (and stored-test - (> (db:test-get-event_time testdat)(db:test-get-event_time stored-test)))) - ;; this test is younger, store it in the hash - (hash-table-set! tests-hash full-testname testdat)))) - results) - (if (null? tal) - (map cdr (hash-table->alist tests-hash)) ;; return a list of the most recent tests - (loop (car tal)(cdr tal)))))))))) - -(define (test-set-status! db run-id test-name state status itemdat-or-path comment dat) - (let* ((real-status status) - (item-path (if (string? itemdat-or-path) itemdat-or-path (item-list->path itemdat-or-path))) - (testdat (db:get-test-info db run-id test-name item-path)) - (test-id (if testdat (db:test-get-id testdat) #f)) - (otherdat (if dat dat (make-hash-table))) - ;; before proceeding we must find out if the previous test (where all keys matched except runname) - ;; was WAIVED if this test is FAIL - (waived (if (equal? status "FAIL") - (let ((prev-test (test:get-previous-test-run-record db run-id test-name item-path))) - (if prev-test ;; true if we found a previous test in this run series - (let ((prev-status (db:test-get-status prev-test)) - (prev-state (db:test-get-state prev-test)) - (prev-comment (db:test-get-comment prev-test))) - (debug:print 4 "prev-status " prev-status ", prev-state " prev-state ", prev-comment " prev-comment) - (if (and (equal? prev-state "COMPLETED") - (equal? prev-status "WAIVED")) - prev-comment ;; waived is either the comment or #f - #f)) - #f)) - #f))) - (if waived (set! real-status "WAIVED")) - (debug:print 4 "real-status " real-status ", waived " waived ", status " status) - - ;; update the primary record IF state AND status are defined - (if (and state status) - (sqlite3:execute db "UPDATE tests SET state=?,status=?,event_time=strftime('%s','now') WHERE run_id=? AND testname=? AND item_path=?;" - state real-status run-id test-name item-path)) - - ;; if status is "AUTO" then call rollup - (if (and test-id state status (equal? status "AUTO")) - (db:test-data-rollup db test-id)) - - ;; add metadata (need to do this way to avoid SQL injection issues) - - ;; :first_err - ;; (let ((val (hash-table-ref/default otherdat ":first_err" #f))) - ;; (if val - ;; (sqlite3:execute db "UPDATE tests SET first_err=? WHERE run_id=? AND testname=? AND item_path=?;" val run-id test-name item-path))) - ;; - ;; ;; :first_warn - ;; (let ((val (hash-table-ref/default otherdat ":first_warn" #f))) - ;; (if val - ;; (sqlite3:execute db "UPDATE tests SET first_warn=? WHERE run_id=? AND testname=? AND item_path=?;" val run-id test-name item-path))) - - (let ((category (hash-table-ref/default otherdat ":category" "")) - (variable (hash-table-ref/default otherdat ":variable" "")) - (value (hash-table-ref/default otherdat ":value" #f)) - (expected (hash-table-ref/default otherdat ":expected" #f)) - (tol (hash-table-ref/default otherdat ":tol" #f)) - (units (hash-table-ref/default otherdat ":units" "")) - (dcomment (hash-table-ref/default otherdat ":comment" ""))) - (debug:print 4 - "category: " category ", variable: " variable ", value: " value - ", expected: " expected ", tol: " tol ", units: " units) - (if (and value expected tol) ;; all three required - (db:csv->test-data db test-id - (conc category "," - variable "," - value "," - expected "," - tol "," - units "," - dcomment ",")))) - - ;; need to update the top test record if PASS or FAIL and this is a subtest - (if (and (not (equal? item-path "")) - (or (equal? status "PASS") - (equal? status "WARN") - (equal? status "FAIL") - (equal? status "WAIVED") - (equal? status "RUNNING"))) - (begin - (sqlite3:execute - db - "UPDATE tests - SET fail_count=(SELECT count(id) FROM tests WHERE run_id=? AND testname=? AND item_path != '' AND status='FAIL'), - pass_count=(SELECT count(id) FROM tests WHERE run_id=? AND testname=? AND item_path != '' AND (status='PASS' OR status='WARN' OR status='WAIVED')) - WHERE run_id=? AND testname=? AND item_path='';" - run-id test-name run-id test-name run-id test-name) - (if (equal? status "RUNNING") ;; running takes priority over all other states, force the test state to RUNNING - (sqlite3:execute db "UPDATE tests SET state=? WHERE run_id=? AND testname=? AND item_path='';" run-id test-name) - (sqlite3:execute - db - "UPDATE tests - SET state=CASE WHEN (SELECT count(id) FROM tests WHERE run_id=? AND testname=? AND item_path != '' AND state in ('RUNNING','NOT_STARTED')) > 0 THEN - 'RUNNING' - ELSE 'COMPLETED' END, - status=CASE WHEN fail_count > 0 THEN 'FAIL' WHEN pass_count > 0 AND fail_count=0 THEN 'PASS' ELSE 'UNKNOWN' END - WHERE run_id=? AND testname=? AND item_path='';" - run-id test-name run-id test-name)))) - (if (or (and (string? comment) - (string-match (regexp "\\S+") comment)) - waived) - (sqlite3:execute db "UPDATE tests SET comment=? WHERE run_id=? AND testname=? AND item_path=?;" - (if waived waived comment) run-id test-name item-path)) - )) - -(define (test-set-log! db run-id test-name itemdat logf) - (let ((item-path (item-list->path itemdat))) - (sqlite3:execute db "UPDATE tests SET final_logf=? WHERE run_id=? AND testname=? AND item_path=?;" - logf run-id test-name item-path))) - -(define (test-set-toplog! db run-id test-name logf) - (sqlite3:execute db "UPDATE tests SET final_logf=? WHERE run_id=? AND testname=? AND item_path='';" - logf run-id test-name)) - -(define (tests:summarize-items db run-id test-name force) - ;; if not force then only update the record if one of these is true: - ;; 1. logf is "log/final.log - ;; 2. logf is same as outputfilename - (let ((outputfilename (conc "megatest-rollup-" test-name ".html")) - (orig-dir (current-directory)) - (logf #f)) - (sqlite3:for-each-row - (lambda (path final_logf) - (set! logf final_logf) - (if (directory? path) - (begin - (print "Found path: " path) - (change-directory path)) - ;; (set! outputfilename (conc path "/" outputfilename))) - (print "No such path: " path))) - db - "SELECT rundir,final_logf FROM tests WHERE run_id=? AND testname=? AND item_path='';" - run-id test-name) - (print "summarize-items with logf " logf) - (if (or (equal? logf "logs/final.log") - (equal? logf outputfilename) - force) - (begin - (if (obtain-dot-lock outputfilename 1 20 30) ;; retry every second for 20 seconds, call it dead after 30 seconds and steal the lock - (print "Obtained lock for " outputfilename) - (print "Failed to obtain lock for " outputfilename)) - (let ((oup (open-output-file outputfilename)) - (counts (make-hash-table)) - (statecounts (make-hash-table)) - (outtxt "") - (tot 0)) - (with-output-to-port - oup - (lambda () - (set! outtxt (conc outtxt "Summary: " test-name - "

Summary for " test-name "

")) - (sqlite3:for-each-row - (lambda (id itempath state status run_duration logf comment) - (hash-table-set! counts status (+ 1 (hash-table-ref/default counts status 0))) - (hash-table-set! statecounts state (+ 1 (hash-table-ref/default statecounts state 0))) - (set! outtxt (conc outtxt "" - " " itempath "" - "" state "" - "" status "" - "" (if (equal? comment "") - " " - comment) "" - ""))) - db - "SELECT id,item_path,state,status,run_duration,final_logf,comment FROM tests WHERE run_id=? AND testname=? AND item_path != '';" - run-id test-name) - - (print "
") - ;; Print out stats for status - (set! tot 0) - (print "") - (for-each (lambda (state) - (set! tot (+ tot (hash-table-ref statecounts state))) - (print "")) - (hash-table-keys statecounts)) - (print "

State stats

" state "" (hash-table-ref statecounts state) "
Total" tot "
") - (print "
") - ;; Print out stats for state - (set! tot 0) - (print "") - (for-each (lambda (status) - (set! tot (+ tot (hash-table-ref counts status))) - (print "")) - (hash-table-keys counts)) - (print "

Status stats

" status - "" (hash-table-ref counts status) "
Total" tot "
") - (print "
") - - (print "" - "" - outtxt "
ItemStateStatusComment
") - (release-dot-lock outputfilename))) - (close-output-port oup) - (change-directory orig-dir) - (test-set-toplog! db run-id test-name outputfilename) - ))))) - ;; ;; TODO: Converge this with db:get-test-info ;; (define (runs:get-test-info db run-id test-name item-path) ;; (let ((res #f)) ;; (vector #f #f #f #f #f #f))) ;; (sqlite3:for-each-row ;; (lambda (id run-id test-name state status) @@ -390,74 +71,10 @@ (define (runs:test-get-full-path test) (let* ((testname (db:test-get-testname test)) (itempath (db:test-get-item-path test))) (conc testname (if (equal? itempath "") "" (conc "(" itempath ")"))))) -(define (check-valid-items class item) - (let ((valid-values (let ((s (config-lookup *configdat* "validvalues" class))) - (if s (string-split s) #f)))) - (if valid-values - (if (member item valid-values) - item #f) - item))) - -(define (teststep-set-status! db run-id test-name teststep-name state-in status-in itemdat comment logfile) - (debug:print 4 "run-id: " run-id " test-name: " test-name) - (let* ((state (check-valid-items "state" state-in)) - (status (check-valid-items "status" status-in)) - (item-path (item-list->path itemdat)) - (testdat (db:get-test-info db run-id test-name item-path))) - (debug:print 5 "testdat: " testdat) - (if (and testdat ;; if the section exists then force specification BUG, I don't like how this works. - (or (not state)(not status))) - (debug:print 0 "WARNING: Invalid " (if status "status" "state") - " value \"" (if status state-in status-in) "\", update your validvalues section in megatest.config")) - (if testdat - (let ((test-id (test:get-id testdat))) - ;; FIXME - this should not update the logfile unless it is specified. - (sqlite3:execute db - "INSERT OR REPLACE into test_steps (test_id,stepname,state,status,event_time,comment,logfile) VALUES(?,?,?,?,strftime('%s','now'),?,?);" - test-id teststep-name state-in status-in (if comment comment "") (if logfile logfile ""))) - (debug:print 0 "ERROR: Can't update " test-name " for run " run-id " -> no such test in db")))) - -(define (test-get-kill-request db run-id test-name itemdat) - (let* ((item-path (item-list->path itemdat)) - (testdat (db:get-test-info db run-id test-name item-path))) - (equal? (test:get-state testdat) "KILLREQ"))) - -(define (test-set-meta-info db run-id testname itemdat) - (let ((item-path (item-list->path itemdat)) - (cpuload (get-cpu-load)) - (hostname (get-host-name)) - (diskfree (get-df (current-directory))) - (uname (get-uname "-srvpio")) - (runpath (current-directory))) - (sqlite3:execute db "UPDATE tests SET host=?,cpuload=?,diskfree=?,uname=?,rundir=? WHERE run_id=? AND testname=? AND item_path=?;" - hostname - cpuload - diskfree - uname - runpath - run-id - testname - item-path))) - -(define (test-update-meta-info db run-id testname itemdat minutes cpuload diskfree tmpfree) - (let ((item-path (item-list->path itemdat))) - (if (not item-path)(begin (debug:print 0 "WARNING: ITEMPATH not set.") (set! item-path ""))) - ;; (let ((testinfo (db:get-test-info db run-id testname item-path))) - ;; (if (and (not (equal? (db:test-get-status testinfo) "COMPLETED")) - ;; (not (equal? (db:test-get-status testinfo) "KILLREQ")) - (sqlite3:execute - db - "UPDATE tests SET cpuload=?,diskfree=?,run_duration=?,state='RUNNING' WHERE run_id=? AND testname=? AND item_path=? AND state NOT IN ('COMPLETED','KILLREQ','KILLED');" - cpuload - diskfree - minutes - run-id - testname - item-path))) (define (set-megatest-env-vars db run-id) (let ((keys (db-get-keys db))) (for-each (lambda (key) (sqlite3:for-each-row @@ -473,348 +90,38 @@ (for-each (lambda (item) (debug:print 2 "setenv " (car item) " " (cadr item)) (setenv (car item) (cadr item))) itemdat)) -(define (get-all-legal-tests) - (let* ((tests (glob (conc *toppath* "/tests/*"))) - (res '())) - (debug:print 4 "INFO: Looking at tests " (string-intersperse tests ",")) - (for-each (lambda (testpath) - (if (file-exists? (conc testpath "/testconfig")) - (set! res (cons (last (string-split testpath "/")) res)))) - tests) - res)) - -(define (runs:can-run-more-tests db) - (let ((num-running (db:get-count-tests-running db)) - (max-concurrent-jobs (config-lookup *configdat* "setup" "max_concurrent_jobs"))) +(define (runs:can-run-more-tests db test-record) + (let* ((tconfig (tests:testqueue-get-testconfig test-record)) + (jobgroup (config-lookup tconfig "requirements" "jobgroup")) + (num-running (db:get-count-tests-running db)) + (num-running-in-jobgroup (db:get-count-tests-running-in-jobgroup db jobgroup)) + (max-concurrent-jobs (config-lookup *configdat* "setup" "max_concurrent_jobs")) + (job-group-limit (config-lookup *configdat* "jobgroups" jobgroup))) (debug:print 2 "max-concurrent-jobs: " max-concurrent-jobs ", num-running: " num-running) (if (not (eq? 0 *globalexitstatus*)) #f - (if (or (not max-concurrent-jobs) - (and max-concurrent-jobs - (string->number max-concurrent-jobs) - (not (>= num-running (string->number max-concurrent-jobs))))) - #t - (begin - (debug:print 0 "WARNING: Max running jobs exceeded, current number running: " num-running - ", max_concurrent_jobs: " max-concurrent-jobs) - #f))))) - -(define (test:get-testconfig test-name system-allowed) - (let* ((test-path (conc *toppath* "/tests/" test-name)) - (test-configf (conc test-path "/testconfig")) - (testexists (and (file-exists? test-configf)(file-read-access? test-configf)))) - (if testexists - (read-config test-configf #f system-allowed environ-patt: (if system-allowed - "pre-launch-env-vars" - #f)) - #f))) - -;; sort tests by priority and waiton -;; Move test specific stuff to a test unit FIXME one of these days -(define (tests:sort-by-priority-and-waiton test-names) - (let ((testdetails (make-hash-table)) - (mungepriority (lambda (priority) - (if priority - (let ((tmp (any->number priority))) - (if tmp tmp (begin (debug:print 0 "ERROR: bad priority value " priority ", using 0") 0))) - 0)))) - (for-each (lambda (test-name) - (let ((test-config (test:get-testconfig test-name #f))) - (if test-config (hash-table-set! testdetails test-name test-config)))) - test-names) - (sort - (hash-table-keys testdetails) ;; avoid dealing with deleted tests, look at the hash table - (lambda (a b) - (let* ((tconf-a (hash-table-ref testdetails a)) - (tconf-b (hash-table-ref testdetails b)) - (a-waiton (config-lookup tconf-a "requirements" "waiton")) - (b-waiton (config-lookup tconf-b "requirements" "waiton")) - (a-priority (mungepriority (config-lookup tconf-a "requirements" "priority"))) - (b-priority (mungepriority (config-lookup tconf-b "requirements" "priority")))) - (if (and a-waiton (equal? a-waiton b)) - #f ;; cannot have a which is waiting on b happening before b - (if (and b-waiton (equal? b-waiton a)) - #t ;; this is the correct order, b is waiting on a and b is before a - (if (> a-priority b-priority) - #t ;; if a is a higher priority than b then we are good to go - #f)))))))) - -;; This is original run-tests, this routine is deprecated and we will transition to using runs:run-tests (see below) -;; -(define (run-tests db test-names) - (let* ((keys (db-get-keys db)) - (keyvallst (keys->vallist keys #t)) - (run-id (register-run db keys)) ;; test-name))) - (deferred '()) ;; delay running these since they have a waiton clause - (runconfigf (conc *toppath* "/runconfigs.config")) - (required-tests '())) - - ;; now add non-directly referenced dependencies (i.e. waiton) - ;; could cache all these since they need to be read again ... - ;; FIXME SOMEDAY - (if (not (null? test-names)) - (let loop ((hed (car test-names)) - (tal (cdr test-names))) - (let* ((config (test:get-testconfig hed #f)) - (waitons (string-split (let ((w (config-lookup config "requirements" "waiton"))) - (if w w ""))))) - (for-each - (lambda (waiton) - (if (and waiton (not (member waiton test-names))) - (begin - (set! required-tests (cons waiton required-tests)) - (set! test-names (append test-names (list waiton)))))) - waitons) - (let ((remtests (delete-duplicates (append waitons tal)))) - (if (not (null? remtests)) - (loop (car remtests)(cdr remtests))))))) - - (if (not (null? required-tests)) - (debug:print 1 "INFO: Adding " required-tests " to the run queue") - (debug:print 1 "INFO: No prerequisites added")) - - ;; on the first pass or call to run-tests set FAILS to NOT_STARTED if - ;; -keepgoing is specified - - (set-megatest-env-vars db run-id) ;; these may be needed by the launching process - - (if (file-exists? runconfigf) - (setup-env-defaults db runconfigf run-id *already-seen-runconfig-info* environ-patt: ".*") - (debug:print 0 "WARNING: You do not have a run config file: " runconfigf)) - - (if (and (eq? *passnum* 0) - (args:get-arg "-keepgoing")) - (begin - ;; have to delete test records where NOT_STARTED since they can cause -keepgoing to - ;; get stuck due to becoming inaccessible from a failed test. I.e. if test B depends - ;; on test A but test B reached the point on being registered as NOT_STARTED and test - ;; A failed for some reason then on re-run using -keepgoing the run can never complete. - (db:delete-tests-in-state db run-id "NOT_STARTED") - (db:set-tests-state-status db run-id test-names #f "FAIL" "NOT_STARTED" "FAIL"))) - (set! *passnum* (+ *passnum* 1)) - (let loop ((numtimes 0)) - (for-each - (lambda (test-name) - (if (runs:can-run-more-tests db) - (run-one-test db run-id test-name keyvallst) - ;; add some delay - ;(sleep 2) - )) - (tests:sort-by-priority-and-waiton test-names)) - ;; (run-waiting-tests db) - (if (args:get-arg "-keepgoing") - (let ((estrem (db:estimated-tests-remaining db run-id))) - (if (and (> estrem 0) - (eq? *globalexitstatus* 0)) - (begin - (debug:print 1 "Keep going, estimated " estrem " tests remaining to run, will continue in 3 seconds ...") - (thread-sleep! 3) - (run-waiting-tests db) - (loop (+ numtimes 1))))))))) - -;; VERY INEFFICIENT! Move stuff that should be done once up to calling proc -(define (run-one-test db run-id test-name keyvallst) - (debug:print 1 "Launching test " test-name) - ;; All these vars might be referenced by the testconfig file reader - (setenv "MT_TEST_NAME" test-name) ;; - (setenv "MT_RUNNAME" (args:get-arg ":runname")) - - ;; (set-megatest-env-vars db run-id) ;; these may be needed by the launching process - - (change-directory *toppath*) - (let* ((test-path (conc *toppath* "/tests/" test-name)) ;; could use test:get-testconfig here ... - (test-configf (conc test-path "/testconfig")) - (testexists (and (file-exists? test-configf)(file-read-access? test-configf))) - (test-conf (if testexists (read-config test-configf #f #t) (make-hash-table))) - (waiton (let ((w (config-lookup test-conf "requirements" "waiton"))) - (if (string? w)(string-split w)'()))) - (tags (let ((t (config-lookup test-conf "setup" "tags"))) - ;; we want our tags to be separated by commas and fully delimited by commas - ;; so that queries with "like" can tie to the commas at either end of each tag - ;; while also allowing the end user to freely use spaces and commas to separate tags - (if (string? t)(string-substitute (regexp "[,\\s]+") "," (conc "," t ",") #t) - '())))) - (if (not testexists) - (begin - (debug:print 0 "ERROR: Can't find config file " test-configf) - (exit 2)) - ;; put top vars into convenient variables and open the db - (let* (;; db is always at *toppath*/db/megatest.db - (items (hash-table-ref/default test-conf "items" '())) - (itemstable (hash-table-ref/default test-conf "itemstable" '())) - (allitems (if (or (not (null? items))(not (null? itemstable))) - (append (item-assoc->item-list items) - (item-table->item-list itemstable)) - '(())))) ;; a list with one null list is a test with no items -;; (runconfigf (conc *toppath* "/runconfigs.config"))) - (debug:print 1 "items: ") - (if (>= *verbosity* 1)(pp allitems)) - (if (>= *verbosity* 5) - (begin - (print "items: ")(pp (item-assoc->item-list items)) - (print "itestable: ")(pp (item-table->item-list itemstable)))) - (if (args:get-arg "-m") - (db:set-comment-for-run db run-id (args:get-arg "-m"))) - - ;; Here is where the test_meta table is best updated - (runs:update-test_meta db test-name test-conf) - - ;; braindead work-around for poorly specified allitems list BUG!!! FIXME - (if (null? allitems)(set! allitems '(()))) - (let loop ((itemdat (car allitems)) - (tal (cdr allitems))) - ;; (lambda (itemdat) ;;; ((ripeness "overripe") (temperature "cool") (season "summer")) - ;; Handle lists of items - (let* ((item-path (item-list->path itemdat)) ;; (string-intersperse (map cadr itemdat) "/")) - (new-test-path (string-intersperse (cons test-path (map cadr itemdat)) "/")) - (new-test-name (if (equal? item-path "") test-name (conc test-name "/" item-path))) ;; just need it to be unique - (testdat #f) - (num-running (db:get-count-tests-running db)) - (max-concurrent-jobs (config-lookup *configdat* "setup" "max_concurrent_jobs")) - (parent-test (and (not (null? items))(equal? item-path ""))) - (single-test (and (null? items) (equal? item-path ""))) - (item-test (not (equal? item-path ""))) - (item-patt (args:get-arg "-itempatt")) - (patt-match (if item-patt - (string-search (glob->regexp - (string-translate item-patt "%" "*")) - item-path) - #t))) - (debug:print 3 "max-concurrent-jobs: " max-concurrent-jobs ", num-running: " num-running) - (if (and patt-match (runs:can-run-more-tests db)) - (begin - (let loop2 ((ts (db:get-test-info db run-id test-name item-path)) ;; #f) - (ct 0)) - (if (and (not ts) - (< ct 10)) - (begin - (register-test db run-id test-name item-path) - (db:test-set-comment db run-id test-name item-path "") - (loop2 (db:get-test-info db run-id test-name item-path) - (+ ct 1))) - (if ts - (set! testdat ts) - (begin - (debug:print 0 "WARNING: Couldn't register test " test-name " with item path " item-path ", skipping") - (if (not (null? tal)) - (loop (car tal)(cdr tal))))))) - (change-directory test-path) - ;; this block is here only to inform the user early on - - ;; NB// Moving the setting of runconfig.config vars to *before* the - ;; the calling of each test. - ;; (if (file-exists? runconfigf) - ;; (setup-env-defaults db runconfigf run-id *already-seen-runconfig-info*) - ;; (debug:print 0 "WARNING: You do not have a run config file: " runconfigf)) - (debug:print 4 "run-id: " run-id " test-name: " test-name " item-path: " item-path " testdat: " (test:get-status testdat) " test-state: " (test:get-state testdat)) - (case (if (args:get-arg "-force") - 'NOT_STARTED - (if testdat - (string->symbol (test:get-state testdat)) - 'failed-to-insert)) - ((failed-to-insert) - (debug:print 0 "ERROR: Failed to insert the record into the db")) - ((NOT_STARTED COMPLETED) - (debug:print 6 "Got here, " (test:get-state testdat)) - (let ((runflag #f)) - (cond - ;; i.e. this is the parent test to a suite of items, never "run" it - (parent-test - (set! runflag #f)) - ;; -force, run no matter what - ((args:get-arg "-force")(set! runflag #t)) - ;; NOT_STARTED, run no matter what - ((equal? (test:get-state testdat) "NOT_STARTED")(set! runflag #t)) - ;; not -rerun and PASS, WARN or CHECK, do no run - ((and (or (not (args:get-arg "-rerun")) - (args:get-arg "-keepgoing")) - (member (test:get-status testdat) '("PASS" "WARN" "CHECK"))) - (set! runflag #f)) - ;; -rerun and status is one of the specifed, run it - ((and (args:get-arg "-rerun") - (let ((rerunlst (string-split (args:get-arg "-rerun") ","))) ;; FAIL, - (member (test:get-status testdat) rerunlst))) - (set! runflag #t)) - ;; -keepgoing, do not rerun FAIL - ((and (args:get-arg "-keepgoing") - (member (test:get-status testdat) '("FAIL"))) - (set! runflag #f)) - ((and (not (args:get-arg "-rerun")) - (member (test:get-status testdat) '("FAIL" "n/a"))) - (set! runflag #t)) - (else (set! runflag #f))) - (debug:print 6 "RUNNING => runflag: " runflag " STATE: " (test:get-state testdat) " STATUS: " (test:get-status testdat)) - (if (not runflag) - (if (not parent-test) - (debug:print 1 "NOTE: Not starting test " new-test-name " as it is state \"COMPLETED\" and status \"" (test:get-status testdat) "\", use -force to override")) - (let* ((get-prereqs-cmd (lambda () - (db-get-prereqs-not-met db run-id waiton))) ;; check before running .... - (launch-cmd (lambda () - (launch-test db run-id (args:get-arg ":runname") test-conf keyvallst test-name test-path itemdat args:arg-hash))) - (testrundat (list get-prereqs-cmd launch-cmd))) - (if (or (args:get-arg "-force") - (let ((preqs-not-yet-met ((car testrundat)))) - (debug:print 2 "Preqrequesites for " test-name ": " preqs-not-yet-met) - (null? preqs-not-yet-met))) ;; are there any tests that must be run before this one... - (if (not ((cadr testrundat))) ;; this is the line that launches the test to the remote host - (begin - (print "ERROR: Failed to launch the test. Exiting as soon as possible") - (set! *globalexitstatus* 1) ;; - (process-signal (current-process-id) signal/kill) - ;(exit 1) - )) - (if (not (args:get-arg "-keepgoing")) - (hash-table-set! *waiting-queue* new-test-name testrundat))))))) - ((KILLED) - (debug:print 1 "NOTE: " new-test-name " is already running or was explictly killed, use -force to launch it.")) - ((LAUNCHED REMOTEHOSTSTART RUNNING) - (if (> (- (current-seconds)(+ (db:test-get-event_time testdat) - (db:test-get-run_duration testdat))) - 100) ;; i.e. no update for more than 100 seconds - (begin - (debug:print 0 "WARNING: Test " test-name " appears to be dead. Forcing it to state INCOMPLETE and status STUCK/DEAD") - (test-set-status! db run-id test-name "INCOMPLETE" "STUCK/DEAD" itemdat "Test is stuck or dead" #f)) - (debug:print 2 "NOTE: " test-name " is already running"))) - (else (debug:print 0 "ERROR: Failed to launch test " new-test-name ". Unrecognised state " (test:get-state testdat)))))) - (if (not (null? tal)) - (loop (car tal)(cdr tal))))))))) - -(define (run-waiting-tests db) - (let ((numtries 0) - (last-try-time (current-seconds)) - (times (list 1))) ;; minutes to wait before trying again to kick off runs - ;; BUG this hack of brute force retrying works quite well for many cases but - ;; what is needed is to check the db for tests that have failed less than - ;; N times or never been started and kick them off again - (let loop ((waiting-test-names (hash-table-keys *waiting-queue*))) - (cond - ((not (runs:can-run-more-tests db)) - (thread-sleep! 2) - (loop waiting-test-names)) - ((null? waiting-test-names) - (debug:print 1 "All tests launched")) - (else - (set! numtries (+ numtries 1)) - (for-each (lambda (testname) - (if (runs:can-run-more-tests db) - (let* ((testdat (hash-table-ref *waiting-queue* testname)) - (prereqs ((car testdat))) - (ldb (if db db (open-db)))) - (debug:print 2 "prereqs remaining: " prereqs) - (if (null? prereqs) - (begin - (debug:print 2 "Prerequisites met, launching " testname) - ((cadr testdat)) - (hash-table-delete! *waiting-queue* testname))) - (if (not db) - (sqlite3:finalize! ldb))))) - waiting-test-names) - ;; (sleep 10) ;; no point in rushing things at this stage? - (loop (hash-table-keys *waiting-queue*))))))) + (let ((can-not-run-more (cond + ;; if max-concurrent-jobs is set and the number running is greater + ;; than it than cannot run more jobs + ((and max-concurrent-jobs + (string->number max-concurrent-jobs) + (>= num-running (string->number max-concurrent-jobs))) + (debug:print 0 "WARNING: Max running jobs exceeded, current number running: " num-running + ", max_concurrent_jobs: " max-concurrent-jobs) + #t) + ;; if job-group-limit is set and number of jobs in the group is greater + ;; than the limit then cannot run more jobs of this kind + ((and job-group-limit + (>= num-running-in-jobgroup job-group-limit)) + (debug:print 1 "WARNING: number of jobs " num-running-in-jobgroup + " in " jobgroup " exceeded, will not run " (tests:testqueue-get-testname test-record)) + #t) + (else #f)))) + (not can-not-run-more))))) ;;====================================================================== ;; New methodology. These routines will replace the above in time. For ;; now the code is duplicated. This stuff is initially used in the monitor ;; based code. @@ -856,14 +163,16 @@ (define (runs:run-tests db target runname test-patts item-patts user flags) (let* ((keys (db-get-keys db)) (keyvallst (keys:target->keyval keys target)) (run-id (runs:register-run db keys keyvallst runname "new" "n/a" user)) ;; test-name))) (deferred '()) ;; delay running these since they have a waiton clause - (keepgoing (hash-table-ref/default flags "-keepgoing" #f)) + ;; keepgoing is the defacto modality now, will add hit-n-run a bit later + ;; (keepgoing (hash-table-ref/default flags "-keepgoing" #f)) (test-names '()) (runconfigf (conc *toppath* "/runconfigs.config")) - (required-tests '())) + (required-tests '()) + (test-records (make-hash-table))) (set-megatest-env-vars db run-id) ;; these may be needed by the launching process (if (file-exists? runconfigf) (setup-env-defaults db runconfigf run-id *already-seen-runconfig-info* "pre-launch-env-vars") @@ -877,26 +186,57 @@ (set! tests (filter (lambda (test)(file-exists? (conc test "/testconfig"))) tests)) (set! test-names (append test-names (map (lambda (testp) (last (string-split testp "/"))) tests))))) - (string-split test-patts ",")) + (if test-patts (string-split test-patts ",")(list "%"))) ;; now remove duplicates (set! test-names (delete-duplicates test-names)) (debug:print 0 "INFO: test names " test-names) + ;; on the first pass or call to run-tests set FAILS to NOT_STARTED if + ;; -keepgoing is specified + (if (eq? *passnum* 0) + (begin + ;; have to delete test records where NOT_STARTED since they can cause -keepgoing to + ;; get stuck due to becoming inaccessible from a failed test. I.e. if test B depends + ;; on test A but test B reached the point on being registered as NOT_STARTED and test + ;; A failed for some reason then on re-run using -keepgoing the run can never complete. + (db:delete-tests-in-state db run-id "NOT_STARTED") + (db:set-tests-state-status db run-id test-names #f "FAIL" "NOT_STARTED" "FAIL"))) + ;; now add non-directly referenced dependencies (i.e. waiton) - ;; could cache all these since they need to be read again ... - ;; FIXME SOMEDAY (if (not (null? test-names)) (let loop ((hed (car test-names)) - (tal (cdr test-names))) - (let* ((config (test:get-testconfig hed #f)) + (tal (cdr test-names))) ;; 'return-procs tells the config reader to prep running system but return a proc + (let* ((config (test:get-testconfig hed 'return-procs)) (waitons (string-split (let ((w (config-lookup config "requirements" "waiton"))) (if w w ""))))) +;; (items (items:get-items-from-config config))) + (if (not (hash-table-ref/default test-records hed #f)) + (hash-table-set! test-records + hed (vector hed ;; 0 + config ;; 1 + waitons ;; 2 + (config-lookup config "requirements" "priority") ;; priority 3 + (let ((items (hash-table-ref/default config "items" #f)) ;; items 4 + (itemstable (hash-table-ref/default config "itemstable" #f))) + ;; if either items or items table is a proc return it so test running + ;; process can know to call items:get-items-from-config + ;; if either is a list and none is a proc go ahead and call get-items + ;; otherwise return #f - this is not an iterated test + (cond + ((procedure? items) items) ;; calc later + ((procedure? itemstable) itemstable) ;; calc later + ((or (list? items)(list? itemstable)) ;; calc now + (items:get-items-from-config config)) + (else #f))) ;; not iterated + #f ;; itemsdat 5 + ;; #f ;; spare + ))) (for-each (lambda (waiton) (if (and waiton (not (member waiton test-names))) (begin (set! required-tests (cons waiton required-tests)) @@ -906,217 +246,200 @@ (if (not (null? remtests)) (loop (car remtests)(cdr remtests))))))) (if (not (null? required-tests)) (debug:print 1 "INFO: Adding " required-tests " to the run queue")) - - ;; on the first pass or call to run-tests set FAILS to NOT_STARTED if - ;; -keepgoing is specified - (if (and (eq? *passnum* 0) - keepgoing) - (begin - ;; have to delete test records where NOT_STARTED since they can cause -keepgoing to - ;; get stuck due to becoming inaccessible from a failed test. I.e. if test B depends - ;; on test A but test B reached the point on being registered as NOT_STARTED and test - ;; A failed for some reason then on re-run using -keepgoing the run can never complete. - (db:delete-tests-in-state db run-id "NOT_STARTED") - (db:set-tests-state-status db run-id test-names #f "FAIL" "NOT_STARTED" "FAIL"))) - (set! *passnum* (+ *passnum* 1)) - (let loop ((numtimes 0)) - (for-each - (lambda (test-name) - (if (runs:can-run-more-tests db) - (run:test db run-id runname test-name keyvallst item-patts flags) - )) - (tests:sort-by-priority-and-waiton test-names)) - ;; (run-waiting-tests db) - (if keepgoing - (let ((estrem (db:estimated-tests-remaining db run-id))) - (if (and (> estrem 0) - (eq? *globalexitstatus* 0)) - (begin - (debug:print 1 "Keep going, estimated " estrem " tests remaining to run, will continue in 3 seconds ...") - (thread-sleep! 3) - (run-waiting-tests db) - (loop (+ numtimes 1))))))))) - -(define (run:test db run-id runname test-name keyvallst item-patts flags) - (debug:print 1 "Launching test " test-name) + ;; NOTE: these are all parent tests, items are not expanded yet. + (runs:run-tests-queue db run-id runname test-records keyvallst flags))) + +(define (runs:run-tests-queue db run-id runname test-records keyvallst flags) + ;; At this point the list of parent tests is expanded + ;; NB// Should expand items here and then insert into the run queue. + (debug:print 5 "test-records: " test-records ", keyvallst: " keyvallst) + (let ((sorted-test-names (tests:sort-by-priority-and-waiton test-records)) + (item-patts (hash-table-ref/default flags "-itempatt" #f))) + (let loop (; (numtimes 0) ;; shouldn't need this + (hed (car sorted-test-names)) + (tal (cdr sorted-test-names))) + (let* ((test-record (hash-table-ref test-records hed)) + (tconfig (tests:testqueue-get-testconfig test-record)) + (waitons (tests:testqueue-get-waitons test-record)) + (priority (tests:testqueue-get-priority test-record)) + (itemdat (tests:testqueue-get-itemdat test-record)) + (items (tests:testqueue-get-items test-record)) + (item-path (item-list->path itemdat))) + (debug:print 0 "WHERE TO DO: (items:get-items-from-config config)") + (debug:print 6 + "itemdat: " itemdat + "\n items: " items + "\n item-path: " item-path) + (cond + ((not items) ;; when false the test is ok to be handed off to launch (but not before) + (let ((have-resources (runs:can-run-more-tests db test-record)) ;; look at the test jobgroup and tot jobs running + (prereqs-not-met (db:get-prereqs-not-met db run-id waitons item-path))) + (if (and have-resources + (null? prereqs-not-met)) + ;; no loop - drop though and use the loop at the bottom + (run:test db run-id runname keyvallst test-record flags #f) + ;; else the run is stuck, temporarily or permanently + (let ((newtal (append tal (list hed)))) + ;; couldn't run, take a breather + (thread-sleep! 1) + (loop (car tal)(cdr tal)))))) + + ;; case where an items came in as a list been processed + ((and (list? items) ;; thus we know our items are already calculated + (not itemdat)) ;; and not yet expanded into the list of things to be done + (if (>= *verbosity* 1)(pp items)) + ;; (if (>= *verbosity* 5) + ;; (begin + ;; (print "items: ") (pp (item-assoc->item-list items)) + ;; (print "itemstable: ")(pp (item-table->item-list itemstable)))) + (for-each + (lambda (my-itemdat) + (let* ((new-test-record (let ((newrec (make-tests:testqueue))) + (vector-copy! test-record newrec) + newrec)) + (my-item-path (item-list->path my-itemdat)) + (item-matches (if item-patts ;; here we are filtering for matches with -itempatt + (let ((res #f)) ;; look through all the item-patts if defined, format is patt1,patt2,patt3 ... wildcard is % + (for-each + (lambda (patt) + (if (string-search (glob->regexp + (string-translate patt "%" "*")) + item-path) + (set! res #t))) + (string-split item-patts ",")) + res) + #t))) + (if item-matches ;; yes, we want to process this item + (let ((newtestname (conc hed "/" my-item-path))) + (tests:testqueue-set-items! new-test-record #f) + (tests:testqueue-set-itemdat! new-test-record my-itemdat) + (hash-table-set! test-records newtestname new-test-record) + (set! tal (cons newtestname tal)))))) ;; since these are itemized create new test names testname/itempath + items) + (loop (car tal)(cdr tal))) + + ;; if items is a proc then need to run items:get-items-from-config, get the list and loop + ;; - but only do that if resources exist to kick off the job + ((procedure? items) + (if (runs:can-run-more-tests db test-record) + (let ((items-list (items:get-items-from-config tconfig))) + (if (list? items-list) + (begin + (tests:testqueue-set-items test-record items-list) + (loop hed tal)) + (begin + (debug:print 0 "ERROR: The proc from reading the setup did not yield a list - please report this") + (exit 1)))) + (let ((newtal (append tal (list hed)))) + ;; if can't run more tests, lets take a breather + (thread-sleep! 1) + (loop (car newtal)(cdr newtal))))) + + ;; this case should not happen, added to help catch any bugs + ((and (list? items) itemdat) + (debug:print 0 "ERROR: Should not have a list of items in a test and the itemspath set - please report this") + (exit 1))) + + ;; we get here on "drop through" - loop for next test in queue + (if (null? tal) + (debug:print 1 "INFO: All tests launched") + (loop (car tal)(cdr tal))))))) + +;; parent-test is there as a placeholder for when parent-tests can be run as a setup step +(define (run:test db run-id runname keyvallst test-record flags parent-test) ;; All these vars might be referenced by the testconfig file reader - (setenv "MT_TEST_NAME" test-name) ;; - (setenv "MT_RUNNAME" runname) - (set-megatest-env-vars db run-id) ;; these may be needed by the launching process - (change-directory *toppath*) - (let* ((test-path (conc *toppath* "/tests/" test-name)) ;; could use test:get-testconfig here ... - (test-configf (conc test-path "/testconfig")) - (testexists (and (file-exists? test-configf)(file-read-access? test-configf))) - (test-conf (if testexists (read-config test-configf #f #t) (make-hash-table))) - (waiton (let ((w (config-lookup test-conf "requirements" "waiton"))) - (if (string? w)(string-split w)'()))) + (let* ((test-name (tests:testqueue-get-testname test-record)) + (test-waitons (tests:testqueue-get-waitons test-record)) + (test-conf (tests:testqueue-get-testconfig test-record)) + (itemdat (tests:testqueue-get-itemdat test-record)) + (test-path (conc *toppath* "/tests/" test-name)) ;; could use test:get-testconfig here ... (force (hash-table-ref/default flags "-force" #f)) (rerun (hash-table-ref/default flags "-rerun" #f)) - (keepgoing (hash-table-ref/default flags "-keepgoing" #f)) - ;; Are these tags still used? I don't think so... - ;;(tags (let ((t (config-lookup test-conf "setup" "tags"))) - ;; ;; we want our tags to be separated by commas and fully delimited by commas - ;; ;; so that queries with "like" can tie to the commas at either end of each tag - ;; ;; while also allowing the end user to freely use spaces and commas to separate tags - ;; (if (string? t)(string-substitute (regexp "[,\\s]+") "," (conc "," t ",") #t) - ;; '())))) - ) - (if (not testexists) - ;; if the test is ill defined spit out an error but keep going (different from how done previously - (debug:print 0 "ERROR: Can't find config file " test-configf) - ;; put top vars into convenient variables and open the db - (let* (;; db is always at *toppath*/db/megatest.db - (items (hash-table-ref/default test-conf "items" '())) - (itemstable (hash-table-ref/default test-conf "itemstable" '())) - (allitems (if (or (not (null? items))(not (null? itemstable))) - (append (item-assoc->item-list items) - (item-table->item-list itemstable)) - '(())))) ;; a list with one null list is a test with no items - ;; (runconfigf (conc *toppath* "/runconfigs.config"))) - (debug:print 1 "items: ") - (if (>= *verbosity* 1)(pp allitems)) - (if (>= *verbosity* 5) - (begin - (print "items: ")(pp (item-assoc->item-list items)) - (print "itemstable: ")(pp (item-table->item-list itemstable)))) - - ;; Comments are loaded by the test run, not at launch time (in general) - ;;(if (args:get-arg "-m") - ;; (db:set-comment-for-run db run-id (args:get-arg "-m"))) - - ;; Here is where the test_meta table is best updated - (runs:update-test_meta db test-name test-conf) - - ;; braindead work-around for poorly specified allitems list BUG!!! FIXME - (if (null? allitems)(set! allitems '(()))) - (let loop ((itemdat (car allitems)) - (tal (cdr allitems))) - ;; (lambda (itemdat) ;;; ((ripeness "overripe") (temperature "cool") (season "summer")) - ;; Handle lists of items - (let* ((item-path (item-list->path itemdat)) ;; (string-intersperse (map cadr itemdat) "/")) - (new-test-path (string-intersperse (cons test-path (map cadr itemdat)) "/")) - (new-test-name (if (equal? item-path "") test-name (conc test-name "/" item-path))) ;; just need it to be unique - (testdat #f) - (num-running (db:get-count-tests-running db)) - (max-concurrent-jobs (config-lookup *configdat* "setup" "max_concurrent_jobs")) - (parent-test (and (not (null? items))(equal? item-path ""))) - (single-test (and (null? items) (equal? item-path ""))) - (item-test (not (equal? item-path ""))) - ;; look through all the item-patts if defined, format is patt1,patt2,patt3 ... wildcard is % - (item-matches (if item-patts - (let ((res #f)) - (for-each - (lambda (patt) - (if (string-search (glob->regexp - (string-translate patt "%" "*")) - item-path) - (set! res #t))) - (string-split item-patts ",")) - res) - #t))) - (debug:print 3 "max-concurrent-jobs: " max-concurrent-jobs ", num-running: " num-running) - (if (and item-matches (runs:can-run-more-tests db)) - (begin - (let loop2 ((ts (db:get-test-info db run-id test-name item-path)) ;; #f) - (ct 0)) - (if (and (not ts) - (< ct 10)) - (begin - (register-test db run-id test-name item-path) - ;; Why did I set the comment here?!? POSSIBLE BUG BUT I'M REMOVING IT FOR NOW 10/23/2011 - ;; (db:test-set-comment db run-id test-name item-path "") - (loop2 (db:get-test-info db run-id test-name item-path) - (+ ct 1))) - (if ts - (set! testdat ts) - (begin - (debug:print 0 "WARNING: Couldn't register test " test-name " with item path " item-path ", skipping") - (if (not (null? tal)) - (loop (car tal)(cdr tal))))))) - (change-directory test-path) - ;; this block is here only to inform the user early on - - ;; Moving this to the run calling block - - ;; (if (file-exists? runconfigf) - ;; (setup-env-defaults db runconfigf run-id *already-seen-runconfig-info*) - ;; (debug:print 0 "WARNING: You do not have a run config file: " runconfigf)) - (debug:print 4 "run-id: " run-id " test-name: " test-name " item-path: " item-path " testdat: " (test:get-status testdat) " test-state: " (test:get-state testdat)) - (case (if force ;; (args:get-arg "-force") - 'NOT_STARTED - (if testdat - (string->symbol (test:get-state testdat)) - 'failed-to-insert)) - ((failed-to-insert) - (debug:print 0 "ERROR: Failed to insert the record into the db")) - ((NOT_STARTED COMPLETED) - (debug:print 6 "Got here, " (test:get-state testdat)) - (let ((runflag #f)) - (cond - ;; i.e. this is the parent test to a suite of items, never "run" it - (parent-test - (set! runflag #f)) - ;; -force, run no matter what - (force (set! runflag #t)) - ;; NOT_STARTED, run no matter what - ((equal? (test:get-state testdat) "NOT_STARTED")(set! runflag #t)) - ;; not -rerun and PASS, WARN or CHECK, do no run - ((and (or (not rerun) - keepgoing) - (member (test:get-status testdat) '("PASS" "WARN" "CHECK"))) - (set! runflag #f)) - ;; -rerun and status is one of the specifed, run it - ((and rerun - (let ((rerunlst (string-split rerun ","))) ;; FAIL, - (member (test:get-status testdat) rerunlst))) - (set! runflag #t)) - ;; -keepgoing, do not rerun FAIL - ((and keepgoing - (member (test:get-status testdat) '("FAIL"))) - (set! runflag #f)) - ((and (not rerun) - (member (test:get-status testdat) '("FAIL" "n/a"))) - (set! runflag #t)) - (else (set! runflag #f))) - (debug:print 6 "RUNNING => runflag: " runflag " STATE: " (test:get-state testdat) " STATUS: " (test:get-status testdat)) - (if (not runflag) - (if (not parent-test) - (debug:print 1 "NOTE: Not starting test " new-test-name " as it is state \"COMPLETED\" and status \"" (test:get-status testdat) "\", use -force to override")) - (let* ((get-prereqs-cmd (lambda () - (db-get-prereqs-not-met db run-id waiton))) ;; check before running .... - (launch-cmd (lambda () - (launch-test db run-id runname test-conf keyvallst test-name test-path itemdat flags))) - (testrundat (list get-prereqs-cmd launch-cmd))) - (if (or force - (let ((preqs-not-yet-met ((car testrundat)))) - (debug:print 2 "Preqrequesites for " test-name ": " preqs-not-yet-met) - (null? preqs-not-yet-met))) ;; are there any tests that must be run before this one... - (if (not ((cadr testrundat))) ;; this is the line that launches the test to the remote host - (begin - (print "ERROR: Failed to launch the test. Exiting as soon as possible") - (set! *globalexitstatus* 1) ;; - (process-signal (current-process-id) signal/kill) - ;(exit 1) - )) - (if (not keepgoing) - (hash-table-set! *waiting-queue* new-test-name testrundat))))))) - ((KILLED) - (debug:print 1 "NOTE: " new-test-name " is already running or was explictly killed, use -force to launch it.")) - ((LAUNCHED REMOTEHOSTSTART RUNNING) - (if (> (- (current-seconds)(+ (db:test-get-event_time testdat) - (db:test-get-run_duration testdat))) - 100) ;; i.e. no update for more than 100 seconds - (begin - (debug:print 0 "WARNING: Test " test-name " appears to be dead. Forcing it to state INCOMPLETE and status STUCK/DEAD") - (test-set-status! db run-id test-name "INCOMPLETE" "STUCK/DEAD" itemdat "Test is stuck or dead" #f)) - (debug:print 2 "NOTE: " test-name " is already running"))) - (else (debug:print 0 "ERROR: Failed to launch test " new-test-name ". Unrecognised state " (test:get-state testdat)))))) - (if (not (null? tal)) - (loop (car tal)(cdr tal))))))))) + (keepgoing (hash-table-ref/default flags "-keepgoing" #f))) + (debug:print 1 "Launching test " test-name) + (debug:print 5 + "test-config: " (hash-table->alist test-conf) + "\n itemdat: " itemdat + ) + ;; setting itemdat to a list if it is #f + (if (not itemdat)(set! itemdat '())) + (setenv "MT_TEST_NAME" test-name) ;; + (setenv "MT_RUNNAME" runname) + (set-megatest-env-vars db run-id) ;; these may be needed by the launching process + (change-directory *toppath*) + + ;; Here is where the test_meta table is best updated + (runs:update-test_meta db test-name test-conf) + + ;; (lambda (itemdat) ;;; ((ripeness "overripe") (temperature "cool") (season "summer")) + (let* ((item-path (item-list->path itemdat)) ;; (string-intersperse (map cadr itemdat) "/")) + (new-test-path (string-intersperse (cons test-path (map cadr itemdat)) "/")) + (new-test-name (if (equal? item-path "") test-name (conc test-name "/" item-path))) ;; just need it to be unique + (testdat (db:get-test-info db run-id test-name item-path))) + (if (not testdat) + (begin + (register-test db run-id test-name item-path) + (set! testdat (db:get-test-info db run-id test-name item-path)))) + (change-directory test-path) + (case (if force ;; (args:get-arg "-force") + 'NOT_STARTED + (if testdat + (string->symbol (test:get-state testdat)) + 'failed-to-insert)) + ((failed-to-insert) + (debug:print 0 "ERROR: Failed to insert the record into the db")) + ((NOT_STARTED COMPLETED) + (debug:print 6 "Got here, " (test:get-state testdat)) + (let ((runflag #f)) + (cond + ;; -force, run no matter what + (force (set! runflag #t)) + ;; NOT_STARTED, run no matter what + ((equal? (test:get-state testdat) "NOT_STARTED")(set! runflag #t)) + ;; not -rerun and PASS, WARN or CHECK, do no run + ((and (or (not rerun) + keepgoing) + (member (test:get-status testdat) '("PASS" "WARN" "CHECK"))) + (set! runflag #f)) + ;; -rerun and status is one of the specifed, run it + ((and rerun + (let ((rerunlst (string-split rerun ","))) ;; FAIL, + (member (test:get-status testdat) rerunlst))) + (set! runflag #t)) + ;; -keepgoing, do not rerun FAIL + ((and keepgoing + (member (test:get-status testdat) '("FAIL"))) + (set! runflag #f)) + ((and (not rerun) + (member (test:get-status testdat) '("FAIL" "n/a"))) + (set! runflag #t)) + (else (set! runflag #f))) + (debug:print 6 "RUNNING => runflag: " runflag " STATE: " (test:get-state testdat) " STATUS: " (test:get-status testdat)) + (if (not runflag) + (if (not parent-test) + (debug:print 1 "NOTE: Not starting test " new-test-name " as it is state \"COMPLETED\" and status \"" (test:get-status testdat) "\", use -force to override")) + ;; NOTE: No longer be checking prerequisites here! Will never get here unless prereqs are + ;; already met. + (if (not (launch-test db run-id runname test-conf keyvallst test-name test-path itemdat flags)) + (begin + (print "ERROR: Failed to launch the test. Exiting as soon as possible") + (set! *globalexitstatus* 1) ;; + (process-signal (current-process-id) signal/kill)))))) + ((KILLED) + (debug:print 1 "NOTE: " new-test-name " is already running or was explictly killed, use -force to launch it.")) + ((LAUNCHED REMOTEHOSTSTART RUNNING) + (if (> (- (current-seconds)(+ (db:test-get-event_time testdat) + (db:test-get-run_duration testdat))) + 600) ;; i.e. no update for more than 600 seconds + (begin + (debug:print 0 "WARNING: Test " test-name " appears to be dead. Forcing it to state INCOMPLETE and status STUCK/DEAD") + (test-set-status! db run-id test-name "INCOMPLETE" "STUCK/DEAD" itemdat "Test is stuck or dead" #f)) + (debug:print 2 "NOTE: " test-name " is already running"))) + (else (debug:print 0 "ERROR: Failed to launch test " new-test-name ". Unrecognised state " (test:get-state testdat))))))) ;;====================================================================== ;; END OF NEW STUFF ;;====================================================================== @@ -1213,14 +536,22 @@ ;;====================================================================== ;; Since many calls to a run require pretty much the same setup ;; this wrapper is used to reduce the replication of code (define (general-run-call switchname action-desc proc) - (if (not (args:get-arg ":runname")) - (begin - (debug:print 0 "ERROR: Missing required parameter for " switchname ", you must specify the run name with :runname runname") - (exit 2)) + (let ((runname (args:get-arg ":runname")) + (target (if (args:get-arg "-target") + (args:get-arg "-target") + (args:get-arg "-reqtarg")))) + (cond + ((not target) + (debug:print 0 "ERROR: Missing required parameter for " switchname ", you must specify the target with -target") + (exit 3)) + ((not runname) + (debug:print 0 "ERROR: Missing required parameter for " switchname ", you must specify the run name with :runname runname") + (exit 3)) + (else (let ((db #f) (keys #f)) (if (not (setup-for-run)) (begin (debug:print 0 "Failed to setup, exiting") @@ -1245,13 +576,13 @@ (exit 1)) ;; Extract out stuff needed in most or many calls ;; here then call proc (let* ((keynames (map key:get-fieldname keys)) (keyvallst (keys->vallist keys #t))) - (proc db keys keynames keyvallst))) + (proc db target runname keys keynames keyvallst))) (sqlite3:finalize! db) - (set! *didsomething* #t)))) + (set! *didsomething* #t)))))) ;;====================================================================== ;; Rollup runs ;;====================================================================== @@ -1265,10 +596,11 @@ (for-each (lambda (key) (let* ((idx (cadr key)) (fld (car key)) (val (config-lookup test-conf "test_meta" fld))) + ;; (debug:print 5 "idx: " idx " fld: " fld " val: " val) (if (and val (not (equal? (vector-ref currrecord idx) val))) (begin (print "Updating " test-name " " fld " to " val) (db:testmeta-update-field db test-name fld val))))) '(("author" 2)("owner" 3)("description" 4)("reviewed" 5)("tags" 9))))) @@ -1293,11 +625,11 @@ (new-run-id (runs:register-run db keys keyvallst runname "new" "n/a" user)) (prev-tests (test:get-matching-previous-test-run-records db new-run-id "%" "%")) (curr-tests (db-get-tests-for-run db new-run-id "%" "%" '() '())) (curr-tests-hash (make-hash-table))) (db:update-run-event_time db new-run-id) - ;; index the already saved tests by testname and itempath in curr-tests-hash + ;; index the already saved tests by testname and itemdat in curr-tests-hash (for-each (lambda (testdat) (let* ((testname (db:test-get-testname testdat)) (item-path (db:test-get-item-path testdat)) (full-name (conc testname "/" item-path))) ADDED test_records.scm Index: test_records.scm ================================================================== --- /dev/null +++ test_records.scm @@ -0,0 +1,16 @@ +;; make-vector-record tests testqueue testname testconfig waitons priority items +(define (make-tests:testqueue)(make-vector 6 #f)) +(define-inline (tests:testqueue-get-testname vec) (vector-ref vec 0)) +(define-inline (tests:testqueue-get-testconfig vec) (vector-ref vec 1)) +(define-inline (tests:testqueue-get-waitons vec) (vector-ref vec 2)) +(define-inline (tests:testqueue-get-priority vec) (vector-ref vec 3)) +;; items: #f=no items, list=list of items remaining, proc=need to call to get items +(define-inline (tests:testqueue-get-items vec) (vector-ref vec 4)) +(define-inline (tests:testqueue-get-itemdat vec) (vector-ref vec 5)) + +(define-inline (tests:testqueue-set-testname! vec val)(vector-set! vec 0 val)) +(define-inline (tests:testqueue-set-testconfig! vec val)(vector-set! vec 1 val)) +(define-inline (tests:testqueue-set-waitons! vec val)(vector-set! vec 2 val)) +(define-inline (tests:testqueue-set-priority! vec val)(vector-set! vec 3 val)) +(define-inline (tests:testqueue-set-items! vec val)(vector-set! vec 4 val)) +(define-inline (tests:testqueue-set-itemdat! vec val)(vector-set! vec 5 val)) ADDED tests.scm Index: tests.scm ================================================================== --- /dev/null +++ tests.scm @@ -0,0 +1,428 @@ +(use sqlite3 srfi-1 posix regex regex-case srfi-69 dot-locking) +(import (prefix sqlite3 sqlite3:)) + +(declare (unit tests)) +(declare (uses db)) +(declare (uses common)) +(declare (uses items)) +(declare (uses runconfig)) + +(include "common_records.scm") +(include "key_records.scm") +(include "db_records.scm") +(include "run_records.scm") +(include "test_records.scm") + + +(define (register-test db run-id test-name item-path) + (let ((item-paths (if (equal? item-path "") + (list item-path) + (list item-path "")))) + (for-each + (lambda (pth) + (sqlite3:execute db "INSERT OR IGNORE INTO tests (run_id,testname,event_time,item_path,state,status) VALUES (?,?,strftime('%s','now'),?,'NOT_STARTED','n/a');" + run-id + test-name + pth + ;; (conc "," (string-intersperse tags ",") ",") + )) + item-paths ))) + +;; get the previous record for when this test was run where all keys match but runname +;; returns #f if no such test found, returns a single test record if found +(define (test:get-previous-test-run-record db run-id test-name item-path) + (let* ((keys (db:get-keys db)) + (selstr (string-intersperse (map (lambda (x)(vector-ref x 0)) keys) ",")) + (qrystr (string-intersperse (map (lambda (x)(conc (vector-ref x 0) "=?")) keys) " AND ")) + (keyvals #f)) + ;; first look up the key values from the run selected by run-id + (sqlite3:for-each-row + (lambda (a . b) + (set! keyvals (cons a b))) + db + (conc "SELECT " selstr " FROM runs WHERE id=? ORDER BY event_time DESC;") run-id) + (if (not keyvals) + #f + (let ((prev-run-ids '())) + (apply sqlite3:for-each-row + (lambda (id) + (set! prev-run-ids (cons id prev-run-ids))) + db + (conc "SELECT id FROM runs WHERE " qrystr " AND id != ?;") (append keyvals (list run-id))) + ;; for each run starting with the most recent look to see if there is a matching test + ;; if found then return that matching test record + (debug:print 4 "selstr: " selstr ", qrystr: " qrystr ", keyvals: " keyvals ", previous run ids found: " prev-run-ids) + (if (null? prev-run-ids) #f + (let loop ((hed (car prev-run-ids)) + (tal (cdr prev-run-ids))) + (let ((results (db-get-tests-for-run db hed test-name item-path '() '()))) + (debug:print 4 "Got tests for run-id " run-id ", test-name " test-name ", item-path " item-path ": " results) + (if (and (null? results) + (not (null? tal))) + (loop (car tal)(cdr tal)) + (if (null? results) #f + (car results)))))))))) + +;; get the previous records for when these tests were run where all keys match but runname +;; NB// Merge this with test:get-previous-test-run-records? This one looks for all matching tests +;; can use wildcards. +(define (test:get-matching-previous-test-run-records db run-id test-name item-path) + (let* ((keys (db:get-keys db)) + (selstr (string-intersperse (map (lambda (x)(vector-ref x 0)) keys) ",")) + (qrystr (string-intersperse (map (lambda (x)(conc (vector-ref x 0) "=?")) keys) " AND ")) + (keyvals #f) + (tests-hash (make-hash-table))) + ;; first look up the key values from the run selected by run-id + (sqlite3:for-each-row + (lambda (a . b) + (set! keyvals (cons a b))) + db + (conc "SELECT " selstr " FROM runs WHERE id=? ORDER BY event_time DESC;") run-id) + (if (not keyvals) + '() + (let ((prev-run-ids '())) + (apply sqlite3:for-each-row + (lambda (id) + (set! prev-run-ids (cons id prev-run-ids))) + db + (conc "SELECT id FROM runs WHERE " qrystr " AND id != ?;") (append keyvals (list run-id))) + ;; collect all matching tests for the runs then + ;; extract the most recent test and return that. + (debug:print 4 "selstr: " selstr ", qrystr: " qrystr ", keyvals: " keyvals + ", previous run ids found: " prev-run-ids) + (if (null? prev-run-ids) '() ;; no previous runs? return null + (let loop ((hed (car prev-run-ids)) + (tal (cdr prev-run-ids))) + (let ((results (db-get-tests-for-run db hed test-name item-path '() '()))) + (debug:print 4 "Got tests for run-id " run-id ", test-name " test-name + ", item-path " item-path " results: " (intersperse results "\n")) + ;; Keep only the youngest of any test/item combination + (for-each + (lambda (testdat) + (let* ((full-testname (conc (db:test-get-testname testdat) "/" (db:test-get-item-path testdat))) + (stored-test (hash-table-ref/default tests-hash full-testname #f))) + (if (or (not stored-test) + (and stored-test + (> (db:test-get-event_time testdat)(db:test-get-event_time stored-test)))) + ;; this test is younger, store it in the hash + (hash-table-set! tests-hash full-testname testdat)))) + results) + (if (null? tal) + (map cdr (hash-table->alist tests-hash)) ;; return a list of the most recent tests + (loop (car tal)(cdr tal)))))))))) + +(define (test-set-status! db run-id test-name state status itemdat-or-path comment dat) + (let* ((real-status status) + (item-path (if (string? itemdat-or-path) itemdat-or-path (item-list->path itemdat-or-path))) + (testdat (db:get-test-info db run-id test-name item-path)) + (test-id (if testdat (db:test-get-id testdat) #f)) + (otherdat (if dat dat (make-hash-table))) + ;; before proceeding we must find out if the previous test (where all keys matched except runname) + ;; was WAIVED if this test is FAIL + (waived (if (equal? status "FAIL") + (let ((prev-test (test:get-previous-test-run-record db run-id test-name item-path))) + (if prev-test ;; true if we found a previous test in this run series + (let ((prev-status (db:test-get-status prev-test)) + (prev-state (db:test-get-state prev-test)) + (prev-comment (db:test-get-comment prev-test))) + (debug:print 4 "prev-status " prev-status ", prev-state " prev-state ", prev-comment " prev-comment) + (if (and (equal? prev-state "COMPLETED") + (equal? prev-status "WAIVED")) + prev-comment ;; waived is either the comment or #f + #f)) + #f)) + #f))) + (if waived (set! real-status "WAIVED")) + (debug:print 4 "real-status " real-status ", waived " waived ", status " status) + + ;; update the primary record IF state AND status are defined + (if (and state status) + (sqlite3:execute db "UPDATE tests SET state=?,status=?,event_time=strftime('%s','now') WHERE run_id=? AND testname=? AND item_path=?;" + state real-status run-id test-name item-path)) + + ;; if status is "AUTO" then call rollup + (if (and test-id state status (equal? status "AUTO")) + (db:test-data-rollup db test-id)) + + ;; add metadata (need to do this way to avoid SQL injection issues) + + ;; :first_err + ;; (let ((val (hash-table-ref/default otherdat ":first_err" #f))) + ;; (if val + ;; (sqlite3:execute db "UPDATE tests SET first_err=? WHERE run_id=? AND testname=? AND item_path=?;" val run-id test-name item-path))) + ;; + ;; ;; :first_warn + ;; (let ((val (hash-table-ref/default otherdat ":first_warn" #f))) + ;; (if val + ;; (sqlite3:execute db "UPDATE tests SET first_warn=? WHERE run_id=? AND testname=? AND item_path=?;" val run-id test-name item-path))) + + (let ((category (hash-table-ref/default otherdat ":category" "")) + (variable (hash-table-ref/default otherdat ":variable" "")) + (value (hash-table-ref/default otherdat ":value" #f)) + (expected (hash-table-ref/default otherdat ":expected" #f)) + (tol (hash-table-ref/default otherdat ":tol" #f)) + (units (hash-table-ref/default otherdat ":units" "")) + (dcomment (hash-table-ref/default otherdat ":comment" ""))) + (debug:print 4 + "category: " category ", variable: " variable ", value: " value + ", expected: " expected ", tol: " tol ", units: " units) + (if (and value expected tol) ;; all three required + (db:csv->test-data db test-id + (conc category "," + variable "," + value "," + expected "," + tol "," + units "," + dcomment ",")))) + + ;; need to update the top test record if PASS or FAIL and this is a subtest + (if (and (not (equal? item-path "")) + (or (equal? status "PASS") + (equal? status "WARN") + (equal? status "FAIL") + (equal? status "WAIVED") + (equal? status "RUNNING"))) + (begin + (sqlite3:execute + db + "UPDATE tests + SET fail_count=(SELECT count(id) FROM tests WHERE run_id=? AND testname=? AND item_path != '' AND status='FAIL'), + pass_count=(SELECT count(id) FROM tests WHERE run_id=? AND testname=? AND item_path != '' AND (status='PASS' OR status='WARN' OR status='WAIVED')) + WHERE run_id=? AND testname=? AND item_path='';" + run-id test-name run-id test-name run-id test-name) + (if (equal? status "RUNNING") ;; running takes priority over all other states, force the test state to RUNNING + (sqlite3:execute db "UPDATE tests SET state=? WHERE run_id=? AND testname=? AND item_path='';" run-id test-name) + (sqlite3:execute + db + "UPDATE tests + SET state=CASE WHEN (SELECT count(id) FROM tests WHERE run_id=? AND testname=? AND item_path != '' AND state in ('RUNNING','NOT_STARTED')) > 0 THEN + 'RUNNING' + ELSE 'COMPLETED' END, + status=CASE WHEN fail_count > 0 THEN 'FAIL' WHEN pass_count > 0 AND fail_count=0 THEN 'PASS' ELSE 'UNKNOWN' END + WHERE run_id=? AND testname=? AND item_path='';" + run-id test-name run-id test-name)))) + (if (or (and (string? comment) + (string-match (regexp "\\S+") comment)) + waived) + (sqlite3:execute db "UPDATE tests SET comment=? WHERE run_id=? AND testname=? AND item_path=?;" + (if waived waived comment) run-id test-name item-path)) + )) + +(define (test-set-log! db run-id test-name itemdat logf) + (let ((item-path (item-list->path itemdat))) + (sqlite3:execute db "UPDATE tests SET final_logf=? WHERE run_id=? AND testname=? AND item_path=?;" + logf run-id test-name item-path))) + +(define (test-set-toplog! db run-id test-name logf) + (sqlite3:execute db "UPDATE tests SET final_logf=? WHERE run_id=? AND testname=? AND item_path='';" + logf run-id test-name)) + +(define (tests:summarize-items db run-id test-name force) + ;; if not force then only update the record if one of these is true: + ;; 1. logf is "log/final.log + ;; 2. logf is same as outputfilename + (let ((outputfilename (conc "megatest-rollup-" test-name ".html")) + (orig-dir (current-directory)) + (logf #f)) + (sqlite3:for-each-row + (lambda (path final_logf) + (set! logf final_logf) + (if (directory? path) + (begin + (print "Found path: " path) + (change-directory path)) + ;; (set! outputfilename (conc path "/" outputfilename))) + (print "No such path: " path))) + db + "SELECT rundir,final_logf FROM tests WHERE run_id=? AND testname=? AND item_path='';" + run-id test-name) + (print "summarize-items with logf " logf) + (if (or (equal? logf "logs/final.log") + (equal? logf outputfilename) + force) + (begin + (if (obtain-dot-lock outputfilename 1 20 30) ;; retry every second for 20 seconds, call it dead after 30 seconds and steal the lock + (print "Obtained lock for " outputfilename) + (print "Failed to obtain lock for " outputfilename)) + (let ((oup (open-output-file outputfilename)) + (counts (make-hash-table)) + (statecounts (make-hash-table)) + (outtxt "") + (tot 0)) + (with-output-to-port + oup + (lambda () + (set! outtxt (conc outtxt "Summary: " test-name + "

Summary for " test-name "

")) + (sqlite3:for-each-row + (lambda (id itempath state status run_duration logf comment) + (hash-table-set! counts status (+ 1 (hash-table-ref/default counts status 0))) + (hash-table-set! statecounts state (+ 1 (hash-table-ref/default statecounts state 0))) + (set! outtxt (conc outtxt "" + " " itempath "" + "" state "" + "" status "" + "" (if (equal? comment "") + " " + comment) "" + ""))) + db + "SELECT id,item_path,state,status,run_duration,final_logf,comment FROM tests WHERE run_id=? AND testname=? AND item_path != '';" + run-id test-name) + + (print "
") + ;; Print out stats for status + (set! tot 0) + (print "") + (for-each (lambda (state) + (set! tot (+ tot (hash-table-ref statecounts state))) + (print "")) + (hash-table-keys statecounts)) + (print "

State stats

" state "" (hash-table-ref statecounts state) "
Total" tot "
") + (print "
") + ;; Print out stats for state + (set! tot 0) + (print "") + (for-each (lambda (status) + (set! tot (+ tot (hash-table-ref counts status))) + (print "")) + (hash-table-keys counts)) + (print "

Status stats

" status + "" (hash-table-ref counts status) "
Total" tot "
") + (print "
") + + (print "" + "" + outtxt "
ItemStateStatusComment
") + (release-dot-lock outputfilename))) + (close-output-port oup) + (change-directory orig-dir) + (test-set-toplog! db run-id test-name outputfilename) + ))))) + +(define (get-all-legal-tests) + (let* ((tests (glob (conc *toppath* "/tests/*"))) + (res '())) + (debug:print 4 "INFO: Looking at tests " (string-intersperse tests ",")) + (for-each (lambda (testpath) + (if (file-exists? (conc testpath "/testconfig")) + (set! res (cons (last (string-split testpath "/")) res)))) + tests) + res)) + +(define (test:get-testconfig test-name system-allowed) + (let* ((test-path (conc *toppath* "/tests/" test-name)) + (test-configf (conc test-path "/testconfig")) + (testexists (and (file-exists? test-configf)(file-read-access? test-configf)))) + (if testexists + (read-config test-configf #f system-allowed environ-patt: (if system-allowed + "pre-launch-env-vars" + #f)) + #f))) + +;; sort tests by priority and waiton +;; Move test specific stuff to a test unit FIXME one of these days +(define (tests:sort-by-priority-and-waiton test-records) + (let ((mungepriority (lambda (priority) + (if priority + (let ((tmp (any->number priority))) + (if tmp tmp (begin (debug:print 0 "ERROR: bad priority value " priority ", using 0") 0))) + 0)))) + (sort + (hash-table-keys test-records) ;; avoid dealing with deleted tests, look at the hash table + (lambda (a b) + (let* ((a-record (hash-table-ref test-records a)) + (b-record (hash-table-ref test-records b)) + (a-waitons (tests:testqueue-get-waitons a-record)) + (b-waitons (tests:testqueue-get-waitons b-record)) + (a-config (tests:testqueue-get-testconfig a-record)) + (b-config (tests:testqueue-get-testconfig b-record)) + (a-raw-pri (config-lookup a-config "requirements" "priority")) + (b-raw-pri (config-lookup b-config "requirements" "priority")) + (a-priority (mungepriority a-raw-pri)) + (b-priority (mungepriority b-raw-pri))) + ;; (debug:print 5 "sort-by-priority-and-waiton, a: " a " b: " b + ;; "\n a-record: " a-record + ;; "\n b-record: " b-record + ;; "\n a-waitons: " a-waitons + ;; "\n b-waitons: " b-waitons + ;; "\n a-config: " (hash-table->alist a-config) + ;; "\n b-config: " (hash-table->alist b-config) + ;; "\n a-raw-pri: " a-raw-pri + ;; "\n b-raw-pri: " b-raw-pri + ;; "\n a-priority: " a-priority + ;; "\n b-priority: " b-priority) + (tests:testqueue-set-priority! a-record a-priority) + (tests:testqueue-set-priority! b-record b-priority) + (if (and a-waitons (member (tests:testqueue-get-testname b-record) a-waitons)) + #f ;; cannot have a which is waiting on b happening before b + (if (and b-waitons (member (tests:testqueue-get-testname a-record) b-waitons)) + #t ;; this is the correct order, b is waiting on a and b is before a + (if (> a-priority b-priority) + #t ;; if a is a higher priority than b then we are good to go + #f)))))))) + + +;;====================================================================== +;; test steps +;;====================================================================== + +(define (teststep-set-status! db run-id test-name teststep-name state-in status-in itemdat comment logfile) + (debug:print 4 "run-id: " run-id " test-name: " test-name) + (let* ((state (check-valid-items "state" state-in)) + (status (check-valid-items "status" status-in)) + (item-path (item-list->path itemdat)) + (testdat (db:get-test-info db run-id test-name item-path))) + (debug:print 5 "testdat: " testdat) + (if (and testdat ;; if the section exists then force specification BUG, I don't like how this works. + (or (not state)(not status))) + (debug:print 0 "WARNING: Invalid " (if status "status" "state") + " value \"" (if status state-in status-in) "\", update your validvalues section in megatest.config")) + (if testdat + (let ((test-id (test:get-id testdat))) + ;; FIXME - this should not update the logfile unless it is specified. + (sqlite3:execute db + "INSERT OR REPLACE into test_steps (test_id,stepname,state,status,event_time,comment,logfile) VALUES(?,?,?,?,strftime('%s','now'),?,?);" + test-id teststep-name state-in status-in (if comment comment "") (if logfile logfile ""))) + (debug:print 0 "ERROR: Can't update " test-name " for run " run-id " -> no such test in db")))) + +(define (test-get-kill-request db run-id test-name itemdat) + (let* ((item-path (item-list->path itemdat)) + (testdat (db:get-test-info db run-id test-name item-path))) + (equal? (test:get-state testdat) "KILLREQ"))) + +(define (test-set-meta-info db run-id testname itemdat) + (let ((item-path (item-list->path itemdat)) + (cpuload (get-cpu-load)) + (hostname (get-host-name)) + (diskfree (get-df (current-directory))) + (uname (get-uname "-srvpio")) + (runpath (current-directory))) + (sqlite3:execute db "UPDATE tests SET host=?,cpuload=?,diskfree=?,uname=?,rundir=? WHERE run_id=? AND testname=? AND item_path=?;" + hostname + cpuload + diskfree + uname + runpath + run-id + testname + item-path))) + +(define (test-update-meta-info db run-id testname itemdat minutes cpuload diskfree tmpfree) + (let ((item-path (item-list->path itemdat))) + (if (not item-path)(begin (debug:print 0 "WARNING: ITEMPATH not set.") (set! item-path ""))) + ;; (let ((testinfo (db:get-test-info db run-id testname item-path))) + ;; (if (and (not (equal? (db:test-get-status testinfo) "COMPLETED")) + ;; (not (equal? (db:test-get-status testinfo) "KILLREQ")) + (sqlite3:execute + db + "UPDATE tests SET cpuload=?,diskfree=?,run_duration=?,state='RUNNING' WHERE run_id=? AND testname=? AND item_path=? AND state NOT IN ('COMPLETED','KILLREQ','KILLED');" + cpuload + diskfree + minutes + run-id + testname + item-path))) +