Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -1,38 +1,40 @@ 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 +GUISRCF = dashboard.scm dashboard-tests.scm dashboard-guimonitor.scm dashboard-main.scm OFILES = $(SRCFILES:%.scm=%.o) GOFILES = $(GUISRCF:%.scm=%.o) 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 dashboard-main.o monitor.o dashboard.o megatest.o : db_records.scm +tests.o runs.o dashboard.o dashboard-tests.o dashboard-main.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 @@ -48,18 +50,16 @@ # install dashboard as dboard so wrapper script can be called dashboard $(PREFIX)/bin/dboard : dboard $(FILES) cp dboard $(PREFIX)/bin/dboard utils/mk_dashboard_wrapper $(PREFIX) > $(PREFIX)/bin/dashboard chmod a+x $(PREFIX)/bin/dashboard - utils/mk_dashboard_wrapper $(PREFIX) > $(PREFIX)/bin/dashboard - chmod a+x $(PREFIX)/bin/dashboard install : bin $(PREFIX)/bin/megatest $(PREFIX)/bin/dboard $(PREFIX)/bin/dashboard $(HELPERS) $(PREFIX)/bin/nbfake bin : - mkdir $(PREFIX)/bin + mkdir -p $(PREFIX)/bin test: tests/tests.scm cd tests;csi -I .. -b -n tests.scm clean : rm -f $(OFILES) $(GOFILES) megatest dboard dboard.o megatest.o DELETED cells.scm Index: cells.scm ================================================================== --- cells.scm +++ /dev/null @@ -1,38 +0,0 @@ -(require-library iup canvas-draw canvas-draw-iup) - -(module cells-test - (cells-dialog) - (import - scheme chicken extras - iup canvas-draw canvas-draw-iup - (only canvas-draw-base pointer->canvas)) - -(define ncols 8) -(define nlins 8) -(define width 32) -(define height 32) - -(define (render-cell handle i j x-min x-max y-min y-max canvas) - (set! (canvas-foreground canvas) - (if (or (and (odd? i) (odd? j)) (and (even? i) (even? j))) - #xffffff - #x000000)) - (canvas-box! canvas x-min x-max y-min y-max)) - -(define cells-dialog - (dialog - #:title "Cells Test" - (cells - #:rastersize (format "~sx~s" (* ncols width) (* nlins height)) - #:ncols-cb (lambda _ ncols) #:width-cb (lambda _ width) - #:nlines-cb (lambda _ nlins) #:height-cb (lambda _ height) - #:draw-cb - (make-cells-draw-cb render-cell)))) -) - -(import - (only iup show main-loop) - cells-test) - -(show cells-dialog) -(main-loop) Index: configf.scm ================================================================== --- configf.scm +++ configf.scm @@ -40,10 +40,22 @@ (define (config:eval-string-in-environment str) (let ((cmdres (cmd-run->list (conc "echo " str)))) (if (null? cmdres) "" (caar cmdres)))) +;;====================================================================== +;; Make the regexp's needed globally available +;;====================================================================== + +(define configf:include-rx (regexp "^\\[include\\s+(.*)\\]\\s*$")) +(define configf:section-rx (regexp "^\\[(.*)\\]\\s*$")) +(define configf:blank-l-rx (regexp "^\\s*$")) +(define configf:key-sys-pr (regexp "^(\\S+)\\s+\\[system\\s+(\\S+.*)\\]\\s*$")) +(define configf:key-val-pr (regexp "^(\\S+)\\s+(.*)$")) +(define configf:comment-rx (regexp "^\\s*#.*")) +(define configf:cont-ln-rx (regexp "^(\\s+)(\\S+.*)$")) + ;; read a config file, returns hash table of alists ;; adds to ht if given (must be #f otherwise) ;; envion-patt is a regex spec that identifies sections that will be eval'd ;; in the environment on the fly @@ -50,18 +62,11 @@ (define (read-config path ht allow-system #!key (environ-patt #f)) (debug:print 4 "INFO: read-config " path " allow-system " allow-system " environ-patt " environ-patt) (if (not (file-exists? path)) (if (not ht)(make-hash-table) ht) (let ((inp (open-input-file path)) - (res (if (not ht)(make-hash-table) ht)) - (include-rx (regexp "^\\[include\\s+(.*)\\]\\s*$")) - (section-rx (regexp "^\\[(.*)\\]\\s*$")) - (blank-l-rx (regexp "^\\s*$")) - (key-sys-pr (regexp "^(\\S+)\\s+\\[system\\s+(\\S+.*)\\]\\s*$")) - (key-val-pr (regexp "^(\\S+)\\s+(.*)$")) - (comment-rx (regexp "^\\s*#.*")) - (cont-ln-rx (regexp "^(\\s+)(\\S+.*)$"))) + (res (if (not ht)(make-hash-table) ht))) (let loop ((inl (read-line inp)) (curr-section-name "default") (var-flag #f);; turn on for key-var-pr and cont-ln-rx, turn off elsewhere (lead #f)) (if (eof-object? inl) @@ -68,33 +73,39 @@ (begin (close-input-port inp) res) (regex-case inl - (comment-rx _ (loop (read-line inp) curr-section-name #f #f)) - (blank-l-rx _ (loop (read-line inp) curr-section-name #f #f)) - (include-rx ( x include-file ) (begin + (configf:comment-rx _ (loop (read-line inp) curr-section-name #f #f)) + (configf:blank-l-rx _ (loop (read-line inp) curr-section-name #f #f)) + (configf:include-rx ( x include-file ) (begin (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 " "))))) - (hash-table-set! res curr-section-name - (config:assoc-safe-add alist key val)) - (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 '())) + (configf:section-rx ( x section-name ) (loop (read-line inp) section-name #f #f)) + (configf:key-sys-pr ( x key cmd ) (if allow-system + (let ((alist (hash-table-ref/default res curr-section-name '())) + (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 + (case allow-system + ((return-procs) val-proc) + ((return-string) cmd) + (else (val-proc))))) + (loop (read-line inp) curr-section-name #f #f)) + (loop (read-line inp) curr-section-name #f #f))) + (configf: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 (config:eval-string-in-environment val) val))) (if envar @@ -103,11 +114,11 @@ (setenv key realval))) (hash-table-set! res curr-section-name (config:assoc-safe-add alist key realval)) (loop (read-line inp) curr-section-name key #f))) ;; if a continued line - (cont-ln-rx ( x whsp val ) (let ((alist (hash-table-ref/default res curr-section-name '()))) + (configf:cont-ln-rx ( x whsp val ) (let ((alist (hash-table-ref/default res curr-section-name '()))) (if var-flag ;; if set to a string then we have a continued var (let ((newval (conc (config-lookup res curr-section-name var-flag) "\n" ;; trim lead from the incoming whsp to support some indenting. (if lead @@ -136,17 +147,164 @@ (define (config-lookup cfgdat section var) (let ((sectdat (hash-table-ref/default cfgdat section '()))) (if (null? sectdat) #f (let ((match (assoc var sectdat))) - (if match + (if match ;; (and match (list? match)(> (length match) 1)) (cadr match) #f)) ))) + +(define (configf:section-vars cfgdat section) + (let ((sectdat (hash-table-ref/default cfgdat section '()))) + (if (null? sectdat) + '() + (map car sectdat)))) (define (setup) (let* ((configf (find-config)) (config (if configf (read-config configf #f #t) #f))) (if config (setenv "RUN_AREA_HOME" (pathname-directory configf))) config)) + +;;====================================================================== +;; Non destructive writing of config file +;;====================================================================== + +(define (configf:compress-multi-lines fdat) + ;; step 1.5 - compress any continued lines + (if (null? fdat) fdat + (let loop ((hed (car fdat)) + (tal (cdr fdat)) + (cur "") + (led #f) + (res '())) + ;; ALL WHITESPACE LEADING LINES ARE TACKED ON!! + ;; 1. remove led whitespace + ;; 2. tack on to hed with "\n" + (let ((match (string-match configf:cont-ln-rx hed))) + (if match ;; blast! have to deal with a multiline + (let* ((lead (cadr match)) + (lval (caddr match)) + (newl (conc cur "\n" lval))) + (if (not led)(set! led lead)) + (if (null? tal) + (set! fdat (append fdat (list newl))) + (loop (car tal)(cdr tal) newl led res))) ;; NB// not tacking newl onto res + (let ((newres (if led + (append res (list cur hed)) + (append res (list hed))))) + ;; prev was a multiline + (if (null? tal) + newres + (loop (car tal)(cdr tal) "" #f newres)))))))) + +;; note: I'm cheating a little here. I merely replace "\n" with "\n " +(define (configf:expand-multi-lines fdat) + ;; step 1.5 - compress any continued lines + (if (null? fdat) fdat + (let loop ((hed (car fdat)) + (tal (cdr fdat)) + (res '())) + (let ((newres (append res (list (string-substitute (regexp "\n") "\n " hed #t))))) + (if (null? tal) + newres + (loop (car tal)(cdr tal) newres)))))) + +(define (configf:file->list fname) + (if (file-exists? fname) + (let ((inp (open-input-file fname))) + (let loop ((inl (read-line inp)) + (res '())) + (if (eof-object? inl) + (begin + (close-input-port inp) + (reverse res)) + (loop (read-line inp)(cons inl))))) + '())) + +;;====================================================================== +;; Write a config +;; 0. Given a refererence data structure "indat" +;; 1. Open the output file and read it into a list +;; 2. Flatten any multiline entries +;; 3. Modify values per contents of "indat" and remove absent values +;; 4. Append new values to the section (immediately after last legit entry) +;; 5. Write out the new list +;;====================================================================== + +(define (configf:write-config indat fname #!key (required-sections '())) + (let* (;; step 1: Open the output file and read it into a list + (fdat (configf:file->list fname)) + (refdat (make-hash-table)) + (sechash (make-hash-table)) ;; current section hash, init with hash for "default" section + (new #f) ;; put the line to be used in new, if it is to be deleted the set new to #f + (secname #f)) + + ;; step 2: Flatten multiline entries + (if (not (null? fdat))(set! fdat (configf:compress-multi-line fdat))) + + ;; step 3: Modify values per contents of "indat" and remove absent values + (if (not (null? fdat)) + (let loop ((hed (car fdat)) + (tal (cadr fdat)) + (res '()) + (lnum 0)) + (regex-case + hed + (configf:comment-rx _ (set! res (append res (list hed)))) ;; (loop (read-line inp) curr-section-name #f #f)) + (configf:blank-l-rx _ (set! res (append res (list hed)))) ;; (loop (read-line inp) curr-section-name #f #f)) + (configf:section-rx ( x section-name ) (let ((section-hash (hash-table-ref/default refdat section-name #f))) + (if (not section-hash) + (let ((newhash (make-hash-table))) + (hash-table-set! refhash section-name newhash) + (set! sechash newhash)) + (set! sechash section-hash)) + (set! new hed) ;; will append this at the bottom of the loop + (set! secname section-name) + )) + ;; No need to process key cmd, let it fall though to key val + (configf:key-val-pr ( x key val ) + (let ((newval (config-lookup indat sec key))) + ;; can handle newval == #f here => that means key is removed + (cond + ((equal? newval val) + (set! res (append res (list hed)))) + ((not newval) ;; key has been removed + (set! new #f)) + ((not (equal? newval val)) + (hash-table-set! sechash key newval) + (set! new (conc key " " newval))) + (else + (debug:print 0 "ERROR: problem parsing line number " lnum "\"" hed "\""))))) + (else + (debug:print 0 "ERROR: Problem parsing line num " lnum " :\n " hed ))) + (if (not (null? tal)) + (loop (car tal)(cdr tal)(if new (append res (list new)) res)(+ lnum 1))) + ;; drop to here when done processing, res contains modified list of lines + (set! fdat res))) + + ;; step 4: Append new values to the section + (for-each + (lambda (section) + (let ((sdat '()) ;; append needed bits here + (svars (configf:section-vars indat section))) + (for-each + (lambda (var) + (let ((val (config-lookup refdat section var))) + (if (not val) ;; this one is new + (begin + (if (null? sdat)(set! sdat (list (conc "[" section "]")))) + (set! sdat (append sdat (list (conc var " " val)))))))) + svars) + (set! fdat (append fdat sdat)))) + (delete-duplicates (append require-sections (hash-table-keys indat)))) + + ;; step 5: Write out new file + (with-output-to-file fname + (lambda () + (for-each + (lambda (line) + (print line)) + (configf:expand-multi-lines fdat)))))) ADDED dashboard-main.scm Index: dashboard-main.scm ================================================================== --- /dev/null +++ dashboard-main.scm @@ -0,0 +1,220 @@ +;;====================================================================== +;; 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. +;;====================================================================== + +;;====================================================================== +;; Main Megatest Panel +;;====================================================================== + +(use format) +(require-library iup) +(import (prefix iup iup:)) + +(use canvas-draw) + +(use sqlite3 srfi-1 posix regex regex-case srfi-69) +(import (prefix sqlite3 sqlite3:)) + +(declare (unit dashboard-main)) +(declare (uses common)) +(declare (uses keys)) +(declare (uses db)) +(declare (uses tasks)) + +(include "common_records.scm") +(include "db_records.scm") +(include "run_records.scm") +(include "task_records.scm") + +(define (main-menu) + (iup:menu ;; a menu is a special attribute to a dialog (think Gnome putting the menu at screen top) + (iup:menu-item "Files" (iup:menu ;; Note that you can use either #:action or action: for options + (iup:menu-item "Open" action: (lambda (obj) + (show (iup:file-dialog)) + (print "File->open " obj))) + (iup:menu-item "Save" #:action (lambda (obj)(print "File->save " obj))) + (iup:menu-item "Exit" #:action (lambda (obj)(exit))))) + (iup:menu-item "Tools" (iup:menu + (iup:menu-item "Create new blah" #:action (lambda (obj)(print "Tools->new blah"))) + ;; (iup:menu-item "Show dialog" #:action (lambda (obj) + ;; (show message-window + ;; #:modal? #t + ;; ;; set positon using coordinates or center, start, top, left, end, bottom, right, parent-center, current + ;; ;; #:x 'mouse + ;; ;; #:y 'mouse + ;; ) + )))) + + +(define (mtest) + (let* ((curr-row-num 0) + (rawconfig (read-config (conc *toppath* "/megatest.config") #f 'return-string)) + (keys-matrix (iup:matrix + #:expand "VERTICAL" + ;; #:scrollbar "YES" + #:numcol 1 + #:numlin 20 + #:numcol-visible 1 + #:numlin-visible 5 + #:click-cb (lambda (obj lin col status) + (print "obj: " obj " lin: " lin " col: " col " status: " status)))) + (setup-matrix (iup:matrix + #:expand "YES" + #:numcol 1 + #:numlin 5 + #:numcol-visible 1 + #:numlin-visible 3)) + (jobtools-matrix (iup:matrix + #:expand "YES" + #:numcol 1 + #:numlin 5 + #:numcol-visible 1 + #:numlin-visible 3)) + (validvals-matrix (iup:matrix + #:expand "YES" + #:numcol 1 + #:numlin 2 + #:numcol-visible 1 + #:numlin-visible 2)) + (envovrd-matrix (iup:matrix + #:expand "YES" + #:numcol 1 + #:numlin 20 + #:numcol-visible 1 + #:numlin-visible 8)) + (disks-matrix (iup:matrix + #:expand "YES" + #:numcol 1 + #:numlin 20 + #:numcol-visible 1 + #:numlin-visible 8))) + (iup:attribute-set! keys-matrix "0:0" "Field Num") + (iup:attribute-set! keys-matrix "0:1" "Field Name") + (iup:attribute-set! keys-matrix "WIDTH1" "100") + (iup:attribute-set! disks-matrix "0:0" "Disk Name") + (iup:attribute-set! disks-matrix "0:1" "Disk Path") + (iup:attribute-set! disks-matrix "WIDTH1" "120") + (iup:attribute-set! disks-matrix "WIDTH0" "100") + (iup:attribute-set! disks-matrix "ALIGNMENT1" "ALEFT") + (iup:attribute-set! disks-matrix "FIXTOTEXT" "C1") + (iup:attribute-set! disks-matrix "RESIZEMATRIX" "YES") + ;; fill in keys + (set! curr-row-num 1) + (for-each + (lambda (var) + (iup:attribute-set! keys-matrix (conc curr-row-num ":0") curr-row-num) + (iup:attribute-set! keys-matrix (conc curr-row-num ":1") var) + (set! curr-row-num (+ 1 curr-row-num))) ;; (config-lookup *configdat* "fields" var))) + (configf:section-vars rawconfig "fields")) + + ;; fill in existing info + (for-each + (lambda (mat fname) + (set! curr-row-num 1) + (for-each + (lambda (var) + (iup:attribute-set! mat (conc curr-row-num ":0") var) + (iup:attribute-set! mat (conc curr-row-num ":1") (config-lookup rawconfig fname var)) + (set! curr-row-num (+ curr-row-num 1))) + (configf:section-vars rawconfig fname))) + (list setup-matrix jobtools-matrix validvals-matrix envovrd-matrix disks-matrix) + (list "setup" "jobtools" "validvalues" "env-override" "disks")) + + (for-each + (lambda (mat) + (iup:attribute-set! mat "0:1" "Value") + (iup:attribute-set! mat "0:0" "Var") + (iup:attribute-set! mat "ALIGNMENT1" "ALEFT") + (iup:attribute-set! mat "FIXTOTEXT" "C1") + (iup:attribute-set! mat "RESIZEMATRIX" "YES") + (iup:attribute-set! mat "WIDTH1" "120") + (iup:attribute-set! mat "WIDTH0" "100") + ) + (list setup-matrix jobtools-matrix validvals-matrix envovrd-matrix)) + + (iup:attribute-set! validvals-matrix "WIDTH1" "290") + (iup:attribute-set! envovrd-matrix "WIDTH1" "290") + + (iup:vbox + (iup:hbox + + (iup:vbox + (let ((tabs (iup:tabs + ;; The required tab + (iup:hbox + ;; The keys + (iup:frame + #:title "Keys (required)" + (iup:vbox + (iup:label (conc "Set the fields for organising your runs\n" + "here. Note: can only be changed before\n" + "running the first run when megatest.db\n" + "is created.")) + keys-matrix)) + (iup:vbox + ;; The setup section + (iup:frame + #:title "Setup" + (iup:vbox + (iup:label (conc "max_concurrent_jobs : limits total concurrent jobs (optional)\n" + "linktree : directory where linktree will be created.")) + setup-matrix)) + ;; The jobtools + (iup:frame + #:title "Jobtools" + (iup:vbox + (iup:label (conc "launcher : tool or script to run jobs (try nbfake)\n" + "useshell : use system to run your launcher\n" + "workhosts : spread jobs out on these hosts")) + jobtools-matrix)) + ;; The disks + (iup:frame + #:title "Disks" + (iup:vbox + (iup:label (conc "Enter names and existing paths of locations to run tests")) + disks-matrix)))) + ;; The optional tab + (iup:vbox + ;; The Environment Overrides + (iup:frame + #:title "Env override" + envovrd-matrix) + ;; The valid values + (iup:frame + #:title "Validvalues" + validvals-matrix) + )))) + (iup:attribute-set! tabs "TABTITLE0" "Required settings") + (iup:attribute-set! tabs "TABTITLE1" "Optional settings") + tabs)) + )))) + +(define (rconfig) + (iup:vbox + (iup:frame #:title "Default"))) + +(define (tests) + (iup:hbox + (iup:frame #:title "Tests browser"))) + +(define (runs) + (iup:hbox + (iup:frame #:title "Runs browser"))) + +(define (main-panel) + (iup:dialog + #:title "Menu Test" + #:menu (main-menu) + (let ((tabtop (iup:tabs (mtest) (rconfig) (tests) (runs)))) + (iup:attribute-set! tabtop "TABTITLE0" "Megatest") + (iup:attribute-set! tabtop "TABTITLE1" "Runconfigs") + (iup:attribute-set! tabtop "TABTITLE2" "Tests") + (iup:attribute-set! tabtop "TABTITLE3" "Runs") + tabtop))) Index: dashboard-tests.scm ================================================================== --- dashboard-tests.scm +++ dashboard-tests.scm @@ -266,13 +266,14 @@ (if tm tm (make-db:testmeta))) (make-db:testmeta))) (keystring (string-intersperse (map (lambda (keyval) - (conc ":" (car keyval) " " (cadr keyval))) + ;; (conc ":" (car keyval) " " (cadr keyval))) + (cadr keyval)) keydat) - " ")) + "/")) (item-path (db:test-get-item-path testdat)) (viewlog (lambda (x) (if (file-exists? logfile) ;(system (conc "firefox " logfile "&")) (iup:send-url logfile) @@ -332,23 +333,23 @@ (let ((cmd (iup:attribute command-text-box "VALUE"))) (system (conc cmd " &")))))) (run-test (lambda (x) (iup:attribute-set! command-text-box "VALUE" - (conc "megatest -runtests " testname " " keystring " :runname " runname + (conc "megatest -runtests " testname " -target " keystring " :runname " runname " -itempatt " (if (equal? item-path "") "%" item-path) - " -keepgoing > run.log" )))) + "" )))) (remove-test (lambda (x) (iup:attribute-set! command-text-box "VALUE" - (conc "megatest -remove-runs " keystring " :runname " runname " -testpatt " testname " -itempatt " + (conc "megatest -remove-runs -target " keystring " :runname " runname " -testpatt " testname " -itempatt " (if (equal? item-path "") "%" item-path) - " > clean.log"))))) + " -v "))))) (cond ((not testdat)(begin (print "ERROR: bad test info for " test-id)(exit 1))) ((not rundat)(begin (print "ERROR: found test info but there is a problem with the run info for " run-id)(exit 1))) (else ;; (test-set-status! db run-id test-name state status itemdat) @@ -431,25 +432,26 @@ #:font "Courier New, -10" #:size "100x100"))) (hash-table-set! widgets "Test Data" (lambda (testdat) ;; (let* ((currval (iup:attribute test-data "VALUE")) ;; "TITLE")) - (fmtstr "~10a~10a~10a~10a~7a~7a~6a~a") ;; category,variable,value,expected,tol,units,comment + (fmtstr "~10a~10a~10a~10a~7a~7a~6a~6a~a") ;; category,variable,value,expected,tol,units,type,comment (newval (string-intersperse (append (list - (format #f fmtstr "Category" "Variable" "Value" "Expected" "Tol" "Status" "Units" "Comment") - (format #f fmtstr "========" "========" "=====" "========" "===" "======" "=====" "=======")) + (format #f fmtstr "Category" "Variable" "Value" "Expected" "Tol" "Status" "Units" "Type" "Comment") + (format #f fmtstr "========" "========" "=====" "========" "===" "======" "=====" "====" "=======")) (map (lambda (x) (format #f fmtstr (db:test-data-get-category x) (db:test-data-get-variable x) (db:test-data-get-value x) (db:test-data-get-expected x) (db:test-data-get-tol x) (db:test-data-get-status x) (db:test-data-get-units x) + (db:test-data-get-type x) (db:test-data-get-comment x))) (db:read-test-data db test-id "%"))) "\n"))) (if (not (equal? currval newval)) (iup:attribute-set! test-data "VALUE" newval ))))) ;; "TITLE" newval))))) Index: dashboard.scm ================================================================== --- dashboard.scm +++ dashboard.scm @@ -27,10 +27,11 @@ (declare (uses process)) (declare (uses launch)) (declare (uses runs)) (declare (uses dashboard-tests)) (declare (uses dashboard-guimonitor)) +(declare (uses dashboard-main)) (declare (uses megatest-version)) (include "common_records.scm") (include "db_records.scm") (include "run_records.scm") @@ -57,10 +58,11 @@ "-test" "-debug" ) (list "-h" "-guimonitor" + "-main" "-v" "-q" ) args:arg-hash 0)) @@ -99,10 +101,12 @@ (define *status-ignore-hash* (make-hash-table)) (define *state-ignore-hash* (make-hash-table)) (define *last-db-update-time* 0) (define *please-update-buttons* #t) +(define *delayed-update* 0) + (define *db-file-path* (conc *toppath* "/megatest.db")) (define *tests-sort-reverse* #f) (define *hide-empty-runs* #f) @@ -197,14 +201,16 @@ (null? (filter (lambda (x)(> x 3)) delta)))) ;; keypatts: ( (KEY1 "abc%def")(KEY2 "%") ) (define (update-rundat runnamepatt numruns testnamepatt itemnamepatt keypatts) (let ((modtime (file-modification-time *db-file-path*))) - (if (> modtime *last-db-update-time*) + (if (or (> modtime *last-db-update-time*) + (> *delayed-update* 0)) (begin (set! *please-update-buttons* #t) (set! *last-db-update-time* modtime) + (set! *delayed-update* (- *delayed-update* 1)) (let* ((allruns (db:get-runs *db* runnamepatt numruns ;; (+ numruns 1) ;; (/ numruns 2)) *start-run-offset* keypatts)) (header (db:get-header allruns)) (runs (db:get-rows allruns)) (result '()) @@ -446,10 +452,15 @@ (string-intersperse (map conc x) ",")) (define (update-search x val) ;; (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) + ) (define (make-dashboard-buttons nruns ntests keynames) (let* ((nkeys (length keynames)) (runsvec (make-vector nruns)) (header (make-vector nruns)) @@ -468,28 +479,28 @@ (iup:frame #:title "filter test and items" (iup:hbox (iup:textbox #:size "60x15" #:fontsize "10" #:value "%" #:action (lambda (obj unk val) - (set! *last-db-update-time* 0) + (mark-for-update) (update-search "test-name" val))) (iup:textbox #:size "60x15" #:fontsize "10" #:value "%" #:action (lambda (obj unk val) - (set! *last-db-update-time* 0) + (mark-for-update) (update-search "item-name" val))))) (iup:vbox (iup:hbox (iup:button "Sort" #:action (lambda (obj) (set! *tests-sort-reverse* (not *tests-sort-reverse*)) (iup:attribute-set! obj "TITLE" (if *tests-sort-reverse* "+Sort" "-Sort")) - (set! *last-db-update-time* 0))) + (mark-for-update))) (iup:button "HideEmpty" #:action (lambda (obj) (set! *hide-empty-runs* (not *hide-empty-runs*)) (iup:attribute-set! obj "TITLE" (if *hide-empty-runs* "+Hide" "-Hide")) - (set! *last-db-update-time* 0))) + (mark-for-update))) (iup:button "Refresh" #:action (lambda (obj) - (set! *last-db-update-time* 0)))) + (mark-for-update)))) (iup:hbox (iup:button "Quit" #:action (lambda (obj)(sqlite3:finalize! *db*)(exit))) (iup:button "Monitor" #:action (lambda (obj)(system (conc (car (argv))" -guimonitor &"))))) )) ;; (iup:button "<- Left" #:action (lambda (obj)(set! *start-run-offset* (+ *start-run-offset* 1)))) @@ -501,30 +512,30 @@ (iup:vbox (apply iup:hbox (map (lambda (status) (iup:toggle status #:action (lambda (obj val) - (set! *last-db-update-time* 0) + (mark-for-update) (if (eq? val 1) (hash-table-set! *status-ignore-hash* status #t) (hash-table-delete! *status-ignore-hash* status))))) '("PASS" "FAIL" "WARN" "CHECK" "WAIVED" "STUCK/DEAD" "n/a"))) (apply iup:hbox (map (lambda (state) (iup:toggle state #:action (lambda (obj val) - (set! *last-db-update-time* 0) + (mark-for-update) (if (eq? val 1) (hash-table-set! *state-ignore-hash* state #t) (hash-table-delete! *state-ignore-hash* state))))) '("RUNNING" "COMPLETED" "INCOMPLETE" "LAUNCHED" "NOT_STARTED" "KILLED"))) (iup:valuator #:valuechanged_cb (lambda (obj) (let ((val (inexact->exact (round (/ (string->number (iup:attribute obj "VALUE")) 10)))) (oldmax (string->number (iup:attribute obj "MAX"))) (maxruns *tot-run-count*)) (set! *start-run-offset* val) - (set! *last-db-update-time* 0) + (mark-for-update) (debug:print 6 "*start-run-offset* " *start-run-offset* " maxruns: " maxruns ", val: " val " oldmax: " oldmax) (iup:attribute-set! obj "MAX" (* maxruns 10)))) #:expand "YES" #:max (* 10 (length *allruns*))))) ;(iup:button "inc rows" #:action (lambda (obj)(set! *num-tests* (+ *num-tests* 1)))) @@ -539,11 +550,11 @@ (map (lambda (x) (let ((res (iup:hbox (iup:label x #:size "40x15" #:fontsize "10") ;; #:expand "HORIZONTAL") (iup:textbox #:size "60x15" #:fontsize "10" #:value "%" ;; #:expand "HORIZONTAL" #:action (lambda (obj unk val) - (set! *last-db-update-time* 0) + (mark-for-update) (update-search x val)))))) (set! i (+ i 1)) res)) keynames))))) (let loop ((testnum 0) @@ -572,11 +583,11 @@ ; #:image img1 ; #:impress img2 #:size "100x15" #:fontsize "10" #:action (lambda (obj) - (set! *last-db-update-time* 0) + (mark-for-update) (toggle-hide testnum))))) ;; (iup:attribute obj "TITLE")))) (vector-set! lftcol testnum labl) (loop (+ testnum 1)(cons labl res)))))) ;; (let loop ((runnum 0) @@ -691,14 +702,16 @@ (begin (print "ERROR: testid is not a number " (args:get-arg "-test")) (exit 1))))) ((args:get-arg "-guimonitor") (gui-monitor *db*)) + ((args:get-arg "-main") + (iup:show (main-panel))) (else (set! uidat (make-dashboard-buttons *num-runs* *num-tests* *dbkeys*)) (iup:callback-set! *tim* "ACTION_CB" (lambda (x) (run-update x))))) ;(print x))))) (iup:main-loop) 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;") @@ -195,21 +196,25 @@ value REAL, expected REAL, tol REAL, units TEXT, comment TEXT DEFAULT '', - status TEXT DEFAULT 'n/a',foss + status TEXT DEFAULT 'n/a', CONSTRAINT test_data UNIQUE (test_id,category,variable));") + (print "WARNING: Table test_data and test_meta where recreated. Please do megatest -update-meta") (patch-db)) ((< mver 1.27) (db:set-var db "MEGATEST_VERSION" 1.27) (sqlite3:execute db "ALTER TABLE test_data ADD COLUMN type TEXT DEFAULT '';") (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 @@ -255,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) @@ -362,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;" @@ -392,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) @@ -415,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)) @@ -454,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 ;;====================================================================== @@ -497,11 +515,11 @@ (open-input-string csvdata) '((strip-leading-whitespace? #t) (strip-trailing-whitespace? #t)) )))) ;; (csv->list csvdata))) (for-each (lambda (csvrow) - (let* ((padded-row (take (append csvrow (list #f #f #f #f #f #f #f #f)) 8)) + (let* ((padded-row (take (append csvrow (list #f #f #f #f #f #f #f #f #f)) 9)) (category (list-ref padded-row 0)) (variable (list-ref padded-row 1)) (value (any->number-if-possible (list-ref padded-row 2))) (expected (any->number-if-possible (list-ref padded-row 3))) (tol (any->number-if-possible (list-ref padded-row 4))) ;; >, <, >=, <=, or a number @@ -509,22 +527,23 @@ (comment (list-ref padded-row 6)) (status (let ((s (list-ref padded-row 7))) (if (and (string? s)(or (string-match (regexp "^\\s*$") s) (string-match (regexp "^n/a$") s))) #f - s)))) ;; if specified on the input then use, else calculate + s))) ;; if specified on the input then use, else calculate + (type (list-ref padded-row 8))) ;; look up expected,tol,units from previous best fit test if they are all either #f or '' (debug:print 4 "BEFORE: category: " category " variable: " variable " value: " value - ", expected: " expected " tol: " tol " units: " units " status: " status " comment: " comment) + ", expected: " expected " tol: " tol " units: " units " status: " status " comment: " comment " type: " type) (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 @@ -541,22 +560,22 @@ ((>=) (if (>= value expected) "pass" "fail")) ((<=) (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))) + (sqlite3:execute db "INSERT OR REPLACE INTO test_data (test_id,category,variable,value,expected,tol,units,comment,status,type) VALUES (?,?,?,?,?,?,?,?,?,?);" + test-id category variable value expected tol units (if comment comment "") status type))) csvlist))) ;; get a list of test_data records matching categorypatt (define (db:read-test-data db test-id categorypatt) (let ((res '())) (sqlite3:for-each-row - (lambda (id test_id category variable value expected tol units comment status) - (set! res (cons (vector id test_id category variable value expected tol units comment status) res))) + (lambda (id test_id category variable value expected tol units comment status type) + (set! res (cons (vector id test_id category variable value expected tol units comment status type) res))) db - "SELECT id,test_id,category,variable,value,expected,tol,units,comment,status FROM test_data WHERE test_id=? AND category LIKE ? ORDER BY category,variable;" test-id categorypatt) + "SELECT id,test_id,category,variable,value,expected,tol,units,comment,status,type FROM test_data WHERE test_id=? AND category LIKE ? ORDER BY category,variable;" test-id categorypatt) (reverse res))) (define (db:load-test-data 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)) @@ -571,11 +590,11 @@ (db:csv->test-data db test-id lin) (loop (read-line)))))) ;; roll up the current results. ;; FIXME: Add the status to (db:test-data-rollup db test-id #f))) - + ;; 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 @@ -588,20 +607,20 @@ WHERE id=?;" test-id test-id test-id) ;; if the test is not FAIL then set status based on the fail and pass counts. (thread-sleep! 1) (sqlite3:execute - db + db ;;; NOTE: Should this be WARN,FAIL? A WARN is not a FAIL????? BUG FIXME "UPDATE tests SET status=CASE WHEN (SELECT fail_count FROM tests WHERE id=?) > 0 THEN 'FAIL' WHEN (SELECT pass_count FROM tests WHERE id=?) > 0 AND (SELECT status FROM tests WHERE id=?) NOT IN ('WARN','FAIL') THEN 'PASS' ELSE status END WHERE id=?;" - test-id test-id test-id)) + test-id test-id test-id test-id)) (define (db:get-prev-tol-for-test db test-id category variable) ;; Finish me? (values #f #f #f)) @@ -608,11 +627,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 @@ -662,13 +681,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) @@ -699,10 +718,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 ;;====================================================================== @@ -736,63 +801,66 @@ "Rundir" ; 18 "Host" ; 19 "Cpu Load" ; 20 ))) (results (list runsheader)) - (testdata-header (list "Run Id" "Testname" "Item Path" "Category" "Variable" "Value" "Expected" "Tol" "Units" "Status" "Comment"))) - (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 + (testdata-header (list "Run Id" "Testname" "Item Path" "Category" "Variable" "Value" "Expected" "Tol" "Units" "Status" "Comment")) + (mainqry (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, author, 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)) + FROM tests AS t JOIN runs AS r ON t.run_id=r.id JOIN test_meta AS tm ON tm.testname=t.testname + WHERE runname LIKE ? AND " keyqry ";"))) + (debug:print 2 "Using " tempdir " for constructing the ods file. keyqry: " keyqry " keystr: " keysstr " with keys: " (map cadr keypatt-alist) + "\n mainqry: " mainqry) + ;; "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 + mainqry + runspatt (map cadr keypatt-alist)) + (debug:print 2 "Found " (length test-ids) " records") (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)) @@ -816,8 +884,10 @@ (if (string-match (regexp "^[/~]+.*") outputfile) ;; full path? outputfile (begin (debug:print 0 "WARNING: path given, " outputfile " is relative, prefixing with current directory") (conc (current-directory) "/" outputfile))) - results))) + results) + ;; brutal clean up + (system "rm -rf tempdir"))) ;; (db:extract-ods-file db "outputfile.ods" '(("sysname" "%")("fsname" "%")("datapath" "%")) "%") Index: db_records.scm ================================================================== --- db_records.scm +++ db_records.scm @@ -65,10 +65,11 @@ (define-inline (db:test-data-get-expected vec) (vector-ref vec 5)) (define-inline (db:test-data-get-tol vec) (vector-ref vec 6)) (define-inline (db:test-data-get-units vec) (vector-ref vec 7)) (define-inline (db:test-data-get-comment vec) (vector-ref vec 8)) (define-inline (db:test-data-get-status vec) (vector-ref vec 9)) +(define-inline (db:test-data-get-type vec) (vector-ref vec 10)) ;;====================================================================== ;; S T E P S ;;====================================================================== ;; Run steps Index: docs/html/dashboard-test.png ================================================================== --- docs/html/dashboard-test.png +++ docs/html/dashboard-test.png cannot compute difference between binary files Index: docs/html/dashboard.png ================================================================== --- docs/html/dashboard.png +++ docs/html/dashboard.png cannot compute difference between binary files Index: docs/html/megatest.html ================================================================== --- docs/html/megatest.html +++ docs/html/megatest.html @@ -2,12 +2,12 @@ - - + + Megatest User Manual
@@ -27,37 +27,31 @@

