0030241: Test system - create option to exclude test groups from testgrid command
[occt.git] / src / DrawResources / TestCommands.tcl
CommitLineData
d5f74e42 1# Copyright (c) 2013-2014 OPEN CASCADE SAS
40093367 2#
973c2be1 3# This file is part of Open CASCADE Technology software library.
40093367 4#
d5f74e42 5# This library is free software; you can redistribute it and/or modify it under
6# the terms of the GNU Lesser General Public License version 2.1 as published
973c2be1 7# by the Free Software Foundation, with special exception defined in the file
8# OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
9# distribution for complete text of the license and disclaimer of any warranty.
40093367 10#
973c2be1 11# Alternatively, this file may be used under the terms of Open CASCADE
12# commercial license or contractual agreement.
40093367 13
14############################################################################
15# This file defines scripts for execution of OCCT tests.
16# It should be loaded automatically when DRAW is started, and provides
351bbcba 17# top-level commands starting with 'test'. Type 'help test' to get their
18# synopsys.
40093367 19# See OCCT Tests User Guide for description of the test system.
20#
21# Note: procedures with names starting with underscore are for internal use
22# inside the test system.
23############################################################################
24
25# Default verbose level for command _run_test
26set _tests_verbose 0
27
28# regexp for parsing test case results in summary log
29set _test_case_regexp {^CASE\s+([\w.-]+)\s+([\w.-]+)\s+([\w.-]+)\s*:\s*([\w]+)(.*)}
30
31# Basic command to run indicated test case in DRAW
b725d7c5 32help test {
33 Run specified test case
433673e0 34 Use: test group grid casename [options...]
35 Allowed options are:
36 -echo: all commands and results are echoed immediately,
37 but log is not saved and summary is not produced
38 It is also possible to use "1" instead of "-echo"
39 If echo is OFF, log is stored in memory and only summary
40 is output (the log can be obtained with command "dlog get")
41 -outfile filename: set log file (should be non-existing),
42 it is possible to save log file in text file or
43 in html file(with snapshot), for that "filename"
44 should have ".html" extension
45 -overwrite: force writing log in existing file
46 -beep: play sound signal at the end of the test
5fd3308a 47 -errors: show all lines from the log report that are recognized as errors
48 This key will be ignored if the "-echo" key is already set.
b725d7c5 49}
433673e0 50proc test {group grid casename {args {}}} {
51 # set default values of arguments
52 set echo 0
5fd3308a 53 set errors 0
433673e0 54 set logfile ""
55 set overwrite 0
56 set signal 0
57
40093367 58 # get test case paths (will raise error if input is invalid)
59 _get_test $group $grid $casename dir gridname casefile
60
433673e0 61 # check arguments
62 for {set narg 0} {$narg < [llength $args]} {incr narg} {
63 set arg [lindex $args $narg]
433673e0 64 # if echo specified as "-echo", convert it to bool
65 if { $arg == "-echo" || $arg == "1" } {
6d368502 66 set echo t
67 continue
433673e0 68 }
69
70 # output log file
71 if { $arg == "-outfile" } {
6d368502 72 incr narg
73 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
74 set logfile [lindex $args $narg]
75 } else {
76 error "Option -outfile requires argument"
77 }
78 continue
433673e0 79 }
80
81 # allow overwrite existing log
82 if { $arg == "-overwrite" } {
6d368502 83 set overwrite 1
84 continue
433673e0 85 }
86
87 # sound signal at the end of the test
88 if { $arg == "-beep" } {
6d368502 89 set signal t
90 continue
433673e0 91 }
92
5fd3308a 93 # if errors specified as "-errors", convert it to bool
94 if { $arg == "-errors" } {
6d368502 95 set errors t
96 continue
5fd3308a 97 }
98
433673e0 99 # unsupported option
100 error "Error: unsupported option \"$arg\""
101 }
40093367 102 # run test
c97067ad 103 uplevel _run_test $dir $group $gridname $casefile $echo
40093367 104
105 # check log
433673e0 106 if { !$echo } {
5fd3308a 107 _check_log $dir $group $gridname $casename $errors [dlog get] summary html_log
433673e0 108
109 # create log file
110 if { ! $overwrite && [file isfile $logfile] } {
111 error "Error: Specified log file \"$logfile\" exists; please remove it before running test or use -overwrite option"
112 }
113 if {$logfile != ""} {
6d368502 114 if {[file extension $logfile] == ".html"} {
115 if {[regexp {vdump ([^\s\n]+)} $html_log dump snapshot]} {
116 catch {file copy -force $snapshot [file rootname $logfile][file extension $snapshot]}
117 }
118 _log_html $logfile $html_log "Test $group $grid $casename"
119 } else {
120 _log_save $logfile "[dlog get]\n$summary" "Test $group $grid $casename"
433673e0 121 }
433673e0 122 }
5df3a117 123 }
40093367 124
433673e0 125 # play sound signal at the end of test
126 if {$signal} {
6d368502 127 puts "\7\7\7\7"
433673e0 128 }
40093367 129 return
130}
131
132# Basic command to run indicated test case in DRAW
b725d7c5 133help testgrid {
134 Run all tests, or specified group, or one grid
87f42a3f 135 Use: testgrid [groupmask [gridmask [casemask]]] [options...]
b725d7c5 136 Allowed options are:
20f720f1 137 -exclude N: exclude group, subgroup or single test case from executing, where
138 N is name of group, subgroup or case. Excluded items should be separated by comma.
139 Option should be used as the first argument after list of executed groups, grids, and test cases.
b725d7c5 140 -parallel N: run N parallel processes (default is number of CPUs, 0 to disable)
9753e6de 141 -refresh N: save summary logs every N seconds (default 600, minimal 1, 0 to disable)
b725d7c5 142 -outdir dirname: set log directory (should be empty or non-existing)
143 -overwrite: force writing logs in existing non-empty directory
144 -xml filename: write XML report for Jenkins (in JUnit-like format)
433673e0 145 -beep: play sound signal at the end of the tests
b502ddc1 146 -regress dirname: re-run only a set of tests that have been detected as regressions on some previous run.
147 Here "dirname" is path to directory containing results of previous run.
87f42a3f 148 Groups, grids, and test cases to be executed can be specified by list of file
149 masks, separated by spaces or comma; default is all (*).
40093367 150}
b725d7c5 151proc testgrid {args} {
40093367 152 global env tcl_platform _tests_verbose
153
154 ######################################################
155 # check arguments
156 ######################################################
157
158 # check that environment variable defining paths to test scripts is defined
159 if { ! [info exists env(CSF_TestScriptsPath)] ||
6d368502 160 [llength $env(CSF_TestScriptsPath)] <= 0 } {
40093367 161 error "Error: Environment variable CSF_TestScriptsPath is not defined"
162 }
163
164 # treat options
b725d7c5 165 set parallel [_get_nb_cpus]
40093367 166 set refresh 60
b725d7c5 167 set logdir ""
40093367 168 set overwrite 0
169 set xmlfile ""
433673e0 170 set signal 0
20f720f1 171 set exc_group 0
172 set exc_grid 0
173 set exc_case 0
b502ddc1 174 set regress 0
175 set prev_logdir ""
40093367 176 for {set narg 0} {$narg < [llength $args]} {incr narg} {
6d368502 177 set arg [lindex $args $narg]
178
179 # parallel execution
180 if { $arg == "-parallel" } {
181 incr narg
182 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
183 set parallel [expr [lindex $args $narg]]
184 } else {
185 error "Option -parallel requires argument"
186 }
187 continue
188 }
189
190 # refresh logs time
191 if { $arg == "-refresh" } {
192 incr narg
193 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
194 set refresh [expr [lindex $args $narg]]
195 } else {
196 error "Option -refresh requires argument"
197 }
198 continue
199 }
200
201 # output directory
202 if { $arg == "-outdir" } {
203 incr narg
204 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
205 set logdir [lindex $args $narg]
206 } else {
207 error "Option -outdir requires argument"
208 }
209 continue
210 }
211
212 # allow overwrite logs
213 if { $arg == "-overwrite" } {
214 set overwrite 1
215 continue
216 }
217
218 # refresh logs time
219 if { $arg == "-xml" } {
220 incr narg
221 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
222 set xmlfile [lindex $args $narg]
223 }
224 if { $xmlfile == "" } {
225 set xmlfile TESTS-summary.xml
226 }
227 continue
228 }
229
230 # sound signal at the end of the test
231 if { $arg == "-beep" } {
232 set signal t
233 continue
234 }
235
b502ddc1 236 # re-run only a set of tests that have been detected as regressions on some previous run
237 if { $arg == "-regress" } {
238 incr narg
239 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
240 set prev_logdir [lindex $args $narg]
241 set regress 1
242 } else {
243 error "Option -regress requires argument"
244 }
245 continue
246 }
247
20f720f1 248 # exclude group, subgroup or single test case from executing
249 if { $arg == "-exclude" } {
250 incr narg
251 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
252 set argts $args
253 set idx_begin [string first " -ex" $argts]
254 if { ${idx_begin} != "-1" } {
255 set argts [string replace $argts 0 $idx_begin]
256 }
257 set idx_exclude [string first "exclude" $argts]
258 if { ${idx_exclude} != "-1" } {
259 set argts [string replace $argts 0 $idx_exclude+7]
260 }
261 set idx [string first " -" $argts]
262 if { ${idx} != "-1" } {
263 set argts [string replace $argts $idx end]
264 }
265 set argts [split $argts ,]
266 foreach argt $argts {
267 if { [llength $argt] == 1 } {
268 lappend exclude_group $argt
269 set exc_group 1
270 } elseif { [llength $argt] == 2 } {
271 lappend exclude_grid $argt
272 set exc_grid 1
273 incr narg
274 } elseif { [llength $argt] == 3 } {
275 lappend exclude_case $argt
276 set exc_case 1
277 incr narg
278 incr narg
279 }
280 }
281 } else {
282 error "Option -exclude requires argument"
283 }
284 continue
285 }
286
6d368502 287 # unsupported option
288 if { [regexp {^-} $arg] } {
289 error "Error: unsupported option \"$arg\""
290 }
291
292 # treat arguments not recognized as options as group and grid names
293 if { ! [info exists groupmask] } {
294 set groupmask [split $arg ,]
295 } elseif { ! [info exists gridmask] } {
296 set gridmask [split $arg ,]
297 } elseif { ! [info exists casemask] } {
298 set casemask [split $arg ,]
299 } else {
300 error "Error: cannot interpret argument $narg ($arg)"
301 }
40093367 302 }
303
304 # check that target log directory is empty or does not exist
305 set logdir [file normalize [string trim $logdir]]
b502ddc1 306 set prev_logdir [file normalize [string trim $prev_logdir]]
40093367 307 if { $logdir == "" } {
6d368502 308 # if specified logdir is empty string, generate unique name like
ffc26091 309 # results/<branch>_<timestamp>
310 set prefix ""
b725d7c5 311 if { ! [catch {exec git branch} gitout] &&
b502ddc1 312 [regexp {[*] ([\w-]+)} $gitout res branch] } {
ffc26091 313 set prefix "${branch}_"
b725d7c5 314 }
6d368502 315 set logdir "results/${prefix}[clock format [clock seconds] -format {%Y-%m-%dT%H%M}]"
096ef695 316
6d368502 317 set logdir [file normalize $logdir]
40093367 318 }
319 if { [file isdirectory $logdir] && ! $overwrite && ! [catch {glob -directory $logdir *}] } {
6d368502 320 error "Error: Specified log directory \"$logdir\" is not empty; please clean it before running tests"
40093367 321 }
322 if { [catch {file mkdir $logdir}] || ! [file writable $logdir] } {
6d368502 323 error "Error: Cannot create directory \"$logdir\", or it is not writable"
40093367 324 }
325
87f42a3f 326 # masks for search of test groups, grids, and cases
327 if { ! [info exists groupmask] } { set groupmask * }
328 if { ! [info exists gridmask ] } { set gridmask * }
329 if { ! [info exists casemask ] } { set casemask * }
330
b502ddc1 331 # Find test cases with FAILED and IMPROVEMENT statuses in previous run
332 # if option "regress" is given
333 set rerun_group_grid_case {}
334
335 if { ${regress} > 0 } {
336 if { "${groupmask}" != "*"} {
337 lappend rerun_group_grid_case [list $groupmask $gridmask $casemask]
338 }
339 } else {
340 lappend rerun_group_grid_case [list $groupmask $gridmask $casemask]
341 }
342
343 if { ${regress} > 0 } {
344 if { [file exists ${prev_logdir}/tests.log] } {
345 set fd [open ${prev_logdir}/tests.log]
346 while { [gets $fd line] >= 0 } {
347 if {[regexp {CASE ([^\s]+) ([^\s]+) ([^\s]+): FAILED} $line dump group grid casename] ||
348 [regexp {CASE ([^\s]+) ([^\s]+) ([^\s]+): IMPROVEMENT} $line dump group grid casename]} {
349 lappend rerun_group_grid_case [list $group $grid $casename]
350 }
351 }
352 close $fd
353 } else {
354 error "Error: file ${prev_logdir}/tests.log is not found, check your input arguments!"
355 }
356 }
357
40093367 358 ######################################################
359 # prepare list of tests to be performed
360 ######################################################
361
362 # list of tests, each defined by a list of:
363 # test scripts directory
364 # group (subfolder) name
365 # grid (subfolder) name
366 # test case name
367 # path to test case file
368 set tests_list {}
369
b502ddc1 370 foreach group_grid_case ${rerun_group_grid_case} {
371 set groupmask [lindex $group_grid_case 0]
372 set gridmask [lindex $group_grid_case 1]
373 set casemask [lindex $group_grid_case 2]
40093367 374
b502ddc1 375 # iterate by all script paths
376 foreach dir [lsort -unique [_split_path $env(CSF_TestScriptsPath)]] {
377 # protection against empty paths
378 set dir [string trim $dir]
379 if { $dir == "" } { continue }
40093367 380
b502ddc1 381 if { $_tests_verbose > 0 } { _log_and_puts log "Examining tests directory $dir" }
40093367 382
b502ddc1 383 # check that directory exists
384 if { ! [file isdirectory $dir] } {
385 _log_and_puts log "Warning: directory $dir listed in CSF_TestScriptsPath does not exist, skipped"
6d368502 386 continue
387 }
388
b502ddc1 389 # search all directories in the current dir with specified mask
390 if [catch {glob -directory $dir -tail -types d {*}$groupmask} groups] { continue }
6d368502 391
20f720f1 392 # exclude selected groups from all groups
393 if { ${exc_group} > 0 } {
394 foreach exclude_group_element ${exclude_group} {
395 set idx [lsearch $groups "${exclude_group_element}"]
396 if { ${idx} != "-1" } {
397 set groups [lreplace $groups $idx $idx]
398 } else {
399 continue
400 }
401 }
402 }
403
b502ddc1 404 # iterate by groups
405 if { $_tests_verbose > 0 } { _log_and_puts log "Groups to be executed: $groups" }
406 foreach group [lsort -dictionary $groups] {
407 if { $_tests_verbose > 0 } { _log_and_puts log "Examining group directory $group" }
6d368502 408
b502ddc1 409 # file grids.list must exist: it defines sequence of grids in the group
410 if { ! [file exists $dir/$group/grids.list] } {
411 _log_and_puts log "Warning: directory $dir/$group does not contain file grids.list, skipped"
6d368502 412 continue
413 }
414
b502ddc1 415 # read grids.list file and make a list of grids to be executed
416 set gridlist {}
417 set fd [open $dir/$group/grids.list]
418 set nline 0
419 while { [gets $fd line] >= 0 } {
420 incr nline
6d368502 421
b502ddc1 422 # skip comments and empty lines
423 if { [regexp "\[ \t\]*\#.*" $line] } { continue }
424 if { [string trim $line] == "" } { continue }
6d368502 425
b502ddc1 426 # get grid id and name
427 if { ! [regexp "^\(\[0-9\]+\)\[ \t\]*\(\[A-Za-z0-9_.-\]+\)\$" $line res gridid grid] } {
428 _log_and_puts log "Warning: cannot recognize line $nline in file $dir/$group/grids.list as \"gridid gridname\"; ignored"
429 continue
87f42a3f 430 }
6d368502 431
b502ddc1 432 # check that grid fits into the specified mask
433 foreach mask $gridmask {
434 if { $mask == $gridid || [string match $mask $grid] } {
435 lappend gridlist $grid
436 }
437 }
6d368502 438 }
b502ddc1 439 close $fd
440
20f720f1 441 # exclude selected grids from all grids
442 if { ${exc_grid} > 0 } {
443 foreach exclude_grid_element ${exclude_grid} {
444 set exclude_elem [lindex $exclude_grid_element end]
445 set idx [lsearch $gridlist "${exclude_elem}"]
446 if { ${idx} != "-1" } {
447 set gridlist [lreplace $gridlist $idx $idx]
448 } else {
449 continue
450 }
451 }
452 }
453
b502ddc1 454 # iterate by all grids
455 foreach grid $gridlist {
456
457 # check if this grid is aliased to another one
458 set griddir $dir/$group/$grid
459 if { [file exists $griddir/cases.list] } {
460 set fd [open $griddir/cases.list]
461 if { [gets $fd line] >= 0 } {
462 set griddir [file normalize $dir/$group/$grid/[string trim $line]]
463 }
464 close $fd
465 }
6d368502 466
b502ddc1 467 # check if grid directory actually exists
468 if { ! [file isdirectory $griddir] } {
469 _log_and_puts log "Error: tests directory for grid $grid ($griddir) is missing; skipped"
6d368502 470 continue
87f42a3f 471 }
40093367 472
b502ddc1 473 # create directory for logging test results
474 if { $logdir != "" } { file mkdir $logdir/$group/$grid }
475
476 # iterate by all tests in the grid directory
477 if { [catch {glob -directory $griddir -type f {*}$casemask} testfiles] } { continue }
20f720f1 478
479 # exclude selected test cases from all testfiles
480 if { ${exc_case} > 0 } {
481 foreach exclude_case_element ${exclude_case} {
482 set exclude_casegroup_elem [lindex $exclude_case_element end-2]
483 set exclude_casegrid_elem [lindex $exclude_case_element end-1]
484 set exclude_elem [lindex $exclude_case_element end]
485 if { ${exclude_casegrid_elem} == "${grid}" } {
486 set idx [lsearch $testfiles "${dir}/${exclude_casegroup_elem}/${exclude_casegrid_elem}/${exclude_elem}"]
487 if { ${idx} != "-1" } {
488 set testfiles [lreplace $testfiles $idx $idx]
489 } else {
490 continue
491 }
492 }
493 }
494 }
495
b502ddc1 496 foreach casefile [lsort -dictionary $testfiles] {
497 # filter out files with reserved names
498 set casename [file tail $casefile]
499 if { $casename == "begin" || $casename == "end" ||
500 $casename == "parse.rules" } {
501 continue
502 }
503
504 lappend tests_list [list $dir $group $grid $casename $casefile]
505 }
6d368502 506 }
507 }
508 }
40093367 509 }
510 if { [llength $tests_list] < 1 } {
b502ddc1 511 error "Error: no tests are found, check your input arguments and variable CSF_TestScriptsPath!"
433673e0 512 } else {
6d368502 513 puts "Running tests (total [llength $tests_list] test cases)..."
40093367 514 }
515
516 ######################################################
517 # run tests
518 ######################################################
519
520 # log command arguments and environment
9753e6de 521 lappend log "Command: testgrid $args"
522 lappend log "Host: [info hostname]"
523 lappend log "Started on: [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]"
524 catch {lappend log "DRAW build:\n[dversion]" }
525 lappend log "Environment:"
8a262fa1 526 foreach envar [lsort [array names env]] {
6d368502 527 lappend log "$envar=\"$env($envar)\""
40093367 528 }
9753e6de 529 lappend log ""
40093367 530
531 set refresh_timer [clock seconds]
532 uplevel dchrono _timer reset
533 uplevel dchrono _timer start
534
535 # if parallel execution is requested, allocate thread pool
536 if { $parallel > 0 } {
6d368502 537 if { ! [info exists tcl_platform(threaded)] || [catch {package require Thread}] } {
538 _log_and_puts log "Warning: Tcl package Thread is not available, running in sequential mode"
539 set parallel 0
540 } else {
541 set worker [tpool::create -minworkers $parallel -maxworkers $parallel]
542 # suspend the pool until all jobs are posted, to prevent blocking of the process
543 # of starting / processing jobs by running threads
544 catch {tpool::suspend $worker}
545 if { $_tests_verbose > 0 } { _log_and_puts log "Executing tests in (up to) $parallel threads" }
9753e6de 546 # limit number of jobs in the queue by reasonable value
547 # to prevent slowdown due to unnecessary queue processing
548 set nbpooled 0
549 set nbpooled_max [expr 10 * $parallel]
550 set nbpooled_ok [expr 5 * $parallel]
6d368502 551 }
40093367 552 }
553
554 # start test cases
8a262fa1 555 set userbreak 0
40093367 556 foreach test_def $tests_list {
8a262fa1 557 # check for user break
9753e6de 558 if { $userbreak || "[info commands dbreak]" == "dbreak" && [catch dbreak] } {
8a262fa1 559 set userbreak 1
560 break
561 }
562
6d368502 563 set dir [lindex $test_def 0]
564 set group [lindex $test_def 1]
565 set grid [lindex $test_def 2]
566 set casename [lindex $test_def 3]
567 set casefile [lindex $test_def 4]
568
569 # command to set tests for generation of image in results directory
570 set imgdir_cmd ""
571 if { $logdir != "" } { set imgdir_cmd "set imagedir $logdir/$group/$grid" }
572
573 # prepare command file for running test case in separate instance of DRAW
574 set fd_cmd [open $logdir/$group/$grid/${casename}.tcl w]
575 puts $fd_cmd "$imgdir_cmd"
576 puts $fd_cmd "set test_image $casename"
577 puts $fd_cmd "_run_test $dir $group $grid $casefile t"
578
579 # use dlog command to obtain complete output of the test when it is absent (i.e. since OCCT 6.6.0)
580 # note: this is not needed if echo is set to 1 in call to _run_test above
581 if { ! [catch {dlog get}] } {
582 puts $fd_cmd "puts \[dlog get\]"
583 } else {
584 # else try to use old-style QA_ variables to get more output...
585 set env(QA_DUMP) 1
586 set env(QA_DUP) 1
587 set env(QA_print_command) 1
588 }
589
590 # final 'exit' is needed when running on Linux under VirtualGl
591 puts $fd_cmd "exit"
592 close $fd_cmd
8418c617 593
594 # commant to run DRAW with a command file;
595 # note that empty string is passed as standard input to avoid possible
596 # hang-ups due to waiting for stdin of the launching process
6d368502 597 set command "exec <<{} DRAWEXE -f $logdir/$group/$grid/${casename}.tcl"
598
599 # alternative method to run without temporary file; disabled as it needs too many backslashes
600 # else {
601 # set command "exec <<\"\" DRAWEXE -c $imgdir_cmd\\\; set test_image $casename\\\; \
602 # _run_test $dir $group $grid $casefile\\\; \
603 # puts \\\[dlog get\\\]\\\; exit"
604 # }
605
606 # run test case, either in parallel or sequentially
607 if { $parallel > 0 } {
608 # parallel execution
609 set job [tpool::post -nowait $worker "catch \"$command\" output; return \$output"]
610 set job_def($job) [list $logdir $dir $group $grid $casename]
9753e6de 611 incr nbpooled
612 if { $nbpooled > $nbpooled_max } {
613 _testgrid_process_jobs $worker $nbpooled_ok
614 }
6d368502 615 } else {
616 # sequential execution
617 catch {eval $command} output
618 _log_test_case $output $logdir $dir $group $grid $casename log
619
620 # update summary log with requested period
621 if { $logdir != "" && $refresh > 0 && [expr [clock seconds] - $refresh_timer > $refresh] } {
622 # update and dump summary
623 _log_summarize $logdir $log
624 set refresh_timer [clock seconds]
625 }
626 }
40093367 627 }
628
629 # get results of started threads
630 if { $parallel > 0 } {
9753e6de 631 _testgrid_process_jobs $worker
6d368502 632 # release thread pool
9753e6de 633 if { $nbpooled > 0 } {
6d368502 634 tpool::cancel $worker [array names job_def]
9753e6de 635 }
636 catch {tpool::resume $worker}
6d368502 637 tpool::release $worker
40093367 638 }
639
640 uplevel dchrono _timer stop
641 set time [lindex [split [uplevel dchrono _timer show] "\n"] 0]
642
8a262fa1 643 if { $userbreak } {
9753e6de 644 _log_and_puts log "*********** Stopped by user break ***********"
8a262fa1 645 set time "${time} \nNote: the process is not finished, stopped by user break!"
646 }
647
40093367 648 ######################################################
649 # output summary logs and exit
650 ######################################################
651
652 _log_summarize $logdir $log $time
653 if { $logdir != "" } {
6d368502 654 puts "Detailed logs are saved in $logdir"
40093367 655 }
656 if { $logdir != "" && $xmlfile != "" } {
6d368502 657 # XML output file is assumed relative to log dir unless it is absolute
658 if { [ file pathtype $xmlfile] == "relative" } {
659 set xmlfile [file normalize $logdir/$xmlfile]
660 }
661 _log_xml_summary $logdir $xmlfile $log 0
662 puts "XML summary is saved to $xmlfile"
40093367 663 }
433673e0 664 # play sound signal at the end of test
665 if {$signal} {
6d368502 666 puts "\7\7\7\7"
433673e0 667 }
40093367 668 return
669}
670
22db40eb 671# Procedure to regenerate summary log from logs of test cases
672help testsummarize {
673 Regenerate summary log in the test directory from logs of test cases.
674 This can be necessary if test grids are executed separately (e.g. on
675 different stations) or some grids have been re-executed.
676 Use: testsummarize dir
677}
678proc testsummarize {dir} {
679 global _test_case_regexp
680
681 if { ! [file isdirectory $dir] } {
6d368502 682 error "Error: \"$dir\" is not a directory"
22db40eb 683 }
684
685 # get summary statements from all test cases in one log
9753e6de 686 set log {}
22db40eb 687
688 # to avoid huge listing of logs, first find all subdirectories and iterate
689 # by them, parsing log files in each subdirectory independently
690 foreach grid [glob -directory $dir -types d -tails */*] {
691 foreach caselog [glob -nocomplain -directory [file join $dir $grid] -types f -tails *.log] {
692 set file [file join $dir $grid $caselog]
693 set nbfound 0
694 set fd [open $file r]
6d368502 695 while { [gets $fd line] >= 0 } {
696 if { [regexp $_test_case_regexp $line res grp grd cas status message] } {
22db40eb 697 if { "[file join $grid $caselog]" != "[file join $grp $grd ${cas}.log]" } {
698 puts "Error: $file contains status line for another test case ($line)"
699 }
6d368502 700 lappend log $line
701 incr nbfound
702 }
703 }
22db40eb 704 close $fd
705
706 if { $nbfound != 1 } {
707 puts "Error: $file contains $nbfound status lines, expected 1"
708 }
709 }
710 }
711
712 _log_summarize $dir $log "Summary regenerated from logs at [clock format [clock seconds]]"
713 return
714}
715
cc6a292d 716# Procedure to compare results of two runs of test cases
b725d7c5 717help testdiff {
718 Compare results of two executions of tests (CPU times, ...)
22db40eb 719 Use: testdiff dir1 dir2 [groupname [gridname]] [options...]
b725d7c5 720 Where dir1 and dir2 are directories containing logs of two test runs.
721 Allowed options are:
28cab376 722 -image [filename]: compare only images and save its in specified file (default
723 name is <dir1>/diffimage-<dir2>.log)
724 -cpu [filename]: compare only CPU and save it in specified file (default
725 name is <dir1>/diffcpu-<dir2>.log)
726 -memory [filename]: compare only memory and save it in specified file (default
727 name is <dir1>/diffmemory-<dir2>.log)
22db40eb 728 -save filename: save resulting log in specified file (default name is
60874ff8 729 <dir1>/diff-<dir2>.log); HTML log is saved with same name
22db40eb 730 and extension .html
b725d7c5 731 -status {same|ok|all}: filter cases for comparing by their status:
732 same - only cases with same status are compared (default)
733 ok - only cases with OK status in both logs are compared
734 all - results are compared regardless of status
735 -verbose level:
736 1 - output only differences
22db40eb 737 2 - output also list of logs and directories present in one of dirs only
738 3 - (default) output also progress messages
d5f061b6 739 -highlight_percent value: highlight considerable (>value in %) deviations
740 of CPU and memory (default value is 5%)
cc6a292d 741}
742proc testdiff {dir1 dir2 args} {
743 if { "$dir1" == "$dir2" } {
6d368502 744 error "Input directories are the same"
cc6a292d 745 }
746
747 ######################################################
748 # check arguments
749 ######################################################
750
751 # treat options
22db40eb 752 set logfile [file join $dir1 "diff-[file tail $dir2].log"]
28cab376 753 set logfile_image ""
754 set logfile_cpu ""
755 set logfile_memory ""
756 set image false
757 set cpu false
758 set memory false
cc6a292d 759 set basename ""
28cab376 760 set save false
cc6a292d 761 set status "same"
762 set verbose 3
6d368502 763 set highlight_percent 5
cc6a292d 764 for {set narg 0} {$narg < [llength $args]} {incr narg} {
6d368502 765 set arg [lindex $args $narg]
6d368502 766 # log file name
767 if { $arg == "-save" } {
768 incr narg
769 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
770 set logfile [lindex $args $narg]
771 } else {
772 error "Error: Option -save must be followed by log file name"
773 }
28cab376 774 set save true
6d368502 775 continue
776 }
28cab376 777
778 # image compared log
779 if { $arg == "-image" } {
780 incr narg
781 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
782 set logfile_image [lindex $args $narg]
783 } else {
784 set logfile_image [file join $dir1 "diffimage-[file tail $dir2].log"]
785 incr narg -1
786 }
787 set image true
788 continue
789 }
790
791 # CPU compared log
792 if { $arg == "-cpu" } {
793 incr narg
794 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
795 set logfile_cpu [lindex $args $narg]
796 } else {
797 set logfile_cpu [file join $dir1 "diffcpu-[file tail $dir2].log"]
798 incr narg -1
799 }
800 set cpu true
801 continue
802 }
803
804 # memory compared log
805 if { $arg == "-memory" } {
806 incr narg
807 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
808 set logfile_memory [lindex $args $narg]
809 } else {
810 set logfile_memory [file join $dir1 "diffmemory-[file tail $dir2].log"]
811 incr narg -1
812 }
813 set memory true
814 continue
815 }
816
6d368502 817 # status filter
818 if { $arg == "-status" } {
819 incr narg
820 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
821 set status [lindex $args $narg]
822 } else {
823 set status ""
824 }
825 if { "$status" != "same" && "$status" != "all" && "$status" != "ok" } {
826 error "Error: Option -status must be followed by one of \"same\", \"all\", or \"ok\""
827 }
828 continue
829 }
830
831 # verbose level
832 if { $arg == "-verbose" } {
833 incr narg
834 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
835 set verbose [expr [lindex $args $narg]]
836 } else {
837 error "Error: Option -verbose must be followed by integer verbose level"
838 }
839 continue
840 }
841
842 # highlight_percent
843 if { $arg == "-highlight_percent" } {
844 incr narg
845 if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
846 set highlight_percent [expr [lindex $args $narg]]
847 } else {
848 error "Error: Option -highlight_percent must be followed by integer value"
849 }
850 continue
851 }
852
853 if { [regexp {^-} $arg] } {
854 error "Error: unsupported option \"$arg\""
855 }
22db40eb 856
857 # non-option arguments form a subdirectory path
6d368502 858 set basename [file join $basename $arg]
cc6a292d 859 }
28cab376 860
861 if {$image != false || $cpu != false || $memory != false} {
862 if {$save != false} {
863 error "Error: Option -save can not be used with image/cpu/memory options"
864 }
865 }
cc6a292d 866
867 # run diff procedure (recursive)
28cab376 868 _test_diff $dir1 $dir2 $basename $image $cpu $memory $status $verbose log log_image log_cpu log_memory
869
cc6a292d 870 # save result to log file
28cab376 871 if {$image == false && $cpu == false && $memory == false} {
872 if { "$logfile" != "" } {
873 _log_save $logfile [join $log "\n"]
874 _log_html_diff "[file rootname $logfile].html" $log $dir1 $dir2 ${highlight_percent}
875 puts "Log is saved to $logfile (and .html)"
876 }
877 } else {
878 foreach mode {image cpu memory} {
879 if {"[set logfile_${mode}]" != ""} {
880 _log_save "[set logfile_${mode}]" [join "[set log_${mode}]" "\n"]
881 _log_html_diff "[file rootname [set logfile_${mode}]].html" "[set log_${mode}]" $dir1 $dir2 ${highlight_percent}
882 puts "Log (${mode}) is saved to [set logfile_${mode}] (and .html)"
883 }
884 }
cc6a292d 885 }
cc6a292d 886 return
887}
888
351bbcba 889# Procedure to check data file before adding it to repository
890help testfile {
4eb6e8f4 891 Checks specified data files for putting them into the test data files repository.
892
893 Use: testfile filelist
351bbcba 894
895 Will report if:
896 - data file (non-binary) is in DOS encoding (CR/LF)
897 - same data file (with same or another name) already exists in the repository
898 - another file with the same name already exists
4eb6e8f4 899 Note that names are considered to be case-insensitive (for compatibility
900 with Windows).
351bbcba 901
902 Unless the file is already in the repository, tries to load it, reports
903 the recognized file format, file size, number of faces and edges in the
4eb6e8f4 904 loaded shape (if any), information contained its triangulation, and makes
905 snapshot (in the temporary directory).
906
351bbcba 907 Finally it advises whether the file should be put to public section of the
908 repository.
4eb6e8f4 909
910 Use: testfile -check
911
912 If "-check" is given as an argument, then procedure will check files already
913 located in the repository (for possible duplicates and for DOS encoding).
351bbcba 914}
915proc testfile {filelist} {
916 global env
917
918 # check that CSF_TestDataPath is defined
919 if { ! [info exists env(CSF_TestDataPath)] } {
920 error "Environment variable CSF_TestDataPath must be defined!"
921 }
922
4eb6e8f4 923 set checkrepo f
924 if { "$filelist" == "-check" } { set checkrepo t }
925
351bbcba 926 # build registry of existing data files (name -> path) and (size -> path)
4eb6e8f4 927 puts "Collecting info on test data files repository..."
351bbcba 928 foreach dir [_split_path $env(CSF_TestDataPath)] {
929 while {[llength $dir] != 0} {
930 set curr [lindex $dir 0]
931 set dir [lrange $dir 1 end]
932 eval lappend dir [glob -nocomplain -directory $curr -type d *]
933 foreach file [glob -nocomplain -directory $curr -type f *] {
934 set name [file tail $file]
935 set name_lower [string tolower $name]
4eb6e8f4 936 set size [file size $file]
351bbcba 937
938 # check that the file is not in DOS encoding
4eb6e8f4 939 if { $checkrepo } {
940 if { [_check_dos_encoding $file] } {
941 puts "Warning: file $file is in DOS encoding; was this intended?"
351bbcba 942 }
4eb6e8f4 943 _check_file_format $file
944
945 # check if file with the same name is present twice or more
946 if { [info exists names($name_lower)] } {
947 puts "Error: more than one file with name $name is present in the repository:"
948 if { [_diff_files $file $names($name_lower)] } {
949 puts "(files are different by content)"
950 } else {
951 puts "(files are same by content)"
952 }
953 puts "--> $file"
954 puts "--> $names($name_lower)"
955 continue
956 }
351bbcba 957
4eb6e8f4 958 # check if file with the same content exists
959 if { [info exists sizes($size)] } {
960 foreach other $sizes($size) {
961 if { ! [_diff_files $file $other] } {
962 puts "Warning: two files with the same content found:"
963 puts "--> $file"
964 puts "--> $other"
965 }
351bbcba 966 }
967 }
968 }
969
970 # add the file to the registry
4eb6e8f4 971 lappend names($name_lower) $file
351bbcba 972 lappend sizes($size) $file
6d368502 973 }
974 }
351bbcba 975 }
4eb6e8f4 976 if { $checkrepo || [llength $filelist] <= 0 } { return }
351bbcba 977
978 # check the new files
979 set has_images f
980 puts "Checking new file(s)..."
981 foreach file $filelist {
351bbcba 982 set name [file tail $file]
983 set name_lower [string tolower $name]
4eb6e8f4 984 set found f
351bbcba 985
986 # check for presence of the file with same name
987 if { [info exists names($name_lower)] } {
4eb6e8f4 988 set found f
989 foreach other $names($name_lower) {
990 # avoid comparing the file with itself
991 if { [file normalize $file] == [file normalize $other] } {
992 continue
993 }
994 # compare content
995 if { [_diff_files $file $other] } {
996 puts "\n* $file: error\n name is already used by existing file\n --> $other"
997 } else {
998 puts "\n* $file: already present \n --> $other"
999 }
1000 set found t
1001 break
351bbcba 1002 }
4eb6e8f4 1003 if { $found } { continue }
1004 }
1005
1006 # get size of the file; if it is in DOS encoding and less than 1 MB,
1007 # estimate also its size in UNIX encoding to be able to find same
1008 # file if already present but in UNIX encoding
1009 set sizeact [file size $file]
1010 set sizeunx ""
1011 set isdos [_check_dos_encoding $file]
1012 if { $isdos && $sizeact < 10000000 } {
1013 set fd [open $file r]
1014 fconfigure $fd -translation crlf
1015 set sizeunx [string length [read $fd]]
1016 close $fd
351bbcba 1017 }
1018
1019 # check if file with the same content exists
4eb6e8f4 1020 foreach size "$sizeact $sizeunx" {
1021 if { [info exists sizes($size)] } {
351bbcba 1022 foreach other $sizes($size) {
4eb6e8f4 1023 # avoid comparing the file with itself
1024 if { [file normalize $file] == [file normalize $other] } {
1025 continue
1026 }
1027 # compare content
351bbcba 1028 if { ! [_diff_files $file $other] } {
4eb6e8f4 1029 puts "\n* $file: duplicate \n already present under name [file tail $other]\n --> $other"
6d368502 1030 set found t
1031 break
351bbcba 1032 }
1033 }
4eb6e8f4 1034 if { $found } { break }
1035 }
1036 }
1037 if { $found } { continue }
1038
1039 # file is not present yet, so to be analyzed
1040 puts "\n* $file: new file"
1041
1042 # add the file to the registry as if it were added to the repository,
1043 # to report possible duplicates among the currently processed files
1044 lappend names($name_lower) $file
1045 if { "$sizeunx" != "" } {
1046 lappend sizes($sizeunx) $file
1047 } else {
1048 lappend sizes($sizeact) $file
1049 }
1050
1051 # first of all, complain if it is in DOS encoding
1052 if { $isdos } {
1053 puts " Warning: DOS encoding detected, consider converting to"
1054 puts " UNIX unless DOS line ends are needed for the test"
351bbcba 1055 }
1056
1057 # try to read the file
1058 set format [_check_file_format $file]
1059 if { [catch {uplevel load_data_file $file $format a}] } {
4eb6e8f4 1060 puts " Warning: Cannot read as $format file"
351bbcba 1061 continue
1062 }
1063
4eb6e8f4 1064 # warn if shape contains triangulation
1065 pload MODELING
1066 if { "$format" != "STL" &&
1067 [regexp {contains\s+([0-9]+)\s+triangles} [uplevel trinfo a] res nbtriangles] &&
1068 $nbtriangles != 0 } {
1069 puts " Warning: shape contains triangulation ($nbtriangles triangles),"
1070 puts " consider removing them unless they are needed for the test!"
1071 }
1072
351bbcba 1073 # get number of faces and edges
1074 set edges 0
1075 set faces 0
1076 set nbs [uplevel nbshapes a]
1077 regexp {EDGE[ \t:]*([0-9]+)} $nbs res edges
1078 regexp {FACE[ \t:]*([0-9]+)} $nbs res faces
1079
1080 # classify; first check file size and number of faces and edges
1081 if { $size < 95000 && $faces < 20 && $edges < 100 } {
1082 set dir public
1083 } else {
1084 set dir private
351bbcba 1085 }
1086
1087 # add stats
4eb6e8f4 1088 puts " $format size=[expr $size / 1024] KiB, nbfaces=$faces, nbedges=$edges -> $dir"
351bbcba 1089
60874ff8 1090 set tmpdir [_get_temp_dir]
1091 file mkdir $tmpdir/$dir
351bbcba 1092
1093 # make snapshot
1094 pload AISV
1095 uplevel vdisplay a
4eb6e8f4 1096 uplevel vsetdispmode 1
351bbcba 1097 uplevel vfit
1098 uplevel vzfit
60874ff8 1099 uplevel vdump $tmpdir/$dir/[file rootname [file tail $file]].png
351bbcba 1100 set has_images t
1101 }
1102 if { $has_images } {
60874ff8 1103 puts "Snapshots are saved in subdirectory [_get_temp_dir]"
351bbcba 1104 }
1105}
1106
22db40eb 1107# Procedure to locate data file for test given its name.
1108# The search is performed assuming that the function is called
1109# from the test case script; the search order is:
1110# - subdirectory "data" of the test script (grid) folder
1111# - subdirectories in environment variable CSF_TestDataPath
1112# - subdirectory set by datadir command
1113# If file is not found, raises Tcl error.
1114proc locate_data_file {filename} {
1115 global env groupname gridname casename
1116
1117 # check if the file is located in the subdirectory data of the script dir
1118 set scriptfile [info script]
1119 if { $scriptfile != "" } {
1120 set path [file join [file dirname $scriptfile] data $filename]
6d368502 1121 if { [file exists $path] } {
1122 return [file normalize $path]
1123 }
22db40eb 1124 }
1125
1126 # check sub-directories in paths indicated by CSF_TestDataPath
1127 if { [info exists env(CSF_TestDataPath)] } {
6d368502 1128 foreach dir [_split_path $env(CSF_TestDataPath)] {
1129 while {[llength $dir] != 0} {
1130 set name [lindex $dir 0]
1131 set dir [lrange $dir 1 end]
343f7e4d 1132
80482e01 1133 # skip directories starting with dot
343f7e4d 1134 set aTail [file tail $name]
1135 if { [regexp {^[.]} $aTail] } { continue }
6d368502 1136 if { [file exists $name/$filename] } {
1137 return [file normalize $name/$filename]
1138 }
1139 eval lappend dir [glob -nocomplain -directory $name -type d *]
1140 }
1141 }
22db40eb 1142 }
1143
1144 # check current datadir
1145 if { [file exists [uplevel datadir]/$filename] } {
6d368502 1146 return [file normalize [uplevel datadir]/$filename]
22db40eb 1147 }
1148
1149 # raise error
c97067ad 1150 error [join [list "File $filename could not be found" \
6d368502 1151 "(should be in paths indicated by CSF_TestDataPath environment variable, " \
22db40eb 1152 "or in subfolder data in the script directory)"] "\n"]
1153}
1154
40093367 1155# Internal procedure to find test case indicated by group, grid, and test case names;
1156# returns:
1157# - dir: path to the base directory of the tests group
1158# - gridname: actual name of the grid
1159# - casefile: path to the test case script
1160# if no such test is found, raises error with appropriate message
1161proc _get_test {group grid casename _dir _gridname _casefile} {
1162 upvar $_dir dir
1163 upvar $_gridname gridname
1164 upvar $_casefile casefile
1165
1166 global env
1167
1168 # check that environment variable defining paths to test scripts is defined
1169 if { ! [info exists env(CSF_TestScriptsPath)] ||
1170 [llength $env(CSF_TestScriptsPath)] <= 0 } {
1171 error "Error: Environment variable CSF_TestScriptsPath is not defined"
1172 }
1173
1174 # iterate by all script paths
1175 foreach dir [_split_path $env(CSF_TestScriptsPath)] {
6d368502 1176 # protection against empty paths
1177 set dir [string trim $dir]
1178 if { $dir == "" } { continue }
40093367 1179
1180 # check that directory exists
1181 if { ! [file isdirectory $dir] } {
1182 puts "Warning: directory $dir listed in CSF_TestScriptsPath does not exist, skipped"
6d368502 1183 continue
40093367 1184 }
1185
1186 # check if test group with given name exists in this dir
1187 # if not, continue to the next test dir
1188 if { ! [file isdirectory $dir/$group] } { continue }
1189
1190 # check that grid with given name (possibly alias) exists; stop otherwise
1191 set gridname $grid
1192 if { ! [file isdirectory $dir/$group/$gridname] } {
1193 # check if grid is named by alias rather than by actual name
1194 if { [file exists $dir/$group/grids.list] } {
1195 set fd [open $dir/$group/grids.list]
1196 while { [gets $fd line] >= 0 } {
1197 if { [regexp "\[ \t\]*\#.*" $line] } { continue }
1198 if { [regexp "^$grid\[ \t\]*\(\[A-Za-z0-9_.-\]+\)\$" $line res gridname] } {
1199 break
1200 }
1201 }
1202 close $fd
6d368502 1203 }
1204 }
40093367 1205 if { ! [file isdirectory $dir/$group/$gridname] } { continue }
1206
1207 # get actual file name of the script; stop if it cannot be found
1208 set casefile $dir/$group/$gridname/$casename
1209 if { ! [file exists $casefile] } {
1210 # check if this grid is aliased to another one
1211 if { [file exists $dir/$group/$gridname/cases.list] } {
1212 set fd [open $dir/$group/$gridname/cases.list]
1213 if { [gets $fd line] >= 0 } {
1214 set casefile [file normalize $dir/$group/$gridname/[string trim $line]/$casename]
1215 }
6d368502 1216 close $fd
1217 }
40093367 1218 }
1219 if { [file exists $casefile] } {
1220 # normal return
1221 return
1222 }
1223 }
1224
1225 # coming here means specified test is not found; report error
1226 error [join [list "Error: test case $group / $grid / $casename is not found in paths listed in variable" \
6d368502 1227 "CSF_TestScriptsPath (current value is \"$env(CSF_TestScriptsPath)\")"] "\n"]
40093367 1228}
1229
1230# Internal procedure to run test case indicated by base directory,
1231# grid and grid names, and test case file path.
1232# The log can be obtained by command "dlog get".
5df3a117 1233proc _run_test {scriptsdir group gridname casefile echo} {
40093367 1234 global env
1235
1236 # start timer
1237 uplevel dchrono _timer reset
1238 uplevel dchrono _timer start
433673e0 1239 catch {uplevel meminfo h} membase
40093367 1240
1241 # enable commands logging; switch to old-style mode if dlog command is not present
1242 set dlog_exists 1
1243 if { [catch {dlog reset}] } {
6d368502 1244 set dlog_exists 0
5df3a117 1245 } elseif { $echo } {
1246 decho on
40093367 1247 } else {
6d368502 1248 dlog reset
1249 dlog on
1250 rename puts puts-saved
1251 proc puts args {
1252 global _tests_verbose
1253
1254 # log only output to stdout and stderr, not to file!
1255 if {[llength $args] > 1} {
1256 set optarg [lindex $args end-1]
1257 if { $optarg == "stdout" || $optarg == "stderr" || $optarg == "-newline" } {
1258 dlog add [lindex $args end]
1259 } else {
1260 eval puts-saved $args
1261 }
1262 } else {
1263 dlog add [lindex $args end]
1264 }
1265 }
40093367 1266 }
1267
40093367 1268 # evaluate test case
936f43da 1269 set tmp_imagedir 0
40093367 1270 if [catch {
60874ff8 1271 # set variables identifying test case
6d368502 1272 uplevel set casename [file tail $casefile]
1273 uplevel set groupname $group
1274 uplevel set gridname $gridname
1275 uplevel set dirname $scriptsdir
40093367 1276
6d368502 1277 # set path for saving of log and images (if not yet set) to temp dir
1278 if { ! [uplevel info exists imagedir] } {
1279 uplevel set test_image \$casename
a3ef6605 1280
1281 # create subdirectory in temp named after group and grid with timestamp
1282 set rootlogdir [_get_temp_dir]
1283
1284 set imagedir "${group}-${gridname}-${::casename}-[clock format [clock seconds] -format {%Y-%m-%dT%Hh%Mm%Ss}]"
1285 set imagedir [file normalize ${rootlogdir}/$imagedir]
1286
1287 if { [catch {file mkdir $imagedir}] || ! [file writable $imagedir] ||
1288 ! [catch {glob -directory $imagedir *}] } {
6d368502 1289 # puts "Warning: Cannot create directory \"$imagedir\", or it is not empty; \"${rootlogdir}\" is used"
a3ef6605 1290 set imagedir $rootlogdir
1291 }
1292
d16b64f1 1293 uplevel set imagedir \"$imagedir\"
936f43da 1294 set tmp_imagedir 1
6d368502 1295 }
60874ff8 1296
6d368502 1297 # execute test scripts
40093367 1298 if { [file exists $scriptsdir/$group/begin] } {
6d368502 1299 puts "Executing $scriptsdir/$group/begin..."; flush stdout
1300 uplevel source $scriptsdir/$group/begin
1301 }
1302 if { [file exists $scriptsdir/$group/$gridname/begin] } {
1303 puts "Executing $scriptsdir/$group/$gridname/begin..."; flush stdout
1304 uplevel source $scriptsdir/$group/$gridname/begin
1305 }
1306
1307 puts "Executing $casefile..."; flush stdout
1308 uplevel source $casefile
1309
1310 if { [file exists $scriptsdir/$group/$gridname/end] } {
1311 puts "Executing $scriptsdir/$group/$gridname/end..."; flush stdout
1312 uplevel source $scriptsdir/$group/$gridname/end
1313 }
1314 if { [file exists $scriptsdir/$group/end] } {
1315 puts "Executing $scriptsdir/$group/end..."; flush stdout
1316 uplevel source $scriptsdir/$group/end
1317 }
40093367 1318 } res] {
6d368502 1319 puts "Tcl Exception: $res"
40093367 1320 }
1321
40093367 1322 # stop logging
1323 if { $dlog_exists } {
5df3a117 1324 if { $echo } {
6d368502 1325 decho off
5df3a117 1326 } else {
1327 rename puts {}
6d368502 1328 rename puts-saved puts
1329 dlog off
1330 }
40093367 1331 }
1332
8418c617 1333 # stop cpulimit killer if armed by the test
1334 cpulimit
1335
22db40eb 1336 # add memory and timing info
1337 set stats ""
433673e0 1338 if { ! [catch {uplevel meminfo h} memuse] } {
936f43da 1339 append stats "MEMORY DELTA: [expr ($memuse - $membase) / 1024] KiB\n"
22db40eb 1340 }
40093367 1341 uplevel dchrono _timer stop
1342 set time [uplevel dchrono _timer show]
936f43da 1343 if { [regexp -nocase {CPU user time:[ \t]*([0-9.e-]+)} $time res cpu_usr] } {
6d368502 1344 append stats "TOTAL CPU TIME: $cpu_usr sec\n"
22db40eb 1345 }
1346 if { $dlog_exists && ! $echo } {
6d368502 1347 dlog add $stats
22db40eb 1348 } else {
6d368502 1349 puts $stats
40093367 1350 }
936f43da 1351
1352 # unset global vars
1353 uplevel unset casename groupname gridname dirname
1354 if { $tmp_imagedir } { uplevel unset imagedir test_image }
40093367 1355}
1356
1357# Internal procedure to check log of test execution and decide if it passed or failed
5fd3308a 1358proc _check_log {dir group gridname casename errors log {_summary {}} {_html_log {}}} {
40093367 1359 global env
1360 if { $_summary != "" } { upvar $_summary summary }
1361 if { $_html_log != "" } { upvar $_html_log html_log }
9753e6de 1362 set summary {}
1363 set html_log {}
5fd3308a 1364 set errors_log {}
40093367 1365
6d368502 1366 if [catch {
1367
1368 # load definition of 'bad words' indicating test failure
1369 # note that rules are loaded in the order of decreasing priority (grid - group - common),
1370 # thus grid rules will override group ones
1371 set badwords {}
1372 foreach rulesfile [list $dir/$group/$gridname/parse.rules $dir/$group/parse.rules $dir/parse.rules] {
1373 if [catch {set fd [open $rulesfile r]}] { continue }
1374 while { [gets $fd line] >= 0 } {
1375 # skip comments and empty lines
1376 if { [regexp "\[ \t\]*\#.*" $line] } { continue }
1377 if { [string trim $line] == "" } { continue }
1378 # extract regexp
1379 if { ! [regexp {^([^/]*)/([^/]*)/(.*)$} $line res status rexp comment] } {
1380 puts "Warning: cannot recognize parsing rule \"$line\" in file $rulesfile"
1381 continue
1382 }
1383 set status [string trim $status]
1384 if { $comment != "" } { append status " ([string trim $comment])" }
1385 set rexp [regsub -all {\\b} $rexp {\\y}] ;# convert regexp from Perl to Tcl style
1386 lappend badwords [list $status $rexp]
1387 }
1388 close $fd
1389 }
1390 if { [llength $badwords] <= 0 } {
1391 puts "Warning: no definition of error indicators found (check files parse.rules)"
1392 }
1393
1394 # analyse log line-by-line
1395 set todos {} ;# TODO statements
1396 set requs {} ;# REQUIRED statements
1397 set todo_incomplete -1
1398 set status ""
1399 foreach line [split $log "\n"] {
1400 # check if line defines specific treatment of some messages
1401 if [regexp -nocase {^[ \s]*TODO ([^:]*):(.*)$} $line res platforms pattern] {
1402 if { ! [regexp -nocase {\mAll\M} $platforms] &&
863f782a 1403 ! [regexp -nocase "\\m[checkplatform]\\M" $platforms] } {
6d368502 1404 lappend html_log [_html_highlight IGNORE $line]
1405 continue ;# TODO statement is for another platform
1406 }
1407
1408 # record TODOs that mark unstable cases
1409 if { [regexp {[\?]} $platforms] } {
1410 set todos_unstable([llength $todos]) 1
1411 }
1412
1413 # convert legacy regexps from Perl to Tcl style
1414 set pattern [regsub -all {\\b} [string trim $pattern] {\\y}]
1415
1416 # special case: TODO TEST INCOMPLETE
1417 if { [string trim $pattern] == "TEST INCOMPLETE" } {
1418 set todo_incomplete [llength $todos]
1419 }
1420
1421 lappend todos [list $pattern [llength $html_log] $line]
1422 lappend html_log [_html_highlight BAD $line]
1423 continue
1424 }
1425 if [regexp -nocase {^[ \s]*REQUIRED ([^:]*):[ \s]*(.*)$} $line res platforms pattern] {
1426 if { ! [regexp -nocase {\mAll\M} $platforms] &&
863f782a 1427 ! [regexp -nocase "\\m[checkplatform]\\M" $platforms] } {
6d368502 1428 lappend html_log [_html_highlight IGNORE $line]
1429 continue ;# REQUIRED statement is for another platform
1430 }
1431 lappend requs [list $pattern [llength $html_log] $line]
1432 lappend html_log [_html_highlight OK $line]
1433 continue
1434 }
1435
1436 # check for presence of required messages
1437 set ismarked 0
1438 for {set i 0} {$i < [llength $requs]} {incr i} {
1439 set pattern [lindex $requs $i 0]
1440 if { [regexp $pattern $line] } {
1441 incr required_count($i)
1442 lappend html_log [_html_highlight OK $line]
1443 set ismarked 1
1444 continue
1445 }
1446 }
1447 if { $ismarked } {
1448 continue
1449 }
1450
1451 # check for presence of messages indicating test result
1452 foreach bw $badwords {
1453 if { [regexp [lindex $bw 1] $line] } {
1454 # check if this is known bad case
1455 set is_known 0
1456 for {set i 0} {$i < [llength $todos]} {incr i} {
1457 set pattern [lindex $todos $i 0]
1458 if { [regexp $pattern $line] } {
1459 set is_known 1
1460 incr todo_count($i)
1461 lappend html_log [_html_highlight BAD $line]
1462 break
1463 }
1464 }
1465
1466 # if it is not in todo, define status
1467 if { ! $is_known } {
1468 set stat [lindex $bw 0 0]
1469 if {$errors} {
1470 lappend errors_log $line
1471 }
1472 lappend html_log [_html_highlight $stat $line]
1473 if { $status == "" && $stat != "OK" && ! [regexp -nocase {^IGNOR} $stat] } {
1474 set status [lindex $bw 0]
1475 }
1476 }
1477 set ismarked 1
1478 break
1479 }
1480 }
1481 if { ! $ismarked } {
1482 lappend html_log $line
1483 }
1484 }
1485
1486 # check for presence of TEST COMPLETED statement
1487 if { $status == "" && ! [regexp {TEST COMPLETED} $log] } {
1488 # check whether absence of TEST COMPLETED is known problem
1489 if { $todo_incomplete >= 0 } {
1490 incr todo_count($todo_incomplete)
1491 } else {
1492 set status "FAILED (no final message is found)"
1493 }
1494 }
1495
1496 # report test as failed if it doesn't contain required pattern
1497 if { $status == "" } {
1498 for {set i 0} {$i < [llength $requs]} {incr i} {
1499 if { ! [info exists required_count($i)] } {
1500 set linenum [lindex $requs $i 1]
1501 set html_log [lreplace $html_log $linenum $linenum [_html_highlight FAILED [lindex $requs $i 2]]]
1502 set status "FAILED (REQUIRED statement no. [expr $i + 1] is not found)"
1503 }
1504 }
1505 }
1506
1507 # check declared bad cases and diagnose possible improvement
1508 # (bad case declared but not detected).
1509 # Note that absence of the problem marked by TODO with question mark
1510 # (unstable) is not reported as improvement.
1511 if { $status == "" } {
1512 for {set i 0} {$i < [llength $todos]} {incr i} {
1513 if { ! [info exists todos_unstable($i)] &&
1514 (! [info exists todo_count($i)] || $todo_count($i) <= 0) } {
1515 set linenum [lindex $todos $i 1]
1516 set html_log [lreplace $html_log $linenum $linenum [_html_highlight IMPROVEMENT [lindex $todos $i 2]]]
1517 set status "IMPROVEMENT (expected problem TODO no. [expr $i + 1] is not detected)"
1518 break;
1519 }
1520 }
1521 }
1522
1523 # report test as known bad if at least one of expected problems is found
1524 if { $status == "" && [llength [array names todo_count]] > 0 } {
1525 set status "BAD (known problem)"
1526 }
1527
1528 # report normal OK
1529 if { $status == "" } {set status "OK" }
1530
1531 } res] {
1532 set status "FAILED ($res)"
1533 }
40093367 1534
1535 # put final message
1536 _log_and_puts summary "CASE $group $gridname $casename: $status"
9753e6de 1537 set summary [join $summary "\n"]
5fd3308a 1538 if {$errors} {
6d368502 1539 foreach error $errors_log {
1540 _log_and_puts summary " $error"
1541 }
5fd3308a 1542 }
9753e6de 1543 set html_log "[_html_highlight [lindex $status 0] $summary]\n[join $html_log \n]"
40093367 1544}
1545
1546# Auxiliary procedure putting message to both cout and log variable (list)
1547proc _log_and_puts {logvar message} {
1548 if { $logvar != "" } {
6d368502 1549 upvar $logvar log
1550 lappend log $message
40093367 1551 }
1552 puts $message
1553}
1554
1555# Auxiliary procedure to log result on single test case
1556proc _log_test_case {output logdir dir group grid casename logvar} {
1557 upvar $logvar log
5fd3308a 1558 set show_errors 0
40093367 1559 # check result and make HTML log
5fd3308a 1560 _check_log $dir $group $grid $casename $show_errors $output summary html_log
9753e6de 1561 lappend log $summary
40093367 1562
1563 # save log to file
1564 if { $logdir != "" } {
6d368502 1565 _log_html $logdir/$group/$grid/$casename.html $html_log "Test $group $grid $casename"
1566 _log_save $logdir/$group/$grid/$casename.log "$output\n$summary" "Test $group $grid $casename"
40093367 1567 }
1568}
1569
1570# Auxiliary procedure to save log to file
1571proc _log_save {file log {title {}}} {
1572 # create missing directories as needed
1573 catch {file mkdir [file dirname $file]}
1574
1575 # try to open a file
1576 if [catch {set fd [open $file w]} res] {
6d368502 1577 error "Error saving log file $file: $res"
40093367 1578 }
1579
1580 # dump log and close
1581 puts $fd "$title\n"
1582 puts $fd $log
1583 close $fd
1584 return
1585}
1586
22db40eb 1587# Auxiliary procedure to make a (relative if possible) URL to a file for
1588# inclusion a reference in HTML log
1589proc _make_url {htmldir file} {
1590 set htmlpath [file split [file normalize $htmldir]]
1591 set filepath [file split [file normalize $file]]
1592 for {set i 0} {$i < [llength $htmlpath]} {incr i} {
1593 if { "[lindex $htmlpath $i]" != "[lindex $filepath $i]" } {
1594 if { $i == 0 } { break }
1595 return "[string repeat "../" [expr [llength $htmlpath] - $i - 1]][eval file join [lrange $filepath $i end]]"
1596 }
1597 }
1598
1599 # if relative path could not be made, return full file URL
1600 return "file://[file normalize $file]"
1601}
1602
40093367 1603# Auxiliary procedure to save log to file
1604proc _log_html {file log {title {}}} {
1605 # create missing directories as needed
1606 catch {file mkdir [file dirname $file]}
1607
1608 # try to open a file
1609 if [catch {set fd [open $file w]} res] {
6d368502 1610 error "Error saving log file $file: $res"
40093367 1611 }
1612
1613 # print header
22db40eb 1614 puts $fd "<html><head><title>$title</title></head><body><h1>$title</h1>"
40093367 1615
ffc26091 1616 # add images if present; these should have either PNG, GIF, or JPG extension,
1617 # and start with name of the test script, with optional suffix separated
1618 # by underscore or dash
40093367 1619 set imgbasename [file rootname [file tail $file]]
ffc26091 1620 foreach img [lsort [glob -nocomplain -directory [file dirname $file] -tails \
1621 ${imgbasename}.gif ${imgbasename}.png ${imgbasename}.jpg \
1622 ${imgbasename}_*.gif ${imgbasename}_*.png ${imgbasename}_*.jpg \
1623 ${imgbasename}-*.gif ${imgbasename}-*.png ${imgbasename}-*.jpg]] {
6d368502 1624 puts $fd "<p>[file tail $img]<br><img src=\"$img\"/><p>"
40093367 1625 }
1626
b725d7c5 1627 # print log body, trying to add HTML links to script files on lines like
1628 # "Executing <filename>..."
40093367 1629 puts $fd "<pre>"
b725d7c5 1630 foreach line [split $log "\n"] {
1631 if { [regexp {Executing[ \t]+([a-zA-Z0-9._/:-]+[^.])} $line res script] &&
1632 [file exists $script] } {
22db40eb 1633 set line [regsub $script $line "<a href=\"[_make_url $file $script]\">$script</a>"]
b725d7c5 1634 }
1635 puts $fd $line
1636 }
40093367 1637 puts $fd "</pre></body></html>"
1638
1639 close $fd
1640 return
1641}
1642
1643# Auxiliary method to make text with HTML highlighting according to status
1644proc _html_color {status} {
1645 # choose a color for the cell according to result
1646 if { $status == "OK" } {
6d368502 1647 return lightgreen
40093367 1648 } elseif { [regexp -nocase {^FAIL} $status] } {
77887d30 1649 return ff8080
40093367 1650 } elseif { [regexp -nocase {^BAD} $status] } {
6d368502 1651 return yellow
40093367 1652 } elseif { [regexp -nocase {^IMP} $status] } {
6d368502 1653 return orange
40093367 1654 } elseif { [regexp -nocase {^SKIP} $status] } {
6d368502 1655 return gray
40093367 1656 } elseif { [regexp -nocase {^IGNOR} $status] } {
6d368502 1657 return gray
40093367 1658 } else {
6d368502 1659 puts "Warning: no color defined for status $status, using red as if FAILED"
1660 return red
40093367 1661 }
1662}
1663
1664# Format text line in HTML to be colored according to the status
1665proc _html_highlight {status line} {
1666 return "<table><tr><td bgcolor=\"[_html_color $status]\">$line</td></tr></table>"
1667}
1668
1669# Internal procedure to generate HTML page presenting log of the tests
1670# execution in tabular form, with links to reports on individual cases
4514dc74 1671proc _log_html_summary {logdir log totals regressions improvements skipped total_time} {
40093367 1672 global _test_case_regexp
1673
9753e6de 1674 # create missing directories as needed
1675 file mkdir $logdir
40093367 1676
1677 # try to open a file and start HTML
1678 if [catch {set fd [open $logdir/summary.html w]} res] {
6d368502 1679 error "Error creating log file: $res"
40093367 1680 }
1681
1682 # write HRML header, including command to refresh log if still in progress
1683 puts $fd "<html><head>"
1684 puts $fd "<title>Tests summary</title>"
1685 if { $total_time == "" } {
6d368502 1686 puts $fd "<meta http-equiv=\"refresh\" content=\"10\">"
40093367 1687 }
1688 puts $fd "<meta http-equiv=\"pragma\" content=\"NO-CACHE\">"
1689 puts $fd "</head><body>"
1690
1691 # put summary
1692 set legend(OK) "Test passed OK"
1693 set legend(FAILED) "Test failed (regression)"
1694 set legend(BAD) "Known problem"
1695 set legend(IMPROVEMENT) "Possible improvement (expected problem not detected)"
1696 set legend(SKIPPED) "Test skipped due to lack of data file"
1697 puts $fd "<h1>Summary</h1><table>"
1698 foreach nbstat $totals {
6d368502 1699 set status [lindex $nbstat 1]
1700 if { [info exists legend($status)] } {
1701 set comment $legend($status)
1702 } else {
1703 set comment "User-defined status"
1704 }
1705 puts $fd "<tr><td align=\"right\">[lindex $nbstat 0]</td><td bgcolor=\"[_html_color $status]\">$status</td><td>$comment</td></tr>"
40093367 1706 }
1707 puts $fd "</table>"
1708
1709 # time stamp and elapsed time info
1710 if { $total_time != "" } {
6d368502 1711 puts $fd "<p>Generated on [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] on [info hostname]\n<p>"
8a262fa1 1712 puts $fd [join [split $total_time "\n"] "<p>"]
40093367 1713 } else {
6d368502 1714 puts $fd "<p>NOTE: This is intermediate summary; the tests are still running! This page will refresh automatically until tests are finished."
40093367 1715 }
1716
1717 # print regressions and improvements
4514dc74 1718 foreach featured [list $regressions $improvements $skipped] {
6d368502 1719 if { [llength $featured] <= 1 } { continue }
1720 set status [string trim [lindex $featured 0] { :}]
1721 puts $fd "<h2>$status</h2>"
1722 puts $fd "<table>"
1723 set groupgrid ""
1724 foreach test [lrange $featured 1 end] {
93808a4a 1725 if { ! [regexp {^(.*)\s+([\w\-.]+)$} $test res gg name] } {
6d368502 1726 set gg UNKNOWN
1727 set name "Error building short list; check details"
1728 }
1729 if { $gg != $groupgrid } {
1730 if { $groupgrid != "" } { puts $fd "</tr>" }
1731 set groupgrid $gg
1732 puts $fd "<tr><td>$gg</td>"
1733 }
1734 puts $fd "<td bgcolor=\"[_html_color $status]\"><a href=\"[regsub -all { } $gg /]/${name}.html\">$name</a></td>"
1735 }
1736 if { $groupgrid != "" } { puts $fd "</tr>" }
1737 puts $fd "</table>"
40093367 1738 }
1739
9753e6de 1740 # put detailed log with TOC
1741 puts $fd "<hr><h1>Details</h1>"
1742 puts $fd "<div style=\"float:right; padding: 10px; border-style: solid; border-color: blue; border-width: 2px;\">"
40093367 1743
1744 # process log line-by-line
1745 set group {}
1746 set letter {}
9753e6de 1747 set body {}
1748 foreach line [lsort -dictionary $log] {
6d368502 1749 # check that the line is case report in the form "CASE group grid name: result (explanation)"
1750 if { ! [regexp $_test_case_regexp $line res grp grd casename result message] } {
1751 continue
1752 }
1753
1754 # start new group
1755 if { $grp != $group } {
1756 if { $letter != "" } { lappend body "</tr></table>" }
1757 set letter {}
1758 set group $grp
1759 set grid {}
1760 puts $fd "<a href=\"#$group\">$group</a><br>"
1761 lappend body "<h2><a name=\"$group\">Group $group</a></h2>"
1762 }
1763
1764 # start new grid
1765 if { $grd != $grid } {
1766 if { $letter != "" } { lappend body "</tr></table>" }
1767 set letter {}
1768 set grid $grd
1769 puts $fd "&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"#$group-$grid\">$grid</a><br>"
1770 lappend body "<h2><a name=\"$group-$grid\">Grid $group $grid</a></h2>"
1771 }
1772
1773 # check if test case name is <letter><digit>;
1774 # if not, set alnum to period "." to recognize non-standard test name
1775 if { ! [regexp {\A([A-Za-z]{1,2})([0-9]{1,2})\Z} $casename res alnum number] &&
9753e6de 1776 ! [regexp {\A([A-Za-z0-9]+)_([0-9]+)\Z} $casename res alnum number] } {
6d368502 1777 set alnum $casename
1778 }
40093367 1779
6d368502 1780 # start new row when letter changes or for non-standard names
1781 if { $alnum != $letter || $alnum == "." } {
1782 if { $letter != "" } {
1783 lappend body "</tr><tr>"
1784 } else {
1785 lappend body "<table><tr>"
1786 }
1787 set letter $alnum
1788 }
40093367 1789
6d368502 1790 lappend body "<td bgcolor=\"[_html_color $result]\"><a href=\"$group/$grid/${casename}.html\">$casename</a></td>"
40093367 1791 }
9753e6de 1792 puts $fd "</div>\n[join $body "\n"]</tr></table>"
40093367 1793
1794 # add remaining lines of log as plain text
1795 puts $fd "<h2>Plain text messages</h2>\n<pre>"
9753e6de 1796 foreach line $log {
6d368502 1797 if { ! [regexp $_test_case_regexp $line] } {
1798 puts $fd "$line"
1799 }
40093367 1800 }
1801 puts $fd "</pre>"
1802
1803 # close file and exit
1804 puts $fd "</body>"
1805 close $fd
1806 return
1807}
1808
1809# Procedure to dump summary logs of tests
1810proc _log_summarize {logdir log {total_time {}}} {
1811
1812 # sort log records alphabetically to have the same behavior on Linux and Windows
1813 # (also needed if tests are run in parallel)
9753e6de 1814 set loglist [lsort -dictionary $log]
40093367 1815
1816 # classify test cases by status
1817 foreach line $loglist {
6d368502 1818 if { [regexp {^CASE ([^:]*): ([[:alnum:]]+).*$} $line res caseid status] } {
1819 lappend stat($status) $caseid
1820 }
40093367 1821 }
1822 set totals {}
1823 set improvements {Improvements:}
1824 set regressions {Failed:}
4514dc74 1825 set skipped {Skipped:}
40093367 1826 if { [info exists stat] } {
6d368502 1827 foreach status [lsort [array names stat]] {
1828 lappend totals [list [llength $stat($status)] $status]
1829
4514dc74 1830 # separately count improvements (status starting with IMP), skipped (status starting with SKIP) and regressions (all except IMP, OK, BAD, and SKIP)
6d368502 1831 if { [regexp -nocase {^IMP} $status] } {
1832 eval lappend improvements $stat($status)
4514dc74 1833 } elseif { [regexp -nocase {^SKIP} $status] } {
1834 eval lappend skipped $stat($status)
6d368502 1835 } elseif { $status != "OK" && ! [regexp -nocase {^BAD} $status] && ! [regexp -nocase {^SKIP} $status] } {
1836 eval lappend regressions $stat($status)
1837 }
1838 }
40093367 1839 }
1840
1841 # if time is specified, add totals
1842 if { $total_time != "" } {
6d368502 1843 if { [llength $improvements] > 1 } {
1844 _log_and_puts log [join $improvements "\n "]
1845 }
1846 if { [llength $regressions] > 1 } {
1847 _log_and_puts log [join $regressions "\n "]
1848 }
4514dc74 1849 if { [llength $skipped] > 1 } {
1850 _log_and_puts log [join $skipped "\n "]
1851 }
6d368502 1852 if { [llength $improvements] == 1 && [llength $regressions] == 1 } {
1853 _log_and_puts log "No regressions"
1854 }
1855 _log_and_puts log "Total cases: [join $totals {, }]"
1856 _log_and_puts log $total_time
40093367 1857 }
1858
1859 # save log to files
1860 if { $logdir != "" } {
4514dc74 1861 _log_html_summary $logdir $log $totals $regressions $improvements $skipped $total_time
6d368502 1862 _log_save $logdir/tests.log [join $log "\n"] "Tests summary"
40093367 1863 }
1864
1865 return
1866}
1867
1868# Internal procedure to generate XML log in JUnit style, for further
1869# consumption by Jenkins or similar systems.
1870#
1871# The output is intended to conform to XML schema supported by Jenkins found at
1872# https://svn.jenkins-ci.org/trunk/hudson/dtkit/dtkit-format/dtkit-junit-model/src/main/resources/com/thalesgroup/dtkit/junit/model/xsd/junit-4.xsd
1873#
1874# The mapping of the fields is inspired by annotated schema of Apache Ant JUnit XML format found at
1875# http://windyroad.org/dl/Open%20Source/JUnit.xsd
1876proc _log_xml_summary {logdir filename log include_cout} {
1877 global _test_case_regexp
1878
1879 catch {file mkdir [file dirname $filename]}
1880
1881 # try to open a file and start XML
1882 if [catch {set fd [open $filename w]} res] {
6d368502 1883 error "Error creating XML summary file $filename: $res"
40093367 1884 }
1885 puts $fd "<?xml version='1.0' encoding='utf-8'?>"
1886 puts $fd "<testsuites>"
1887
1888 # prototype for command to generate test suite tag
1889 set time_and_host "timestamp=\"[clock format [clock seconds] -format {%Y-%m-%dT%H:%M:%S}]\" hostname=\"[info hostname]\""
1890 set cmd_testsuite {puts $fd "<testsuite name=\"$group $grid\" tests=\"$nbtests\" failures=\"$nbfail\" errors=\"$nberr\" time=\"$time\" skipped=\"$nbskip\" $time_and_host>\n$testcases\n</testsuite>\n"}
1891
1892 # sort log and process it line-by-line
1893 set group {}
9753e6de 1894 foreach line [lsort -dictionary $log] {
6d368502 1895 # check that the line is case report in the form "CASE group grid name: result (explanation)"
1896 if { ! [regexp $_test_case_regexp $line res grp grd casename result message] } {
1897 continue
1898 }
1899 set message [string trim $message " \t\r\n()"]
1900
1901 # start new testsuite for each grid
1902 if { $grp != $group || $grd != $grid } {
1903
1904 # write previous test suite
1905 if [info exists testcases] { eval $cmd_testsuite }
1906
1907 set testcases {}
1908 set nbtests 0
1909 set nberr 0
1910 set nbfail 0
1911 set nbskip 0
1912 set time 0.
1913
1914 set group $grp
1915 set grid $grd
1916 }
1917
1918 incr nbtests
1919
1920 # parse test log and get its CPU time
1921 set testout {}
1922 set add_cpu {}
1923 if { [catch {set fdlog [open $logdir/$group/$grid/${casename}.log r]} ret] } {
1924 puts "Error: cannot open $logdir/$group/$grid/${casename}.log: $ret"
1925 } else {
1926 while { [gets $fdlog logline] >= 0 } {
1927 if { $include_cout } {
1928 append testout "$logline\n"
1929 }
1930 if [regexp -nocase {TOTAL CPU TIME:\s*([\d.]+)\s*sec} $logline res cpu] {
1931 set add_cpu " time=\"$cpu\""
1932 set time [expr $time + $cpu]
1933 }
1934 }
1935 close $fdlog
1936 }
1937 if { ! $include_cout } {
40093367 1938 set testout "$line\n"
6d368502 1939 }
1940
1941 # record test case with its output and status
1942 # Mapping is: SKIPPED, BAD, and OK to OK, all other to failure
1943 append testcases "\n <testcase name=\"$casename\"$add_cpu status=\"$result\">\n"
1944 append testcases "\n <system-out>\n$testout </system-out>"
1945 if { $result != "OK" } {
1946 if { [regexp -nocase {^SKIP} $result] } {
1947 incr nberr
1948 append testcases "\n <error name=\"$result\" message=\"$message\"/>"
1949 } elseif { [regexp -nocase {^BAD} $result] } {
1950 incr nbskip
1951 append testcases "\n <skipped>$message</skipped>"
1952 } else {
1953 incr nbfail
1954 append testcases "\n <failure name=\"$result\" message=\"$message\"/>"
1955 }
1956 }
1957 append testcases "\n </testcase>"
40093367 1958 }
1959
1960 # write last test suite
1961 if [info exists testcases] { eval $cmd_testsuite }
1962
1963 # the end
1964 puts $fd "</testsuites>"
1965 close $fd
1966 return
1967}
1968
40093367 1969# Auxiliary procedure to split path specification (usually defined by
1970# environment variable) into list of directories or files
1971proc _split_path {pathspec} {
1972 global tcl_platform
1973
1974 # first replace all \ (which might occur on Windows) by /
1975 regsub -all "\\\\" $pathspec "/" pathspec
1976
1977 # split path by platform-specific separator
1978 return [split $pathspec [_path_separator]]
1979}
1980
1981# Auxiliary procedure to define platform-specific separator for directories in
1982# path specification
1983proc _path_separator {} {
1984 global tcl_platform
1985
1986 # split path by platform-specific separator
1987 if { $tcl_platform(platform) == "windows" } {
6d368502 1988 return ";"
40093367 1989 } else {
6d368502 1990 return ":"
40093367 1991 }
1992}
1993
cc6a292d 1994# Procedure to make a diff and common of two lists
1995proc _list_diff {list1 list2 _in1 _in2 _common} {
1996 upvar $_in1 in1
1997 upvar $_in2 in2
1998 upvar $_common common
1999
2000 set in1 {}
2001 set in2 {}
2002 set common {}
2003 foreach item $list1 {
6d368502 2004 if { [lsearch -exact $list2 $item] >= 0 } {
2005 lappend common $item
cc6a292d 2006 } else {
6d368502 2007 lappend in1 $item
2008 }
cc6a292d 2009 }
2010 foreach item $list2 {
6d368502 2011 if { [lsearch -exact $common $item] < 0 } {
2012 lappend in2 $item
2013 }
cc6a292d 2014 }
2015 return
2016}
2017
2018# procedure to load a file to Tcl string
2019proc _read_file {filename} {
2020 set fd [open $filename r]
2021 set result [read -nonewline $fd]
2022 close $fd
2023 return $result
2024}
2025
22db40eb 2026# procedure to construct name for the mage diff file
2027proc _diff_img_name {dir1 dir2 casepath imgfile} {
2028 return [file join $dir1 $casepath "diff-[file tail $dir2]-$imgfile"]
2029}
2030
936f43da 2031# auxiliary procedure to produce string comparing two values
2032proc _diff_show_ratio {value1 value2} {
6ae07908 2033 if {[expr double ($value2)] == 0.} {
2034 return "$value1 / $value2"
2035 } else {
2036 return "$value1 / $value2 \[[format "%+5.2f%%" [expr 100 * ($value1 - $value2) / double($value2)]]\]"
2037 }
936f43da 2038}
2039
44fae8b1 2040# procedure to check cpu user time
2041proc _check_time {regexp_msg} {
2042 upvar log log
2043 upvar log1 log1
2044 upvar log2 log2
2045 upvar log_cpu log_cpu
2046 upvar cpu cpu
2047 upvar basename basename
2048 upvar casename casename
2049 set time1_list [dict create]
2050 set time2_list [dict create]
2051 set cpu_find UNDEFINED
2052
2053 foreach line1 [split $log1 "\n"] {
2054 if { [regexp "${regexp_msg}" $line1 dump chronometer_name cpu_find] } {
2055 dict set time1_list "${chronometer_name}" "${cpu_find}"
2056 }
2057 }
2058
2059 foreach line2 [split $log2 "\n"] {
2060 if { [regexp "${regexp_msg}" $line2 dump chronometer_name cpu_find] } {
2061 dict set time2_list "${chronometer_name}" "${cpu_find}"
2062 }
2063 }
2064
2065 if { [llength [dict keys $time1_list]] != [llength [dict keys $time2_list]] } {
2066 puts "Error: number of dchrono/chrono COUNTER are different in the same test cases"
2067 } else {
2068 foreach key [dict keys $time1_list] {
2069 set time1 [dict get $time1_list $key]
2070 set time2 [dict get $time2_list $key]
2071
2072 # compare CPU user time with 10% precision (but not less 0.5 sec)
2073 if { [expr abs ($time1 - $time2) > 0.5 + 0.05 * abs ($time1 + $time2)] } {
2074 if {$cpu != false} {
2075 _log_and_puts log_cpu "COUNTER $key: [split $basename /] $casename: [_diff_show_ratio $time1 $time2]"
2076 } else {
2077 _log_and_puts log "COUNTER $key: [split $basename /] $casename: [_diff_show_ratio $time1 $time2]"
2078 }
2079 }
2080 }
2081 }
2082}
2083
cc6a292d 2084# Procedure to compare results of two runs of test cases
28cab376 2085proc _test_diff {dir1 dir2 basename image cpu memory status verbose _logvar _logimage _logcpu _logmemory {_statvar ""}} {
cc6a292d 2086 upvar $_logvar log
28cab376 2087 upvar $_logimage log_image
2088 upvar $_logcpu log_cpu
2089 upvar $_logmemory log_memory
cc6a292d 2090
22db40eb 2091 # make sure to load diffimage command
2092 uplevel pload VISUALIZATION
2093
cc6a292d 2094 # prepare variable (array) for collecting statistics
2095 if { "$_statvar" != "" } {
2096 upvar $_statvar stat
2097 } else {
2098 set stat(cpu1) 0
2099 set stat(cpu2) 0
22db40eb 2100 set stat(mem1) 0
2101 set stat(mem2) 0
cc6a292d 2102 set log {}
28cab376 2103 set log_image {}
2104 set log_cpu {}
2105 set log_memory {}
cc6a292d 2106 }
2107
2108 # first check subdirectories
2109 set path1 [file join $dir1 $basename]
2110 set path2 [file join $dir2 $basename]
2111 set list1 [glob -directory $path1 -types d -tails -nocomplain *]
2112 set list2 [glob -directory $path2 -types d -tails -nocomplain *]
2113 if { [llength $list1] >0 || [llength $list2] > 0 } {
2114 _list_diff $list1 $list2 in1 in2 common
2115 if { "$verbose" > 1 } {
2116 if { [llength $in1] > 0 } { _log_and_puts log "Only in $path1: $in1" }
2117 if { [llength $in2] > 0 } { _log_and_puts log "Only in $path2: $in2" }
2118 }
2119 foreach subdir $common {
2120 if { "$verbose" > 2 } {
6d368502 2121 _log_and_puts log "Checking [file join $basename $subdir]"
2122 }
28cab376 2123 _test_diff $dir1 $dir2 [file join $basename $subdir] $image $cpu $memory $status $verbose log log_image log_cpu log_memory stat
cc6a292d 2124 }
2125 } else {
2126 # check log files (only if directory has no subdirs)
2127 set list1 [glob -directory $path1 -types f -tails -nocomplain *.log]
2128 set list2 [glob -directory $path2 -types f -tails -nocomplain *.log]
2129 _list_diff $list1 $list2 in1 in2 common
2130 if { "$verbose" > 1 } {
2131 if { [llength $in1] > 0 } { _log_and_puts log "Only in $path1: $in1" }
2132 if { [llength $in2] > 0 } { _log_and_puts log "Only in $path2: $in2" }
2133 }
936f43da 2134 set gcpu1 0
2135 set gcpu2 0
2136 set gmem1 0
2137 set gmem2 0
cc6a292d 2138 foreach logfile $common {
2139 # load two logs
6d368502 2140 set log1 [_read_file [file join $dir1 $basename $logfile]]
2141 set log2 [_read_file [file join $dir2 $basename $logfile]]
22db40eb 2142 set casename [file rootname $logfile]
28cab376 2143
cc6a292d 2144 # check execution statuses
28cab376 2145 if {$image == false && $cpu == false && $memory == false} {
2146 set status1 UNDEFINED
2147 set status2 UNDEFINED
2148 if { ! [regexp {CASE [^:]*:\s*([\w]+)} $log1 res1 status1] ||
2149 ! [regexp {CASE [^:]*:\s*([\w]+)} $log2 res2 status2] ||
2150 "$status1" != "$status2" } {
2151 _log_and_puts log "STATUS [split $basename /] $casename: $status1 / $status2"
2152 # if test statuses are different, further comparison makes
2153 # no sense unless explicitly requested
2154 if { "$status" != "all" } {
2155 continue
2156 }
2157 }
2158 if { "$status" == "ok" && "$status1" != "OK" } {
cc6a292d 2159 continue
6d368502 2160 }
2161 }
44fae8b1 2162
2163 if { ! $image } {
2164 # check CPU user time in test cases
2165 set checkCPURegexp "COUNTER (.+): (\[-0-9.+eE\]+)"
2166 if { [regexp "${checkCPURegexp}" $log1] &&
2167 [regexp "${checkCPURegexp}" $log2] } {
2168 _check_time "${checkCPURegexp}"
2169 }
2170 }
28cab376 2171
cc6a292d 2172 # check CPU times
28cab376 2173 if {$cpu != false || ($image == false && $cpu == false && $memory == false)} {
2174 set cpu1 UNDEFINED
2175 set cpu2 UNDEFINED
2176 if { [regexp {TOTAL CPU TIME:\s*([\d.]+)} $log1 res1 cpu1] &&
2177 [regexp {TOTAL CPU TIME:\s*([\d.]+)} $log2 res1 cpu2] } {
2178 set stat(cpu1) [expr $stat(cpu1) + $cpu1]
2179 set stat(cpu2) [expr $stat(cpu2) + $cpu2]
2180 set gcpu1 [expr $gcpu1 + $cpu1]
2181 set gcpu2 [expr $gcpu2 + $cpu2]
2182
2183 # compare CPU times with 10% precision (but not less 0.5 sec)
2184 if { [expr abs ($cpu1 - $cpu2) > 0.5 + 0.05 * abs ($cpu1 + $cpu2)] } {
2185 if {$cpu != false} {
2186 _log_and_puts log_cpu "CPU [split $basename /] $casename: [_diff_show_ratio $cpu1 $cpu2]"
2187 } else {
2188 _log_and_puts log "CPU [split $basename /] $casename: [_diff_show_ratio $cpu1 $cpu2]"
2189 }
2190 }
cc6a292d 2191 }
6d368502 2192 }
22db40eb 2193
2194 # check memory delta
28cab376 2195 if {$memory != false || ($image == false && $cpu == false && $memory == false)} {
2196 set mem1 UNDEFINED
2197 set mem2 UNDEFINED
2198 if { [regexp {MEMORY DELTA:\s*([\d.]+)} $log1 res1 mem1] &&
2199 [regexp {MEMORY DELTA:\s*([\d.]+)} $log2 res1 mem2] } {
2200 set stat(mem1) [expr $stat(mem1) + $mem1]
2201 set stat(mem2) [expr $stat(mem2) + $mem2]
2202 set gmem1 [expr $gmem1 + $mem1]
2203 set gmem2 [expr $gmem2 + $mem2]
2204
2205 # compare memory usage with 10% precision (but not less 16 KiB)
2206 if { [expr abs ($mem1 - $mem2) > 16 + 0.05 * abs ($mem1 + $mem2)] } {
2207 if {$memory != false} {
2208 _log_and_puts log_memory "MEMORY [split $basename /] $casename: [_diff_show_ratio $mem1 $mem2]"
2209 } else {
2210 _log_and_puts log "MEMORY [split $basename /] $casename: [_diff_show_ratio $mem1 $mem2]"
2211 }
2212 }
22db40eb 2213 }
6d368502 2214 }
22db40eb 2215
2216 # check images
28cab376 2217 if {$image != false || ($image == false && $cpu == false && $memory == false)} {
2218 set imglist1 [glob -directory $path1 -types f -tails -nocomplain ${casename}.{png,gif} ${casename}-*.{png,gif} ${casename}_*.{png,gif}]
2219 set imglist2 [glob -directory $path2 -types f -tails -nocomplain ${casename}.{png,gif} ${casename}-*.{png,gif} ${casename}_*.{png,gif}]
2220 _list_diff $imglist1 $imglist2 imgin1 imgin2 imgcommon
2221 if { "$verbose" > 1 } {
2222 if { [llength $imgin1] > 0 } {
2223 if {$image != false} {
2224 _log_and_puts log_image "Only in $path1: $imgin1"
2225 } else {
2226 _log_and_puts log "Only in $path1: $imgin1"
2227 }
2228 }
2229 if { [llength $imgin2] > 0 } {
2230 if {$image != false} {
2231 _log_and_puts log_image "Only in $path2: $imgin2"
2232 } else {
2233 _log_and_puts log "Only in $path2: $imgin2"
2234 }
2235 }
2236 }
2237 foreach imgfile $imgcommon {
2238 # if { $verbose > 1 } { _log_and_puts log "Checking [split basename /] $casename: $imgfile" }
2239 set diffile [_diff_img_name $dir1 $dir2 $basename $imgfile]
2240 if { [catch {diffimage [file join $dir1 $basename $imgfile] \
2241 [file join $dir2 $basename $imgfile] \
2242 0 0 0 $diffile} diff] } {
2243 if {$image != false} {
2244 _log_and_puts log_image "IMAGE [split $basename /] $casename: $imgfile cannot be compared"
2245 } else {
2246 _log_and_puts log "IMAGE [split $basename /] $casename: $imgfile cannot be compared"
2247 }
2248 file delete -force $diffile ;# clean possible previous result of diffimage
2249 } elseif { $diff != 0 } {
2250 if {$image != false} {
2251 _log_and_puts log_image "IMAGE [split $basename /] $casename: $imgfile differs"
2252 } else {
2253 _log_and_puts log "IMAGE [split $basename /] $casename: $imgfile differs"
2254 }
2255 } else {
2256 file delete -force $diffile ;# clean useless artifact of diffimage
2257 }
22db40eb 2258 }
2259 }
6d368502 2260 }
936f43da 2261
2262 # report CPU and memory difference in group if it is greater than 10%
28cab376 2263 if {$cpu != false || ($image == false && $cpu == false && $memory == false)} {
2264 if { [expr abs ($gcpu1 - $gcpu2) > 0.5 + 0.005 * abs ($gcpu1 + $gcpu2)] } {
2265 if {$cpu != false} {
2266 _log_and_puts log_cpu "CPU [split $basename /]: [_diff_show_ratio $gcpu1 $gcpu2]"
2267 } else {
2268 _log_and_puts log "CPU [split $basename /]: [_diff_show_ratio $gcpu1 $gcpu2]"
2269 }
2270 }
936f43da 2271 }
28cab376 2272 if {$memory != false || ($image == false && $cpu == false && $memory == false)} {
2273 if { [expr abs ($gmem1 - $gmem2) > 16 + 0.005 * abs ($gmem1 + $gmem2)] } {
2274 if {$memory != false} {
2275 _log_and_puts log_memory "MEMORY [split $basename /]: [_diff_show_ratio $gmem1 $gmem2]"
2276 } else {
2277 _log_and_puts log "MEMORY [split $basename /]: [_diff_show_ratio $gmem1 $gmem2]"
2278 }
2279 }
936f43da 2280 }
cc6a292d 2281 }
2282
2283 if { "$_statvar" == "" } {
28cab376 2284 if {$memory != false || ($image == false && $cpu == false && $memory == false)} {
2285 if {$memory != false} {
2286 _log_and_puts log_memory "Total MEMORY difference: [_diff_show_ratio $stat(mem1) $stat(mem2)]"
2287 } else {
2288 _log_and_puts log "Total MEMORY difference: [_diff_show_ratio $stat(mem1) $stat(mem2)]"
2289 }
2290 }
2291 if {$cpu != false || ($image == false && $cpu == false && $memory == false)} {
2292 if {$cpu != false} {
2293 _log_and_puts log_cpu "Total CPU difference: [_diff_show_ratio $stat(cpu1) $stat(cpu2)]"
2294 } else {
2295 _log_and_puts log "Total CPU difference: [_diff_show_ratio $stat(cpu1) $stat(cpu2)]"
2296 }
2297 }
cc6a292d 2298 }
2299}
b725d7c5 2300
22db40eb 2301# Auxiliary procedure to save log of results comparison to file
d5f061b6 2302proc _log_html_diff {file log dir1 dir2 highlight_percent} {
22db40eb 2303 # create missing directories as needed
2304 catch {file mkdir [file dirname $file]}
2305
2306 # try to open a file
2307 if [catch {set fd [open $file w]} res] {
6d368502 2308 error "Error saving log file $file: $res"
22db40eb 2309 }
2310
2311 # print header
2312 puts $fd "<html><head><title>Diff $dir1 vs. $dir2</title></head><body>"
433673e0 2313 puts $fd "<h1>Comparison of test results:</h1>"
2314 puts $fd "<h2>Version A - $dir1</h2>"
2315 puts $fd "<h2>Version B - $dir2</h2>"
22db40eb 2316
c231f393 2317 # add script for switching between images on click
2318 puts $fd ""
2319 puts $fd "<script type=\"text/javascript\">"
2320 puts $fd " function diffimage_toggle(img,url1,url2)"
2321 puts $fd " {"
66d61d8a 2322 puts $fd " if (img.show2nd) { img.src = url1; img.show2nd = false; }"
2323 puts $fd " else { img.src = url2; img.show2nd = true; }"
c231f393 2324 puts $fd " }"
66d61d8a 2325 puts $fd " function diffimage_reset(img,url) { img.src = url; img.show2nd = true; }"
c231f393 2326 puts $fd "</script>"
2327 puts $fd ""
2328
2329 # print log body
22db40eb 2330 puts $fd "<pre>"
2331 set logpath [file split [file normalize $file]]
9753e6de 2332 foreach line $log {
d5f061b6 2333 # put a line; highlight considerable (> ${highlight_percent}%) deviations of CPU and memory
936f43da 2334 if { [regexp "\[\\\[](\[0-9.e+-]+)%\[\]]" $line res value] &&
d5f061b6 2335 [expr abs($value)] > ${highlight_percent} } {
77887d30 2336 puts $fd "<table><tr><td bgcolor=\"[expr $value > 0 ? \"ff8080\" : \"lightgreen\"]\">$line</td></tr></table>"
936f43da 2337 } else {
2338 puts $fd $line
2339 }
22db40eb 2340
936f43da 2341 # add images
22db40eb 2342 if { [regexp {IMAGE[ \t]+([^:]+):[ \t]+([A-Za-z0-9_.-]+)} $line res case img] } {
2343 if { [catch {eval file join "" [lrange $case 0 end-1]} gridpath] } {
6d368502 2344 # note: special handler for the case if test grid directoried are compared directly
2345 set gridpath ""
22db40eb 2346 }
17b45a29 2347 set aCaseName [lindex $case end]
c231f393 2348 set img1url [_make_url $file [file join $dir1 $gridpath $img]]
2349 set img2url [_make_url $file [file join $dir2 $gridpath $img]]
2350 set img1 "<a href=\"[_make_url $file [file join $dir1 $gridpath $aCaseName.html]]\"><img src=\"$img1url\"></a>"
2351 set img2 "<a href=\"[_make_url $file [file join $dir2 $gridpath $aCaseName.html]]\"><img src=\"$img2url\"></a>"
22db40eb 2352
2353 set difffile [_diff_img_name $dir1 $dir2 $gridpath $img]
c231f393 2354 set imgdurl [_make_url $file $difffile]
22db40eb 2355 if { [file exists $difffile] } {
c231f393 2356 set imgd "<img src=\"$imgdurl\" onmouseout=diffimage_reset(this,\"$imgdurl\") onclick=diffimage_toggle(this,\"$img1url\",\"$img2url\")>"
22db40eb 2357 } else {
2358 set imgd "N/A"
2359 }
2360
c231f393 2361 puts $fd "<table><tr><th><abbr title=\"$dir1\">Version A</abbr></th><th><abbr title=\"$dir2\">Version B</abbr></th><th>Diff (click to toggle)</th></tr>"
22db40eb 2362 puts $fd "<tr><td>$img1</td><td>$img2</td><td>$imgd</td></tr></table>"
2363 }
2364 }
2365 puts $fd "</pre></body></html>"
2366
2367 close $fd
2368 return
2369}
2370
b725d7c5 2371# get number of CPUs on the system
2372proc _get_nb_cpus {} {
2373 global tcl_platform env
2374
2375 if { "$tcl_platform(platform)" == "windows" } {
2376 # on Windows, take the value of the environment variable
2377 if { [info exists env(NUMBER_OF_PROCESSORS)] &&
2378 ! [catch {expr $env(NUMBER_OF_PROCESSORS) > 0} res] && $res >= 0 } {
2379 return $env(NUMBER_OF_PROCESSORS)
2380 }
2381 } elseif { "$tcl_platform(os)" == "Linux" } {
2382 # on Linux, take number of logical processors listed in /proc/cpuinfo
2383 if { [catch {open "/proc/cpuinfo" r} fd] } {
2384 return 0 ;# should never happen, but...
2385 }
2386 set nb 0
6d368502 2387 while { [gets $fd line] >= 0 } {
2388 if { [regexp {^processor[ \t]*:} $line] } {
2389 incr nb
2390 }
2391 }
b725d7c5 2392 close $fd
2393 return $nb
2394 } elseif { "$tcl_platform(os)" == "Darwin" } {
2395 # on MacOS X, call sysctl command
2396 if { ! [catch {exec sysctl hw.ncpu} ret] &&
2397 [regexp {^hw[.]ncpu[ \t]*:[ \t]*([0-9]+)} $ret res nb] } {
2398 return $nb
2399 }
2400 }
2401
2402 # if cannot get good value, return 0 as default
2403 return 0
2404}
351bbcba 2405
2406# check two files for difference
2407proc _diff_files {file1 file2} {
2408 set fd1 [open $file1 "r"]
2409 set fd2 [open $file2 "r"]
2410
2411 set differ f
2412 while {! $differ} {
2413 set nb1 [gets $fd1 line1]
2414 set nb2 [gets $fd2 line2]
2415 if { $nb1 != $nb2 } { set differ t; break }
2416 if { $nb1 < 0 } { break }
2417 if { [string compare $line1 $line2] } {
2418 set differ t
2419 }
2420 }
2421
2422 close $fd1
2423 close $fd2
2424
2425 return $differ
2426}
2427
2428# Check if file is in DOS encoding.
2429# This check is done by presence of \r\n combination at the end of the first
2430# line (i.e. prior to any other \n symbol).
2431# Note that presence of non-ascii symbols typically used for recognition
2432# of binary files is not suitable since some IGES and STEP files contain
2433# non-ascii symbols.
2434# Special check is added for PNG files which contain \r\n in the beginning.
2435proc _check_dos_encoding {file} {
2436 set fd [open $file rb]
2437 set isdos f
2438 if { [gets $fd line] && [regexp {.*\r$} $line] &&
2439 ! [regexp {^.PNG} $line] } {
2440 set isdos t
2441 }
2442 close $fd
2443 return $isdos
2444}
2445
2446# procedure to recognize format of a data file by its first symbols (for OCCT
2447# BREP and geometry DRAW formats, IGES, and STEP) and extension (all others)
2448proc _check_file_format {file} {
2449 set fd [open $file rb]
2450 set line [read $fd 1024]
2451 close $fd
2452
2453 set warn f
2454 set ext [file extension $file]
2455 set format unknown
2456 if { [regexp {^DBRep_DrawableShape} $line] } {
2457 set format BREP
2458 if { "$ext" != ".brep" && "$ext" != ".rle" &&
2459 "$ext" != ".draw" && "$ext" != "" } {
2460 set warn t
2461 }
2462 } elseif { [regexp {^DrawTrSurf_} $line] } {
2463 set format DRAW
2464 if { "$ext" != ".rle" &&
2465 "$ext" != ".draw" && "$ext" != "" } {
2466 set warn t
2467 }
2468 } elseif { [regexp {^[ \t]*ISO-10303-21} $line] } {
2469 set format STEP
2470 if { "$ext" != ".step" && "$ext" != ".stp" } {
2471 set warn t
2472 }
2473 } elseif { [regexp {^.\{72\}S[0 ]\{6\}1} $line] } {
2474 set format IGES
2475 if { "$ext" != ".iges" && "$ext" != ".igs" } {
2476 set warn t
2477 }
2478 } elseif { "$ext" == ".igs" } {
2479 set format IGES
2480 } elseif { "$ext" == ".stp" } {
2481 set format STEP
2482 } else {
2483 set format [string toupper [string range $ext 1 end]]
2484 }
2485
2486 if { $warn } {
2487 puts "$file: Warning: extension ($ext) does not match format ($format)"
2488 }
2489
2490 return $format
2491}
2492
2493# procedure to load file knowing its format
2494proc load_data_file {file format shape} {
2495 switch $format {
6d368502 2496 BREP { uplevel restore $file $shape }
2497 DRAW { uplevel restore $file $shape }
2498 IGES { pload XSDRAW; uplevel igesbrep $file $shape * }
2499 STEP { pload XSDRAW; uplevel stepread $file __a *; uplevel renamevar __a_1 $shape }
4eb6e8f4 2500 STL { pload XSDRAW; uplevel readstl $shape $file triangulation }
6d368502 2501 default { error "Cannot read $format file $file" }
351bbcba 2502 }
2503}
60874ff8 2504
2505# procedure to get name of temporary directory,
2506# ensuring it is existing and writeable
2507proc _get_temp_dir {} {
723754e2 2508 global env tcl_platform
60874ff8 2509
2510 # check typical environment variables
2511 foreach var {TempDir Temp Tmp} {
2512 # check different case
2513 foreach name [list [string toupper $var] $var [string tolower $var]] {
2514 if { [info exists env($name)] && [file isdirectory $env($name)] &&
2515 [file writable $env($name)] } {
2516 return [regsub -all {\\} $env($name) /]
2517 }
2518 }
2519 }
2520
2521 # check platform-specific locations
2522 set fallback tmp
2523 if { "$tcl_platform(platform)" == "windows" } {
2524 set paths "c:/TEMP c:/TMP /TEMP /TMP"
2525 if { [info exists env(HOMEDRIVE)] && [info exists env(HOMEPATH)] } {
338434c7 2526 set fallback [regsub -all {\\} "$env(HOMEDRIVE)$env(HOMEPATH)/tmp" /]
60874ff8 2527 }
2528 } else {
2529 set paths "/tmp /var/tmp /usr/tmp"
2530 if { [info exists env(HOME)] } {
2531 set fallback "$env(HOME)/tmp"
2532 }
2533 }
2534 foreach dir $paths {
723754e2 2535 if { [file isdirectory $dir] && [file writable $dir] } {
60874ff8 2536 return $dir
2537 }
2538 }
2539
2540 # fallback case: use subdir /tmp of home or current dir
2541 file mkdir $fallback
2542 return $fallback
2543}
9753e6de 2544
2545# extract of code from testgrid command used to process jobs running in
2546# parallel until number of jobs in the queue becomes equal or less than
2547# specified value
2548proc _testgrid_process_jobs {worker {nb_ok 0}} {
2549 # bind local vars to variables of the caller procedure
2550 upvar log log
2551 upvar logdir logdir
2552 upvar job_def job_def
2553 upvar nbpooled nbpooled
2554 upvar userbreak userbreak
2555 upvar refresh refresh
2556 upvar refresh_timer refresh_timer
2557
2558 catch {tpool::resume $worker}
2559 while { ! $userbreak && $nbpooled > $nb_ok } {
2560 foreach job [tpool::wait $worker [array names job_def]] {
2561 eval _log_test_case \[tpool::get $worker $job\] $job_def($job) log
2562 unset job_def($job)
2563 incr nbpooled -1
2564 }
2565
2566 # check for user break
2567 if { "[info commands dbreak]" == "dbreak" && [catch dbreak] } {
2568 set userbreak 1
2569 }
2570
2571 # update summary log with requested period
2572 if { $logdir != "" && $refresh > 0 && [clock seconds] > $refresh_timer + $refresh } {
6d368502 2573 _log_summarize $logdir $log
2574 set refresh_timer [clock seconds]
9753e6de 2575 }
2576 }
2577 catch {tpool::suspend $worker}
2578}
ccadc126 2579
2580help checkcolor {
2581 Check pixel color.
2582 Use: checkcolor x y red green blue
2583 x y - pixel coordinates
2584 red green blue - expected pixel color (values from 0 to 1)
2585 Function check color with tolerance (5x5 area)
2586}
2587# Procedure to check color using command vreadpixel with tolerance
2588proc checkcolor { coord_x coord_y rd_get gr_get bl_get } {
2589 puts "Coordinate x = $coord_x"
2590 puts "Coordinate y = $coord_y"
2591 puts "RED color of RGB is $rd_get"
2592 puts "GREEN color of RGB is $gr_get"
2593 puts "BLUE color of RGB is $bl_get"
2594
2595 if { $coord_x <= 1 || $coord_y <= 1 } {
6d368502 2596 puts "Error : minimal coordinate is x = 2, y = 2. But we have x = $coord_x y = $coord_y"
2597 return -1
ccadc126 2598 }
2599
2600 set color ""
2601 catch { [set color "[vreadpixel ${coord_x} ${coord_y} rgb]"] }
2602 if {"$color" == ""} {
6d368502 2603 puts "Error : Pixel coordinates (${position_x}; ${position_y}) are out of view"
ccadc126 2604 }
2605 set rd [lindex $color 0]
2606 set gr [lindex $color 1]
2607 set bl [lindex $color 2]
2608 set rd_int [expr int($rd * 1.e+05)]
2609 set gr_int [expr int($gr * 1.e+05)]
2610 set bl_int [expr int($bl * 1.e+05)]
2611 set rd_ch [expr int($rd_get * 1.e+05)]
2612 set gr_ch [expr int($gr_get * 1.e+05)]
2613 set bl_ch [expr int($bl_get * 1.e+05)]
2614
2615 if { $rd_ch != 0 } {
6d368502 2616 set tol_rd [expr abs($rd_ch - $rd_int)/$rd_ch]
ccadc126 2617 } else {
6d368502 2618 set tol_rd $rd_int
ccadc126 2619 }
2620 if { $gr_ch != 0 } {
6d368502 2621 set tol_gr [expr abs($gr_ch - $gr_int)/$gr_ch]
ccadc126 2622 } else {
6d368502 2623 set tol_gr $gr_int
ccadc126 2624 }
2625 if { $bl_ch != 0 } {
6d368502 2626 set tol_bl [expr abs($bl_ch - $bl_int)/$bl_ch]
ccadc126 2627 } else {
6d368502 2628 set tol_bl $bl_int
ccadc126 2629 }
2630
2631 set status 0
2632 if { $tol_rd > 0.2 } {
6d368502 2633 puts "Warning : RED light of additive color model RGB is invalid"
2634 set status 1
ccadc126 2635 }
2636 if { $tol_gr > 0.2 } {
6d368502 2637 puts "Warning : GREEN light of additive color model RGB is invalid"
2638 set status 1
ccadc126 2639 }
2640 if { $tol_bl > 0.2 } {
6d368502 2641 puts "Warning : BLUE light of additive color model RGB is invalid"
2642 set status 1
ccadc126 2643 }
2644
2645 if { $status != 0 } {
6d368502 2646 puts "Warning : Colors of default coordinate are not equal"
ccadc126 2647 }
2648
2649 global stat
2650 if { $tol_rd > 0.2 || $tol_gr > 0.2 || $tol_bl > 0.2 } {
6d368502 2651 set info [_checkpoint $coord_x $coord_y $rd_ch $gr_ch $bl_ch]
2652 set stat [lindex $info end]
2653 if { ${stat} != 1 } {
2654 puts "Error : Colors are not equal in default coordinate and in the near coordinates too"
2655 return $stat
2656 } else {
2657 puts "Point with valid color was found"
2658 return $stat
2659 }
ccadc126 2660 } else {
6d368502 2661 set stat 1
ccadc126 2662 }
2663}
2664
2665# Procedure to check color in the point near default coordinate
2666proc _checkpoint {coord_x coord_y rd_ch gr_ch bl_ch} {
2667 set x_start [expr ${coord_x} - 2]
2668 set y_start [expr ${coord_y} - 2]
2669 set mistake 0
2670 set i 0
2671 while { $mistake != 1 && $i <= 5 } {
6d368502 2672 set j 0
2673 while { $mistake != 1 && $j <= 5 } {
2674 set position_x [expr ${x_start} + $j]
2675 set position_y [expr ${y_start} + $i]
2676 puts $position_x
2677 puts $position_y
2678
2679 set color ""
2680 catch { [set color "[vreadpixel ${position_x} ${position_y} rgb]"] }
2681 if {"$color" == ""} {
2682 puts "Warning : Pixel coordinates (${position_x}; ${position_y}) are out of view"
2683 incr j
2684 continue
2685 }
2686 set rd [lindex $color 0]
2687 set gr [lindex $color 1]
2688 set bl [lindex $color 2]
2689 set rd_int [expr int($rd * 1.e+05)]
2690 set gr_int [expr int($gr * 1.e+05)]
2691 set bl_int [expr int($bl * 1.e+05)]
2692
2693 if { $rd_ch != 0 } {
2694 set tol_rd [expr abs($rd_ch - $rd_int)/$rd_ch]
2695 } else {
2696 set tol_rd $rd_int
2697 }
2698 if { $gr_ch != 0 } {
2699 set tol_gr [expr abs($gr_ch - $gr_int)/$gr_ch]
2700 } else {
2701 set tol_gr $gr_int
2702 }
2703 if { $bl_ch != 0 } {
2704 set tol_bl [expr abs($bl_ch - $bl_int)/$bl_ch]
2705 } else {
2706 set tol_bl $bl_int
2707 }
2708
2709 if { $tol_rd > 0.2 || $tol_gr > 0.2 || $tol_bl > 0.2 } {
2710 puts "Warning : Point with true color was not found near default coordinates"
2711 set mistake 0
2712 } else {
2713 set mistake 1
2714 }
ccadc126 2715 incr j
6d368502 2716 }
2717 incr i
ccadc126 2718 }
2719 return $mistake
2720}
a2f76b15 2721
2722# Procedure to check if sequence of values in listval follows linear trend
2723# adding the same delta on each step.
2724#
2725# The function does statistical estimation of the mean variation of the
2726# values of the sequence, and dispersion, and returns true only if both
2727# dispersion and deviation of the mean from expected delta are within
2728# specified tolerance.
2729#
2730# If mean variation differs from expected delta on more than two dispersions,
2731# the check fails and procedure raises error with specified message.
2732#
2733# Otherwise the procedure returns false meaning that more iterations are needed.
2734# Note that false is returned in any case if length of listval is less than 3.
2735#
2736# See example of use to check memory leaks in bugs/caf/bug23489
2737#
2738proc checktrend {listval delta tolerance message} {
2739 set nbval [llength $listval]
2740 if { $nbval < 3} {
2741 return 0
2742 }
2743
2744 # calculate mean value
2745 set mean 0.
2746 set prev [lindex $listval 0]
2747 foreach val [lrange $listval 1 end] {
2748 set mean [expr $mean + ($val - $prev)]
2749 set prev $val
2750 }
2751 set mean [expr $mean / ($nbval - 1)]
2752
2753 # calculate dispersion
2754 set sigma 0.
2755 set prev [lindex $listval 0]
2756 foreach val [lrange $listval 1 end] {
2757 set d [expr ($val - $prev) - $mean]
2758 set sigma [expr $sigma + $d * $d]
2759 set prev $val
2760 }
2761 set sigma [expr sqrt ($sigma / ($nbval - 2))]
2762
2763 puts "Checking trend: nb = $nbval, mean delta = $mean, sigma = $sigma"
2764
2765 # check if deviation is definitely too big
2766 if { abs ($mean - $delta) > $tolerance + 2. * $sigma } {
2767 puts "Checking trend failed: mean delta per step = $mean, sigma = $sigma, expected delta = $delta"
2768 error "$message"
2769 }
2770
2771 # check if deviation is clearly within a range
2772 return [expr abs ($mean - $delta) <= $sigma && $sigma <= $tolerance]
2773}