From 71b2409df0b4cae6ac3a1fbebaf0c7f111f20b45 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 29 Nov 2016 20:28:59 +0300 Subject: [PATCH] ts: added findDataFile() utility function and SkipTestException --- CMakeLists.txt | 14 +-- cmake/OpenCVUtils.cmake | 18 +++ modules/ts/CMakeLists.txt | 20 ++++ modules/ts/include/opencv2/ts.hpp | 58 +++++++-- modules/ts/include/opencv2/ts/ts_ext.hpp | 24 +++- modules/ts/src/ts.cpp | 144 +++++++++++++++++++++-- modules/ts/src/ts_perf.cpp | 4 + 7 files changed, 252 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8337ec9fb9..5ac27a2242 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,14 +362,12 @@ if (OPENCV_TEST_DATA_PATH) get_filename_component(OPENCV_TEST_DATA_PATH ${OPENCV_TEST_DATA_PATH} ABSOLUTE) endif() -if(OPENCV_TEST_DATA_PATH AND NOT OPENCV_TEST_DATA_INSTALL_PATH) - if(ANDROID) - ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "sdk/etc/testdata") - elseif(WIN32) - ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "testdata") - else() - ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "share/OpenCV/testdata") - endif() +if(ANDROID) + ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "sdk/etc/testdata") +elseif(WIN32) + ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "testdata") +else() + ocv_update(OPENCV_TEST_DATA_INSTALL_PATH "share/OpenCV/testdata") endif() if(ANDROID) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 34b4565cf8..8969a50903 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -1035,3 +1035,21 @@ function(ocv_add_test_from_target test_name test_kind the_target) endif() endif() endfunction() + +macro(ocv_add_testdata basedir dest_subdir) + if(BUILD_TESTS) + cmake_parse_arguments(__TESTDATA "" "COMPONENT" "" ${ARGN}) + if(NOT CMAKE_CROSSCOMPILING AND NOT INSTALL_TESTS) + file(COPY ${basedir}/ + DESTINATION ${CMAKE_BINARY_DIR}/${OPENCV_TEST_DATA_INSTALL_PATH}/${dest_subdir} + ${__TESTDATA_UNPARSED_ARGUMENTS} + ) + endif() + if(INSTALL_TESTS) + install(DIRECTORY ${basedir}/ + DESTINATION ${OPENCV_TEST_DATA_INSTALL_PATH}/contrib/text + ${ARGN} + ) + endif() + endif() +endmacro() diff --git a/modules/ts/CMakeLists.txt b/modules/ts/CMakeLists.txt index 8d625f8d00..f95bed0793 100644 --- a/modules/ts/CMakeLists.txt +++ b/modules/ts/CMakeLists.txt @@ -21,3 +21,23 @@ ocv_add_module(ts INTERNAL opencv_core opencv_imgproc opencv_imgcodecs opencv_vi ocv_glob_module_sources() ocv_module_include_directories() ocv_create_module() + +# generate config file +set(OPENCV_TESTS_CONFIG_FILE "${CMAKE_BINARY_DIR}/opencv_tests_config.hpp") +set(OPENCV_TESTS_CONFIG_STR "") +if(CMAKE_INSTALL_PREFIX) + set(OPENCV_TESTS_CONFIG_STR "${OPENCV_TESTS_CONFIG_STR} +#define OPENCV_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\" +") +endif() +if(OPENCV_TEST_DATA_INSTALL_PATH) + set(OPENCV_TESTS_CONFIG_STR "${OPENCV_TESTS_CONFIG_STR} +#define OPENCV_TEST_DATA_INSTALL_PATH \"${OPENCV_TEST_DATA_INSTALL_PATH}\" +") +endif() +if(EXISTS "${OPENCV_TESTS_CONFIG_FILE}") + file(READ "${OPENCV_TESTS_CONFIG_FILE}" __content) +endif() +if(NOT OPENCV_TESTS_CONFIG_STR STREQUAL "${__content}") + file(WRITE "${OPENCV_TESTS_CONFIG_FILE}" "${OPENCV_TESTS_CONFIG_STR}") +endif() diff --git a/modules/ts/include/opencv2/ts.hpp b/modules/ts/include/opencv2/ts.hpp index 5f5dec65a2..28a071a565 100644 --- a/modules/ts/include/opencv2/ts.hpp +++ b/modules/ts/include/opencv2/ts.hpp @@ -1,5 +1,5 @@ -#ifndef OPENCV_GTESTCV_HPP -#define OPENCV_GTESTCV_HPP +#ifndef OPENCV_TS_HPP +#define OPENCV_TS_HPP #include "opencv2/core/cvdef.h" #include // for va_list @@ -55,6 +55,14 @@ using cv::Rect; using cv::InputArray; using cv::noArray; +class SkipTestException: public cv::Exception +{ +public: + int dummy; // workaround for MacOSX Xcode 7.3 bug (don't make class "empty") + SkipTestException() : dummy(0) {} + SkipTestException(const cv::String& message) : dummy(0) { this->msg = message; } +}; + class CV_EXPORTS TS; CV_EXPORTS int64 readSeed(const char* str); @@ -420,6 +428,8 @@ public: // returns textual description of failure code static string str_from_code( const TS::FailureCode code ); + std::vector data_search_path; + std::vector data_search_subdir; protected: // these are allocated within a test to try keep them valid in case of stack corruption @@ -539,17 +549,37 @@ struct CV_EXPORTS DefaultRngAuto DefaultRngAuto& operator=(const DefaultRngAuto&); }; -} - -namespace cvtest -{ // test images generation functions CV_EXPORTS void fillGradient(Mat& img, int delta = 5); CV_EXPORTS void smoothBorder(Mat& img, const Scalar& color, int delta = 3); CV_EXPORTS void printVersionInfo(bool useStdOut = true); -} //namespace cvtest + + +// Utility functions + +CV_EXPORTS void addDataSearchPath(const std::string& path); +CV_EXPORTS void addDataSearchSubDirectory(const std::string& subdir); + +/*! @brief Try to find requested data file + + Search directories: + + 0. TS::data_search_path (search sub-directories are not used) + 1. OPENCV_TEST_DATA_PATH environment variable + 2. One of these: + a. OpenCV testdata based on build location: "./" + "share/OpenCV/testdata" + b. OpenCV testdata at install location: CMAKE_INSTALL_PREFIX + "share/OpenCV/testdata" + + Search sub-directories: + + - addDataSearchSubDirectory() + - modulename from TS::init() + + */ +CV_EXPORTS std::string findDataFile(const std::string& relative_path, bool required = true); + #ifndef __CV_TEST_EXEC_ARGS #if defined(_MSC_VER) && (_MSC_VER <= 1400) @@ -562,9 +592,9 @@ CV_EXPORTS void printVersionInfo(bool useStdOut = true); #endif #ifdef HAVE_OPENCL -namespace cvtest { namespace ocl { +namespace ocl { void dumpOpenCLDevice(); -} } +} #define TEST_DUMP_OCL_INFO cvtest::ocl::dumpOpenCLDevice(); #else #define TEST_DUMP_OCL_INFO @@ -575,11 +605,13 @@ void parseCustomOptions(int argc, char **argv); #define CV_TEST_MAIN(resourcesubdir, ...) \ int main(int argc, char **argv) \ { \ - __CV_TEST_EXEC_ARGS(__VA_ARGS__) \ - cvtest::TS::ptr()->init(resourcesubdir); \ + using namespace cvtest; \ + TS* ts = TS::ptr(); \ + ts->init(resourcesubdir); \ ::testing::InitGoogleTest(&argc, argv); \ cvtest::printVersionInfo(); \ TEST_DUMP_OCL_INFO \ + __CV_TEST_EXEC_ARGS(__VA_ARGS__) \ parseCustomOptions(argc, argv); \ return RUN_ALL_TESTS(); \ } @@ -591,7 +623,9 @@ int main(int argc, char **argv) \ FAIL() << "No equivalent implementation."; \ } while (0) -#endif +} //namespace cvtest + +#endif // OPENCV_TS_HPP #include "opencv2/ts/ts_perf.hpp" diff --git a/modules/ts/include/opencv2/ts/ts_ext.hpp b/modules/ts/include/opencv2/ts/ts_ext.hpp index ed6009ccd8..781b61e4dd 100644 --- a/modules/ts/include/opencv2/ts/ts_ext.hpp +++ b/modules/ts/include/opencv2/ts/ts_ext.hpp @@ -8,7 +8,25 @@ #ifndef OPENCV_TS_EXT_HPP #define OPENCV_TS_EXT_HPP +namespace cvtest { void checkIppStatus(); +} + +#define CV_TEST_INIT cv::ipp::setIppStatus(0); +#define CV_TEST_CLEANUP ::cvtest::checkIppStatus(); +#define CV_TEST_BODY_IMPL \ + { \ + try { \ + CV_TEST_INIT \ + Body(); \ + CV_TEST_CLEANUP \ + } \ + catch (cvtest::SkipTestException& e) \ + { \ + printf("[ SKIP ] %s\n", e.what()); \ + } \ + } \ + #undef TEST #define TEST(test_case_name, test_name) \ @@ -33,7 +51,7 @@ void checkIppStatus(); ::testing::Test::TearDownTestCase, \ new ::testing::internal::TestFactoryImpl<\ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\ - void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() { cv::ipp::setIppStatus(0); Body(); checkIppStatus(); } \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() CV_TEST_BODY_IMPL \ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::Body() #undef TEST_F @@ -59,7 +77,7 @@ void checkIppStatus(); test_fixture::TearDownTestCase, \ new ::testing::internal::TestFactoryImpl<\ GTEST_TEST_CLASS_NAME_(test_fixture, test_name)>);\ - void GTEST_TEST_CLASS_NAME_(test_fixture, test_name)::TestBody() { cv::ipp::setIppStatus(0); Body(); checkIppStatus(); } \ + void GTEST_TEST_CLASS_NAME_(test_fixture, test_name)::TestBody() CV_TEST_BODY_IMPL \ void GTEST_TEST_CLASS_NAME_(test_fixture, test_name)::Body() #undef TEST_P @@ -91,7 +109,7 @@ void checkIppStatus(); int GTEST_TEST_CLASS_NAME_(test_case_name, \ test_name)::gtest_registering_dummy_ = \ GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ - void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() { cv::ipp::setIppStatus(0); Body(); checkIppStatus(); } \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() CV_TEST_BODY_IMPL \ void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::Body() #endif // OPENCV_TS_EXT_HPP diff --git a/modules/ts/src/ts.cpp b/modules/ts/src/ts.cpp index c02822ee08..c60e5d635a 100644 --- a/modules/ts/src/ts.cpp +++ b/modules/ts/src/ts.cpp @@ -66,9 +66,32 @@ #include #endif +// isDirectory +#if defined WIN32 || defined _WIN32 || defined WINCE +# include +#else +# include +# include +#endif + + +#include "opencv_tests_config.hpp" + namespace cvtest { +static std::string path_join(const std::string& prefix, const std::string& subpath) +{ + CV_Assert(subpath.empty() || subpath[0] != '/'); + if (prefix.empty()) + return subpath; + bool skipSlash = prefix.size() > 0 ? (prefix[prefix.size()-1] == '/' || prefix[prefix.size()-1] == '\\') : false; + std::string path = prefix + (skipSlash ? "" : "/") + subpath; + return path; +} + + + /*****************************************************************************************\ * Exception and memory handlers * \*****************************************************************************************/ @@ -449,6 +472,7 @@ static int tsErrorCallback( int status, const char* func_name, const char* err_m void TS::init( const string& modulename ) { + data_search_subdir.push_back(modulename); #ifndef WINRT char* datapath_dir = getenv("OPENCV_TEST_DATA_PATH"); #else @@ -457,11 +481,7 @@ void TS::init( const string& modulename ) if( datapath_dir ) { - char buf[1024]; - size_t l = strlen(datapath_dir); - bool haveSlash = l > 0 && (datapath_dir[l-1] == '/' || datapath_dir[l-1] == '\\'); - sprintf( buf, "%s%s%s/", datapath_dir, haveSlash ? "" : "/", modulename.c_str() ); - data_path = string(buf); + data_path = path_join(path_join(datapath_dir, modulename), ""); } cv::redirectError((cv::ErrorCallback)tsErrorCallback, this); @@ -583,7 +603,7 @@ void TS::printf( int streams, const char* fmt, ... ) } -TS ts; +static TS ts; TS* TS::ptr() { return &ts; } void fillGradient(Mat& img, int delta) @@ -659,7 +679,6 @@ void smoothBorder(Mat& img, const Scalar& color, int delta) } } -} //namespace cvtest bool test_ipp_check = false; @@ -694,4 +713,115 @@ void parseCustomOptions(int argc, char **argv) #endif } + +static bool isDirectory(const std::string& path) +{ +#if defined WIN32 || defined _WIN32 || defined WINCE + WIN32_FILE_ATTRIBUTE_DATA all_attrs; +#ifdef WINRT + wchar_t wpath[MAX_PATH]; + size_t copied = mbstowcs(wpath, path.c_str(), MAX_PATH); + CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1)); + BOOL status = ::GetFileAttributesExW(wpath, GetFileExInfoStandard, &all_attrs); +#else + BOOL status = ::GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &all_attrs); +#endif + DWORD attributes = all_attrs.dwFileAttributes; + return status && ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0); +#else + struct stat s; + if (0 != stat(path.c_str(), &s)) + return false; + return S_ISDIR(s.st_mode); +#endif +} + +CV_EXPORTS void addDataSearchPath(const std::string& path) +{ + if (isDirectory(path)) + TS::ptr()->data_search_path.push_back(path); +} +CV_EXPORTS void addDataSearchSubDirectory(const std::string& subdir) +{ + TS::ptr()->data_search_subdir.push_back(subdir); +} + +std::string findDataFile(const std::string& relative_path, bool required) +{ +#define TEST_TRY_FILE_WITH_PREFIX(prefix) \ +{ \ + std::string path = path_join(prefix, relative_path); \ + /*printf("Trying %s\n", path.c_str());*/ \ + FILE* f = fopen(path.c_str(), "rb"); \ + if(f) { \ + fclose(f); \ + return path; \ + } \ +} + + const std::vector& search_path = TS::ptr()->data_search_path; + for(size_t i = search_path.size(); i > 0; i--) + { + const std::string& prefix = search_path[i - 1]; + TEST_TRY_FILE_WITH_PREFIX(prefix); + } + + const std::vector& search_subdir = TS::ptr()->data_search_subdir; + +#ifndef WINRT + char* datapath_dir = getenv("OPENCV_TEST_DATA_PATH"); +#else + char* datapath_dir = OPENCV_TEST_DATA_PATH; +#endif + + std::string datapath; + if (datapath_dir) + { + datapath = datapath_dir; + //CV_Assert(isDirectory(datapath) && "OPENCV_TEST_DATA_PATH is specified but it doesn't exist"); + if (isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const std::string& subdir = search_subdir[i - 1]; + std::string prefix = path_join(datapath, subdir); + TEST_TRY_FILE_WITH_PREFIX(prefix); + } + } + } +#ifdef OPENCV_TEST_DATA_INSTALL_PATH + datapath = path_join("./", OPENCV_TEST_DATA_INSTALL_PATH); + if (isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const std::string& subdir = search_subdir[i - 1]; + std::string prefix = path_join(datapath, subdir); + TEST_TRY_FILE_WITH_PREFIX(prefix); + } + } +#ifdef OPENCV_INSTALL_PREFIX + else + { + datapath = path_join(OPENCV_INSTALL_PREFIX, OPENCV_TEST_DATA_INSTALL_PATH); + if (isDirectory(datapath)) + { + for(size_t i = search_subdir.size(); i > 0; i--) + { + const std::string& subdir = search_subdir[i - 1]; + std::string prefix = path_join(datapath, subdir); + TEST_TRY_FILE_WITH_PREFIX(prefix); + } + } + } +#endif +#endif + if (required) + CV_ErrorNoReturn(cv::Error::StsError, cv::format("OpenCV tests: Can't find required data file: %s", relative_path.c_str())); + throw SkipTestException(cv::format("OpenCV tests: Can't find data file: %s", relative_path.c_str())); +} + + +} //namespace cvtest + /* End of file. */ diff --git a/modules/ts/src/ts_perf.cpp b/modules/ts/src/ts_perf.cpp index 1c40fb1ed4..f4967748ee 100644 --- a/modules/ts/src/ts_perf.cpp +++ b/modules/ts/src/ts_perf.cpp @@ -19,6 +19,7 @@ # include #endif +using namespace cvtest; using namespace perf; int64 TestBase::timeLimitDefault = 0; @@ -48,7 +49,10 @@ static bool param_collect_impl; #ifdef ENABLE_INSTRUMENTATION static int param_instrument; #endif + +namespace cvtest { extern bool test_ipp_check; +} #ifdef HAVE_CUDA static int param_cuda_device;