Sept. 20,

- +\thispagestyleempty


-2011 Matthew Welland. All rights reserved. +©2011 Matthew Welland. All rights reserved.
Megatest is free software released under the General Public License v2.0. Please see the file COPYING in the source distribution for details.
-
- -
-Email: matt@kiatoa.com. +
Email: matt@kiatoa.com.
Web: www.kiatoa.com/fossils/megatest
-
- -
-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. 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, exemplary, incidental, or consequential damages resulting from any defect or omission in this document, even if advised of the possibility of such damages. +
This document is believed to be acurate at the time of writing but as with 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, exemplary, incidental, or consequential damages resulting from any defect or omission in this document, even if advised of the possibility of such damages.
-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.


@@ -70,48 +64,48 @@
- - - - - - - -
+ Version + Author + Description + Date
+ v1.25 + matt + converted to new document template -
- -
- +
+\thedate
+
+


+

Table of Contents
- - - - - - - - - - - +Section 4: Choose Flow or Unstructured Run? +
+ + + + + + + + + + + + + + + + + +
+


@@ -281,26 +302,26 @@

1.1 Megatest design philosophy

-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. 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. +Megatest is intended to provide the minimum needed resources to make writing a suite of tests and implementing continuous build for software, design engineering or process control (via owlfs for example) without being specialized 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.

