From 5cce03895822c852a00dffe038f5f6c517f5dd42 Mon Sep 17 00:00:00 2001 From: Andrey Kamaev Date: Tue, 6 Sep 2011 15:30:28 +0000 Subject: [PATCH] Performance testing branch is merged back into trunk --- CMakeLists.txt | 17 +- OpenCVModule.cmake | 169 +- modules/CMakeLists.txt | 18 +- modules/core/CMakeLists.txt | 4 +- modules/core/include/opencv2/core/core.hpp | 19 +- modules/core/perf/perf_abs.cpp | 25 + modules/core/perf/perf_bitwise.cpp | 25 + modules/core/perf/perf_core_arithm.cpp | 66 + modules/core/perf/perf_main.cpp | 3 + modules/core/perf/perf_precomp.cpp | 1 + modules/core/perf/perf_precomp.hpp | 10 + modules/core/perf/perf_stat.cpp | 426 +++ modules/core/src/cmdparser.cpp | 18 +- modules/core/src/persistence.cpp | 3 +- modules/highgui/CMakeLists.txt | 76 +- modules/imgproc/perf/perf_cvt_color.cpp | 111 + .../perf/perf_geometric_transformations.cpp | 55 + modules/imgproc/perf/perf_integral.cpp | 44 + modules/imgproc/perf/perf_main.cpp | 3 + modules/imgproc/perf/perf_precomp.cpp | 1 + modules/imgproc/perf/perf_precomp.hpp | 11 + modules/ts/CMakeLists.txt | 7 +- modules/ts/include/opencv2/ts/ts.hpp | 14 +- modules/ts/include/opencv2/ts/ts_gtest.h | 2747 +++++++++++++---- modules/ts/include/opencv2/ts/ts_perf.hpp | 444 +++ modules/ts/misc/chart.py | 180 ++ modules/ts/misc/color.py | 384 +++ modules/ts/misc/report.py | 80 + modules/ts/misc/run.py | 421 +++ modules/ts/misc/summary.py | 144 + modules/ts/misc/table_formatter.py | 586 ++++ modules/ts/misc/testlog_parser.py | 171 + modules/ts/src/precomp.cpp | 12 - modules/ts/src/precomp.hpp | 10 +- modules/ts/src/ts_gtest.cpp | 2014 +++++++----- modules/ts/src/ts_perf.cpp | 930 ++++++ 36 files changed, 7764 insertions(+), 1485 deletions(-) create mode 100644 modules/core/perf/perf_abs.cpp create mode 100644 modules/core/perf/perf_bitwise.cpp create mode 100644 modules/core/perf/perf_core_arithm.cpp create mode 100644 modules/core/perf/perf_main.cpp create mode 100644 modules/core/perf/perf_precomp.cpp create mode 100644 modules/core/perf/perf_precomp.hpp create mode 100644 modules/core/perf/perf_stat.cpp create mode 100644 modules/imgproc/perf/perf_cvt_color.cpp create mode 100644 modules/imgproc/perf/perf_geometric_transformations.cpp create mode 100644 modules/imgproc/perf/perf_integral.cpp create mode 100644 modules/imgproc/perf/perf_main.cpp create mode 100644 modules/imgproc/perf/perf_precomp.cpp create mode 100644 modules/imgproc/perf/perf_precomp.hpp create mode 100644 modules/ts/include/opencv2/ts/ts_perf.hpp create mode 100644 modules/ts/misc/chart.py create mode 100644 modules/ts/misc/color.py create mode 100644 modules/ts/misc/report.py create mode 100644 modules/ts/misc/run.py create mode 100644 modules/ts/misc/summary.py create mode 100644 modules/ts/misc/table_formatter.py create mode 100644 modules/ts/misc/testlog_parser.py create mode 100644 modules/ts/src/ts_perf.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 365c434e5..3a89d6807 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -339,6 +339,7 @@ if(NOT IOS) else() set(BUILD_TESTS OFF CACHE BOOL "Build tests") endif() +set(BUILD_PERF_TESTS ON CACHE BOOL "Build performance tests") # Build 3rdparty libraries under unix # =================================================== @@ -1555,6 +1556,17 @@ if(ENABLE_SOLUTION_FOLDERS) set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets") endif() +#----------------------------------- +# performance tests +#----------------------------------- +if(BUILD_PERF_TESTS AND PYTHON_EXECUTABLE) + add_custom_target(perf + ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/modules/ts/misc/run.py" "${CMAKE_BINARY_DIR}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/modules/ts/misc/run.py" + ) +endif() + #----------------------------------- # Subdirectories: #----------------------------------- @@ -1763,8 +1775,9 @@ endif() # samples and tests status("") status(" Tests and samples:") -status(" Tests:" BUILD_TESTS THEN YES ELSE NO) -status(" Examples:" BUILD_EXAMPLES THEN YES ELSE NO) +status(" Tests:" BUILD_TESTS THEN YES ELSE NO) +status(" Performance tests:" BUILD_PERF_TESTS THEN YES ELSE NO) +status(" Examples:" BUILD_EXAMPLES THEN YES ELSE NO) if(ANDROID) status(" Android tests:" BUILD_TESTS AND CAN_BUILD_ANDROID_PROJECTS THEN YES ELSE NO) diff --git a/OpenCVModule.cmake b/OpenCVModule.cmake index 53a24bdfd..aa523423c 100644 --- a/OpenCVModule.cmake +++ b/OpenCVModule.cmake @@ -3,6 +3,8 @@ macro(add_opencv_precompiled_headers the_target) if("${the_target}" MATCHES "opencv_test_.*") SET(pch_name "test/test_precomp") + elseif("${the_target}" MATCHES "opencv_perf_.*") + SET(pch_name "perf/perf_precomp") else() SET(pch_name "src/precomp") endif() @@ -19,7 +21,110 @@ macro(add_opencv_precompiled_headers the_target) endif() endmacro() -# this is template for a OpenCV module +# this is a template for a OpenCV performance tests +# define_opencv_perf_test( ) +macro(define_opencv_perf_test name) + set(perf_path "${CMAKE_CURRENT_SOURCE_DIR}/perf") + if(BUILD_PERF_TESTS AND EXISTS "${perf_path}") + + include_directories("${perf_path}" "${CMAKE_CURRENT_BINARY_DIR}") + + # opencv_highgui is required for imread/imwrite + set(perf_deps opencv_${name} ${ARGN} opencv_ts opencv_highgui ${EXTRA_OPENCV_${name}_DEPS}) + + foreach(d ${perf_deps}) + if(d MATCHES "opencv_") + string(REPLACE "opencv_" "${OpenCV_SOURCE_DIR}/modules/" d_dir ${d}) + if (EXISTS "${d_dir}/include") + include_directories("${d_dir}/include") + endif() + endif() + endforeach() + + file(GLOB perf_srcs "${perf_path}/*.cpp") + file(GLOB perf_hdrs "${perf_path}/*.h*") + + source_group("Src" FILES ${perf_srcs}) + source_group("Include" FILES ${perf_hdrs}) + + set(the_target "opencv_perf_${name}") + add_executable(${the_target} ${perf_srcs} ${perf_hdrs}) + + # Additional target properties + set_target_properties(${the_target} PROPERTIES + DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" + RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}" + ) + + if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(${the_target} PROPERTIES FOLDER "performance tests") + endif() + + add_dependencies(${the_target} ${perf_deps}) + target_link_libraries(${the_target} ${OPENCV_LINKER_LIBS} ${perf_deps}) + + add_opencv_precompiled_headers(${the_target}) + + if (PYTHON_EXECUTABLE) + add_dependencies(perf ${the_target}) + endif() + endif() +endmacro() + +# this is a template for a OpenCV regression tests +# define_opencv_test( ) +macro(define_opencv_test name) + set(test_path "${CMAKE_CURRENT_SOURCE_DIR}/test") + if(BUILD_TESTS AND EXISTS "${test_path}") + include_directories("${test_path}" "${CMAKE_CURRENT_BINARY_DIR}") + + # opencv_highgui is required for imread/imwrite + set(test_deps opencv_${name} ${ARGN} opencv_ts opencv_highgui ${EXTRA_OPENCV_${name}_DEPS}) + + foreach(d ${test_deps}) + if(d MATCHES "opencv_") + string(REPLACE "opencv_" "${OpenCV_SOURCE_DIR}/modules/" d_dir ${d}) + if (EXISTS "${d_dir}/include") + include_directories("${d_dir}/include") + endif() + endif() + endforeach() + + file(GLOB test_srcs "${test_path}/*.cpp") + file(GLOB test_hdrs "${test_path}/*.h*") + + source_group("Src" FILES ${test_srcs}) + source_group("Include" FILES ${test_hdrs}) + + set(the_target "opencv_test_${name}") + add_executable(${the_target} ${test_srcs} ${test_hdrs}) + + # Additional target properties + set_target_properties(${the_target} PROPERTIES + DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" + RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}" + ) + + if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(${the_target} PROPERTIES FOLDER "tests") + endif() + + add_dependencies(${the_target} ${test_deps}) + target_link_libraries(${the_target} ${OPENCV_LINKER_LIBS} ${test_deps}) + + enable_testing() + get_target_property(LOC ${the_target} LOCATION) + add_test(${the_target} "${LOC}") + + #if(WIN32) + # install(TARGETS ${the_target} RUNTIME DESTINATION bin COMPONENT main) + #endif() + add_opencv_precompiled_headers(${the_target}) + endif() +endmacro() + +# this is a template for a OpenCV module +# define_opencv_module( ) macro(define_opencv_module name) project(opencv_${name}) @@ -30,21 +135,22 @@ macro(define_opencv_module name) foreach(d ${ARGN}) if(d MATCHES "opencv_") - string(REPLACE "opencv_" "${CMAKE_CURRENT_SOURCE_DIR}/../" d_dir ${d}) - include_directories("${d_dir}/include") + string(REPLACE "opencv_" "${OpenCV_SOURCE_DIR}/modules/" d_dir ${d}) + if (EXISTS "${d_dir}/include") + include_directories("${d_dir}/include") + endif() endif() endforeach() file(GLOB lib_srcs "src/*.cpp") file(GLOB lib_int_hdrs "src/*.h*") + file(GLOB lib_hdrs "include/opencv2/${name}/*.h*") if(COMMAND get_module_external_sources) get_module_external_sources(${name}) endif() source_group("Src" FILES ${lib_srcs} ${lib_int_hdrs}) - - file(GLOB lib_hdrs "include/opencv2/${name}/*.h*") source_group("Include" FILES ${lib_hdrs}) set(the_target "opencv_${name}") @@ -86,8 +192,6 @@ macro(define_opencv_module name) INSTALL_NAME_DIR lib ) - add_opencv_precompiled_headers(${the_target}) - # Add the required libraries for linking: target_link_libraries(${the_target} ${OPENCV_LINKER_LIBS} ${IPP_LIBS} ${ARGN}) @@ -114,53 +218,8 @@ macro(define_opencv_module name) DESTINATION ${OPENCV_INCLUDE_PREFIX}/opencv2/${name} COMPONENT main) - if(BUILD_TESTS AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/test) - include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_SOURCE_DIR}/test" - "${CMAKE_CURRENT_BINARY_DIR}") - - set(test_deps opencv_${name} ${ARGN} opencv_ts opencv_highgui ${EXTRA_${the_target}_DEPS}) - foreach(d ${test_deps}) - if(d MATCHES "opencv_") - string(REPLACE "opencv_" "${CMAKE_CURRENT_SOURCE_DIR}/../" d_dir ${d}) - include_directories("${d_dir}/include") - endif() - endforeach() - - file(GLOB test_srcs "test/*.cpp") - file(GLOB test_hdrs "test/*.h*") - - source_group("Src" FILES ${test_srcs}) - source_group("Include" FILES ${test_hdrs}) - - set(the_target "opencv_test_${name}") - - add_executable(${the_target} ${test_srcs} ${test_hdrs}) - - add_opencv_precompiled_headers(${the_target}) - - # Additional target properties - set_target_properties(${the_target} PROPERTIES - DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" - RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}" - ) - - if(ENABLE_SOLUTION_FOLDERS) - set_target_properties(${the_target} PROPERTIES FOLDER "tests") - endif() - - add_dependencies(${the_target} ${test_deps}) - - # Add the required libraries for linking: - target_link_libraries(${the_target} ${OPENCV_LINKER_LIBS} ${test_deps}) - - enable_testing() - get_target_property(LOC ${the_target} LOCATION) - add_test(${the_target} "${LOC}") - - #if(WIN32) - # install(TARGETS ${the_target} RUNTIME DESTINATION bin COMPONENT main) - #endif() - endif() + add_opencv_precompiled_headers(${the_target}) + define_opencv_test(${name}) + define_opencv_perf_test(${name}) endmacro() diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 43ae43744..320ea8c5a 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,12 +1,5 @@ -if(ANDROID) - ADD_DEFINITIONS(-DGTEST_HAS_STD_WSTRING=0) - if(ANDROID_API_LEVEL LESS 8) - ADD_DEFINITIONS(-DGTEST_HAS_CLONE=0) - endif() - - IF(WITH_ANDROID_CAMERA) - add_subdirectory(androidcamera) - endif() +if(ANDROID AND WITH_ANDROID_CAMERA) + add_subdirectory(androidcamera) endif() add_subdirectory(calib3d) @@ -14,9 +7,10 @@ add_subdirectory(core) add_subdirectory(features2d) add_subdirectory(flann) -if(BUILD_TESTS) +if(BUILD_TESTS OR BUILD_PERF_TESTS) add_subdirectory(ts) endif() + add_subdirectory(highgui) add_subdirectory(imgproc) add_subdirectory(legacy) @@ -35,8 +29,8 @@ endif() add_subdirectory(video) if(NOT IOS) -add_subdirectory(traincascade) -add_subdirectory(haartraining) + add_subdirectory(traincascade) + add_subdirectory(haartraining) endif() if(NOT (ANDROID OR IOS)) diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 84c3ef558..85c874ef2 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -1,8 +1,6 @@ if(ZLIB_FOUND) include_directories(${ZLIB_INCUDE_DIR}) - set(deps ${ZLIB_LIBRARIES}) else() include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../3rdparty/zlib") - set(deps zlib) endif() -define_opencv_module(core ${deps}) +define_opencv_module(core ${ZLIB_LIBRARY}) diff --git a/modules/core/include/opencv2/core/core.hpp b/modules/core/include/opencv2/core/core.hpp index cfe096b93..13a326daa 100644 --- a/modules/core/include/opencv2/core/core.hpp +++ b/modules/core/include/opencv2/core/core.hpp @@ -4276,7 +4276,7 @@ class CV_EXPORTS CommandLineParser public: //! the default constructor - CommandLineParser(int argc, const char* argv[], const char* key_map); + CommandLineParser(int argc, const char* const argv[], const char* key_map); //! get parameter, you can choose: delete spaces in end and begin or not template @@ -4287,7 +4287,7 @@ class CV_EXPORTS CommandLineParser return _Tp(); } std::string str = getString(name); - return analizeValue<_Tp>(str, space_delete); + return analyzeValue<_Tp>(str, space_delete); } //! print short name, full name, current value and help for all params @@ -4300,7 +4300,7 @@ class CV_EXPORTS CommandLineParser bool has(const std::string& keys); template - _Tp analizeValue(const std::string& str, bool space_delete=false); + _Tp analyzeValue(const std::string& str, bool space_delete=false); template static _Tp getData(const std::string& str) @@ -4320,19 +4320,22 @@ template<> CV_EXPORTS bool CommandLineParser::get(const std::string& name, bool space_delete); template<> CV_EXPORTS -std::string CommandLineParser::analizeValue(const std::string& str, bool space_delete); +std::string CommandLineParser::analyzeValue(const std::string& str, bool space_delete); template<> CV_EXPORTS -int CommandLineParser::analizeValue(const std::string& str, bool space_delete); +int CommandLineParser::analyzeValue(const std::string& str, bool space_delete); template<> CV_EXPORTS -unsigned CommandLineParser::analizeValue(const std::string& str, bool space_delete); +unsigned int CommandLineParser::analyzeValue(const std::string& str, bool space_delete); template<> CV_EXPORTS -float CommandLineParser::analizeValue(const std::string& str, bool space_delete); +uint64 CommandLineParser::analyzeValue(const std::string& str, bool space_delete); template<> CV_EXPORTS -double CommandLineParser::analizeValue(const std::string& str, bool space_delete); +float CommandLineParser::analyzeValue(const std::string& str, bool space_delete); + +template<> CV_EXPORTS +double CommandLineParser::analyzeValue(const std::string& str, bool space_delete); } diff --git a/modules/core/perf/perf_abs.cpp b/modules/core/perf/perf_abs.cpp new file mode 100644 index 000000000..8898a45ed --- /dev/null +++ b/modules/core/perf/perf_abs.cpp @@ -0,0 +1,25 @@ +#include "perf_precomp.hpp" + +using namespace std; +using namespace cv; +using namespace perf; + +#define TYPICAL_MAT_SIZES_ABS TYPICAL_MAT_SIZES +#define TYPICAL_MAT_TYPES_ABS CV_8SC1, CV_8SC4, CV_32SC1, CV_32FC1 +#define TYPICAL_MATS_ABS testing::Combine( testing::Values( TYPICAL_MAT_SIZES_ABS), testing::Values( TYPICAL_MAT_TYPES_ABS) ) + +PERF_TEST_P(Size_MatType, abs, TYPICAL_MATS_ABS) +{ + Size sz = std::tr1::get<0>(GetParam()); + int type = std::tr1::get<1>(GetParam()); + + cv::Mat a = Mat(sz, type); + cv::Mat c = Mat(sz, type); + + declare.in(a, ::perf::TestBase::WARMUP_RNG).out(c).time(0.5); + + TEST_CYCLE(100) c = cv::abs(a); + + SANITY_CHECK(c); +} + diff --git a/modules/core/perf/perf_bitwise.cpp b/modules/core/perf/perf_bitwise.cpp new file mode 100644 index 000000000..1ebf11e6c --- /dev/null +++ b/modules/core/perf/perf_bitwise.cpp @@ -0,0 +1,25 @@ +#include "perf_precomp.hpp" + +using namespace std; +using namespace cv; +using namespace perf; + +#define TYPICAL_MAT_SIZES_BITWNOT TYPICAL_MAT_SIZES +#define TYPICAL_MAT_TYPES_BITWNOT CV_8SC1, CV_8SC4, CV_32SC1, CV_32SC4 +#define TYPICAL_MATS_BITWNOT testing::Combine( testing::Values( TYPICAL_MAT_SIZES_BITWNOT), testing::Values( TYPICAL_MAT_TYPES_BITWNOT) ) + +PERF_TEST_P(Size_MatType, bitwise_not, TYPICAL_MATS_BITWNOT) +{ + Size sz = std::tr1::get<0>(GetParam()); + int type = std::tr1::get<1>(GetParam()); + + cv::Mat a = Mat(sz, type); + cv::Mat c = Mat(sz, type); + + declare.in(a, WARMUP_RNG).out(c); + + TEST_CYCLE(100) cv::bitwise_not(a, c); + + SANITY_CHECK(c); +} + diff --git a/modules/core/perf/perf_core_arithm.cpp b/modules/core/perf/perf_core_arithm.cpp new file mode 100644 index 000000000..8b5db8314 --- /dev/null +++ b/modules/core/perf/perf_core_arithm.cpp @@ -0,0 +1,66 @@ +#include "perf_precomp.hpp" + +using namespace std; +using namespace cv; +using namespace perf; + +#define TYPICAL_MAT_SIZES_CORE_ARITHM TYPICAL_MAT_SIZES +#define TYPICAL_MAT_TYPES_CORE_ARITHM CV_8UC1, CV_8SC1, CV_8UC4, CV_32SC1, CV_32FC1 +#define TYPICAL_MATS_CORE_ARITHM testing::Combine( testing::Values( TYPICAL_MAT_SIZES_CORE_ARITHM ), testing::Values( TYPICAL_MAT_TYPES_CORE_ARITHM ) ) + +#define TYPICAL_MAT_TYPES_BITW_ARITHM CV_8UC1, CV_8SC1, CV_8UC4, CV_32SC1, CV_32SC4 +#define TYPICAL_MATS_BITW_ARITHM testing::Combine( testing::Values( TYPICAL_MAT_SIZES_CORE_ARITHM ), testing::Values( TYPICAL_MAT_TYPES_BITW_ARITHM ) ) + +#define PERF_TEST_P__CORE_ARITHM(__f, __testset) \ +PERF_TEST_P(Size_MatType, core_arithm__ ## __f, __testset) \ +{ \ + Size sz = std::tr1::get<0>(GetParam()); \ + int type = std::tr1::get<1>(GetParam()); \ + cv::Mat a = Mat(sz, type); \ + cv::Mat b = Mat(sz, type); \ + cv::Mat c = Mat(sz, type); \ + \ + declare.in(a, b, WARMUP_RNG) \ + .out(c); \ + \ + TEST_CYCLE(100) __f(a,b, c); \ + \ + SANITY_CHECK(c); \ +} + +#define PERF_TEST_P__CORE_ARITHM_SCALAR(__f, __testset) \ +PERF_TEST_P(Size_MatType, core_arithm__ ## __f ##__Scalar, __testset) \ +{ \ + Size sz = std::tr1::get<0>(GetParam()); \ + int type = std::tr1::get<1>(GetParam()); \ + cv::Mat a = Mat(sz, type); \ + cv::Scalar b; \ + cv::Mat c = Mat(sz, type); \ + \ + declare.in(a, b, WARMUP_RNG) \ + .out(c); \ + \ + TEST_CYCLE(100) __f(a,b, c); \ + \ + SANITY_CHECK(c); \ +} + +PERF_TEST_P__CORE_ARITHM(bitwise_and, TYPICAL_MATS_BITW_ARITHM) +PERF_TEST_P__CORE_ARITHM(bitwise_or, TYPICAL_MATS_BITW_ARITHM) +PERF_TEST_P__CORE_ARITHM(bitwise_xor, TYPICAL_MATS_BITW_ARITHM) +PERF_TEST_P__CORE_ARITHM(add, TYPICAL_MATS_CORE_ARITHM) +PERF_TEST_P__CORE_ARITHM(subtract, TYPICAL_MATS_CORE_ARITHM) +PERF_TEST_P__CORE_ARITHM(min, TYPICAL_MATS_CORE_ARITHM) +PERF_TEST_P__CORE_ARITHM(max, TYPICAL_MATS_CORE_ARITHM) +PERF_TEST_P__CORE_ARITHM(absdiff, TYPICAL_MATS_CORE_ARITHM) + +PERF_TEST_P__CORE_ARITHM_SCALAR(bitwise_and, TYPICAL_MATS_BITW_ARITHM) +PERF_TEST_P__CORE_ARITHM_SCALAR(bitwise_or, TYPICAL_MATS_BITW_ARITHM) +PERF_TEST_P__CORE_ARITHM_SCALAR(bitwise_xor, TYPICAL_MATS_BITW_ARITHM) +PERF_TEST_P__CORE_ARITHM_SCALAR(add, TYPICAL_MATS_CORE_ARITHM) +PERF_TEST_P__CORE_ARITHM_SCALAR(subtract, TYPICAL_MATS_CORE_ARITHM) +PERF_TEST_P__CORE_ARITHM_SCALAR(min, TYPICAL_MATS_CORE_ARITHM) +PERF_TEST_P__CORE_ARITHM_SCALAR(max, TYPICAL_MATS_CORE_ARITHM) +PERF_TEST_P__CORE_ARITHM_SCALAR(absdiff, TYPICAL_MATS_CORE_ARITHM) + + diff --git a/modules/core/perf/perf_main.cpp b/modules/core/perf/perf_main.cpp new file mode 100644 index 000000000..79c28a645 --- /dev/null +++ b/modules/core/perf/perf_main.cpp @@ -0,0 +1,3 @@ +#include "perf_precomp.hpp" + +CV_PERF_TEST_MAIN(core) diff --git a/modules/core/perf/perf_precomp.cpp b/modules/core/perf/perf_precomp.cpp new file mode 100644 index 000000000..8552ac3d4 --- /dev/null +++ b/modules/core/perf/perf_precomp.cpp @@ -0,0 +1 @@ +#include "perf_precomp.hpp" diff --git a/modules/core/perf/perf_precomp.hpp b/modules/core/perf/perf_precomp.hpp new file mode 100644 index 000000000..901f889c2 --- /dev/null +++ b/modules/core/perf/perf_precomp.hpp @@ -0,0 +1,10 @@ +#ifndef __OPENCV_PERF_PRECOMP_HPP__ +#define __OPENCV_PERF_PRECOMP_HPP__ + +#include "opencv2/ts/ts.hpp" + +#if GTEST_CREATE_SHARED_LIBRARY +#error no modules except ts should have GTEST_CREATE_SHARED_LIBRARY defined +#endif + +#endif diff --git a/modules/core/perf/perf_stat.cpp b/modules/core/perf/perf_stat.cpp new file mode 100644 index 000000000..266a99071 --- /dev/null +++ b/modules/core/perf/perf_stat.cpp @@ -0,0 +1,426 @@ +#include "perf_precomp.hpp" +#include "opencv2/core/core_c.h" + +using namespace std; +using namespace cv; +using namespace perf; + + +/* +// Scalar sum(InputArray arr) +*/ +PERF_TEST_P( Size_MatType, sum, TYPICAL_MATS ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int type = std::tr1::get<1>(GetParam()); + + Mat arr(sz, type); + Scalar s; + + declare.in(arr, WARMUP_RNG); + + TEST_CYCLE(100) { s = sum(arr); } + + SANITY_CHECK(s); +} + + +/* +// Scalar mean(InputArray src) +*/ +PERF_TEST_P( Size_MatType, mean, TYPICAL_MATS ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int type = std::tr1::get<1>(GetParam()); + + Mat src(sz, type); + Scalar s; + + declare.in(src, WARMUP_RNG); + + TEST_CYCLE(100) { s = mean(src); } + + SANITY_CHECK(s); +} + + +/* +// Scalar mean(InputArray src, InputArray mask=noArray()) +*/ +PERF_TEST_P( Size_MatType, mean_mask, TYPICAL_MATS ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int type = std::tr1::get<1>(GetParam()); + + Mat src(sz, type); + Mat mask = Mat::ones(src.size(), CV_8U); + Scalar s; + + declare.in(src, WARMUP_RNG).in(mask); + + TEST_CYCLE(100) { s = mean(src, mask); } + + SANITY_CHECK(s); +} + +CV_FLAGS(NormType, NORM_INF, NORM_L1, NORM_L2, NORM_TYPE_MASK, NORM_RELATIVE, NORM_MINMAX) +typedef std::tr1::tuple Size_MatType_NormType_t; +typedef perf::TestBaseWithParam Size_MatType_NormType; + +/* +// double norm(InputArray src1, int normType=NORM_L2) +*/ +PERF_TEST_P( Size_MatType_NormType, norm, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( (int)NORM_INF, (int)NORM_L1, (int)NORM_L2 ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int normType = std::tr1::get<2>(GetParam()); + + Mat src1(sz, matType); + double n; + + declare.in(src1, WARMUP_RNG); + + TEST_CYCLE(100) { n = norm(src1, normType); } + + SANITY_CHECK(n); +} + + +/* +// double norm(InputArray src1, int normType=NORM_L2, InputArray mask=noArray()) +*/ +PERF_TEST_P( Size_MatType_NormType, norm_mask, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( (int)NORM_INF, (int)NORM_L1, (int)NORM_L2 ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int normType = std::tr1::get<2>(GetParam()); + + Mat src1(sz, matType); + Mat mask = Mat::ones(sz, CV_8U); + double n; + + declare.in(src1, WARMUP_RNG).in(mask); + + TEST_CYCLE(100) { n = norm(src1, normType, mask); } + + SANITY_CHECK(n); +} + + +/* +// double norm(InputArray src1, InputArray src2, int normType) +*/ +PERF_TEST_P( Size_MatType_NormType, norm2, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( (int)NORM_INF, (int)NORM_L1, (int)NORM_L2, (int)(NORM_RELATIVE+NORM_INF), (int)(NORM_RELATIVE+NORM_L1), (int)(NORM_RELATIVE+NORM_L2) ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int normType = std::tr1::get<2>(GetParam()); + + Mat src1(sz, matType); + Mat src2(sz, matType); + double n; + + declare.in(src1, src2, WARMUP_RNG); + + TEST_CYCLE(100) { n = norm(src1, src2, normType); } + + SANITY_CHECK(n); +} + + +/* +// double norm(InputArray src1, InputArray src2, int normType, InputArray mask=noArray()) +*/ +PERF_TEST_P( Size_MatType_NormType, norm2_mask, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( (int)NORM_INF, (int)NORM_L1, (int)NORM_L2, (int)(NORM_RELATIVE+NORM_INF), (int)(NORM_RELATIVE+NORM_L1), (int)(NORM_RELATIVE+NORM_L2) ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int normType = std::tr1::get<2>(GetParam()); + + Mat src1(sz, matType); + Mat src2(sz, matType); + Mat mask = Mat::ones(sz, CV_8U); + double n; + + declare.in(src1, src2, WARMUP_RNG).in(mask); + + TEST_CYCLE(100) { n = norm(src1, src2, normType, mask); } + + SANITY_CHECK(n); +} + + +/* +// void normalize(const InputArray src, OutputArray dst, double alpha=1, double beta=0, int normType=NORM_L2) +*/ +PERF_TEST_P( Size_MatType_NormType, normalize, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( (int)NORM_INF, (int)NORM_L1, (int)NORM_L2 ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int normType = std::tr1::get<2>(GetParam()); + + Mat src(sz, matType); + Mat dst(sz, matType); + double alpha = 100.; + if(normType==NORM_L1) alpha = (double)src.total() * src.channels(); + if(normType==NORM_L2) alpha = (double)src.total()/10; + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE(100) { normalize(src, dst, alpha, 0., normType); } + + SANITY_CHECK(dst); +} + + +/* +// void normalize(const InputArray src, OutputArray dst, double alpha=1, double beta=0, int normType=NORM_L2, int rtype=-1, InputArray mask=noArray()) +*/ +PERF_TEST_P( Size_MatType_NormType, normalize_mask, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( (int)NORM_INF, (int)NORM_L1, (int)NORM_L2 ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int normType = std::tr1::get<2>(GetParam()); + + Mat src(sz, matType); + Mat dst(sz, matType); + Mat mask = Mat::ones(sz, CV_8U); + double alpha = 100.; + if(normType==NORM_L1) alpha = (double)src.total() * src.channels(); + if(normType==NORM_L2) alpha = (double)src.total()/10; + + declare.in(src, WARMUP_RNG).in(mask).out(dst); + + TEST_CYCLE(100) { normalize(src, dst, alpha, 0., normType, -1, mask); } + + SANITY_CHECK(dst); +} + + +/* +// void normalize(const InputArray src, OutputArray dst, double alpha=1, double beta=0, int normType=NORM_L2, int rtype=-1) +*/ +PERF_TEST_P( Size_MatType_NormType, normalize_32f, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( (int)NORM_INF, (int)NORM_L1, (int)NORM_L2 ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int normType = std::tr1::get<2>(GetParam()); + + Mat src(sz, matType); + Mat dst(sz, matType); + double alpha = 100.; + if(normType==NORM_L1) alpha = (double)src.total() * src.channels(); + if(normType==NORM_L2) alpha = (double)src.total()/10; + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE(100) { normalize(src, dst, alpha, 0., normType, CV_32F); } + + SANITY_CHECK(dst); +} + + +/* +// void normalize(const InputArray src, OutputArray dst, double alpha=1, double beta=0, int normType=NORM_L2) +*/ +PERF_TEST_P( Size_MatType, normalize_minmax, TYPICAL_MATS ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + + Mat src(sz, matType); + randu(src, 0, 256); + Mat dst(sz, matType); + + declare.in(src).out(dst); + + TEST_CYCLE(100) { normalize(src, dst, 20., 100., NORM_MINMAX); } + + SANITY_CHECK(dst); +} + + +/* +// void meanStdDev(InputArray src, OutputArray mean, OutputArray stddev) +*/ +PERF_TEST_P( Size_MatType, meanStdDev, TYPICAL_MATS ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + + Mat src(sz, matType); + Mat mean, dev; + + declare.in(src, WARMUP_RNG); + + TEST_CYCLE(100) { meanStdDev(src, mean, dev); } + + SANITY_CHECK(mean); + SANITY_CHECK(dev); +} + + +/* +// void meanStdDev(InputArray src, OutputArray mean, OutputArray stddev, InputArray mask=noArray()) +*/ +PERF_TEST_P( Size_MatType, meanStdDev_mask, TYPICAL_MATS ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + + Mat src(sz, matType); + Mat mask = Mat::ones(sz, CV_8U); + Mat mean, dev; + + declare.in(src, WARMUP_RNG).in(mask); + + TEST_CYCLE(100) { meanStdDev(src, mean, dev, mask); } + + SANITY_CHECK(mean); + SANITY_CHECK(dev); +} + + +/* +// int countNonZero(InputArray mtx) +*/ +PERF_TEST_P( Size_MatType, countNonZero, TYPICAL_MATS_C1 ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + + Mat src(sz, matType); + int cnt = 0; + + declare.in(src, WARMUP_RNG); + + TEST_CYCLE(100) { cnt = countNonZero(src); } + + SANITY_CHECK(cnt); +} + +/* +// void minMaxLoc(InputArray src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, InputArray mask=noArray()) +*/ +PERF_TEST_P( Size_MatType, minMaxLoc, TYPICAL_MATS_C1 ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + + Mat src(sz, matType); + double minVal, maxVal; + Point minLoc, maxLoc; + + declare.in(src, WARMUP_RNG); + + TEST_CYCLE(100) { minMaxLoc(src, &minVal, &maxVal, &minLoc, &maxLoc); } + + SANITY_CHECK(minVal); + SANITY_CHECK(maxVal); +} + + + +CV_ENUM(ROp, CV_REDUCE_SUM, CV_REDUCE_AVG, CV_REDUCE_MAX, CV_REDUCE_MIN) +typedef std::tr1::tuple Size_MatType_ROp_t; +typedef perf::TestBaseWithParam Size_MatType_ROp; + + +/* +// void reduce(InputArray mtx, OutputArray vec, int dim, int reduceOp, int dtype=-1) +*/ +PERF_TEST_P( Size_MatType_ROp, reduceR, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( CV_REDUCE_SUM, CV_REDUCE_AVG, CV_REDUCE_MAX, CV_REDUCE_MIN ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int reduceOp = std::tr1::get<2>(GetParam()); + + int ddepth = -1; + if( CV_MAT_DEPTH(matType)< CV_32S && (reduceOp == CV_REDUCE_SUM || reduceOp == CV_REDUCE_AVG) ) + ddepth = CV_32S; + Mat src(sz, matType); + Mat vec; + + declare.in(src, WARMUP_RNG); + + TEST_CYCLE(100) { reduce(src, vec, 0, reduceOp, ddepth); } + + SANITY_CHECK(vec); +} + +/* +// void reduce(InputArray mtx, OutputArray vec, int dim, int reduceOp, int dtype=-1) +*/ +PERF_TEST_P( Size_MatType_ROp, reduceC, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( TYPICAL_MAT_TYPES ), + testing::Values( CV_REDUCE_SUM, CV_REDUCE_AVG, CV_REDUCE_MAX, CV_REDUCE_MIN ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int reduceOp = std::tr1::get<2>(GetParam()); + + int ddepth = -1; + if( CV_MAT_DEPTH(matType)< CV_32S && (reduceOp == CV_REDUCE_SUM || reduceOp == CV_REDUCE_AVG) ) + ddepth = CV_32S; + Mat src(sz, matType); + Mat vec; + + declare.in(src, WARMUP_RNG); + + TEST_CYCLE(100) { reduce(src, vec, 1, reduceOp, ddepth); } + + SANITY_CHECK(vec); +} diff --git a/modules/core/src/cmdparser.cpp b/modules/core/src/cmdparser.cpp index e19c05e2a..4dc0dbee6 100644 --- a/modules/core/src/cmdparser.cpp +++ b/modules/core/src/cmdparser.cpp @@ -72,7 +72,7 @@ vector split_string(const string& str, const string& delimiters) return res; } -CommandLineParser::CommandLineParser(int argc, const char* argv[], const char* keys) +CommandLineParser::CommandLineParser(int argc, const char* const argv[], const char* keys) { std::string keys_buffer; @@ -272,7 +272,7 @@ bool CommandLineParser::get(const std::string& name, bool space_delete) return true; } template<> -std::string CommandLineParser::analizeValue(const std::string& str, bool space_delete) +std::string CommandLineParser::analyzeValue(const std::string& str, bool space_delete) { if (space_delete) { @@ -287,25 +287,31 @@ std::string CommandLineParser::analizeValue(const std::string& str, } template<> -int CommandLineParser::analizeValue(const std::string& str, bool /*space_delete*/) +int CommandLineParser::analyzeValue(const std::string& str, bool /*space_delete*/) { return fromStringNumber(str); } template<> -unsigned int CommandLineParser::analizeValue(const std::string& str, bool /*space_delete*/) +unsigned int CommandLineParser::analyzeValue(const std::string& str, bool /*space_delete*/) { return fromStringNumber(str); } template<> -float CommandLineParser::analizeValue(const std::string& str, bool /*space_delete*/) +uint64 CommandLineParser::analyzeValue(const std::string& str, bool /*space_delete*/) +{ + return fromStringNumber(str); +} + +template<> +float CommandLineParser::analyzeValue(const std::string& str, bool /*space_delete*/) { return fromStringNumber(str); } template<> -double CommandLineParser::analizeValue(const std::string& str, bool /*space_delete*/) +double CommandLineParser::analyzeValue(const std::string& str, bool /*space_delete*/) { return fromStringNumber(str); } diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index 64097e6a6..ffd9c021d 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -134,7 +134,7 @@ cv::string cv::FileStorage::getDefaultObjectName(const string& _filename) namespace cv { -#ifndef ANDROID //unsuported wcstombs on android +#if !defined(ANDROID) || defined(_GLIBCXX_USE_WCHAR_T) string fromUtf16(const WString& str) { cv::AutoBuffer _buf(str.size()*4 + 1); @@ -2160,6 +2160,7 @@ icvXMLParse( CvFileStorage* fs ) ptr = icvXMLSkipSpaces( fs, ptr, CV_XML_INSIDE_TAG ); if( memcmp( ptr, "\'" ); ptr = icvXMLParseTag( fs, ptr, &key, &list, &tag_type ); diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index 26e5ea50d..7cb2dee70 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -125,7 +125,7 @@ if(WIN32) endif() endif() -if(UNIX) +if(UNIX) if(NOT HAVE_QT) if(HAVE_GTK) set(highgui_srcs ${highgui_srcs} src/window_gtk.cpp) @@ -190,7 +190,7 @@ endif() #YV if(APPLE) if (NOT IOS) - add_definitions(-DHAVE_QUICKTIME=1) + add_definitions(-DHAVE_QUICKTIME=1) endif() if(NOT OPENCV_BUILD_3RDPARTY_LIBS) @@ -208,22 +208,17 @@ if(APPLE) endif() if(WITH_QUICKTIME) - set(highgui_srcs ${highgui_srcs} src/cap_qt.cpp) - else() + set(highgui_srcs ${highgui_srcs} src/cap_qt.cpp) + else() if (WITH_AVFOUNDATION) add_definitions(-DHAVE_AVFOUNDATION=1) set(highgui_srcs ${highgui_srcs} src/cap_avfoundation.mm) else() - set(highgui_srcs ${highgui_srcs} src/cap_qtkit.mm) - endif() - endif() - - - + set(highgui_srcs ${highgui_srcs} src/cap_qtkit.mm) + endif() + endif() endif(APPLE) - - if(WITH_ANDROID_CAMERA) include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../androidcamera/include") set(highgui_srcs ${highgui_srcs} src/cap_android.cpp) @@ -387,58 +382,5 @@ install(FILES ${highgui_ext_hdrs} ############################# highgui tests ################################ -if(BUILD_TESTS) - - include_directories("${CMAKE_CURRENT_SOURCE_DIR}/test" - "${CMAKE_CURRENT_BINARY_DIR}") - - set(test_deps opencv_ts opencv_highgui opencv_imgproc) - if(WITH_ANDROID_CAMERA) - set(test_deps ${test_deps} opencv_androidcamera) - endif() - - foreach(d ${test_deps}) - if(${d} MATCHES "opencv_") - string(REPLACE "opencv_" "${CMAKE_CURRENT_SOURCE_DIR}/../" d_dir ${d}) - include_directories("${d_dir}/include") - endif() - endforeach() - - file(GLOB test_srcs "test/*.cpp") - file(GLOB test_hdrs "test/*.h*") - - source_group("Src" FILES ${test_srcs}) - source_group("Include" FILES ${test_hdrs}) - - set(the_target "opencv_test_highgui") - - add_executable(${the_target} ${test_srcs} ${test_hdrs}) - - add_opencv_precompiled_headers(${the_target}) - - # Additional target properties - set_target_properties(${the_target} PROPERTIES - DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" - RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}" - ) - - if(ENABLE_SOLUTION_FOLDERS) - set_target_properties(${the_target} PROPERTIES FOLDER "tests") - endif() - - add_dependencies(${the_target} ${test_deps}) - - # Add the required libraries for linking: - target_link_libraries(${the_target} ${OPENCV_LINKER_LIBS} ${test_deps}) - - enable_testing() - get_target_property(LOC ${the_target} LOCATION) - add_test(${the_target} "${LOC}") - - if(WIN32) - if (MSVC AND NOT BUILD_SHARED_LIBS) - set_target_properties(${the_target} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:atlthunk.lib /NODEFAULTLIB:atlsd.lib /DEBUG") - endif() - #install(TARGETS ${the_target} RUNTIME DESTINATION bin COMPONENT main) - endif() -endif(BUILD_TESTS) +define_opencv_test(highgui) +define_opencv_perf_test(highgui) diff --git a/modules/imgproc/perf/perf_cvt_color.cpp b/modules/imgproc/perf/perf_cvt_color.cpp new file mode 100644 index 000000000..dc5fb4f19 --- /dev/null +++ b/modules/imgproc/perf/perf_cvt_color.cpp @@ -0,0 +1,111 @@ +#include "perf_precomp.hpp" + +using namespace std; +using namespace cv; +using namespace perf; + +CV_ENUM(CvtMode, CV_YUV2BGR, CV_YUV2RGB, //YUV + CV_YUV420i2BGR, CV_YUV420i2RGB, CV_YUV420sp2BGR, CV_YUV420sp2RGB, //YUV420 + CV_RGB2GRAY, CV_RGBA2GRAY, CV_BGR2GRAY, CV_BGRA2GRAY, //Gray + CV_GRAY2RGB, CV_GRAY2RGBA/*, CV_GRAY2BGR, CV_GRAY2BGRA*/ //Gray2 + ) + +typedef std::tr1::tuple Size_CvtMode_t; +typedef perf::TestBaseWithParam Size_CvtMode; + +typedef std::tr1::tuple Size_CvtMode_OutChNum_t; +typedef perf::TestBaseWithParam Size_CvtMode_OutChNum; + + +/* +// void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0 ) +*/ + + +PERF_TEST_P( Size_CvtMode_OutChNum, cvtColorYUV, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( (int)CV_YUV2BGR, (int)CV_YUV2RGB ), + testing::Values( 3, 4 ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int mode = std::tr1::get<1>(GetParam()); + int ch = std::tr1::get<2>(GetParam()); + + Mat src(sz, CV_8UC3); + Mat dst(sz, CV_8UC(ch)); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE(100) { cvtColor(src, dst, mode, ch); } + + SANITY_CHECK(dst); +} + + +PERF_TEST_P( Size_CvtMode_OutChNum, cvtColorYUV420, + testing::Combine( + testing::Values( szVGA, sz720p, sz1080p, Size(130, 60) ), + testing::Values( (int)CV_YUV420i2BGR, (int)CV_YUV420i2RGB, (int)CV_YUV420sp2BGR, (int)CV_YUV420sp2RGB ), + testing::Values( 3, 4 ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int mode = std::tr1::get<1>(GetParam()); + int ch = std::tr1::get<2>(GetParam()); + + Mat src(sz.height+sz.height/2, sz.width, CV_8UC1); + Mat dst(sz, CV_8UC(ch)); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE(100) { cvtColor(src, dst, mode, ch); } + + SANITY_CHECK(dst); +} + + +PERF_TEST_P( Size_CvtMode, cvtColorGray, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( (int)CV_RGB2GRAY, (int)CV_RGBA2GRAY, (int)CV_BGR2GRAY, (int)CV_BGRA2GRAY ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int mode = std::tr1::get<1>(GetParam()); + + Mat src(sz, CV_8UC((mode==CV_RGBA2GRAY || mode==CV_BGRA2GRAY)?4:3)); + Mat dst(sz, CV_8UC1); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE(100) { cvtColor(src, dst, mode); } + + SANITY_CHECK(dst); +} + + +PERF_TEST_P( Size_CvtMode, cvtColorGray2, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( (int)CV_GRAY2RGB, (int)CV_GRAY2RGBA/*, CV_GRAY2BGR, CV_GRAY2BGRA*/ ) + ) +) +{ + Size sz = std::tr1::get<0>(GetParam()); + int mode = std::tr1::get<1>(GetParam()); + + Mat src(sz, CV_8UC1); + Mat dst(sz, CV_8UC((mode==CV_GRAY2RGBA || mode==CV_GRAY2BGRA)?4:3)); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE(100) { cvtColor(src, dst, mode); } + + SANITY_CHECK(dst); +} + diff --git a/modules/imgproc/perf/perf_geometric_transformations.cpp b/modules/imgproc/perf/perf_geometric_transformations.cpp new file mode 100644 index 000000000..a2961bb4f --- /dev/null +++ b/modules/imgproc/perf/perf_geometric_transformations.cpp @@ -0,0 +1,55 @@ +#include "perf_precomp.hpp" + +using namespace std; +using namespace cv; +using namespace perf; + +typedef tr1::tuple MatInfo_Size_Size_t; +typedef TestBaseWithParam MatInfo_Size_Size; + +PERF_TEST_P(MatInfo_Size_Size, resizeUpLinear, + testing::Values( + MatInfo_Size_Size_t(CV_8UC1, szVGA, szqHD), + MatInfo_Size_Size_t(CV_8UC1, szVGA, sz720p), + MatInfo_Size_Size_t(CV_8UC4, szVGA, sz720p) + ) +) +{ + int matType = tr1::get<0>(GetParam()); + Size from = tr1::get<1>(GetParam()); + Size to = tr1::get<2>(GetParam()); + + cv::Mat src(from, matType); + cv::Mat dst(to, matType); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE(100) cv::resize(src, dst, to); + + SANITY_CHECK(dst); +} + +PERF_TEST_P(MatInfo_Size_Size, resizeDownLinear, + testing::Values( + MatInfo_Size_Size_t(CV_8UC1, szVGA, szQVGA), + MatInfo_Size_Size_t(CV_8UC4, szqHD, szVGA), + MatInfo_Size_Size_t(CV_8UC1, sz720p, Size(120 * sz720p.width / sz720p.height, 120)),//face detection min_face_size = 20% + MatInfo_Size_Size_t(CV_8UC4, sz720p, szVGA), + MatInfo_Size_Size_t(CV_8UC4, sz720p, szQVGA) + ) +) +{ + int matType = tr1::get<0>(GetParam()); + Size from = tr1::get<1>(GetParam()); + Size to = tr1::get<2>(GetParam()); + + cv::Mat src(from, matType); + cv::Mat dst(to, matType); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE(100) cv::resize(src, dst, to); + + SANITY_CHECK(dst); +} + diff --git a/modules/imgproc/perf/perf_integral.cpp b/modules/imgproc/perf/perf_integral.cpp new file mode 100644 index 000000000..0be744a20 --- /dev/null +++ b/modules/imgproc/perf/perf_integral.cpp @@ -0,0 +1,44 @@ +#include "perf_precomp.hpp" + +using namespace std; +using namespace cv; +using namespace perf; + +typedef std::tr1::tuple Size_MatType_OutMatDepth_t; +typedef perf::TestBaseWithParam Size_MatType_OutMatDepth; + +/* +// void integral(InputArray image, OutputArray sum, int sdepth=-1 ) +*/ +PERF_TEST_P( Size_MatType_OutMatDepth, integral1, + testing::Combine( + testing::Values( TYPICAL_MAT_SIZES ), + testing::Values( CV_8UC1, CV_8UC4 ), + testing::Values( CV_32S, CV_32F, CV_64F ) + ) + ) +{ + Size sz = std::tr1::get<0>(GetParam()); + int matType = std::tr1::get<1>(GetParam()); + int sdepth = std::tr1::get<2>(GetParam()); + + Mat src(sz, matType); + Mat sum(sz, sdepth); + + declare.in(src, WARMUP_RNG); + + TEST_CYCLE(100) { integral(src, sum, sdepth); } + + SANITY_CHECK(sum); +} + + + +/* +// void integral(InputArray image, OutputArray sum, OutputArray sqsum, int sdepth=-1 ) +*/ + + +/* +// void integral(InputArray image, OutputArray sum, OutputArray sqsum, OutputArray tilted, int sdepth=-1 ) +*/ \ No newline at end of file diff --git a/modules/imgproc/perf/perf_main.cpp b/modules/imgproc/perf/perf_main.cpp new file mode 100644 index 000000000..fc89f5202 --- /dev/null +++ b/modules/imgproc/perf/perf_main.cpp @@ -0,0 +1,3 @@ +#include "perf_precomp.hpp" + +CV_PERF_TEST_MAIN(imgproc) diff --git a/modules/imgproc/perf/perf_precomp.cpp b/modules/imgproc/perf/perf_precomp.cpp new file mode 100644 index 000000000..8552ac3d4 --- /dev/null +++ b/modules/imgproc/perf/perf_precomp.cpp @@ -0,0 +1 @@ +#include "perf_precomp.hpp" diff --git a/modules/imgproc/perf/perf_precomp.hpp b/modules/imgproc/perf/perf_precomp.hpp new file mode 100644 index 000000000..f819ab12e --- /dev/null +++ b/modules/imgproc/perf/perf_precomp.hpp @@ -0,0 +1,11 @@ +#ifndef __OPENCV_PERF_PRECOMP_HPP__ +#define __OPENCV_PERF_PRECOMP_HPP__ + +#include "opencv2/ts/ts.hpp" +#include "opencv2/imgproc/imgproc.hpp" + +#if GTEST_CREATE_SHARED_LIBRARY +#error no modules except ts should have GTEST_CREATE_SHARED_LIBRARY defined +#endif + +#endif diff --git a/modules/ts/CMakeLists.txt b/modules/ts/CMakeLists.txt index 41abebf6a..f9735b9de 100644 --- a/modules/ts/CMakeLists.txt +++ b/modules/ts/CMakeLists.txt @@ -1,4 +1,9 @@ if(BUILD_SHARED_LIBS) -add_definitions(-DGTEST_CREATE_SHARED_LIBRARY=1) + add_definitions(-DGTEST_CREATE_SHARED_LIBRARY=1) + if (MSVC) + add_definitions( "/wd4251 /wd4275") + endif() +else() + add_definitions(-DGTEST_CREATE_SHARED_LIBRARY=0) endif() define_opencv_module(ts opencv_core) diff --git a/modules/ts/include/opencv2/ts/ts.hpp b/modules/ts/include/opencv2/ts/ts.hpp index 056e34ca5..bcaa691f3 100644 --- a/modules/ts/include/opencv2/ts/ts.hpp +++ b/modules/ts/include/opencv2/ts/ts.hpp @@ -1,12 +1,23 @@ #ifndef __OPENCV_GTESTCV_HPP__ #define __OPENCV_GTESTCV_HPP__ -#ifndef GTEST_CREATE_AS_SHARED_LIBRARY +#ifndef GTEST_CREATE_SHARED_LIBRARY #define GTEST_LINKED_AS_SHARED_LIBRARY 1 #endif +#ifdef ANDROID +# include +# define GTEST_HAS_CLONE (__ANDROID_API__ > 7) +# define GTEST_HAS_POSIX_RE (__ANDROID_API__ > 7) +# define GTEST_HAS_STD_WSTRING _GLIBCXX_USE_WCHAR_T +#endif + #include // for va_list +#if _MSC_VER >= 1200 +#pragma warning( disable: 4251 4275 4355 4127 ) +#endif + #include "opencv2/ts/ts_gtest.h" #include "opencv2/core/core.hpp" @@ -542,3 +553,4 @@ int main(int argc, char **argv) \ #endif +#include "ts_perf.hpp" diff --git a/modules/ts/include/opencv2/ts/ts_gtest.h b/modules/ts/include/opencv2/ts/ts_gtest.h index 9872440fd..4d49fdccf 100644 --- a/modules/ts/include/opencv2/ts/ts_gtest.h +++ b/modules/ts/include/opencv2/ts/ts_gtest.h @@ -145,6 +145,8 @@ // GTEST_HAS_GLOBAL_WSTRING - Define it to 1/0 to indicate that ::string // is/isn't available (some systems define // ::wstring, which is different to std::wstring). +// GTEST_HAS_POSIX_RE - Define it to 1/0 to indicate that POSIX regular +// expressions are/aren't available. // GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that // is/isn't available. // GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't @@ -157,6 +159,10 @@ // GTEST_HAS_SEH - Define it to 1/0 to indicate whether the // compiler supports Microsoft's "Structured // Exception Handling". +// GTEST_HAS_STREAM_REDIRECTION +// - Define it to 1/0 to indicate whether the +// platform supports I/O stream redirection using +// dup() and dup2(). // GTEST_USE_OWN_TR1_TUPLE - Define it to 1/0 to indicate whether Google // Test's own tr1 tuple implementation should be // used. Unused when the user sets @@ -175,8 +181,11 @@ // the given platform; otherwise undefined): // GTEST_OS_AIX - IBM AIX // GTEST_OS_CYGWIN - Cygwin +// GTEST_OS_HPUX - HP-UX // GTEST_OS_LINUX - Linux +// GTEST_OS_LINUX_ANDROID - Google Android // GTEST_OS_MAC - Mac OS X +// GTEST_OS_NACL - Google Native Client (NaCl) // GTEST_OS_SOLARIS - Sun Solaris // GTEST_OS_SYMBIAN - Symbian // GTEST_OS_WINDOWS - Windows (Desktop, MinGW, or Mobile) @@ -202,7 +211,9 @@ // GTEST_HAS_PARAM_TEST - value-parameterized tests // GTEST_HAS_TYPED_TEST - typed tests // GTEST_HAS_TYPED_TEST_P - type-parameterized tests -// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. +// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. Do not confuse with +// GTEST_HAS_POSIX_RE (see above) which users can +// define themselves. // GTEST_USES_SIMPLE_RE - our own simple regex is used; // the above two are mutually exclusive. // GTEST_CAN_COMPARE_NULL - accepts untyped NULL in EXPECT_EQ(). @@ -224,14 +235,17 @@ // // Template meta programming: // is_pointer - as in TR1; needed on Symbian and IBM XL C/C++ only. +// IteratorTraits - partial implementation of std::iterator_traits, which +// is not available in libCstd when compiled with Sun C++. // // Smart pointers: // scoped_ptr - as in TR2. // // Regular expressions: // RE - a simple regular expression class using the POSIX -// Extended Regular Expression syntax. Not available on -// Windows. +// Extended Regular Expression syntax on UNIX-like +// platforms, or a reduced regular exception syntax on +// other platforms, including Windows. // // Logging: // GTEST_LOG_() - logs messages at the specified severity level. @@ -264,12 +278,14 @@ // Int32FromGTestEnv() - parses an Int32 environment variable. // StringFromGTestEnv() - parses a string environment variable. -#include // For ptrdiff_t +#include // for isspace, etc +#include // for ptrdiff_t #include #include #include #ifndef _WIN32_WCE -#include +# include +# include #endif // !_WIN32_WCE #include // NOLINT @@ -286,118 +302,146 @@ // Determines the version of gcc that is used to compile this. #ifdef __GNUC__ // 40302 means version 4.3.2. -#define GTEST_GCC_VER_ \ +# define GTEST_GCC_VER_ \ (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) #endif // __GNUC__ // Determines the platform on which Google Test is compiled. #ifdef __CYGWIN__ -#define GTEST_OS_CYGWIN 1 +# define GTEST_OS_CYGWIN 1 #elif defined __SYMBIAN32__ -#define GTEST_OS_SYMBIAN 1 +# define GTEST_OS_SYMBIAN 1 #elif defined _WIN32 -#define GTEST_OS_WINDOWS 1 -#ifdef _WIN32_WCE -#define GTEST_OS_WINDOWS_MOBILE 1 -#elif defined(__MINGW__) || defined(__MINGW32__) -#define GTEST_OS_WINDOWS_MINGW 1 -#else -#define GTEST_OS_WINDOWS_DESKTOP 1 -#endif // _WIN32_WCE +# define GTEST_OS_WINDOWS 1 +# ifdef _WIN32_WCE +# define GTEST_OS_WINDOWS_MOBILE 1 +# elif defined(__MINGW__) || defined(__MINGW32__) +# define GTEST_OS_WINDOWS_MINGW 1 +# else +# define GTEST_OS_WINDOWS_DESKTOP 1 +# endif // _WIN32_WCE #elif defined __APPLE__ -#define GTEST_OS_MAC 1 +# define GTEST_OS_MAC 1 +# include +# if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR +# define GTEST_OS_MAC_IOS 1 +# endif #include #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR #define GTEST_OS_MAC_IOS 1 #endif #elif defined __linux__ -#define GTEST_OS_LINUX 1 +# define GTEST_OS_LINUX 1 +# ifdef ANDROID +# define GTEST_OS_LINUX_ANDROID 1 +# endif // ANDROID #elif defined __MVS__ -#define GTEST_OS_ZOS 1 +# define GTEST_OS_ZOS 1 #elif defined(__sun) && defined(__SVR4) -#define GTEST_OS_SOLARIS 1 +# define GTEST_OS_SOLARIS 1 #elif defined(_AIX) -#define GTEST_OS_AIX 1 +# define GTEST_OS_AIX 1 +#elif defined(__hpux) +# define GTEST_OS_HPUX 1 +#elif defined __native_client__ +# define GTEST_OS_NACL 1 #endif // __CYGWIN__ -#if (GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_SYMBIAN || \ - GTEST_OS_SOLARIS || GTEST_OS_AIX) && !defined(ANDROID) +// Brings in definitions for functions used in the testing::internal::posix +// namespace (read, write, close, chdir, isatty, stat). We do not currently +// use them on Windows Mobile. +#if !GTEST_OS_WINDOWS +// This assumes that non-Windows OSes provide unistd.h. For OSes where this +// is not the case, we need to include headers that provide the functions +// mentioned above. +# include +# if !GTEST_OS_NACL +// TODO(vladl@google.com): Remove this condition when Native Client SDK adds +// strings.h (tracked in +// http://code.google.com/p/nativeclient/issues/detail?id=1175). +# include // Native Client doesn't provide strings.h. +# endif +#elif !GTEST_OS_WINDOWS_MOBILE +# include +# include +#endif + +// Defines this to true iff Google Test can use POSIX regular expressions. +#ifndef GTEST_HAS_POSIX_RE +# define GTEST_HAS_POSIX_RE (!GTEST_OS_WINDOWS) +#endif + +#if GTEST_HAS_POSIX_RE // On some platforms, needs someone to define size_t, and // won't compile otherwise. We can #include it here as we already // included , which is guaranteed to define size_t through // . -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT +# include // NOLINT -#define GTEST_USES_POSIX_RE 1 +# define GTEST_USES_POSIX_RE 1 #elif GTEST_OS_WINDOWS -#if !GTEST_OS_WINDOWS_MOBILE -#include // NOLINT -#include // NOLINT -#endif - // is not available on Windows. Use our own simple regex // implementation instead. -#define GTEST_USES_SIMPLE_RE 1 +# define GTEST_USES_SIMPLE_RE 1 #else // may not be available on this platform. Use our own // simple regex implementation instead. -#define GTEST_USES_SIMPLE_RE 1 +# define GTEST_USES_SIMPLE_RE 1 -#endif // GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC || - // GTEST_OS_SYMBIAN || GTEST_OS_SOLARIS || GTEST_OS_AIX +#endif // GTEST_HAS_POSIX_RE #ifndef GTEST_HAS_EXCEPTIONS // The user didn't tell us whether exceptions are enabled, so we need // to figure it out. -#if defined(_MSC_VER) || defined(__BORLANDC__) +# if defined(_MSC_VER) || defined(__BORLANDC__) // MSVC's and C++Builder's implementations of the STL use the _HAS_EXCEPTIONS // macro to enable exceptions, so we'll do the same. // Assumes that exceptions are enabled by default. -#ifndef _HAS_EXCEPTIONS -#define _HAS_EXCEPTIONS 1 -#endif // _HAS_EXCEPTIONS -#define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS -#elif defined(__GNUC__) && __EXCEPTIONS +# ifndef _HAS_EXCEPTIONS +# define _HAS_EXCEPTIONS 1 +# endif // _HAS_EXCEPTIONS +# define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS +# elif defined(__GNUC__) && __EXCEPTIONS // gcc defines __EXCEPTIONS to 1 iff exceptions are enabled. -#define GTEST_HAS_EXCEPTIONS 1 -#elif defined(__SUNPRO_CC) +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__SUNPRO_CC) // Sun Pro CC supports exceptions. However, there is no compile-time way of // detecting whether they are enabled or not. Therefore, we assume that // they are enabled unless the user tells us otherwise. -#define GTEST_HAS_EXCEPTIONS 1 -#elif defined(__IBMCPP__) && __EXCEPTIONS +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__IBMCPP__) && __EXCEPTIONS // xlC defines __EXCEPTIONS to 1 iff exceptions are enabled. -#define GTEST_HAS_EXCEPTIONS 1 -#else +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__HP_aCC) +// Exception handling is in effect by default in HP aCC compiler. It has to +// be turned of by +noeh compiler option if desired. +# define GTEST_HAS_EXCEPTIONS 1 +# else // For other compilers, we assume exceptions are disabled to be // conservative. -#define GTEST_HAS_EXCEPTIONS 0 -#endif // defined(_MSC_VER) || defined(__BORLANDC__) +# define GTEST_HAS_EXCEPTIONS 0 +# endif // defined(_MSC_VER) || defined(__BORLANDC__) #endif // GTEST_HAS_EXCEPTIONS #if !defined(GTEST_HAS_STD_STRING) // Even though we don't use this macro any longer, we keep it in case // some clients still depend on it. -#define GTEST_HAS_STD_STRING 1 +# define GTEST_HAS_STD_STRING 1 #elif !GTEST_HAS_STD_STRING // The user told us that ::std::string isn't available. -#error "Google Test cannot be used where ::std::string isn't available." +# error "Google Test cannot be used where ::std::string isn't available." #endif // !defined(GTEST_HAS_STD_STRING) #ifndef GTEST_HAS_GLOBAL_STRING // The user didn't tell us whether ::string is available, so we need // to figure it out. -#define GTEST_HAS_GLOBAL_STRING 0 +# define GTEST_HAS_GLOBAL_STRING 0 #endif // GTEST_HAS_GLOBAL_STRING @@ -407,17 +451,18 @@ // TODO(wan@google.com): uses autoconf to detect whether ::std::wstring // is available. -// Cygwin 1.5 and below doesn't support ::std::wstring. -// Cygwin 1.7 might add wstring support; this should be updated when clear. -// Solaris' libc++ doesn't support it either. -#define GTEST_HAS_STD_WSTRING (!(GTEST_OS_CYGWIN || GTEST_OS_SOLARIS)) +// Cygwin 1.7 and below doesn't support ::std::wstring. +// Solaris' libc++ doesn't support it either. Android has +// no support for it at least as recent as Froyo (2.2). +# define GTEST_HAS_STD_WSTRING \ + (!(GTEST_OS_LINUX_ANDROID || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS)) #endif // GTEST_HAS_STD_WSTRING #ifndef GTEST_HAS_GLOBAL_WSTRING // The user didn't tell us whether ::wstring is available, so we need // to figure it out. -#define GTEST_HAS_GLOBAL_WSTRING \ +# define GTEST_HAS_GLOBAL_WSTRING \ (GTEST_HAS_STD_WSTRING && GTEST_HAS_GLOBAL_STRING) #endif // GTEST_HAS_GLOBAL_WSTRING @@ -426,46 +471,46 @@ // The user didn't tell us whether RTTI is enabled, so we need to // figure it out. -#ifdef _MSC_VER +# ifdef _MSC_VER -#ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled. -#define GTEST_HAS_RTTI 1 -#else -#define GTEST_HAS_RTTI 0 -#endif +# ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled. +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif // Starting with version 4.3.2, gcc defines __GXX_RTTI iff RTTI is enabled. -#elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40302) +# elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40302) -#ifdef __GXX_RTTI -#define GTEST_HAS_RTTI 1 -#else -#define GTEST_HAS_RTTI 0 -#endif // __GXX_RTTI +# ifdef __GXX_RTTI +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif // __GXX_RTTI // Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if // both the typeid and dynamic_cast features are present. -#elif defined(__IBMCPP__) && (__IBMCPP__ >= 900) +# elif defined(__IBMCPP__) && (__IBMCPP__ >= 900) -#ifdef __RTTI_ALL__ -#define GTEST_HAS_RTTI 1 -#else -#define GTEST_HAS_RTTI 0 -#endif +# ifdef __RTTI_ALL__ +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif -#else +# else // For all other compilers, we assume RTTI is enabled. -#define GTEST_HAS_RTTI 1 +# define GTEST_HAS_RTTI 1 -#endif // _MSC_VER +# endif // _MSC_VER #endif // GTEST_HAS_RTTI // It's this header's responsibility to #include when RTTI // is enabled. #if GTEST_HAS_RTTI -#include +# include #endif // Determines whether Google Test can use the pthreads library. @@ -475,15 +520,24 @@ // // To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0 // to your compiler flags. -#define GTEST_HAS_PTHREAD (GTEST_OS_LINUX || GTEST_OS_MAC) +# define GTEST_HAS_PTHREAD (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX) #endif // GTEST_HAS_PTHREAD +#if GTEST_HAS_PTHREAD +// gtest-port.h guarantees to #include when GTEST_HAS_PTHREAD is +// true. +# include // NOLINT + +// For timespec and nanosleep, used below. +# include // NOLINT +#endif + // Determines whether Google Test can use tr1/tuple. You can define // this macro to 0 to prevent Google Test from using tuple (any // feature depending on tuple with be disabled in this mode). #ifndef GTEST_HAS_TR1_TUPLE // The user didn't tell us not to do it, so we assume it's OK. -#define GTEST_HAS_TR1_TUPLE 1 +# define GTEST_HAS_TR1_TUPLE 1 #endif // GTEST_HAS_TR1_TUPLE // Determines whether Google Test's own tr1 tuple implementation @@ -498,12 +552,12 @@ // defining __GNUC__ and friends, but cannot compile GCC's tuple // implementation. MSVC 2008 (9.0) provides TR1 tuple in a 323 MB // Feature Pack download, which we cannot assume the user has. -#if (defined(__GNUC__) && !defined(__CUDACC__) && (GTEST_GCC_VER_ >= 40000)) \ +# if (defined(__GNUC__) && !defined(__CUDACC__) && (GTEST_GCC_VER_ >= 40000)) \ || _MSC_VER >= 1600 -#define GTEST_USE_OWN_TR1_TUPLE 0 -#else -#define GTEST_USE_OWN_TR1_TUPLE 1 -#endif +# define GTEST_USE_OWN_TR1_TUPLE 0 +# else +# define GTEST_USE_OWN_TR1_TUPLE 1 +# endif #endif // GTEST_USE_OWN_TR1_TUPLE @@ -512,7 +566,7 @@ // tr1/tuple. #if GTEST_HAS_TR1_TUPLE -#if GTEST_USE_OWN_TR1_TUPLE +# if GTEST_USE_OWN_TR1_TUPLE // This file was GENERATED by a script. DO NOT EDIT BY HAND!!! // Copyright 2009 Google Inc. @@ -559,9 +613,9 @@ // private as public. // Sun Studio versions < 12 also have the above bug. #if defined(__SYMBIAN32__) || (defined(__SUNPRO_CC) && __SUNPRO_CC < 0x590) -#define GTEST_DECLARE_TUPLE_AS_FRIEND_ public: +# define GTEST_DECLARE_TUPLE_AS_FRIEND_ public: #else -#define GTEST_DECLARE_TUPLE_AS_FRIEND_ \ +# define GTEST_DECLARE_TUPLE_AS_FRIEND_ \ template friend class tuple; \ private: #endif @@ -1481,45 +1535,45 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, #undef GTEST_TUPLE_ELEMENT_ #endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ -#elif GTEST_OS_SYMBIAN +# elif GTEST_OS_SYMBIAN // On Symbian, BOOST_HAS_TR1_TUPLE causes Boost's TR1 tuple library to // use STLport's tuple implementation, which unfortunately doesn't // work as the copy of STLport distributed with Symbian is incomplete. // By making sure BOOST_HAS_TR1_TUPLE is undefined, we force Boost to // use its own tuple implementation. -#ifdef BOOST_HAS_TR1_TUPLE -#undef BOOST_HAS_TR1_TUPLE -#endif // BOOST_HAS_TR1_TUPLE +# ifdef BOOST_HAS_TR1_TUPLE +# undef BOOST_HAS_TR1_TUPLE +# endif // BOOST_HAS_TR1_TUPLE // This prevents , which defines // BOOST_HAS_TR1_TUPLE, from being #included by Boost's . -#define BOOST_TR1_DETAIL_CONFIG_HPP_INCLUDED -#include +# define BOOST_TR1_DETAIL_CONFIG_HPP_INCLUDED +# include -#elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40000) +# elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40000) // GCC 4.0+ implements tr1/tuple in the header. This does // not conform to the TR1 spec, which requires the header to be . -#if !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 +# if !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 // Until version 4.3.2, gcc has a bug that causes , // which is #included by , to not compile when RTTI is // disabled. _TR1_FUNCTIONAL is the header guard for // . Hence the following #define is a hack to prevent // from being included. -#define _TR1_FUNCTIONAL 1 -#include -#undef _TR1_FUNCTIONAL // Allows the user to #include +# define _TR1_FUNCTIONAL 1 +# include +# undef _TR1_FUNCTIONAL // Allows the user to #include // if he chooses to. -#else -#include // NOLINT -#endif // !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 +# else +# include // NOLINT +# endif // !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 -#else +# else // If the compiler is not GCC 4.0+, we assume the user is using a // spec-conforming TR1 implementation. -#include // NOLINT -#endif // GTEST_USE_OWN_TR1_TUPLE +# include // NOLINT +# endif // GTEST_USE_OWN_TR1_TUPLE #endif // GTEST_HAS_TR1_TUPLE @@ -1530,19 +1584,25 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, #ifndef GTEST_HAS_CLONE // The user didn't tell us, so we need to figure it out. -#if GTEST_OS_LINUX && !defined(__ia64__) -#define GTEST_HAS_CLONE 1 -#else -#define GTEST_HAS_CLONE 0 -#endif // GTEST_OS_LINUX && !defined(__ia64__) +# if GTEST_OS_LINUX && !defined(__ia64__) +# define GTEST_HAS_CLONE 1 +# else +# define GTEST_HAS_CLONE 0 +# endif // GTEST_OS_LINUX && !defined(__ia64__) #endif // GTEST_HAS_CLONE // Determines whether to support stream redirection. This is used to test // output correctness and to implement death tests. -#if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_SYMBIAN -#define GTEST_HAS_STREAM_REDIRECTION_ 1 -#endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_SYMBIAN +#ifndef GTEST_HAS_STREAM_REDIRECTION +// By default, we assume that stream redirection is supported on all +// platforms except known mobile ones. +# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN +# define GTEST_HAS_STREAM_REDIRECTION 0 +# else +# define GTEST_HAS_STREAM_REDIRECTION 1 +# endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_SYMBIAN +#endif // GTEST_HAS_STREAM_REDIRECTION // Determines whether to support death tests. // Google Test does not support death tests for VC 7.1 and earlier as @@ -1550,9 +1610,9 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, // pops up a dialog window that cannot be suppressed programmatically. #if (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER >= 1400) || \ - GTEST_OS_WINDOWS_MINGW || GTEST_OS_AIX) -#define GTEST_HAS_DEATH_TEST 1 -#include // NOLINT + GTEST_OS_WINDOWS_MINGW || GTEST_OS_AIX || GTEST_OS_HPUX) +# define GTEST_HAS_DEATH_TEST 1 +# include // NOLINT #endif // We don't support MSVC 7.1 with exceptions disabled now. Therefore @@ -1563,11 +1623,11 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, // Determines whether to support type-driven tests. // Typed tests need and variadic macros, which GCC, VC++ 8.0, -// Sun Pro CC, and IBM Visual Age support. +// Sun Pro CC, IBM Visual Age, and HP aCC support. #if defined(__GNUC__) || (_MSC_VER >= 1400) || defined(__SUNPRO_CC) || \ - defined(__IBMCPP__) -#define GTEST_HAS_TYPED_TEST 1 -#define GTEST_HAS_TYPED_TEST_P 1 + defined(__IBMCPP__) || defined(__HP_aCC) +# define GTEST_HAS_TYPED_TEST 1 +# define GTEST_HAS_TYPED_TEST_P 1 #endif // Determines whether to support Combine(). This only makes sense when @@ -1575,13 +1635,18 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, // work on Sun Studio since it doesn't understand templated conversion // operators. #if GTEST_HAS_PARAM_TEST && GTEST_HAS_TR1_TUPLE && !defined(__SUNPRO_CC) -#define GTEST_HAS_COMBINE 1 +# define GTEST_HAS_COMBINE 1 #endif // Determines whether the system compiler uses UTF-16 for encoding wide strings. #define GTEST_WIDE_STRING_USES_UTF16_ \ (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_SYMBIAN || GTEST_OS_AIX) +// Determines whether test results can be streamed to a socket. +#if GTEST_OS_LINUX +# define GTEST_CAN_STREAM_RESULTS_ 1 +#endif + // Defines some utility macros. // The GNU compiler emits a warning if nested "if" statements are followed by @@ -1593,9 +1658,9 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, // // The "switch (0) case 0:" idiom is used to suppress this. #ifdef __INTEL_COMPILER -#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ +# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ #else -#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: // NOLINT +# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: default: // NOLINT #endif // Use this annotation at the end of a struct/class definition to @@ -1610,9 +1675,9 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, // Also use it after a variable or parameter declaration to tell the // compiler the variable/parameter does not have to be used. #if defined(__GNUC__) && !defined(COMPILER_ICC) -#define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) #else -#define GTEST_ATTRIBUTE_UNUSED_ +# define GTEST_ATTRIBUTE_UNUSED_ #endif // A macro to disallow operator= @@ -1632,9 +1697,9 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, // // Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_; #if defined(__GNUC__) && (GTEST_GCC_VER_ >= 30400) && !defined(COMPILER_ICC) -#define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result)) +# define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result)) #else -#define GTEST_MUST_USE_RESULT_ +# define GTEST_MUST_USE_RESULT_ #endif // __GNUC__ && (GTEST_GCC_VER_ >= 30400) && !COMPILER_ICC // Determine whether the compiler supports Microsoft's Structured Exception @@ -1643,31 +1708,35 @@ inline bool operator!=(const GTEST_10_TUPLE_(T)& t, #ifndef GTEST_HAS_SEH // The user didn't tell us, so we need to figure it out. -#if defined(_MSC_VER) || defined(__BORLANDC__) +# if defined(_MSC_VER) || defined(__BORLANDC__) // These two compilers are known to support SEH. -#define GTEST_HAS_SEH 1 -#else +# define GTEST_HAS_SEH 1 +# else // Assume no SEH. -#define GTEST_HAS_SEH 0 -#endif +# define GTEST_HAS_SEH 0 +# endif #endif // GTEST_HAS_SEH #ifdef _MSC_VER -#if GTEST_LINKED_AS_SHARED_LIBRARY -#define GTEST_API_ -#define GTEST_API_2 __declspec(dllimport) -#elif GTEST_CREATE_SHARED_LIBRARY -#define GTEST_API_ __declspec(dllexport) -#define GTEST_API_2 GTEST_API_ -#endif +# if GTEST_LINKED_AS_SHARED_LIBRARY +# define GTEST_API_ __declspec(dllimport) +# elif GTEST_CREATE_SHARED_LIBRARY +# define GTEST_API_ __declspec(dllexport) +# endif #endif // _MSC_VER #ifndef GTEST_API_ -#define GTEST_API_ -#define GTEST_API_2 +# define GTEST_API_ +#endif + +#ifdef __GNUC__ +// Ask the compiler to never inline a given function. +# define GTEST_NO_INLINE_ __attribute__((noinline)) +#else +# define GTEST_NO_INLINE_ #endif namespace testing { @@ -1678,7 +1747,90 @@ namespace internal { class String; -typedef ::std::stringstream StrStream; +// The GTEST_COMPILE_ASSERT_ macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// GTEST_COMPILE_ASSERT_(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// GTEST_COMPILE_ASSERT_(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +template +struct CompileAssert { +}; + +#define GTEST_COMPILE_ASSERT_(expr, msg) \ + typedef ::testing::internal::CompileAssert<(bool(expr))> \ + msg[bool(expr) ? 1 : -1] + +// Implementation details of GTEST_COMPILE_ASSERT_: +// +// - GTEST_COMPILE_ASSERT_ works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define GTEST_COMPILE_ASSERT_(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// GTEST_COMPILE_ASSERT_(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outter parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert +// +// instead, these compilers will refuse to compile +// +// GTEST_COMPILE_ASSERT_(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + +// StaticAssertTypeEqHelper is used by StaticAssertTypeEq defined in gtest.h. +// +// This template is declared, but intentionally undefined. +template +struct StaticAssertTypeEqHelper; + +template +struct StaticAssertTypeEqHelper {}; + +#if GTEST_HAS_GLOBAL_STRING +typedef ::string string; +#else +typedef ::std::string string; +#endif // GTEST_HAS_GLOBAL_STRING + +#if GTEST_HAS_GLOBAL_WSTRING +typedef ::wstring wstring; +#elif GTEST_HAS_STD_WSTRING +typedef ::std::wstring wstring; +#endif // GTEST_HAS_GLOBAL_WSTRING // A helper for suppressing warnings on constant condition. It just // returns 'condition'. @@ -1734,7 +1886,9 @@ class GTEST_API_ RE { RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT #if GTEST_HAS_GLOBAL_STRING + RE(const ::string& regex) { Init(regex.c_str()); } // NOLINT + #endif // GTEST_HAS_GLOBAL_STRING RE(const char* regex) { Init(regex); } // NOLINT @@ -1758,12 +1912,14 @@ class GTEST_API_ RE { } #if GTEST_HAS_GLOBAL_STRING + static bool FullMatch(const ::string& str, const RE& re) { return FullMatch(str.c_str(), re); } static bool PartialMatch(const ::string& str, const RE& re) { return PartialMatch(str.c_str(), re); } + #endif // GTEST_HAS_GLOBAL_STRING static bool FullMatch(const char* str, const RE& re); @@ -1778,16 +1934,31 @@ class GTEST_API_ RE { // files. const char* pattern_; bool is_valid_; + #if GTEST_USES_POSIX_RE + regex_t full_regex_; // For FullMatch(). regex_t partial_regex_; // For PartialMatch(). + #else // GTEST_USES_SIMPLE_RE + const char* full_pattern_; // For FullMatch(); + #endif GTEST_DISALLOW_ASSIGN_(RE); }; +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line); + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file, + int line); + // Defines logging utilities: // GTEST_LOG_(severity) - logs messages at the specified severity level. The // message itself is streamed into the macro. @@ -1859,6 +2030,66 @@ inline void FlushInfoLog() { fflush(NULL); } // INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. // +// Use ImplicitCast_ as a safe version of static_cast for upcasting in +// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a +// const Foo*). When you use ImplicitCast_, the compiler checks that +// the cast is safe. Such explicit ImplicitCast_s are necessary in +// surprisingly many situations where C++ demands an exact type match +// instead of an argument type convertable to a target type. +// +// The syntax for using ImplicitCast_ is the same as for static_cast: +// +// ImplicitCast_(expr) +// +// ImplicitCast_ would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., implicit_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template +inline To ImplicitCast_(To x) { return x; } + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use ImplicitCast_<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., down_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template // use like this: DownCast_(foo); +inline To DownCast_(From* f) { // so we only accept pointers + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + if (false) { + const To to = NULL; + ::testing::internal::ImplicitCast_(to); + } + +#if GTEST_HAS_RTTI + // RTTI: debug mode only! + GTEST_CHECK_(f == NULL || dynamic_cast(f) != NULL); +#endif + return static_cast(f); +} + // Downcasts the pointer of type Base to Derived. // Derived must be a subclass of Base. The parameter MUST // point to a class of type Derived, not any subclass of it. @@ -1874,7 +2105,7 @@ Derived* CheckedDowncastToActualType(Base* base) { #endif } -#if GTEST_HAS_STREAM_REDIRECTION_ +#if GTEST_HAS_STREAM_REDIRECTION // Defines the stderr capturer: // CaptureStdout - starts capturing stdout. @@ -1887,7 +2118,7 @@ GTEST_API_ String GetCapturedStdout(); GTEST_API_ void CaptureStderr(); GTEST_API_ String GetCapturedStderr(); -#endif // GTEST_HAS_STREAM_REDIRECTION_ +#endif // GTEST_HAS_STREAM_REDIRECTION #if GTEST_HAS_DEATH_TEST @@ -2021,10 +2252,6 @@ class ThreadWithParam : public ThreadWithParamBase { GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); }; -// gtest-port.h guarantees to #include when GTEST_HAS_PTHREAD is -// true. -#include - // MutexBase and Mutex implement mutex on pthreads-based platforms. They // are used in conjunction with class MutexLock: // @@ -2079,11 +2306,11 @@ class MutexBase { }; // Forward-declares a static mutex. -#define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ extern ::testing::internal::MutexBase mutex // Defines and statically (i.e. at link time) initializes a static mutex. -#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ ::testing::internal::MutexBase mutex = { PTHREAD_MUTEX_INITIALIZER, 0 } // The Mutex class can only be used for mutexes created at runtime. It @@ -2230,7 +2457,7 @@ class ThreadLocal { GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); }; -#define GTEST_IS_THREADSAFE 1 +# define GTEST_IS_THREADSAFE 1 #else // GTEST_HAS_PTHREAD @@ -2245,10 +2472,10 @@ class Mutex { void AssertHeld() const {} }; -#define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ extern ::testing::internal::Mutex mutex -#define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex class GTestMutexLock { public: @@ -2272,7 +2499,7 @@ class ThreadLocal { // The above synchronization primitives have dummy implementations. // Therefore Google Test is not thread-safe. -#define GTEST_IS_THREADSAFE 0 +# define GTEST_IS_THREADSAFE 0 #endif // GTEST_HAS_PTHREAD @@ -2289,9 +2516,9 @@ GTEST_API_ size_t GetThreadCount(); #if defined(__SYMBIAN32__) || defined(__IBMCPP__) || defined(__SUNPRO_CC) // We lose support for NULL detection where the compiler doesn't like // passing non-POD classes through ellipsis (...). -#define GTEST_ELLIPSIS_NEEDS_POD_ 1 +# define GTEST_ELLIPSIS_NEEDS_POD_ 1 #else -#define GTEST_CAN_COMPARE_NULL 1 +# define GTEST_CAN_COMPARE_NULL 1 #endif // The Nokia Symbian and IBM XL C/C++ compilers cannot decide between @@ -2299,7 +2526,7 @@ GTEST_API_ size_t GetThreadCount(); // _can_ decide between class template specializations for T and T*, // so a tr1::type_traits-like is_pointer works. #if defined(__SYMBIAN32__) || defined(__IBMCPP__) -#define GTEST_NEEDS_IS_POINTER_ 1 +# define GTEST_NEEDS_IS_POINTER_ 1 #endif template @@ -2318,17 +2545,68 @@ struct is_pointer : public false_type {}; template struct is_pointer : public true_type {}; +template +struct IteratorTraits { + typedef typename Iterator::value_type value_type; +}; + +template +struct IteratorTraits { + typedef T value_type; +}; + +template +struct IteratorTraits { + typedef T value_type; +}; + #if GTEST_OS_WINDOWS -#define GTEST_PATH_SEP_ "\\" -#define GTEST_HAS_ALT_PATH_SEP_ 1 +# define GTEST_PATH_SEP_ "\\" +# define GTEST_HAS_ALT_PATH_SEP_ 1 // The biggest signed integer type the compiler supports. typedef __int64 BiggestInt; #else -#define GTEST_PATH_SEP_ "/" -#define GTEST_HAS_ALT_PATH_SEP_ 0 +# define GTEST_PATH_SEP_ "/" +# define GTEST_HAS_ALT_PATH_SEP_ 0 typedef long long BiggestInt; // NOLINT #endif // GTEST_OS_WINDOWS +// Utilities for char. + +// isspace(int ch) and friends accept an unsigned char or EOF. char +// may be signed, depending on the compiler (or compiler flags). +// Therefore we need to cast a char to unsigned char before calling +// isspace(), etc. + +inline bool IsAlpha(char ch) { + return isalpha(static_cast(ch)) != 0; +} +inline bool IsAlNum(char ch) { + return isalnum(static_cast(ch)) != 0; +} +inline bool IsDigit(char ch) { + return isdigit(static_cast(ch)) != 0; +} +inline bool IsLower(char ch) { + return islower(static_cast(ch)) != 0; +} +inline bool IsSpace(char ch) { + return isspace(static_cast(ch)) != 0; +} +inline bool IsUpper(char ch) { + return isupper(static_cast(ch)) != 0; +} +inline bool IsXDigit(char ch) { + return isxdigit(static_cast(ch)) != 0; +} + +inline char ToLower(char ch) { + return static_cast(tolower(static_cast(ch))); +} +inline char ToUpper(char ch) { + return static_cast(toupper(static_cast(ch))); +} + // The testing::internal::posix namespace holds wrappers for common // POSIX functions. These wrappers hide the differences between // Windows/MSVC and POSIX systems. Since some compilers define these @@ -2343,36 +2621,36 @@ namespace posix { typedef struct _stat StatStruct; -#ifdef __BORLANDC__ +# ifdef __BORLANDC__ inline int IsATTY(int fd) { return isatty(fd); } inline int StrCaseCmp(const char* s1, const char* s2) { return stricmp(s1, s2); } inline char* StrDup(const char* src) { return strdup(src); } -#else // !__BORLANDC__ -#if GTEST_OS_WINDOWS_MOBILE +# else // !__BORLANDC__ +# if GTEST_OS_WINDOWS_MOBILE inline int IsATTY(int /* fd */) { return 0; } -#else +# else inline int IsATTY(int fd) { return _isatty(fd); } -#endif // GTEST_OS_WINDOWS_MOBILE +# endif // GTEST_OS_WINDOWS_MOBILE inline int StrCaseCmp(const char* s1, const char* s2) { return _stricmp(s1, s2); } inline char* StrDup(const char* src) { return _strdup(src); } -#endif // __BORLANDC__ +# endif // __BORLANDC__ -#if GTEST_OS_WINDOWS_MOBILE +# if GTEST_OS_WINDOWS_MOBILE inline int FileNo(FILE* file) { return reinterpret_cast(_fileno(file)); } // Stat(), RmDir(), and IsDir() are not needed on Windows CE at this // time and thus not defined there. -#else +# else inline int FileNo(FILE* file) { return _fileno(file); } inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); } inline int RmDir(const char* dir) { return _rmdir(dir); } inline bool IsDir(const StatStruct& st) { return (_S_IFDIR & st.st_mode) != 0; } -#endif // GTEST_OS_WINDOWS_MOBILE +# endif // GTEST_OS_WINDOWS_MOBILE #else @@ -2394,8 +2672,8 @@ inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } #ifdef _MSC_VER // Temporarily disable warning 4996 (deprecated function). -#pragma warning(push) -#pragma warning(disable:4996) +# pragma warning(push) +# pragma warning(disable:4996) #endif inline const char* StrNCpy(char* dest, const char* src, size_t n) { @@ -2444,7 +2722,7 @@ inline const char* GetEnv(const char* name) { } #ifdef _MSC_VER -#pragma warning(pop) // Restores the warning state. +# pragma warning(pop) // Restores the warning state. #endif #if GTEST_OS_WINDOWS_MOBILE @@ -2510,6 +2788,7 @@ class TypeWithSize<4> { template <> class TypeWithSize<8> { public: + #if GTEST_OS_WINDOWS typedef __int64 Int; typedef unsigned __int64 UInt; @@ -2566,10 +2845,10 @@ const char* StringFromGTestEnv(const char* flag, const char* default_val); #endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ #if GTEST_OS_LINUX -#include -#include -#include -#include +# include +# include +# include +# include #endif // GTEST_OS_LINUX #include @@ -2623,7 +2902,7 @@ const char* StringFromGTestEnv(const char* flag, const char* default_val); #ifdef __BORLANDC__ // string.h is not guaranteed to provide strcpy on C++ Builder. -#include +# include #endif #include @@ -2875,7 +3154,7 @@ class GTEST_API_ String { private: // Constructs a non-NULL String from the given content. This - // function can only be called when data_ has not been allocated. + // function can only be called when c_str_ has not been allocated. // ConstructNonNull(NULL, 0) results in an empty string (""). // ConstructNonNull(NULL, non_zero) is undefined behavior. void ConstructNonNull(const char* buffer, size_t a_length) { @@ -2908,9 +3187,9 @@ inline ::std::ostream& operator<<(::std::ostream& os, const String& str) { return os; } -// Gets the content of the StrStream's buffer as a String. Each '\0' +// Gets the content of the stringstream's buffer as a String. Each '\0' // character in the buffer is replaced with "\\0". -GTEST_API_ String StrStreamToString(StrStream* stream); +GTEST_API_ String StringStreamToString(::std::stringstream* stream); // Converts a streamable value to a String. A NULL pointer is // converted to "(null)". When the input value is a ::string, @@ -3183,17 +3462,49 @@ class GTEST_API_ FilePath { #define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ -#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P - // #ifdef __GNUC__ is too general here. It is possible to use gcc without using // libstdc++ (which is where cxxabi.h comes from). -#ifdef __GLIBCXX__ -#include -#endif // __GLIBCXX__ +# ifdef __GLIBCXX__ +# include +# elif defined(__HP_aCC) +# include +# endif // __GLIBCXX__ namespace testing { namespace internal { +// GetTypeName() returns a human-readable name of type T. +// NB: This function is also used in Google Mock, so don't move it inside of +// the typed-test-only section below. +template +String GetTypeName() { +# if GTEST_HAS_RTTI + + const char* const name = typeid(T).name(); +# if defined(__GLIBCXX__) || defined(__HP_aCC) + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. +# ifdef __GLIBCXX__ + using abi::__cxa_demangle; +# endif // __GLIBCXX__ + char* const readable_name = __cxa_demangle(name, 0, 0, &status); + const String name_str(status == 0 ? readable_name : name); + free(readable_name); + return name_str; +# else + return name; +# endif // __GLIBCXX__ || __HP_aCC + +# else + + return ""; + +# endif // GTEST_HAS_RTTI +} + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + // AssertyTypeEq::type is defined iff T1 and T2 are the same // type. This can be used as a compile-time assertion to ensure that // two types are equal. @@ -3206,29 +3517,6 @@ struct AssertTypeEq { typedef bool type; }; -// GetTypeName() returns a human-readable name of type T. -template -String GetTypeName() { -#if GTEST_HAS_RTTI - - const char* const name = typeid(T).name(); -#ifdef __GLIBCXX__ - int status = 0; - // gcc's implementation of typeid(T).name() mangles the type name, - // so we have to demangle it. - char* const readable_name = abi::__cxa_demangle(name, 0, 0, &status); - const String name_str(status == 0 ? readable_name : name); - free(readable_name); - return name_str; -#else - return name; -#endif // __GLIBCXX__ - -#else - return ""; -#endif // GTEST_HAS_RTTI -} - // A unique type used as the default value for the arguments of class // template Types. This allows us to simulate variadic templates // (e.g. Types, Type, and etc), which C++ doesn't @@ -4747,7 +5035,7 @@ struct Types class +# define GTEST_TEMPLATE_ template class // The template "selector" struct TemplateSel is used to // represent Tmpl, which must be a class template with one type @@ -4765,7 +5053,7 @@ struct TemplateSel { }; }; -#define GTEST_BIND_(TmplSel, T) \ +# define GTEST_BIND_(TmplSel, T) \ TmplSel::template Bind::type // A unique struct template used as the default value for the @@ -6449,11 +6737,11 @@ struct TypeList::type type; }; +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + } // namespace internal } // namespace testing -#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P - #endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ // Due to C++ preprocessor weirdness, we need double indirection to @@ -6497,9 +6785,12 @@ inline void GTestStreamToHelper(std::ostream* os, const T& val) { *os << val; } +class ProtocolMessage; +namespace proto2 { class Message; } + namespace testing { -// Forward declaration of classes. +// Forward declarations. class AssertionResult; // Result of an assertion. class Message; // Represents a failure message. @@ -6508,6 +6799,9 @@ class TestInfo; // Information about a test. class TestPartResult; // Result of a test part. class UnitTest; // A collection of test cases. +template +::std::string PrintToString(const T& value); + namespace internal { struct TraceInfo; // Information about a trace point. @@ -6550,9 +6844,9 @@ char (&IsNullLiteralHelper(...))[2]; // NOLINT #ifdef GTEST_ELLIPSIS_NEEDS_POD_ // We lose support for NULL detection where the compiler doesn't like // passing non-POD classes through ellipsis (...). -#define GTEST_IS_NULL_LITERAL_(x) false +# define GTEST_IS_NULL_LITERAL_(x) false #else -#define GTEST_IS_NULL_LITERAL_(x) \ +# define GTEST_IS_NULL_LITERAL_(x) \ (sizeof(::testing::internal::IsNullLiteralHelper(x)) == 1) #endif // GTEST_ELLIPSIS_NEEDS_POD_ @@ -6589,72 +6883,32 @@ class GTEST_API_ ScopedTrace { template String StreamableToString(const T& streamable); -// Formats a value to be used in a failure message. - -#ifdef GTEST_NEEDS_IS_POINTER_ - -// These are needed as the Nokia Symbian and IBM XL C/C++ compilers -// cannot decide between const T& and const T* in a function template. -// These compilers _can_ decide between class template specializations -// for T and T*, so a tr1::type_traits-like is_pointer works, and we -// can overload on that. - -// This overload makes sure that all pointers (including -// those to char or wchar_t) are printed as raw pointers. -template -inline String FormatValueForFailureMessage(internal::true_type /*dummy*/, - T* pointer) { - return StreamableToString(static_cast(pointer)); -} - -template -inline String FormatValueForFailureMessage(internal::false_type /*dummy*/, - const T& value) { - return StreamableToString(value); -} - -template -inline String FormatForFailureMessage(const T& value) { - return FormatValueForFailureMessage( - typename internal::is_pointer::type(), value); -} - +// The Symbian compiler has a bug that prevents it from selecting the +// correct overload of FormatForComparisonFailureMessage (see below) +// unless we pass the first argument by reference. If we do that, +// however, Visual Age C++ 10.1 generates a compiler error. Therefore +// we only apply the work-around for Symbian. +#if defined(__SYMBIAN32__) +# define GTEST_CREF_WORKAROUND_ const& #else +# define GTEST_CREF_WORKAROUND_ +#endif -// These are needed as the above solution using is_pointer has the -// limitation that T cannot be a type without external linkage, when -// compiled using MSVC. - -template -inline String FormatForFailureMessage(const T& value) { - return StreamableToString(value); -} - -// This overload makes sure that all pointers (including -// those to char or wchar_t) are printed as raw pointers. -template -inline String FormatForFailureMessage(T* pointer) { - return StreamableToString(static_cast(pointer)); -} - -#endif // GTEST_NEEDS_IS_POINTER_ - -// These overloaded versions handle narrow and wide characters. -GTEST_API_ String FormatForFailureMessage(char ch); -GTEST_API_ String FormatForFailureMessage(wchar_t wchar); - -// When this operand is a const char* or char*, and the other operand +// When this operand is a const char* or char*, if the other operand // is a ::std::string or ::string, we print this operand as a C string -// rather than a pointer. We do the same for wide strings. +// rather than a pointer (we do the same for wide strings); otherwise +// we print it as a pointer to be safe. // This internal macro is used to avoid duplicated code. #define GTEST_FORMAT_IMPL_(operand2_type, operand1_printer)\ inline String FormatForComparisonFailureMessage(\ - operand2_type::value_type* str, const operand2_type& /*operand2*/) {\ + operand2_type::value_type* GTEST_CREF_WORKAROUND_ str, \ + const operand2_type& /*operand2*/) {\ return operand1_printer(str);\ }\ inline String FormatForComparisonFailureMessage(\ - const operand2_type::value_type* str, const operand2_type& /*operand2*/) {\ + const operand2_type::value_type* GTEST_CREF_WORKAROUND_ str, \ + const operand2_type& /*operand2*/) {\ return operand1_printer(str);\ } @@ -6672,6 +6926,24 @@ GTEST_FORMAT_IMPL_(::wstring, String::ShowWideCStringQuoted) #undef GTEST_FORMAT_IMPL_ +// The next four overloads handle the case where the operand being +// printed is a char/wchar_t pointer and the other operand is not a +// string/wstring object. In such cases, we just print the operand as +// a pointer to be safe. +#define GTEST_FORMAT_CHAR_PTR_IMPL_(CharType) \ + template \ + String FormatForComparisonFailureMessage(CharType* GTEST_CREF_WORKAROUND_ p, \ + const T&) { \ + return PrintToString(static_cast(p)); \ + } + +GTEST_FORMAT_CHAR_PTR_IMPL_(char) +GTEST_FORMAT_CHAR_PTR_IMPL_(const char) +GTEST_FORMAT_CHAR_PTR_IMPL_(wchar_t) +GTEST_FORMAT_CHAR_PTR_IMPL_(const wchar_t) + +#undef GTEST_FORMAT_CHAR_PTR_IMPL_ + // Constructs and returns the message for an equality assertion // (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. // @@ -6958,20 +7230,6 @@ GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr, #endif // GTEST_OS_WINDOWS -// Formats a source file path and a line number as they would appear -// in a compiler error message. -inline String FormatFileLocation(const char* file, int line) { - const char* const file_name = file == NULL ? "unknown file" : file; - if (line < 0) { - return String::Format("%s:", file_name); - } -#ifdef _MSC_VER - return String::Format("%s(%d):", file_name, line); -#else - return String::Format("%s:%d:", file_name, line); -#endif // _MSC_VER -} - // Types of SetUpTestCase() and TearDownTestCase() functions. typedef void (*SetUpTestCaseFunc)(); typedef void (*TearDownTestCaseFunc)(); @@ -6983,10 +7241,10 @@ typedef void (*TearDownTestCaseFunc)(); // // test_case_name: name of the test case // name: name of the test -// test_case_comment: a comment on the test case that will be included in -// the test output -// comment: a comment on the test that will be included in the -// test output +// type_param the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param text representation of the test's value parameter, +// or NULL if this is not a type-parameterized test. // fixture_class_id: ID of the test fixture class // set_up_tc: pointer to the function that sets up the test case // tear_down_tc: pointer to the function that tears down the test case @@ -6995,7 +7253,8 @@ typedef void (*TearDownTestCaseFunc)(); // ownership of the factory object. GTEST_API_ TestInfo* MakeAndRegisterTestInfo( const char* test_case_name, const char* name, - const char* test_case_comment, const char* comment, + const char* type_param, + const char* value_param, TypeId fixture_class_id, SetUpTestCaseFunc set_up_tc, TearDownTestCaseFunc tear_down_tc, @@ -7004,7 +7263,7 @@ GTEST_API_ TestInfo* MakeAndRegisterTestInfo( // If *pstr starts with the given prefix, modifies *pstr to be right // past the prefix and returns true; otherwise leaves *pstr unchanged // and returns false. None of pstr, *pstr, and prefix can be NULL. -bool SkipPrefix(const char* prefix, const char** pstr); +GTEST_API_ bool SkipPrefix(const char* prefix, const char** pstr); #if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P @@ -7047,7 +7306,7 @@ inline const char* SkipComma(const char* str) { if (comma == NULL) { return NULL; } - while (isspace(*(++comma))) {} + while (IsSpace(*(++comma))) {} return comma; } @@ -7084,8 +7343,8 @@ class TypeParameterizedTest { String::Format("%s%s%s/%d", prefix, prefix[0] == '\0' ? "" : "/", case_name, index).c_str(), GetPrefixUntilComma(test_names).c_str(), - String::Format("TypeParam = %s", GetTypeName().c_str()).c_str(), - "", + GetTypeName().c_str(), + NULL, // No value parameter. GetTypeId(), TestClass::SetUpTestCase, TestClass::TearDownTestCase, @@ -7162,6 +7421,15 @@ GTEST_API_ bool AlwaysTrue(); // Always returns false. inline bool AlwaysFalse() { return !AlwaysTrue(); } +// Helper for suppressing false warning from Clang on a const char* +// variable declared in a conditional expression always being NULL in +// the else branch. +struct GTEST_API_ ConstCharPtr { + ConstCharPtr(const char* str) : value(str) {} + operator bool() const { return true; } + const char* value; +}; + // A simple Linear Congruential Generator for generating random // numbers with a uniform distribution. Unlike rand() and srand(), it // doesn't use global state (and therefore can't interfere with user @@ -7184,13 +7452,338 @@ class GTEST_API_ Random { GTEST_DISALLOW_COPY_AND_ASSIGN_(Random); }; +// Defining a variable of type CompileAssertTypesEqual will cause a +// compiler error iff T1 and T2 are different types. +template +struct CompileAssertTypesEqual; + +template +struct CompileAssertTypesEqual { +}; + +// Removes the reference from a type if it is a reference type, +// otherwise leaves it unchanged. This is the same as +// tr1::remove_reference, which is not widely available yet. +template +struct RemoveReference { typedef T type; }; // NOLINT +template +struct RemoveReference { typedef T type; }; // NOLINT + +// A handy wrapper around RemoveReference that works when the argument +// T depends on template parameters. +#define GTEST_REMOVE_REFERENCE_(T) \ + typename ::testing::internal::RemoveReference::type + +// Removes const from a type if it is a const type, otherwise leaves +// it unchanged. This is the same as tr1::remove_const, which is not +// widely available yet. +template +struct RemoveConst { typedef T type; }; // NOLINT +template +struct RemoveConst { typedef T type; }; // NOLINT + +// MSVC 8.0, Sun C++, and IBM XL C++ have a bug which causes the above +// definition to fail to remove the const in 'const int[3]' and 'const +// char[3][4]'. The following specialization works around the bug. +// However, it causes trouble with GCC and thus needs to be +// conditionally compiled. +#if defined(_MSC_VER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) +template +struct RemoveConst { + typedef typename RemoveConst::type type[N]; +}; +#endif + +// A handy wrapper around RemoveConst that works when the argument +// T depends on template parameters. +#define GTEST_REMOVE_CONST_(T) \ + typename ::testing::internal::RemoveConst::type + +// Turns const U&, U&, const U, and U all into U. +#define GTEST_REMOVE_REFERENCE_AND_CONST_(T) \ + GTEST_REMOVE_CONST_(GTEST_REMOVE_REFERENCE_(T)) + +// Adds reference to a type if it is not a reference type, +// otherwise leaves it unchanged. This is the same as +// tr1::add_reference, which is not widely available yet. +template +struct AddReference { typedef T& type; }; // NOLINT +template +struct AddReference { typedef T& type; }; // NOLINT + +// A handy wrapper around AddReference that works when the argument T +// depends on template parameters. +#define GTEST_ADD_REFERENCE_(T) \ + typename ::testing::internal::AddReference::type + +// Adds a reference to const on top of T as necessary. For example, +// it transforms +// +// char ==> const char& +// const char ==> const char& +// char& ==> const char& +// const char& ==> const char& +// +// The argument T must depend on some template parameters. +#define GTEST_REFERENCE_TO_CONST_(T) \ + GTEST_ADD_REFERENCE_(const GTEST_REMOVE_REFERENCE_(T)) + +// ImplicitlyConvertible::value is a compile-time bool +// constant that's true iff type From can be implicitly converted to +// type To. +template +class ImplicitlyConvertible { + private: + // We need the following helper functions only for their types. + // They have no implementations. + + // MakeFrom() is an expression whose type is From. We cannot simply + // use From(), as the type From may not have a public default + // constructor. + static From MakeFrom(); + + // These two functions are overloaded. Given an expression + // Helper(x), the compiler will pick the first version if x can be + // implicitly converted to type To; otherwise it will pick the + // second version. + // + // The first version returns a value of size 1, and the second + // version returns a value of size 2. Therefore, by checking the + // size of Helper(x), which can be done at compile time, we can tell + // which version of Helper() is used, and hence whether x can be + // implicitly converted to type To. + static char Helper(To); + static char (&Helper(...))[2]; // NOLINT + + // We have to put the 'public' section after the 'private' section, + // or MSVC refuses to compile the code. + public: + // MSVC warns about implicitly converting from double to int for + // possible loss of data, so we need to temporarily disable the + // warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4244) // Temporarily disables warning 4244. + + static const bool value = + sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; +# pragma warning(pop) // Restores the warning state. +#elif defined(__BORLANDC__) + // C++Builder cannot use member overload resolution during template + // instantiation. The simplest workaround is to use its C++0x type traits + // functions (C++Builder 2009 and above only). + static const bool value = __is_convertible(From, To); +#else + static const bool value = + sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; +#endif // _MSV_VER +}; +template +const bool ImplicitlyConvertible::value; + +// IsAProtocolMessage::value is a compile-time bool constant that's +// true iff T is type ProtocolMessage, proto2::Message, or a subclass +// of those. +template +struct IsAProtocolMessage + : public bool_constant< + ImplicitlyConvertible::value || + ImplicitlyConvertible::value> { +}; + +// When the compiler sees expression IsContainerTest(0), if C is an +// STL-style container class, the first overload of IsContainerTest +// will be viable (since both C::iterator* and C::const_iterator* are +// valid types and NULL can be implicitly converted to them). It will +// be picked over the second overload as 'int' is a perfect match for +// the type of argument 0. If C::iterator or C::const_iterator is not +// a valid type, the first overload is not viable, and the second +// overload will be picked. Therefore, we can determine whether C is +// a container class by checking the type of IsContainerTest(0). +// The value of the expression is insignificant. +// +// Note that we look for both C::iterator and C::const_iterator. The +// reason is that C++ injects the name of a class as a member of the +// class itself (e.g. you can refer to class iterator as either +// 'iterator' or 'iterator::iterator'). If we look for C::iterator +// only, for example, we would mistakenly think that a class named +// iterator is an STL container. +// +// Also note that the simpler approach of overloading +// IsContainerTest(typename C::const_iterator*) and +// IsContainerTest(...) doesn't work with Visual Age C++ and Sun C++. +typedef int IsContainer; +template +IsContainer IsContainerTest(int /* dummy */, + typename C::iterator* /* it */ = NULL, + typename C::const_iterator* /* const_it */ = NULL) { + return 0; +} + +typedef char IsNotContainer; +template +IsNotContainer IsContainerTest(long /* dummy */) { return '\0'; } + +// EnableIf::type is void when 'Cond' is true, and +// undefined when 'Cond' is false. To use SFINAE to make a function +// overload only apply when a particular expression is true, add +// "typename EnableIf::type* = 0" as the last parameter. +template struct EnableIf; +template<> struct EnableIf { typedef void type; }; // NOLINT + +// Utilities for native arrays. + +// ArrayEq() compares two k-dimensional native arrays using the +// elements' operator==, where k can be any integer >= 0. When k is +// 0, ArrayEq() degenerates into comparing a single pair of values. + +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs); + +// This generic version is used when k is 0. +template +inline bool ArrayEq(const T& lhs, const U& rhs) { return lhs == rhs; } + +// This overload is used when k >= 1. +template +inline bool ArrayEq(const T(&lhs)[N], const U(&rhs)[N]) { + return internal::ArrayEq(lhs, N, rhs); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous ArrayEq() function, arrays with different sizes would +// lead to different copies of the template code. +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs) { + for (size_t i = 0; i != size; i++) { + if (!internal::ArrayEq(lhs[i], rhs[i])) + return false; + } + return true; +} + +// Finds the first element in the iterator range [begin, end) that +// equals elem. Element may be a native array type itself. +template +Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) { + for (Iter it = begin; it != end; ++it) { + if (internal::ArrayEq(*it, elem)) + return it; + } + return end; +} + +// CopyArray() copies a k-dimensional native array using the elements' +// operator=, where k can be any integer >= 0. When k is 0, +// CopyArray() degenerates into copying a single value. + +template +void CopyArray(const T* from, size_t size, U* to); + +// This generic version is used when k is 0. +template +inline void CopyArray(const T& from, U* to) { *to = from; } + +// This overload is used when k >= 1. +template +inline void CopyArray(const T(&from)[N], U(*to)[N]) { + internal::CopyArray(from, N, *to); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous CopyArray() function, arrays with different sizes +// would lead to different copies of the template code. +template +void CopyArray(const T* from, size_t size, U* to) { + for (size_t i = 0; i != size; i++) { + internal::CopyArray(from[i], to + i); + } +} + +// The relation between an NativeArray object (see below) and the +// native array it represents. +enum RelationToSource { + kReference, // The NativeArray references the native array. + kCopy // The NativeArray makes a copy of the native array and + // owns the copy. +}; + +// Adapts a native array to a read-only STL-style container. Instead +// of the complete STL container concept, this adaptor only implements +// members useful for Google Mock's container matchers. New members +// should be added as needed. To simplify the implementation, we only +// support Element being a raw type (i.e. having no top-level const or +// reference modifier). It's the client's responsibility to satisfy +// this requirement. Element can be an array type itself (hence +// multi-dimensional arrays are supported). +template +class NativeArray { + public: + // STL-style container typedefs. + typedef Element value_type; + typedef Element* iterator; + typedef const Element* const_iterator; + + // Constructs from a native array. + NativeArray(const Element* array, size_t count, RelationToSource relation) { + Init(array, count, relation); + } + + // Copy constructor. + NativeArray(const NativeArray& rhs) { + Init(rhs.array_, rhs.size_, rhs.relation_to_source_); + } + + ~NativeArray() { + // Ensures that the user doesn't instantiate NativeArray with a + // const or reference type. + static_cast(StaticAssertTypeEqHelper()); + if (relation_to_source_ == kCopy) + delete[] array_; + } + + // STL-style container methods. + size_t size() const { return size_; } + const_iterator begin() const { return array_; } + const_iterator end() const { return array_ + size_; } + bool operator==(const NativeArray& rhs) const { + return size() == rhs.size() && + ArrayEq(begin(), size(), rhs.begin()); + } + + private: + // Initializes this object; makes a copy of the input array if + // 'relation' is kCopy. + void Init(const Element* array, size_t a_size, RelationToSource relation) { + if (relation == kReference) { + array_ = array; + } else { + Element* const copy = new Element[a_size]; + CopyArray(array, a_size, copy); + array_ = copy; + } + size_ = a_size; + relation_to_source_ = relation; + } + + const Element* array_; + size_t size_; + RelationToSource relation_to_source_; + + GTEST_DISALLOW_ASSIGN_(NativeArray); +}; + } // namespace internal } // namespace testing -#define GTEST_MESSAGE_(message, result_type) \ - ::testing::internal::AssertHelper(result_type, __FILE__, __LINE__, message) \ +#define GTEST_MESSAGE_AT_(file, line, message, result_type) \ + ::testing::internal::AssertHelper(result_type, file, line, message) \ = ::testing::Message() +#define GTEST_MESSAGE_(message, result_type) \ + GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type) + #define GTEST_FATAL_FAILURE_(message) \ return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure) @@ -7208,7 +7801,7 @@ class GTEST_API_ Random { #define GTEST_TEST_THROW_(statement, expected_exception, fail) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (const char* gtest_msg = "") { \ + if (::testing::internal::ConstCharPtr gtest_msg = "") { \ bool gtest_caught_expected = false; \ try { \ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ @@ -7217,38 +7810,38 @@ class GTEST_API_ Random { gtest_caught_expected = true; \ } \ catch (...) { \ - gtest_msg = "Expected: " #statement " throws an exception of type " \ - #expected_exception ".\n Actual: it throws a different " \ - "type."; \ + gtest_msg.value = \ + "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws a different type."; \ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ } \ if (!gtest_caught_expected) { \ - gtest_msg = "Expected: " #statement " throws an exception of type " \ - #expected_exception ".\n Actual: it throws nothing."; \ + gtest_msg.value = \ + "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws nothing."; \ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ } \ } else \ GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \ - fail(gtest_msg) + fail(gtest_msg.value) #define GTEST_TEST_NO_THROW_(statement, fail) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (const char* gtest_msg = "") { \ + if (::testing::internal::AlwaysTrue()) { \ try { \ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ } \ catch (...) { \ - gtest_msg = "Expected: " #statement " doesn't throw an exception.\n" \ - " Actual: it throws."; \ goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ } \ } else \ GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \ - fail(gtest_msg) + fail("Expected: " #statement " doesn't throw an exception.\n" \ + " Actual: it throws.") #define GTEST_TEST_ANY_THROW_(statement, fail) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (const char* gtest_msg = "") { \ + if (::testing::internal::AlwaysTrue()) { \ bool gtest_caught_any = false; \ try { \ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ @@ -7257,13 +7850,12 @@ class GTEST_API_ Random { gtest_caught_any = true; \ } \ if (!gtest_caught_any) { \ - gtest_msg = "Expected: " #statement " throws an exception.\n" \ - " Actual: it doesn't."; \ goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \ } \ } else \ GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \ - fail(gtest_msg) + fail("Expected: " #statement " throws an exception.\n" \ + " Actual: it doesn't.") // Implements Boolean test assertions such as EXPECT_TRUE. expression can be @@ -7280,18 +7872,17 @@ class GTEST_API_ Random { #define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (const char* gtest_msg = "") { \ + if (::testing::internal::AlwaysTrue()) { \ ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \ - gtest_msg = "Expected: " #statement " doesn't generate new fatal " \ - "failures in the current thread.\n" \ - " Actual: it does."; \ goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \ } \ } else \ GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \ - fail(gtest_msg) + fail("Expected: " #statement " doesn't generate new fatal " \ + "failures in the current thread.\n" \ + " Actual: it does.") // Expands to the name of the class that implements the given test. #define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ @@ -7304,7 +7895,7 @@ class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\ private:\ virtual void TestBody();\ - static ::testing::TestInfo* const test_info_;\ + static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\ GTEST_DISALLOW_COPY_AND_ASSIGN_(\ GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\ };\ @@ -7312,7 +7903,7 @@ class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\ ::test_info_ =\ ::testing::internal::MakeAndRegisterTestInfo(\ - #test_case_name, #test_name, "", "", \ + #test_case_name, #test_name, NULL, NULL, \ (parent_id), \ parent_class::SetUpTestCase, \ parent_class::TearDownTestCase, \ @@ -7401,6 +7992,8 @@ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() #define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ +#include + namespace testing { namespace internal { @@ -7458,8 +8051,12 @@ class GTEST_API_ DeathTest { // test, then wait for it to complete. enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; - // An enumeration of the two reasons that a test might be aborted. - enum AbortReason { TEST_ENCOUNTERED_RETURN_STATEMENT, TEST_DID_NOT_DIE }; + // An enumeration of the three reasons that a test might be aborted. + enum AbortReason { + TEST_ENCOUNTERED_RETURN_STATEMENT, + TEST_THREW_EXCEPTION, + TEST_DID_NOT_DIE + }; // Assumes one of the above roles. virtual TestRole AssumeRole() = 0; @@ -7511,9 +8108,34 @@ class DefaultDeathTestFactory : public DeathTestFactory { // by a signal, or exited normally with a nonzero exit code. GTEST_API_ bool ExitedUnsuccessfully(int exit_status); +// Traps C++ exceptions escaping statement and reports them as test +// failures. Note that trapping SEH exceptions is not implemented here. +# if GTEST_HAS_EXCEPTIONS +# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (const ::std::exception& gtest_exception) { \ + fprintf(\ + stderr, \ + "\n%s: Caught std::exception-derived exception escaping the " \ + "death test statement. Exception message: %s\n", \ + ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \ + gtest_exception.what()); \ + fflush(stderr); \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } catch (...) { \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } + +# else +# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) + +# endif + // This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, // ASSERT_EXIT*, and EXPECT_EXIT*. -#define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \ +# define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ if (::testing::internal::AlwaysTrue()) { \ const ::testing::internal::RE& gtest_regex = (regex); \ @@ -7534,10 +8156,12 @@ GTEST_API_ bool ExitedUnsuccessfully(int exit_status); case ::testing::internal::DeathTest::EXECUTE_TEST: { \ ::testing::internal::DeathTest::ReturnSentinel \ gtest_sentinel(gtest_dt); \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \ gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ break; \ } \ + default: \ + break; \ } \ } \ } else \ @@ -7616,7 +8240,7 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag(); // statement unconditionally returns or throws. The Message constructor at // the end allows the syntax of streaming additional messages into the // macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH. -#define GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, terminator) \ +# define GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, terminator) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ if (::testing::internal::AlwaysTrue()) { \ GTEST_LOG_(WARNING) \ @@ -7750,24 +8374,24 @@ GTEST_DECLARE_string_(death_test_style); // Asserts that a given statement causes the program to exit, with an // integer exit status that satisfies predicate, and emitting error output // that matches regex. -#define ASSERT_EXIT(statement, predicate, regex) \ - GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_) +# define ASSERT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_) // Like ASSERT_EXIT, but continues on to successive tests in the // test case, if any: -#define EXPECT_EXIT(statement, predicate, regex) \ - GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_) +# define EXPECT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_) // Asserts that a given statement causes the program to exit, either by // explicitly exiting with a nonzero exit code or being killed by a // signal, and emitting error output that matches regex. -#define ASSERT_DEATH(statement, regex) \ - ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) +# define ASSERT_DEATH(statement, regex) \ + ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) // Like ASSERT_DEATH, but continues on to successive tests in the // test case, if any: -#define EXPECT_DEATH(statement, regex) \ - EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) +# define EXPECT_DEATH(statement, regex) \ + EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) // Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*: @@ -7783,7 +8407,7 @@ class GTEST_API_ ExitedWithCode { const int exit_code_; }; -#if !GTEST_OS_WINDOWS +# if !GTEST_OS_WINDOWS // Tests that an exit code describes an exit due to termination by a // given signal. class GTEST_API_ KilledBySignal { @@ -7793,7 +8417,7 @@ class GTEST_API_ KilledBySignal { private: const int signum_; }; -#endif // !GTEST_OS_WINDOWS +# endif // !GTEST_OS_WINDOWS // EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode. // The death testing framework causes this to have interesting semantics, @@ -7838,23 +8462,23 @@ class GTEST_API_ KilledBySignal { // EXPECT_EQ(12, DieInDebugOr12(&sideeffect)); // }, "death"); // -#ifdef NDEBUG +# ifdef NDEBUG -#define EXPECT_DEBUG_DEATH(statement, regex) \ +# define EXPECT_DEBUG_DEATH(statement, regex) \ do { statement; } while (::testing::internal::AlwaysFalse()) -#define ASSERT_DEBUG_DEATH(statement, regex) \ +# define ASSERT_DEBUG_DEATH(statement, regex) \ do { statement; } while (::testing::internal::AlwaysFalse()) -#else +# else -#define EXPECT_DEBUG_DEATH(statement, regex) \ +# define EXPECT_DEBUG_DEATH(statement, regex) \ EXPECT_DEATH(statement, regex) -#define ASSERT_DEBUG_DEATH(statement, regex) \ +# define ASSERT_DEBUG_DEATH(statement, regex) \ ASSERT_DEATH(statement, regex) -#endif // NDEBUG for EXPECT_DEBUG_DEATH +# endif // NDEBUG for EXPECT_DEBUG_DEATH #endif // GTEST_HAS_DEATH_TEST // EXPECT_DEATH_IF_SUPPORTED(statement, regex) and @@ -7863,14 +8487,14 @@ class GTEST_API_ KilledBySignal { // useful when you are combining death test assertions with normal test // assertions in one test. #if GTEST_HAS_DEATH_TEST -#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ +# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ EXPECT_DEATH(statement, regex) -#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ +# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ ASSERT_DEATH(statement, regex) #else -#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ +# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, ) -#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ +# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, return) #endif @@ -7935,7 +8559,7 @@ namespace testing { // Typical usage: // // 1. You stream a bunch of values to a Message object. -// It will remember the text in a StrStream. +// It will remember the text in a stringstream. // 2. Then you stream the Message object to an ostream. // This causes the text in the Message to be streamed // to the ostream. @@ -7951,7 +8575,7 @@ namespace testing { // Message is not intended to be inherited from. In particular, its // destructor is not virtual. // -// Note that StrStream behaves differently in gcc and in MSVC. You +// Note that stringstream behaves differently in gcc and in MSVC. You // can stream a NULL char pointer to it in the former, but not in the // latter (it causes an access violation if you do). The Message // class hides this difference by treating a NULL char pointer as @@ -7964,27 +8588,26 @@ class GTEST_API_ Message { public: // Constructs an empty Message. - // We allocate the StrStream separately because it otherwise each use of + // We allocate the stringstream separately because otherwise each use of // ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's // stack frame leading to huge stack frames in some cases; gcc does not reuse // the stack space. - Message() : ss_(new internal::StrStream) { + Message() : ss_(new ::std::stringstream) { // By default, we want there to be enough precision when printing // a double to a Message. *ss_ << std::setprecision(std::numeric_limits::digits10 + 2); } // Copy constructor. - Message(const Message& msg) : ss_(new internal::StrStream) { // NOLINT + Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT *ss_ << msg.GetString(); } // Constructs a Message from a C-string. - explicit Message(const char* str) : ss_(new internal::StrStream) { + explicit Message(const char* str) : ss_(new ::std::stringstream) { *ss_ << str; } - ~Message() { delete ss_; } #if GTEST_OS_SYMBIAN // Streams a value (either a pointer or not) to this object. template @@ -7996,7 +8619,7 @@ class GTEST_API_ Message { // Streams a non-pointer value to this object. template inline Message& operator <<(const T& val) { - ::GTestStreamToHelper(ss_, val); + ::GTestStreamToHelper(ss_.get(), val); return *this; } @@ -8018,7 +8641,7 @@ class GTEST_API_ Message { if (pointer == NULL) { *ss_ << "(null)"; } else { - ::GTestStreamToHelper(ss_, pointer); + ::GTestStreamToHelper(ss_.get(), pointer); } return *this; } @@ -8066,10 +8689,11 @@ class GTEST_API_ Message { // // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. internal::String GetString() const { - return internal::StrStreamToString(ss_); + return internal::StringStreamToString(ss_.get()); } private: + #if GTEST_OS_SYMBIAN // These are needed as the Nokia Symbian Compiler cannot decide between // const T& and const T* in a function template. The Nokia compiler _can_ @@ -8080,17 +8704,17 @@ class GTEST_API_ Message { if (pointer == NULL) { *ss_ << "(null)"; } else { - ::GTestStreamToHelper(ss_, pointer); + ::GTestStreamToHelper(ss_.get(), pointer); } } template inline void StreamHelper(internal::false_type /*dummy*/, const T& value) { - ::GTestStreamToHelper(ss_, value); + ::GTestStreamToHelper(ss_.get(), value); } #endif // GTEST_OS_SYMBIAN // We'll hold the text streamed to this object here. - internal::StrStream* const ss_; + const internal::scoped_ptr< ::std::stringstream> ss_; // We declare (but don't implement) this to prevent the compiler // from implementing the assignment operator. @@ -8105,7 +8729,9 @@ inline std::ostream& operator <<(std::ostream& os, const Message& sb) { } // namespace testing #endif // GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ -// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! +// This file was GENERATED by command: +// pump.py gtest-param-test.h.pump +// DO NOT EDIT BY HAND!!! // Copyright 2008, Google Inc. // All rights reserved. @@ -8155,10 +8781,12 @@ inline std::ostream& operator <<(std::ostream& os, const Message& sb) { #if 0 // To write value-parameterized tests, first you should define a fixture -// class. It must be derived from testing::TestWithParam, where T is -// the type of your parameter values. TestWithParam is itself derived -// from testing::Test. T can be any copyable type. If it's a raw pointer, -// you are responsible for managing the lifespan of the pointed values. +// class. It is usually derived from testing::TestWithParam (see below for +// another inheritance scheme that's sometimes useful in more complicated +// class hierarchies), where the type of your parameter values. +// TestWithParam is itself derived from testing::Test. T can be any +// copyable type. If it's a raw pointer, you are responsible for managing the +// lifespan of the pointed values. class FooTest : public ::testing::TestWithParam { // You can implement all the usual class fixture members here. @@ -8253,12 +8881,38 @@ INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); // In the future, we plan to publish the API for defining new parameter // generators. But for now this interface remains part of the internal // implementation and is subject to change. +// +// +// A parameterized test fixture must be derived from testing::Test and from +// testing::WithParamInterface, where T is the type of the parameter +// values. Inheriting from TestWithParam satisfies that requirement because +// TestWithParam inherits from both Test and WithParamInterface. In more +// complicated hierarchies, however, it is occasionally useful to inherit +// separately from Test and WithParamInterface. For example: + +class BaseTest : public ::testing::Test { + // You can inherit all the usual members for a non-parameterized test + // fixture here. +}; + +class DerivedTest : public BaseTest, public ::testing::WithParamInterface { + // The usual test fixture members go here too. +}; + +TEST_F(BaseTest, HasFoo) { + // This is an ordinary non-parameterized test. +} + +TEST_P(DerivedTest, DoesBlah) { + // GetParam works just the same here as if you inherit from TestWithParam. + EXPECT_TRUE(foo.Blah(GetParam())); +} #endif // 0 #if !GTEST_OS_SYMBIAN -#include +# include #endif // scripts/fuse_gtest.py depends on gtest's own header being #included @@ -8385,7 +9039,7 @@ namespace testing { namespace internal { // Protects copying of all linked_ptr objects. -GTEST_API_2 GTEST_DECLARE_STATIC_MUTEX_(g_linked_ptr_mutex); +GTEST_API_ GTEST_DECLARE_STATIC_MUTEX_(g_linked_ptr_mutex); // This is used internally by all instances of linked_ptr<>. It needs to be // a non-template class because different types of linked_ptr<> can refer to @@ -8480,15 +9134,6 @@ class linked_ptr { T* get() const { return value_; } T* operator->() const { return value_; } T& operator*() const { return *value_; } - // Release ownership of the pointed object and returns it. - // Sole ownership by this linked_ptr object is required. - T* release() { - bool last = link_.depart(); - assert(last); - T* v = value_; - value_ = NULL; - return v; - } bool operator==(T* p) const { return value_ == p; } bool operator!=(T* p) const { return value_ != p; } @@ -8548,6 +9193,800 @@ linked_ptr make_linked_ptr(T* ptr) { } // namespace testing #endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Test - The Google C++ Testing Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// A user can teach this function how to print a class type T by +// defining either operator<<() or PrintTo() in the namespace that +// defines T. More specifically, the FIRST defined function in the +// following list will be used (assuming T is defined in namespace +// foo): +// +// 1. foo::PrintTo(const T&, ostream*) +// 2. operator<<(ostream&, const T&) defined in either foo or the +// global namespace. +// +// If none of the above is defined, it will print the debug string of +// the value if it is a protocol buffer, or print the raw bytes in the +// value otherwise. +// +// To aid debugging: when T is a reference type, the address of the +// value is also printed; when T is a (const) char pointer, both the +// pointer value and the NUL-terminated string it points to are +// printed. +// +// We also provide some convenient wrappers: +// +// // Prints a value to a string. For a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// std::string ::testing::PrintToString(const T& value); +// +// // Prints a value tersely: for a reference type, the referenced +// // value (but not the address) is printed; for a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// void ::testing::internal::UniversalTersePrint(const T& value, ostream*); +// +// // Prints value using the type inferred by the compiler. The difference +// // from UniversalTersePrint() is that this function prints both the +// // pointer and the NUL-terminated string for a (const or not) char pointer. +// void ::testing::internal::UniversalPrint(const T& value, ostream*); +// +// // Prints the fields of a tuple tersely to a string vector, one +// // element for each field. Tuple support must be enabled in +// // gtest-port.h. +// std::vector UniversalTersePrintTupleFieldsToStrings( +// const Tuple& value); +// +// Known limitation: +// +// The print primitives print the elements of an STL-style container +// using the compiler-inferred type of *iter where iter is a +// const_iterator of the container. When const_iterator is an input +// iterator but not a forward iterator, this inferred type may not +// match value_type, and the print output may be incorrect. In +// practice, this is rarely a problem as for most containers +// const_iterator is a forward iterator. We'll fix this if there's an +// actual need for it. Note that this fix cannot rely on value_type +// being defined as many user-defined container types don't have +// value_type. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ + +#include // NOLINT +#include +#include +#include +#include + +namespace testing { + +// Definitions in the 'internal' and 'internal2' name spaces are +// subject to change without notice. DO NOT USE THEM IN USER CODE! +namespace internal2 { + +// Prints the given number of bytes in the given object to the given +// ostream. +GTEST_API_ void PrintBytesInObjectTo(const unsigned char* obj_bytes, + size_t count, + ::std::ostream* os); + +// For selecting which printer to use when a given type has neither << +// nor PrintTo(). +enum TypeKind { + kProtobuf, // a protobuf type + kConvertibleToInteger, // a type implicitly convertible to BiggestInt + // (e.g. a named or unnamed enum type) + kOtherType // anything else +}; + +// TypeWithoutFormatter::PrintValue(value, os) is called +// by the universal printer to print a value of type T when neither +// operator<< nor PrintTo() is defined for T, where kTypeKind is the +// "kind" of T as defined by enum TypeKind. +template +class TypeWithoutFormatter { + public: + // This default version is called when kTypeKind is kOtherType. + static void PrintValue(const T& value, ::std::ostream* os) { + PrintBytesInObjectTo(reinterpret_cast(&value), + sizeof(value), os); + } +}; + +// We print a protobuf using its ShortDebugString() when the string +// doesn't exceed this many characters; otherwise we print it using +// DebugString() for better readability. +const size_t kProtobufOneLinerMaxLength = 50; + +template +class TypeWithoutFormatter { + public: + static void PrintValue(const T& value, ::std::ostream* os) { + const ::testing::internal::string short_str = value.ShortDebugString(); + const ::testing::internal::string pretty_str = + short_str.length() <= kProtobufOneLinerMaxLength ? + short_str : ("\n" + value.DebugString()); + *os << ("<" + pretty_str + ">"); + } +}; + +template +class TypeWithoutFormatter { + public: + // Since T has no << operator or PrintTo() but can be implicitly + // converted to BiggestInt, we print it as a BiggestInt. + // + // Most likely T is an enum type (either named or unnamed), in which + // case printing it as an integer is the desired behavior. In case + // T is not an enum, printing it as an integer is the best we can do + // given that it has no user-defined printer. + static void PrintValue(const T& value, ::std::ostream* os) { + const internal::BiggestInt kBigInt = value; + *os << kBigInt; + } +}; + +// Prints the given value to the given ostream. If the value is a +// protocol message, its debug string is printed; if it's an enum or +// of a type implicitly convertible to BiggestInt, it's printed as an +// integer; otherwise the bytes in the value are printed. This is +// what UniversalPrinter::Print() does when it knows nothing about +// type T and T has neither << operator nor PrintTo(). +// +// A user can override this behavior for a class type Foo by defining +// a << operator in the namespace where Foo is defined. +// +// We put this operator in namespace 'internal2' instead of 'internal' +// to simplify the implementation, as much code in 'internal' needs to +// use << in STL, which would conflict with our own << were it defined +// in 'internal'. +// +// Note that this operator<< takes a generic std::basic_ostream type instead of the more restricted std::ostream. If +// we define it to take an std::ostream instead, we'll get an +// "ambiguous overloads" compiler error when trying to print a type +// Foo that supports streaming to std::basic_ostream, as the compiler cannot tell whether +// operator<<(std::ostream&, const T&) or +// operator<<(std::basic_stream, const Foo&) is more +// specific. +template +::std::basic_ostream& operator<<( + ::std::basic_ostream& os, const T& x) { + TypeWithoutFormatter::value ? kProtobuf : + internal::ImplicitlyConvertible::value ? + kConvertibleToInteger : kOtherType)>::PrintValue(x, &os); + return os; +} + +} // namespace internal2 +} // namespace testing + +// This namespace MUST NOT BE NESTED IN ::testing, or the name look-up +// magic needed for implementing UniversalPrinter won't work. +namespace testing_internal { + +// Used to print a value that is not an STL-style container when the +// user doesn't define PrintTo() for it. +template +void DefaultPrintNonContainerTo(const T& value, ::std::ostream* os) { + // With the following statement, during unqualified name lookup, + // testing::internal2::operator<< appears as if it was declared in + // the nearest enclosing namespace that contains both + // ::testing_internal and ::testing::internal2, i.e. the global + // namespace. For more details, refer to the C++ Standard section + // 7.3.4-1 [namespace.udir]. This allows us to fall back onto + // testing::internal2::operator<< in case T doesn't come with a << + // operator. + // + // We cannot write 'using ::testing::internal2::operator<<;', which + // gcc 3.3 fails to compile due to a compiler bug. + using namespace ::testing::internal2; // NOLINT + + // Assuming T is defined in namespace foo, in the next statement, + // the compiler will consider all of: + // + // 1. foo::operator<< (thanks to Koenig look-up), + // 2. ::operator<< (as the current namespace is enclosed in ::), + // 3. testing::internal2::operator<< (thanks to the using statement above). + // + // The operator<< whose type matches T best will be picked. + // + // We deliberately allow #2 to be a candidate, as sometimes it's + // impossible to define #1 (e.g. when foo is ::std, defining + // anything in it is undefined behavior unless you are a compiler + // vendor.). + *os << value; +} + +} // namespace testing_internal + +namespace testing { +namespace internal { + +// UniversalPrinter::Print(value, ostream_ptr) prints the given +// value to the given ostream. The caller must ensure that +// 'ostream_ptr' is not NULL, or the behavior is undefined. +// +// We define UniversalPrinter as a class template (as opposed to a +// function template), as we need to partially specialize it for +// reference types, which cannot be done with function templates. +template +class UniversalPrinter; + +template +void UniversalPrint(const T& value, ::std::ostream* os); + +// Used to print an STL-style container when the user doesn't define +// a PrintTo() for it. +template +void DefaultPrintTo(IsContainer /* dummy */, + false_type /* is not a pointer */, + const C& container, ::std::ostream* os) { + const size_t kMaxCount = 32; // The maximum number of elements to print. + *os << '{'; + size_t count = 0; + for (typename C::const_iterator it = container.begin(); + it != container.end(); ++it, ++count) { + if (count > 0) { + *os << ','; + if (count == kMaxCount) { // Enough has been printed. + *os << " ..."; + break; + } + } + *os << ' '; + // We cannot call PrintTo(*it, os) here as PrintTo() doesn't + // handle *it being a native array. + internal::UniversalPrint(*it, os); + } + + if (count > 0) { + *os << ' '; + } + *os << '}'; +} + +// Used to print a pointer that is neither a char pointer nor a member +// pointer, when the user doesn't define PrintTo() for it. (A member +// variable pointer or member function pointer doesn't really point to +// a location in the address space. Their representation is +// implementation-defined. Therefore they will be printed as raw +// bytes.) +template +void DefaultPrintTo(IsNotContainer /* dummy */, + true_type /* is a pointer */, + T* p, ::std::ostream* os) { + if (p == NULL) { + *os << "NULL"; + } else { + // C++ doesn't allow casting from a function pointer to any object + // pointer. + // + // IsTrue() silences warnings: "Condition is always true", + // "unreachable code". + if (IsTrue(ImplicitlyConvertible::value)) { + // T is not a function type. We just call << to print p, + // relying on ADL to pick up user-defined << for their pointer + // types, if any. + *os << p; + } else { + // T is a function type, so '*os << p' doesn't do what we want + // (it just prints p as bool). We want to print p as a const + // void*. However, we cannot cast it to const void* directly, + // even using reinterpret_cast, as earlier versions of gcc + // (e.g. 3.4.5) cannot compile the cast when p is a function + // pointer. Casting to UInt64 first solves the problem. + *os << reinterpret_cast( + reinterpret_cast(p)); + } + } +} + +// Used to print a non-container, non-pointer value when the user +// doesn't define PrintTo() for it. +template +void DefaultPrintTo(IsNotContainer /* dummy */, + false_type /* is not a pointer */, + const T& value, ::std::ostream* os) { + ::testing_internal::DefaultPrintNonContainerTo(value, os); +} + +// Prints the given value using the << operator if it has one; +// otherwise prints the bytes in it. This is what +// UniversalPrinter::Print() does when PrintTo() is not specialized +// or overloaded for type T. +// +// A user can override this behavior for a class type Foo by defining +// an overload of PrintTo() in the namespace where Foo is defined. We +// give the user this option as sometimes defining a << operator for +// Foo is not desirable (e.g. the coding style may prevent doing it, +// or there is already a << operator but it doesn't do what the user +// wants). +template +void PrintTo(const T& value, ::std::ostream* os) { + // DefaultPrintTo() is overloaded. The type of its first two + // arguments determine which version will be picked. If T is an + // STL-style container, the version for container will be called; if + // T is a pointer, the pointer version will be called; otherwise the + // generic version will be called. + // + // Note that we check for container types here, prior to we check + // for protocol message types in our operator<<. The rationale is: + // + // For protocol messages, we want to give people a chance to + // override Google Mock's format by defining a PrintTo() or + // operator<<. For STL containers, other formats can be + // incompatible with Google Mock's format for the container + // elements; therefore we check for container types here to ensure + // that our format is used. + // + // The second argument of DefaultPrintTo() is needed to bypass a bug + // in Symbian's C++ compiler that prevents it from picking the right + // overload between: + // + // PrintTo(const T& x, ...); + // PrintTo(T* x, ...); + DefaultPrintTo(IsContainerTest(0), is_pointer(), value, os); +} + +// The following list of PrintTo() overloads tells +// UniversalPrinter::Print() how to print standard types (built-in +// types, strings, plain arrays, and pointers). + +// Overloads for various char types. +GTEST_API_ void PrintTo(unsigned char c, ::std::ostream* os); +GTEST_API_ void PrintTo(signed char c, ::std::ostream* os); +inline void PrintTo(char c, ::std::ostream* os) { + // When printing a plain char, we always treat it as unsigned. This + // way, the output won't be affected by whether the compiler thinks + // char is signed or not. + PrintTo(static_cast(c), os); +} + +// Overloads for other simple built-in types. +inline void PrintTo(bool x, ::std::ostream* os) { + *os << (x ? "true" : "false"); +} + +// Overload for wchar_t type. +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its decimal code (except for L'\0'). +// The L'\0' char is printed as "L'\\0'". The decimal code is printed +// as signed integer when wchar_t is implemented by the compiler +// as a signed type and is printed as an unsigned integer when wchar_t +// is implemented as an unsigned type. +GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os); + +// Overloads for C strings. +GTEST_API_ void PrintTo(const char* s, ::std::ostream* os); +inline void PrintTo(char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} + +// signed/unsigned char is often used for representing binary data, so +// we print pointers to it as void* to be safe. +inline void PrintTo(const signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(const unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} + +// MSVC can be configured to define wchar_t as a typedef of unsigned +// short. It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native +// type. When wchar_t is a typedef, defining an overload for const +// wchar_t* would cause unsigned short* be printed as a wide string, +// possibly causing invalid memory accesses. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Overloads for wide C strings +GTEST_API_ void PrintTo(const wchar_t* s, ::std::ostream* os); +inline void PrintTo(wchar_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +#endif + +// Overload for C arrays. Multi-dimensional arrays are printed +// properly. + +// Prints the given number of elements in an array, without printing +// the curly braces. +template +void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) { + UniversalPrint(a[0], os); + for (size_t i = 1; i != count; i++) { + *os << ", "; + UniversalPrint(a[i], os); + } +} + +// Overloads for ::string and ::std::string. +#if GTEST_HAS_GLOBAL_STRING +GTEST_API_ void PrintStringTo(const ::string&s, ::std::ostream* os); +inline void PrintTo(const ::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} +#endif // GTEST_HAS_GLOBAL_STRING + +GTEST_API_ void PrintStringTo(const ::std::string&s, ::std::ostream* os); +inline void PrintTo(const ::std::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} + +// Overloads for ::wstring and ::std::wstring. +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_API_ void PrintWideStringTo(const ::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ void PrintWideStringTo(const ::std::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_TR1_TUPLE +// Overload for ::std::tr1::tuple. Needed for printing function arguments, +// which are packed as tuples. + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T& t, ::std::ostream* os); + +// Overloaded PrintTo() for tuples of various arities. We support +// tuples of up-to 10 fields. The following implementation works +// regardless of whether tr1::tuple is implemented using the +// non-standard variadic template feature or not. + +inline void PrintTo(const ::std::tr1::tuple<>& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo( + const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} +#endif // GTEST_HAS_TR1_TUPLE + +// Overload for std::pair. +template +void PrintTo(const ::std::pair& value, ::std::ostream* os) { + *os << '('; + // We cannot use UniversalPrint(value.first, os) here, as T1 may be + // a reference type. The same for printing value.second. + UniversalPrinter::Print(value.first, os); + *os << ", "; + UniversalPrinter::Print(value.second, os); + *os << ')'; +} + +// Implements printing a non-reference type T by letting the compiler +// pick the right overload of PrintTo() for T. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4180) // Temporarily disables warning 4180. +#endif // _MSC_VER + + // Note: we deliberately don't call this PrintTo(), as that name + // conflicts with ::testing::internal::PrintTo in the body of the + // function. + static void Print(const T& value, ::std::ostream* os) { + // By default, ::testing::internal::PrintTo() is used for printing + // the value. + // + // Thanks to Koenig look-up, if T is a class and has its own + // PrintTo() function defined in its namespace, that function will + // be visible here. Since it is more specific than the generic ones + // in ::testing::internal, it will be picked by the compiler in the + // following statement - exactly what we want. + PrintTo(value, os); + } + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif // _MSC_VER +}; + +// UniversalPrintArray(begin, len, os) prints an array of 'len' +// elements, starting at address 'begin'. +template +void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) { + if (len == 0) { + *os << "{}"; + } else { + *os << "{ "; + const size_t kThreshold = 18; + const size_t kChunkSize = 8; + // If the array has more than kThreshold elements, we'll have to + // omit some details by printing only the first and the last + // kChunkSize elements. + // TODO(wan@google.com): let the user control the threshold using a flag. + if (len <= kThreshold) { + PrintRawArrayTo(begin, len, os); + } else { + PrintRawArrayTo(begin, kChunkSize, os); + *os << ", ..., "; + PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os); + } + *os << " }"; + } +} +// This overload prints a (const) char array compactly. +GTEST_API_ void UniversalPrintArray(const char* begin, + size_t len, + ::std::ostream* os); + +// Implements printing an array type T[N]. +template +class UniversalPrinter { + public: + // Prints the given array, omitting some elements when there are too + // many. + static void Print(const T (&a)[N], ::std::ostream* os) { + UniversalPrintArray(a, N, os); + } +}; + +// Implements printing a reference type T&. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4180) // Temporarily disables warning 4180. +#endif // _MSC_VER + + static void Print(const T& value, ::std::ostream* os) { + // Prints the address of the value. We use reinterpret_cast here + // as static_cast doesn't compile when T is a function type. + *os << "@" << reinterpret_cast(&value) << " "; + + // Then prints the value itself. + UniversalPrint(value, os); + } + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif // _MSC_VER +}; + +// Prints a value tersely: for a reference type, the referenced value +// (but not the address) is printed; for a (const) char pointer, the +// NUL-terminated string (but not the pointer) is printed. +template +void UniversalTersePrint(const T& value, ::std::ostream* os) { + UniversalPrint(value, os); +} +inline void UniversalTersePrint(const char* str, ::std::ostream* os) { + if (str == NULL) { + *os << "NULL"; + } else { + UniversalPrint(string(str), os); + } +} +inline void UniversalTersePrint(char* str, ::std::ostream* os) { + UniversalTersePrint(static_cast(str), os); +} + +// Prints a value using the type inferred by the compiler. The +// difference between this and UniversalTersePrint() is that for a +// (const) char pointer, this prints both the pointer and the +// NUL-terminated string. +template +void UniversalPrint(const T& value, ::std::ostream* os) { + UniversalPrinter::Print(value, os); +} + +#if GTEST_HAS_TR1_TUPLE +typedef ::std::vector Strings; + +// This helper template allows PrintTo() for tuples and +// UniversalTersePrintTupleFieldsToStrings() to be defined by +// induction on the number of tuple fields. The idea is that +// TuplePrefixPrinter::PrintPrefixTo(t, os) prints the first N +// fields in tuple t, and can be defined in terms of +// TuplePrefixPrinter. + +// The inductive case. +template +struct TuplePrefixPrinter { + // Prints the first N fields of a tuple. + template + static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { + TuplePrefixPrinter::PrintPrefixTo(t, os); + *os << ", "; + UniversalPrinter::type> + ::Print(::std::tr1::get(t), os); + } + + // Tersely prints the first N fields of a tuple to a string vector, + // one element for each field. + template + static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { + TuplePrefixPrinter::TersePrintPrefixToStrings(t, strings); + ::std::stringstream ss; + UniversalTersePrint(::std::tr1::get(t), &ss); + strings->push_back(ss.str()); + } +}; + +// Base cases. +template <> +struct TuplePrefixPrinter<0> { + template + static void PrintPrefixTo(const Tuple&, ::std::ostream*) {} + + template + static void TersePrintPrefixToStrings(const Tuple&, Strings*) {} +}; +// We have to specialize the entire TuplePrefixPrinter<> class +// template here, even though the definition of +// TersePrintPrefixToStrings() is the same as the generic version, as +// Embarcadero (formerly CodeGear, formerly Borland) C++ doesn't +// support specializing a method template of a class template. +template <> +struct TuplePrefixPrinter<1> { + template + static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { + UniversalPrinter::type>:: + Print(::std::tr1::get<0>(t), os); + } + + template + static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { + ::std::stringstream ss; + UniversalTersePrint(::std::tr1::get<0>(t), &ss); + strings->push_back(ss.str()); + } +}; + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T& t, ::std::ostream* os) { + *os << "("; + TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + PrintPrefixTo(t, os); + *os << ")"; +} + +// Prints the fields of a tuple tersely to a string vector, one +// element for each field. See the comment before +// UniversalTersePrint() for how we define "tersely". +template +Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) { + Strings result; + TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + TersePrintPrefixToStrings(value, &result); + return result; +} +#endif // GTEST_HAS_TR1_TUPLE + +} // namespace internal + +template +::std::string PrintToString(const T& value) { + ::std::stringstream ss; + internal::UniversalTersePrint(value, &ss); + return ss.str(); +} + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ #if GTEST_HAS_PARAM_TEST @@ -8675,7 +10114,7 @@ class ParamGenerator { iterator end() const { return iterator(impl_->End()); } private: - ::testing::internal::linked_ptr > impl_; + linked_ptr > impl_; }; // Generates values from a range of two comparable values. Can be used to @@ -8789,7 +10228,7 @@ class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface { public: Iterator(const ParamGeneratorInterface* base, typename ContainerType::const_iterator iterator) - : base_(base), iterator_(iterator) {} + : base_(base), iterator_(iterator) {} virtual ~Iterator() {} virtual const ParamGeneratorInterface* BaseGenerator() const { @@ -8920,7 +10359,7 @@ class ParameterizedTestCaseInfoBase { virtual ~ParameterizedTestCaseInfoBase() {} // Base part of test case name for display purposes. - virtual const String& GetTestCaseName() const = 0; + virtual const string& GetTestCaseName() const = 0; // Test case id to verify identity. virtual TypeId GetTestCaseTypeId() const = 0; // UnitTest class invokes this method to register tests in this @@ -8957,7 +10396,7 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { : test_case_name_(name) {} // Test case base name for display purposes. - virtual const String& GetTestCaseName() const { return test_case_name_; } + virtual const string& GetTestCaseName() const { return test_case_name_; } // Test case id to verify identity. virtual TypeId GetTestCaseTypeId() const { return GetTypeId(); } // TEST_P macro uses AddTestPattern() to record information @@ -8975,7 +10414,7 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { } // INSTANTIATE_TEST_CASE_P macro uses AddGenerator() to record information // about a generator. - int AddTestCaseInstantiation(const char* instantiation_name, + int AddTestCaseInstantiation(const string& instantiation_name, GeneratorCreationFunc* func, const char* /* file */, int /* line */) { @@ -8994,26 +10433,25 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { for (typename InstantiationContainer::iterator gen_it = instantiations_.begin(); gen_it != instantiations_.end(); ++gen_it) { - const String& instantiation_name = gen_it->first; + const string& instantiation_name = gen_it->first; ParamGenerator generator((*gen_it->second)()); Message test_case_name_stream; if ( !instantiation_name.empty() ) - test_case_name_stream << instantiation_name.c_str() << "/"; - test_case_name_stream << test_info->test_case_base_name.c_str(); + test_case_name_stream << instantiation_name << "/"; + test_case_name_stream << test_info->test_case_base_name; int i = 0; for (typename ParamGenerator::iterator param_it = generator.begin(); param_it != generator.end(); ++param_it, ++i) { Message test_name_stream; - test_name_stream << test_info->test_base_name.c_str() << "/" << i; - ::testing::internal::MakeAndRegisterTestInfo( + test_name_stream << test_info->test_base_name << "/" << i; + MakeAndRegisterTestInfo( test_case_name_stream.GetString().c_str(), test_name_stream.GetString().c_str(), - "", // test_case_comment - "", // comment; TODO(vladl@google.com): provide parameter value - // representation. + NULL, // No type parameter. + PrintToString(*param_it).c_str(), GetTestCaseTypeId(), TestCase::SetUpTestCase, TestCase::TearDownTestCase, @@ -9034,17 +10472,17 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { test_base_name(a_test_base_name), test_meta_factory(a_test_meta_factory) {} - const String test_case_base_name; - const String test_base_name; + const string test_case_base_name; + const string test_base_name; const scoped_ptr > test_meta_factory; }; typedef ::std::vector > TestInfoContainer; // Keeps pairs of // received from INSTANTIATE_TEST_CASE_P macros. - typedef ::std::vector > + typedef ::std::vector > InstantiationContainer; - const String test_case_name_; + const string test_case_name_; TestInfoContainer tests_; InstantiationContainer instantiations_; @@ -9083,7 +10521,7 @@ class ParameterizedTestCaseRegistry { // and terminate the program since we cannot guaranty correct // test case setup and tear-down in this case. ReportInvalidTestCaseType(test_case_name, file, line); - abort(); + posix::Abort(); } else { // At this point we are sure that the object we found is of the same // type we are looking for, so we downcast it to that type @@ -9121,7 +10559,9 @@ class ParameterizedTestCaseRegistry { #endif // GTEST_HAS_PARAM_TEST #endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ -// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! +// This file was GENERATED by command: +// pump.py gtest-param-util-generated.h.pump +// DO NOT EDIT BY HAND!!! // Copyright 2008 Google Inc. // All Rights Reserved. @@ -9179,8 +10619,8 @@ namespace testing { // include/gtest/gtest-param-test.h. template internal::ParamGenerator< - typename ::std::iterator_traits::value_type> ValuesIn( - ForwardIterator begin, ForwardIterator end); + typename ::testing::internal::IteratorTraits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end); template internal::ParamGenerator ValuesIn(const T (&array)[N]); @@ -11947,7 +13387,7 @@ class ValueArray50 { const T50 v50_; }; -#if GTEST_HAS_COMBINE +# if GTEST_HAS_COMBINE // INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. // // Generates values from the Cartesian product of values produced @@ -13931,7 +15371,7 @@ CartesianProductHolder10(const Generator1& g1, const Generator2& g2, const Generator10 g10_; }; // class CartesianProductHolder10 -#endif // GTEST_HAS_COMBINE +# endif // GTEST_HAS_COMBINE } // namespace internal } // namespace testing @@ -14054,11 +15494,10 @@ internal::ParamGenerator Range(T start, T end) { // template internal::ParamGenerator< - typename ::std::iterator_traits::value_type> ValuesIn( - ForwardIterator begin, - ForwardIterator end) { - typedef typename ::std::iterator_traits::value_type - ParamType; + typename ::testing::internal::IteratorTraits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end) { + typedef typename ::testing::internal::IteratorTraits + ::value_type ParamType; return internal::ParamGenerator( new internal::ValuesInIteratorRangeGenerator(begin, end)); } @@ -14970,7 +16409,7 @@ inline internal::ParamGenerator Bool() { return Values(false, true); } -#if GTEST_HAS_COMBINE +# if GTEST_HAS_COMBINE // Combine() allows the user to combine two or more sequences to produce // values of a Cartesian product of those sequences' elements. // @@ -15122,11 +16561,11 @@ internal::CartesianProductHolder10( g1, g2, g3, g4, g5, g6, g7, g8, g9, g10); } -#endif // GTEST_HAS_COMBINE +# endif // GTEST_HAS_COMBINE -#define TEST_P(test_case_name, test_name) \ +# define TEST_P(test_case_name, test_name) \ class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ : public test_case_name { \ public: \ @@ -15152,7 +16591,7 @@ internal::CartesianProductHolder10 \ gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ int gtest_##prefix##test_case_name##_dummy_ = \ @@ -15366,7 +16805,7 @@ class GTEST_API_ TestPartResultArray { }; // This interface knows how to report a test part result. -class GTEST_API_ TestPartResultReporterInterface { +class TestPartResultReporterInterface { public: virtual ~TestPartResultReporterInterface() {} @@ -15557,16 +16996,16 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); // // Expands to the name of the typedef for the type parameters of the // given test case. -#define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_ +# define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_ // The 'Types' template argument below must have spaces around it // since some compilers may choke on '>>' when passing a template // instance (e.g. Types) -#define TYPED_TEST_CASE(CaseName, Types) \ +# define TYPED_TEST_CASE(CaseName, Types) \ typedef ::testing::internal::TypeList< Types >::type \ GTEST_TYPE_PARAMS_(CaseName) -#define TYPED_TEST(CaseName, TestName) \ +# define TYPED_TEST(CaseName, TestName) \ template \ class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ : public CaseName { \ @@ -15575,7 +17014,7 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); typedef gtest_TypeParam_ TypeParam; \ virtual void TestBody(); \ }; \ - bool gtest_##CaseName##_##TestName##_registered_ = \ + bool gtest_##CaseName##_##TestName##_registered_ GTEST_ATTRIBUTE_UNUSED_ = \ ::testing::internal::TypeParameterizedTest< \ CaseName, \ ::testing::internal::TemplateSel< \ @@ -15596,31 +17035,31 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); // Expands to the namespace name that the type-parameterized tests for // the given type-parameterized test case are defined in. The exact // name of the namespace is subject to change without notice. -#define GTEST_CASE_NAMESPACE_(TestCaseName) \ +# define GTEST_CASE_NAMESPACE_(TestCaseName) \ gtest_case_##TestCaseName##_ // INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. // // Expands to the name of the variable used to remember the names of // the defined tests in the given test case. -#define GTEST_TYPED_TEST_CASE_P_STATE_(TestCaseName) \ +# define GTEST_TYPED_TEST_CASE_P_STATE_(TestCaseName) \ gtest_typed_test_case_p_state_##TestCaseName##_ // INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY. // // Expands to the name of the variable used to remember the names of // the registered tests in the given test case. -#define GTEST_REGISTERED_TEST_NAMES_(TestCaseName) \ +# define GTEST_REGISTERED_TEST_NAMES_(TestCaseName) \ gtest_registered_test_names_##TestCaseName##_ // The variables defined in the type-parameterized test macros are // static as typically these macros are used in a .h file that can be // #included in multiple translation units linked together. -#define TYPED_TEST_CASE_P(CaseName) \ +# define TYPED_TEST_CASE_P(CaseName) \ static ::testing::internal::TypedTestCasePState \ GTEST_TYPED_TEST_CASE_P_STATE_(CaseName) -#define TYPED_TEST_P(CaseName, TestName) \ +# define TYPED_TEST_P(CaseName, TestName) \ namespace GTEST_CASE_NAMESPACE_(CaseName) { \ template \ class TestName : public CaseName { \ @@ -15629,14 +17068,14 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); typedef gtest_TypeParam_ TypeParam; \ virtual void TestBody(); \ }; \ - static bool gtest_##TestName##_defined_ = \ + static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \ GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).AddTestName(\ __FILE__, __LINE__, #CaseName, #TestName); \ } \ template \ void GTEST_CASE_NAMESPACE_(CaseName)::TestName::TestBody() -#define REGISTER_TYPED_TEST_CASE_P(CaseName, ...) \ +# define REGISTER_TYPED_TEST_CASE_P(CaseName, ...) \ namespace GTEST_CASE_NAMESPACE_(CaseName) { \ typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_; \ } \ @@ -15647,8 +17086,8 @@ INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); // The 'Types' template argument below must have spaces around it // since some compilers may choke on '>>' when passing a template // instance (e.g. Types) -#define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types) \ - bool gtest_##Prefix##_##CaseName = \ +# define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types) \ + bool gtest_##Prefix##_##CaseName GTEST_ATTRIBUTE_UNUSED_ = \ ::testing::internal::TypeParameterizedTestCase::type>::Register(\ @@ -15731,6 +17170,11 @@ GTEST_DECLARE_int32_(stack_trace_depth); // non-zero code otherwise. GTEST_DECLARE_bool_(throw_on_failure); +// When this flag is set with a "host:port" string, on supported +// platforms test results are streamed to the specified port on +// the specified host machine. +GTEST_DECLARE_string_(stream_result_to); + // The upper limit for valid stack trace depths. const int kMaxStackTraceDepth = 100; @@ -15742,7 +17186,6 @@ class ExecDeathTest; class NoExecDeathTest; class FinalSuccessChecker; class GTestFlagSaver; -class TestInfoImpl; class TestResultAccessor; class TestEventListenersAccessor; class TestEventRepeater; @@ -15750,8 +17193,6 @@ class WindowsDeathTest; class UnitTestImpl* GetUnitTestImpl(); void ReportFailureInUnknownLocation(TestPartResult::Type result_type, const String& message); -class PrettyUnitTestResultPrinter; -class XmlUnitTestResultPrinter; // Converts a streamable value to a String. A NULL pointer is // converted to "(null)". When the input value is a ::string, @@ -15767,6 +17208,14 @@ String StreamableToString(const T& streamable) { } // namespace internal +// The friend relationship of some of these classes is cyclic. +// If we don't forward declare them the compiler might confuse the classes +// in friendship clauses with same named classes on the scope. +class Test; +class TestCase; +class TestInfo; +class UnitTest; + // A class for indicating whether an assertion was successful. When // the assertion wasn't successful, the AssertionResult object // remembers a non-empty message that describes how it failed. @@ -15865,20 +17314,33 @@ class GTEST_API_ AssertionResult { // assertion's expectation). When nothing has been streamed into the // object, returns an empty string. const char* message() const { - return message_.get() != NULL && message_->c_str() != NULL ? - message_->c_str() : ""; + return message_.get() != NULL ? message_->c_str() : ""; } // TODO(vladl@google.com): Remove this after making sure no clients use it. // Deprecated; please use message() instead. const char* failure_message() const { return message(); } // Streams a custom failure message into this object. - template AssertionResult& operator<<(const T& value); + template AssertionResult& operator<<(const T& value) { + AppendMessage(Message() << value); + return *this; + } + + // Allows streaming basic output manipulators such as endl or flush into + // this object. + AssertionResult& operator<<( + ::std::ostream& (*basic_manipulator)(::std::ostream& stream)) { + AppendMessage(Message() << basic_manipulator); + return *this; + } private: - // No implementation - we want AssertionResult to be - // copy-constructible but not assignable. - void operator=(const AssertionResult& other); + // Appends the contents of message to message_. + void AppendMessage(const Message& a_message) { + if (message_.get() == NULL) + message_.reset(new ::std::string); + message_->append(a_message.GetString().c_str()); + } // Stores result of the assertion predicate. bool success_; @@ -15886,19 +17348,10 @@ class GTEST_API_ AssertionResult { // construct is not satisfied with the predicate's outcome. // Referenced via a pointer to avoid taking too much stack frame space // with test assertions. - internal::scoped_ptr message_; -}; // class AssertionResult + internal::scoped_ptr< ::std::string> message_; -// Streams a custom failure message into this object. -template -AssertionResult& AssertionResult::operator<<(const T& value) { - Message msg; - if (message_.get() != NULL) - msg << *message_; - msg << value; - message_.reset(new internal::String(msg.GetString())); - return *this; -} + GTEST_DISALLOW_ASSIGN_(AssertionResult); +}; // Makes a successful assertion result. GTEST_API_ AssertionResult AssertionSuccess(); @@ -15935,7 +17388,7 @@ GTEST_API_ AssertionResult AssertionFailure(const Message& msg); // Test is not copyable. class GTEST_API_ Test { public: - friend class internal::TestInfoImpl; + friend class TestInfo; // Defines types for pointers to functions that set up and tear down // a test case. @@ -16012,6 +17465,10 @@ class GTEST_API_ Test { // Sets up, executes, and tears down the test. void Run(); + // Deletes self. We deliberately pick an unusual name for this + // internal method to avoid clashing with names used in user TESTs. + void DeleteSelf_() { delete this; } + // Uses a GTestFlagSaver to save and restore all Google Test flags. const internal::GTestFlagSaver* const gtest_flag_saver_; @@ -16126,7 +17583,6 @@ class GTEST_API_ TestResult { friend class UnitTest; friend class internal::DefaultGlobalTestPartResultReporter; friend class internal::ExecDeathTest; - friend class internal::TestInfoImpl; friend class internal::TestResultAccessor; friend class internal::UnitTestImpl; friend class internal::WindowsDeathTest; @@ -16206,16 +17662,26 @@ class GTEST_API_ TestInfo { ~TestInfo(); // Returns the test case name. - const char* test_case_name() const; + const char* test_case_name() const { return test_case_name_.c_str(); } // Returns the test name. - const char* name() const; + const char* name() const { return name_.c_str(); } - // Returns the test case comment. - const char* test_case_comment() const; + // Returns the name of the parameter type, or NULL if this is not a typed + // or a type-parameterized test. + const char* type_param() const { + if (type_param_.get() != NULL) + return type_param_->c_str(); + return NULL; + } - // Returns the test comment. - const char* comment() const; + // Returns the text representation of the value parameter, or NULL if this + // is not a value-parameterized test. + const char* value_param() const { + if (value_param_.get() != NULL) + return value_param_->c_str(); + return NULL; + } // Returns true if this test should run, that is if the test is not disabled // (or it is disabled but the also_run_disabled_tests flag has been specified) @@ -16233,47 +17699,70 @@ class GTEST_API_ TestInfo { // // For example, *A*:Foo.* is a filter that matches any string that // contains the character 'A' or starts with "Foo.". - bool should_run() const; + bool should_run() const { return should_run_; } // Returns the result of the test. - const TestResult* result() const; + const TestResult* result() const { return &result_; } private: + #if GTEST_HAS_DEATH_TEST friend class internal::DefaultDeathTestFactory; #endif // GTEST_HAS_DEATH_TEST friend class Test; friend class TestCase; - friend class internal::TestInfoImpl; friend class internal::UnitTestImpl; friend TestInfo* internal::MakeAndRegisterTestInfo( const char* test_case_name, const char* name, - const char* test_case_comment, const char* comment, + const char* type_param, + const char* value_param, internal::TypeId fixture_class_id, Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc, internal::TestFactoryBase* factory); - // Returns true if this test matches the user-specified filter. - bool matches_filter() const; - - // Increments the number of death tests encountered in this test so - // far. - int increment_death_test_count(); - - // Accessors for the implementation object. - internal::TestInfoImpl* impl() { return impl_; } - const internal::TestInfoImpl* impl() const { return impl_; } - // Constructs a TestInfo object. The newly constructed instance assumes // ownership of the factory object. TestInfo(const char* test_case_name, const char* name, - const char* test_case_comment, const char* comment, + const char* a_type_param, + const char* a_value_param, internal::TypeId fixture_class_id, internal::TestFactoryBase* factory); - // An opaque implementation object. - internal::TestInfoImpl* impl_; + // Increments the number of death tests encountered in this test so + // far. + int increment_death_test_count() { + return result_.increment_death_test_count(); + } + + // Creates the test object, runs it, records its result, and then + // deletes it. + void Run(); + + static void ClearTestResult(TestInfo* test_info) { + test_info->result_.Clear(); + } + + // These fields are immutable properties of the test. + const std::string test_case_name_; // Test case name + const std::string name_; // Test name + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const internal::scoped_ptr type_param_; + // Text representation of the value parameter, or NULL if this is not a + // value-parameterized test. + const internal::scoped_ptr value_param_; + const internal::TypeId fixture_class_id_; // ID of the test fixture class + bool should_run_; // True iff this test should run + bool is_disabled_; // True iff this test is disabled + bool matches_filter_; // True if this test matches the + // user-specified filter. + internal::TestFactoryBase* const factory_; // The factory that creates + // the test object + + // This field is mutable and needs to be reset before running the + // test for the second time. + TestResult result_; GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo); }; @@ -16291,9 +17780,11 @@ class GTEST_API_ TestCase { // Arguments: // // name: name of the test case + // a_type_param: the name of the test's type parameter, or NULL if + // this is not a type-parameterized test. // set_up_tc: pointer to the function that sets up the test case // tear_down_tc: pointer to the function that tears down the test case - TestCase(const char* name, const char* comment, + TestCase(const char* name, const char* a_type_param, Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc); @@ -16303,8 +17794,13 @@ class GTEST_API_ TestCase { // Gets the name of the TestCase. const char* name() const { return name_.c_str(); } - // Returns the test case comment. - const char* comment() const { return comment_.c_str(); } + // Returns the name of the parameter type, or NULL if this is not a + // type-parameterized test case. + const char* type_param() const { + if (type_param_.get() != NULL) + return type_param_->c_str(); + return NULL; + } // Returns true if any test in this test case should run. bool should_run() const { return should_run_; } @@ -16371,17 +17867,33 @@ class GTEST_API_ TestCase { // Runs every test in this TestCase. void Run(); + // Runs SetUpTestCase() for this TestCase. This wrapper is needed + // for catching exceptions thrown from SetUpTestCase(). + void RunSetUpTestCase() { (*set_up_tc_)(); } + + // Runs TearDownTestCase() for this TestCase. This wrapper is + // needed for catching exceptions thrown from TearDownTestCase(). + void RunTearDownTestCase() { (*tear_down_tc_)(); } + // Returns true iff test passed. - static bool TestPassed(const TestInfo * test_info); + static bool TestPassed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Passed(); + } // Returns true iff test failed. - static bool TestFailed(const TestInfo * test_info); + static bool TestFailed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Failed(); + } // Returns true iff test is disabled. - static bool TestDisabled(const TestInfo * test_info); + static bool TestDisabled(const TestInfo* test_info) { + return test_info->is_disabled_; + } // Returns true if the given test should run. - static bool ShouldRunTest(const TestInfo *test_info); + static bool ShouldRunTest(const TestInfo* test_info) { + return test_info->should_run(); + } // Shuffles the tests in this test case. void ShuffleTests(internal::Random* random); @@ -16391,8 +17903,9 @@ class GTEST_API_ TestCase { // Name of the test case. internal::String name_; - // Comment on the test case. - internal::String comment_; + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const internal::scoped_ptr type_param_; // The vector of TestInfos in their original order. It owns the // elements in the vector. std::vector test_info_list_; @@ -16471,7 +17984,7 @@ class TestEventListener { // Fired before the test starts. virtual void OnTestStart(const TestInfo& test_info) = 0; - // Fired after a failed assertion or a SUCCESS(). + // Fired after a failed assertion or a SUCCEED() invocation. virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0; // Fired after the test ends. @@ -16556,10 +18069,10 @@ class GTEST_API_ TestEventListeners { private: friend class TestCase; + friend class TestInfo; friend class internal::DefaultGlobalTestPartResultReporter; friend class internal::NoExecDeathTest; friend class internal::TestEventListenersAccessor; - friend class internal::TestInfoImpl; friend class internal::UnitTestImpl; // Returns repeater that broadcasts the TestEventListener events to all @@ -16801,30 +18314,6 @@ GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv); namespace internal { -// These overloaded versions handle ::std::string and ::std::wstring. -GTEST_API_ inline String FormatForFailureMessage(const ::std::string& str) { - return (Message() << '"' << str << '"').GetString(); -} - -#if GTEST_HAS_STD_WSTRING -GTEST_API_ inline String FormatForFailureMessage(const ::std::wstring& wstr) { - return (Message() << "L\"" << wstr << '"').GetString(); -} -#endif // GTEST_HAS_STD_WSTRING - -// These overloaded versions handle ::string and ::wstring. -#if GTEST_HAS_GLOBAL_STRING -GTEST_API_ inline String FormatForFailureMessage(const ::string& str) { - return (Message() << '"' << str << '"').GetString(); -} -#endif // GTEST_HAS_GLOBAL_STRING - -#if GTEST_HAS_GLOBAL_WSTRING -GTEST_API_ inline String FormatForFailureMessage(const ::wstring& wstr) { - return (Message() << "L\"" << wstr << '"').GetString(); -} -#endif // GTEST_HAS_GLOBAL_WSTRING - // Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) // operand to be used in a failure message. The type (but not value) // of the other operand may affect the format. This allows us to @@ -16840,7 +18329,9 @@ GTEST_API_ inline String FormatForFailureMessage(const ::wstring& wstr) { template String FormatForComparisonFailureMessage(const T1& value, const T2& /* other_operand */) { - return FormatForFailureMessage(value); + // C++Builder compiles this incorrectly if the namespace isn't explicitly + // given. + return ::testing::PrintToString(value); } // The helper function for {ASSERT|EXPECT}_EQ. @@ -16850,8 +18341,8 @@ AssertionResult CmpHelperEQ(const char* expected_expression, const T1& expected, const T2& actual) { #ifdef _MSC_VER -#pragma warning(push) // Saves the current warning state. -#pragma warning(disable:4389) // Temporarily disables warning on +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4389) // Temporarily disables warning on // signed/unsigned mismatch. #endif @@ -16860,7 +18351,7 @@ AssertionResult CmpHelperEQ(const char* expected_expression, } #ifdef _MSC_VER -#pragma warning(pop) // Restores the warning state. +# pragma warning(pop) // Restores the warning state. #endif return EqFailure(expected_expression, @@ -16911,7 +18402,7 @@ class EqHelper { }; // This specialization is used when the first argument to ASSERT_EQ() -// is a null pointer literal. +// is a null pointer literal, like NULL, false, or 0. template <> class EqHelper { public: @@ -16920,24 +18411,38 @@ class EqHelper { // NOT a pointer, e.g. ASSERT_EQ(0, AnIntFunction()) or // EXPECT_EQ(false, a_bool). template - static AssertionResult Compare(const char* expected_expression, - const char* actual_expression, - const T1& expected, - const T2& actual) { + static AssertionResult Compare( + const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual, + // The following line prevents this overload from being considered if T2 + // is not a pointer type. We need this because ASSERT_EQ(NULL, my_ptr) + // expands to Compare("", "", NULL, my_ptr), which requires a conversion + // to match the Secret* in the other overload, which would otherwise make + // this template match better. + typename EnableIf::value>::type* = 0) { return CmpHelperEQ(expected_expression, actual_expression, expected, actual); } - // This version will be picked when the second argument to - // ASSERT_EQ() is a pointer, e.g. ASSERT_EQ(NULL, a_pointer). - template - static AssertionResult Compare(const char* expected_expression, - const char* actual_expression, - const T1& /* expected */, - T2* actual) { + // This version will be picked when the second argument to ASSERT_EQ() is a + // pointer, e.g. ASSERT_EQ(NULL, a_pointer). + template + static AssertionResult Compare( + const char* expected_expression, + const char* actual_expression, + // We used to have a second template parameter instead of Secret*. That + // template parameter would deduce to 'long', making this a better match + // than the first overload even without the first overload's EnableIf. + // Unfortunately, gcc with -Wconversion-null warns when "passing NULL to + // non-pointer argument" (even a deduced integral argument), so the old + // implementation caused warnings in user code. + Secret* /* expected (NULL) */, + T* actual) { // We already know that 'expected' is a null pointer. return CmpHelperEQ(expected_expression, actual_expression, - static_cast(NULL), actual); + static_cast(NULL), actual); } }; @@ -16958,11 +18463,10 @@ AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ if (val1 op val2) {\ return AssertionSuccess();\ } else {\ - Message msg;\ - msg << "Expected: (" << expr1 << ") " #op " (" << expr2\ + return AssertionFailure() \ + << "Expected: (" << expr1 << ") " #op " (" << expr2\ << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ << " vs " << FormatForComparisonFailureMessage(val2, val1);\ - return AssertionFailure(msg);\ }\ }\ GTEST_API_ AssertionResult CmpHelper##op_name(\ @@ -17090,18 +18594,18 @@ AssertionResult CmpHelperFloatingPointEQ(const char* expected_expression, return AssertionSuccess(); } - StrStream expected_ss; + ::std::stringstream expected_ss; expected_ss << std::setprecision(std::numeric_limits::digits10 + 2) << expected; - StrStream actual_ss; + ::std::stringstream actual_ss; actual_ss << std::setprecision(std::numeric_limits::digits10 + 2) << actual; return EqFailure(expected_expression, actual_expression, - StrStreamToString(&expected_ss), - StrStreamToString(&actual_ss), + StringStreamToString(&expected_ss), + StringStreamToString(&actual_ss), false); } @@ -17159,9 +18663,13 @@ class GTEST_API_ AssertHelper { } // namespace internal #if GTEST_HAS_PARAM_TEST -// The abstract base class that all value-parameterized tests inherit from. +// The pure interface class that all value-parameterized tests inherit from. +// A value-parameterized class must inherit from both ::testing::Test and +// ::testing::WithParamInterface. In most cases that just means inheriting +// from ::testing::TestWithParam, but more complicated test hierarchies +// may need to inherit from Test and WithParamInterface at different levels. // -// This class adds support for accessing the test parameter value via +// This interface has support for accessing the test parameter value via // the GetParam() method. // // Use it with one of the parameter generator defining functions, like Range(), @@ -17190,12 +18698,16 @@ class GTEST_API_ AssertHelper { // INSTANTIATE_TEST_CASE_P(OneToTenRange, FooTest, ::testing::Range(1, 10)); template -class TestWithParam : public Test { +class WithParamInterface { public: typedef T ParamType; + virtual ~WithParamInterface() {} // The current parameter value. Is also available in the test fixture's - // constructor. + // constructor. This member function is non-static, even though it only + // references static data, to reduce the opportunity for incorrect uses + // like writing 'WithParamInterface::GetParam()' for a test that + // uses a fixture whose parameter type is int. const ParamType& GetParam() const { return *parameter_; } private: @@ -17208,12 +18720,19 @@ class TestWithParam : public Test { // Static value used for accessing parameter during a test lifetime. static const ParamType* parameter_; - // TestClass must be a subclass of TestWithParam. + // TestClass must be a subclass of WithParamInterface and Test. template friend class internal::ParameterizedTestFactory; }; template -const T* TestWithParam::parameter_ = NULL; +const T* WithParamInterface::parameter_ = NULL; + +// Most value-parameterized classes can ignore the existence of +// WithParamInterface, and can just inherit from ::testing::TestWithParam. + +template +class TestWithParam : public Test, public WithParamInterface { +}; #endif // GTEST_HAS_PARAM_TEST @@ -17245,13 +18764,19 @@ const T* TestWithParam::parameter_ = NULL; // Generates a nonfatal failure with a generic message. #define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed") +// Generates a nonfatal failure at the given source file location with +// a generic message. +#define ADD_FAILURE_AT(file, line) \ + GTEST_MESSAGE_AT_(file, line, "Failed", \ + ::testing::TestPartResult::kNonFatalFailure) + // Generates a fatal failure with a generic message. #define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed") // Define this macro to 1 to omit the definition of FAIL(), which is a // generic name and clashes with some other libraries. #if !GTEST_DONT_DEFINE_FAIL -#define FAIL() GTEST_FAIL() +# define FAIL() GTEST_FAIL() #endif // Generates a success with a generic message. @@ -17260,7 +18785,7 @@ const T* TestWithParam::parameter_ = NULL; // Define this macro to 1 to omit the definition of SUCCEED(), which // is a generic name and clashes with some other libraries. #if !GTEST_DONT_DEFINE_SUCCEED -#define SUCCEED() GTEST_SUCCEED() +# define SUCCEED() GTEST_SUCCEED() #endif // Macros for testing exceptions. @@ -17332,7 +18857,7 @@ const T* TestWithParam::parameter_ = NULL; // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// This file is AUTOMATICALLY GENERATED on 10/02/2008 by command +// This file is AUTOMATICALLY GENERATED on 09/24/2010 by command // 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND! // // Implements a family of generic predicate assertion macros. @@ -17342,7 +18867,7 @@ const T* TestWithParam::parameter_ = NULL; // Makes sure this header is not included before gtest.h. #ifndef GTEST_INCLUDE_GTEST_GTEST_H_ -#error Do not include gtest_pred_impl.h directly. Include gtest.h instead. +# error Do not include gtest_pred_impl.h directly. Include gtest.h instead. #endif // GTEST_INCLUDE_GTEST_GTEST_H_ // This header implements a family of generic predicate assertion @@ -17395,11 +18920,9 @@ AssertionResult AssertPred1Helper(const char* pred_text, const T1& v1) { if (pred(v1)) return AssertionSuccess(); - Message msg; - msg << pred_text << "(" - << e1 << ") evaluates to false, where" - << "\n" << e1 << " evaluates to " << v1; - return AssertionFailure(msg); + return AssertionFailure() << pred_text << "(" + << e1 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1; } // Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1. @@ -17441,13 +18964,11 @@ AssertionResult AssertPred2Helper(const char* pred_text, const T2& v2) { if (pred(v1, v2)) return AssertionSuccess(); - Message msg; - msg << pred_text << "(" - << e1 << ", " - << e2 << ") evaluates to false, where" - << "\n" << e1 << " evaluates to " << v1 - << "\n" << e2 << " evaluates to " << v2; - return AssertionFailure(msg); + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2; } // Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. @@ -17494,15 +19015,13 @@ AssertionResult AssertPred3Helper(const char* pred_text, const T3& v3) { if (pred(v1, v2, v3)) return AssertionSuccess(); - Message msg; - msg << pred_text << "(" - << e1 << ", " - << e2 << ", " - << e3 << ") evaluates to false, where" - << "\n" << e1 << " evaluates to " << v1 - << "\n" << e2 << " evaluates to " << v2 - << "\n" << e3 << " evaluates to " << v3; - return AssertionFailure(msg); + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3; } // Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3. @@ -17554,17 +19073,15 @@ AssertionResult AssertPred4Helper(const char* pred_text, const T4& v4) { if (pred(v1, v2, v3, v4)) return AssertionSuccess(); - Message msg; - msg << pred_text << "(" - << e1 << ", " - << e2 << ", " - << e3 << ", " - << e4 << ") evaluates to false, where" - << "\n" << e1 << " evaluates to " << v1 - << "\n" << e2 << " evaluates to " << v2 - << "\n" << e3 << " evaluates to " << v3 - << "\n" << e4 << " evaluates to " << v4; - return AssertionFailure(msg); + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4; } // Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4. @@ -17621,19 +19138,17 @@ AssertionResult AssertPred5Helper(const char* pred_text, const T5& v5) { if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess(); - Message msg; - msg << pred_text << "(" - << e1 << ", " - << e2 << ", " - << e3 << ", " - << e4 << ", " - << e5 << ") evaluates to false, where" - << "\n" << e1 << " evaluates to " << v1 - << "\n" << e2 << " evaluates to " << v2 - << "\n" << e3 << " evaluates to " << v3 - << "\n" << e4 << " evaluates to " << v4 - << "\n" << e5 << " evaluates to " << v5; - return AssertionFailure(msg); + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ", " + << e5 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4 + << "\n" << e5 << " evaluates to " << v5; } // Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5. @@ -17733,21 +19248,48 @@ AssertionResult AssertPred5Helper(const char* pred_text, #define EXPECT_GT(val1, val2) \ EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) -#define ASSERT_EQ(expected, actual) \ +#define GTEST_ASSERT_EQ(expected, actual) \ ASSERT_PRED_FORMAT2(::testing::internal:: \ EqHelper::Compare, \ expected, actual) -#define ASSERT_NE(val1, val2) \ +#define GTEST_ASSERT_NE(val1, val2) \ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) -#define ASSERT_LE(val1, val2) \ +#define GTEST_ASSERT_LE(val1, val2) \ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) -#define ASSERT_LT(val1, val2) \ +#define GTEST_ASSERT_LT(val1, val2) \ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) -#define ASSERT_GE(val1, val2) \ +#define GTEST_ASSERT_GE(val1, val2) \ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) -#define ASSERT_GT(val1, val2) \ +#define GTEST_ASSERT_GT(val1, val2) \ ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) +// Define macro GTEST_DONT_DEFINE_ASSERT_XY to 1 to omit the definition of +// ASSERT_XY(), which clashes with some users' own code. + +#if !GTEST_DONT_DEFINE_ASSERT_EQ +# define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_NE +# define ASSERT_NE(val1, val2) GTEST_ASSERT_NE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_LE +# define ASSERT_LE(val1, val2) GTEST_ASSERT_LE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_LT +# define ASSERT_LT(val1, val2) GTEST_ASSERT_LT(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_GE +# define ASSERT_GE(val1, val2) GTEST_ASSERT_GE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_GT +# define ASSERT_GT(val1, val2) GTEST_ASSERT_GT(val1, val2) +#endif + // C String Comparisons. All tests treat NULL and any non-NULL string // as different. Two NULLs are equal. // @@ -17844,16 +19386,16 @@ GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2, // expected result and the actual result with both a human-readable // string representation of the error, if available, as well as the // hex result code. -#define EXPECT_HRESULT_SUCCEEDED(expr) \ +# define EXPECT_HRESULT_SUCCEEDED(expr) \ EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) -#define ASSERT_HRESULT_SUCCEEDED(expr) \ +# define ASSERT_HRESULT_SUCCEEDED(expr) \ ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) -#define EXPECT_HRESULT_FAILED(expr) \ +# define EXPECT_HRESULT_FAILED(expr) \ EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) -#define ASSERT_HRESULT_FAILED(expr) \ +# define ASSERT_HRESULT_FAILED(expr) \ ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) #endif // GTEST_OS_WINDOWS @@ -17888,17 +19430,6 @@ GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2, ::testing::internal::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\ __FILE__, __LINE__, ::testing::Message() << (message)) -namespace internal { - -// This template is declared, but intentionally undefined. -template -struct StaticAssertTypeEqHelper; - -template -struct StaticAssertTypeEqHelper {}; - -} // namespace internal - // Compile-time assertion for type equality. // StaticAssertTypeEq() compiles iff type1 and type2 are // the same type. The value it returns is not interesting. @@ -17931,7 +19462,7 @@ struct StaticAssertTypeEqHelper {}; // to cause a compiler error. template bool StaticAssertTypeEq() { - internal::StaticAssertTypeEqHelper(); + (void)internal::StaticAssertTypeEqHelper(); return true; } @@ -17967,7 +19498,7 @@ bool StaticAssertTypeEq() { // Define this macro to 1 to omit the definition of TEST(), which // is a generic name and clashes with some other libraries. #if !GTEST_DONT_DEFINE_TEST -#define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name) +# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name) #endif // Defines a test that uses a test fixture. diff --git a/modules/ts/include/opencv2/ts/ts_perf.hpp b/modules/ts/include/opencv2/ts/ts_perf.hpp new file mode 100644 index 000000000..fbd7a2dc0 --- /dev/null +++ b/modules/ts/include/opencv2/ts/ts_perf.hpp @@ -0,0 +1,444 @@ +#ifndef __OPENCV_TS_PERF_HPP__ +#define __OPENCV_TS_PERF_HPP__ + +#include "opencv2/core/core.hpp" +#include "ts_gtest.h" + +#if defined(ANDROID) && defined(USE_ANDROID_LOGGING) +#include + +#define PERF_TESTS_LOG_TAG "OpenCV_perf" +#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, PERF_TESTS_LOG_TAG, __VA_ARGS__)) +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, PERF_TESTS_LOG_TAG, __VA_ARGS__)) +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, PERF_TESTS_LOG_TAG, __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, PERF_TESTS_LOG_TAG, __VA_ARGS__)) +#else +#define LOGD(_str, ...) do{printf(_str , ## __VA_ARGS__); printf("\n");fflush(stdout);} while(0) +#define LOGI(_str, ...) do{printf(_str , ## __VA_ARGS__); printf("\n");fflush(stdout);} while(0) +#define LOGW(_str, ...) do{printf(_str , ## __VA_ARGS__); printf("\n");fflush(stdout);} while(0) +#define LOGE(_str, ...) do{printf(_str , ## __VA_ARGS__); printf("\n");fflush(stdout);} while(0) +#endif + + +namespace perf +{ + +/*****************************************************************************************\ +* Predefined typical frame sizes and typical test parameters * +\*****************************************************************************************/ +const cv::Size szQVGA = cv::Size(320, 240); +const cv::Size szVGA = cv::Size(640, 480); +const cv::Size szSVGA = cv::Size(800, 600); +const cv::Size szXGA = cv::Size(1024, 768); +const cv::Size szSXGA = cv::Size(1280, 1024); + +const cv::Size sznHD = cv::Size(640, 360); +const cv::Size szqHD = cv::Size(960, 540); +const cv::Size sz720p = cv::Size(1280, 720); +const cv::Size sz1080p = cv::Size(1920, 1080); + +const cv::Size szODD = cv::Size(127, 61); + +#define SZ_ALL_VGA ::testing::Values(::perf::szQVGA, ::perf::szVGA, ::perf::szSVGA) +#define SZ_ALL_GA ::testing::Values(::perf::szQVGA, ::perf::szVGA, ::perf::szSVGA, ::perf::szXGA, ::perf::szSXGA) +#define SZ_ALL_HD ::testing::Values(::perf::sznHD, ::perf::szqHD, ::perf::sz720p, ::perf::sz1080p) +#define SZ_ALL ::testing::Values(::perf::szQVGA, ::perf::szVGA, ::perf::szSVGA, ::perf::szXGA, ::perf::szSXGA, ::perf::sznHD, ::perf::szqHD, ::perf::sz720p, ::perf::sz1080p) +#define SZ_TYPICAL ::testing::Values(::perf::szVGA, ::perf::szqHD, ::perf::sz720p, ::perf::szODD) + + +#define TYPICAL_MAT_SIZES ::perf::szVGA, ::perf::sz720p, ::perf::sz1080p, ::perf::szODD +#define TYPICAL_MAT_TYPES CV_8UC1, CV_8UC4, CV_32FC1 +#define TYPICAL_MATS testing::Combine( testing::Values( TYPICAL_MAT_SIZES ), testing::Values( TYPICAL_MAT_TYPES ) ) +#define TYPICAL_MATS_C1 testing::Combine( testing::Values( TYPICAL_MAT_SIZES ), testing::Values( CV_8UC1, CV_32FC1 ) ) +#define TYPICAL_MATS_C4 testing::Combine( testing::Values( TYPICAL_MAT_SIZES ), testing::Values( CV_8UC4 ) ) + + +/*****************************************************************************************\ +* MatType - printable wrapper over integer 'type' of Mat * +\*****************************************************************************************/ +class MatType +{ +public: + MatType(int val=0) : _type(val) {} + operator int() const {return _type;} + +private: + int _type; +}; + +/*****************************************************************************************\ +* CV_ENUM and CV_FLAGS - macro to create printable wrappers for defines and enums * +\*****************************************************************************************/ + +#define CV_ENUM(class_name, ...) \ +class CV_EXPORTS class_name {\ +public:\ + class_name(int val = 0) : _val(val) {}\ + operator int() const {return _val;}\ + void PrintTo(std::ostream* os) const {\ + const int vals[] = {__VA_ARGS__};\ + const char* svals = #__VA_ARGS__;\ + for(int i = 0, pos = 0; i < (int)(sizeof(vals)/sizeof(int)); ++i){\ + while(isspace(svals[pos]) || svals[pos] == ',') ++pos;\ + int start = pos;\ + while(!(isspace(svals[pos]) || svals[pos] == ',' || svals[pos] == 0)) ++pos;\ + if (_val == vals[i]) {\ + *os << std::string(svals + start, svals + pos);\ + return;\ + }\ + }\ + *os << "UNKNOWN";\ + }\ +private: int _val;\ +};\ +inline void PrintTo(const class_name& t, std::ostream* os) { t.PrintTo(os); } + +CV_ENUM(MatDepth, CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F, CV_64F, CV_USRTYPE1) + +#define CV_FLAGS(class_name, ...) \ +class CV_EXPORTS class_name {\ +public:\ + class_name(int val = 0) : _val(val) {}\ + operator int() const {return _val;}\ + void PrintTo(std::ostream* os) const {\ + const int vals[] = {__VA_ARGS__};\ + const char* svals = #__VA_ARGS__;\ + int value = _val;\ + bool first = true;\ + for(int i = 0, pos = 0; i < (int)(sizeof(vals)/sizeof(int)); ++i){\ + while(isspace(svals[pos]) || svals[pos] == ',') ++pos;\ + int start = pos;\ + while(!(isspace(svals[pos]) || svals[pos] == ',' || svals[pos] == 0)) ++pos;\ + if ((value & vals[i]) == vals[i]) {\ + value &= ~vals[i]; \ + if (first) first = false; else *os << "|"; \ + *os << std::string(svals + start, svals + pos);\ + if (!value) return;\ + }\ + }\ + if (first) *os << "UNKNOWN";\ + }\ +private: int _val;\ +};\ +inline void PrintTo(const class_name& t, std::ostream* os) { t.PrintTo(os); } + +/*****************************************************************************************\ +* Regression control utility for performance testing * +\*****************************************************************************************/ +class CV_EXPORTS Regression +{ +public: + static Regression& add(const std::string& name, cv::InputArray array, double eps = DBL_EPSILON); + static void Init(const std::string& testSuitName, const std::string& ext = ".xml"); + + Regression& operator() (const std::string& name, cv::InputArray array, double eps = DBL_EPSILON); + +private: + static Regression& instance(); + Regression(); + ~Regression(); + + Regression(const Regression&); + Regression& operator=(const Regression&); + + cv::RNG regRNG;//own random numbers generator to make collection and verification work identical + std::string storageInPath; + std::string storageOutPath; + cv::FileStorage storageIn; + cv::FileStorage storageOut; + cv::FileNode rootIn; + std::string currentTestNodeName; + cv::FileStorage& write(); + + static std::string getCurrentTestNodeName(); + static bool isVector(cv::InputArray a); + static double getElem(cv::Mat& m, int x, int y, int cn = 0); + + void init(const std::string& testSuitName, const std::string& ext); + void write(cv::InputArray array); + void write(cv::Mat m); + void verify(cv::FileNode node, cv::InputArray array, double eps); + void verify(cv::FileNode node, cv::Mat actual, double eps, std::string argname); +}; + +#define SANITY_CHECK(array, ...) ::perf::Regression::add(#array, array , ## __VA_ARGS__) + + +/*****************************************************************************************\ +* Container for performance metrics * +\*****************************************************************************************/ +typedef struct CV_EXPORTS performance_metrics +{ + size_t bytesIn; + size_t bytesOut; + unsigned int samples; + unsigned int outliers; + double gmean; + double gstddev;//stddev for log(time) + double mean; + double stddev; + double median; + double min; + double frequency; + int terminationReason; + + enum + { + TERM_ITERATIONS = 0, + TERM_TIME = 1, + TERM_UNKNOWN = 2 + }; + + performance_metrics(); +} performance_metrics; + + +/*****************************************************************************************\ +* Base fixture for performance tests * +\*****************************************************************************************/ +class CV_EXPORTS TestBase: public ::testing::Test +{ +public: + TestBase(); + + static void Init(int argc, const char* const argv[]); + +protected: + virtual void PerfTestBody() = 0; + + virtual void SetUp(); + virtual void TearDown(); + + void startTimer(); + void stopTimer(); + bool next(); + + std::string getDataPath(const std::string& relativePath); + + //_declareHelper declare; + + enum + { + WARMUP_READ, + WARMUP_WRITE, + WARMUP_RNG, + WARMUP_NONE + }; + static void warmup(cv::InputOutputArray a, int wtype = WARMUP_READ); + + performance_metrics& calcMetrics(); + void reportMetrics(bool toJUnitXML = false); +private: + typedef std::vector > SizeVector; + typedef std::vector TimeVector; + + SizeVector inputData; + SizeVector outputData; + unsigned int getTotalInputSize() const; + unsigned int getTotalOutputSize() const; + + TimeVector times; + int64 lastTime; + int64 totalTime; + int64 timeLimit; + static int64 timeLimitDefault; + + unsigned int nIters; + unsigned int currentIter; + + performance_metrics metrics; + void validateMetrics(); + + static int64 _timeadjustment; + static int64 _calibrate(); + + static void warmup(cv::Mat m, int wtype); + static int getSizeInBytes(cv::InputArray a); + static cv::Size getSize(cv::InputArray a); + static void declareArray(SizeVector& sizes, cv::InputOutputArray a, int wtype = 0); + + class CV_EXPORTS _declareHelper + { + public: + _declareHelper& in(cv::InputOutputArray a1, int wtype = WARMUP_READ); + _declareHelper& in(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype = WARMUP_READ); + _declareHelper& in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype = WARMUP_READ); + _declareHelper& in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype = WARMUP_READ); + + _declareHelper& out(cv::InputOutputArray a1, int wtype = WARMUP_WRITE); + _declareHelper& out(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype = WARMUP_WRITE); + _declareHelper& out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype = WARMUP_WRITE); + _declareHelper& out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype = WARMUP_WRITE); + + _declareHelper& iterations(int n); + _declareHelper& time(double timeLimitSecs); + private: + TestBase* test; + _declareHelper(TestBase* t); + _declareHelper(const _declareHelper&); + _declareHelper& operator=(const _declareHelper&); + friend class TestBase; + }; + friend class _declareHelper; + +public: + _declareHelper declare; +}; + +template class TestBaseWithParam: public TestBase, public ::testing::WithParamInterface {}; + +typedef std::tr1::tuple Size_MatType_t; +typedef TestBaseWithParam Size_MatType; + +/*****************************************************************************************\ +* Print functions for googletest * +\*****************************************************************************************/ +CV_EXPORTS void PrintTo(const MatType& t, std::ostream* os); + +} //namespace perf + +namespace cv +{ + +CV_EXPORTS void PrintTo(const Size& sz, ::std::ostream* os); + +} //namespace cv + + +/*****************************************************************************************\ +* Macro definitions for performance tests * +\*****************************************************************************************/ +#define PERF_PROXY_NAMESPACE_NAME_(test_case_name, test_name) \ + test_case_name##_##test_name##_perf_namespace_proxy + +// Defines a performance test. +// +// The first parameter is the name of the test case, and the second +// parameter is the name of the test within the test case. +// +// The user should put his test code between braces after using this +// macro. Example: +// +// PERF_TEST(FooTest, InitializesCorrectly) { +// Foo foo; +// EXPECT_TRUE(foo.StatusIsOK()); +// } +#define PERF_TEST(test_case_name, test_name)\ + namespace PERF_PROXY_NAMESPACE_NAME_(test_case_name, test_name) {\ + class TestBase {/*compile error for this class means that you are trying to use perf::TestBase as a fixture*/};\ + class test_case_name : public ::perf::TestBase {\ + public:\ + test_case_name() {}\ + protected:\ + virtual void PerfTestBody();\ + };\ + TEST_F(test_case_name, test_name){\ + try {\ + PerfTestBody();\ + }catch(cv::Exception e) { FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws:\n " << e.what(); }\ + catch(...) { FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws."; }\ + }\ + }\ + void PERF_PROXY_NAMESPACE_NAME_(test_case_name, test_name)::test_case_name::PerfTestBody() + +// Defines a performance test that uses a test fixture. +// +// The first parameter is the name of the test fixture class, which +// also doubles as the test case name. The second parameter is the +// name of the test within the test case. +// +// A test fixture class must be declared earlier. The user should put +// his test code between braces after using this macro. Example: +// +// class FooTest : public ::perf::TestBase { +// protected: +// virtual void SetUp() { TestBase::SetUp(); b_.AddElement(3); } +// +// Foo a_; +// Foo b_; +// }; +// +// PERF_TEST_F(FooTest, InitializesCorrectly) { +// EXPECT_TRUE(a_.StatusIsOK()); +// } +// +// PERF_TEST_F(FooTest, ReturnsElementCountCorrectly) { +// EXPECT_EQ(0, a_.size()); +// EXPECT_EQ(1, b_.size()); +// } +#define PERF_TEST_F(fixture, testname) \ + namespace PERF_PROXY_NAMESPACE_NAME_(fixture, testname) {\ + class TestBase {/*compile error for this class means that you are trying to use perf::TestBase as a fixture*/};\ + class fixture : public ::fixture {\ + public:\ + fixture() {}\ + protected:\ + virtual void PerfTestBody();\ + };\ + TEST_F(fixture, testname){\ + try {\ + PerfTestBody();\ + }catch(cv::Exception e) { FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws:\n " << e.what(); }\ + catch(...) { FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws."; }\ + }\ + }\ + void PERF_PROXY_NAMESPACE_NAME_(fixture, testname)::fixture::PerfTestBody() + +// Defines a parametrized performance test. +// +// The first parameter is the name of the test fixture class, which +// also doubles as the test case name. The second parameter is the +// name of the test within the test case. +// +// The user should put his test code between braces after using this +// macro. Example: +// +// typedef ::perf::TestBaseWithParam FooTest; +// +// PERF_TEST_P(FooTest, DoTestingRight, ::testing::Values(::perf::szVGA, ::perf::sz720p) { +// cv::Mat b(GetParam(), CV_8U, cv::Scalar(10)); +// cv::Mat a(GetParam(), CV_8U, cv::Scalar(20)); +// cv::Mat c(GetParam(), CV_8U, cv::Scalar(0)); +// +// declare.in(a, b).out(c).time(0.5); +// +// SIMPLE_TEST_CYCLE() cv::add(a, b, c); +// +// SANITY_CHECK(c); +// } +#define PERF_TEST_P(fixture, name, params) \ + class fixture##_##name : public ::fixture {\ + public:\ + fixture##_##name() {}\ + protected:\ + virtual void PerfTestBody();\ + };\ + TEST_P(fixture##_##name, name /*perf*/){\ + try {\ + PerfTestBody();\ + }catch(cv::Exception e) { FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws:\n " << e.what(); }\ + catch(...) { FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws."; }\ + }\ + INSTANTIATE_TEST_CASE_P(/*none*/, fixture##_##name, params);\ + void fixture##_##name::PerfTestBody() + + +#define CV_PERF_TEST_MAIN(testsuitname) \ +int main(int argc, char **argv)\ +{\ + ::perf::Regression::Init(#testsuitname);\ + ::perf::TestBase::Init(argc, argv);\ + ::testing::InitGoogleTest(&argc, argv);\ + return RUN_ALL_TESTS();\ +} + +#define TEST_CYCLE(n) for(declare.iterations(n); startTimer(), next(); stopTimer()) +#define SIMPLE_TEST_CYCLE() for(; startTimer(), next(); stopTimer()) + +//flags +namespace perf +{ +//GTEST_DECLARE_int32_(allowed_outliers); +} //namespace perf + +#endif //__OPENCV_TS_PERF_HPP__ diff --git a/modules/ts/misc/chart.py b/modules/ts/misc/chart.py new file mode 100644 index 000000000..b8bfea390 --- /dev/null +++ b/modules/ts/misc/chart.py @@ -0,0 +1,180 @@ +import testlog_parser, sys, os, xml, re +from table_formatter import * +from optparse import OptionParser + +cvsize_re = re.compile("^\d+x\d+$") +cvtype_re = re.compile("^(8U|8S|16U|16S|32S|32F|64F)C\d{1,3}$") + +def keyselector(a): + if cvsize_re.match(a): + size = [int(d) for d in a.split('x')] + return size[0] * size[1] + elif cvtype_re.match(a): + depth = 7 + if a[0] == '8': + depth = (0, 1) [a[1] == 'S'] + elif a[0] == '1': + depth = (2, 3) [a[2] == 'S'] + elif a[2] == 'S': + depth = 4 + elif a[0] == '3': + depth = 5 + elif a[0] == '6': + depth = 6 + channels = int(a[a.index('C') + 1:]) + #return (depth & 7) + ((channels - 1) << 3) + return ((channels-1) & 511) + (depth << 8) + return a + +def getValueParams(test): + param = test.get("value_param") + if not param: + return [] + if param.startswith("("): + param = param[1:] + if param.endswith(")"): + param = param[:-1] + return [p.strip() for p in param.split(",")] + +def nextPermutation(indexes, lists, x, y): + idx = len(indexes)-1 + while idx >= 0: + while idx == x or idx == y: + idx -= 1 + if idx < 0: + return False + v = indexes[idx] + 1 + if v < len(lists[idx]): + indexes[idx] = v; + return True; + else: + indexes[idx] = 0; + idx -= 1 + return False + +def getTestWideName(sname, indexes, lists, x, y): + name = sname + "::(" + for i in range(len(indexes)): + if i > 0: + name += ", " + if i == x: + name += "X" + elif i == y: + name += "Y" + else: + name += lists[i][indexes[i]] + return str(name + ")") + +def getTest(stests, x, y, row, col): + for pair in stests: + if pair[1][x] == row and pair[1][y] == col: + return pair[0] + return None + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option("-o", "--output", dest="format", help="output results in text format (can be 'txt', 'html' or 'auto' - default)", metavar="FMT", default="auto") + parser.add_option("-u", "--units", dest="units", help="units for output values (s, ms (default), mks, ns or ticks)", metavar="UNITS", default="ms") + parser.add_option("-m", "--metric", dest="metric", help="output metric", metavar="NAME", default="gmean") + parser.add_option("-x", "", dest="x", help="argument number for rows", metavar="ROW", default=1) + parser.add_option("-y", "", dest="y", help="argument number for columns", metavar="COL", default=0) + parser.add_option("-f", "--filter", dest="filter", help="regex to filter tests", metavar="REGEX", default=None) + (options, args) = parser.parse_args() + + if len(args) != 1: + print >> sys.stderr, "Usage:\n", os.path.basename(sys.argv[0]), ".xml" + exit(1) + + options.generateHtml = detectHtmlOutputType(options.format) + args[0] = os.path.basename(args[0]) + if options.metric not in metrix_table: + options.metric = "gmean" + if options.metric.endswith("%"): + options.metric = options.metric[:-1] + getter = metrix_table[options.metric][1] + + tests = testlog_parser.parseLogFile(args[0]) + if options.filter: + expr = re.compile(options.filter) + tests = [(t,getValueParams(t)) for t in tests if expr.search(str(t))] + + if not tests: + print >> sys.stderr, "Error - no tests matched" + exit(1) + + argsnum = len(tests[0][1]) + sname = tests[0][0].shortName() + + arglists = [] + for i in range(argsnum): + arglists.append({}) + + for pair in tests: + if len(pair[1]) != argsnum: + print >> sys.stderr, "Error - unable to create chart tables for functions having different argument numbers" + exit(1) + if pair[0].shortName() != sname: + print >> sys.stderr, "Error - unable to create chart tables for functions from different test suits:" + print >> sys.stderr, "First: ", sname + print >> sys.stderr, "Second:", pair[0].shortName() + exit(1) + for i in range(argsnum): + arglists[i][pair[1][i]] = 1 + + for i in range(argsnum): + arglists[i] = sorted([str(key) for key in arglists[i].iterkeys()], key=keyselector) + + if options.generateHtml: + htmlPrintHeader(sys.stdout, "Report %s for %s" % (args[0], sname)) + + indexes = [0] * argsnum + x = int(options.x) + y = int(options.y) + if x == y or x < 0 or y < 0 or x >= argsnum or y >= argsnum: + x = 1 + y = 0 + + while True: + stests = [] + for pair in tests: + t = pair[0] + v = pair[1] + for i in range(argsnum): + if i != x and i != y: + if v[i] != arglists[i][indexes[i]]: + t = None + break + if t: + stests.append(pair) + + tbl = table(metrix_table[options.metric][0] + " for\n" + getTestWideName(sname, indexes, arglists, x, y)) + tbl.newColumn("x", "X\Y") + for col in arglists[y]: + tbl.newColumn(col, col, align="center") + for row in arglists[x]: + tbl.newRow() + tbl.newCell("x", row) + for col in arglists[y]: + case = getTest(stests, x, y, row, col) + if case: + status = case.get("status") + if status != "run": + tbl.newCell(col, status, color = "red") + else: + val = getter(case, None, options.units) + if isinstance(val, float): + tbl.newCell(col, "%.2f %s" % (val, options.units), val) + else: + tbl.newCell(col, val, val) + else: + tbl.newCell(col, "-") + + if options.generateHtml: + tbl.htmlPrintTable(sys.stdout) + else: + tbl.consolePrintTable(sys.stdout) + if not nextPermutation(indexes, arglists, x, y): + break + + if options.generateHtml: + htmlPrintFooter(sys.stdout) \ No newline at end of file diff --git a/modules/ts/misc/color.py b/modules/ts/misc/color.py new file mode 100644 index 000000000..0c8963aa7 --- /dev/null +++ b/modules/ts/misc/color.py @@ -0,0 +1,384 @@ +import math, os, sys + +webcolors = { +"indianred": "#cd5c5c", +"lightcoral": "#f08080", +"salmon": "#fa8072", +"darksalmon": "#e9967a", +"lightsalmon": "#ffa07a", +"red": "#ff0000", +"crimson": "#dc143c", +"firebrick": "#b22222", +"darkred": "#8b0000", +"pink": "#ffc0cb", +"lightpink": "#ffb6c1", +"hotpink": "#ff69b4", +"deeppink": "#ff1493", +"mediumvioletred": "#c71585", +"palevioletred": "#db7093", +"lightsalmon": "#ffa07a", +"coral": "#ff7f50", +"tomato": "#ff6347", +"orangered": "#ff4500", +"darkorange": "#ff8c00", +"orange": "#ffa500", +"gold": "#ffd700", +"yellow": "#ffff00", +"lightyellow": "#ffffe0", +"lemonchiffon": "#fffacd", +"lightgoldenrodyellow": "#fafad2", +"papayawhip": "#ffefd5", +"moccasin": "#ffe4b5", +"peachpuff": "#ffdab9", +"palegoldenrod": "#eee8aa", +"khaki": "#f0e68c", +"darkkhaki": "#bdb76b", +"lavender": "#e6e6fa", +"thistle": "#d8bfd8", +"plum": "#dda0dd", +"violet": "#ee82ee", +"orchid": "#da70d6", +"fuchsia": "#ff00ff", +"magenta": "#ff00ff", +"mediumorchid": "#ba55d3", +"mediumpurple": "#9370db", +"blueviolet": "#8a2be2", +"darkviolet": "#9400d3", +"darkorchid": "#9932cc", +"darkmagenta": "#8b008b", +"purple": "#800080", +"indigo": "#4b0082", +"darkslateblue": "#483d8b", +"slateblue": "#6a5acd", +"mediumslateblue": "#7b68ee", +"greenyellow": "#adff2f", +"chartreuse": "#7fff00", +"lawngreen": "#7cfc00", +"lime": "#00ff00", +"limegreen": "#32cd32", +"palegreen": "#98fb98", +"lightgreen": "#90ee90", +"mediumspringgreen": "#00fa9a", +"springgreen": "#00ff7f", +"mediumseagreen": "#3cb371", +"seagreen": "#2e8b57", +"forestgreen": "#228b22", +"green": "#008000", +"darkgreen": "#006400", +"yellowgreen": "#9acd32", +"olivedrab": "#6b8e23", +"olive": "#808000", +"darkolivegreen": "#556b2f", +"mediumaquamarine": "#66cdaa", +"darkseagreen": "#8fbc8f", +"lightseagreen": "#20b2aa", +"darkcyan": "#008b8b", +"teal": "#008080", +"aqua": "#00ffff", +"cyan": "#00ffff", +"lightcyan": "#e0ffff", +"paleturquoise": "#afeeee", +"aquamarine": "#7fffd4", +"turquoise": "#40e0d0", +"mediumturquoise": "#48d1cc", +"darkturquoise": "#00ced1", +"cadetblue": "#5f9ea0", +"steelblue": "#4682b4", +"lightsteelblue": "#b0c4de", +"powderblue": "#b0e0e6", +"lightblue": "#add8e6", +"skyblue": "#87ceeb", +"lightskyblue": "#87cefa", +"deepskyblue": "#00bfff", +"dodgerblue": "#1e90ff", +"cornflowerblue": "#6495ed", +"royalblue": "#4169e1", +"blue": "#0000ff", +"mediumblue": "#0000cd", +"darkblue": "#00008b", +"navy": "#000080", +"midnightblue": "#191970", +"cornsilk": "#fff8dc", +"blanchedalmond": "#ffebcd", +"bisque": "#ffe4c4", +"navajowhite": "#ffdead", +"wheat": "#f5deb3", +"burlywood": "#deb887", +"tan": "#d2b48c", +"rosybrown": "#bc8f8f", +"sandybrown": "#f4a460", +"goldenrod": "#daa520", +"darkgoldenrod": "#b8860b", +"peru": "#cd853f", +"chocolate": "#d2691e", +"saddlebrown": "#8b4513", +"sienna": "#a0522d", +"brown": "#a52a2a", +"maroon": "#800000", +"white": "#ffffff", +"snow": "#fffafa", +"honeydew": "#f0fff0", +"mintcream": "#f5fffa", +"azure": "#f0ffff", +"aliceblue": "#f0f8ff", +"ghostwhite": "#f8f8ff", +"whitesmoke": "#f5f5f5", +"seashell": "#fff5ee", +"beige": "#f5f5dc", +"oldlace": "#fdf5e6", +"floralwhite": "#fffaf0", +"ivory": "#fffff0", +"antiquewhite": "#faebd7", +"linen": "#faf0e6", +"lavenderblush": "#fff0f5", +"mistyrose": "#ffe4e1", +"gainsboro": "#dcdcdc", +"lightgrey": "#d3d3d3", +"silver": "#c0c0c0", +"darkgray": "#a9a9a9", +"gray": "#808080", +"dimgray": "#696969", +"lightslategray": "#778899", +"slategray": "#708090", +"darkslategray": "#2f4f4f", +"black": "#000000", +} + +if os.name == "nt": + consoleColors = [ + "#000000", #{ 0, 0, 0 },//0 - black + "#000080", #{ 0, 0, 128 },//1 - navy + "#008000", #{ 0, 128, 0 },//2 - green + "#008080", #{ 0, 128, 128 },//3 - teal + "#800000", #{ 128, 0, 0 },//4 - maroon + "#800080", #{ 128, 0, 128 },//5 - purple + "#808000", #{ 128, 128, 0 },//6 - olive + "#C0C0C0", #{ 192, 192, 192 },//7 - silver + "#808080", #{ 128, 128, 128 },//8 - gray + "#0000FF", #{ 0, 0, 255 },//9 - blue + "#00FF00", #{ 0, 255, 0 },//a - lime + "#00FFFF", #{ 0, 255, 255 },//b - cyan + "#FF0000", #{ 255, 0, 0 },//c - red + "#FF00FF", #{ 255, 0, 255 },//d - magenta + "#FFFF00", #{ 255, 255, 0 },//e - yellow + "#FFFFFF", #{ 255, 255, 255 } //f - white + ] +else: + consoleColors = [ + "#2e3436", + "#cc0000", + "#4e9a06", + "#c4a000", + "#3465a4", + "#75507b", + "#06989a", + "#d3d7cf", + "#ffffff", + + "#555753", + "#ef2929", + "#8ae234", + "#fce94f", + "#729fcf", + "#ad7fa8", + "#34e2e2", + "#eeeeec", + ] + +def RGB2LAB(r,g,b): + if max(r,g,b): + r /= 255. + g /= 255. + b /= 255. + + X = (0.412453 * r + 0.357580 * g + 0.180423 * b) / 0.950456 + Y = (0.212671 * r + 0.715160 * g + 0.072169 * b) + Z = (0.019334 * r + 0.119193 * g + 0.950227 * b) / 1.088754 + + #[X * 0.950456] [0.412453 0.357580 0.180423] [R] + #[Y ] = [0.212671 0.715160 0.072169] * [G] + #[Z * 1.088754] [0.019334 0.119193 0.950227] [B] + + T = 0.008856 #threshold + + if X > T: + fX = math.pow(X, 1./3.) + else: + fX = 7.787 * X + 16./116. + + # Compute L + if Y > T: + Y3 = math.pow(Y, 1./3.) + fY = Y3 + L = 116. * Y3 - 16.0 + else: + fY = 7.787 * Y + 16./116. + L = 903.3 * Y + + if Z > T: + fZ = math.pow(Z, 1./3.) + else: + fZ = 7.787 * Z + 16./116. + + # Compute a and b + a = 500. * (fX - fY) + b = 200. * (fY - fZ) + + return (L,a,b) + +def colorDistance(r1,g1,b1 = None, r2 = None, g2 = None,b2 = None): + if type(r1) == tuple and type(g1) == tuple and b1 is None and r2 is None and g2 is None and b2 is None: + (l1,a1,b1) = RGB2LAB(*r1) + (l2,a2,b2) = RGB2LAB(*g1) + else: + (l1,a1,b1) = RGB2LAB(r1,g1,b1) + (l2,a2,b2) = RGB2LAB(r2,g2,b2) + #CIE94 + dl = l1-l2 + C1 = math.sqrt(a1*a1 + b1*b1) + C2 = math.sqrt(a2*a2 + b2*b2) + dC = C1 - C2 + da = a1-a2 + db = b1-b2 + dH = math.sqrt(max(0, da*da + db*db - dC*dC)) + Kl = 1 + K1 = 0.045 + K2 = 0.015 + + s1 = dl/Kl + s2 = dC/(1. + K1 * C1) + s3 = dH/(1. + K2 * C1) + return math.sqrt(s1*s1 + s2*s2 + s3*s3) + +def parseHexColor(col): + if len(col) != 4 and len(col) != 7 and not col.startswith("#"): + return (0,0,0) + if len(col) == 4: + r = col[1]*2 + g = col[2]*2 + b = col[3]*2 + else: + r = col[1:3] + g = col[3:5] + b = col[5:7] + return (int(r,16), int(g,16), int(b,16)) + +def getColor(col): + if isinstance(col, str): + if col.lower() in webcolors: + return parseHexColor(webcolors[col.lower()]) + else: + return parseHexColor(col) + else: + return col + +def getNearestConsoleColor(col): + color = getColor(col) + minidx = 0 + mindist = colorDistance(color, getColor(consoleColors[0])) + for i in range(len(consoleColors)): + dist = colorDistance(color, getColor(consoleColors[i])) + if dist < mindist: + mindist = dist + minidx = i + return minidx + +if os.name == 'nt': + import msvcrt + from ctypes import windll, Structure, c_short, c_ushort, byref + SHORT = c_short + WORD = c_ushort + + class COORD(Structure): + _fields_ = [ + ("X", SHORT), + ("Y", SHORT)] + + class SMALL_RECT(Structure): + _fields_ = [ + ("Left", SHORT), + ("Top", SHORT), + ("Right", SHORT), + ("Bottom", SHORT)] + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", WORD), + ("srWindow", SMALL_RECT), + ("dwMaximumWindowSize", COORD)] + + class winConsoleColorizer(object): + def __init__(self, stream): + self.handle = msvcrt.get_osfhandle(stream.fileno()) + self.default_attrs = 7#self.get_text_attr() + self.stream = stream + + def get_text_attr(self): + csbi = CONSOLE_SCREEN_BUFFER_INFO() + windll.kernel32.GetConsoleScreenBufferInfo(self.handle, byref(csbi)) + return csbi.wAttributes + + def set_text_attr(self, color): + windll.kernel32.SetConsoleTextAttribute(self.handle, color) + + def write(self, *text, **attrs): + if not text: + return + color = attrs.get("color", None) + if color: + col = getNearestConsoleColor(color) + self.stream.flush() + self.set_text_attr(col) + self.stream.write(" ".join([str(t) for t in text])) + if color: + self.stream.flush() + self.set_text_attr(self.default_attrs) + +class dummyColorizer(object): + def __init__(self, stream): + self.stream = stream + + def write(self, *text, **attrs): + if text: + self.stream.write(" ".join([str(t) for t in text])) + +class asciiSeqColorizer(object): + RESET_SEQ = "\033[0m" + #BOLD_SEQ = "\033[1m" + ITALIC_SEQ = "\033[3m" + UNDERLINE_SEQ = "\033[4m" + STRIKEOUT_SEQ = "\033[9m" + COLOR_SEQ0 = "\033[00;%dm" #dark + COLOR_SEQ1 = "\033[01;%dm" #bold and light + + def __init__(self, stream): + self.stream = stream + + def get_seq(self, code): + if code > 8: + return self.__class__.COLOR_SEQ1 % (30 + code - 9) + else: + return self.__class__.COLOR_SEQ0 % (30 + code) + + def write(self, *text, **attrs): + if not text: + return + color = attrs.get("color", None) + if color: + col = getNearestConsoleColor(color) + self.stream.write(self.get_seq(col)) + self.stream.write(" ".join([str(t) for t in text])) + if color: + self.stream.write(self.__class__.RESET_SEQ) + + +def getColorizer(stream): + if stream.isatty(): + if os.name == "nt": + return winConsoleColorizer(stream) + else: + return asciiSeqColorizer(stream) + else: + return dummyColorizer(stream) diff --git a/modules/ts/misc/report.py b/modules/ts/misc/report.py new file mode 100644 index 000000000..e37996777 --- /dev/null +++ b/modules/ts/misc/report.py @@ -0,0 +1,80 @@ +import testlog_parser, sys, os, xml, re +from table_formatter import * +from optparse import OptionParser + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option("-o", "--output", dest="format", help="output results in text format (can be 'txt', 'html' or 'auto' - default)", metavar="FMT", default="auto") + parser.add_option("-u", "--units", dest="units", help="units for output values (s, ms (default), mks, ns or ticks)", metavar="UNITS", default="ms") + parser.add_option("-c", "--columns", dest="columns", help="comma-separated list of columns to show", metavar="COLS", default="") + parser.add_option("-f", "--filter", dest="filter", help="regex to filter tests", metavar="REGEX", default=None) + parser.add_option("", "--show-all", action="store_true", dest="showall", default=False, help="also include empty and \"notrun\" lines") + (options, args) = parser.parse_args() + + if len(args) < 1: + print >> sys.stderr, "Usage:\n", os.path.basename(sys.argv[0]), ".xml" + exit(0) + + options.generateHtml = detectHtmlOutputType(options.format) + args[0] = os.path.basename(args[0]) + + tests = [] + files = [] + for arg in set(args): + files.append(os.path.basename(arg)) + tests.extend(testlog_parser.parseLogFile(arg)) + + if options.filter: + expr = re.compile(options.filter) + tests = [t for t in tests if expr.search(str(t))] + + tbl = table(", ".join(files)) + if options.columns: + metrics = [s.strip() for s in options.columns.split(",")] + metrics = [m for m in metrics if m and not m.endswith("%") and m in metrix_table] + else: + metrics = None + if not metrics: + metrics = ["name", "samples", "outliers", "min", "median", "gmean", "mean", "stddev"] + if "name" not in metrics: + metrics.insert(0, "name") + + for m in metrics: + if m == "name": + tbl.newColumn(m, metrix_table[m][0]) + else: + tbl.newColumn(m, metrix_table[m][0], align = "center") + + needNewRow = True + for case in sorted(tests): + if needNewRow: + tbl.newRow() + if not options.showall: + needNewRow = False + status = case.get("status") + if status != "run": + if status != "notrun": + needNewRow = True + for m in metrics: + if m == "name": + tbl.newCell(m, str(case)) + else: + tbl.newCell(m, status, color = "red") + else: + needNewRow = True + for m in metrics: + val = metrix_table[m][1](case, None, options.units) + if isinstance(val, float): + tbl.newCell(m, "%.2f %s" % (val, options.units), val) + else: + tbl.newCell(m, val, val) + if not needNewRow: + tbl.trimLastRow() + + # output table + if options.generateHtml: + htmlPrintHeader(sys.stdout, "Report %s tests from %s" % (len(tests), ", ".join(files))) + tbl.htmlPrintTable(sys.stdout) + htmlPrintFooter(sys.stdout) + else: + tbl.consolePrintTable(sys.stdout) \ No newline at end of file diff --git a/modules/ts/misc/run.py b/modules/ts/misc/run.py new file mode 100644 index 000000000..42e56e136 --- /dev/null +++ b/modules/ts/misc/run.py @@ -0,0 +1,421 @@ +import sys, os, platform, xml, re, tempfile, glob, datetime, getpass +from optparse import OptionParser +from subprocess import Popen, PIPE + +hostos = os.name # 'nt', 'posix' +hostmachine = platform.machine() # 'x86', 'AMD64', 'x86_64' +nameprefix = "opencv_perf_" + +parse_patterns = ( + {'name': "has_perf_tests", 'default': "OFF", 'pattern': re.compile("^BUILD_PERF_TESTS:BOOL=(ON)$")}, + {'name': "cmake_home", 'default': None, 'pattern': re.compile("^CMAKE_HOME_DIRECTORY:INTERNAL=(.+)$")}, + {'name': "opencv_home", 'default': None, 'pattern': re.compile("^OpenCV_SOURCE_DIR:STATIC=(.+)$")}, + {'name': "tests_dir", 'default': None, 'pattern': re.compile("^EXECUTABLE_OUTPUT_PATH:PATH=(.+)$")}, + {'name': "build_type", 'default': "Release", 'pattern': re.compile("^CMAKE_BUILD_TYPE:STRING=(.*)$")}, + {'name': "svnversion_path", 'default': None, 'pattern': re.compile("^SVNVERSION_PATH:FILEPATH=(.*)$")}, + {'name': "cxx_flags", 'default': None, 'pattern': re.compile("^CMAKE_CXX_FLAGS:STRING=(.*)$")}, + {'name': "cxx_flags_debug", 'default': None, 'pattern': re.compile("^CMAKE_CXX_FLAGS_DEBUG:STRING=(.*)$")}, + {'name': "cxx_flags_release", 'default': None, 'pattern': re.compile("^CMAKE_CXX_FLAGS_RELEASE:STRING=(.*)$")}, + {'name': "ndk_path", 'default': None, 'pattern': re.compile("^ANDROID_NDK(?:_TOOLCHAIN_ROOT)?:PATH=(.*)$")}, + {'name': "arm_target", 'default': None, 'pattern': re.compile("^ARM_TARGET:INTERNAL=(.*)$")}, + {'name': "android_executable", 'default': None, 'pattern': re.compile("^ANDROID_EXECUTABLE:FILEPATH=(.*android.*)$")}, + {'name': "is_x64", 'default': "OFF", 'pattern': re.compile("^CUDA_64_BIT_DEVICE_CODE:BOOL=(ON)$")},#ugly( + {'name': "cmake_generator", 'default': None, 'pattern': re.compile("^CMAKE_GENERATOR:INTERNAL=(.+)$")}, + {'name': "cxx_compiler", 'default': None, 'pattern': re.compile("^CMAKE_CXX_COMPILER:FILEPATH=(.+)$")}, +) + +def query_yes_no(stdout, question, default="yes"): + valid = {"yes":True, "y":True, "ye":True, "no":False, "n":False} + if default == None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + stdout.write(os.linesep + question + prompt) + choice = raw_input().lower() + if default is not None and choice == '': + return valid[default] + elif choice in valid: + return valid[choice] + else: + stdout.write("Please respond with 'yes' or 'no' "\ + "(or 'y' or 'n').\n") + +class RunInfo(object): + def __init__(self, path): + self.path = path + self.error = None + for p in parse_patterns: + setattr(self, p["name"], p["default"]) + cachefile = open(os.path.join(path, "CMakeCache.txt"), "rt") + try: + for l in cachefile.readlines(): + ll = l.strip() + if not ll or ll.startswith("#"): + continue + for p in parse_patterns: + match = p["pattern"].match(ll) + if match: + value = match.groups()[0] + if value and not value.endswith("-NOTFOUND"): + setattr(self, p["name"], value) + except: + pass + cachefile.close() + # fix empty tests dir + if not self.tests_dir: + self.tests_dir = self.path + # add path to adb + if self.android_executable: + self.adb = os.path.join(os.path.dirname(os.path.dirname(self.android_executable)), ("platform-tools/adb","platform-tools/adb.exe")[hostos == 'nt']) + else: + self.adb = None + + # detect target platform + if self.android_executable or self.arm_target or self.ndk_path: + self.targetos = "android" + if not self.adb: + try: + output = Popen(["adb", "devices"], stdout=PIPE, stderr=PIPE).communicate() + self.adb = "adb" + except OSError: + pass + else: + self.targetos = hostos + + # fix has_perf_tests param + self.has_perf_tests = self.has_perf_tests == "ON" + # fix is_x64 flag + self.is_x64 = self.is_x64 == "ON" + if not self.is_x64 and ("X64" in "%s %s %s" % (self.cxx_flags, self.cxx_flags_release, self.cxx_flags_debug) or "Win64" in self.cmake_generator): + self.is_x64 = True + + # fix test path + if "Visual Studio" in self.cmake_generator: + self.tests_dir = os.path.join(self.tests_dir, self.build_type) + elif not self.is_x64 and self.cxx_compiler: + #one more attempt to detect x64 compiler + try: + output = Popen([self.cxx_compiler, "-v"], stdout=PIPE, stderr=PIPE).communicate() + if not output[0] and "x86_64" in output[1]: + self.is_x64 = True + except OSError: + pass + + # detect target arch + if self.targetos == "android": + self.targetarch = "arm" + elif self.is_x64 and hostmachine in ["AMD64", "x86_64"]: + self.targetarch = "x64" + elif hostmachine in ["x86", "AMD64", "x86_64"]: + self.targetarch = "x86" + else: + self.targetarch = "unknown" + + self.hardware = None + + self.getSvnVersion(self.cmake_home, "cmake_home_svn") + if self.opencv_home == self.cmake_home: + self.opencv_home_svn = self.cmake_home_svn + else: + self.getSvnVersion(self.opencv_home, "opencv_home_svn") + + self.tests = self.getAvailableTestApps() + + def getSvnVersion(self, path, name): + if not path: + setattr(self, name, None) + return + if not self.svnversion_path and hostos == 'nt': + self.tryGetSvnVersionWithTortoise(path, name) + else: + svnversion = self.svnversion_path + if not svnversion: + svnversion = "svnversion" + try: + output = Popen([svnversion, "-n", path], stdout=PIPE, stderr=PIPE).communicate() + if not output[1]: + setattr(self, name, output[0]) + else: + setattr(self, name, None) + except OSError: + setattr(self, name, None) + + def tryGetSvnVersionWithTortoise(self, path, name): + try: + wcrev = "SubWCRev.exe" + dir = tempfile.mkdtemp() + #print dir + tmpfilename = os.path.join(dir, "svn.tmp") + tmpfilename2 = os.path.join(dir, "svn_out.tmp") + tmpfile = open(tmpfilename, "w") + tmpfile.write("$WCRANGE$$WCMODS?M:$") + tmpfile.close(); + output = Popen([wcrev, path, tmpfilename, tmpfilename2, "-f"], stdout=PIPE, stderr=PIPE).communicate() + if "is not a working copy" in output[0]: + version = "exported" + else: + tmpfile = open(tmpfilename2, "r") + version = tmpfile.read() + tmpfile.close() + setattr(self, name, version) + except: + setattr(self, name, None) + finally: + if dir: + import shutil + shutil.rmtree(dir) + + def isTest(self, fullpath): + if not os.path.isfile(fullpath): + return False + if hostos == self.targetos: + return os.access(fullpath, os.X_OK) + return True + + def getAvailableTestApps(self): + if self.tests_dir and os.path.isdir(self.tests_dir): + files = glob.glob(os.path.join(self.tests_dir, nameprefix + "*")) + if self.targetos == hostos: + files = [f for f in files if self.isTest(f)] + return files + return [] + + def getLogName(self, app, timestamp): + app = os.path.basename(app) + if app.endswith(".exe"): + app = app[:-4] + if app.startswith(nameprefix): + app = app[len(nameprefix):] + if self.cmake_home_svn: + if self.cmake_home_svn == self.opencv_home_svn: + rev = self.cmake_home_svn + elif self.opencv_home_svn: + rev = self.cmake_home_svn + "-" + self.opencv_home_svn + else: + rev = self.cmake_home_svn + else: + rev = None + if rev: + rev = rev.replace(":","to") + "_" + else: + rev = "" + if self.hardware: + hw = str(self.hardware).replace(" ", "_") + "_" + else: + hw = "" + return "%s_%s_%s_%s%s%s.xml" %(app, self.targetos, self.targetarch, hw, rev, timestamp.strftime("%Y%m%dT%H%M%S")) + + def getTest(self, name): + # full path + if self.isTest(name): + return name + + # name only + fullname = os.path.join(self.tests_dir, name) + if self.isTest(fullname): + return fullname + + # name without extension + fullname += ".exe" + if self.isTest(fullname): + return fullname + + # short name for OpenCV tests + for t in self.tests: + if t == name: + return t + fname = os.path.basename(t) + if fname == name: + return t + if fname.endswith(".exe"): + fname = fname[:-4] + if fname == name: + return t + if fname.startswith(nameprefix): + fname = fname[len(nameprefix):] + if fname == name: + return t + return None + + def runAdb(self, *args): + cmd = [self.adb] + cmd.extend(args) + try: + output = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate() + if not output[1]: + return output[0] + self.error = output[1] + print self.error + except OSError: + pass + return None + + def isRunnable(self): + #if not self.has_perf_tests or not self.tests: + #self.error = "Performance tests are not built (at %s)" % self.path + #return False + if self.targetarch == "x64" and hostmachine == "x86": + self.error = "Target architecture is incompatible with current platform (at %s)" % self.path + return False + if self.targetos == "android": + if not self.adb or not os.path.isfile(self.adb) or not os.access(self.adb, os.X_OK): + self.error = "Could not find adb executable (at %s)" % self.path + return False + adb_res = self.runAdb("devices") + if not adb_res: + self.error = "Could not run adb command: %s (at %s)" % (self.error, self.path) + return False + connected_devices = len(re.findall(r"^[^ \t]+[ \t]+device$", adb_res, re.MULTILINE)) + if connected_devices == 0: + self.error = "No Android device connected (at %s)" % self.path + return False + if connected_devices > 1: + self.error = "Too many (%s) devices are connected. Single device is required. (at %s)" % (connected_devices, self.path) + return False + if "armeabi-v7a" in self.arm_target: + adb_res = self.runAdb("shell", "cat /proc/cpuinfo") + if not adb_res: + self.error = "Could not get info about Android platform: %s (at %s)" % (self.error, self.path) + return False + if "ARMv7" not in adb_res: + self.error = "Android device does not support ARMv7 commands, but tests are built for armeabi-v7a (at %s)" % self.path + return False + if "NEON" in self.arm_target and "neon" not in adb_res: + self.error = "Android device has no NEON, but tests are built for %s (at %s)" % (self.arm_target, self.path) + return False + hw = re.search(r"^Hardware[ \t]*:[ \t]*(.*?)$", adb_res, re.MULTILINE) + if hw: + self.hardware = hw.groups()[0].strip() + return True + + def runTest(self, path, workingDir, _stdout, _stderr, args = []): + if self.error: + return + args = args[:] + timestamp = datetime.datetime.now() + logfile = self.getLogName(path, timestamp) + exe = os.path.abspath(path) + + userlog = [a for a in args if a.startswith("--gtest_output=")] + if len(userlog) == 0: + args.append("--gtest_output=xml:" + logfile) + else: + logfile = userlog[userlog[0].find(":")+1:] + + if self.targetos == "android": + try: + andoidcwd = "/data/bin/" + getpass.getuser().replace(" ","") + "_perf/" + exename = os.path.basename(exe) + androidexe = andoidcwd + exename + #upload + print >> _stderr, "Uploading", exename, "to device..." + output = Popen([self.adb, "push", exe, androidexe], stdout=_stdout, stderr=_stderr).wait() + if output != 0: + print >> _stderr, "adb finishes unexpectedly with error code", output + return + #chmod + print >> _stderr, "Changing mode of ", androidexe + output = Popen([self.adb, "shell", "chmod 777 " + androidexe], stdout=_stdout, stderr=_stderr).wait() + if output != 0: + print >> _stderr, "adb finishes unexpectedly with error code", output + return + #run + command = exename + " " + " ".join(args) + print >> _stderr, "Running:", command + Popen([self.adb, "shell", "export OPENCV_TEST_DATA_PATH=" + self.test_data_path + "&& cd " + andoidcwd + "&& ./" + command], stdout=_stdout, stderr=_stderr).wait() + # try get log + print >> _stderr, "Pulling", logfile, "from device..." + hostlogpath = os.path.join(workingDir, logfile) + output = Popen([self.adb, "pull", andoidcwd + logfile, hostlogpath], stdout=_stdout, stderr=_stderr).wait() + if output != 0: + print >> _stderr, "adb finishes unexpectedly with error code", output + return + #rm log + Popen([self.adb, "shell", "rm " + andoidcwd + logfile], stdout=_stdout, stderr=_stderr).wait() + except OSError: + pass + if os.path.isfile(hostlogpath): + return hostlogpath + return None + else: + cmd = [exe] + cmd.extend(args) + print >> _stderr, "Running:", " ".join(cmd) + try: + Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait() + except OSError: + pass + + logpath = os.path.join(workingDir, logfile) + if os.path.isfile(logpath): + return logpath + return None + + def runTests(self, tests, _stdout, _stderr, workingDir, args = []): + if self.error: + return [] + if not tests: + tests = self.tests + logs = [] + for test in tests: + t = self.getTest(test) + if t: + logfile = self.runTest(t, workingDir, _stdout, _stderr, args) + if logfile: + logs.append(os.path.relpath(logfile, ".")) + else: + print >> _stderr, "Error: Test \"%s\" is not found in %s" % (test, self.tests_dir) + return logs + +if __name__ == "__main__": + test_args = [a for a in sys.argv if a.startswith("--perf_") or a.startswith("--gtest_")] + argv = [a for a in sys.argv if not(a.startswith("--perf_") or a.startswith("--gtest_"))] + + parser = OptionParser() + parser.add_option("-t", "--tests", dest="tests", help="comma-separated list of modules to test", metavar="SUITS", default="") + parser.add_option("-w", "--cwd", dest="cwd", help="working directory for tests", metavar="PATH", default=".") + parser.add_option("", "--android_test_data_path", dest="test_data_path", help="OPENCV_TEST_DATA_PATH for Android run", metavar="PATH", default="/sdcard/opencv_testdata/") + + (options, args) = parser.parse_args(argv) + + run_args = [] + + for path in args: + path = os.path.abspath(path) + while (True): + if os.path.isdir(path) and os.path.isfile(os.path.join(path, "CMakeCache.txt")): + run_args.append(path) + break + npath = os.path.dirname(path) + if npath == path: + break + path = npath + + if len(run_args) == 0: + print >> sys.stderr, "Usage:\n", os.path.basename(sys.argv[0]), "" + exit(1) + + tests = [s.strip() for s in options.tests.split(",") if s] + + if len(tests) != 1 or len(run_args) != 1: + #remove --gtest_output from params + test_args = [a for a in test_args if not a.startswith("--gtest_output=")] + + logs = [] + for path in run_args: + info = RunInfo(path) + #print vars(info),"\n" + if not info.isRunnable(): + print >> sys.stderr, "Error:", info.error + else: + info.test_data_path = options.test_data_path + logs.extend(info.runTests(tests, sys.stdout, sys.stderr, options.cwd, test_args)) + + if logs: + print >> sys.stderr, "Collected:", " ".join(logs) diff --git a/modules/ts/misc/summary.py b/modules/ts/misc/summary.py new file mode 100644 index 000000000..3279930c1 --- /dev/null +++ b/modules/ts/misc/summary.py @@ -0,0 +1,144 @@ +import testlog_parser, sys, os, xml, glob +from table_formatter import * +from optparse import OptionParser + +if __name__ == "__main__": + if len(sys.argv) < 2: + print >> sys.stderr, "Usage:\n", os.path.basename(sys.argv[0]), ".xml [.xml ...]" + exit(0) + + parser = OptionParser() + parser.add_option("-o", "--output", dest="format", help="output results in text format (can be 'txt', 'html' or 'auto' - default)", metavar="FMT", default="auto") + parser.add_option("-m", "--metric", dest="metric", help="output metric", metavar="NAME", default="gmean") + parser.add_option("-u", "--units", dest="units", help="units for output values (s, ms (default), mks, ns or ticks)", metavar="UNITS", default="ms") + parser.add_option("-f", "--filter", dest="filter", help="regex to filter tests", metavar="REGEX", default=None) + parser.add_option("", "--no-relatives", action="store_false", dest="calc_relatives", default=True, help="do not output relative values") + parser.add_option("", "--show-all", action="store_true", dest="showall", default=False, help="also include empty and \"notrun\" lines") + (options, args) = parser.parse_args() + + options.generateHtml = detectHtmlOutputType(options.format) + if options.metric not in metrix_table: + options.metric = "gmean" + if options.metric.endswith("%"): + options.calc_relatives = False + + # expand wildcards and filter duplicates + files = [] + files1 = [] + for arg in args: + if ("*" in arg) or ("?" in arg): + files1.extend([os.path.abspath(f) for f in glob.glob(arg)]) + else: + files.append(os.path.abspath(arg)) + seen = set() + files = [ x for x in files if x not in seen and not seen.add(x)] + files.extend((set(files1) - set(files))) + + # read all passed files + test_sets = [] + for arg in files: + try: + tests = testlog_parser.parseLogFile(arg) + if options.filter: + expr = re.compile(options.filter) + tests = [t for t in tests if expr.search(str(t))] + if tests: + test_sets.append((os.path.basename(arg), tests)) + except IOError as err: + sys.stderr.write("IOError reading \"" + arg + "\" - " + str(err) + os.linesep) + except xml.parsers.expat.ExpatError as err: + sys.stderr.write("ExpatError reading \"" + arg + "\" - " + str(err) + os.linesep) + + if not test_sets: + sys.stderr.write("Error: no test data found" + os.linesep) + quit() + + # find matches + setsCount = len(test_sets) + test_cases = {} + + for i in range(setsCount): + for case in test_sets[i][1]: + name = str(case) + if name not in test_cases: + test_cases[name] = [None] * setsCount + test_cases[name][i] = case + + # build table + getter = metrix_table[options.metric][1] + if options.calc_relatives: + getter_p = metrix_table[options.metric + "%"][1] + tbl = table(metrix_table[options.metric][0]) + + # header + tbl.newColumn("name", "Name of Test", align = "left") + i = 0 + for set in test_sets: + tbl.newColumn(str(i), set[0].replace(".xml","").replace("_", "\n"), align = "center") + i += 1 + if options.calc_relatives: + i = 1 + for set in test_sets[1:]: + tbl.newColumn(str(i) + "%", set[0].replace(".xml","").replace("_", "\n") + "\nvs\n" + test_sets[0][0].replace(".xml","").replace("_", "\n"), align = "center") + i += 1 + + # rows + needNewRow = True + for name in sorted(test_cases.iterkeys()): + cases = test_cases[name] + if needNewRow: + tbl.newRow() + if not options.showall: + needNewRow = False + tbl.newCell("name", name) + for i in range(setsCount): + case = cases[i] + if case is None: + tbl.newCell(str(i), "-") + if options.calc_relatives and i > 0: + tbl.newCell(str(i) + "%", "-") + else: + status = case.get("status") + if status != "run": + tbl.newCell(str(i), status, color = "red") + if status != "notrun": + needNewRow = True + if options.calc_relatives and i > 0: + tbl.newCell(str(i) + "%", "-", color = "red") + else: + val = getter(case, cases[0], options.units) + if options.calc_relatives and i > 0 and val: + valp = getter_p(case, cases[0], options.units) + else: + valp = None + if not valp or i == 0: + color = None + elif valp > 1.05: + color = "red" + elif valp < 0.95: + color = "green" + else: + color = None + if val: + needNewRow = True + if options.metric.endswith("%"): + tbl.newCell(str(i), "%.2f" % val, val, color = color) + else: + tbl.newCell(str(i), "%.3f %s" % (val, options.units), val, color = color) + else: + tbl.newCell(str(i), "-") + if options.calc_relatives and i > 0: + if valp: + tbl.newCell(str(i) + "%", "%.2f" % valp, valp, color = color, bold = color) + else: + tbl.newCell(str(i) + "%", "-") + if not needNewRow: + tbl.trimLastRow() + + # output table + if options.generateHtml: + htmlPrintHeader(sys.stdout, "Summary report for %s tests from %s test logs" % (len(test_cases), setsCount)) + tbl.htmlPrintTable(sys.stdout) + htmlPrintFooter(sys.stdout) + else: + tbl.consolePrintTable(sys.stdout) diff --git a/modules/ts/misc/table_formatter.py b/modules/ts/misc/table_formatter.py new file mode 100644 index 000000000..aacfe0fd1 --- /dev/null +++ b/modules/ts/misc/table_formatter.py @@ -0,0 +1,586 @@ +import sys, re, os.path, cgi, stat +from optparse import OptionParser +from color import getColorizer + +class tblCell(object): + def __init__(self, text, value = None, props = None): + self.text = text + self.value = value + self.props = props + +class tblColumn(object): + def __init__(self, caption, title = None, props = None): + self.text = caption + self.title = title + self.props = props + +class tblRow(object): + def __init__(self, colsNum, props = None): + self.cells = [None] * colsNum + self.props = props + +def htmlEncode(str): + return '
'.join([cgi.escape(s) for s in str]) + +class table(object): + def_align = "left" + def_valign = "middle" + def_color = None + def_colspan = 1 + def_rowspan = 1 + def_bold = False + def_italic = False + def_text="-" + + def __init__(self, caption = None): + self.columns = {} + self.rows = [] + self.ridx = -1; + self.caption = caption + pass + + def newRow(self, **properties): + if len(self.rows) - 1 == self.ridx: + self.rows.append(tblRow(len(self.columns), properties)) + else: + self.rows[ridx + 1].props = properties + self.ridx += 1 + + def trimLastRow(self): + if self.rows: + self.rows.pop() + if self.ridx >= len(self.rows): + self.ridx = len(self.rows) - 1 + + def newColumn(self, name, caption, title = None, **properties): + if name in self.columns: + index = self.columns[name].index + else: + index = len(self.columns) + if isinstance(caption, tblColumn): + caption.index = index + self.columns[name] = caption + return caption + else: + col = tblColumn(caption, title, properties) + col.index = index + self.columns[name] = col + return col + + def getColumn(self, name): + if isinstance(name, str): + return self.columns.get(name, None) + else: + vals = [v for v in self.columns.values() if v.index == name] + if vals: + return vals[0] + return None + + def newCell(self, col_name, text, value = None, **properties): + if self.ridx < 0: + self.newRow() + col = self.getColumn(col_name) + row = self.rows[self.ridx] + if not col: + return None + if isinstance(text, tblCell): + cl = text + else: + cl = tblCell(text, value, properties) + row.cells[col.index] = cl + return cl + + def layoutTable(self): + columns = self.columns.values() + columns.sort(key=lambda c: c.index) + + colspanned = [] + rowspanned = [] + + self.headerHeight = 1 + rowsToAppend = 0 + + for col in columns: + self.measureCell(col) + if col.height > self.headerHeight: + self.headerHeight = col.height + col.minwidth = col.width + col.line = None + + for r in range(len(self.rows)): + row = self.rows[r] + row.minheight = 1 + for i in range(len(row.cells)): + cell = row.cells[i] + if row.cells[i] is None: + continue + cell.line = None + self.measureCell(cell) + colspan = int(self.getValue("colspan", cell)) + rowspan = int(self.getValue("rowspan", cell)) + if colspan > 1: + colspanned.append((r,i)) + if i + colspan > len(columns): + colspan = len(columns) - i + cell.colspan = colspan + #clear spanned cells + for j in range(i+1, min(len(row.cells), i + colspan)): + row.cells[j] = None + elif columns[i].minwidth < cell.width: + columns[i].minwidth = cell.width + if rowspan > 1: + rowspanned.append((r,i)) + rowsToAppend2 = r + colspan - len(self.rows) + if rowsToAppend2 > rowsToAppend: + rowsToAppend = rowsToAppend2 + cell.rowspan = rowspan + #clear spanned cells + for j in range(r+1, min(len(self.rows), r + rowspan)): + if len(self.rows[j].cells) > i: + self.rows[j].cells[i] = None + elif row.minheight < cell.height: + row.minheight = cell.height + + self.ridx = len(self.rows) - 1 + for r in range(rowsToAppend): + self.newRow() + self.rows[len(self.rows) - 1].minheight = 1 + + while colspanned: + colspanned_new = [] + for r, c in colspanned: + cell = self.rows[r].cells[c] + sum([col.minwidth for col in columns[c:c + cell.colspan]]) + cell.awailable = sum([col.minwidth for col in columns[c:c + cell.colspan]]) + cell.colspan - 1 + if cell.awailable < cell.width: + colspanned_new.append((r,c)) + colspanned = colspanned_new + if colspanned: + r,c = colspanned[0] + cell = self.rows[r].cells[c] + cols = columns[c:c + cell.colspan] + total = cell.awailable - cell.colspan + 1 + budget = cell.width - cell.awailable + spent = 0 + s = 0 + for col in cols: + s += col.minwidth + addition = s * budget / total - spent + spent += addition + col.minwidth += addition + + while rowspanned: + rowspanned_new = [] + for r, c in rowspanned: + cell = self.rows[r].cells[c] + cell.awailable = sum([row.minheight for row in self.rows[r:r + cell.rowspan]]) + if cell.awailable < cell.height: + rowspanned_new.append((r,c)) + rowspanned = rowspanned_new + if rowspanned: + r,c = rowspanned[0] + cell = self.rows[r].cells[c] + rows = self.rows[r:r + cell.rowspan] + total = cell.awailable + budget = cell.height - cell.awailable + spent = 0 + s = 0 + for row in rows: + s += row.minheight + addition = s * budget / total - spent + spent += addition + row.minheight += addition + + return columns + + def measureCell(self, cell): + text = self.getValue("text", cell) + cell.text = self.reformatTextValue(text) + cell.height = len(cell.text) + cell.width = len(max(cell.text, key = lambda line: len(line))) + + def reformatTextValue(self, value): + if isinstance(value, str): + vstr = value + elif isinstance(value, unicode): + vstr = str(value) + else: + try: + vstr = '\n'.join([str(v) for v in value]) + except TypeError: + vstr = str(value) + return vstr.splitlines() + + def adjustColWidth(self, cols, width): + total = sum([c.minWidth for c in cols]) + if total + len(cols) - 1 >= width: + return + budget = width - len(cols) + 1 - total + spent = 0 + s = 0 + for col in cols: + s += col.minWidth + addition = s * budget / total - spent + spent += addition + col.minWidth += addition + + def getValue(self, name, *elements): + for el in elements: + try: + return getattr(el, name) + except AttributeError: + pass + try: + val = el.props[name] + if val: + return val + except AttributeError: + pass + except KeyError: + pass + try: + return getattr(self.__class__, "def_" + name) + except AttributeError: + return None + + def consolePrintTable(self, out): + columns = self.layoutTable() + colrizer = getColorizer(out) + + if self.caption: + out.write("%s%s%s" % ( os.linesep, os.linesep.join(self.reformatTextValue(self.caption)), os.linesep * 2)) + + headerRow = tblRow(len(columns), {"align": "center", "valign": "top", "bold": True, "header": True}) + headerRow.cells = columns + headerRow.minheight = self.headerHeight + + self.consolePrintRow2(colrizer, headerRow, columns) + + for i in range(0, len(self.rows)): + self.consolePrintRow2(colrizer, i, columns) + + def consolePrintRow2(self, out, r, columns): + if isinstance(r, tblRow): + row = r + r = -1 + else: + row = self.rows[r] + + #evaluate initial values for line numbers + i = 0 + while i < len(row.cells): + cell = row.cells[i] + colspan = self.getValue("colspan", cell) + if cell is not None: + cell.wspace = sum([col.minwidth for col in columns[i:i + colspan]]) + colspan - 1 + if cell.line is None: + if r < 0: + rows = [row] + else: + rows = self.rows[r:r + self.getValue("rowspan", cell)] + cell.line = self.evalLine(cell, rows, columns[i]) + if len(rows) > 1: + for rw in rows: + rw.cells[i] = cell + i += colspan + + #print content + for ln in range(row.minheight): + i = 0 + while i < len(row.cells): + if i > 0: + out.write(" ") + cell = row.cells[i] + column = columns[i] + if cell is None: + out.write(" " * column.minwidth) + i += 1 + else: + self.consolePrintLine(cell, row, column, out) + i += self.getValue("colspan", cell) + out.write(os.linesep) + + def consolePrintLine(self, cell, row, column, out): + if cell.line < 0 or cell.line >= cell.height: + line = "" + else: + line = cell.text[cell.line] + width = cell.wspace + align = self.getValue("align", ((None, cell)[isinstance(cell, tblCell)]), row, column) + + if align == "right": + pattern = "%" + str(width) + "s" + elif align == "center": + pattern = "%" + str((width - len(line)) / 2 + len(line)) + "s" + " " * (width - len(line) - (width - len(line)) / 2) + else: + pattern = "%-" + str(width) + "s" + + out.write(pattern % line, color = self.getValue("color", cell, row, column)) + cell.line += 1 + + def evalLine(self, cell, rows, column): + height = cell.height + valign = self.getValue("valign", cell, rows[0], column) + space = sum([row.minheight for row in rows]) + if valign == "bottom": + return height - space + if valign == "middle": + return (height - space + 1) / 2 + return 0 + + def htmlPrintTable(self, out): + columns = self.layoutTable() + + out.write("
\n\n") + if self.caption: + out.write(" \n" % htmlEncode(self.reformatTextValue(self.caption))) + out.write(" \n") + + headerRow = tblRow(len(columns), {"align": "center", "valign": "top", "bold": True, "header": True}) + headerRow.cells = columns + + header_rows = [headerRow] + + header_rows.extend([row for row in self.rows if self.getValue("header")]) + + for row in header_rows: + out.write(" \n") + for th in row.cells: + align = self.getValue("align", ((None, th)[isinstance(th, tblCell)]), row, row) + valign = self.getValue("valign", th, row) + attr = "" + if align: + attr += " align=\"%s\"" % align + if valign: + attr += " valign=\"%s\"" % valign + out.write(" \n" % attr) + if th is not None: + out.write(" %s\n" % htmlEncode(th.text)) + out.write(" \n") + out.write(" \n") + + out.write(" \n \n") + + rows = [row for row in self.rows if not self.getValue("header")] + for r in range(len(rows)): + row = rows[r] + out.write(" \n") + i = 0 + while i < len(row.cells): + column = columns[i] + td = row.cells[i] + if isinstance(td, int): + i += td + continue + colspan = self.getValue("colspan", td) + rowspan = self.getValue("rowspan", td) + align = self.getValue("align", td, row, column) + valign = self.getValue("valign", td, row, column) + color = self.getValue("color", td, row, column) + bold = self.getValue("bold", td, row, column) + italic = self.getValue("italic", td, row, column) + style = "" + attr = "" + if color: + style += "color:%s;" % color + if bold: + style += "font-weight: bold;" + if italic: + style += "font-style: italic;" + if align and align != "left": + attr += " align=\"%s\"" % align + if valign and valign != "middle": + attr += " valign=\"%s\"" % valign + if colspan > 1: + attr += " colspan=\"%s\"" % colspan + if rowspan > 1: + attr += " rowspan=\"%s\"" % rowspan + for q in range(r+1, min(r+rowspan, len(rows))): + rows[q].cells[i] = colspan + if style: + attr += " style=\"%s\"" % style + out.write(" \n" % attr) + if th is not None: + out.write(" %s\n" % htmlEncode(td.text)) + out.write(" \n") + i += colspan + out.write(" \n") + + out.write(" \n
%s
\n
\n") + +def htmlPrintHeader(out, title = None): + if title: + titletag = "%s\n" % htmlEncode([str(title)]) + else: + titletag = "" + out.write(""" + + + +%s + + +""" % titletag) + +def htmlPrintFooter(out): + out.write("\n") + +def getStdoutFilename(): + try: + if os.name == "nt": + import msvcrt, ctypes + handle = msvcrt.get_osfhandle(sys.stdout.fileno()) + size = ctypes.c_ulong(1024) + nameBuffer = ctypes.create_string_buffer(size.value) + ctypes.windll.kernel32.GetFinalPathNameByHandleA(handle, nameBuffer, size, 4) + return nameBuffer.value + else: + return os.readlink('/proc/self/fd/1') + except: + return "" + +def detectHtmlOutputType(requestedType): + if requestedType == "txt": + return False + elif requestedType == "html": + return True + else: + if sys.stdout.isatty(): + return False + else: + outname = getStdoutFilename() + if outname: + if outname.endswith(".htm") or outname.endswith(".html"): + return True + else: + return False + else: + return False + +def getRelativeVal(test, test0, metric): + if not test or not test0: + return None + val0 = test0.get(metric, "s") + if not val0 or val0 == 0: + return None + val = test.get(metric, "s") + if not val: + return None + return float(val)/val0 + + +metrix_table = \ +{ + "name": ("Name of Test", lambda test,test0,units: str(test)), + + "samples": ("Number of\ncollected samples", lambda test,test0,units: test.get("samples", units)), + "outliers": ("Number of\noutliers", lambda test,test0,units: test.get("outliers", units)), + + "gmean": ("Geometric mean", lambda test,test0,units: test.get("gmean", units)), + "mean": ("Mean", lambda test,test0,units: test.get("mean", units)), + "min": ("Min", lambda test,test0,units: test.get("min", units)), + "median": ("Median", lambda test,test0,units: test.get("median", units)), + "stddev": ("Standard deviation", lambda test,test0,units: test.get("stddev", units)), + "gstddev": ("Standard deviation of Ln(time)", lambda test,test0,units: test.get("gstddev")), + + "gmean%": ("Geometric mean (relative)", lambda test,test0,units: getRelativeVal(test, test0, "gmean")), + "mean%": ("Mean (relative)", lambda test,test0,units: getRelativeVal(test, test0, "mean")), + "min%": ("Min (relative)", lambda test,test0,units: getRelativeVal(test, test0, "min")), + "median%": ("Median (relative)", lambda test,test0,units: getRelativeVal(test, test0, "median")), + "stddev%": ("Standard deviation (relative)", lambda test,test0,units: getRelativeVal(test, test0, "stddev")), + "gstddev%": ("Standard deviation of Ln(time) (relative)", lambda test,test0,units: getRelativeVal(test, test0, "gstddev")), +} + +if __name__ == "__main__": + if len(sys.argv) < 2: + print "Usage:\n", os.path.basename(sys.argv[0]), ".xml" + exit(0) + + parser = OptionParser() + parser.add_option("-o", "--output", dest="format", help="output results in text format (can be 'txt', 'html' or 'auto' - default)", metavar="FMT", default="auto") + parser.add_option("-m", "--metric", dest="metric", help="output metric", metavar="NAME", default="gmean") + parser.add_option("-u", "--units", dest="units", help="units for output values (s, ms (default), mks, ns or ticks)", metavar="UNITS", default="ms") + (options, args) = parser.parse_args() + + options.generateHtml = detectHtmlOutputType(options.format) + if options.metric not in metrix_table: + options.metric = "gmean" + + #print options + #print args + +# tbl = table() +# tbl.newColumn("first", "qqqq", align = "left") +# tbl.newColumn("second", "wwww\nz\nx\n") +# tbl.newColumn("third", "wwasdas") +# +# tbl.newCell(0, "ccc111", align = "right") +# tbl.newCell(1, "dddd1") +# tbl.newCell(2, "8768756754") +# tbl.newRow() +# tbl.newCell(0, "1\n2\n3\n4\n5\n6\n7", align = "center", colspan = 2, rowspan = 2) +# tbl.newCell(2, "xxx\nqqq", align = "center", colspan = 1, valign = "middle") +# tbl.newRow() +# tbl.newCell(2, "+", align = "center", colspan = 1, valign = "middle") +# tbl.newRow() +# tbl.newCell(0, "vcvvbasdsadassdasdasv", align = "right", colspan = 2) +# tbl.newCell(2, "dddd1") +# tbl.newRow() +# tbl.newCell(0, "vcvvbv") +# tbl.newCell(1, "3445324", align = "right") +# tbl.newCell(2, None) +# tbl.newCell(1, "0000") +# if sys.stdout.isatty(): +# tbl.consolePrintTable(sys.stdout) +# else: +# htmlPrintHeader(sys.stdout) +# tbl.htmlPrintTable(sys.stdout) +# htmlPrintFooter(sys.stdout) + + import testlog_parser + + if options.generateHtml: + htmlPrintHeader(sys.stdout, "Tables demo") + + getter = metrix_table[options.metric][1] + + for arg in args: + tests = testlog_parser.parseLogFile(arg) + tbl = table(arg) + tbl.newColumn("name", "Name of Test", align = "left") + tbl.newColumn("value", metrix_table[options.metric][0], align = "center", bold = "true") + + for t in sorted(tests): + tbl.newRow() + tbl.newCell("name", str(t)) + + status = t.get("status") + if status != "run": + tbl.newCell("value", status) + else: + val = getter(t, None, options.units) + if val: + if options.metric.endswith("%"): + tbl.newCell("value", "%.2f" % val, val) + else: + tbl.newCell("value", "%.3f %s" % (val, options.units), val) + else: + tbl.newCell("value", "-") + + if options.generateHtml: + tbl.htmlPrintTable(sys.stdout) + else: + tbl.consolePrintTable(sys.stdout) + + if options.generateHtml: + htmlPrintFooter(sys.stdout) \ No newline at end of file diff --git a/modules/ts/misc/testlog_parser.py b/modules/ts/misc/testlog_parser.py new file mode 100644 index 000000000..caab53d4e --- /dev/null +++ b/modules/ts/misc/testlog_parser.py @@ -0,0 +1,171 @@ +import sys, re, os.path +from xml.dom.minidom import parse + +class TestInfo(object): + + def __init__(self, xmlnode): + self.fixture = xmlnode.getAttribute("classname") + self.name = xmlnode.getAttribute("name") + self.value_param = xmlnode.getAttribute("value_param") + self.type_param = xmlnode.getAttribute("type_param") + self.name = xmlnode.getAttribute("name") + if xmlnode.getElementsByTagName("failure"): + self.status = "failed" + else: + self.status = xmlnode.getAttribute("status") + if self.name.startswith("DISABLED_"): + self.status = "disabled" + self.fixture = self.fixture.replace("DISABLED_", "") + self.name = self.name.replace("DISABLED_", "") + self.metrix = {} + self.parseLongMetric(xmlnode, "bytesIn"); + self.parseLongMetric(xmlnode, "bytesOut"); + self.parseIntMetric(xmlnode, "samples"); + self.parseIntMetric(xmlnode, "outliers"); + self.parseFloatMetric(xmlnode, "frequency", 1); + self.parseLongMetric(xmlnode, "min"); + self.parseLongMetric(xmlnode, "median"); + self.parseLongMetric(xmlnode, "gmean"); + self.parseLongMetric(xmlnode, "mean"); + self.parseLongMetric(xmlnode, "stddev"); + self.parseFloatMetric(xmlnode, "gstddev"); + + def parseLongMetric(self, xmlnode, name, default = 0): + if xmlnode.hasAttribute(name): + tmp = xmlnode.getAttribute(name) + val = long(tmp) + self.metrix[name] = val + else: + self.metrix[name] = default + + def parseIntMetric(self, xmlnode, name, default = 0): + if xmlnode.hasAttribute(name): + tmp = xmlnode.getAttribute(name) + val = int(tmp) + self.metrix[name] = val + else: + self.metrix[name] = default + + def parseFloatMetric(self, xmlnode, name, default = 0): + if xmlnode.hasAttribute(name): + tmp = xmlnode.getAttribute(name) + val = float(tmp) + self.metrix[name] = val + else: + self.metrix[name] = default + + def parseStringMetric(self, xmlnode, name, default = None): + if xmlnode.hasAttribute(name): + tmp = xmlnode.getAttribute(name) + self.metrix[name] = tmp.strip() + else: + self.metrix[name] = default + + def get(self, name, units="ms"): + if name == "classname": + return self.fixture + if name == "name": + return self.name + if name == "fullname": + return self.__str__() + if name == "value_param": + return self.value_param + if name == "type_param": + return self.type_param + if name == "status": + return self.status + val = self.metrix.get(name, None) + if not val: + return val + if name in ["gmean", "min", "mean", "median", "stddev"]: + scale = 1.0 + frequency = self.metrix.get("frequency", 1.0) or 1.0 + if units == "ms": + scale = 1000.0 + if units == "mks": + scale = 1000000.0 + if units == "ns": + scale = 1000000000.0 + if units == "ticks": + frequency = long(1) + scale = long(1) + return val * scale / frequency + return val + + + def dump(self, units="ms"): + print "%s ->\t\033[1;31m%s\033[0m = \t%.2f%s" % (str(self), self.status, self.get("gmean", units), units) + + def shortName(self): + pos = self.name.find("/") + if pos > 0: + name = self.name[:pos] + else: + name = self.name + if self.fixture.endswith(name): + fixture = self.fixture[:-len(name)] + else: + fixture = self.fixture + if fixture.endswith("_"): + fixture = fixture[:-1] + return '::'.join(filter(None, [name, fixture])) + + def __str__(self): + pos = self.name.find("/") + if pos > 0: + name = self.name[:pos] + else: + name = self.name + if self.fixture.endswith(name): + fixture = self.fixture[:-len(name)] + else: + fixture = self.fixture + if fixture.endswith("_"): + fixture = fixture[:-1] + return '::'.join(filter(None, [name, fixture, self.type_param, self.value_param])) + + def __cmp__(self, other): + r = cmp(self.fixture, other.fixture); + if r != 0: + return r + if self.type_param: + if other.type_param: + r = cmp(self.type_param, other.type_param); + if r != 0: + return r + else: + return -1 + else: + if other.type_param: + return 1 + if self.value_param: + if other.value_param: + r = cmp(self.value_param, other.value_param); + if r != 0: + return r + else: + return -1 + else: + if other.value_param: + return 1 + return 0 + +def parseLogFile(filename): + tests = [] + log = parse(filename) + for case in log.getElementsByTagName("testcase"): + tests.append(TestInfo(case)) + return tests + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print "Usage:\n", os.path.basename(sys.argv[0]), ".xml" + exit(0) + + for arg in sys.argv[1:]: + print "Tests found in", arg + tests = parseLogFile(arg) + for t in sorted(tests): + t.dump() + print diff --git a/modules/ts/src/precomp.cpp b/modules/ts/src/precomp.cpp index b68b160c5..c149df18f 100644 --- a/modules/ts/src/precomp.cpp +++ b/modules/ts/src/precomp.cpp @@ -1,13 +1 @@ #include "precomp.hpp" - -#if ANDROID -int wcscasecmp(const wchar_t* lhs, const wchar_t* rhs) -{ - wint_t left, right; - do { - left = towlower(*lhs++); - right = towlower(*rhs++); - } while (left && left == right); - return left == right; -} -#endif diff --git a/modules/ts/src/precomp.hpp b/modules/ts/src/precomp.hpp index f59fc73f6..d2967ea7d 100644 --- a/modules/ts/src/precomp.hpp +++ b/modules/ts/src/precomp.hpp @@ -1,12 +1,10 @@ -#define GTEST_CREATE_AS_SHARED_LIBRARY 1 - #if _MSC_VER >= 1200 #pragma warning( disable: 4127 4251) #endif -#include "opencv2/ts/ts.hpp" #include "opencv2/core/core_c.h" +#include "opencv2/ts/ts.hpp" -#if ANDROID -int wcscasecmp(const wchar_t* lhs, const wchar_t* rhs); -#endif \ No newline at end of file +#if GTEST_LINKED_AS_SHARED_LIBRARY +#error ts module should not have GTEST_LINKED_AS_SHARED_LIBRARY defined +#endif diff --git a/modules/ts/src/ts_gtest.cpp b/modules/ts/src/ts_gtest.cpp index 9b1daefee..c4828e5de 100644 --- a/modules/ts/src/ts_gtest.cpp +++ b/modules/ts/src/ts_gtest.cpp @@ -36,7 +36,7 @@ // This line ensures that gtest.h can be compiled on its own, even // when it's fused. -#include "precomp.hpp" +#include "opencv2/ts/ts_gtest.h" // The following lines pull in the real gtest *.cc files. // Copyright 2005, Google Inc. @@ -171,12 +171,12 @@ class GTEST_API_ SingleFailureChecker { // The constructor remembers the arguments. SingleFailureChecker(const TestPartResultArray* results, TestPartResult::Type type, - const char* substr); + const string& substr); ~SingleFailureChecker(); private: const TestPartResultArray* const results_; const TestPartResult::Type type_; - const String substr_; + const string substr_; GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); }; @@ -313,7 +313,7 @@ class GTEST_API_ SingleFailureChecker { #include #include -#include +#include // NOLINT #include #include @@ -321,72 +321,76 @@ class GTEST_API_ SingleFailureChecker { // TODO(kenton@google.com): Use autoconf to detect availability of // gettimeofday(). -#define GTEST_HAS_GETTIMEOFDAY_ 1 +# define GTEST_HAS_GETTIMEOFDAY_ 1 -#include -#include -#include +# include // NOLINT +# include // NOLINT +# include // NOLINT // Declares vsnprintf(). This header is not available on Windows. -#include -#include -#include -#include -#include -#include +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include #elif GTEST_OS_SYMBIAN -#define GTEST_HAS_GETTIMEOFDAY_ 1 -#include // NOLINT +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT #elif GTEST_OS_ZOS -#define GTEST_HAS_GETTIMEOFDAY_ 1 -#include // NOLINT +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT // On z/OS we additionally need strings.h for strcasecmp. -#include // NOLINT +# include // NOLINT #elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE. -#include // NOLINT +# include // NOLINT #elif GTEST_OS_WINDOWS // We are on Windows proper. -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include // NOLINT -#if GTEST_OS_WINDOWS_MINGW +# if GTEST_OS_WINDOWS_MINGW // MinGW has gettimeofday() but not _ftime64(). // TODO(kenton@google.com): Use autoconf to detect availability of // gettimeofday(). // TODO(kenton@google.com): There are other ways to get the time on // Windows, like GetTickCount() or GetSystemTimeAsFileTime(). MinGW // supports these. consider using them instead. -#define GTEST_HAS_GETTIMEOFDAY_ 1 -#include // NOLINT -#endif // GTEST_OS_WINDOWS_MINGW +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT +# endif // GTEST_OS_WINDOWS_MINGW // cpplint thinks that the header is already included, so we want to // silence it. -#include // NOLINT +# include // NOLINT #else // Assume other platforms have gettimeofday(). // TODO(kenton@google.com): Use autoconf to detect availability of // gettimeofday(). -#define GTEST_HAS_GETTIMEOFDAY_ 1 +# define GTEST_HAS_GETTIMEOFDAY_ 1 // cpplint thinks that the header is already included, so we want to // silence it. -#include // NOLINT -#include // NOLINT +# include // NOLINT +# include // NOLINT #endif // GTEST_OS_LINUX #if GTEST_HAS_EXCEPTIONS -#include +# include +#endif + +#if GTEST_CAN_STREAM_RESULTS_ +# include // NOLINT +# include // NOLINT #endif // Indicates that this translation unit is part of Google Test's @@ -438,12 +442,12 @@ class GTEST_API_ SingleFailureChecker { // part of Google Test's implementation; otherwise it's undefined. #if !GTEST_IMPLEMENTATION_ // A user is trying to include this from his code - just say no. -#error "gtest-internal-inl.h is part of Google Test's internal implementation." -#error "It must not be included except by Google Test itself." +# error "gtest-internal-inl.h is part of Google Test's internal implementation." +# error "It must not be included except by Google Test itself." #endif // GTEST_IMPLEMENTATION_ #ifndef _WIN32_WCE -#include +# include #endif // !_WIN32_WCE #include #include // For strtoll/_strtoul64/malloc/free. @@ -455,7 +459,7 @@ class GTEST_API_ SingleFailureChecker { #if GTEST_OS_WINDOWS -#include // For DWORD. +# include // NOLINT #endif // GTEST_OS_WINDOWS @@ -487,6 +491,7 @@ const char kRandomSeedFlag[] = "random_seed"; const char kRepeatFlag[] = "repeat"; const char kShuffleFlag[] = "shuffle"; const char kStackTraceDepthFlag[] = "stack_trace_depth"; +const char kStreamResultToFlag[] = "stream_result_to"; const char kThrowOnFailureFlag[] = "throw_on_failure"; // A valid random seed must be in [1, kMaxRandomSeed]. @@ -559,6 +564,7 @@ class GTestFlagSaver { repeat_ = GTEST_FLAG(repeat); shuffle_ = GTEST_FLAG(shuffle); stack_trace_depth_ = GTEST_FLAG(stack_trace_depth); + stream_result_to_ = GTEST_FLAG(stream_result_to); throw_on_failure_ = GTEST_FLAG(throw_on_failure); } @@ -579,6 +585,7 @@ class GTestFlagSaver { GTEST_FLAG(repeat) = repeat_; GTEST_FLAG(shuffle) = shuffle_; GTEST_FLAG(stack_trace_depth) = stack_trace_depth_; + GTEST_FLAG(stream_result_to) = stream_result_to_; GTEST_FLAG(throw_on_failure) = throw_on_failure_; } private: @@ -599,6 +606,7 @@ class GTestFlagSaver { internal::Int32 repeat_; bool shuffle_; internal::Int32 stack_trace_depth_; + String stream_result_to_; bool throw_on_failure_; } GTEST_ATTRIBUTE_UNUSED_; @@ -661,7 +669,14 @@ GTEST_API_ bool ShouldRunTestOnShard( // the given predicate. template inline int CountIf(const Container& c, Predicate predicate) { - return static_cast(std::count_if(c.begin(), c.end(), predicate)); + // Implemented as an explicit loop since std::count_if() in libCstd on + // Solaris has a non-standard signature. + int count = 0; + for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) { + if (predicate(*it)) + ++count; + } + return count; } // Applies a function/functor to each element in the container. @@ -734,85 +749,6 @@ class TestPropertyKeyIs { String key_; }; -class TestInfoImpl { - public: - TestInfoImpl(TestInfo* parent, const char* test_case_name, - const char* name, const char* test_case_comment, - const char* comment, TypeId fixture_class_id, - internal::TestFactoryBase* factory); - ~TestInfoImpl(); - - // Returns true if this test should run. - bool should_run() const { return should_run_; } - - // Sets the should_run member. - void set_should_run(bool should) { should_run_ = should; } - - // Returns true if this test is disabled. Disabled tests are not run. - bool is_disabled() const { return is_disabled_; } - - // Sets the is_disabled member. - void set_is_disabled(bool is) { is_disabled_ = is; } - - // Returns true if this test matches the filter specified by the user. - bool matches_filter() const { return matches_filter_; } - - // Sets the matches_filter member. - void set_matches_filter(bool matches) { matches_filter_ = matches; } - - // Returns the test case name. - const char* test_case_name() const { return test_case_name_.c_str(); } - - // Returns the test name. - const char* name() const { return name_.c_str(); } - - // Returns the test case comment. - const char* test_case_comment() const { return test_case_comment_.c_str(); } - - // Returns the test comment. - const char* comment() const { return comment_.c_str(); } - - // Returns the ID of the test fixture class. - TypeId fixture_class_id() const { return fixture_class_id_; } - - // Returns the test result. - TestResult* result() { return &result_; } - const TestResult* result() const { return &result_; } - - // Creates the test object, runs it, records its result, and then - // deletes it. - void Run(); - - // Clears the test result. - void ClearResult() { result_.Clear(); } - - // Clears the test result in the given TestInfo object. - static void ClearTestResult(TestInfo * test_info) { - test_info->impl()->ClearResult(); - } - - private: - // These fields are immutable properties of the test. - TestInfo* const parent_; // The owner of this object - const String test_case_name_; // Test case name - const String name_; // Test name - const String test_case_comment_; // Test case comment - const String comment_; // Test comment - const TypeId fixture_class_id_; // ID of the test fixture class - bool should_run_; // True iff this test should run - bool is_disabled_; // True iff this test is disabled - bool matches_filter_; // True if this test matches the - // user-specified filter. - internal::TestFactoryBase* const factory_; // The factory that creates - // the test object - - // This field is mutable and needs to be reset before running the - // test for the second time. - TestResult result_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfoImpl); -}; - // Class UnitTestOptions. // // This class contains functions for processing options the user @@ -1076,10 +1012,12 @@ class GTEST_API_ UnitTestImpl { // Arguments: // // test_case_name: name of the test case + // type_param: the name of the test's type parameter, or NULL if + // this is not a typed or a type-parameterized test. // set_up_tc: pointer to the function that sets up the test case // tear_down_tc: pointer to the function that tears down the test case TestCase* GetTestCase(const char* test_case_name, - const char* comment, + const char* type_param, Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc); @@ -1092,7 +1030,7 @@ class GTEST_API_ UnitTestImpl { // test_info: the TestInfo object void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc, - TestInfo * test_info) { + TestInfo* test_info) { // In order to support thread-safe death tests, we need to // remember the original working directory when the test program // was first invoked. We cannot do this in RUN_ALL_TESTS(), as @@ -1107,7 +1045,7 @@ class GTEST_API_ UnitTestImpl { } GetTestCase(test_info->test_case_name(), - test_info->test_case_comment(), + test_info->type_param(), set_up_tc, tear_down_tc)->AddTestInfo(test_info); } @@ -1133,24 +1071,26 @@ class GTEST_API_ UnitTestImpl { } // Registers all parameterized tests defined using TEST_P and - // INSTANTIATE_TEST_P, creating regular tests for each test/parameter - // combination. This method can be called more then once; it has - // guards protecting from registering the tests more then once. - // If value-parameterized tests are disabled, RegisterParameterizedTests - // is present but does nothing. + // INSTANTIATE_TEST_CASE_P, creating regular tests for each test/parameter + // combination. This method can be called more then once; it has guards + // protecting from registering the tests more then once. If + // value-parameterized tests are disabled, RegisterParameterizedTests is + // present but does nothing. void RegisterParameterizedTests(); // Runs all tests in this UnitTest object, prints the result, and - // returns 0 if all tests are successful, or 1 otherwise. If any - // exception is thrown during a test on Windows, this test is - // considered to be failed, but the rest of the tests will still be - // run. (We disable exceptions on Linux and Mac OS X, so the issue - // doesn't apply there.) - int RunAllTests(); + // returns true if all tests are successful. If any exception is + // thrown during a test, this test is considered to be failed, but + // the rest of the tests will still be run. + bool RunAllTests(); - // Clears the results of all tests, including the ad hoc test. - void ClearResult() { + // Clears the results of all tests, except the ad hoc tests. + void ClearNonAdHocTestResult() { ForEach(test_cases_, TestCase::ClearTestCaseResult); + } + + // Clears the results of ad-hoc test assertions. + void ClearAdHocTestResult() { ad_hoc_test_result_.Clear(); } @@ -1212,6 +1152,12 @@ class GTEST_API_ UnitTestImpl { // UnitTestOptions. Must not be called before InitGoogleTest. void ConfigureXmlOutput(); +#if GTEST_CAN_STREAM_RESULTS_ + // Initializes the event listener for streaming test results to a socket. + // Must not be called before InitGoogleTest. + void ConfigureStreamingOutput(); +#endif + // Performs initialization dependent upon flag values obtained in // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest @@ -1232,9 +1178,17 @@ class GTEST_API_ UnitTestImpl { // Restores the test cases and tests to their order before the first shuffle. void UnshuffleTests(); + // Returns the value of GTEST_FLAG(catch_exceptions) at the moment + // UnitTest::Run() starts. + bool catch_exceptions() const { return catch_exceptions_; } + private: friend class ::testing::UnitTest; + // Used by UnitTest::Run() to capture the state of + // GTEST_FLAG(catch_exceptions) at the moment it starts. + void set_catch_exceptions(bool value) { catch_exceptions_ = value; } + // The UnitTest object that owns this implementation object. UnitTest* const parent_; @@ -1337,6 +1291,10 @@ class GTEST_API_ UnitTestImpl { // A per-thread stack of traces created by the SCOPED_TRACE() macro. internal::ThreadLocal > gtest_trace_stack_; + // The value of GTEST_FLAG(catch_exceptions) at the moment RunAllTests() + // starts. + bool catch_exceptions_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTestImpl); }; // class UnitTestImpl @@ -1346,14 +1304,16 @@ inline UnitTestImpl* GetUnitTestImpl() { return UnitTest::GetInstance()->impl(); } +#if GTEST_USES_SIMPLE_RE + // Internal helper functions for implementing the simple regular // expression matcher. GTEST_API_ bool IsInSet(char ch, const char* str); -GTEST_API_ bool IsDigit(char ch); -GTEST_API_ bool IsPunct(char ch); +GTEST_API_ bool IsAsciiDigit(char ch); +GTEST_API_ bool IsAsciiPunct(char ch); GTEST_API_ bool IsRepeat(char ch); -GTEST_API_ bool IsWhiteSpace(char ch); -GTEST_API_ bool IsWordChar(char ch); +GTEST_API_ bool IsAsciiWhiteSpace(char ch); +GTEST_API_ bool IsAsciiWordChar(char ch); GTEST_API_ bool IsValidEscape(char ch); GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch); GTEST_API_ bool ValidateRegex(const char* regex); @@ -1362,6 +1322,8 @@ GTEST_API_ bool MatchRepetitionAndRegexAtHead( bool escaped, char ch, char repeat, const char* regex, const char* str); GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str); +#endif // GTEST_USES_SIMPLE_RE + // Parses the command line for Google Test flags, without initializing // other parts of Google Test. GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv); @@ -1371,9 +1333,9 @@ GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); // Returns the message describing the last system error, regardless of the // platform. -String GetLastErrnoDescription(); +GTEST_API_ String GetLastErrnoDescription(); -#if GTEST_OS_WINDOWS +# if GTEST_OS_WINDOWS // Provides leak-safe Windows kernel handle ownership. class AutoHandle { public: @@ -1397,7 +1359,7 @@ class AutoHandle { GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle); }; -#endif // GTEST_OS_WINDOWS +# endif // GTEST_OS_WINDOWS // Attempts to parse a string into a positive integer pointed to by the // number parameter. Returns true if that is possible. @@ -1408,7 +1370,7 @@ bool ParseNaturalNumber(const ::std::string& str, Integer* number) { // Fail fast if the given string does not begin with a digit; // this bypasses strtoXXX's "optional leading whitespace and plus // or minus sign" semantics, which are undesirable here. - if (str.empty() || !isdigit(str[0])) { + if (str.empty() || !IsDigit(str[0])) { return false; } errno = 0; @@ -1416,14 +1378,20 @@ bool ParseNaturalNumber(const ::std::string& str, Integer* number) { char* end; // BiggestConvertible is the largest integer type that system-provided // string-to-number conversion routines can return. -#if GTEST_OS_WINDOWS && !defined(__GNUC__) + +# if GTEST_OS_WINDOWS && !defined(__GNUC__) + // MSVC and C++ Builder define __int64 instead of the standard long long. typedef unsigned __int64 BiggestConvertible; const BiggestConvertible parsed = _strtoui64(str.c_str(), &end, 10); -#else + +# else + typedef unsigned long long BiggestConvertible; // NOLINT const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10); -#endif // GTEST_OS_WINDOWS && !defined(__GNUC__) + +# endif // GTEST_OS_WINDOWS && !defined(__GNUC__) + const bool parse_success = *end == '\0' && errno == 0; // TODO(vladl@google.com): Convert this to compile time assertion when it is @@ -1469,7 +1437,7 @@ class TestResultAccessor { #undef GTEST_IMPLEMENTATION_ #if GTEST_OS_WINDOWS -#define vsnprintf _vsnprintf +# define vsnprintf _vsnprintf #endif // GTEST_OS_WINDOWS namespace testing { @@ -1527,7 +1495,7 @@ GTEST_DEFINE_bool_( GTEST_DEFINE_bool_( catch_exceptions, - internal::BoolFromGTestEnv("catch_exceptions", false), + internal::BoolFromGTestEnv("catch_exceptions", true), "True iff " GTEST_NAME_ " should catch exceptions and treat them as test failures."); @@ -1598,6 +1566,13 @@ GTEST_DEFINE_int32_( "The maximum number of stack frames to print when an " "assertion fails. The valid range is 0 through 100, inclusive."); +GTEST_DEFINE_string_( + stream_result_to, + internal::StringFromGTestEnv("stream_result_to", ""), + "This flag specifies the host name and the port number on which to stream " + "test results. Example: \"localhost:555\". The flag is effective only on " + "Linux."); + GTEST_DEFINE_bool_( throw_on_failure, internal::BoolFromGTestEnv("throw_on_failure", false), @@ -1830,20 +1805,33 @@ bool UnitTestOptions::FilterMatchesTest(const String &test_case_name, !MatchesFilter(full_name, negative.c_str())); } -#if GTEST_OS_WINDOWS +#if GTEST_HAS_SEH // Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the // given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. // This function is useful as an __except condition. int UnitTestOptions::GTestShouldProcessSEH(DWORD exception_code) { - // Google Test should handle an exception if: + // Google Test should handle a SEH exception if: // 1. the user wants it to, AND - // 2. this is not a breakpoint exception. - return (GTEST_FLAG(catch_exceptions) && - exception_code != EXCEPTION_BREAKPOINT) ? - EXCEPTION_EXECUTE_HANDLER : - EXCEPTION_CONTINUE_SEARCH; + // 2. this is not a breakpoint exception, AND + // 3. this is not a C++ exception (VC++ implements them via SEH, + // apparently). + // + // SEH exception code for C++ exceptions. + // (see http://support.microsoft.com/kb/185294 for more information). + const DWORD kCxxExceptionCode = 0xe06d7363; + + bool should_handle = true; + + if (!GTEST_FLAG(catch_exceptions)) + should_handle = false; + else if (exception_code == EXCEPTION_BREAKPOINT) + should_handle = false; + else if (exception_code == kCxxExceptionCode) + should_handle = false; + + return should_handle ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH; } -#endif // GTEST_OS_WINDOWS +#endif // GTEST_HAS_SEH } // namespace internal @@ -1923,7 +1911,7 @@ AssertionResult HasOneFailure(const char* /* results_expr */, const char* /* substr_expr */, const TestPartResultArray& results, TestPartResult::Type type, - const char* substr) { + const string& substr) { const String expected(type == TestPartResult::kFatalFailure ? "1 fatal failure" : "1 non-fatal failure"); @@ -1934,23 +1922,21 @@ AssertionResult HasOneFailure(const char* /* results_expr */, for (int i = 0; i < results.size(); i++) { msg << "\n" << results.GetTestPartResult(i); } - return AssertionFailure(msg); + return AssertionFailure() << msg; } const TestPartResult& r = results.GetTestPartResult(0); if (r.type() != type) { - msg << "Expected: " << expected << "\n" - << " Actual:\n" - << r; - return AssertionFailure(msg); + return AssertionFailure() << "Expected: " << expected << "\n" + << " Actual:\n" + << r; } - if (strstr(r.message(), substr) == NULL) { - msg << "Expected: " << expected << " containing \"" - << substr << "\"\n" - << " Actual:\n" - << r; - return AssertionFailure(msg); + if (strstr(r.message(), substr.c_str()) == NULL) { + return AssertionFailure() << "Expected: " << expected << " containing \"" + << substr << "\"\n" + << " Actual:\n" + << r; } return AssertionSuccess(); @@ -1962,7 +1948,7 @@ AssertionResult HasOneFailure(const char* /* results_expr */, SingleFailureChecker:: SingleFailureChecker( const TestPartResultArray* results, TestPartResult::Type type, - const char* substr) + const string& substr) : results_(results), type_(type), substr_(substr) {} @@ -1972,7 +1958,7 @@ SingleFailureChecker:: SingleFailureChecker( // type and contains the given substring. If that's not the case, a // non-fatal failure will be generated. SingleFailureChecker::~SingleFailureChecker() { - EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_.c_str()); + EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_); } DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter( @@ -2104,25 +2090,30 @@ TimeInMillis GetTimeInMillis() { return 0; #elif GTEST_OS_WINDOWS && !GTEST_HAS_GETTIMEOFDAY_ __timeb64 now; -#ifdef _MSC_VER + +# ifdef _MSC_VER + // MSVC 8 deprecates _ftime64(), so we want to suppress warning 4996 // (deprecated function) there. // TODO(kenton@google.com): Use GetTickCount()? Or use // SystemTimeToFileTime() -#pragma warning(push) // Saves the current warning state. -#pragma warning(disable:4996) // Temporarily disables warning 4996. +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4996) // Temporarily disables warning 4996. _ftime64(&now); -#pragma warning(pop) // Restores the warning state. -#else +# pragma warning(pop) // Restores the warning state. +# else + _ftime64(&now); -#endif // _MSC_VER + +# endif // _MSC_VER + return static_cast(now.time) * 1000 + now.millitm; #elif GTEST_HAS_GETTIMEOFDAY_ struct timeval now; gettimeofday(&now, NULL); return static_cast(now.tv_sec) * 1000 + now.tv_usec / 1000; #else -#error "Don't know how to get the current time on your system." +# error "Don't know how to get the current time on your system." #endif } @@ -2258,55 +2249,13 @@ Message& Message::operator <<(const ::wstring& wstr) { } #endif // GTEST_HAS_GLOBAL_WSTRING -namespace internal { - -// Formats a value to be used in a failure message. - -// For a char value, we print it as a C++ char literal and as an -// unsigned integer (both in decimal and in hexadecimal). -String FormatForFailureMessage(char ch) { - const unsigned int ch_as_uint = ch; - // A String object cannot contain '\0', so we print "\\0" when ch is - // '\0'. - return String::Format("'%s' (%u, 0x%X)", - ch ? String::Format("%c", ch).c_str() : "\\0", - ch_as_uint, ch_as_uint); -} - -// For a wchar_t value, we print it as a C++ wchar_t literal and as an -// unsigned integer (both in decimal and in hexidecimal). -String FormatForFailureMessage(wchar_t wchar) { - // The C++ standard doesn't specify the exact size of the wchar_t - // type. It just says that it shall have the same size as another - // integral type, called its underlying type. - // - // Therefore, in order to print a wchar_t value in the numeric form, - // we first convert it to the largest integral type (UInt64) and - // then print the converted value. - // - // We use streaming to print the value as "%llu" doesn't work - // correctly with MSVC 7.1. - const UInt64 wchar_as_uint64 = wchar; - Message msg; - // A String object cannot contain '\0', so we print "\\0" when wchar is - // L'\0'. - char buffer[32]; // CodePointToUtf8 requires a buffer that big. - msg << "L'" - << (wchar ? CodePointToUtf8(static_cast(wchar), buffer) : "\\0") - << "' (" << wchar_as_uint64 << ", 0x" << ::std::setbase(16) - << wchar_as_uint64 << ")"; - return msg.GetString(); -} - -} // namespace internal - // AssertionResult constructors. // Used in EXPECT_TRUE/FALSE(assertion_result). AssertionResult::AssertionResult(const AssertionResult& other) : success_(other.success_), message_(other.message_.get() != NULL ? - new internal::String(*other.message_) : - static_cast(NULL)) { + new ::std::string(*other.message_) : + static_cast< ::std::string*>(NULL)) { } // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. @@ -2369,7 +2318,7 @@ AssertionResult EqFailure(const char* expected_expression, msg << "\nWhich is: " << expected_value; } - return AssertionFailure(msg); + return AssertionFailure() << msg; } // Constructs a failure message for Boolean assertions such as EXPECT_TRUE. @@ -2399,13 +2348,12 @@ AssertionResult DoubleNearPredFormat(const char* expr1, // TODO(wan): do not print the value of an expression if it's // already a literal. - Message msg; - msg << "The difference between " << expr1 << " and " << expr2 + return AssertionFailure() + << "The difference between " << expr1 << " and " << expr2 << " is " << diff << ", which exceeds " << abs_error_expr << ", where\n" << expr1 << " evaluates to " << val1 << ",\n" << expr2 << " evaluates to " << val2 << ", and\n" << abs_error_expr << " evaluates to " << abs_error << "."; - return AssertionFailure(msg); } @@ -2430,20 +2378,18 @@ AssertionResult FloatingPointLE(const char* expr1, // val2 is NaN, as the IEEE floating-point standard requires that // any predicate involving a NaN must return false. - StrStream val1_ss; + ::std::stringstream val1_ss; val1_ss << std::setprecision(std::numeric_limits::digits10 + 2) << val1; - StrStream val2_ss; + ::std::stringstream val2_ss; val2_ss << std::setprecision(std::numeric_limits::digits10 + 2) << val2; - Message msg; - msg << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n" - << " Actual: " << StrStreamToString(&val1_ss) << " vs " - << StrStreamToString(&val2_ss); - - return AssertionFailure(msg); + return AssertionFailure() + << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n" + << " Actual: " << StringStreamToString(&val1_ss) << " vs " + << StringStreamToString(&val2_ss); } } // namespace internal @@ -2490,11 +2436,10 @@ AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ if (val1 op val2) {\ return AssertionSuccess();\ } else {\ - Message msg;\ - msg << "Expected: (" << expr1 << ") " #op " (" << expr2\ + return AssertionFailure() \ + << "Expected: (" << expr1 << ") " #op " (" << expr2\ << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ << " vs " << FormatForComparisonFailureMessage(val2, val1);\ - return AssertionFailure(msg);\ }\ } @@ -2556,11 +2501,9 @@ AssertionResult CmpHelperSTRNE(const char* s1_expression, if (!String::CStringEquals(s1, s2)) { return AssertionSuccess(); } else { - Message msg; - msg << "Expected: (" << s1_expression << ") != (" - << s2_expression << "), actual: \"" - << s1 << "\" vs \"" << s2 << "\""; - return AssertionFailure(msg); + return AssertionFailure() << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; } } @@ -2572,11 +2515,10 @@ AssertionResult CmpHelperSTRCASENE(const char* s1_expression, if (!String::CaseInsensitiveCStringEquals(s1, s2)) { return AssertionSuccess(); } else { - Message msg; - msg << "Expected: (" << s1_expression << ") != (" + return AssertionFailure() + << "Expected: (" << s1_expression << ") != (" << s2_expression << ") (ignoring case), actual: \"" << s1 << "\" vs \"" << s2 << "\""; - return AssertionFailure(msg); } } @@ -2625,13 +2567,12 @@ AssertionResult IsSubstringImpl( const bool is_wide_string = sizeof(needle[0]) > 1; const char* const begin_string_quote = is_wide_string ? "L\"" : "\""; - return AssertionFailure( - Message() + return AssertionFailure() << "Value of: " << needle_expr << "\n" << " Actual: " << begin_string_quote << needle << "\"\n" << "Expected: " << (expected_to_be_substring ? "" : "not ") << "a substring of " << haystack_expr << "\n" - << "Which is: " << begin_string_quote << haystack << "\""); + << "Which is: " << begin_string_quote << haystack << "\""; } } // namespace @@ -2700,10 +2641,13 @@ namespace { AssertionResult HRESULTFailureHelper(const char* expr, const char* expected, long hr) { // NOLINT -#if GTEST_OS_WINDOWS_MOBILE +# if GTEST_OS_WINDOWS_MOBILE + // Windows CE doesn't support FormatMessage. const char error_text[] = ""; -#else + +# else + // Looks up the human-readable system message for the HRESULT code // and since we're not passing any params to FormatMessage, we don't // want inserts expanded. @@ -2720,18 +2664,17 @@ AssertionResult HRESULTFailureHelper(const char* expr, kBufSize, // buf size NULL); // no arguments for inserts // Trims tailing white space (FormatMessage leaves a trailing cr-lf) - for (; message_length && isspace(error_text[message_length - 1]); + for (; message_length && IsSpace(error_text[message_length - 1]); --message_length) { error_text[message_length - 1] = '\0'; } -#endif // GTEST_OS_WINDOWS_MOBILE + +# endif // GTEST_OS_WINDOWS_MOBILE const String error_hex(String::Format("0x%08X ", hr)); - Message msg; - msg << "Expected: " << expr << " " << expected << ".\n" + return ::testing::AssertionFailure() + << "Expected: " << expr << " " << expected << ".\n" << " Actual: " << error_hex << error_text << "\n"; - - return ::testing::AssertionFailure(msg); } } // namespace @@ -2866,7 +2809,7 @@ String WideStringToUtf8(const wchar_t* str, int num_chars) { if (num_chars == -1) num_chars = static_cast(wcslen(str)); - StrStream stream; + ::std::stringstream stream; for (int i = 0; i < num_chars; ++i) { UInt32 unicode_code_point; @@ -2883,7 +2826,7 @@ String WideStringToUtf8(const wchar_t* str, int num_chars) { char buffer[32]; // CodePointToUtf8 requires a buffer this big. stream << CodePointToUtf8(unicode_code_point, buffer); } - return StrStreamToString(&stream); + return StringStreamToString(&stream); } // Converts a wide C string to a String using the UTF-8 encoding. @@ -2942,12 +2885,10 @@ AssertionResult CmpHelperSTRNE(const char* s1_expression, return AssertionSuccess(); } - Message msg; - msg << "Expected: (" << s1_expression << ") != (" - << s2_expression << "), actual: " - << String::ShowWideCStringQuoted(s1) - << " vs " << String::ShowWideCStringQuoted(s2); - return AssertionFailure(msg); + return AssertionFailure() << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: " + << String::ShowWideCStringQuoted(s1) + << " vs " << String::ShowWideCStringQuoted(s2); } // Compares two C strings, ignoring case. Returns true iff they have @@ -2978,17 +2919,17 @@ bool String::CaseInsensitiveCStringEquals(const char * lhs, const char * rhs) { // current locale. bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs, const wchar_t* rhs) { - if ( lhs == NULL ) return rhs == NULL; + if (lhs == NULL) return rhs == NULL; - if ( rhs == NULL ) return false; + if (rhs == NULL) return false; #if GTEST_OS_WINDOWS return _wcsicmp(lhs, rhs) == 0; -#elif GTEST_OS_LINUX +#elif GTEST_OS_LINUX && !GTEST_OS_LINUX_ANDROID return wcscasecmp(lhs, rhs) == 0; #else - // Mac OS X and Cygwin don't define wcscasecmp. Other unknown OSes - // may not define it either. + // Android, Mac OS X and Cygwin don't define wcscasecmp. + // Other unknown OSes may not define it either. wint_t left, right; do { left = towlower(*lhs++); @@ -3070,10 +3011,12 @@ String String::Format(const char * format, ...) { // MSVC 8 deprecates vsnprintf(), so we want to suppress warning // 4996 (deprecated function) there. #ifdef _MSC_VER // We are using MSVC. -#pragma warning(push) // Saves the current warning state. -#pragma warning(disable:4996) // Temporarily disables warning 4996. +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4996) // Temporarily disables warning 4996. + const int size = vsnprintf(buffer, kBufferSize, format, args); -#pragma warning(pop) // Restores the warning state. + +# pragma warning(pop) // Restores the warning state. #else // We are not using MSVC. const int size = vsnprintf(buffer, kBufferSize, format, args); #endif // _MSC_VER @@ -3091,16 +3034,16 @@ String String::Format(const char * format, ...) { } } -// Converts the buffer in a StrStream to a String, converting NUL +// Converts the buffer in a stringstream to a String, converting NUL // bytes to "\\0" along the way. -String StrStreamToString(StrStream* ss) { +String StringStreamToString(::std::stringstream* ss) { const ::std::string& str = ss->str(); const char* const start = str.c_str(); const char* const end = start + str.length(); - // We need to use a helper StrStream to do this transformation + // We need to use a helper stringstream to do this transformation // because String doesn't support push_back(). - StrStream helper; + ::std::stringstream helper; for (const char* ch = start; ch != end; ++ch) { if (*ch == '\0') { helper << "\\0"; // Replaces NUL with "\\0"; @@ -3304,22 +3247,6 @@ void ReportFailureInUnknownLocation(TestPartResult::Type result_type, } // namespace internal -#if GTEST_OS_WINDOWS -// We are on Windows. - -// Adds an "exception thrown" fatal failure to the current test. -static void AddExceptionThrownFailure(DWORD exception_code, - const char* location) { - Message message; - message << "Exception thrown with code 0x" << std::setbase(16) << - exception_code << std::setbase(10) << " in " << location << "."; - - internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure, - message.GetString()); -} - -#endif // GTEST_OS_WINDOWS - // Google Test requires all tests in the same test case to use the same test // fixture class. This function checks if the current test has the // same fixture class as the first test in the current test case. If @@ -3330,15 +3257,13 @@ bool Test::HasSameFixtureClass() { const TestCase* const test_case = impl->current_test_case(); // Info about the first test in the current test case. - const internal::TestInfoImpl* const first_test_info = - test_case->test_info_list()[0]->impl(); - const internal::TypeId first_fixture_id = first_test_info->fixture_class_id(); + const TestInfo* const first_test_info = test_case->test_info_list()[0]; + const internal::TypeId first_fixture_id = first_test_info->fixture_class_id_; const char* const first_test_name = first_test_info->name(); // Info about the current test. - const internal::TestInfoImpl* const this_test_info = - impl->current_test_info()->impl(); - const internal::TypeId this_fixture_id = this_test_info->fixture_class_id(); + const TestInfo* const this_test_info = impl->current_test_info(); + const internal::TypeId this_fixture_id = this_test_info->fixture_class_id_; const char* const this_test_name = this_test_info->name(); if (this_fixture_id != first_fixture_id) { @@ -3388,62 +3313,167 @@ bool Test::HasSameFixtureClass() { return true; } +#if GTEST_HAS_SEH + +// Adds an "exception thrown" fatal failure to the current test. This +// function returns its result via an output parameter pointer because VC++ +// prohibits creation of objects with destructors on stack in functions +// using __try (see error C2712). +static internal::String* FormatSehExceptionMessage(DWORD exception_code, + const char* location) { + Message message; + message << "SEH exception with code 0x" << std::setbase(16) << + exception_code << std::setbase(10) << " thrown in " << location << "."; + + return new internal::String(message.GetString()); +} + +#endif // GTEST_HAS_SEH + +#if GTEST_HAS_EXCEPTIONS + +// Adds an "exception thrown" fatal failure to the current test. +static internal::String FormatCxxExceptionMessage(const char* description, + const char* location) { + Message message; + if (description != NULL) { + message << "C++ exception with description \"" << description << "\""; + } else { + message << "Unknown C++ exception"; + } + message << " thrown in " << location << "."; + + return message.GetString(); +} + +static internal::String PrintTestPartResultToString( + const TestPartResult& test_part_result); + +// A failed Google Test assertion will throw an exception of this type when +// GTEST_FLAG(throw_on_failure) is true (if exceptions are enabled). We +// derive it from std::runtime_error, which is for errors presumably +// detectable only at run time. Since std::runtime_error inherits from +// std::exception, many testing frameworks know how to extract and print the +// message inside it. +class GoogleTestFailureException : public ::std::runtime_error { + public: + explicit GoogleTestFailureException(const TestPartResult& failure) + : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {} +}; +#endif // GTEST_HAS_EXCEPTIONS + +namespace internal { +// We put these helper functions in the internal namespace as IBM's xlC +// compiler rejects the code if they were declared static. + +// Runs the given method and handles SEH exceptions it throws, when +// SEH is supported; returns the 0-value for type Result in case of an +// SEH exception. (Microsoft compilers cannot handle SEH and C++ +// exceptions in the same function. Therefore, we provide a separate +// wrapper function for handling SEH exceptions.) +template +Result HandleSehExceptionsInMethodIfSupported( + T* object, Result (T::*method)(), const char* location) { +#if GTEST_HAS_SEH + __try { + return (object->*method)(); + } __except (internal::UnitTestOptions::GTestShouldProcessSEH( // NOLINT + GetExceptionCode())) { + // We create the exception message on the heap because VC++ prohibits + // creation of objects with destructors on stack in functions using __try + // (see error C2712). + internal::String* exception_message = FormatSehExceptionMessage( + GetExceptionCode(), location); + internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure, + *exception_message); + delete exception_message; + return static_cast(0); + } +#else + (void)location; + return (object->*method)(); +#endif // GTEST_HAS_SEH +} + +// Runs the given method and catches and reports C++ and/or SEH-style +// exceptions, if they are supported; returns the 0-value for type +// Result in case of an SEH exception. +template +Result HandleExceptionsInMethodIfSupported( + T* object, Result (T::*method)(), const char* location) { + // NOTE: The user code can affect the way in which Google Test handles + // exceptions by setting GTEST_FLAG(catch_exceptions), but only before + // RUN_ALL_TESTS() starts. It is technically possible to check the flag + // after the exception is caught and either report or re-throw the + // exception based on the flag's value: + // + // try { + // // Perform the test method. + // } catch (...) { + // if (GTEST_FLAG(catch_exceptions)) + // // Report the exception as failure. + // else + // throw; // Re-throws the original exception. + // } + // + // However, the purpose of this flag is to allow the program to drop into + // the debugger when the exception is thrown. On most platforms, once the + // control enters the catch block, the exception origin information is + // lost and the debugger will stop the program at the point of the + // re-throw in this function -- instead of at the point of the original + // throw statement in the code under test. For this reason, we perform + // the check early, sacrificing the ability to affect Google Test's + // exception handling in the method where the exception is thrown. + if (internal::GetUnitTestImpl()->catch_exceptions()) { +#if GTEST_HAS_EXCEPTIONS + try { + return HandleSehExceptionsInMethodIfSupported(object, method, location); + } catch (const GoogleTestFailureException&) { // NOLINT + // This exception doesn't originate in code under test. It makes no + // sense to report it as a test failure. + throw; + } catch (const std::exception& e) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(e.what(), location)); + } catch (...) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(NULL, location)); + } + return static_cast(0); +#else + return HandleSehExceptionsInMethodIfSupported(object, method, location); +#endif // GTEST_HAS_EXCEPTIONS + } else { + return (object->*method)(); + } +} + +} // namespace internal + // Runs the test and updates the test result. void Test::Run() { if (!HasSameFixtureClass()) return; internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); -#if GTEST_HAS_SEH - // Catch SEH-style exceptions. impl->os_stack_trace_getter()->UponLeavingGTest(); - __try { - SetUp(); - } __except(internal::UnitTestOptions::GTestShouldProcessSEH( - GetExceptionCode())) { - AddExceptionThrownFailure(GetExceptionCode(), "SetUp()"); - } - - // We will run the test only if SetUp() had no fatal failure. - if (!HasFatalFailure()) { - impl->os_stack_trace_getter()->UponLeavingGTest(); - __try { - TestBody(); - } __except(internal::UnitTestOptions::GTestShouldProcessSEH( - GetExceptionCode())) { - AddExceptionThrownFailure(GetExceptionCode(), "the test body"); - } - } - - // However, we want to clean up as much as possible. Hence we will - // always call TearDown(), even if SetUp() or the test body has - // failed. - impl->os_stack_trace_getter()->UponLeavingGTest(); - __try { - TearDown(); - } __except(internal::UnitTestOptions::GTestShouldProcessSEH( - GetExceptionCode())) { - AddExceptionThrownFailure(GetExceptionCode(), "TearDown()"); - } - -#else // We are on a compiler or platform that doesn't support SEH. - impl->os_stack_trace_getter()->UponLeavingGTest(); - SetUp(); - + internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()"); // We will run the test only if SetUp() was successful. if (!HasFatalFailure()) { impl->os_stack_trace_getter()->UponLeavingGTest(); - TestBody(); + internal::HandleExceptionsInMethodIfSupported( + this, &Test::TestBody, "the test body"); } // However, we want to clean up as much as possible. Hence we will // always call TearDown(), even if SetUp() or the test body has // failed. impl->os_stack_trace_getter()->UponLeavingGTest(); - TearDown(); -#endif // GTEST_HAS_SEH + internal::HandleExceptionsInMethodIfSupported( + this, &Test::TearDown, "TearDown()"); } - // Returns true iff the current test has a fatal failure. bool Test::HasFatalFailure() { return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure(); @@ -3458,22 +3488,28 @@ bool Test::HasNonfatalFailure() { // class TestInfo // Constructs a TestInfo object. It assumes ownership of the test factory -// object via impl_. +// object. +// TODO(vladl@google.com): Make a_test_case_name and a_name const string&'s +// to signify they cannot be NULLs. TestInfo::TestInfo(const char* a_test_case_name, const char* a_name, - const char* a_test_case_comment, - const char* a_comment, + const char* a_type_param, + const char* a_value_param, internal::TypeId fixture_class_id, - internal::TestFactoryBase* factory) { - impl_ = new internal::TestInfoImpl(this, a_test_case_name, a_name, - a_test_case_comment, a_comment, - fixture_class_id, factory); -} + internal::TestFactoryBase* factory) + : test_case_name_(a_test_case_name), + name_(a_name), + type_param_(a_type_param ? new std::string(a_type_param) : NULL), + value_param_(a_value_param ? new std::string(a_value_param) : NULL), + fixture_class_id_(fixture_class_id), + should_run_(false), + is_disabled_(false), + matches_filter_(false), + factory_(factory), + result_() {} // Destructs a TestInfo object. -TestInfo::~TestInfo() { - delete impl_; -} +TestInfo::~TestInfo() { delete factory_; } namespace internal { @@ -3484,10 +3520,10 @@ namespace internal { // // test_case_name: name of the test case // name: name of the test -// test_case_comment: a comment on the test case that will be included in -// the test output -// comment: a comment on the test that will be included in the -// test output +// type_param: the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param: text representation of the test's value parameter, +// or NULL if this is not a value-parameterized test. // fixture_class_id: ID of the test fixture class // set_up_tc: pointer to the function that sets up the test case // tear_down_tc: pointer to the function that tears down the test case @@ -3496,13 +3532,14 @@ namespace internal { // ownership of the factory object. TestInfo* MakeAndRegisterTestInfo( const char* test_case_name, const char* name, - const char* test_case_comment, const char* comment, + const char* type_param, + const char* value_param, TypeId fixture_class_id, SetUpTestCaseFunc set_up_tc, TearDownTestCaseFunc tear_down_tc, TestFactoryBase* factory) { TestInfo* const test_info = - new TestInfo(test_case_name, name, test_case_comment, comment, + new TestInfo(test_case_name, name, type_param, value_param, fixture_class_id, factory); GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); return test_info; @@ -3529,41 +3566,6 @@ void ReportInvalidTestCaseType(const char* test_case_name, } // namespace internal -// Returns the test case name. -const char* TestInfo::test_case_name() const { - return impl_->test_case_name(); -} - -// Returns the test name. -const char* TestInfo::name() const { - return impl_->name(); -} - -// Returns the test case comment. -const char* TestInfo::test_case_comment() const { - return impl_->test_case_comment(); -} - -// Returns the test comment. -const char* TestInfo::comment() const { - return impl_->comment(); -} - -// Returns true if this test should run. -bool TestInfo::should_run() const { return impl_->should_run(); } - -// Returns true if this test matches the user-specified filter. -bool TestInfo::matches_filter() const { return impl_->matches_filter(); } - -// Returns the result of the test. -const TestResult* TestInfo::result() const { return impl_->result(); } - -// Increments the number of death tests encountered in this test so -// far. -int TestInfo::increment_death_test_count() { - return impl_->result()->increment_death_test_count(); -} - namespace { // A predicate that checks the test name of a TestInfo against a known @@ -3607,70 +3609,54 @@ void UnitTestImpl::RegisterParameterizedTests() { #endif } +} // namespace internal + // Creates the test object, runs it, records its result, and then // deletes it. -void TestInfoImpl::Run() { +void TestInfo::Run() { if (!should_run_) return; // Tells UnitTest where to store test result. - UnitTestImpl* const impl = internal::GetUnitTestImpl(); - impl->set_current_test_info(parent_); + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_info(this); TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); // Notifies the unit test event listeners that a test is about to start. - repeater->OnTestStart(*parent_); + repeater->OnTestStart(*this); - const TimeInMillis start = GetTimeInMillis(); + const TimeInMillis start = internal::GetTimeInMillis(); impl->os_stack_trace_getter()->UponLeavingGTest(); -#if GTEST_HAS_SEH - // Catch SEH-style exceptions. - Test* test = NULL; - - __try { - // Creates the test object. - test = factory_->CreateTest(); - } __except(internal::UnitTestOptions::GTestShouldProcessSEH( - GetExceptionCode())) { - AddExceptionThrownFailure(GetExceptionCode(), - "the test fixture's constructor"); - return; - } -#else // We are on a compiler or platform that doesn't support SEH. - - // TODO(wan): If test->Run() throws, test won't be deleted. This is - // not a problem now as we don't use exceptions. If we were to - // enable exceptions, we should revise the following to be - // exception-safe. // Creates the test object. - Test* test = factory_->CreateTest(); -#endif // GTEST_HAS_SEH + Test* const test = internal::HandleExceptionsInMethodIfSupported( + factory_, &internal::TestFactoryBase::CreateTest, + "the test fixture's constructor"); - // Runs the test only if the constructor of the test fixture didn't - // generate a fatal failure. - if (!Test::HasFatalFailure()) { + // Runs the test only if the test object was created and its + // constructor didn't generate a fatal failure. + if ((test != NULL) && !Test::HasFatalFailure()) { + // This doesn't throw as all user code that can throw are wrapped into + // exception handling code. test->Run(); } // Deletes the test object. impl->os_stack_trace_getter()->UponLeavingGTest(); - delete test; - test = NULL; + internal::HandleExceptionsInMethodIfSupported( + test, &Test::DeleteSelf_, "the test fixture's destructor"); - result_.set_elapsed_time(GetTimeInMillis() - start); + result_.set_elapsed_time(internal::GetTimeInMillis() - start); // Notifies the unit test event listener that a test has just finished. - repeater->OnTestEnd(*parent_); + repeater->OnTestEnd(*this); // Tells UnitTest to stop associating assertion results to this // test. impl->set_current_test_info(NULL); } -} // namespace internal - // class TestCase // Gets the number of successful tests in this test case. @@ -3702,13 +3688,15 @@ int TestCase::total_test_count() const { // Arguments: // // name: name of the test case +// a_type_param: the name of the test case's type parameter, or NULL if +// this is not a typed or a type-parameterized test case. // set_up_tc: pointer to the function that sets up the test case // tear_down_tc: pointer to the function that tears down the test case -TestCase::TestCase(const char* a_name, const char* a_comment, +TestCase::TestCase(const char* a_name, const char* a_type_param, Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc) : name_(a_name), - comment_(a_comment), + type_param_(a_type_param ? new std::string(a_type_param) : NULL), set_up_tc_(set_up_tc), tear_down_tc_(tear_down_tc), should_run_(false), @@ -3753,45 +3741,26 @@ void TestCase::Run() { repeater->OnTestCaseStart(*this); impl->os_stack_trace_getter()->UponLeavingGTest(); - set_up_tc_(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestCase::RunSetUpTestCase, "SetUpTestCase()"); const internal::TimeInMillis start = internal::GetTimeInMillis(); for (int i = 0; i < total_test_count(); i++) { - GetMutableTestInfo(i)->impl()->Run(); + GetMutableTestInfo(i)->Run(); } elapsed_time_ = internal::GetTimeInMillis() - start; impl->os_stack_trace_getter()->UponLeavingGTest(); - tear_down_tc_(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestCase::RunTearDownTestCase, "TearDownTestCase()"); + repeater->OnTestCaseEnd(*this); impl->set_current_test_case(NULL); } // Clears the results of all tests in this test case. void TestCase::ClearResult() { - ForEach(test_info_list_, internal::TestInfoImpl::ClearTestResult); -} - -// Returns true iff test passed. -bool TestCase::TestPassed(const TestInfo * test_info) { - const internal::TestInfoImpl* const impl = test_info->impl(); - return impl->should_run() && impl->result()->Passed(); -} - -// Returns true iff test failed. -bool TestCase::TestFailed(const TestInfo * test_info) { - const internal::TestInfoImpl* const impl = test_info->impl(); - return impl->should_run() && impl->result()->Failed(); -} - -// Returns true iff test is disabled. -bool TestCase::TestDisabled(const TestInfo * test_info) { - return test_info->impl()->is_disabled(); -} - -// Returns true if the given test should run. -bool TestCase::ShouldRunTest(const TestInfo *test_info) { - return test_info->impl()->should_run(); + ForEach(test_info_list_, TestInfo::ClearTestResult); } // Shuffles the tests in this test case. @@ -3844,9 +3813,9 @@ static const char * TestPartResultTypeToString(TestPartResult::Type type) { #else return "Failure\n"; #endif + default: + return "Unknown result type"; } - - return "Unknown result type"; } // Prints a TestPartResult to a String. @@ -3932,6 +3901,7 @@ bool ShouldUseColor(bool stdout_is_tty) { String::CStringEquals(term, "xterm") || String::CStringEquals(term, "xterm-color") || String::CStringEquals(term, "xterm-256color") || + String::CStringEquals(term, "screen") || String::CStringEquals(term, "linux") || String::CStringEquals(term, "cygwin"); return stdout_is_tty && term_supports_color; @@ -3997,6 +3967,23 @@ void ColoredPrintf(GTestColor color, const char* fmt, ...) { va_end(args); } +void PrintFullTestCommentIfPresent(const TestInfo& test_info) { + const char* const type_param = test_info.type_param(); + const char* const value_param = test_info.value_param(); + + if (type_param != NULL || value_param != NULL) { + printf(", where "); + if (type_param != NULL) { + printf("TypeParam = %s", type_param); + if (value_param != NULL) + printf(" and "); + } + if (value_param != NULL) { + printf("GetParam() = %s", value_param); + } + } +} + // This class implements the TestEventListener interface. // // Class PrettyUnitTestResultPrinter is copyable. @@ -4044,9 +4031,10 @@ void PrettyUnitTestResultPrinter::OnTestIterationStart( } if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) { + const Int32 shard_index = Int32FromEnvOrDie(kTestShardIndex, -1); ColoredPrintf(COLOR_YELLOW, - "Note: This is test shard %s of %s.\n", - internal::posix::GetEnv(kTestShardIndex), + "Note: This is test shard %d of %s.\n", + static_cast(shard_index) + 1, internal::posix::GetEnv(kTestTotalShards)); } @@ -4076,10 +4064,10 @@ void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) { FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); ColoredPrintf(COLOR_GREEN, "[----------] "); printf("%s from %s", counts.c_str(), test_case_name_.c_str()); - if (test_case.comment()[0] == '\0') { + if (test_case.type_param() == NULL) { printf("\n"); } else { - printf(", where %s\n", test_case.comment()); + printf(", where TypeParam = %s\n", test_case.type_param()); } fflush(stdout); } @@ -4087,11 +4075,7 @@ void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) { void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) { ColoredPrintf(COLOR_GREEN, "[ RUN ] "); PrintTestName(test_case_name_.c_str(), test_info.name()); - if (test_info.comment()[0] == '\0') { - printf("\n"); - } else { - printf(", where %s\n", test_info.comment()); - } + printf("\n"); fflush(stdout); } @@ -4114,6 +4098,9 @@ void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { ColoredPrintf(COLOR_RED, "[ FAILED ] "); } PrintTestName(test_case_name_.c_str(), test_info.name()); + if (test_info.result()->Failed()) + PrintFullTestCommentIfPresent(test_info); + if (GTEST_FLAG(print_time)) { printf(" (%s ms)\n", internal::StreamableToString( test_info.result()->elapsed_time()).c_str()); @@ -4162,21 +4149,14 @@ void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { } ColoredPrintf(COLOR_RED, "[ FAILED ] "); printf("%s.%s", test_case.name(), test_info.name()); - if (test_case.comment()[0] != '\0' || - test_info.comment()[0] != '\0') { - printf(", where %s", test_case.comment()); - if (test_case.comment()[0] != '\0' && - test_info.comment()[0] != '\0') { - printf(" and "); - } - } - printf("%s\n", test_info.comment()); + PrintFullTestCommentIfPresent(test_info); + printf("\n"); } } } - void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, - int /*iteration*/) { +void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { ColoredPrintf(COLOR_GREEN, "[==========] "); printf("%s from %s ran.", FormatTestCount(unit_test.test_to_run_count()).c_str(), @@ -4356,7 +4336,7 @@ class XmlUnitTestResultPrinter : public EmptyTestEventListener { static String EscapeXml(const char* str, bool is_attribute); // Returns the given string with all characters invalid in XML removed. - static String RemoveInvalidXmlCharacters(const char* str); + static string RemoveInvalidXmlCharacters(const string& str); // Convenience wrapper around EscapeXml when str is an attribute value. static String EscapeXmlAttribute(const char* str) { @@ -4490,17 +4470,14 @@ String XmlUnitTestResultPrinter::EscapeXml(const char* str, bool is_attribute) { // Returns the given string with all characters invalid in XML removed. // Currently invalid characters are dropped from the string. An // alternative is to replace them with certain characters such as . or ?. -String XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters(const char* str) { - char* const output = new char[strlen(str) + 1]; - char* appender = output; - for (char ch = *str; ch != '\0'; ch = *++str) - if (IsValidXmlCharacter(ch)) - *appender++ = ch; - *appender = '\0'; +string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters(const string& str) { + string output; + output.reserve(str.size()); + for (string::const_iterator it = str.begin(); it != str.end(); ++it) + if (IsValidXmlCharacter(*it)) + output.push_back(*it); - String ret_value(output); - delete[] output; - return ret_value; + return output; } // The following routines generate an XML representation of a UnitTest @@ -4553,8 +4530,18 @@ void XmlUnitTestResultPrinter::OutputXmlTestInfo(::std::ostream* stream, const TestInfo& test_info) { const TestResult& result = *test_info.result(); *stream << " "; - const String message = RemoveInvalidXmlCharacters(String::Format( - "%s:%d\n%s", - part.file_name(), part.line_number(), - part.message()).c_str()); - OutputXmlCDataSection(stream, message.c_str()); + const string location = internal::FormatCompilerIndependentFileLocation( + part.file_name(), part.line_number()); + const string message = location + "\n" + part.message(); + OutputXmlCDataSection(stream, + RemoveInvalidXmlCharacters(message).c_str()); *stream << "\n"; } } @@ -4599,9 +4586,9 @@ void XmlUnitTestResultPrinter::PrintXmlTestCase(FILE* out, "errors=\"0\" time=\"%s\">\n", FormatTimeInMillisAsSeconds(test_case.elapsed_time()).c_str()); for (int i = 0; i < test_case.total_test_count(); ++i) { - StrStream stream; + ::std::stringstream stream; OutputXmlTestInfo(&stream, test_case.name(), *test_case.GetTestInfo(i)); - fprintf(out, "%s", StrStreamToString(&stream).c_str()); + fprintf(out, "%s", StringStreamToString(&stream).c_str()); } fprintf(out, " \n"); } @@ -4641,6 +4628,182 @@ String XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes( // End XmlUnitTestResultPrinter +#if GTEST_CAN_STREAM_RESULTS_ + +// Streams test results to the given port on the given host machine. +class StreamingListener : public EmptyTestEventListener { + public: + // Escapes '=', '&', '%', and '\n' characters in str as "%xx". + static string UrlEncode(const char* str); + + StreamingListener(const string& host, const string& port) + : sockfd_(-1), host_name_(host), port_num_(port) { + MakeConnection(); + Send("gtest_streaming_protocol_version=1.0\n"); + } + + virtual ~StreamingListener() { + if (sockfd_ != -1) + CloseConnection(); + } + + void OnTestProgramStart(const UnitTest& /* unit_test */) { + Send("event=TestProgramStart\n"); + } + + void OnTestProgramEnd(const UnitTest& unit_test) { + // Note that Google Test current only report elapsed time for each + // test iteration, not for the entire test program. + Send(String::Format("event=TestProgramEnd&passed=%d\n", + unit_test.Passed())); + + // Notify the streaming server to stop. + CloseConnection(); + } + + void OnTestIterationStart(const UnitTest& /* unit_test */, int iteration) { + Send(String::Format("event=TestIterationStart&iteration=%d\n", + iteration)); + } + + void OnTestIterationEnd(const UnitTest& unit_test, int /* iteration */) { + Send(String::Format("event=TestIterationEnd&passed=%d&elapsed_time=%sms\n", + unit_test.Passed(), + StreamableToString(unit_test.elapsed_time()).c_str())); + } + + void OnTestCaseStart(const TestCase& test_case) { + Send(String::Format("event=TestCaseStart&name=%s\n", test_case.name())); + } + + void OnTestCaseEnd(const TestCase& test_case) { + Send(String::Format("event=TestCaseEnd&passed=%d&elapsed_time=%sms\n", + test_case.Passed(), + StreamableToString(test_case.elapsed_time()).c_str())); + } + + void OnTestStart(const TestInfo& test_info) { + Send(String::Format("event=TestStart&name=%s\n", test_info.name())); + } + + void OnTestEnd(const TestInfo& test_info) { + Send(String::Format( + "event=TestEnd&passed=%d&elapsed_time=%sms\n", + (test_info.result())->Passed(), + StreamableToString((test_info.result())->elapsed_time()).c_str())); + } + + void OnTestPartResult(const TestPartResult& test_part_result) { + const char* file_name = test_part_result.file_name(); + if (file_name == NULL) + file_name = ""; + Send(String::Format("event=TestPartResult&file=%s&line=%d&message=", + UrlEncode(file_name).c_str(), + test_part_result.line_number())); + Send(UrlEncode(test_part_result.message()) + "\n"); + } + + private: + // Creates a client socket and connects to the server. + void MakeConnection(); + + // Closes the socket. + void CloseConnection() { + GTEST_CHECK_(sockfd_ != -1) + << "CloseConnection() can be called only when there is a connection."; + + close(sockfd_); + sockfd_ = -1; + } + + // Sends a string to the socket. + void Send(const string& message) { + GTEST_CHECK_(sockfd_ != -1) + << "Send() can be called only when there is a connection."; + + const int len = static_cast(message.length()); + if (write(sockfd_, message.c_str(), len) != len) { + GTEST_LOG_(WARNING) + << "stream_result_to: failed to stream to " + << host_name_ << ":" << port_num_; + } + } + + int sockfd_; // socket file descriptor + const string host_name_; + const string port_num_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamingListener); +}; // class StreamingListener + +// Checks if str contains '=', '&', '%' or '\n' characters. If yes, +// replaces them by "%xx" where xx is their hexadecimal value. For +// example, replaces "=" with "%3D". This algorithm is O(strlen(str)) +// in both time and space -- important as the input str may contain an +// arbitrarily long test failure message and stack trace. +string StreamingListener::UrlEncode(const char* str) { + string result; + result.reserve(strlen(str) + 1); + for (char ch = *str; ch != '\0'; ch = *++str) { + switch (ch) { + case '%': + case '=': + case '&': + case '\n': + result.append(String::Format("%%%02x", static_cast(ch))); + break; + default: + result.push_back(ch); + break; + } + } + return result; +} + +void StreamingListener::MakeConnection() { + GTEST_CHECK_(sockfd_ == -1) + << "MakeConnection() can't be called when there is already a connection."; + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; // To allow both IPv4 and IPv6 addresses. + hints.ai_socktype = SOCK_STREAM; + addrinfo* servinfo = NULL; + + // Use the getaddrinfo() to get a linked list of IP addresses for + // the given host name. + const int error_num = getaddrinfo( + host_name_.c_str(), port_num_.c_str(), &hints, &servinfo); + if (error_num != 0) { + GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: " + << gai_strerror(error_num); + } + + // Loop through all the results and connect to the first we can. + for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != NULL; + cur_addr = cur_addr->ai_next) { + sockfd_ = socket( + cur_addr->ai_family, cur_addr->ai_socktype, cur_addr->ai_protocol); + if (sockfd_ != -1) { + // Connect the client socket to the server socket. + if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) { + close(sockfd_); + sockfd_ = -1; + } + } + } + + freeaddrinfo(servinfo); // all done with this structure + + if (sockfd_ == -1) { + GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to " + << host_name_ << ":" << port_num_; + } +} + +// End of class Streaming Listener +#endif // GTEST_CAN_STREAM_RESULTS__ + // Class ScopedTrace // Pushes the given source file location and message onto a per-thread @@ -4881,19 +5044,6 @@ Environment* UnitTest::AddEnvironment(Environment* env) { return env; } -#if GTEST_HAS_EXCEPTIONS -// A failed Google Test assertion will throw an exception of this type -// when exceptions are enabled. We derive it from std::runtime_error, -// which is for errors presumably detectable only at run time. Since -// std::runtime_error inherits from std::exception, many testing -// frameworks know how to extract and print the message inside it. -class GoogleTestFailureException : public ::std::runtime_error { - public: - explicit GoogleTestFailureException(const TestPartResult& failure) - : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {} -}; -#endif - // Adds a TestPartResult to the current TestResult object. All Google Test // assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call // this to report their results. The user code should use the @@ -4942,7 +5092,11 @@ void UnitTest::AddTestPartResult(TestPartResult::Type result_type, // the --gtest_catch_exceptions flags are specified. DebugBreak(); #else - *static_cast(NULL) = 1; + // Dereference NULL through a volatile pointer to prevent the compiler + // from removing. We use this rather than abort() or __builtin_trap() for + // portability: Symbian doesn't implement abort() well, and some debuggers + // don't correctly trap abort(). + *static_cast(NULL) = 1; #endif // GTEST_OS_WINDOWS } else if (GTEST_FLAG(throw_on_failure)) { #if GTEST_HAS_EXCEPTIONS @@ -4970,31 +5124,34 @@ void UnitTest::RecordPropertyForCurrentTest(const char* key, // We don't protect this under mutex_, as we only support calling it // from the main thread. int UnitTest::Run() { -#if GTEST_HAS_SEH - // Catch SEH-style exceptions. + // Captures the value of GTEST_FLAG(catch_exceptions). This value will be + // used for the duration of the program. + impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions)); +#if GTEST_HAS_SEH const bool in_death_test_child_process = internal::GTEST_FLAG(internal_run_death_test).length() > 0; // Either the user wants Google Test to catch exceptions thrown by the // tests or this is executing in the context of death test child // process. In either case the user does not want to see pop-up dialogs - // about crashes - they are expected.. - if (GTEST_FLAG(catch_exceptions) || in_death_test_child_process) { -#if !GTEST_OS_WINDOWS_MOBILE + // about crashes - they are expected. + if (impl()->catch_exceptions() || in_death_test_child_process) { + +# if !GTEST_OS_WINDOWS_MOBILE // SetErrorMode doesn't exist on CE. SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); -#endif // !GTEST_OS_WINDOWS_MOBILE +# endif // !GTEST_OS_WINDOWS_MOBILE -#if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE +# if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE // Death test children can be terminated with _abort(). On Windows, // _abort() can show a dialog with a warning message. This forces the // abort message to go to stderr instead. _set_error_mode(_OUT_TO_STDERR); -#endif +# endif -#if _MSC_VER >= 1400 && !GTEST_OS_WINDOWS_MOBILE +# if _MSC_VER >= 1400 && !GTEST_OS_WINDOWS_MOBILE // In the debug version, Visual Studio pops up a separate dialog // offering a choice to debug the aborted program. We need to suppress // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement @@ -5010,22 +5167,15 @@ int UnitTest::Run() { _set_abort_behavior( 0x0, // Clear the following flags: _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // pop-up window, core dump. -#endif +# endif + } - - __try { - return impl_->RunAllTests(); - } __except(internal::UnitTestOptions::GTestShouldProcessSEH( - GetExceptionCode())) { - printf("Exception thrown with code 0x%x.\nFAIL\n", GetExceptionCode()); - fflush(stdout); - return 1; - } - -#else // We are on a compiler or platform that doesn't support SEH. - - return impl_->RunAllTests(); #endif // GTEST_HAS_SEH + + return internal::HandleExceptionsInMethodIfSupported( + impl(), + &internal::UnitTestImpl::RunAllTests, + "auxiliary test code (environments or event listeners)") ? 0 : 1; } // Returns the working directory when the first TEST() or TEST_F() was @@ -5093,12 +5243,12 @@ namespace internal { UnitTestImpl::UnitTestImpl(UnitTest* parent) : parent_(parent), #ifdef _MSC_VER -#pragma warning(push) // Saves the current warning state. -#pragma warning(disable:4355) // Temporarily disables warning 4355 +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4355) // Temporarily disables warning 4355 // (using this in initializer). default_global_test_part_result_reporter_(this), default_per_thread_test_part_result_reporter_(this), -#pragma warning(pop) // Restores the warning state again. +# pragma warning(pop) // Restores the warning state again. #else default_global_test_part_result_reporter_(this), default_per_thread_test_part_result_reporter_(this), @@ -5119,13 +5269,13 @@ UnitTestImpl::UnitTestImpl(UnitTest* parent) post_flag_parse_init_performed_(false), random_seed_(0), // Will be overridden by the flag before first use. random_(0), // Will be reseeded before first use. -#if GTEST_HAS_DEATH_TEST elapsed_time_(0), +#if GTEST_HAS_DEATH_TEST internal_run_death_test_flag_(NULL), - death_test_factory_(new DefaultDeathTestFactory) { -#else - elapsed_time_(0) { -#endif // GTEST_HAS_DEATH_TEST + death_test_factory_(new DefaultDeathTestFactory), +#endif + // Will be overridden by the flag before first use. + catch_exceptions_(false) { listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter); } @@ -5162,6 +5312,25 @@ void UnitTestImpl::ConfigureXmlOutput() { } } +#if GTEST_CAN_STREAM_RESULTS_ +// Initializes event listeners for streaming test results in String form. +// Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureStreamingOutput() { + const string& target = GTEST_FLAG(stream_result_to); + if (!target.empty()) { + const size_t pos = target.find(':'); + if (pos != string::npos) { + listeners()->Append(new StreamingListener(target.substr(0, pos), + target.substr(pos+1))); + } else { + printf("WARNING: unrecognized streaming target \"%s\" ignored.\n", + target.c_str()); + fflush(stdout); + } + } +} +#endif // GTEST_CAN_STREAM_RESULTS_ + // Performs initialization dependent upon flag values obtained in // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest @@ -5185,6 +5354,11 @@ void UnitTestImpl::PostFlagParsingInit() { // Configures listeners for XML output. This makes it possible for users // to shut down the default XML output before invoking RUN_ALL_TESTS. ConfigureXmlOutput(); + +#if GTEST_CAN_STREAM_RESULTS_ + // Configures listeners for streaming test results to the specified server. + ConfigureStreamingOutput(); +#endif // GTEST_CAN_STREAM_RESULTS_ } } @@ -5219,10 +5393,12 @@ class TestCaseNameIs { // Arguments: // // test_case_name: name of the test case +// type_param: the name of the test case's type parameter, or NULL if +// this is not a typed or a type-parameterized test case. // set_up_tc: pointer to the function that sets up the test case // tear_down_tc: pointer to the function that tears down the test case TestCase* UnitTestImpl::GetTestCase(const char* test_case_name, - const char* comment, + const char* type_param, Test::SetUpTestCaseFunc set_up_tc, Test::TearDownTestCaseFunc tear_down_tc) { // Can we find a TestCase with the given name? @@ -5235,7 +5411,7 @@ TestCase* UnitTestImpl::GetTestCase(const char* test_case_name, // No. Let's create one. TestCase* const new_test_case = - new TestCase(test_case_name, comment, set_up_tc, tear_down_tc); + new TestCase(test_case_name, type_param, set_up_tc, tear_down_tc); // Is this a death test case? if (internal::UnitTestOptions::MatchesFilter(String(test_case_name), @@ -5262,27 +5438,26 @@ static void SetUpEnvironment(Environment* env) { env->SetUp(); } static void TearDownEnvironment(Environment* env) { env->TearDown(); } // Runs all tests in this UnitTest object, prints the result, and -// returns 0 if all tests are successful, or 1 otherwise. If any -// exception is thrown during a test on Windows, this test is -// considered to be failed, but the rest of the tests will still be -// run. (We disable exceptions on Linux and Mac OS X, so the issue -// doesn't apply there.) +// returns true if all tests are successful. If any exception is +// thrown during a test, the test is considered to be failed, but the +// rest of the tests will still be run. +// // When parameterized tests are enabled, it expands and registers // parameterized tests first in RegisterParameterizedTests(). // All other functions called from RunAllTests() may safely assume that // parameterized tests are ready to be counted and run. -int UnitTestImpl::RunAllTests() { +bool UnitTestImpl::RunAllTests() { // Makes sure InitGoogleTest() was called. if (!GTestIsInitialized()) { printf("%s", "\nThis test program did NOT call ::testing::InitGoogleTest " "before calling RUN_ALL_TESTS(). Please fix it.\n"); - return 1; + return false; } // Do not run any test if the --help flag was specified. if (g_help_flag) - return 0; + return true; // Repeats the call to the post-flag parsing initialization in case the // user didn't call InitGoogleTest. @@ -5314,7 +5489,7 @@ int UnitTestImpl::RunAllTests() { if (GTEST_FLAG(list_tests)) { // This must be called *after* FilterTests() has been called. ListTestsMatchingFilter(); - return 0; + return true; } random_seed_ = GTEST_FLAG(shuffle) ? @@ -5333,7 +5508,9 @@ int UnitTestImpl::RunAllTests() { // Repeats forever if the repeat count is negative. const bool forever = repeat < 0; for (int i = 0; forever || i != repeat; i++) { - ClearResult(); + // We want to preserve failures generated by ad-hoc test + // assertions executed before RUN_ALL_TESTS(). + ClearNonAdHocTestResult(); const TimeInMillis start = GetTimeInMillis(); @@ -5398,8 +5575,7 @@ int UnitTestImpl::RunAllTests() { repeater->OnTestProgramEnd(*parent_); - // Returns 0 if all tests passed, or 1 other wise. - return failed ? 1 : 0; + return !failed; } // Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file @@ -5473,7 +5649,7 @@ bool ShouldShard(const char* total_shards_env, // Parses the environment variable var as an Int32. If it is unset, // returns default_val. If it is not an Int32, prints an error // and aborts. -Int32 Int32FromEnvOrDie(const char* const var, Int32 default_val) { +Int32 Int32FromEnvOrDie(const char* var, Int32 default_val) { const char* str_val = posix::GetEnv(var); if (str_val == NULL) { return default_val; @@ -5529,12 +5705,12 @@ int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { kDisableTestFilter) || internal::UnitTestOptions::MatchesFilter(test_name, kDisableTestFilter); - test_info->impl()->set_is_disabled(is_disabled); + test_info->is_disabled_ = is_disabled; const bool matches_filter = internal::UnitTestOptions::FilterMatchesTest(test_case_name, test_name); - test_info->impl()->set_matches_filter(matches_filter); + test_info->matches_filter_ = matches_filter; const bool is_runnable = (GTEST_FLAG(also_run_disabled_tests) || !is_disabled) && @@ -5548,7 +5724,7 @@ int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { num_runnable_tests += is_runnable; num_selected_tests += is_selected; - test_info->impl()->set_should_run(is_selected); + test_info->should_run_ = is_selected; test_case->set_should_run(test_case->should_run() || is_selected); } } @@ -5564,7 +5740,7 @@ void UnitTestImpl::ListTestsMatchingFilter() { for (size_t j = 0; j < test_case->test_info_list().size(); j++) { const TestInfo* const test_info = test_case->test_info_list()[j]; - if (test_info->matches_filter()) { + if (test_info->matches_filter_) { if (!printed_test_case_name) { printed_test_case_name = true; printf("%s.\n", test_case->name()); @@ -5604,7 +5780,7 @@ OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() { // the TestResult for the ad hoc test if no test is running. TestResult* UnitTestImpl::current_test_result() { return current_test_info_ ? - current_test_info_->impl()->result() : &ad_hoc_test_result_; + &(current_test_info_->result_) : &ad_hoc_test_result_; } // Shuffles all test cases, and the tests within each test case, @@ -5633,32 +5809,6 @@ void UnitTestImpl::UnshuffleTests() { } } -// TestInfoImpl constructor. The new instance assumes ownership of the test -// factory object. -TestInfoImpl::TestInfoImpl(TestInfo* parent, - const char* a_test_case_name, - const char* a_name, - const char* a_test_case_comment, - const char* a_comment, - TypeId a_fixture_class_id, - internal::TestFactoryBase* factory) : - parent_(parent), - test_case_name_(String(a_test_case_name)), - name_(String(a_name)), - test_case_comment_(String(a_test_case_comment)), - comment_(String(a_comment)), - fixture_class_id_(a_fixture_class_id), - should_run_(false), - is_disabled_(false), - matches_filter_(false), - factory_(factory) { -} - -// TestInfoImpl destructor. -TestInfoImpl::~TestInfoImpl() { - delete factory_; -} - // Returns the current OS stack trace as a String. // // The maximum number of stack frames to be included is specified by @@ -5676,8 +5826,8 @@ String GetCurrentOsStackTraceExceptTop(UnitTest* /*unit_test*/, return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1); } -// Used by the GTEST_HIDE_UNREACHABLE_CODE_ macro to suppress unreachable -// code warnings. +// Used by the GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_ macro to +// suppress unreachable code warnings. namespace { class ClassUniqueToAlwaysTrue {}; } @@ -5889,6 +6039,10 @@ static const char kColorEncodedHelpMessage[] = GTEST_PATH_SEP_ "@Y|@G:@YFILE_PATH]@D\n" " Generate an XML report in the given directory or with the given file\n" " name. @YFILE_PATH@D defaults to @Gtest_details.xml@D.\n" +#if GTEST_CAN_STREAM_RESULTS_ +" @G--" GTEST_FLAG_PREFIX_ "stream_result_to=@YHOST@G:@YPORT@D\n" +" Stream test results to the given server.\n" +#endif // GTEST_CAN_STREAM_RESULTS_ "\n" "Assertion Behavior:\n" #if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS @@ -5899,10 +6053,9 @@ static const char kColorEncodedHelpMessage[] = " Turn assertion failures into debugger break-points.\n" " @G--" GTEST_FLAG_PREFIX_ "throw_on_failure@D\n" " Turn assertion failures into C++ exceptions.\n" -#if GTEST_OS_WINDOWS -" @G--" GTEST_FLAG_PREFIX_ "catch_exceptions@D\n" -" Suppress pop-ups caused by exceptions.\n" -#endif // GTEST_OS_WINDOWS +" @G--" GTEST_FLAG_PREFIX_ "catch_exceptions=0@D\n" +" Do not report exceptions as test failures. Instead, allow them\n" +" to crash the program or throw a pop-up (on Windows).\n" "\n" "Except for @G--" GTEST_FLAG_PREFIX_ "list_tests@D, you can alternatively set " "the corresponding\n" @@ -5952,7 +6105,10 @@ void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || ParseInt32Flag(arg, kStackTraceDepthFlag, >EST_FLAG(stack_trace_depth)) || - ParseBoolFlag(arg, kThrowOnFailureFlag, >EST_FLAG(throw_on_failure)) + ParseStringFlag(arg, kStreamResultToFlag, + >EST_FLAG(stream_result_to)) || + ParseBoolFlag(arg, kThrowOnFailureFlag, + >EST_FLAG(throw_on_failure)) ) { // Yes. Shift the remainder of the argv list left by one. Note // that argv has (*argc + 1) elements, the last one always being @@ -6010,10 +6166,12 @@ void InitGoogleTestImpl(int* argc, CharType** argv) { internal::g_executable_path = internal::StreamableToString(argv[0]); #if GTEST_HAS_DEATH_TEST + g_argvs.clear(); for (int i = 0; i != *argc; i++) { g_argvs.push_back(StreamableToString(argv[i])); } + #endif // GTEST_HAS_DEATH_TEST ParseGoogleTestFlagsOnly(argc, argv); @@ -6078,21 +6236,21 @@ void InitGoogleTest(int* argc, wchar_t** argv) { #if GTEST_HAS_DEATH_TEST -#if GTEST_OS_MAC && !GTEST_OS_MAC_IOS -#include -#endif // GTEST_OS_MAC +# if GTEST_OS_MAC && !GTEST_OS_MAC_IOS +# include +# endif // GTEST_OS_MAC -#include -#include -#include -#include +# include +# include +# include +# include -#if GTEST_OS_WINDOWS -#include -#else -#include -#include -#endif // GTEST_OS_WINDOWS +# if GTEST_OS_WINDOWS +# include +# else +# include +# include +# endif // GTEST_OS_WINDOWS #endif // GTEST_HAS_DEATH_TEST @@ -6152,14 +6310,18 @@ ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) { // ExitedWithCode function-call operator. bool ExitedWithCode::operator()(int exit_status) const { -#if GTEST_OS_WINDOWS +# if GTEST_OS_WINDOWS + return exit_status == exit_code_; -#else + +# else + return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_; -#endif // GTEST_OS_WINDOWS + +# endif // GTEST_OS_WINDOWS } -#if !GTEST_OS_WINDOWS +# if !GTEST_OS_WINDOWS // KilledBySignal constructor. KilledBySignal::KilledBySignal(int signum) : signum_(signum) { } @@ -6168,7 +6330,7 @@ KilledBySignal::KilledBySignal(int signum) : signum_(signum) { bool KilledBySignal::operator()(int exit_status) const { return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_; } -#endif // !GTEST_OS_WINDOWS +# endif // !GTEST_OS_WINDOWS namespace internal { @@ -6178,20 +6340,25 @@ namespace internal { // specified by wait(2). static String ExitSummary(int exit_code) { Message m; -#if GTEST_OS_WINDOWS + +# if GTEST_OS_WINDOWS + m << "Exited with exit status " << exit_code; -#else + +# else + if (WIFEXITED(exit_code)) { m << "Exited with exit status " << WEXITSTATUS(exit_code); } else if (WIFSIGNALED(exit_code)) { m << "Terminated by signal " << WTERMSIG(exit_code); } -#ifdef WCOREDUMP +# ifdef WCOREDUMP if (WCOREDUMP(exit_code)) { m << " (core dumped)"; } -#endif -#endif // GTEST_OS_WINDOWS +# endif +# endif // GTEST_OS_WINDOWS + return m.GetString(); } @@ -6201,7 +6368,7 @@ bool ExitedUnsuccessfully(int exit_status) { return !ExitedWithCode(0)(exit_status); } -#if !GTEST_OS_WINDOWS +# if !GTEST_OS_WINDOWS // Generates a textual failure message when a death test finds more than // one thread running, or cannot determine the number of threads, prior // to executing the given statement. It is the responsibility of the @@ -6216,20 +6383,24 @@ static String DeathTestThreadWarning(size_t thread_count) { msg << "detected " << thread_count << " threads."; return msg.GetString(); } -#endif // !GTEST_OS_WINDOWS +# endif // !GTEST_OS_WINDOWS // Flag characters for reporting a death test that did not die. static const char kDeathTestLived = 'L'; static const char kDeathTestReturned = 'R'; +static const char kDeathTestThrew = 'T'; static const char kDeathTestInternalError = 'I'; -// An enumeration describing all of the possible ways that a death test -// can conclude. DIED means that the process died while executing the -// test code; LIVED means that process lived beyond the end of the test -// code; and RETURNED means that the test statement attempted a "return," -// which is not allowed. IN_PROGRESS means the test has not yet -// concluded. -enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED }; +// An enumeration describing all of the possible ways that a death test can +// conclude. DIED means that the process died while executing the test +// code; LIVED means that process lived beyond the end of the test code; +// RETURNED means that the test statement attempted to execute a return +// statement, which is not allowed; THREW means that the test statement +// returned control by throwing an exception. IN_PROGRESS means the test +// has not yet concluded. +// TODO(vladl@google.com): Unify names and possibly values for +// AbortReason, DeathTestOutcome, and flag characters above. +enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW }; // Routine for aborting the program which is safe to call from an // exec-style death test child process, in which case the error @@ -6251,13 +6422,13 @@ void DeathTestAbort(const String& message) { } else { fprintf(stderr, "%s", message.c_str()); fflush(stderr); - abort(); + posix::Abort(); } } // A replacement for CHECK that calls DeathTestAbort if the assertion // fails. -#define GTEST_DEATH_TEST_CHECK_(expression) \ +# define GTEST_DEATH_TEST_CHECK_(expression) \ do { \ if (!::testing::internal::IsTrue(expression)) { \ DeathTestAbort(::testing::internal::String::Format( \ @@ -6273,7 +6444,7 @@ void DeathTestAbort(const String& message) { // evaluates the expression as long as it evaluates to -1 and sets // errno to EINTR. If the expression evaluates to -1 but errno is // something other than EINTR, DeathTestAbort is called. -#define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \ +# define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \ do { \ int gtest_retval; \ do { \ @@ -6427,6 +6598,9 @@ void DeathTestImpl::ReadAndInterpretStatusByte() { case kDeathTestReturned: set_outcome(RETURNED); break; + case kDeathTestThrew: + set_outcome(THREW); + break; case kDeathTestLived: set_outcome(LIVED); break; @@ -6455,19 +6629,46 @@ void DeathTestImpl::Abort(AbortReason reason) { // it finds any data in our pipe. So, here we write a single flag byte // to the pipe, then exit. const char status_ch = - reason == TEST_DID_NOT_DIE ? kDeathTestLived : kDeathTestReturned; + reason == TEST_DID_NOT_DIE ? kDeathTestLived : + reason == TEST_THREW_EXCEPTION ? kDeathTestThrew : kDeathTestReturned; + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1)); - GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(write_fd())); + // We are leaking the descriptor here because on some platforms (i.e., + // when built as Windows DLL), destructors of global objects will still + // run after calling _exit(). On such systems, write_fd_ will be + // indirectly closed from the destructor of UnitTestImpl, causing double + // close if it is also closed here. On debug configurations, double close + // may assert. As there are no in-process buffers to flush here, we are + // relying on the OS to close the descriptor after the process terminates + // when the destructors are not run. _exit(1); // Exits w/o any normal exit hooks (we were supposed to crash) } +// Returns an indented copy of stderr output for a death test. +// This makes distinguishing death test output lines from regular log lines +// much easier. +static ::std::string FormatDeathTestOutput(const ::std::string& output) { + ::std::string ret; + for (size_t at = 0; ; ) { + const size_t line_end = output.find('\n', at); + ret += "[ DEATH ] "; + if (line_end == ::std::string::npos) { + ret += output.substr(at); + break; + } + ret += output.substr(at, line_end + 1 - at); + at = line_end + 1; + } + return ret; +} + // Assesses the success or failure of a death test, using both private // members which have previously been set, and one argument: // // Private data members: // outcome: An enumeration describing how the death test -// concluded: DIED, LIVED, or RETURNED. The death test fails -// in the latter two cases. +// concluded: DIED, LIVED, THREW, or RETURNED. The death test +// fails in the latter three cases. // status: The exit status of the child process. On *nix, it is in the // in the format specified by wait(2). On Windows, this is the // value supplied to the ExitProcess() API or a numeric code @@ -6496,11 +6697,15 @@ bool DeathTestImpl::Passed(bool status_ok) { switch (outcome()) { case LIVED: buffer << " Result: failed to die.\n" - << " Error msg: " << error_message; + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case THREW: + buffer << " Result: threw an exception.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); break; case RETURNED: buffer << " Result: illegal return in test statement.\n" - << " Error msg: " << error_message; + << " Error msg:\n" << FormatDeathTestOutput(error_message); break; case DIED: if (status_ok) { @@ -6510,11 +6715,12 @@ bool DeathTestImpl::Passed(bool status_ok) { } else { buffer << " Result: died but not with expected error.\n" << " Expected: " << regex()->pattern() << "\n" - << "Actual msg: " << error_message; + << "Actual msg:\n" << FormatDeathTestOutput(error_message); } } else { buffer << " Result: died but not with expected exit code:\n" - << " " << ExitSummary(status()) << "\n"; + << " " << ExitSummary(status()) << "\n" + << "Actual msg:\n" << FormatDeathTestOutput(error_message); } break; case IN_PROGRESS: @@ -6527,7 +6733,7 @@ bool DeathTestImpl::Passed(bool status_ok) { return success; } -#if GTEST_OS_WINDOWS +# if GTEST_OS_WINDOWS // WindowsDeathTest implements death tests on Windows. Due to the // specifics of starting new processes on Windows, death tests there are // always threadsafe, and Google Test considers the @@ -6558,11 +6764,11 @@ bool DeathTestImpl::Passed(bool status_ok) { // class WindowsDeathTest : public DeathTestImpl { public: - WindowsDeathTest(const char* statement, - const RE* regex, + WindowsDeathTest(const char* a_statement, + const RE* a_regex, const char* file, int line) - : DeathTestImpl(statement, regex), file_(file), line_(line) {} + : DeathTestImpl(a_statement, a_regex), file_(file), line_(line) {} // All of these virtual functions are inherited from DeathTest. virtual int Wait(); @@ -6619,12 +6825,12 @@ int WindowsDeathTest::Wait() { GTEST_DEATH_TEST_CHECK_( WAIT_OBJECT_0 == ::WaitForSingleObject(child_handle_.Get(), INFINITE)); - DWORD status; - GTEST_DEATH_TEST_CHECK_(::GetExitCodeProcess(child_handle_.Get(), &status) - != FALSE); + DWORD status_code; + GTEST_DEATH_TEST_CHECK_( + ::GetExitCodeProcess(child_handle_.Get(), &status_code) != FALSE); child_handle_.Reset(); - set_status(static_cast(status)); - return this->status(); + set_status(static_cast(status_code)); + return status(); } // The AssumeRole process for a Windows death test. It creates a child @@ -6723,7 +6929,7 @@ DeathTest::TestRole WindowsDeathTest::AssumeRole() { set_spawned(true); return OVERSEE_TEST; } -#else // We are not on Windows. +# else // We are not on Windows. // ForkingDeathTest provides implementations for most of the abstract // methods of the DeathTest interface. Only the AssumeRole method is @@ -6871,19 +7077,19 @@ struct ExecDeathTestArgs { int close_fd; // File descriptor to close; the read end of a pipe }; -#if GTEST_OS_MAC && !GTEST_OS_MAC_IOS +# if GTEST_OS_MAC && !GTEST_OS_MAC_IOS inline char** GetEnviron() { // When Google Test is built as a framework on MacOS X, the environ variable // is unavailable. Apple's documentation (man environ) recommends using // _NSGetEnviron() instead. return *_NSGetEnviron(); } -#else +# else // Some POSIX platforms expect you to declare environ. extern "C" makes // it reside in the global namespace. extern "C" char** environ; inline char** GetEnviron() { return environ; } -#endif // GTEST_OS_MAC +# endif // GTEST_OS_MAC // The main function for a threadsafe-style death test child process. // This function is called in a clone()-ed process and thus must avoid @@ -6923,6 +7129,11 @@ static int ExecDeathTestChildMain(void* child_arg) { // This could be accomplished more elegantly by a single recursive // function, but we want to guard against the unlikely possibility of // a smart compiler optimizing the recursion away. +// +// GTEST_NO_INLINE_ is required to prevent GCC 4.6 from inlining +// StackLowerThanAddress into StackGrowsDown, which then doesn't give +// correct answer. +bool StackLowerThanAddress(const void* ptr) GTEST_NO_INLINE_; bool StackLowerThanAddress(const void* ptr) { int dummy; return &dummy < ptr; @@ -6940,7 +7151,7 @@ static pid_t ExecDeathTestFork(char* const* argv, int close_fd) { ExecDeathTestArgs args = { argv, close_fd }; pid_t child_pid = -1; -#if GTEST_HAS_CLONE +# if GTEST_HAS_CLONE const bool use_fork = GTEST_FLAG(death_test_use_fork); if (!use_fork) { @@ -6957,9 +7168,9 @@ static pid_t ExecDeathTestFork(char* const* argv, int close_fd) { GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1); } -#else +# else const bool use_fork = true; -#endif // GTEST_HAS_CLONE +# endif // GTEST_HAS_CLONE if (use_fork && (child_pid = fork()) == 0) { ExecDeathTestChildMain(&args); @@ -7020,7 +7231,7 @@ DeathTest::TestRole ExecDeathTest::AssumeRole() { return OVERSEE_TEST; } -#endif // !GTEST_OS_WINDOWS +# endif // !GTEST_OS_WINDOWS // Creates a concrete DeathTest-derived class that depends on the // --gtest_death_test_style flag, and sets the pointer pointed to @@ -7051,18 +7262,23 @@ bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex, } } -#if GTEST_OS_WINDOWS +# if GTEST_OS_WINDOWS + if (GTEST_FLAG(death_test_style) == "threadsafe" || GTEST_FLAG(death_test_style) == "fast") { *test = new WindowsDeathTest(statement, regex, file, line); } -#else + +# else + if (GTEST_FLAG(death_test_style) == "threadsafe") { *test = new ExecDeathTest(statement, regex, file, line); } else if (GTEST_FLAG(death_test_style) == "fast") { *test = new NoExecDeathTest(statement, regex); } -#endif // GTEST_OS_WINDOWS + +# endif // GTEST_OS_WINDOWS + else { // NOLINT - this is more readable than unbalanced brackets inside #if. DeathTest::set_last_death_test_message(String::Format( "Unknown death test style \"%s\" encountered", @@ -7093,7 +7309,7 @@ static void SplitString(const ::std::string& str, char delimiter, dest->swap(parsed); } -#if GTEST_OS_WINDOWS +# if GTEST_OS_WINDOWS // Recreates the pipe and event handles from the provided parameters, // signals the event, and returns a file descriptor wrapped around the pipe // handle. This function is called in the child process only. @@ -7157,7 +7373,7 @@ int GetStatusFileDescriptor(unsigned int parent_process_id, return write_fd; } -#endif // GTEST_OS_WINDOWS +# endif // GTEST_OS_WINDOWS // Returns a newly created InternalRunDeathTestFlag object with fields // initialized from the GTEST_FLAG(internal_run_death_test) flag if @@ -7173,7 +7389,8 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { SplitString(GTEST_FLAG(internal_run_death_test).c_str(), '|', &fields); int write_fd = -1; -#if GTEST_OS_WINDOWS +# if GTEST_OS_WINDOWS + unsigned int parent_process_id = 0; size_t write_handle_as_size_t = 0; size_t event_handle_as_size_t = 0; @@ -7191,7 +7408,8 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { write_fd = GetStatusFileDescriptor(parent_process_id, write_handle_as_size_t, event_handle_as_size_t); -#else +# else + if (fields.size() != 4 || !ParseNaturalNumber(fields[1], &line) || !ParseNaturalNumber(fields[2], &index) @@ -7200,7 +7418,9 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { "Bad --gtest_internal_run_death_test flag: %s", GTEST_FLAG(internal_run_death_test).c_str())); } -#endif // GTEST_OS_WINDOWS + +# endif // GTEST_OS_WINDOWS + return new InternalRunDeathTestFlag(fields[0], line, index, write_fd); } @@ -7244,26 +7464,26 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { #include #if GTEST_OS_WINDOWS_MOBILE -#include +# include #elif GTEST_OS_WINDOWS -#include -#include -#elif GTEST_OS_SYMBIAN -// Symbian OpenC has PATH_MAX in sys/syslimits.h -#include +# include +# include +#elif GTEST_OS_SYMBIAN || GTEST_OS_NACL +// Symbian OpenC and NaCl have PATH_MAX in sys/syslimits.h +# include #else -#include -#include // Some Linux distributions define PATH_MAX here. +# include +# include // Some Linux distributions define PATH_MAX here. #endif // GTEST_OS_WINDOWS_MOBILE #if GTEST_OS_WINDOWS -#define GTEST_PATH_MAX_ _MAX_PATH +# define GTEST_PATH_MAX_ _MAX_PATH #elif defined(PATH_MAX) -#define GTEST_PATH_MAX_ PATH_MAX +# define GTEST_PATH_MAX_ PATH_MAX #elif defined(_XOPEN_PATH_MAX) -#define GTEST_PATH_MAX_ _XOPEN_PATH_MAX +# define GTEST_PATH_MAX_ _XOPEN_PATH_MAX #else -#define GTEST_PATH_MAX_ _POSIX_PATH_MAX +# define GTEST_PATH_MAX_ _POSIX_PATH_MAX #endif // GTEST_OS_WINDOWS @@ -7279,16 +7499,16 @@ const char kPathSeparator = '\\'; const char kAlternatePathSeparator = '/'; const char kPathSeparatorString[] = "\\"; const char kAlternatePathSeparatorString[] = "/"; -#if GTEST_OS_WINDOWS_MOBILE +# if GTEST_OS_WINDOWS_MOBILE // Windows CE doesn't have a current directory. You should not use // the current directory in tests on Windows CE, but this at least // provides a reasonable fallback. const char kCurrentDirectoryString[] = "\\"; // Windows CE doesn't define INVALID_FILE_ATTRIBUTES const DWORD kInvalidFileAttributes = 0xffffffff; -#else +# else const char kCurrentDirectoryString[] = ".\\"; -#endif // GTEST_OS_WINDOWS_MOBILE +# endif // GTEST_OS_WINDOWS_MOBILE #else const char kPathSeparator = '/'; const char kPathSeparatorString[] = "/"; @@ -7621,20 +7841,21 @@ void FilePath::Normalize() { #include #include #include +#include #if GTEST_OS_WINDOWS_MOBILE -#include // For TerminateProcess() +# include // For TerminateProcess() #elif GTEST_OS_WINDOWS -#include -#include +# include +# include #else -#include +# include #endif // GTEST_OS_WINDOWS_MOBILE #if GTEST_OS_MAC -#include -#include -#include +# include +# include +# include #endif // GTEST_OS_MAC @@ -7763,20 +7984,20 @@ bool IsInSet(char ch, const char* str) { // Returns true iff ch belongs to the given classification. Unlike // similar functions in , these aren't affected by the // current locale. -bool IsDigit(char ch) { return '0' <= ch && ch <= '9'; } -bool IsPunct(char ch) { +bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; } +bool IsAsciiPunct(char ch) { return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); } bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } -bool IsWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } -bool IsWordChar(char ch) { +bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } +bool IsAsciiWordChar(char ch) { return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9') || ch == '_'; } // Returns true iff "\\c" is a supported escape sequence. bool IsValidEscape(char c) { - return (IsPunct(c) || IsInSet(c, "dDfnrsStvwW")); + return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW")); } // Returns true iff the given atom (specified by escaped and pattern) @@ -7784,19 +8005,19 @@ bool IsValidEscape(char c) { bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { if (escaped) { // "\\p" where p is pattern_char. switch (pattern_char) { - case 'd': return IsDigit(ch); - case 'D': return !IsDigit(ch); + case 'd': return IsAsciiDigit(ch); + case 'D': return !IsAsciiDigit(ch); case 'f': return ch == '\f'; case 'n': return ch == '\n'; case 'r': return ch == '\r'; - case 's': return IsWhiteSpace(ch); - case 'S': return !IsWhiteSpace(ch); + case 's': return IsAsciiWhiteSpace(ch); + case 'S': return !IsAsciiWhiteSpace(ch); case 't': return ch == '\t'; case 'v': return ch == '\v'; - case 'w': return IsWordChar(ch); - case 'W': return !IsWordChar(ch); + case 'w': return IsAsciiWordChar(ch); + case 'W': return !IsAsciiWordChar(ch); } - return IsPunct(pattern_char) && pattern_char == ch; + return IsAsciiPunct(pattern_char) && pattern_char == ch; } return (pattern_char == '.' && ch != '\n') || pattern_char == ch; @@ -8005,6 +8226,38 @@ void RE::Init(const char* regex) { #endif // GTEST_USES_POSIX_RE +const char kUnknownFile[] = "unknown file"; + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) { + const char* const file_name = file == NULL ? kUnknownFile : file; + + if (line < 0) { + return String::Format("%s:", file_name).c_str(); + } +#ifdef _MSC_VER + return String::Format("%s(%d):", file_name, line).c_str(); +#else + return String::Format("%s:%d:", file_name, line).c_str(); +#endif // _MSC_VER +} + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +// Note that FormatCompilerIndependentFileLocation() does NOT append colon +// to the file location it produces, unlike FormatFileLocation(). +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation( + const char* file, int line) { + const char* const file_name = file == NULL ? kUnknownFile : file; + + if (line < 0) + return file_name; + else + return String::Format("%s:%d", file_name, line).c_str(); +} + GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) : severity_(severity) { @@ -8027,18 +8280,19 @@ GTestLog::~GTestLog() { // Disable Microsoft deprecation warnings for POSIX functions called from // this class (creat, dup, dup2, and close) #ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4996) +# pragma warning(push) +# pragma warning(disable: 4996) #endif // _MSC_VER -#if GTEST_HAS_STREAM_REDIRECTION_ +#if GTEST_HAS_STREAM_REDIRECTION // Object that captures an output stream (stdout/stderr). class CapturedStream { public: // The ctor redirects the stream to a temporary file. CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { -#if GTEST_OS_WINDOWS + +# if GTEST_OS_WINDOWS char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT @@ -8053,14 +8307,14 @@ class CapturedStream { GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " << temp_file_path; filename_ = temp_file_path; -#else +# else // There's no guarantee that a test has write access to the // current directory, so we create the temporary file in the /tmp // directory instead. char name_template[] = "/tmp/captured_stream.XXXXXX"; const int captured_fd = mkstemp(name_template); filename_ = name_template; -#endif // GTEST_OS_WINDOWS +# endif // GTEST_OS_WINDOWS fflush(NULL); dup2(captured_fd, fd_); close(captured_fd); @@ -8129,9 +8383,9 @@ String CapturedStream::ReadEntireFile(FILE* file) { return content; } -#ifdef _MSC_VER -#pragma warning(pop) -#endif // _MSC_VER +# ifdef _MSC_VER +# pragma warning(pop) +# endif // _MSC_VER static CapturedStream* g_captured_stderr = NULL; static CapturedStream* g_captured_stdout = NULL; @@ -8171,7 +8425,7 @@ String GetCapturedStdout() { return GetCapturedStream(&g_captured_stdout); } // Stops capturing stderr and returns the captured string. String GetCapturedStderr() { return GetCapturedStream(&g_captured_stderr); } -#endif // GTEST_HAS_STREAM_REDIRECTION_ +#endif // GTEST_HAS_STREAM_REDIRECTION #if GTEST_HAS_DEATH_TEST @@ -8201,7 +8455,7 @@ static String FlagToEnvVar(const char* flag) { Message env_var; for (size_t i = 0; i != full_flag.length(); i++) { - env_var << static_cast(toupper(full_flag.c_str()[i])); + env_var << ToUpper(full_flag.c_str()[i]); } return env_var.GetString(); @@ -8291,6 +8545,360 @@ const char* StringFromGTestEnv(const char* flag, const char* default_value) { } } // namespace internal +} // namespace testing +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Test - The Google C++ Testing Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// It uses the << operator when possible, and prints the bytes in the +// object otherwise. A user can override its behavior for a class +// type Foo by defining either operator<<(::std::ostream&, const Foo&) +// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that +// defines Foo. + +#include +#include +#include // NOLINT +#include + +namespace testing { + +namespace { + +using ::std::ostream; + +#if GTEST_OS_WINDOWS_MOBILE // Windows CE does not define _snprintf_s. +# define snprintf _snprintf +#elif _MSC_VER >= 1400 // VC 8.0 and later deprecate snprintf and _snprintf. +# define snprintf _snprintf_s +#elif _MSC_VER +# define snprintf _snprintf +#endif // GTEST_OS_WINDOWS_MOBILE + +// Prints a segment of bytes in the given object. +void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start, + size_t count, ostream* os) { + char text[5] = ""; + for (size_t i = 0; i != count; i++) { + const size_t j = start + i; + if (i != 0) { + // Organizes the bytes into groups of 2 for easy parsing by + // human. + if ((j % 2) == 0) + *os << ' '; + else + *os << '-'; + } + snprintf(text, sizeof(text), "%02X", obj_bytes[j]); + *os << text; + } +} + +// Prints the bytes in the given value to the given ostream. +void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count, + ostream* os) { + // Tells the user how big the object is. + *os << count << "-byte object <"; + + const size_t kThreshold = 132; + const size_t kChunkSize = 64; + // If the object size is bigger than kThreshold, we'll have to omit + // some details by printing only the first and the last kChunkSize + // bytes. + // TODO(wan): let the user control the threshold using a flag. + if (count < kThreshold) { + PrintByteSegmentInObjectTo(obj_bytes, 0, count, os); + } else { + PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); + *os << " ... "; + // Rounds up to 2-byte boundary. + const size_t resume_pos = (count - kChunkSize + 1)/2*2; + PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); + } + *os << ">"; +} + +} // namespace + +namespace internal2 { + +// Delegates to PrintBytesInObjectToImpl() to print the bytes in the +// given object. The delegation simplifies the implementation, which +// uses the << operator and thus is easier done outside of the +// ::testing::internal namespace, which contains a << operator that +// sometimes conflicts with the one in STL. +void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, + ostream* os) { + PrintBytesInObjectToImpl(obj_bytes, count, os); +} + +} // namespace internal2 + +namespace internal { + +// Depending on the value of a char (or wchar_t), we print it in one +// of three formats: +// - as is if it's a printable ASCII (e.g. 'a', '2', ' '), +// - as a hexidecimal escape sequence (e.g. '\x7F'), or +// - as a special escape sequence (e.g. '\r', '\n'). +enum CharFormat { + kAsIs, + kHexEscape, + kSpecialEscape +}; + +// Returns true if c is a printable ASCII character. We test the +// value of c directly instead of calling isprint(), which is buggy on +// Windows Mobile. +inline bool IsPrintableAscii(wchar_t c) { + return 0x20 <= c && c <= 0x7E; +} + +// Prints a wide or narrow char c as a character literal without the +// quotes, escaping it when necessary; returns how c was formatted. +// The template argument UnsignedChar is the unsigned version of Char, +// which is the type of c. +template +static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) { + switch (static_cast(c)) { + case L'\0': + *os << "\\0"; + break; + case L'\'': + *os << "\\'"; + break; + case L'\\': + *os << "\\\\"; + break; + case L'\a': + *os << "\\a"; + break; + case L'\b': + *os << "\\b"; + break; + case L'\f': + *os << "\\f"; + break; + case L'\n': + *os << "\\n"; + break; + case L'\r': + *os << "\\r"; + break; + case L'\t': + *os << "\\t"; + break; + case L'\v': + *os << "\\v"; + break; + default: + if (IsPrintableAscii(c)) { + *os << static_cast(c); + return kAsIs; + } else { + *os << String::Format("\\x%X", static_cast(c)); + return kHexEscape; + } + } + return kSpecialEscape; +} + +// Prints a char c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsWideStringLiteralTo(wchar_t c, ostream* os) { + switch (c) { + case L'\'': + *os << "'"; + return kAsIs; + case L'"': + *os << "\\\""; + return kSpecialEscape; + default: + return PrintAsCharLiteralTo(c, os); + } +} + +// Prints a char c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsNarrowStringLiteralTo(char c, ostream* os) { + return PrintAsWideStringLiteralTo(static_cast(c), os); +} + +// Prints a wide or narrow character c and its code. '\0' is printed +// as "'\\0'", other unprintable characters are also properly escaped +// using the standard C++ escape sequence. The template argument +// UnsignedChar is the unsigned version of Char, which is the type of c. +template +void PrintCharAndCodeTo(Char c, ostream* os) { + // First, print c as a literal in the most readable form we can find. + *os << ((sizeof(c) > 1) ? "L'" : "'"); + const CharFormat format = PrintAsCharLiteralTo(c, os); + *os << "'"; + + // To aid user debugging, we also print c's code in decimal, unless + // it's 0 (in which case c was printed as '\\0', making the code + // obvious). + if (c == 0) + return; + *os << " (" << String::Format("%d", c).c_str(); + + // For more convenience, we print c's code again in hexidecimal, + // unless c was already printed in the form '\x##' or the code is in + // [1, 9]. + if (format == kHexEscape || (1 <= c && c <= 9)) { + // Do nothing. + } else { + *os << String::Format(", 0x%X", + static_cast(c)).c_str(); + } + *os << ")"; +} + +void PrintTo(unsigned char c, ::std::ostream* os) { + PrintCharAndCodeTo(c, os); +} +void PrintTo(signed char c, ::std::ostream* os) { + PrintCharAndCodeTo(c, os); +} + +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its code. L'\0' is printed as "L'\\0'". +void PrintTo(wchar_t wc, ostream* os) { + PrintCharAndCodeTo(wc, os); +} + +// Prints the given array of characters to the ostream. +// The array starts at *begin, the length is len, it may include '\0' characters +// and may not be null-terminated. +static void PrintCharsAsStringTo(const char* begin, size_t len, ostream* os) { + *os << "\""; + bool is_previous_hex = false; + for (size_t index = 0; index < len; ++index) { + const char cur = begin[index]; + if (is_previous_hex && IsXDigit(cur)) { + // Previous character is of '\x..' form and this character can be + // interpreted as another hexadecimal digit in its number. Break string to + // disambiguate. + *os << "\" \""; + } + is_previous_hex = PrintAsNarrowStringLiteralTo(cur, os) == kHexEscape; + } + *os << "\""; +} + +// Prints a (const) char array of 'len' elements, starting at address 'begin'. +void UniversalPrintArray(const char* begin, size_t len, ostream* os) { + PrintCharsAsStringTo(begin, len, os); +} + +// Prints the given array of wide characters to the ostream. +// The array starts at *begin, the length is len, it may include L'\0' +// characters and may not be null-terminated. +static void PrintWideCharsAsStringTo(const wchar_t* begin, size_t len, + ostream* os) { + *os << "L\""; + bool is_previous_hex = false; + for (size_t index = 0; index < len; ++index) { + const wchar_t cur = begin[index]; + if (is_previous_hex && isascii(cur) && IsXDigit(static_cast(cur))) { + // Previous character is of '\x..' form and this character can be + // interpreted as another hexadecimal digit in its number. Break string to + // disambiguate. + *os << "\" L\""; + } + is_previous_hex = PrintAsWideStringLiteralTo(cur, os) == kHexEscape; + } + *os << "\""; +} + +// Prints the given C string to the ostream. +void PrintTo(const char* s, ostream* os) { + if (s == NULL) { + *os << "NULL"; + } else { + *os << ImplicitCast_(s) << " pointing to "; + PrintCharsAsStringTo(s, strlen(s), os); + } +} + +// MSVC compiler can be configured to define whar_t as a typedef +// of unsigned short. Defining an overload for const wchar_t* in that case +// would cause pointers to unsigned shorts be printed as wide strings, +// possibly accessing more memory than intended and causing invalid +// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when +// wchar_t is implemented as a native type. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Prints the given wide C string to the ostream. +void PrintTo(const wchar_t* s, ostream* os) { + if (s == NULL) { + *os << "NULL"; + } else { + *os << ImplicitCast_(s) << " pointing to "; + PrintWideCharsAsStringTo(s, wcslen(s), os); + } +} +#endif // wchar_t is native + +// Prints a ::string object. +#if GTEST_HAS_GLOBAL_STRING +void PrintStringTo(const ::string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_GLOBAL_STRING + +void PrintStringTo(const ::std::string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} + +// Prints a ::wstring object. +#if GTEST_HAS_GLOBAL_WSTRING +void PrintWideStringTo(const ::wstring& s, ostream* os) { + PrintWideCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_WSTRING +void PrintWideStringTo(const ::std::wstring& s, ostream* os) { + PrintWideCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_STD_WSTRING + +} // namespace internal + } // namespace testing // Copyright 2008, Google Inc. // All rights reserved. @@ -8440,7 +9048,7 @@ namespace internal { // Skips to the first non-space char in str. Returns an empty string if str // contains only whitespace characters. static const char* SkipSpaces(const char* str) { - while (isspace(*str)) + while (IsSpace(*str)) str++; return str; } diff --git a/modules/ts/src/ts_perf.cpp b/modules/ts/src/ts_perf.cpp new file mode 100644 index 000000000..0f8e7adea --- /dev/null +++ b/modules/ts/src/ts_perf.cpp @@ -0,0 +1,930 @@ +#include "precomp.hpp" + +using namespace perf; + +void randu(cv::Mat& m) +{ + const int bigValue = 0x00000FFF; + if (m.depth() < CV_32F) + { + int minmax[] = {0, 256}; + cv::Mat mr = cv::Mat(m.rows, m.cols * m.elemSize(), CV_8U, m.ptr(), m.step[0]); + cv::randu(mr, cv::Mat(1, 1, CV_32S, minmax), cv::Mat(1, 1, CV_32S, minmax + 1)); + } + else if (m.depth() == CV_32F) + { + //float minmax[] = {-FLT_MAX, FLT_MAX}; + float minmax[] = {-bigValue, bigValue}; + cv::Mat mr = m.reshape(1); + cv::randu(mr, cv::Mat(1, 1, CV_32F, minmax), cv::Mat(1, 1, CV_32F, minmax + 1)); + } + else + { + //double minmax[] = {-DBL_MAX, DBL_MAX}; + double minmax[] = {-bigValue, bigValue}; + cv::Mat mr = m.reshape(1); + cv::randu(mr, cv::Mat(1, 1, CV_64F, minmax), cv::Mat(1, 1, CV_64F, minmax + 1)); + } +} + + +/*****************************************************************************************\ +* ::perf::Regression +\*****************************************************************************************/ + +Regression& Regression::instance() +{ + static Regression single; + return single; +} + +Regression& Regression::add(const std::string& name, cv::InputArray array, double eps) +{ + return instance()(name, array, eps); +} + +void Regression::Init(const std::string& testSuitName, const std::string& ext) +{ + instance().init(testSuitName, ext); +} + +void Regression::init(const std::string& testSuitName, const std::string& ext) +{ + if (!storageInPath.empty()) + { + LOGE("Subsequent initialisation of Regression utility is not allowed."); + return; + } + + const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH"); + const char *path_separator = "/"; + + if (data_path_dir) + { + int len = strlen(data_path_dir)-1; + if (len < 0) len = 0; + std::string path_base = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir)) + + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator) + + "perf" + + path_separator; + + storageInPath = path_base + testSuitName + ext; + storageOutPath = path_base + testSuitName; + } + else + { + storageInPath = testSuitName + ext; + storageOutPath = testSuitName; + } + + if (storageIn.open(storageInPath, cv::FileStorage::READ)) + { + rootIn = storageIn.root(); + if (storageInPath.length() > 3 && storageInPath.substr(storageInPath.length()-3) == ".gz") + storageOutPath += "_new"; + storageOutPath += ext; + } + else + storageOutPath = storageInPath; +} + +Regression::Regression() : regRNG(cv::getTickCount())//this rng should be really random +{ +} + +Regression::~Regression() +{ + if (storageIn.isOpened()) + storageIn.release(); + if (storageOut.isOpened()) + { + if (!currentTestNodeName.empty()) + storageOut << "}"; + storageOut.release(); + } +} + +cv::FileStorage& Regression::write() +{ + if (!storageOut.isOpened() && !storageOutPath.empty()) + { + int mode = (storageIn.isOpened() && storageInPath == storageOutPath) + ? cv::FileStorage::APPEND : cv::FileStorage::WRITE; + storageOut.open(storageOutPath, mode); + if (!storageOut.isOpened()) + { + LOGE("Could not open \"%s\" file for writing", storageOutPath.c_str()); + storageOutPath.clear(); + } + else if (mode == cv::FileStorage::WRITE && !rootIn.empty()) + { + //TODO: write content of rootIn node into the storageOut + } + } + return storageOut; +} + +std::string Regression::getCurrentTestNodeName() +{ + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + + if (test_info == 0) + return "undefined"; + + std::string nodename = std::string(test_info->test_case_name()) + "--" + test_info->name(); + size_t idx = nodename.find_first_of('/'); + if (idx != std::string::npos) + nodename.erase(idx); + + const char* type_param = test_info->type_param(); + if (type_param != 0) + (nodename += "--") += type_param; + + const char* value_param = test_info->value_param(); + if (value_param != 0) + (nodename += "--") += value_param; + + for(size_t i = 0; i < nodename.length(); ++i) + if (!isalnum(nodename[i]) && '_' != nodename[i]) + nodename[i] = '-'; + + return nodename; +} + +bool Regression::isVector(cv::InputArray a) +{ + return a.kind() == cv::_InputArray::STD_VECTOR_MAT || a.kind() == cv::_InputArray::STD_VECTOR_VECTOR; +} + +double Regression::getElem(cv::Mat& m, int y, int x, int cn) +{ + switch (m.depth()) + { + case CV_8U: return *(m.ptr(y, x) + cn); + case CV_8S: return *(m.ptr(y, x) + cn); + case CV_16U: return *(m.ptr(y, x) + cn); + case CV_16S: return *(m.ptr(y, x) + cn); + case CV_32S: return *(m.ptr(y, x) + cn); + case CV_32F: return *(m.ptr(y, x) + cn); + case CV_64F: return *(m.ptr(y, x) + cn); + default: return 0; + } +} + +void Regression::write(cv::Mat m) +{ + double min, max; + cv::minMaxLoc(m, &min, &max); + write() << "min" << min << "max" << max; + + write() << "last" << "{" << "x" << m.cols-1 << "y" << m.rows-1 + << "val" << getElem(m, m.rows-1, m.cols-1, m.channels()-1) << "}"; + + int x, y, cn; + x = regRNG.uniform(0, m.cols); + y = regRNG.uniform(0, m.rows); + cn = regRNG.uniform(0, m.channels()); + write() << "rng1" << "{" << "x" << x << "y" << y; + if(cn > 0) write() << "cn" << cn; + write() << "val" << getElem(m, y, x, cn) << "}"; + + x = regRNG.uniform(0, m.cols); + y = regRNG.uniform(0, m.rows); + cn = regRNG.uniform(0, m.channels()); + write() << "rng2" << "{" << "x" << x << "y" << y; + if (cn > 0) write() << "cn" << cn; + write() << "val" << getElem(m, y, x, cn) << "}"; +} + +void Regression::verify(cv::FileNode node, cv::Mat actual, double eps, std::string argname) +{ + double actualmin, actualmax; + cv::minMaxLoc(actual, &actualmin, &actualmax); + + ASSERT_NEAR((double)node["min"], actualmin, eps) + << " " << argname << " has unexpected minimal value"; + ASSERT_NEAR((double)node["max"], actualmax, eps) + << " " << argname << " has unexpected maximal value"; + + cv::FileNode last = node["last"]; + double actualLast = getElem(actual, actual.rows - 1, actual.cols - 1, actual.channels() - 1); + ASSERT_EQ((int)last["x"], actual.cols - 1) + << " " << argname << " has unexpected number of columns"; + ASSERT_EQ((int)last["y"], actual.rows - 1) + << " " << argname << " has unexpected number of rows"; + ASSERT_NEAR((double)last["val"], actualLast, eps) + << " " << argname << " has unexpected value of last element"; + + cv::FileNode rng1 = node["rng1"]; + int x1 = rng1["x"]; + int y1 = rng1["y"]; + int cn1 = rng1["cn"]; + ASSERT_NEAR((double)rng1["val"], getElem(actual, y1, x1, cn1), eps) + << " " << argname << " has unexpected value of ["<< x1 << ":" << y1 << ":" << cn1 <<"] element"; + + cv::FileNode rng2 = node["rng2"]; + int x2 = rng2["x"]; + int y2 = rng2["y"]; + int cn2 = rng2["cn"]; + ASSERT_NEAR((double)rng2["val"], getElem(actual, y2, x2, cn2), eps) + << " " << argname << " has unexpected value of ["<< x2 << ":" << y2 << ":" << cn2 <<"] element"; +} + +void Regression::write(cv::InputArray array) +{ + write() << "kind" << array.kind(); + write() << "type" << array.type(); + if (isVector(array)) + { + int total = array.total(); + int idx = regRNG.uniform(0, total); + write() << "len" << total; + write() << "idx" << idx; + + cv::Mat m = array.getMat(idx); + + if (m.total() * m.channels() < 26) //5x5 or smaller + write() << "val" << m; + else + write(m); + } + else + { + if (array.total() * array.channels() < 26) //5x5 or smaller + write() << "val" << array.getMat(); + else + write(array.getMat()); + } +} + +void Regression::verify(cv::FileNode node, cv::InputArray array, double eps) +{ + ASSERT_EQ((int)node["kind"], array.kind()) << " Argument " << node.name() << " has unexpected kind"; + ASSERT_EQ((int)node["type"], array.type()) << " Argument " << node.name() << " has unexpected type"; + + cv::FileNode valnode = node["val"]; + if (isVector(array)) + { + ASSERT_EQ((int)node["len"], (int)array.total()) << " Vector " << node.name() << " has unexpected length"; + int idx = node["idx"]; + + cv::Mat actual = array.getMat(idx); + + if (valnode.isNone()) + { + ASSERT_LE((size_t)26, actual.total() * (size_t)actual.channels()) + << " " << node.name() << "[" << idx << "] has unexpected number of elements"; + verify(node, actual, eps, cv::format("%s[%d]", node.name().c_str(), idx)); + } + else + { + cv::Mat expected; + valnode >> expected; + + ASSERT_EQ(expected.size(), actual.size()) + << " " << node.name() << "[" << idx<< "] has unexpected size"; + + cv::Mat diff; + cv::absdiff(expected, actual, diff); + if (!cv::checkRange(diff, true, 0, 0, eps)) + FAIL() << " Difference between argument " + << node.name() << "[" << idx << "] and expected value is bugger than " << eps; + } + } + else + { + if (valnode.isNone()) + { + ASSERT_LE((size_t)26, array.total() * (size_t)array.channels()) + << " Argument " << node.name() << " has unexpected number of elements"; + verify(node, array.getMat(), eps, "Argument " + node.name()); + } + else + { + cv::Mat expected; + valnode >> expected; + cv::Mat actual = array.getMat(); + + ASSERT_EQ(expected.size(), actual.size()) + << " Argument " << node.name() << " has unexpected size"; + + cv::Mat diff; + cv::absdiff(expected, actual, diff); + if (!cv::checkRange(diff, true, 0, 0, eps)) + FAIL() << " Difference between argument " << node.name() + << " and expected value is bugger than " << eps; + } + } +} + +Regression& Regression::operator() (const std::string& name, cv::InputArray array, double eps) +{ + std::string nodename = getCurrentTestNodeName(); + + cv::FileNode n = rootIn[nodename]; + if(n.isNone()) + { + if (nodename != currentTestNodeName) + { + if (!currentTestNodeName.empty()) + write() << "}"; + currentTestNodeName = nodename; + + write() << nodename << "{"; + } + write() << name << "{"; + write(array); + write() << "}"; + } + else + { + cv::FileNode this_arg = n[name]; + if (!this_arg.isMap()) + ADD_FAILURE() << " No regression data for " << name << " argument"; + else + verify(this_arg, array, eps); + } + return *this; +} + + +/*****************************************************************************************\ +* ::perf::performance_metrics +\*****************************************************************************************/ +performance_metrics::performance_metrics() +{ + bytesIn = 0; + bytesOut = 0; + samples = 0; + outliers = 0; + gmean = 0; + gstddev = 0; + mean = 0; + stddev = 0; + median = 0; + min = 0; + frequency = 0; + terminationReason = TERM_UNKNOWN; +} + + +/*****************************************************************************************\ +* ::perf::TestBase +\*****************************************************************************************/ +int64 TestBase::timeLimitDefault = 0; +int64 TestBase::_timeadjustment = 0; + +const char *command_line_keys = +{ + "{!!bugbugbugbug!! |perf_max_outliers |8 |percent of allowed outliers}" + "{!!bugbugbugbug!! |perf_min_samples |10 |minimal required numer of samples}" + "{!!bugbugbugbug!! |perf_seed |809564 |seed for random numbers generator}" + #if ANDROID + "{!!bugbugbugbug!! |perf_time_limit |2.0 |default time limit for a single test (in seconds)}" + #else + "{!!bugbugbugbug!! |perf_time_limit |1.0 |default time limit for a single test (in seconds)}" + #endif + "{!!bugbugbugbug!! |perf_max_deviation |1.0 |}" + "{h |help |false |}" +}; + +double param_max_outliers; +double param_max_deviation; +unsigned int param_min_samples; +uint64 param_seed; +double param_time_limit; + +void TestBase::Init(int argc, const char* const argv[]) +{ + cv::CommandLineParser args(argc, argv, command_line_keys); + param_max_outliers = std::min(100., std::max(0., args.get("perf_max_outliers"))); + param_min_samples = std::max(1u, args.get("perf_min_samples")); + param_max_deviation = std::max(0., args.get("perf_max_deviation")); + param_seed = args.get("perf_seed"); + param_time_limit = std::max(0., args.get("perf_time_limit")); + + if (args.get("help")) + { + args.printParams(); + printf("\n\n"); + return; + } + + //LOGD("!!!!!!!!!!!! %f !!!!!!", param_time_limit); + + timeLimitDefault = param_time_limit == 0.0 ? 1 : (int64)(param_time_limit * cv::getTickFrequency()); + _timeadjustment = _calibrate(); +} + +int64 TestBase::_calibrate() +{ + class _helper : public ::perf::TestBase + { + public: + performance_metrics& getMetrics() { return calcMetrics(); } + virtual void TestBody() {} + virtual void PerfTestBody() + { + //the whole system warmup + SetUp(); + cv::Mat a(2048, 2048, CV_32S, cv::Scalar(1)); + cv::Mat b(2048, 2048, CV_32S, cv::Scalar(2)); + declare.time(30); + double s = 0; + for(declare.iterations(20); startTimer(), next(); stopTimer()) + s+=a.dot(b); + declare.time(s); + + //self calibration + SetUp(); + for(declare.iterations(1000); startTimer(), next(); stopTimer()){} + } + }; + + _timeadjustment = 0; + _helper h; + h.PerfTestBody(); + double compensation = h.getMetrics().min; + LOGD("Time compensation is %.0f", compensation); + return (int64)compensation; +} + +TestBase::TestBase(): declare(this) +{ +} + +void TestBase::declareArray(SizeVector& sizes, cv::InputOutputArray a, int wtype) +{ + if (!a.empty()) + { + sizes.push_back(std::pair(getSizeInBytes(a), getSize(a))); + warmup(a, wtype); + } + else if (a.kind() != cv::_InputArray::NONE) + ADD_FAILURE() << " Uninitialized input/output parameters are not allowed for performance tests"; +} + +void TestBase::warmup(cv::InputOutputArray a, int wtype) +{ + if (a.empty()) return; + if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR) + warmup(a.getMat(), wtype); + else + { + size_t total = a.total(); + for (size_t i = 0; i < total; ++i) + warmup(a.getMat(i), wtype); + } +} + +int TestBase::getSizeInBytes(cv::InputArray a) +{ + if (a.empty()) return 0; + int total = (int)a.total(); + if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR) + return total * CV_ELEM_SIZE(a.type()); + + int size = 0; + for (int i = 0; i < total; ++i) + size += (int)a.total(i) * CV_ELEM_SIZE(a.type(i)); + + return size; +} + +cv::Size TestBase::getSize(cv::InputArray a) +{ + if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR) + return a.size(); + return cv::Size(); +} + +bool TestBase::next() +{ + return ++currentIter < nIters && totalTime < timeLimit; +} + +void TestBase::warmup(cv::Mat m, int wtype) +{ + switch(wtype) + { + case WARMUP_READ: + cv::sum(m.reshape(1)); + return; + case WARMUP_WRITE: + m.reshape(1).setTo(cv::Scalar::all(0)); + return; + case WARMUP_RNG: + randu(m); + return; + default: + return; + } +} + +unsigned int TestBase::getTotalInputSize() const +{ + unsigned int res = 0; + for (SizeVector::const_iterator i = inputData.begin(); i != inputData.end(); ++i) + res += i->first; + return res; +} + +unsigned int TestBase::getTotalOutputSize() const +{ + unsigned int res = 0; + for (SizeVector::const_iterator i = outputData.begin(); i != outputData.end(); ++i) + res += i->first; + return res; +} + +void TestBase::startTimer() +{ + lastTime = cv::getTickCount(); +} + +void TestBase::stopTimer() +{ + int64 time = cv::getTickCount(); + if (lastTime == 0) + ADD_FAILURE() << " stopTimer() is called before startTimer()"; + lastTime = time - lastTime; + totalTime += lastTime; + lastTime -= _timeadjustment; + if (lastTime < 0) lastTime = 0; + times.push_back(lastTime); + lastTime = 0; +} + +performance_metrics& TestBase::calcMetrics() +{ + if ((metrics.samples == (unsigned int)currentIter) || times.size() == 0) + return metrics; + + metrics.bytesIn = getTotalInputSize(); + metrics.bytesOut = getTotalOutputSize(); + metrics.frequency = cv::getTickFrequency(); + metrics.samples = (unsigned int)times.size(); + metrics.outliers = 0; + + if (currentIter == nIters) + metrics.terminationReason = performance_metrics::TERM_ITERATIONS; + else if (totalTime >= timeLimit) + metrics.terminationReason = performance_metrics::TERM_TIME; + else + metrics.terminationReason = performance_metrics::TERM_UNKNOWN; + + std::sort(times.begin(), times.end()); + + //estimate mean and stddev for log(time) + double gmean = 0; + double gstddev = 0; + int n = 0; + for(TimeVector::const_iterator i = times.begin(); i != times.end(); ++i) + { + double x = (double)*i; + if (x < DBL_EPSILON) continue; + double lx = log(x); + + ++n; + double delta = lx - gmean; + gmean += delta / n; + gstddev += delta * (lx - gmean); + } + + gstddev = n > 1 ? sqrt(gstddev / (n - 1)) : 0; + + TimeVector::const_iterator start = times.begin(); + TimeVector::const_iterator end = times.end(); + + //filter outliers assuming log-normal distribution + //http://stackoverflow.com/questions/1867426/modeling-distribution-of-performance-measurements + int offset = 0; + if (gstddev > DBL_EPSILON) + { + double minout = exp(gmean - 3 * gstddev); + double maxout = exp(gmean + 3 * gstddev); + while(*start < minout) ++start, ++metrics.outliers, ++offset; + do --end, ++metrics.outliers; while(*end > maxout); + ++end, --metrics.outliers; + } + + metrics.min = (double)*start; + //calc final metrics + n = 0; + gmean = 0; + gstddev = 0; + double mean = 0; + double stddev = 0; + int m = 0; + for(; start != end; ++start) + { + double x = (double)*start; + if (x > DBL_EPSILON) + { + double lx = log(x); + ++m; + double gdelta = lx - gmean; + gmean += gdelta / m; + gstddev += gdelta * (lx - gmean); + } + ++n; + double delta = x - mean; + mean += delta / n; + stddev += delta * (x - mean); + } + + metrics.mean = mean; + metrics.gmean = exp(gmean); + metrics.gstddev = m > 1 ? sqrt(gstddev / (m - 1)) : 0; + metrics.stddev = n > 1 ? sqrt(stddev / (n - 1)) : 0; + metrics.median = n % 2 + ? (double)times[offset + n / 2] + : 0.5 * (times[offset + n / 2] + times[offset + n / 2 - 1]); + + return metrics; +} + +void TestBase::validateMetrics() +{ + performance_metrics& m = calcMetrics(); + + if (HasFailure()) return; + + ASSERT_GE(m.samples, 1u) + << " No time measurements was performed.\nstartTimer() and stopTimer() commands are required for performance tests."; + + EXPECT_GE(m.samples, param_min_samples) + << " Only a few samples are collected.\nPlease increase number of iterations or/and time limit to get reliable performance measurements."; + + if (m.gstddev > DBL_EPSILON) + { + EXPECT_GT(/*m.gmean * */1., /*m.gmean * */ 2 * sinh(m.gstddev * param_max_deviation)) + << " Test results are not reliable ((mean-sigma,mean+sigma) deviation interval is bigger than measured time interval)."; + } + + EXPECT_LE(m.outliers, std::max((unsigned int)cvCeil(m.samples * param_max_outliers / 100.), 1u)) + << " Test results are not reliable (too many outliers)."; +} + +void TestBase::reportMetrics(bool toJUnitXML) +{ + performance_metrics& m = calcMetrics(); + + if (toJUnitXML) + { + RecordProperty("bytesIn", (int)m.bytesIn); + RecordProperty("bytesOut", (int)m.bytesOut); + RecordProperty("term", m.terminationReason); + RecordProperty("samples", (int)m.samples); + RecordProperty("outliers", (int)m.outliers); + RecordProperty("frequency", cv::format("%.0f", m.frequency).c_str()); + RecordProperty("min", cv::format("%.0f", m.min).c_str()); + RecordProperty("median", cv::format("%.0f", m.median).c_str()); + RecordProperty("gmean", cv::format("%.0f", m.gmean).c_str()); + RecordProperty("gstddev", cv::format("%.6f", m.gstddev).c_str()); + RecordProperty("mean", cv::format("%.0f", m.mean).c_str()); + RecordProperty("stddev", cv::format("%.0f", m.stddev).c_str()); + } + else + { + const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); + const char* type_param = test_info->type_param(); + const char* value_param = test_info->value_param(); + +#if defined(ANDROID) && defined(USE_ANDROID_LOGGING) + LOGD("[ FAILED ] %s.%s", test_info->test_case_name(), test_info->name()); +#endif + + if (type_param) LOGD("type = %11s", type_param); + if (value_param) LOGD("param = %11s", value_param); + + switch (m.terminationReason) + { + case performance_metrics::TERM_ITERATIONS: + LOGD("termination reason: reached maximum number of iterations"); + break; + case performance_metrics::TERM_TIME: + LOGD("termination reason: reached time limit"); + break; + case performance_metrics::TERM_UNKNOWN: + default: + LOGD("termination reason: unknown"); + break; + }; + + LOGD("bytesIn =%11lu", m.bytesIn); + LOGD("bytesOut =%11lu", m.bytesOut); + if (nIters == (unsigned int)-1 || m.terminationReason == performance_metrics::TERM_ITERATIONS) + LOGD("samples =%11u", m.samples); + else + LOGD("samples =%11u of %u", m.samples, nIters); + LOGD("outliers =%11u", m.outliers); + LOGD("frequency =%11.0f", m.frequency); + LOGD("min =%11.0f = %.2fms", m.min, m.min * 1e3 / m.frequency); + LOGD("median =%11.0f = %.2fms", m.median, m.median * 1e3 / m.frequency); + LOGD("gmean =%11.0f = %.2fms", m.gmean, m.gmean * 1e3 / m.frequency); + LOGD("gstddev =%11.8f = %.2fms for 97%% dispersion interval", m.gstddev, m.gmean * 2 * sinh(m.gstddev * 3) * 1e3 / m.frequency); + LOGD("mean =%11.0f = %.2fms", m.mean, m.mean * 1e3 / m.frequency); + LOGD("stddev =%11.0f = %.2fms", m.stddev, m.stddev * 1e3 / m.frequency); + } +} + +void TestBase::SetUp() +{ + lastTime = 0; + totalTime = 0; + nIters = (unsigned int)-1; + currentIter = (unsigned int)-1; + timeLimit = timeLimitDefault; + times.clear(); + cv::theRNG().state = param_seed;//this rng should generate same numbers for each run +} + +void TestBase::TearDown() +{ + validateMetrics(); + if (HasFailure()) + reportMetrics(false); + else + { + const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); + const char* type_param = test_info->type_param(); + const char* value_param = test_info->value_param(); + if (value_param) printf("[ VALUE ] \t%s\n", value_param), fflush(stdout); + if (type_param) printf("[ TYPE ] \t%s\n", type_param), fflush(stdout); + reportMetrics(true); + } +} + +std::string TestBase::getDataPath(const std::string& relativePath) +{ + if (relativePath.empty()) + { + ADD_FAILURE() << " Bad path to test resource"; + return std::string(); + } + + const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH"); + const char *path_separator = "/"; + + std::string path; + if (data_path_dir) + { + int len = strlen(data_path_dir) - 1; + if (len < 0) len = 0; + path = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir)) + + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator); + } + else + { + path = "."; + path += path_separator; + } + + if (relativePath[0] == '/' || relativePath[0] == '\\') + path += relativePath.substr(1); + else + path += relativePath; + + FILE* fp = fopen(path.c_str(), "r"); + if (fp) + fclose(fp); + else + ADD_FAILURE() << " Requested file \"" << path << "\" does not exist."; + return path; +} + +/*****************************************************************************************\ +* ::perf::TestBase::_declareHelper +\*****************************************************************************************/ +TestBase::_declareHelper& TestBase::_declareHelper::iterations(int n) +{ + test->times.clear(); + test->times.reserve(n); + test->nIters = n; + test->currentIter = (unsigned int)-1; + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::time(double timeLimitSecs) +{ + test->times.clear(); + test->currentIter = (unsigned int)-1; + test->timeLimit = (int64)(timeLimitSecs * cv::getTickFrequency()); + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, int wtype) +{ + if (!test->times.empty()) return *this; + TestBase::declareArray(test->inputData, a1, wtype); + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype) +{ + if (!test->times.empty()) return *this; + TestBase::declareArray(test->inputData, a1, wtype); + TestBase::declareArray(test->inputData, a2, wtype); + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype) +{ + if (!test->times.empty()) return *this; + TestBase::declareArray(test->inputData, a1, wtype); + TestBase::declareArray(test->inputData, a2, wtype); + TestBase::declareArray(test->inputData, a3, wtype); + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype) +{ + if (!test->times.empty()) return *this; + TestBase::declareArray(test->inputData, a1, wtype); + TestBase::declareArray(test->inputData, a2, wtype); + TestBase::declareArray(test->inputData, a3, wtype); + TestBase::declareArray(test->inputData, a4, wtype); + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, int wtype) +{ + if (!test->times.empty()) return *this; + TestBase::declareArray(test->outputData, a1, wtype); + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype) +{ + if (!test->times.empty()) return *this; + TestBase::declareArray(test->outputData, a1, wtype); + TestBase::declareArray(test->outputData, a2, wtype); + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype) +{ + if (!test->times.empty()) return *this; + TestBase::declareArray(test->outputData, a1, wtype); + TestBase::declareArray(test->outputData, a2, wtype); + TestBase::declareArray(test->outputData, a3, wtype); + return *this; +} + +TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype) +{ + if (!test->times.empty()) return *this; + TestBase::declareArray(test->outputData, a1, wtype); + TestBase::declareArray(test->outputData, a2, wtype); + TestBase::declareArray(test->outputData, a3, wtype); + TestBase::declareArray(test->outputData, a4, wtype); + return *this; +} + +TestBase::_declareHelper::_declareHelper(TestBase* t) : test(t) +{ +} + +/*****************************************************************************************\ +* ::perf::PrintTo +\*****************************************************************************************/ +namespace perf +{ + +void PrintTo(const MatType& t, ::std::ostream* os) +{ + switch( CV_MAT_DEPTH((int)t) ) + { + case CV_8U: *os << "8U"; break; + case CV_8S: *os << "8S"; break; + case CV_16U: *os << "16U"; break; + case CV_16S: *os << "16S"; break; + case CV_32S: *os << "32S"; break; + case CV_32F: *os << "32F"; break; + case CV_64F: *os << "64F"; break; + case CV_USRTYPE1: *os << "USRTYPE1"; break; + default: *os << "INVALID_TYPE"; break; + } + *os << 'C' << CV_MAT_CN((int)t); +} + +} //namespace perf + +/*****************************************************************************************\ +* ::cv::PrintTo +\*****************************************************************************************/ +namespace cv { + +void PrintTo(const Size& sz, ::std::ostream* os) +{ + *os << /*"Size:" << */sz.width << "x" << sz.height; +} + +} // namespace cv + + +/*****************************************************************************************\ +* ::cv::PrintTo +\*****************************************************************************************/