diff --git a/dox/dev_guides/debug/debug.md b/dox/dev_guides/debug/debug.md index 8433e2d4df..70b511e3ed 100644 --- a/dox/dev_guides/debug/debug.md +++ b/dox/dev_guides/debug/debug.md @@ -368,3 +368,53 @@ Each counter has its name shown when the collected statistics are printed. 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
+> 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). diff --git a/src/Draw/Draw_BasicCommands.cxx b/src/Draw/Draw_BasicCommands.cxx index 4b5e06007b..b469a0668c 100644 --- a/src/Draw/Draw_BasicCommands.cxx +++ b/src/Draw/Draw_BasicCommands.cxx @@ -537,13 +537,24 @@ static void *CpuFunc(void* /*threadarg*/) } #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 @@ -553,7 +564,7 @@ static Standard_Integer cpulimit(Draw_Interpretor& di, Standard_Integer n, const 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); @@ -573,7 +584,7 @@ static Standard_Integer cpulimit(Draw_Interpretor& di, Standard_Integer n, const 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; @@ -597,10 +608,10 @@ static Standard_Integer cpulimit(Draw_Interpretor& di, Standard_Integer n, const pthread_create(&cpulimitThread, NULL, CpuFunc, NULL); } #endif + di << "CPU and elapsed time limit set to " << (double)CPU_LIMIT << " seconds"; return 0; } - //======================================================================= //function : mallochook //purpose : diff --git a/src/DrawResources/TestCommands.tcl b/src/DrawResources/TestCommands.tcl index 5363fabe84..ec5acf5f98 100644 --- a/src/DrawResources/TestCommands.tcl +++ b/src/DrawResources/TestCommands.tcl @@ -144,7 +144,9 @@ help testgrid { -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 (*). } @@ -172,7 +174,10 @@ proc testgrid {args} { 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] @@ -234,13 +239,29 @@ proc testgrid {args} { } # 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 } @@ -303,7 +324,6 @@ proc testgrid {args} { # 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/_ @@ -332,7 +352,7 @@ proc testgrid {args} { # 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] } @@ -341,8 +361,8 @@ proc testgrid {args} { } 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]} { @@ -351,7 +371,20 @@ proc testgrid {args} { } 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!" } } @@ -501,7 +534,11 @@ proc testgrid {args} { 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] + } } } } @@ -571,7 +608,8 @@ proc testgrid {args} { 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" @@ -594,7 +632,7 @@ proc testgrid {args} { # 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 { @@ -1558,6 +1596,7 @@ proc _log_and_puts {logvar message} { 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 @@ -1567,6 +1606,11 @@ proc _log_test_case {output logdir dir group grid casename logvar} { _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 @@ -1710,8 +1754,8 @@ proc _log_html_summary {logdir log totals regressions improvements skipped total puts $fd "" # time stamp and elapsed time info + puts $fd "

Generated on [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] on [info hostname]\n

" if { $total_time != "" } { - puts $fd "

Generated on [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}] on [info hostname]\n

" puts $fd [join [split $total_time "\n"] "

"] } else { puts $fd "

NOTE: This is intermediate summary; the tests are still running! This page will refresh automatically until tests are finished." diff --git a/tests/demo/begin b/tests/demo/begin deleted file mode 100755 index d664818133..0000000000 --- a/tests/demo/begin +++ /dev/null @@ -1 +0,0 @@ -# File : begin