1.2 Megatest architecture

-All data to specify the tests and configure the system is stored in plain text files. All system state is stored in an sqlite3 database. Tests are launched using the launching system available for the distributed compute platform in use. A template script is provided which can launch jobs on local and remote Linux hosts. Currently megatest uses the network filesystem to “call home” to your master sqlite3 database. +All data to specify the tests and configure the system is stored in plain text files. All system state is stored in an sqlite3 database. Tests are launched using the launching system available for the distributed compute platform in use. A template script is provided which can launch jobs on local and remote Linux hosts. Currently megatest uses the network filesystem to “call home” to your master sqlite3 database.

2 Installation

2.1 Dependencies

-Chicken scheme and a number of “eggs” are required for building megatest. See the file utils/installall.sh for an automated way to install the dependencies on Linux. +Chicken scheme and a number of “eggs” are required for building megatest. See the file utils/installall.sh for an automated way to install the dependencies on Linux.

2.2 Build and Install

@@ -408,14 +429,20 @@

-4 How to Write Tests +4 Choose Flow or Unstructured Run? +

+
+A flow is a structured and specifically sequenced set of tests. See the Flows chapter to understand the difference. +
+

+5 How to Write Tests

-4.1 A Simple Test with one Step +5.1 A Simple Test with one Step

