From 90c74e3be047b484a7902483cb8e9373c9bb65f0 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Tue, 15 Sep 2015 15:14:10 +0300 Subject: [PATCH] ts: refactor run.py script --- modules/ts/misc/run.py | 1045 ++++--------------------------- modules/ts/misc/run_android.py | 197 ++++++ modules/ts/misc/run_suite.py | 164 +++++ modules/ts/misc/run_utils.py | 447 +++++++++++++ platforms/scripts/valgrind.supp | 6 + 5 files changed, 920 insertions(+), 939 deletions(-) create mode 100644 modules/ts/misc/run_android.py create mode 100644 modules/ts/misc/run_suite.py create mode 100644 modules/ts/misc/run_utils.py create mode 100644 platforms/scripts/valgrind.supp diff --git a/modules/ts/misc/run.py b/modules/ts/misc/run.py index f25922d94..a3a13145a 100755 --- a/modules/ts/misc/run.py +++ b/modules/ts/misc/run.py @@ -1,940 +1,87 @@ #!/usr/bin/env python -import sys, os, platform, xml, re, tempfile, glob, datetime, getpass, shutil -from optparse import OptionParser -from subprocess import Popen, PIPE - -hostos = os.name # 'nt', 'posix' -hostmachine = platform.machine() # 'x86', 'AMD64', 'x86_64' - -errorCode = 0 - -SIMD_DETECTION_PROGRAM=""" -#if __SSE5__ -# error SSE5 -#endif -#if __AVX2__ -# error AVX2 -#endif -#if __AVX__ -# error AVX -#endif -#if __SSE4_2__ -# error SSE4.2 -#endif -#if __SSE4_1__ -# error SSE4.1 -#endif -#if __SSSE3__ -# error SSSE3 -#endif -#if __SSE3__ -# error SSE3 -#endif -#if __AES__ -# error AES -#endif -#if __SSE2__ -# error SSE2 -#endif -#if __SSE__ -# error SSE -#endif -#if __3dNOW__ -# error 3dNOW -#endif -#if __MMX__ -# error MMX -#endif -#if __ARM_NEON__ -# error NEON -#endif -#error NOSIMD -""" - -parse_patterns = ( - {'name': "has_perf_tests", 'default': "OFF", 'pattern': re.compile("^BUILD_PERF_TESTS:BOOL=(ON)$")}, - {'name': "has_accuracy_tests", 'default': "OFF", 'pattern': re.compile("^BUILD_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': "git_executable", 'default': None, 'pattern': re.compile("^GIT_EXECUTABLE:FILEPATH=(.*)$")}, - {'name': "cxx_flags", 'default': "", 'pattern': re.compile("^CMAKE_CXX_FLAGS:STRING=(.*)$")}, - {'name': "cxx_flags_debug", 'default': "", 'pattern': re.compile("^CMAKE_CXX_FLAGS_DEBUG:STRING=(.*)$")}, - {'name': "cxx_flags_release", 'default': "", 'pattern': re.compile("^CMAKE_CXX_FLAGS_RELEASE:STRING=(.*)$")}, - {'name': "opencv_cxx_flags", 'default': "", 'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS:INTERNAL=(.*)$")}, - {'name': "opencv_cxx_flags_debug", 'default': "", 'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS_DEBUG:INTERNAL=(.*)$")}, - {'name': "opencv_cxx_flags_release", 'default': "", 'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS_RELEASE:INTERNAL=(.*)$")}, - {'name': "cxx_flags_android", 'default': None, 'pattern': re.compile("^ANDROID_CXX_FLAGS:INTERNAL=(.*)$")}, - {'name': "ndk_path", 'default': None, 'pattern': re.compile("^(?:ANDROID_NDK|ANDROID_STANDALONE_TOOLCHAIN)?:PATH=(.*)$")}, - {'name': "android_abi", 'default': None, 'pattern': re.compile("^ANDROID_ABI:STRING=(.*)$")}, - {'name': "android_executable", 'default': None, 'pattern': re.compile("^ANDROID_EXECUTABLE:FILEPATH=(.*android.*)$")}, - {'name': "ant_executable", 'default': None, 'pattern': re.compile("^ANT_EXECUTABLE:FILEPATH=(.*ant.*)$")}, - {'name': "java_test_binary_dir", 'default': None, 'pattern': re.compile("^opencv_test_java_BINARY_DIR:STATIC=(.*)$")}, - {'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=(.+)$")}, - {'name': "cxx_compiler_arg1", 'default': None, 'pattern': re.compile("^CMAKE_CXX_COMPILER_ARG1:[A-Z]+=(.+)$")}, - {'name': "with_cuda", 'default': "OFF", 'pattern': re.compile("^WITH_CUDA:BOOL=(ON)$")}, - {'name': "cuda_library", 'default': None, 'pattern': re.compile("^CUDA_CUDA_LIBRARY:FILEPATH=(.+)$")}, - {'name': "core_dependencies", 'default': None, 'pattern': re.compile("^opencv_core_LIB_DEPENDS:STATIC=(.+)$")}, -) - -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") - -def getRunningProcessExePathByName_win32(name): - from ctypes import windll, POINTER, pointer, Structure, sizeof - from ctypes import c_long , c_int , c_uint , c_char , c_ubyte , c_char_p , c_void_p - - class PROCESSENTRY32(Structure): - _fields_ = [ ( 'dwSize' , c_uint ) , - ( 'cntUsage' , c_uint) , - ( 'th32ProcessID' , c_uint) , - ( 'th32DefaultHeapID' , c_uint) , - ( 'th32ModuleID' , c_uint) , - ( 'cntThreads' , c_uint) , - ( 'th32ParentProcessID' , c_uint) , - ( 'pcPriClassBase' , c_long) , - ( 'dwFlags' , c_uint) , - ( 'szExeFile' , c_char * 260 ) , - ( 'th32MemoryBase' , c_long) , - ( 'th32AccessKey' , c_long ) ] - - class MODULEENTRY32(Structure): - _fields_ = [ ( 'dwSize' , c_long ) , - ( 'th32ModuleID' , c_long ), - ( 'th32ProcessID' , c_long ), - ( 'GlblcntUsage' , c_long ), - ( 'ProccntUsage' , c_long ) , - ( 'modBaseAddr' , c_long ) , - ( 'modBaseSize' , c_long ) , - ( 'hModule' , c_void_p ) , - ( 'szModule' , c_char * 256 ), - ( 'szExePath' , c_char * 260 ) ] - - TH32CS_SNAPPROCESS = 2 - TH32CS_SNAPMODULE = 0x00000008 - - ## CreateToolhelp32Snapshot - CreateToolhelp32Snapshot= windll.kernel32.CreateToolhelp32Snapshot - CreateToolhelp32Snapshot.reltype = c_long - CreateToolhelp32Snapshot.argtypes = [ c_int , c_int ] - ## Process32First - Process32First = windll.kernel32.Process32First - Process32First.argtypes = [ c_void_p , POINTER( PROCESSENTRY32 ) ] - Process32First.rettype = c_int - ## Process32Next - Process32Next = windll.kernel32.Process32Next - Process32Next.argtypes = [ c_void_p , POINTER(PROCESSENTRY32) ] - Process32Next.rettype = c_int - ## CloseHandle - CloseHandle = windll.kernel32.CloseHandle - CloseHandle.argtypes = [ c_void_p ] - CloseHandle.rettype = c_int - ## Module32First - Module32First = windll.kernel32.Module32First - Module32First.argtypes = [ c_void_p , POINTER(MODULEENTRY32) ] - Module32First.rettype = c_int - - hProcessSnap = c_void_p(0) - hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS , 0 ) - - pe32 = PROCESSENTRY32() - pe32.dwSize = sizeof( PROCESSENTRY32 ) - ret = Process32First( hProcessSnap , pointer( pe32 ) ) - path = None - - while ret : - if name + ".exe" == pe32.szExeFile: - hModuleSnap = c_void_p(0) - me32 = MODULEENTRY32() - me32.dwSize = sizeof( MODULEENTRY32 ) - hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, pe32.th32ProcessID ) - - ret = Module32First( hModuleSnap, pointer(me32) ) - path = me32.szExePath - CloseHandle( hModuleSnap ) - if path: - break - ret = Process32Next( hProcessSnap, pointer(pe32) ) - CloseHandle( hProcessSnap ) - return path - -def getRunningProcessExePathByName_posix(name): - pids= [pid for pid in os.listdir('/proc') if pid.isdigit()] - for pid in pids: - try: - path = os.readlink(os.path.join('/proc', pid, 'exe')) - if path and path.endswith(name): - return path - except: - pass - -def getRunningProcessExePathByName(name): - try: - if hostos == "nt": - return getRunningProcessExePathByName_win32(name) - elif hostos == "posix": - return getRunningProcessExePathByName_posix(name) - else: - return None - except: - return None - -class TestSuite(object): - def __init__(self, options, path = None): - self.options = options - self.path = path - self.error = None - self.setUp = None - self.tearDown = None - self.adb = None - self.targetos = None - self.nameprefix = "opencv_" + self.options.mode + "_" - for p in parse_patterns: - setattr(self, p["name"], p["default"]) - - if self.path: - cachefile = open(os.path.join(self.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() - - # detect target platform - if self.android_executable or self.android_abi or self.ndk_path: - self.targetos = "android" - else: - self.targetos = hostos - - self.initialize() - - def initialize(self): - # fix empty tests dir - if not self.tests_dir: - self.tests_dir = self.path - self.tests_dir = os.path.normpath(self.tests_dir) - - # compute 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']) - if not os.path.isfile(self.adb) or not os.access(self.adb, os.X_OK): - self.adb = None - else: - self.adb = None - - if self.targetos == "android": - # fix adb tool location - if not self.adb: - self.adb = getRunningProcessExePathByName("adb") - if not self.adb: - self.adb = "adb" - if self.options.adb_serial: - self.adb = [self.adb, "-s", self.options.adb_serial] - else: - self.adb = [self.adb] - try: - output = Popen(self.adb + ["shell", "ls"], stdout=PIPE, stderr=PIPE).communicate() - except OSError: - self.adb = [] - # remember current device serial. Needed if another device is connected while this script runs - if self.adb and not self.options.adb_serial: - adb_res = self.runAdb("devices") - if not adb_res: - self.error = "Could not run adb command: %s (for %s)" % (self.error, self.path) - self.adb = [] - else: - # assume here that device name may consists of any characters except newline - connected_devices = re.findall(r"^[^\n]+[ \t]+device\r?$", adb_res, re.MULTILINE) - if not connected_devices: - self.error = "Android device not found" - self.adb = [] - elif len(connected_devices) != 1: - self.error = "Too many (%s) devices are connected. Please specify single device using --serial option:\n\n" % (len(connected_devices)) + adb_res - self.adb = [] - else: - self.options.adb_serial = connected_devices[0].split("\t")[0] - self.adb = self.adb + ["-s", self.options.adb_serial] - if self.adb: - # construct name for aapt tool - self.aapt = [os.path.join(os.path.dirname(self.adb[0]), ("aapt","aapt.exe")[hostos == 'nt'])] - if not os.path.isfile(self.aapt[0]): - # it's moved in SDK r22 - sdk_dir = os.path.dirname( os.path.dirname(self.adb[0]) ) - aapt_fn = ("aapt", "aapt.exe")[hostos == 'nt'] - for r, ds, fs in os.walk( os.path.join(sdk_dir, 'build-tools') ): - if aapt_fn in fs: - self.aapt = [ os.path.join(r, aapt_fn) ] - break - else: - self.error = "Can't find '%s' tool!" % aapt_fn - - # fix has_perf_tests param - self.has_perf_tests = self.has_perf_tests == "ON" - self.has_accuracy_tests = self.has_accuracy_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: - if self.options.configuration: - self.tests_dir = os.path.join(self.tests_dir, self.options.configuration) - else: - 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: - compiler = [self.cxx_compiler] - if self.cxx_compiler_arg1: - compiler.append(self.cxx_compiler_arg1) - output = Popen(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": - if "armeabi-v7a" in self.android_abi: - self.targetarch = "armv7a" - elif "armeabi-v6" in self.android_abi: - self.targetarch = "armv6" - elif "armeabi" in self.android_abi: - self.targetarch = "armv5te" - elif "x86" in self.android_abi: - self.targetarch = "x86" - elif "mips" in self.android_abi: - self.targetarch = "mips" - else: - 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" - - # fix CUDA attributes - self.with_cuda = self.with_cuda == "ON" - if self.cuda_library and self.cuda_library.endswith("-NOTFOUND"): - self.cuda_library = None - self.has_cuda = self.with_cuda and self.cuda_library and self.targetarch in ["x86", "x64"] - - self.hardware = None - - self.cmake_home_vcver = self.getVCVersion(self.cmake_home) - if self.opencv_home == self.cmake_home: - self.opencv_home_vcver = self.cmake_home_vcver - else: - self.opencv_home_vcver = self.getVCVersion(self.opencv_home) - - self.tests = self.getAvailableTestApps() - - def getVCVersion(self, root_path): - if not root_path: - return None - if os.path.isdir(os.path.join(root_path, ".svn")): - return self.getSvnVersion(root_path) - elif os.path.isdir(os.path.join(root_path, ".git")): - return self.getGitHash(root_path) - return None - - def getGitHash(self, path): - if not path or not self.git_executable: - return None - try: - output = Popen([self.git_executable, "rev-parse", "--short", "HEAD"], stdout=PIPE, stderr=PIPE, cwd = path).communicate() - if not output[1]: - return output[0].strip() - else: - return None - except OSError: - return None - - def getSvnVersion(self, path): - if not path: - val = None - elif not self.svnversion_path and hostos == 'nt': - val = self.tryGetSvnVersionWithTortoise(path) - 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]: - val = output[0] - else: - val = None - except OSError: - val = None - if val: - val = val.replace(" ", "_") - return val - - def tryGetSvnVersionWithTortoise(self, path): - 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() - return version - except: - return None - finally: - if dir: - shutil.rmtree(dir) - - def isTest(self, fullpath): - if not os.path.isfile(fullpath): - return False - if self.targetos == "nt" and not fullpath.endswith(".exe"): - return False - if hostos == self.targetos: - return os.access(fullpath, os.X_OK) - if self.targetos == "android" and fullpath.endswith(".apk"): - return True - 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, self.nameprefix + "*")) - files = [f for f in files if self.isTest(f)] - if self.ant_executable and self.java_test_binary_dir: - files.append("java") - return files - return [] - - def getLogName(self, app, timestamp): - app = os.path.basename(app) - if app.endswith(".exe"): - if app.endswith("d.exe"): - app = app[:-5] - else: - app = app[:-4] - if app.startswith(self.nameprefix): - app = app[len(self.nameprefix):] - - if self.cmake_home_vcver: - if self.cmake_home_vcver == self.opencv_home_vcver: - rev = self.cmake_home_vcver - elif self.opencv_home_vcver: - rev = self.cmake_home_vcver + "-" + self.opencv_home_vcver - else: - rev = self.cmake_home_vcver - else: - rev = None - if rev: - rev = rev.replace(":","to") - else: - rev = "" - - if self.options.useLongNames: - if not rev: - rev = "unknown" - tstamp = timestamp.strftime("%Y%m%d-%H%M%S") - - features = [] - #OS - _os = "" - if self.targetos == "android": - _os = "Android" + self.runAdb("shell", "getprop ro.build.version.release").strip() - else: - mv = platform.mac_ver() - if mv[0]: - _os = "Darwin" + mv[0] - else: - wv = platform.win32_ver() - if wv[0]: - _os = "Windows" + wv[0] - else: - lv = platform.linux_distribution() - if lv[0]: - _os = lv[0] + lv[1] - else: - _os = self.targetos - features.append(_os) - - #HW(x86, x64, ARMv7a) - if self.targetarch: - features.append(self.targetarch) - - #TBB - if ";tbb;" in self.core_dependencies: - features.append("TBB") - - #CUDA - if self.has_cuda: - #TODO: determine compute capability - features.append("CUDA") - - #SIMD - compiler_output = "" - try: - tmpfile = tempfile.mkstemp(suffix=".cpp", text = True) - fd = os.fdopen(tmpfile[0], "w+b") - fd.write(SIMD_DETECTION_PROGRAM) - fd.close(); - options = [self.cxx_compiler] - if self.cxx_compiler_arg1: - options.append(self.cxx_compiler_arg1) - cxx_flags = self.cxx_flags + " " + self.cxx_flags_release + " " + self.opencv_cxx_flags + " " + self.opencv_cxx_flags_release - if self.targetos == "android" and self.cxx_flags_android: - cxx_flags = self.cxx_flags_android + " " + cxx_flags - - prev_option = None - for opt in cxx_flags.split(" "): - if opt.count('\"') % 2 == 1: - if prev_option is None: - prev_option = opt - else: - options.append(prev_option + " " + opt) - prev_option = None - elif prev_option is None: - options.append(opt) - else: - prev_option = prev_option + " " + opt - options.append(tmpfile[1]) - output = Popen(options, stdout=PIPE, stderr=PIPE).communicate() - compiler_output = output[1] - os.remove(tmpfile[1]) - except OSError: - pass - if compiler_output: - m = re.search("#error\W+(\w+)", compiler_output) - if m: - features.append(m.group(1)) - - #fin - return "%s__%s__%s__%s.xml" % (app, rev, tstamp, "_".join(features)) - else: - if rev: - rev = rev + "_" - if self.hardware: - hw = str(self.hardware).replace(" ", "_") + "_" - elif self.has_cuda: - hw = "CUDA_" - else: - hw = "" - tstamp = timestamp.strftime("%Y%m%d-%H%M%S") - lname = "%s_%s_%s_%s%s%s.xml" % (app, self.targetos, self.targetarch, hw, rev, tstamp) - lname = str.replace(lname, '(', '_') - lname = str.replace(lname, ')', '_') - return lname - - 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 - if self.targetos == "android": - fullname += ".apk" - 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") or (self.targetos == "android" and fname.endswith(".apk")): - fname = fname[:-4] - if fname == name: - return t - if self.options.configuration == "Debug" and fname == name + 'd': - return t - if fname.startswith(self.nameprefix): - fname = fname[len(self.nameprefix):] - if fname == name: - return t - if self.options.configuration == "Debug" and fname == name + 'd': - 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] - except OSError: - pass - return None - - def isRunnable(self): - if self.error: - 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: - self.error = "Could not find adb executable (for %s)" % self.path - return False - if "armeabi-v7a" in self.android_abi: - adb_res = self.runAdb("shell", "cat /proc/cpuinfo") - if not adb_res: - self.error = "Could not get info about Android platform: %s (for %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 (for %s)" % self.path - return False - if "NEON" in self.android_abi and "neon" not in adb_res: - self.error = "Android device has no NEON, but tests are built for %s (for %s)" % (self.android_abi, 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 = []): - global errorCode - - 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[0][userlog[0].find(":")+1:] - - if self.targetos == "android" and exe.endswith(".apk"): - print "Run java tests:", exe - try: - # get package info - output = Popen(self.aapt + ["dump", "xmltree", exe, "AndroidManifest.xml"], stdout=PIPE, stderr=_stderr).communicate() - if not output[0]: - print >> _stderr, "fail to dump manifest from", exe - return - tags = re.split(r"[ ]+E: ", output[0]) - # get package name - manifest_tag = [t for t in tags if t.startswith("manifest ")] - if not manifest_tag: - print >> _stderr, "fail to read package name from", exe - return - pkg_name = re.search(r"^[ ]+A: package=\"(?P.*?)\" \(Raw: \"(?P=pkg)\"\)\r?$", manifest_tag[0], flags=re.MULTILINE).group("pkg") - # get test instrumentation info - instrumentation_tag = [t for t in tags if t.startswith("instrumentation ")] - if not instrumentation_tag: - print >> _stderr, "can not find instrumentation detials in", exe - return - pkg_runner = re.search(r"^[ ]+A: android:name\(0x[0-9a-f]{8}\)=\"(?P.*?)\" \(Raw: \"(?P=runner)\"\)\r?$", instrumentation_tag[0], flags=re.MULTILINE).group("runner") - pkg_target = re.search(r"^[ ]+A: android:targetPackage\(0x[0-9a-f]{8}\)=\"(?P.*?)\" \(Raw: \"(?P=pkg)\"\)\r?$", instrumentation_tag[0], flags=re.MULTILINE).group("pkg") - if not pkg_name or not pkg_runner or not pkg_target: - print >> _stderr, "can not find instrumentation detials in", exe - return - if self.options.junit_package: - if self.options.junit_package.startswith("."): - pkg_target += self.options.junit_package - else: - pkg_target = self.options.junit_package - # uninstall previously installed package - print >> _stderr, "Uninstalling old", pkg_name, "from device..." - Popen(self.adb + ["uninstall", pkg_name], stdout=PIPE, stderr=_stderr).communicate() - print >> _stderr, "Installing new", exe, "to device...", - output = Popen(self.adb + ["install", exe], stdout=PIPE, stderr=PIPE).communicate() - if output[0] and output[0].strip().endswith("Success"): - print >> _stderr, "Success" - else: - print >> _stderr, "Failure" - print >> _stderr, "Failed to install", exe, "to device" - return - print >> _stderr, "Running jUnit tests for ", pkg_target - if self.setUp: - self.setUp() - Popen(self.adb + ["shell", "am instrument -w -e package " + pkg_target + " " + pkg_name + "/" + pkg_runner], stdout=_stdout, stderr=_stderr).wait() - if self.tearDown: - self.tearDown() - except OSError: - pass - return - elif self.targetos == "android": - hostlogpath = "" - usercolor = [a for a in args if a.startswith("--gtest_color=")] - if len(usercolor) == 0 and _stdout.isatty() and hostos != "nt": - args.append("--gtest_color=yes") - try: - tempdir = "/data/local/tmp/" - andoidcwd = tempdir + getpass.getuser().replace(" ","") + "_" + self.options.mode +"/" - exename = os.path.basename(exe) - androidexe = andoidcwd + exename - # upload - _stderr.write("Uploading... ") - 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 - 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 - if self.options.help: - command = exename + " --help" - else: - command = exename + " " + " ".join(args) - print >> _stderr, "Run command:", command - if self.setUp: - self.setUp() - env = self.options.android_env.copy() - env['OPENCV_TEST_DATA_PATH'] = self.options.test_data_path - if self.options.android_propagate_opencv_env: - for k, v in os.environ.items(): - if k.startswith('OPENCV') and not k in env: - env[k] = v - print >> _stderr, "Android environment variables: \n", '\n'.join([' %s=%s' % (k, v) for k, v in env.items()]) - commandPrefix = ''.join(['export %s=%s && ' % (k, v) for k, v in env.items()]) - Popen(self.adb + ["shell", commandPrefix + "cd " + andoidcwd + "&& ./" + command], stdout=_stdout, stderr=_stderr).wait() - if self.tearDown: - self.tearDown() - # try get log - if not self.options.help: - #_stderr.write("Pull log... ") - hostlogpath = os.path.join(workingDir, logfile) - output = Popen(self.adb + ["pull", andoidcwd + logfile, hostlogpath], stdout=_stdout, stderr=PIPE).wait() - if output != 0: - print >> _stderr, "adb finishes unexpectedly with error code", output - return - #rm log - Popen(self.adb + ["shell", "rm " + andoidcwd + logfile], stdout=PIPE, stderr=PIPE).wait() - - # clean temporary files - Popen(self.adb + ["shell", "rm " + tempdir + "__opencv_temp.*"], stdout=PIPE, stderr=PIPE).wait() - except OSError: - pass - if os.path.isfile(hostlogpath): - return hostlogpath - return None - elif path == "java": - cmd = [self.ant_executable, - "-Dopencv.build.type=" - + (self.options.configuration if self.options.configuration else self.build_type), - "buildAndTest"] - - print >> _stderr, "Run command:", " ".join(cmd) - try: - errorCode = Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = self.java_test_binary_dir + "/.build").wait() - except: - print "Unexpected error:", sys.exc_info()[0] - - return None - else: - cmd = [exe] - if self.options.help: - cmd.append("--help") - else: - cmd.extend(args) - - orig_temp_path = os.environ.get('OPENCV_TEMP_PATH') - temp_path = tempfile.mkdtemp(prefix="__opencv_temp.", dir=orig_temp_path or None) - os.environ['OPENCV_TEMP_PATH'] = temp_path - - print >> _stderr, "Run command:", " ".join(cmd) - try: - errorCode = Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait() - except: - print "Unexpected error:", sys.exc_info()[0] - - # clean temporary files - if orig_temp_path: - os.environ['OPENCV_TEMP_PATH'] = orig_temp_path - else: - del os.environ['OPENCV_TEMP_PATH'] - - try: - shutil.rmtree(temp_path) - pass - except: - 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 not self.isRunnable(): - print >> _stderr, "Error:", self.error - if self.error: - return [] - if self.adb and self.targetos == "android": - print "adb command:", " ".join(self.adb) - 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 - -def getRunArgs(args): - 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 - return run_args - -if hostos == "nt": - def moveTests(instance, destination): - src = os.path.dirname(instance.tests_dir) - # new binaries path - newBinPath = os.path.join(destination, "bin") - - try: - # copy binaries and CMakeCache.txt to the specified destination - shutil.copytree(src, newBinPath) - shutil.copy(os.path.join(instance.path, "CMakeCache.txt"), os.path.join(destination, "CMakeCache.txt")) - except Exception, e: - print "Copying error occurred:", str(e) - exit(e.errno) - - # pattern of CMakeCache.txt string to be replaced - replacePattern = re.compile("EXECUTABLE_OUTPUT_PATH:PATH=(.+)") - - with open(os.path.join(destination, "CMakeCache.txt"), "r") as cachefile: - try: - cachedata = cachefile.read() - if hostos == 'nt': - # fix path slashes on nt systems - newBinPath = re.sub(r"\\", r"/", newBinPath) - # replace old binaries path in CMakeCache.txt - cachedata = re.sub(re.search(replacePattern, cachedata).group(1), newBinPath, cachedata) - except Exception, e: - print "Reading error occurred:", str(e) - exit(e.errno) - - with open(os.path.join(destination, "CMakeCache.txt"), "w") as cachefile: - try: - cachefile.write(cachedata) - except Exception, e: - print "Writing error occurred:", str(e) - exit(e.errno) - exit() +import os, sys +import argparse +import logging +from run_utils import Err, CMakeCache, log, execute +from run_suite import TestSuite +from run_android import AndroidTestSuite + +epilog = ''' +NOTE: +Additional options starting with "--gtest_" and "--perf_" will be passed directly to the test executables. +''' 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(usage="run.py [options] [build_path]", description="Note: build_path is required if running not from CMake build directory") - parser.add_option("-t", "--tests", dest="tests", help="comma-separated list of modules to test", metavar="SUITS", default="") - if hostos == "nt": - parser.add_option("-m", "--move_tests", dest="move", help="location to move current tests build", metavar="PATH", default="") - parser.add_option("-w", "--cwd", dest="cwd", help="working directory for tests", metavar="PATH", default=".") - parser.add_option("-a", "--accuracy", dest="accuracy", help="look for accuracy tests instead of performance tests", action="store_true", default=False) - parser.add_option("-l", "--longname", dest="useLongNames", action="store_true", help="generate log files with long names", default=False) - 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/") - parser.add_option("", "--android_env", dest="android_env_array", help="Environment variable for Android run (NAME=VALUE)", action='append') - parser.add_option("", "--android_propagate_opencv_env", dest="android_propagate_opencv_env", help="Propagate OPENCV* environment variables for Android run", action="store_true", default=False) - parser.add_option("", "--configuration", dest="configuration", help="force Debug or Release configuration", metavar="CFG", default="") - parser.add_option("", "--serial", dest="adb_serial", help="Android: directs command to the USB device or emulator with the given serial number", metavar="serial number", default="") - parser.add_option("", "--package", dest="junit_package", help="Android: run jUnit tests for specified package", metavar="package", default="") - parser.add_option("", "--help-tests", dest="help", help="Show help for test executable", action="store_true", default=False) - parser.add_option("", "--check", dest="check", help="Shortcut for '--perf_min_samples=1 --perf_force_samples=1'", action="store_true", default=False) - parser.add_option("", "--list", dest="list", help="List available tests", action="store_true", default=False) + # log.basicConfig(format='[%(levelname)s] %(message)s', level = log.DEBUG) + # log.basicConfig(format='[%(levelname)s] %(message)s', level = log.INFO) - (options, args) = parser.parse_args(argv) + parser = argparse.ArgumentParser( + description='OpenCV test runner script', + epilog=epilog, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("build_path", nargs = "*", default = ["."], help="Path to build directory (should contain CMakeCache.txt, default is current) or to directory with tests (all platform checks will be disabled in this case)") + parser.add_argument("-t", "--tests", metavar="MODULES", default="", help="Comma-separated list of modules to test (example: -t core,imgproc,java)") + parser.add_argument("-b", "--blacklist", metavar="MODULES", default="", help="Comma-separated list of modules to exclude from test (example: -b java)") + parser.add_argument("-a", "--accuracy", action="store_true", default=False, help="Look for accuracy tests instead of performance tests") + parser.add_argument("--check", action="store_true", default=False, help="Shortcut for '--perf_min_samples=1 --perf_force_samples=1'") + parser.add_argument("-w", "--cwd", metavar="PATH", default=".", help="Working directory for tests (default is current)") + parser.add_argument("-l", "--longname", action="store_true", default=False, help="Generate log files with long names") + parser.add_argument("--list", action="store_true", default=False, help="List available tests (executables)") + parser.add_argument("--list_short", action="store_true", default=False, help="List available tests (aliases)") + parser.add_argument("--list_short_main", action="store_true", default=False, help="List available tests (main repository, aliases)") + parser.add_argument("--configuration", metavar="CFG", default="", help="Visual Studio: force Debug or Release configuration") + parser.add_argument("-n", "--dry_run", action="store_true", help="Do not run the tests") + parser.add_argument("-v", "--verbose", action="store_true", default=False, help="Print more debug information") - if options.accuracy: - options.mode = "test" - else: - options.mode = "perf" + # Valgrind + parser.add_argument("--valgrind", action="store_true", default=False, help="Run C++ tests in valgrind") + parser.add_argument("--valgrind_supp", metavar="FILE", help="Path to valgrind suppression file (example: --valgrind_supp opencv/platforms/scripts/valgrind.supp)") + parser.add_argument("--valgrind_opt", metavar="OPT", action="append", default=[], help="Add command line option to valgrind (example: --valgrind_opt=--leak-check=full)") - run_args = getRunArgs(args[1:] or ['.']) + # Android + parser.add_argument("--android", action="store_true", default=False, help="Android: force all tests to run on device") + parser.add_argument("--android_sdk", metavar="PATH", help="Android: path to SDK to use adb and aapt tools") + parser.add_argument("--android_test_data_path", metavar="PATH", default="/sdcard/opencv_testdata/", help="Android: path to testdata on device") + parser.add_argument("--android_env", action='append', help="Android: add environment variable (NAME=VALUE)") + parser.add_argument("--android_propagate_opencv_env", action="store_true", default=False, help="Android: propagate OPENCV* environment variables") + parser.add_argument("--serial", metavar="serial number", default="", help="Android: directs command to the USB device or emulator with the given serial number") + parser.add_argument("--package", metavar="package", default="", help="Android: run jUnit tests for specified package") - if len(run_args) == 0: - print >> sys.stderr, "Usage:", os.path.basename(sys.argv[0]), "[options] [build_path]" - print >> sys.stderr, "Please specify build_path or run script from CMake build directory" + args, other_args = parser.parse_known_args() + + log.setLevel(logging.DEBUG if args.verbose else logging.INFO) + + test_args = [a for a in other_args if a.startswith("--perf_") or a.startswith("--gtest_")] + bad_args = [a for a in other_args if a not in test_args] + if len(bad_args) > 0: + log.error("Error: Bad arguments: %s", bad_args) exit(1) - options.android_env = {} - if options.android_env_array: - for entry in options.android_env_array: - k, v = entry.split("=", 1) - options.android_env[k] = v + args.mode = "test" if args.accuracy else "perf" - tests = [s.strip() for s in options.tests.split(",") if s] + android_env = [] + if args.android_env: + android_env.extend([entry.split("=", 1) for entry in args.android_env]) + if args.android_propagate_opencv_env: + android_env.extend([entry for entry in os.environ.items() if entry[0].startswith('OPENCV')]) + android_env = dict(android_env) + if args.android_test_data_path: + android_env['OPENCV_TEST_DATA_PATH'] = args.android_test_data_path - if len(tests) != 1 or len(run_args) != 1: - # remove --gtest_output from params + if args.valgrind: + try: + ver = execute(["valgrind", "--version"], silent=True) + log.debug("Using %s", ver) + except OSError as e: + log.error("Failed to run valgrind: %s", e) + exit(1) + + if len(args.build_path) != 1: test_args = [a for a in test_args if not a.startswith("--gtest_output=")] - if options.check: + if args.check: if not [a for a in test_args if a.startswith("--perf_min_samples=")] : test_args.extend(["--perf_min_samples=1"]) if not [a for a in test_args if a.startswith("--perf_force_samples=")] : @@ -942,26 +89,46 @@ if __name__ == "__main__": if not [a for a in test_args if a.startswith("--perf_verify_sanity")] : test_args.extend(["--perf_verify_sanity"]) + ret = 0 logs = [] - test_list = [] - for path in run_args: - suite = TestSuite(options, path) + for path in args.build_path: + try: + if not os.path.isdir(path): + raise Err("Not a directory (should contain CMakeCache.txt ot test executables)") + cache = CMakeCache() + fname = os.path.join(path, "CMakeCache.txt") - if hostos == "nt": - if(options.move): - moveTests(suite, options.move) - #print vars(suite),"\n" - if options.list: - test_list.extend(suite.tests) - else: - logs.extend(suite.runTests(tests, sys.stdout, sys.stderr, options.cwd, test_args)) + if os.path.isfile(fname): + log.debug("Reading cmake cache file: %s", fname) + cache.read(path, fname, args.configuration) + else: + log.debug("Assuming folder contains tests: %s", path) + cache.setDummy(path) - if options.list: - print os.linesep.join(test_list) or "No tests found" + if args.android or cache.getOS() == "android": + log.debug("Creating Android test runner") + suite = AndroidTestSuite(args, cache, android_env) + else: + log.debug("Creating native test runner") + suite = TestSuite(args, cache) + + if args.list or args.list_short or args.list_short_main: + suite.listTests(args.list_short or args.list_short_main, args.list_short_main) + else: + log.debug("Running tests in '%s', working dir: '%s'", path, args.cwd) + def parseTests(s): + return [o.strip() for o in s.split(",") if o] + l, r = suite.runTests(parseTests(args.tests), parseTests(args.blacklist), args.cwd, test_args) + logs.extend(l) + if r != 0: + ret = r + except Err as e: + log.error("ERROR: test path '%s' ==> %s", path, e.msg) + ret = -1 if logs: - print >> sys.stderr, "Collected: ", " ".join(logs) + log.warning("Collected: %s", ", ".join(logs)) - if errorCode != 0: - print "Error code: ", errorCode, (" (0x%x)" % (errorCode & 0xffffffff)) - exit(errorCode) + if ret != 0: + log.error("ERROR: some tests have failed") + exit(ret) diff --git a/modules/ts/misc/run_android.py b/modules/ts/misc/run_android.py new file mode 100644 index 000000000..d03721774 --- /dev/null +++ b/modules/ts/misc/run_android.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python + +import sys +from run_utils import * +from run_suite import TestSuite + +def exe(program): + return program + ".exe" if hostos == 'nt' else program + +class ApkInfo: + def __init__(self): + self.pkg_name = None + self.pkg_target = None + self.pkg_runner = None + + def forcePackage(self, package): + if package: + if package.startswith("."): + self.pkg_target += package + else: + self.pkg_target = package + +#============================================================================== + +class Tool: + def __init__(self): + self.cmd = [] + + def run(self, args = [], silent = False): + cmd = self.cmd[:] + cmd.extend(args) + return execute(self.cmd + args, silent) + + +#============================================================================== + +class Adb(Tool): + def __init__(self, sdk_dir): + Tool.__init__(self) + exe_path = os.path.join(sdk_dir, exe("platform-tools/adb")) + if not os.path.isfile(exe_path) or not os.access(exe_path, os.X_OK): + exe_path = None + # fix adb tool location + if not exe_path: + exe_path = getRunningProcessExePathByName("adb") + if not exe_path: + exe_path = "adb" + self.cmd = [exe_path] + self.cpuinfo = "" + + def init(self, serial): + # remember current device serial. Needed if another device is connected while this script runs + if not serial: + serial = self.detectSerial() + if serial: + self.cmd.extend(["-s", serial]) + # read device cpuinfo + self.cpuinfo = self.run(["shell", "cat /proc/cpuinfo"], silent = True) + if not self.cpuinfo: + raise Err("Can not get cpuinfo from Android device") + + def detectSerial(self): + adb_res = self.run(["devices"], silent = True) + # assume here that device name may consists of any characters except newline + connected_devices = re.findall(r"^[^\n]+[ \t]+device\r?$", adb_res, re.MULTILINE) + if not connected_devices: + raise Err("Can not find Android device") + elif len(connected_devices) != 1: + raise Err("Too many (%s) devices are connected. Please specify single device using --serial option:\n\n%s", len(connected_devices), adb_res) + else: + return connected_devices[0].split("\t")[0] + + def getOSIdentifier(self): + return "Android" + self.run(["shell", "getprop ro.build.version.release"], silent = True).strip() + + def getHardware(self): + hw = re.search(r"^Hardware[ \t]*:[ \t]*(.*?)$", self.cpuinfo, re.MULTILINE) + if hw: + return hw.group(1).strip() + + def checkArmHardware(self, expected_abi): + if expected_abi and "armeabi-v7a" in expected_abi: + if "ARMv7" not in self.cpuinfo: + raise Err("Android device does not support ARMv7 commands, but tests are built for armeabi-v7a") + if "NEON" in expected_abi and "neon" not in self.cpuinfo: + raise Err("Android device has no NEON, but tests are built for %s", expected_abi) + + +#============================================================================== + +class Aapt(Tool): + def __init__(self, sdk_dir): + Tool.__init__(self) + aapt_fn = exe("aapt") + aapt = None + for r, ds, fs in os.walk( os.path.join(sdk_dir, 'build-tools') ): + if aapt_fn in fs: + aapt = os.path.join(r, aapt_fn) + break + if not aapt: + raise Err("Can not find aapt tool: %s", aapt_fn) + self.cmd = [aapt] + + def dump(self, exe): + res = ApkInfo() + output = self.run(["dump", "xmltree", exe, "AndroidManifest.xml"], silent = True) + if not output: + raise Err("Can not dump manifest from %s", exe) + tags = re.split(r"[ ]+E: ", output) + # get package name + manifest_tag = [t for t in tags if t.startswith("manifest ")] + if not manifest_tag: + raise Err("Can not read package name from: %s", exe) + res.pkg_name = re.search(r"^[ ]+A: package=\"(?P.*?)\" \(Raw: \"(?P=pkg)\"\)\r?$", manifest_tag[0], flags=re.MULTILINE).group("pkg") + # get test instrumentation info + instrumentation_tag = [t for t in tags if t.startswith("instrumentation ")] + if not instrumentation_tag: + raise Err("Can not find instrumentation detials in: %s", exe) + res.pkg_runner = re.search(r"^[ ]+A: android:name\(0x[0-9a-f]{8}\)=\"(?P.*?)\" \(Raw: \"(?P=runner)\"\)\r?$", instrumentation_tag[0], flags=re.MULTILINE).group("runner") + res.pkg_target = re.search(r"^[ ]+A: android:targetPackage\(0x[0-9a-f]{8}\)=\"(?P.*?)\" \(Raw: \"(?P=pkg)\"\)\r?$", instrumentation_tag[0], flags=re.MULTILINE).group("pkg") + if not res.pkg_name or not res.pkg_runner or not res.pkg_target: + raise Err("Can not find instrumentation detials in: %s", exe) + return res + +#=================================================================================================== + +class AndroidTestSuite(TestSuite): + def __init__(self, options, cache, android_env = {}): + TestSuite.__init__(self, options, cache) + sdk_dir = options.android_sdk or os.environ.get("ANDROID_SDK", False) or os.path.dirname(os.path.dirname(self.cache.android_executable)) + log.debug("Detecting Android tools in directory: %s", sdk_dir) + self.adb = Adb(sdk_dir) + self.aapt = Aapt(sdk_dir) + self.env = android_env + + def isTest(self, fullpath): + if os.path.isfile(fullpath): + if fullpath.endswith(".apk") or os.access(fullpath, os.X_OK): + return True + return False + + def getOS(self): + return self.adb.getOSIdentifier() + + def getHardware(self): + return [self.adb.getHardware()] + + def checkPrerequisites(self): + self.adb.init(self.options.serial) + self.adb.checkArmHardware(self.cache.android_abi) + + def runTest(self, path, logfile, workingDir, args = []): + args = args[:] + exe = os.path.abspath(path) + + if exe.endswith(".apk"): + info = self.aapt.dump(exe) + if not info: + raise Err("Can not read info from test package: %s", exe) + info.forcePackage(self.options.package) + self.adb.run(["uninstall", info.pkg_name]) + + output = self.adb.run(["install", exe], silent = True) + if not (output and "Success" in output): + raise Err("Can not install package: %s", exe) + + params = ["-e package %s" % info.pkg_target] + ret = self.adb.run(["shell", "am instrument -w %s %s/%s" % (" ".join(params), info.pkg_name, info.pkg_runner)]) + return None, ret + else: + device_dir = getpass.getuser().replace(" ","") + "_" + self.options.mode +"/" + if isColorEnabled(args): + args.append("--gtest_color=yes") + tempdir = "/data/local/tmp/" + android_dir = tempdir + device_dir + exename = os.path.basename(exe) + android_exe = android_dir + exename + self.adb.run(["push", exe, android_exe]) + self.adb.run(["shell", "chmod 777 " + android_exe]) + env_pieces = ["export %s=%s" % (a,b) for a,b in self.env.items()] + pieces = ["cd %s" % android_dir, "./%s %s" % (exename, " ".join(args))] + log.warning("Run: %s" % " && ".join(pieces)) + ret = self.adb.run(["shell", " && ".join(env_pieces + pieces)]) + # try get log + hostlogpath = os.path.join(workingDir, logfile) + self.adb.run(["pull", android_dir + logfile, hostlogpath]) + # cleanup + self.adb.run(["shell", "rm " + android_dir + logfile]) + self.adb.run(["shell", "rm " + tempdir + "__opencv_temp.*"], silent = True) + if os.path.isfile(hostlogpath): + return hostlogpath, ret + return None, ret + +#=================================================================================================== + +if __name__ == "__main__": + log.error("This is utility file, please execute run.py script") diff --git a/modules/ts/misc/run_suite.py b/modules/ts/misc/run_suite.py new file mode 100644 index 000000000..c3d715e16 --- /dev/null +++ b/modules/ts/misc/run_suite.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python + +import datetime +from run_utils import * + +class TestSuite(object): + def __init__(self, options, cache): + self.options = options + self.cache = cache + self.nameprefix = "opencv_" + self.options.mode + "_" + self.tests = self.cache.gatherTests(self.nameprefix + "*", self.isTest) + + def getOS(self): + return getPlatformVersion() or self.cache.getOS() + + def getHardware(self): + res = [] + if self.cache.getArch() in ["x86", "x64"] and self.cache.withCuda(): + res.append("CUDA") + return res + + def getLogName(self, app, timestamp): + app = self.getAlias(app) + rev = self.cache.getGitVersion() + if isinstance(timestamp, datetime.datetime): + timestamp = timestamp.strftime("%Y%m%d-%H%M%S") + if self.options.longname: + small_pieces = [self.getOS(), self.cache.getArch()] + self.cache.getDependencies() + self.getHardware() + [self.cache.getSIMDFeatures()] + big_pieces = [app, str(rev), timestamp, "_".join([p for p in small_pieces if p])] + l = "__".join(big_pieces) + else: + pieces = [app, self.cache.getOS(), self.cache.getArch()] + self.getHardware() + [rev, timestamp] + lname = "_".join([p for p in pieces if p]) + lname = re.sub(r'[\(\)\[\]\s,]', '_', lname) + l = re.sub(r'_+', '_', lname) + return l + ".xml" + + def listTests(self, short = False, main = False): + if len(self.tests) == 0: + raise Err("No tests found") + for t in self.tests: + if short: + t = self.getAlias(t) + if not main or self.cache.isMainModule(t): + log.info("%s", t) + + def getAlias(self, fname): + return sorted(self.getAliases(fname), key = len)[0] + + def getAliases(self, fname): + # input is full path ('/home/.../bin/opencv_test_core') or 'java' + res = [fname] + fname = os.path.basename(fname) + res.append(fname) # filename (opencv_test_core.exe) + noext = re.sub(r"\.(exe|apk)$", '', fname) + res.append(noext) # filename w/o extension (opencv_test_core) + nopref = None + if fname.startswith(self.nameprefix): + nopref = fname[len(self.nameprefix):] + res.append(nopref) # filename w/o prefix (core) + if noext.startswith(self.nameprefix): + res.append(noext[len(self.nameprefix):]) + if self.options.configuration == "Debug": + res.append(re.sub(r"d$", '', noext)) # MSVC debug config, remove 'd' suffix + if nopref: + res.append(re.sub(r"d$", '', nopref)) # MSVC debug config, remove 'd' suffix + return set(res) + + def getTest(self, name): + # return stored test name by provided alias + for t in self.tests: + if name in self.getAliases(t): + return t + raise Err("Can not find test: %s", name) + + def getTestList(self, white, black): + res = [t for t in white or self.tests if self.getAlias(t) not in black] + if len(res) == 0: + raise Err("No tests found") + return set(res) + + def isTest(self, fullpath): + if fullpath == "java": + return True + if not os.path.isfile(fullpath): + return False + if self.cache.getOS() == "nt" and not fullpath.endswith(".exe"): + return False + return os.access(fullpath, os.X_OK) + + def wrapInValgrind(self, cmd = []): + if self.options.valgrind: + res = ['valgrind'] + if self.options.valgrind_supp: + res.append("--suppressions=%s" % self.options.valgrind_supp) + res.extend(self.options.valgrind_opt) + return res + cmd + return cmd + + def runTest(self, path, logfile, workingDir, args = []): + args = args[:] + exe = os.path.abspath(path) + if path == "java": + cfg = self.cache.build_type + if self.options.configuration: + cfg = self.options.configuration + cmd = [self.cache.ant_executable, "-Dopencv.build.type=%s" % cfg, "buildAndTest"] + ret = execute(cmd, cwd = self.cache.java_test_binary_dir + "/.build") + return None, ret + else: + if isColorEnabled(args): + args.append("--gtest_color=yes") + cmd = self.wrapInValgrind([exe] + args) + tempDir = TempEnvDir('OPENCV_TEMP_PATH', "__opencv_temp.") + tempDir.init() + log.warning("Run: %s" % " ".join(cmd)) + ret = execute(cmd, cwd = workingDir) + tempDir.clean() + hostlogpath = os.path.join(workingDir, logfile) + if os.path.isfile(hostlogpath): + return hostlogpath, ret + return None, ret + + def checkPrerequisites(self): + if self.cache.getArch() == "x64" and hostmachine == "x86": + raise Err("Target architecture is incompatible with current platform") + + def runTests(self, tests, black, workingDir, args = []): + self.checkPrerequisites() + args = args[:] + logs = [] + test_list = self.getTestList(tests, black) + date = datetime.datetime.now() + if len(test_list) != 1: + args = [a for a in args if not a.startswith("--gtest_output=")] + ret = 0 + for test in test_list: + more_args = [] + exe = self.getTest(test) + + userlog = [a for a in args if a.startswith("--gtest_output=")] + if len(userlog) == 0: + logname = self.getLogName(exe, date) + more_args.append("--gtest_output=xml:" + logname) + else: + logname = userlog[0][userlog[0].find(":")+1:] + + log.debug("Running the test: %s (%s) ==> %s in %s", exe, args + more_args, logname, workingDir) + if self.options.dry_run: + logfile, r = None, 0 + else: + logfile, r = self.runTest(exe, logname, workingDir, args + more_args) + log.debug("Test returned: %s ==> %s", r, logfile) + + if r != 0: + ret = r + if logfile: + logs.append(os.path.relpath(logfile, workingDir)) + return logs, ret + +#=================================================================================================== + +if __name__ == "__main__": + log.error("This is utility file, please execute run.py script") diff --git a/modules/ts/misc/run_utils.py b/modules/ts/misc/run_utils.py new file mode 100644 index 000000000..dca951d17 --- /dev/null +++ b/modules/ts/misc/run_utils.py @@ -0,0 +1,447 @@ +#!/usr/bin/env python + +import sys, os, platform, re, tempfile, glob, getpass, logging +from subprocess import check_call, check_output, CalledProcessError, STDOUT + +hostos = os.name # 'nt', 'posix' +hostmachine = platform.machine() # 'x86', 'AMD64', 'x86_64' + +def initLogger(): + l = logging.getLogger("run.py") + l.setLevel(logging.DEBUG) + ch = logging.StreamHandler(sys.stderr) + ch.setFormatter(logging.Formatter("%(message)s")) + l.addHandler(ch) + return l + +log = initLogger() + +#=================================================================================================== + +class Err(Exception): + def __init__(self, msg, *args): + self.msg = msg % args + +def execute(cmd, silent = False, cwd = "."): + try: + log.debug("Run: %s", cmd) + if silent: + return check_output(cmd, stderr = STDOUT, cwd = cwd).decode("latin-1") + else: + return check_call(cmd, cwd = cwd) + except CalledProcessError as e: + if silent: + log.debug("Process returned: %d", e.returncode) + return e.output.decode("latin-1") + else: + log.error("Process returned: %d", e.returncode) + return e.returncode + +def isColorEnabled(args): + usercolor = [a for a in args if a.startswith("--gtest_color=")] + return len(usercolor) == 0 and sys.stdout.isatty() and hostos != "nt" + +#=================================================================================================== + +def getPlatformVersion(): + mv = platform.mac_ver() + if mv[0]: + return "Darwin" + mv[0] + else: + wv = platform.win32_ver() + if wv[0]: + return "Windows" + wv[0] + else: + lv = platform.linux_distribution() + if lv[0]: + return lv[0] + lv[1] + return None + +def readGitVersion(git, path): + if not path or not git or not os.path.isdir(os.path.join(path, ".git")): + return None + try: + output = execute([git, "-C", path, "rev-parse", "--short", "HEAD"], silent = True) + return output.strip() + except OSError: + log.warning("Git version read failed") + return None + +SIMD_DETECTION_PROGRAM=""" +#if __SSE5__ +# error SSE5 +#endif +#if __AVX2__ +# error AVX2 +#endif +#if __AVX__ +# error AVX +#endif +#if __SSE4_2__ +# error SSE4.2 +#endif +#if __SSE4_1__ +# error SSE4.1 +#endif +#if __SSSE3__ +# error SSSE3 +#endif +#if __SSE3__ +# error SSE3 +#endif +#if __AES__ +# error AES +#endif +#if __SSE2__ +# error SSE2 +#endif +#if __SSE__ +# error SSE +#endif +#if __3dNOW__ +# error 3dNOW +#endif +#if __MMX__ +# error MMX +#endif +#if __ARM_NEON__ +# error NEON +#endif +#error NOSIMD +""" + +def testSIMD(compiler, cxx_flags, compiler_arg = None): + if not compiler: + return None + compiler_output = "" + try: + _, tmpfile = tempfile.mkstemp(suffix=".cpp", text = True) + with open(tmpfile, "w+") as fd: + fd.write(SIMD_DETECTION_PROGRAM) + options = [compiler] + if compiler_arg: + options.append(compiler_arg) + + prev_option = None + for opt in " ".join(cxx_flags).split(): + if opt.count('\"') % 2 == 1: + if prev_option is None: + prev_option = opt + else: + options.append(prev_option + " " + opt) + prev_option = None + elif prev_option is None: + options.append(opt) + else: + prev_option = prev_option + " " + opt + options.append(tmpfile) + compiler_output = execute(options, silent = True) + os.remove(tmpfile) + m = re.search("#error\W+(\w+)", compiler_output) + if m: + return m.group(1) + except OSError: + pass + log.debug("SIMD detection failed") + return None + +#============================================================================== + +parse_patterns = ( + {'name': "cmake_home", 'default': None, 'pattern': re.compile(r"^CMAKE_HOME_DIRECTORY:INTERNAL=(.+)$")}, + {'name': "opencv_home", 'default': None, 'pattern': re.compile(r"^OpenCV_SOURCE_DIR:STATIC=(.+)$")}, + {'name': "opencv_build", 'default': None, 'pattern': re.compile(r"^OpenCV_BINARY_DIR:STATIC=(.+)$")}, + {'name': "tests_dir", 'default': None, 'pattern': re.compile(r"^EXECUTABLE_OUTPUT_PATH:PATH=(.+)$")}, + {'name': "build_type", 'default': "Release", 'pattern': re.compile(r"^CMAKE_BUILD_TYPE:STRING=(.*)$")}, + {'name': "git_executable", 'default': None, 'pattern': re.compile(r"^GIT_EXECUTABLE:FILEPATH=(.*)$")}, + {'name': "cxx_flags", 'default': "", 'pattern': re.compile(r"^CMAKE_CXX_FLAGS:STRING=(.*)$")}, + {'name': "cxx_flags_debug", 'default': "", 'pattern': re.compile(r"^CMAKE_CXX_FLAGS_DEBUG:STRING=(.*)$")}, + {'name': "cxx_flags_release", 'default': "", 'pattern': re.compile(r"^CMAKE_CXX_FLAGS_RELEASE:STRING=(.*)$")}, + {'name': "opencv_cxx_flags", 'default': "", 'pattern': re.compile(r"^OPENCV_EXTRA_C_FLAGS:INTERNAL=(.*)$")}, + {'name': "cxx_flags_android", 'default': None, 'pattern': re.compile(r"^ANDROID_CXX_FLAGS:INTERNAL=(.*)$")}, + {'name': "android_abi", 'default': None, 'pattern': re.compile(r"^ANDROID_ABI:STRING=(.*)$")}, + {'name': "android_executable", 'default': None, 'pattern': re.compile(r"^ANDROID_EXECUTABLE:FILEPATH=(.*android.*)$")}, + {'name': "ant_executable", 'default': None, 'pattern': re.compile(r"^ANT_EXECUTABLE:FILEPATH=(.*ant.*)$")}, + {'name': "java_test_binary_dir", 'default': None, 'pattern': re.compile(r"^opencv_test_java_BINARY_DIR:STATIC=(.*)$")}, + {'name': "is_x64", 'default': "OFF", 'pattern': re.compile(r"^CUDA_64_BIT_DEVICE_CODE:BOOL=(ON)$")},#ugly( + {'name': "cmake_generator", 'default': None, 'pattern': re.compile(r"^CMAKE_GENERATOR:INTERNAL=(.+)$")}, + {'name': "cxx_compiler", 'default': None, 'pattern': re.compile(r"^CMAKE_CXX_COMPILER:\w*PATH=(.+)$")}, + {'name': "cxx_compiler_arg1", 'default': None, 'pattern': re.compile(r"^CMAKE_CXX_COMPILER_ARG1:[A-Z]+=(.+)$")}, + {'name': "with_cuda", 'default': "OFF", 'pattern': re.compile(r"^WITH_CUDA:BOOL=(ON)$")}, + {'name': "cuda_library", 'default': None, 'pattern': re.compile(r"^CUDA_CUDA_LIBRARY:FILEPATH=(.+)$")}, + {'name': "cuda_version", 'default': None, 'pattern': re.compile(r"^CUDA_VERSION:STRING=(.+)$")}, + {'name': "core_dependencies", 'default': None, 'pattern': re.compile(r"^opencv_core_LIB_DEPENDS:STATIC=(.+)$")}, +) + +class CMakeCache: + def __init__(self): + self.setDefaultAttrs() + self.cmake_home_vcver = None + self.opencv_home_vcver = None + self.featuresSIMD = None + self.main_modules = [] + + def setDummy(self, path): + self.tests_dir = os.path.normpath(path) + + def read(self, path, fname, cfg): + rx = re.compile(r'^opencv_(\w+)_SOURCE_DIR:STATIC=(.*)$') + module_paths = {} # name -> path + with open(fname, "rt") as cachefile: + 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) + # log.debug("cache value: %s = %s", p["name"], value) + + match = rx.search(ll) + if match: + module_paths[match.group(1)] = match.group(2) + + if not self.tests_dir: + self.tests_dir = path + else: + rel = os.path.relpath(self.tests_dir, self.opencv_build) + self.tests_dir = os.path.join(path, rel) + self.tests_dir = os.path.normpath(self.tests_dir) + + # fix VS test binary path (add Debug or Release) + if "Visual Studio" in self.cmake_generator: + if cfg: + self.tests_dir = os.path.join(self.tests_dir, self.options.configuration) + else: + self.tests_dir = os.path.join(self.tests_dir, self.build_type) + + self.cmake_home_vcver = readGitVersion(self.git_executable, self.cmake_home) + if self.opencv_home == self.cmake_home: + self.opencv_home_vcver = self.cmake_home_vcver + else: + self.opencv_home_vcver = readGitVersion(self.git_executable, self.opencv_home) + + for module,path in module_paths.items(): + rel = os.path.relpath(path, self.opencv_home) + if not ".." in rel: + self.main_modules.append(module) + + self.flags = [ + self.cxx_flags_android, + self.cxx_flags, + self.cxx_flags_release, + self.opencv_cxx_flags, + self.cxx_flags_release] + self.flags = [f for f in self.flags if f] + self.featuresSIMD = testSIMD(self.cxx_compiler, self.flags, self.cxx_compiler_arg1) + + def setDefaultAttrs(self): + for p in parse_patterns: + setattr(self, p["name"], p["default"]) + + def gatherTests(self, mask, isGood = None): + if self.tests_dir and os.path.isdir(self.tests_dir): + d = os.path.abspath(self.tests_dir) + files = glob.glob(os.path.join(d, mask)) + if not self.getOS() == "android" and self.withJava(): + files.append("java") + return [f for f in files if isGood(f)] + return [] + + def isMainModule(self, name): + return name in self.main_modules + + def withCuda(self): + return self.cuda_version and self.with_cuda == "ON" and self.cuda_library and not self.cuda_library.endswith("-NOTFOUND") + + def withJava(self): + return self.ant_executable and self.java_test_binary_dir + + def getGitVersion(self): + if self.cmake_home_vcver: + if self.cmake_home_vcver == self.opencv_home_vcver: + rev = self.cmake_home_vcver + elif self.opencv_home_vcver: + rev = self.cmake_home_vcver + "-" + self.opencv_home_vcver + else: + rev = self.cmake_home_vcver + else: + rev = None + if rev: + rev = rev.replace(":","to") + else: + rev = "" + return rev + + def getTestFullName(self, shortname): + return os.path.join(self.tests_dir, shortname) + + def getSIMDFeatures(self): + return self.featuresSIMD + + def getOS(self): + if self.android_executable: + return "android" + else: + return hostos + + def getArch(self): + arch = "unknown" + if self.getOS() == "android": + if "armeabi-v7a" in self.android_abi: + arch = "armv7a" + elif "armeabi-v6" in self.android_abi: + arch = "armv6" + elif "armeabi" in self.android_abi: + arch = "armv5te" + elif "x86" in self.android_abi: + arch = "x86" + elif "mips" in self.android_abi: + arch = "mips" + else: + arch = "ARM" + elif self.is_x64 and hostmachine in ["AMD64", "x86_64"]: + arch = "x64" + elif hostmachine in ["x86", "AMD64", "x86_64"]: + arch = "x86" + return arch + + def getDependencies(self): + if self.core_dependencies: + candidates = ["tbb", "ippicv", "ipp", "pthreads"] + return [a for a in self.core_dependencies.split(";") if a and a in candidates] + return [] + + +#============================================================================== + +def getRunningProcessExePathByName_win32(name): + from ctypes import windll, POINTER, pointer, Structure, sizeof + from ctypes import c_long , c_int , c_uint , c_char , c_ubyte , c_char_p , c_void_p + + class PROCESSENTRY32(Structure): + _fields_ = [ ( 'dwSize' , c_uint ) , + ( 'cntUsage' , c_uint) , + ( 'th32ProcessID' , c_uint) , + ( 'th32DefaultHeapID' , c_uint) , + ( 'th32ModuleID' , c_uint) , + ( 'cntThreads' , c_uint) , + ( 'th32ParentProcessID' , c_uint) , + ( 'pcPriClassBase' , c_long) , + ( 'dwFlags' , c_uint) , + ( 'szExeFile' , c_char * 260 ) , + ( 'th32MemoryBase' , c_long) , + ( 'th32AccessKey' , c_long ) ] + + class MODULEENTRY32(Structure): + _fields_ = [ ( 'dwSize' , c_long ) , + ( 'th32ModuleID' , c_long ), + ( 'th32ProcessID' , c_long ), + ( 'GlblcntUsage' , c_long ), + ( 'ProccntUsage' , c_long ) , + ( 'modBaseAddr' , c_long ) , + ( 'modBaseSize' , c_long ) , + ( 'hModule' , c_void_p ) , + ( 'szModule' , c_char * 256 ), + ( 'szExePath' , c_char * 260 ) ] + + TH32CS_SNAPPROCESS = 2 + TH32CS_SNAPMODULE = 0x00000008 + + ## CreateToolhelp32Snapshot + CreateToolhelp32Snapshot= windll.kernel32.CreateToolhelp32Snapshot + CreateToolhelp32Snapshot.reltype = c_long + CreateToolhelp32Snapshot.argtypes = [ c_int , c_int ] + ## Process32First + Process32First = windll.kernel32.Process32First + Process32First.argtypes = [ c_void_p , POINTER( PROCESSENTRY32 ) ] + Process32First.rettype = c_int + ## Process32Next + Process32Next = windll.kernel32.Process32Next + Process32Next.argtypes = [ c_void_p , POINTER(PROCESSENTRY32) ] + Process32Next.rettype = c_int + ## CloseHandle + CloseHandle = windll.kernel32.CloseHandle + CloseHandle.argtypes = [ c_void_p ] + CloseHandle.rettype = c_int + ## Module32First + Module32First = windll.kernel32.Module32First + Module32First.argtypes = [ c_void_p , POINTER(MODULEENTRY32) ] + Module32First.rettype = c_int + + hProcessSnap = c_void_p(0) + hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS , 0 ) + + pe32 = PROCESSENTRY32() + pe32.dwSize = sizeof( PROCESSENTRY32 ) + ret = Process32First( hProcessSnap , pointer( pe32 ) ) + path = None + + while ret : + if name + ".exe" == pe32.szExeFile: + hModuleSnap = c_void_p(0) + me32 = MODULEENTRY32() + me32.dwSize = sizeof( MODULEENTRY32 ) + hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, pe32.th32ProcessID ) + + ret = Module32First( hModuleSnap, pointer(me32) ) + path = me32.szExePath + CloseHandle( hModuleSnap ) + if path: + break + ret = Process32Next( hProcessSnap, pointer(pe32) ) + CloseHandle( hProcessSnap ) + return path + + +def getRunningProcessExePathByName_posix(name): + pids= [pid for pid in os.listdir('/proc') if pid.isdigit()] + for pid in pids: + try: + path = os.readlink(os.path.join('/proc', pid, 'exe')) + if path and path.endswith(name): + return path + except: + pass + +def getRunningProcessExePathByName(name): + try: + if hostos == "nt": + return getRunningProcessExePathByName_win32(name) + elif hostos == "posix": + return getRunningProcessExePathByName_posix(name) + else: + return None + except: + return None + + +class TempEnvDir: + def __init__(self, envname, prefix): + self.envname = envname + self.prefix = prefix + self.saved_name = None + self.new_name = None + + def init(self): + self.saved_name = os.environ.get(self.envname) + self.new_name = tempfile.mkdtemp(prefix=self.prefix, dir=self.saved_name or None) + os.environ[self.envname] = self.new_name + + def clean(self): + if self.saved_name: + os.environ[self.envname] = self.saved_name + else: + del os.environ[self.envname] + try: + shutil.rmtree(self.new_name) + except: + pass + +#=================================================================================================== + +if __name__ == "__main__": + log.error("This is utility file, please execute run.py script") diff --git a/platforms/scripts/valgrind.supp b/platforms/scripts/valgrind.supp new file mode 100644 index 000000000..b37ca2017 --- /dev/null +++ b/platforms/scripts/valgrind.supp @@ -0,0 +1,6 @@ +{ + IPP static init + Memcheck:Cond + fun:ippicvGetCpuFeatures + fun:ippicvStaticInit +}