In DRAW, use command *dperf* to print all performance statistics.
Note that performance counters are not thread-safe.
+
+@section occt_debug_sanitizers Use of compiler sanitizers
+
+GCC and Clang compilers provide options for instrumenting the code with the tools intended for detection of run-time errors, called sanitizers.
+This section provides some hints for using sanitizers for detecting possible errors in OCCT code.
+
+@subsection occt_debug_sanitizers_linux Linux
+
+Example of configuration steps for Ubuntu:
+
+1. In CMake configuration:
+
+ - Use up-to-date version of the GCC or CLang compiler; make sure that if CMAKE_CXX_COMPILER is set to C++ compiler (e.g. "clang++-6.0") and CMAKE_C_COMPILER is set to C compiler (e.g. "clang-6.0")
+ - Ensure that CMAKE_LINKER is set to the C++ linker bundled with compiler (e.g. clang++-6.0); this is important to avoid linking problems
+ - For building with Address sanitizer, set CMAKE_CXX_FLAGS and CMAKE_C_FLAGS to "-fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls"
+ - For building with Undefined Behavior sanitizer, set CMAKE_CXX_FLAGS and CMAKE_C_FLAGS to "-fsanitize=undefined -fno-omit-frame-pointer -fno-optimize-sibling-calls"
+ - Set CMAKE_BUILD_TYPE to RelWithDebInfo to get more informative stack traces on errors
+
+2. Build as usual (make)
+
+ Be prepared that it works much slower than normal build and consumes more disk space.
+
+3. Before running executable, make sure that "llvm-symbolizer" is in PATH; this is necessary to get human-readable stack traces. The tool must have exactly that name.
+
+ If it is installed in common folder (/usr/bin or similar) with different name, one option is to create a symlink, for instance:
+> sudo ln -s /usr/bin/llvm-symbolizer-6.0 /usr/bin/llvm-symbolizer
+
+ Alternatively, add directory where actual llvm-symbolizer is located (such as /usr/lib/llvm-6.0/bin) to the PATH variable.
+
+4. Set environment variable to disable memory leaks detection (they seem to be reported for every global variable at exit, not much useful):
+> export ASAN_OPTIONS=detect_leaks=0
+
+5. Set environment variable CSF_CPULIMIT_FACTOR to reasonably large number to increase the time limits for program execution (used by OCCT tests) to compensate the performance penalty introduced by sanitizers:
+> export CSF_CPULIMIT_FACTOR=20
+
+6. When using UBSan, set environment variable UBSAN_OPTIONS to get stack traces:
+> export UBSAN_OPTIONS=print_stacktrace=1
+
+7. Run DRAW and perform tests as usual, keeping in mind that running with sanitizer is much heavier than normal build:
+> ./draw.sh relwithdeb <br>
+> Draw[]> testgrid -parallel 0
+
+Note that when running tests under sanitizers, behavior may be different.
+Known problems (as of CLang 6.0) are:
+- Software signals (access violation etc.) are not handled
+- Heap memory usage always reports zero
+
+@subsection occt_debug_sanitizers_windows Windows
+
+Though CLang toolset is available in Visual Studio 2015 and newer, sanitizer do not seem to be available out of the box (last tested with VS 2019 16.2.3).
}
#endif
-#ifdef _WIN32
-static Standard_Integer cpulimit(Draw_Interpretor&, Standard_Integer n, const char** a)
+// Returns time in seconds defined by the argument string,
+// multiplied by factor defined in environment variable
+// CSF_CPULIMIT_FACTOR (if it exists, 1 otherwise)
+static clock_t GetCpuLimit (const Standard_CString theParam)
{
-#else
+ clock_t aValue = Draw::Atoi (theParam);
+
+ OSD_Environment aEnv("CSF_CPULIMIT_FACTOR");
+ TCollection_AsciiString aEnvStr = aEnv.Value();
+ if (!aEnvStr.IsEmpty())
+ {
+ aValue *= Draw::Atoi (aEnvStr.ToCString());
+ }
+ return aValue;
+}
+
static Standard_Integer cpulimit(Draw_Interpretor& di, Standard_Integer n, const char** a)
{
-#endif
static int aFirst = 1;
#ifdef _WIN32
// Windows specific code
if (n <= 1){
CPU_LIMIT = RLIM_INFINITY;
} else {
- CPU_LIMIT = Draw::Atoi (a[1]);
+ CPU_LIMIT = GetCpuLimit (a[1]);
Standard_Real anUserSeconds, aSystemSeconds;
OSD_Chronometer::GetProcessCPU (anUserSeconds, aSystemSeconds);
CPU_CURRENT = clock_t(anUserSeconds + aSystemSeconds);
if (n <= 1)
rlp.rlim_cur = RLIM_INFINITY;
else
- rlp.rlim_cur = Draw::Atoi(a[1]);
+ rlp.rlim_cur = GetCpuLimit (a[1]);
CPU_LIMIT = rlp.rlim_cur;
int status;
pthread_create(&cpulimitThread, NULL, CpuFunc, NULL);
}
#endif
+ di << "CPU and elapsed time limit set to " << (double)CPU_LIMIT << " seconds";
return 0;
}
-
//=======================================================================
//function : mallochook
//purpose :
-xml filename: write XML report for Jenkins (in JUnit-like format)
-beep: play sound signal at the end of the tests
-regress dirname: re-run only a set of tests that have been detected as regressions on some previous run.
+ -skipped dirname: re-run only a set of tests that have been skipped on some previous run.
Here "dirname" is path to directory containing results of previous run.
+ -skip N: skip first N tests (useful to restart after abort)
Groups, grids, and test cases to be executed can be specified by list of file
masks, separated by spaces or comma; default is all (*).
}
set exc_grid 0
set exc_case 0
set regress 0
- set prev_logdir ""
+ set skipped 0
+ set logdir_regr ""
+ set logdir_skip ""
+ set nbskip 0
for {set narg 0} {$narg < [llength $args]} {incr narg} {
set arg [lindex $args $narg]
}
# re-run only a set of tests that have been detected as regressions on some previous run
- if { $arg == "-regress" } {
+ if { $arg == "-regress" || $arg == "-skipped" } {
incr narg
if { $narg < [llength $args] && ! [regexp {^-} [lindex $args $narg]] } {
- set prev_logdir [lindex $args $narg]
- set regress 1
+ if { $arg == "-regress" } {
+ set logdir_regr [file normalize [string trim [lindex $args $narg]]]
+ set regress 1
+ } else {
+ set logdir_skip [file normalize [string trim [lindex $args $narg]]]
+ set skipped 1
+ }
} else {
- error "Option -regress requires argument"
+ error "Option $arg requires argument"
+ }
+ continue
+ }
+
+ # skip N first tests
+ if { $arg == "-skip" } {
+ incr narg
+ if { $narg < [llength $args] && [string is integer [lindex $args $narg]] } {
+ set nbskip [lindex $args $narg]
+ } else {
+ error "Option -skip requires integer argument"
}
continue
}
# check that target log directory is empty or does not exist
set logdir [file normalize [string trim $logdir]]
- set prev_logdir [file normalize [string trim $prev_logdir]]
if { $logdir == "" } {
# if specified logdir is empty string, generate unique name like
# results/<branch>_<timestamp>
# if option "regress" is given
set rerun_group_grid_case {}
- if { ${regress} > 0 } {
+ if { ${regress} > 0 || ${skipped} > 0 } {
if { "${groupmask}" != "*"} {
lappend rerun_group_grid_case [list $groupmask $gridmask $casemask]
}
}
if { ${regress} > 0 } {
- if { [file exists ${prev_logdir}/tests.log] } {
- set fd [open ${prev_logdir}/tests.log]
+ if { [file exists ${logdir_regr}/tests.log] } {
+ set fd [open ${logdir_regr}/tests.log]
while { [gets $fd line] >= 0 } {
if {[regexp {CASE ([^\s]+) ([^\s]+) ([^\s]+): FAILED} $line dump group grid casename] ||
[regexp {CASE ([^\s]+) ([^\s]+) ([^\s]+): IMPROVEMENT} $line dump group grid casename]} {
}
close $fd
} else {
- error "Error: file ${prev_logdir}/tests.log is not found, check your input arguments!"
+ error "Error: file ${logdir_regr}/tests.log is not found, check your input arguments!"
+ }
+ }
+ if { ${skipped} > 0 } {
+ if { [file exists ${logdir_skip}/tests.log] } {
+ set fd [open ${logdir_skip}/tests.log]
+ while { [gets $fd line] >= 0 } {
+ if {[regexp {CASE ([^\s]+) ([^\s]+) ([^\s]+): SKIPPED} $line dump group grid casename] } {
+ lappend rerun_group_grid_case [list $group $grid $casename]
+ }
+ }
+ close $fd
+ } else {
+ error "Error: file ${logdir_skip}/tests.log is not found, check your input arguments!"
}
}
continue
}
- lappend tests_list [list $dir $group $grid $casename $casefile]
+ if { $nbskip > 0 } {
+ incr nbskip -1
+ } else {
+ lappend tests_list [list $dir $group $grid $casename $casefile]
+ }
}
}
}
if { $logdir != "" } { set imgdir_cmd "set imagedir $logdir/$group/$grid" }
# prepare command file for running test case in separate instance of DRAW
- set fd_cmd [open $logdir/$group/$grid/${casename}.tcl w]
+ set file_cmd "$logdir/$group/$grid/${casename}.tcl"
+ set fd_cmd [open $file_cmd w]
puts $fd_cmd "$imgdir_cmd"
puts $fd_cmd "set test_image $casename"
puts $fd_cmd "_run_test $dir $group $grid $casefile t"
# commant to run DRAW with a command file;
# note that empty string is passed as standard input to avoid possible
# hang-ups due to waiting for stdin of the launching process
- set command "exec <<{} DRAWEXE -f $logdir/$group/$grid/${casename}.tcl"
+ set command "exec <<{} DRAWEXE -f $file_cmd"
# alternative method to run without temporary file; disabled as it needs too many backslashes
# else {
proc _log_test_case {output logdir dir group grid casename logvar} {
upvar $logvar log
set show_errors 0
+
# check result and make HTML log
_check_log $dir $group $grid $casename $show_errors $output summary html_log
lappend log $summary
_log_html $logdir/$group/$grid/$casename.html $html_log "Test $group $grid $casename"
_log_save $logdir/$group/$grid/$casename.log "$output\n$summary" "Test $group $grid $casename"
}
+
+ # remove intermediate command file used to run test
+ if { [file exists $logdir/$group/$grid/${casename}.tcl] } {
+ file delete $logdir/$group/$grid/${casename}.tcl
+ }
}
# Auxiliary procedure to save log to file
puts $fd "</table>"
# time stamp and elapsed time info
+ puts $fd "<p>Generated on [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] on [info hostname]\n<p>"
if { $total_time != "" } {
- puts $fd "<p>Generated on [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] on [info hostname]\n<p>"
puts $fd [join [split $total_time "\n"] "<p>"]
} else {
puts $fd "<p>NOTE: This is intermediate summary; the tests are still running! This page will refresh automatically until tests are finished."