mkdir simpletest
 cd simpletest
@@ -422,11 +449,11 @@
 

-4.2 Create your testconfig file +5.2 Create your testconfig file

# testconfig
 ​
@@ -435,11 +462,11 @@
 

-4.3 Create the main.csh script +5.3 Create the main.csh script

Note: Using csh is NOT recommended. Use bash, perl, ruby, zsh or anything other than csh. We use csh here because it is popular in the EDA industry for which Megatest was originally created.
@@ -462,11 +489,11 @@
You can now run megatest and the created test directory will contain the new files “run_simulation.html” and “run_simulation.log”. If you are using the dashboard you can click on the run and then push the “View log” button to view the log file in firefox.

-4.4 Simple Test with Multiple Steps +5.4 Simple Test with Multiple Steps

To run multiple steps simply add them to the main.csh file. Here we add a step to test “cpu2”. The second step that tests cpu2 will only run after the step that tested “cpu1” completes.
@@ -484,14 +511,14 @@

-5 Simple Test with Multiple Steps, Some in Parallel +6 Simple Test with Multiple Steps, Some in Parallel

-5.1 The Makefile +6.1 The Makefile

A good way to run steps in parallel within a single test, especially when there are following steps, is to use the Unix Make utility. Writing Makefiles is beyond the scope of this document but here is a minimal example that will run “runsim cpu1” and “runsim cpu2” in parallel. For more information on make try “info make” at the Linux command prompt.
@@ -506,11 +533,11 @@

-5.2 The main.csh file +6.2 The main.csh file

#!/bin/tcsh -x
 ​
@@ -525,17 +552,17 @@
 

-6 Simple Test with Iteration +7 Simple Test with Iteration

Since no jobs run after the cpu1 and cpu2 simulations in this test it is possible to use iterated mode.

-6.1 Update your testconfig file for iteration +7.1 Update your testconfig file for iteration

[setup]
 runscript main.csh
@@ -548,11 +575,11 @@
 

-6.2 Rewrite your main.csh for iteration +7.2 Rewrite your main.csh for iteration

#!/bin/tcsh -x
 
@@ -567,11 +594,11 @@
 

-6.3 Tests with Inter-test dependencies +7.3 Tests with Inter-test dependencies

Sometimes a test depends on the output from a previous test or it may not make sense to run a test is another test does not complete with status “PASS”. In either of these scenarios you can use the “waiton” keyword in your testconfig file to indicate that this test must wait on one or more tests to complete before being launched. In this example there is no point in running the “system” test if the “cpu” and “mem” tests either do not complete or complete but with status “FAIL”.
@@ -583,11 +610,11 @@

-6.4 Rolling up Miscellaneous Data +7.4 Rolling up Miscellaneous Data

Use the -load-test-data switch to roll up arbitrary data from a test into the test_data table.
@@ -626,11 +653,11 @@ If status is specified its value overrides the above calculations.

-6.5 Rolling up Runs +7.5 Rolling up Runs

To roll up a number of tests in a sequence of runs to a single run use the -rollup command.
@@ -642,32 +669,32 @@
All keys must be specified and the runname is the name of the run that will be created. All paths are kept original inside the database. When -remove-runs is used to delete runs the data is not deleted if there are rollups that refer to the data.

-7 Dashboard +8 Dashboard

> dashboard &
 
-figure dashboard.png +figure dashboard.png
Pushing one of the buttons on the main dashboard will bring up the test specific dashboard. Values are updated in semi-real time as the test runs.
-figure dashboard-test.png +figure dashboard-test.png

-8 Generating an OpenDocument Spreadsheet from the Database +9 Generating an OpenDocument Spreadsheet from the Database

And OpenDocument multi-paned spreadsheet can be generated from the megatest.db file by running -extract-ods
@@ -679,17 +706,82 @@
You can optionally specify the keys for your database to limit further the runs to extract into the spreadsheet. The first sheet contains all the run data and subsequent sheets contain data rolled up for the individual tests.

-9 Reference +10 Flows +

+
+A flow specifies the tests to run, the order and dependencies and is managed by a running megatest process. +
+

+11 Flow Specification and Running (Not released yet) +

+

+11.1 Write your flow file +

+
+flows/<flowname>.config +
+
+
+
# Flow: <flowname>
+[flowconfig]
+# turn on item level dependencies
+itemdeps on
+​
+[flowsteps]
+# <testname>[,<predecessor>]
+​
+# Run the test "copydata"
+copydata
+​
+# Run the test "setup" after copydata completes with PASS, WARN or WAIVE
+setup,copydata
+​
+# once the test "setup" completes successfully run sim1, sim2 and sim3
+sim1,setup
+sim2,setup
+sim3,setup
+
+
+ +
+

+11.2 Run the flow +

+
+
+
megatest -runflow <flowname> :FIELD1 val1 :FIELD2 val2 :runname wk32.4
+
+
+ +
+

+12 Monitor based running +

+

+12.1 Monitor logic +

+
+Note: The monitor is usable but incomplete as of Megatest v1.31. Click on the “Monitor” button on the dashboard to start the monitor and give it a try. +
+
+figure monitor-state-diagram.png + +
+

+13 Reference

-9.1 Configuration file Syntax +13.1 Configuration file Syntax

+
+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. +

-9.1.1 Sections +13.1.1 Sections

[section name]
 
@@ -698,11 +790,11 @@
This creates a section named “section name”

-9.1.2 Variables +13.1.2 Variables

VARX has this value
 
@@ -711,11 +803,11 @@
The variable “VARX” will have the value “has this value”

-9.1.3 Includes +13.1.3 Includes

[include filename]
 
@@ -724,11 +816,11 @@
The file named “filename” will be included as if part of the calling file. NOTE: This means no section can be named “include “ (with the whitespace).

-9.1.4 Setting a variable by running a command +13.1.4 Setting a variable by running a command

VARNAME [system ls /tmp]
 
@@ -737,11 +829,11 @@
The variable “VARNAME” will get a value created by the Unix command “ls /tmp”. All lines of output from the command will be joined with a space.

-9.1.5 Notes +13.1.5 Notes

  • Some variables are infered as lists. Each token on the line separated by whitespace will be member of the list.
  • @@ -749,325 +841,343 @@ Comments (lines starting with #) and blank lines are ignored.

-9.2 Environment variables +13.2 Environment variables

- - - - - - - - - - - - - - - - - -
+ Variable + Purpose
+ MT_CMDINFO + Conveys test variables to the megatest test runner.
+ MT_TEST_RUN_DIR + Directory assigned by megatest for the test to run.
+ MT_TEST_NAME + Name of the test, corrosponds to the directory name under tests.
+ MT_ITEM_INFO + Iterated tests will set this to a sequence of key/values ((KEY val) ...)
+ MT_RUN_AREA_HOME + Directory where megatest was launched from and where the tests code can be found
+ MT_RUNNAME + Name of this run as set by the :runname parameter
+ MT_MEGATEST + Path/Filename to megatest executable. Found either from called path or but using the “exectuable” keyword in the [setup] section.
+ <field1> .... + The field values as set on the megatest -runall command line (e.g. :field1 abc)

-9.3 megatest.config +13.3 megatest.config

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - -
+ section + variable + value + required + comment
+ [setup] + max_concurrent_jobs + if variable is not defined no limit on jobs + no +
+ + executable + full path to megatest binary + no + Use only if necessary, megatest will extract the location from where it used to launch and add append that to the PATH for test runs.
+ + runsdir + full path to where the link tree to all runs will be created + no + Because your runs may be spread out over several disk partitions a central link tree is created to make finding all the runs easy.
+ [fields] + string of letters, numbers and underscore + string of letters, numbers and underscore + at least one +
+ [jobtools] + launcher + command line used to launch jobs - the job command (megatest -execute) will be appended to this + no +
+ + workhosts + list of hostnames to run jobs on NOT SUPPORTED RIGHT NOW + n/a +
+[jobgroups] + +string of letters, numbers and underscore + +number + +no + +Control number of jobs allowed to concurrently run in categories. See [jobgroup] in testconfig +
+ [env-override] + string of letters, numbers and underscore + any string + no + These are set on the test launching machine, not the test running machine. Typical usage is to control the host or run queue for launching tests. These values will not be seen by the test when it runs.
+ [disks] + string of letters, numbers and underscore + a valid path writable by the test launching process and by the test process + yes + The disk usage balancing algorithm is to choose the disk with the least space for each test run.

-9.4 runconfigs.config file +13.4 runconfigs.config file

- - - - - - - - - - - - - - - @@ -1113,95 +1223,131 @@
TESTPATH /nfs/testing/megacorp_runs

-9.5 Writing tests +13.5 Writing tests

-9.5.1 testconfig file +13.5.1 testconfig file

+ section + variable + value + required? + comment
+ [default] + string of letters, numbers and underscore + any + no + variables set in this section will be available for all runs, defining the same variable in another section will override the value from the default section
+ [field1value/field2value...] + string of letters, numbers and underscore + any + no + the values in this section will be set for any run where field1 is field1value, field2 is field2value and fieldN is fieldNvalue.
- - - - - - - - - - - - - - - - + + + + + + + + - - - + + + + + + + + -
+ section + variable + value + required? + comments
+ [setup] + runscript + name of script to execute for this test + yes + The script must be executable and either provide the full path or put a copy at the top of your test directory
+ [requirements] + waiton + list of valid test names + no + This test will not run until the named tests are state completed and status PASS
+ + + +jobgroup + + + + + + +
[items] + any valid + list of values + +no + +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 +
+[eztests] + +any valid + +stepname command + no -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 <stepname>.logpro exists it will be applied to the <stepname>.log and resulting exit code will be used to determine PASS/FAIL/WARN

-9.5.2 Command line +13.5.2 Command line

- @@ -1223,11 +1369,11 @@ -h - @@ -1238,11 +1384,11 @@ -runall - @@ -1253,11 +1399,11 @@ -runtests - @@ -1268,11 +1414,11 @@ -step - @@ -1283,11 +1429,11 @@ -test-status - @@ -1298,11 +1444,11 @@ -setlog - @@ -1313,11 +1459,11 @@ -set-toplog - @@ -1326,13 +1472,13 @@ - @@ -1343,11 +1489,11 @@ :runname - @@ -1358,15 +1504,15 @@ :state - - - @@ -1403,11 +1549,11 @@ -testpatt - @@ -1418,11 +1564,11 @@ -itempatt - @@ -1433,11 +1579,11 @@ -showkeys - @@ -1448,15 +1594,15 @@ -force - - @@ -1478,11 +1624,11 @@ -remove-runs - @@ -1493,11 +1639,11 @@ Test helpers - @@ -1508,11 +1654,11 @@ -runstep - @@ -1523,12 +1669,12 @@ -logpro - @@ -1536,18 +1682,18 @@
@@ -1208,11 +1354,11 @@ switch or param parameter + purpose comments + brief help + run all tests test1,test2,... + run one or more tests stepname + record a step requires :state and :status + record the test status requires :state and :status logfilename + set the logfile name for a test path is assumed to be relative to the test run directory logfilename + set the logfile name for the top test in an iterated test run each sub test can have its own logfile set
-m -“comment” +“comment” + sets a comment for the step, test or run [a-zA-Z0-9_-]+ + directory in which this run will be stored in the test run area any value + Set the step or test state, this is stored in the state field in the steps or tests table respectively -For tests Megatest recognises “INCOMPLETE”, “COMPLETE” +For tests Megatest recognises “INCOMPLETE”, “COMPLETE”
@@ -1373,15 +1519,15 @@ :status any value + Set the step or test status, this is stored in the status field in the steps or tests table respectively -For tests Megatest recognises “PASS”, “FAIL”, and “CHECK” +For tests Megatest recognises “PASS”, “FAIL”, and “CHECK”
@@ -1388,11 +1534,11 @@ -list-runs any value, % is wildcard + Respects -itempatt and -testpatt for filters any value, % is wildcard + any value, % is wildcard + + Print the keys being used for this database -Test will not re-run if in the “PASS”, “CHECK” or “KILLED”, using -force will force the run to be launched. + +Test will not re-run if in the “PASS”, “CHECK” or “KILLED”, using -force will force the run to be launched. -WARNING: The -force switch will bypass any “waiton” dependencies. +WARNING: The -force switch will bypass any “waiton” dependencies.
@@ -1463,11 +1609,11 @@ -xterm + Launch an xterm instead of run the test. The xterm will have the environment that the test would see. + Remove a run, test or subtest from the database and the disk. Cannot be undone. Requires -testpatt, -itempatt, :runname and all keys be specified. + + Used inside a test to run a step, record the start and end of the step and optionally analyze the output using logpro. -If using logpro to asses the PASS/FAIL status of the step you specify the logpro file with this parameter. + +If using logpro to acess the PASS/FAIL status of the step you specify the logpro file with this parameter.

-A Data +A Data

-B References +B References

ADDED docs/html/monitor-state-diagram.png Index: docs/html/monitor-state-diagram.png ================================================================== --- /dev/null +++ docs/html/monitor-state-diagram.png cannot compute difference between binary files 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,37 @@ '() #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" '()))) + (debug:print 5 "items: " items " itemstable: " itemstable) + (set! items (map (lambda (item) + (if (procedure? (cadr item)) + (list (car item)((cadr item))) + item)) + items)) + (set! itemstable (map (lambda (item) + (if (procedure? (cadr item)) + (list (car item)((cadr item))) + item)) + 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 @@ -168,17 +168,18 @@ (debug:print 4 "ezsteps:\n stepname: " stepname " stepinfo: " stepinfo " stepparts: " stepparts " stepparms: " stepparms " stepcmd: " stepcmd) (if (file-exists? (conc stepname ".logpro"))(set! logpro-used #t)) - ;; first source the previous environment - (let ((prev-env (conc ".ezsteps/" prevstep (if (string-search (regexp "csh") (get-environment-variable "SHELL")) ".csh" ".sh")))) - (if (and prevstep (file-exists? prev-env)) - (set! script (conc script "source " prev-env)))) + ;; ;; first source the previous environment + ;; (let ((prev-env (conc ".ezsteps/" prevstep (if (string-search (regexp "csh") + ;; (get-environment-variable "SHELL")) ".csh" ".sh")))) + ;; (if (and prevstep (file-exists? prev-env)) + ;; (set! script (conc script "source " prev-env)))) ;; call the command using mt_ezstep - (set! script (conc script ";mt_ezstep " stepname " " stepcmd)) + (set! script (conc "mt_ezstep " stepname " " (if prevstep prevstep "-") " " stepcmd)) (debug:print 4 "script: " script) (teststep-set-status! db run-id test-name stepname "start" "-" itemdat #f #f) ;; now launch @@ -216,18 +217,22 @@ " this-step-status: " this-step-status " overall-status: " overall-status " next-status: " next-status " rollup-status: " rollup-status) (case next-status ((warn) (set! rollup-status 2) - (test-set-status! db run-id test-name "COMPLETED" "WARN" itemdat + ;; (test-set-status! db run-id test-name "COMPLETED" "WARN" itemdat + (test-set-status! db run-id test-name "RUNNING" "WARN" itemdat (if (eq? this-step-status 'warn) "Logpro warning found" #f) #f)) ((pass) - (test-set-status! db run-id test-name "COMPLETED" "PASS" itemdat #f #f)) + ;; (test-set-status! db run-id test-name "COMPLETED" "PASS" itemdat #f #f)) + (test-set-status! db run-id test-name "RUNNING" "PASS" itemdat #f #f)) (else ;; 'fail (set! rollup-status 1) ;; force fail - (test-set-status! db run-id test-name "COMPLETED" "FAIL" itemdat (conc "Failed at step " stepname) #f))))) + ;; (test-set-status! db run-id test-name "COMPLETED" "FAIL" itemdat (conc "Failed at step " stepname) #f) + (test-set-status! db run-id test-name "RUNNING" "FAIL" itemdat (conc "Failed at step " stepname) #f) + )))) (if (and (steprun-good? logpro-used (vector-ref exit-info 2)) (not (null? tal))) (loop (car tal) (cdr tal) stepname))) (debug:print 4 "WARNING: a prior step failed, stopping at " ezstep)))))))) (monitorjob (lambda () @@ -374,13 +379,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))) + (linktree (let ((rd (config-lookup *configdat* "setup" "linktree"))) + (if rd rd (conc *toppath* "/runs")))) + (lnkpath (conc linktree "/" key-str "/" runname item-path))) + (if (not (file-exists? linktree)) + (begin + (debug:print 0 "WARNING: linktree did not exist! Creating it now at " linktree) + (system (conc "mkdir -p " linktree)))) ;; 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") @@ -387,15 +396,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))) DELETED matrix.scm Index: matrix.scm ================================================================== --- matrix.scm +++ /dev/null @@ -1,44 +0,0 @@ -(require-library iup canvas-draw canvas-draw-iup) - -(module matrix-test - (matrix-dialog) - (import - scheme chicken extras - iup canvas-draw canvas-draw-iup - (only canvas-draw-base pointer->canvas)) - -(define ncols 8) -(define nlins 8) -(define width 32) -(define height 32) - -;; (define (render-cell handle i j x-min x-max y-min y-max canvas) -;; (set! (canvas-foreground canvas) -;; (if (or (and (odd? i) (odd? j)) (and (even? i) (even? j))) -;; #xffffff -;; #x000000)) -;; (canvas-box! canvas x-min x-max y-min y-max)) - -(define matrix-dialog - (dialog - #:title "Matrix Test" - (let ((mat (matrix - ; #:expand "YES" - ; #:scrollbar "YES" - #:numcol ncols - #:numlin nlins - #:numcol-visible ncols - #:numlin-visible nlins - #:click-cb (lambda (obj lin col status) - (print "obj: " obj " lin: " lin " col: " col " status: " status))))) - (attribute-set! mat "0:0" "Testing") - mat))) - -) ;; end module - -(import - (only iup show main-loop) - matrix-test) - -(show matrix-dialog) -(main-loop) 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 @@ -91,11 +91,11 @@ -update-meta : update the tests metadata for all tests -env2file fname : write the environment to fname.csh and fname.sh -setvars VAR1=val1,VAR2=val2 : Add environment variables to a run NB// these are overwritten by values set in config files. Spreadsheet generation - -extract-ods : extract an open document spreadsheet from the database + -extract-ods fname.ods : extract an open document spreadsheet from the database -pathmod path : insert path, i.e. path/runame/itempath/logfile.html will clear the field if no rundir/testname/itempath/logfile if it contains forward slashes the path will be converted to windows style @@ -233,11 +233,15 @@ (args:get-arg "-itempatt"))) (sqlite3:finalize! db) (set! *didsomething* #t))))) (if (args:get-arg "-remove-runs") - (remove-runs)) + (general-run-call + "-remove-runs" + "remove runs" + (lambda (db target runname keys keynames keyvallst) + (remove-runs)))) ;;====================================================================== ;; Query runs ;;====================================================================== @@ -324,15 +328,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") @@ -352,42 +389,18 @@ (if (args:get-arg "-extract-ods") (general-run-call "-extract-ods" "Make ods spreadsheet" - (lambda (db keys keynames keyvallst) + (lambda (db target runname keys keynames keyvallst) (let ((outputfile (args:get-arg "-extract-ods")) (runspatt (args:get-arg ":runname")) (pathmod (args:get-arg "-pathmod")) (keyvalalist (keys->alist keys "%"))) + (debug:print 2 "Extract ods, outputfile: " outputfile " runspatt: " runspatt " keyvalalist: " keyvalalist) (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 (or (equal? status "AUTO")(equal? status "AUTO-WARN"))) - (db:test-data-rollup db test-id status)) - - ;; 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 @@ -465,356 +82,54 @@ (debug:print 2 "setenv " (key:get-fieldname key) " " val) (setenv (key:get-fieldname key) val)) db (conc "SELECT " (key:get-fieldname key) " FROM runs WHERE id=?;") run-id)) - keys))) + keys) + ;; Lets use this as an opportunity to put MT_RUNNAME in the environment + (sqlite3:for-each-row + (lambda (runname) + (setenv "MT_RUNNAME" runname)) + db + "SELECT runname FROM runs WHERE id=?;" + run-id) + )) (define (set-item-env-vars itemdat) (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 +171,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,246 +194,286 @@ (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) - ;; 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")) - ;; on the first pass or call to run-tests set FAILS to NOT_STARTED if ;; -keepgoing is specified - (if (and (eq? *passnum* 0) - keepgoing) + (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"))) - (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) + + ;; now add non-directly referenced dependencies (i.e. waiton) + (if (not (null? test-names)) + (let loop ((hed (car test-names)) + (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) + (debug:print 4 "INFO: items is a procedure, will calc later") + items) ;; calc later + ((procedure? itemstable) + (debug:print 4 "INFO: itemstable is a procedure, will calc later") + itemstable) ;; calc later + ((filter (lambda (x) + (let ((val (car x))) + (if (procedure? val) val #f))) + (append (if (list? items) items '()) + (if (list? itemstable) itemstable '()))) + 'have-procedure) + ((or (list? items)(list? itemstable)) ;; calc now + (debug:print 4 "INFO: items and itemstable are lists, calc now\n" + " items: " items " itemstable: " itemstable) + (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)) + (set! test-names (cons waiton test-names))))) ;; was an append, now a cons + 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")) + ;; NOTE: these are all parent tests, items are not expanded yet. + (runs:run-tests-queue db run-id runname test-records keyvallst flags) + (debug:print 4 "INFO: All done by here"))) + +(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 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))) + ;; Don't know at this time if the test have been launched at some time in the past + ;; i.e. is this a re-launch? + (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! 4) + (loop (car newtal)(cdr newtal)))))) + + ;; 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 + ((or (procedure? items)(eq? items 'have-procedure)) + (if (and (runs:can-run-more-tests db test-record) + (null? (db:get-prereqs-not-met db run-id waitons item-path))) + (let ((test-name (tests:testqueue-get-testname test-record))) + (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 + (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) + (begin + ;; FIXME!!!! THIS SHOULD NOT REQUIRE AN EXIT!!!!!!! + (debug:print 1 "INFO: All tests launched, exiting") + (exit 0)) + (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))))))))) + (item-path "")) + (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 '())) + (set! item-path (item-list->path itemdat)) + (debug:print 2 "Attempting to launch test " test-name "/" item-path) + (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* ((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) + ;; Require to force re-run for COMPLETED or *anything* + PASS,WARN or CHECK + (or (member (test:get-status testdat) '("PASS" "WARN" "CHECK")) + (member (test:get-state testdat) '("COMPLETED")))) + (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 \"" (test:get-state testdat) + "\" and status \"" (test:get-status testdat) "\", use -rerun \"" (test:get-state testdat) "\" or -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 +570,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 +610,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 +630,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 +659,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,430 @@ +(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 status)) + + ;; 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" "")) + (type (hash-table-ref/default otherdat ":type" "")) + (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 ",," ;; extra comma for status + type )))) + + ;; 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='';" "RUNNING" 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))) + Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -4,15 +4,15 @@ MEGATEST=$(BINPATH)/megatest PATH := $(BINPATH):$(PATH) runall : cd ../;make install - $(BINPATH)/dboard & - $(MEGATEST) -keepgoing -runall :sysname ubuntu :fsname nfs :datapath none :runname `date +w%V.%u.%H` -m "This is a comment specific to a run" -v + $(BINPATH)/dboard -rows 15 & + $(MEGATEST) -keepgoing -runall -target ubuntu/nfs/none :runname `date +w%V.%u.%H` -m "This is a comment specific to a run" -v test : - csi -b -I .. ../megatest.scm -- -runall :sysname ubuntu :fsname afs :datapath tmp :runname blah + csi -b -I .. ../megatest.scm -- -runall -target ubuntu/afs/tmp :runname blah cd ../;make test make runall dashboard : cd ../;make dboard @@ -20,6 +20,6 @@ remove : (cd ../;make);$(MEGATEST) -remove-runs :runname $(RUN) -testpatt % -itempatt % :sysname % :fsname % :datapath % runforever : - while(ls); do runname=`date +%F-%R:%S`;$(MEGATEST) -runall :sysname ubuntu :fsname nfs :datapath none :runname $$runname;/home/matt/data/megatest/megatest -runall :sysname ubuntu :fsname nfs :datapath none :runname $$runname;/home/matt/data/megatest/megatest -runall :sysname ubuntu :fsname nfs :datapath none :runname $$runname;done + while(ls); do runname=`date +%F-%R:%S`;$(MEGATEST) -runall -target ubuntu/nfs/none :runname $$runname;/home/matt/data/megatest/megatest -runall -target ubuntu/nfs/none :runname $$runname;/home/matt/data/megatest/megatest -runall -target ubuntu/nfs/none :runname $$runname;done Index: tests/megatest.config ================================================================== --- tests/megatest.config +++ tests/megatest.config @@ -4,11 +4,11 @@ datapath TEXT [setup] # exectutable /path/to/megatest max_concurrent_jobs 50 -runsdir /tmp/runs +linktree /tmp/runs [jobtools] # useshell yes # ## launcher launches jobs, the job is managed on the target host ## by megatest, comment out launcher to run local ADDED tests/tests/ez_exit2_fail/testconfig Index: tests/tests/ez_exit2_fail/testconfig ================================================================== --- /dev/null +++ tests/tests/ez_exit2_fail/testconfig @@ -0,0 +1,15 @@ +[setup] + +[ezsteps] +exit2 exit 2 +lookithome ls /home + +[test_meta] +author matt +owner bob +description This test runs two steps; the first exits with + code 2 (a fail because not using logpro) and the second + is a pass + +tags first,single +reviewed 09/10/2011, by Matt Index: tests/tests/ez_fail/testconfig ================================================================== --- tests/tests/ez_fail/testconfig +++ tests/tests/ez_fail/testconfig @@ -2,14 +2,15 @@ [requirements] priority 10 [ezsteps] -lookittmp ls /tmp -lookithome ls /home -lookitnada ls /nada -lookitusr ls /usr +lookittmp sleep 5s;ls /tmp +lookithome sleep 2;ls /home +# should fail on next step +lookitnada sleep 3;ls /nada +lookitusr sleep 2;ls /usr [test_meta] author matt owner bob description This test runs a single ezstep which is expected to pass, no logpro file. DELETED tests/tests/ez_warn/testconfig Index: tests/tests/ez_warn/testconfig ================================================================== --- tests/tests/ez_warn/testconfig +++ /dev/null @@ -1,15 +0,0 @@ -[setup] - -[ezsteps] -exit2 exit 2 -lookithome ls /home - -[test_meta] -author matt -owner bob -description This test runs two steps; the first exits with - code 2 (a fail because not using logpro) and the second - is a pass - -tags first,single -reviewed 09/10/2011, by Matt Index: tests/tests/ezlog_fail/lookittmp.logpro ================================================================== --- tests/tests/ezlog_fail/lookittmp.logpro +++ tests/tests/ezlog_fail/lookittmp.logpro @@ -1,8 +1,6 @@ ;; (c) 2006,2007,2008,2009 Matthew Welland matt@kiatoa.com ;; ;; License GPL. - -(expect:ignore in "LogFileBody" < 99 "Ignore the word error in comments" #/^\/\/.*error/) (expect:warning in "LogFileBody" = 0 "Any warning" #/WARNING/) (expect:error in "LogFileBody" = 0 "Any error" (list #/ERROR/ #/.*/)) ;; force an error Index: tests/tests/ezlog_warn/lookittmp.logpro ================================================================== --- tests/tests/ezlog_warn/lookittmp.logpro +++ tests/tests/ezlog_warn/lookittmp.logpro @@ -2,9 +2,11 @@ ;; ;; License GPL. (expect:warning in "LogFileBody" = 0 "Any warning" #/.*/) -(expect:required in "LogFileBody" > 0 "Must be some files in the dir" #/.*/) +;; Can't have a required since it will mask the warns! Could make the warn non-overlapping with the +;; required I suppose... +;; (expect:required in "LogFileBody" > 0 "Must be some files in the dir" #/.*/) (expect:ignore in "LogFileBody" < 99 "Ignore the word error in comments" #/^\/\/.*error/) (expect:error in "LogFileBody" = 0 "Any error" (list #/ERROR/ #/error/)) ;; but disallow any other errors ADDED tests/tests/logpro_required_fail/lookittmp.logpro Index: tests/tests/logpro_required_fail/lookittmp.logpro ================================================================== --- /dev/null +++ tests/tests/logpro_required_fail/lookittmp.logpro @@ -0,0 +1,8 @@ +;; (c) 2006,2007,2008,2009 Matthew Welland matt@kiatoa.com +;; +;; License GPL. + +(expect:required in "LogFileBody" > 0 "A file name that should never exist!" #/This is a awfully stupid file name that should never be found in the temp dir/) + +;; (expect:warning in "LogFileBody" = 0 "Any warning" #/WARNING/) +;; (expect:error in "LogFileBody" = 0 "Any error" (list #/ERROR/ #/.*/)) ;; force an error ADDED tests/tests/logpro_required_fail/testconfig Index: tests/tests/logpro_required_fail/testconfig ================================================================== --- /dev/null +++ tests/tests/logpro_required_fail/testconfig @@ -0,0 +1,12 @@ +[setup] + +[ezsteps] +lookittmp ls /tmp + +[test_meta] +author matt +owner bob +description This test runs two ezstep, the first of which is expected to fail using a simple logpro file. + +tags logpro +reviewed 09/10/2011, by Matt Index: tests/tests/runfirst/main.sh ================================================================== --- tests/tests/runfirst/main.sh +++ tests/tests/runfirst/main.sh @@ -3,10 +3,12 @@ # megatest -step wasting_time :state start :status n/a -m "This is a test step comment" # sleep 20 # megatest -step wasting_time :state end :status $? touch ../I_was_here +mkdir -p $MT_RUN_AREA_HOME/tmp/$USER/$sysname/$fsname/$datapath/$MT_RUNNAME +echo 1 2 3 4 5 > $MT_RUN_AREA_HOME/tmp/$USER/$sysname/$fsname/$datapath/$MT_RUNNAME/the_ans $MT_MEGATEST -runstep wasting_time -logpro wasting_time.logpro "sleep 8;echo all done eh?" -m "This is a test step comment" $MT_MEGATEST -load-test-data << EOF foo,bar,1.2,1.9,> @@ -16,9 +18,14 @@ foo,alb,1.2,1.2,<=,Amps,This is the high power circuit test foo,abl,1.2,1.3,0.1 foo,bra,1.2,pass,silly stuff faz,bar,10,8mA,,,"this is a comment" EOF + +$MT_MEGATEST -load-test-data << EOF +cat, var, val, exp, comp, units, comment, status, type +ameas,iout,1.2,1.9,>,Amps,Comment,,meas +EOF $MT_MEGATEST -test-status :state COMPLETED :status $? -m "This is a test level comment" :value 10e6 :expected_value 1.1e6 :tol 100e3 :category nada :variable sillyvar :units mFarks :comment "This is the value/expected comment" # $MT_MEGATEST -test-status :state COMPLETED :status FAIL ADDED tests/tests/test_mt_vars/testconfig Index: tests/tests/test_mt_vars/testconfig ================================================================== --- /dev/null +++ tests/tests/test_mt_vars/testconfig @@ -0,0 +1,20 @@ +[setup] + +[ezsteps] +lookittmp ls /tmp +lookithome ls /home + +[requirements] +waiton runfirst +priority 0 + +[items] +NUMNUM [system cat $MT_RUN_AREA_HOME/tmp/$USER/$sysname/$fsname/$datapath/$MT_RUNNAME/the_ans] + +[test_meta] +author matt +owner bob +description This test runs a single ezstep which is expected to pass, no logpro file. + +tags first,single +reviewed 09/10/2011, by Matt DELETED tree.scm Index: tree.scm ================================================================== --- tree.scm +++ /dev/null @@ -1,28 +0,0 @@ - -(use iup canvas-draw canvas-draw-iup) - -(define t #f) - -(define tree-dialog - (dialog - #:title "Tree Test" - (let ((t1 (treebox - #:selection_cb (lambda (obj id state) - (print "selection_db with id=" id " state=" state) - (print "SPECIALDATA: " (attribute obj "SPECIALDATA")) - )))) - (set! t t1) - t1))) - -(show tree-dialog) -(map (lambda (elname el) - (print "Adding " elname " with value " el) - (attribute-set! t elname el) - (attribute-set! t "SPECIALDATA" el)) - '("VALUE" "NAME" "ADDLEAF" "ADDBRANCH1" "ADDLEAF2" "VALUE") - '("0" "Figures" "Other" "triangle" "equilateral" "4") - ) -(map (lambda (attr) - (print attr " is " (attribute t attr))) - '("KIND1" "PARENT2" "STATE1")) -(main-loop) Index: utils/mt_ezstep ================================================================== --- utils/mt_ezstep +++ utils/mt_ezstep @@ -7,18 +7,25 @@ # Purpose: This is for the [ezsteps] secton in your testconfig file. # DO NOT USE IN YOUR SCRIPTS! # # Call like this: -# mt_ezstep stepname command .... +# mt_ezstep stepname prevstepname command .... # stepname=$1;shift +prevstepname=$1;shift + command=$* allstatus=99 runstatus=99 logpropstatus=99 + +prev_env=.ezsteps/${prevstepname}.sh +if [ -e $prev_env ];then + source $prev_env +fi # source the environment from the previous step if it exists # if a logpro file exists then use it otherwise just run the command, nb// was using 2>&1 if [ -e ${stepname}.logpro ];then