9db1d9ba13
* do not lose auto-selected device while running several tests * reduce output noise * list available devices if unable to auto-select device * fix error message when no devices connected
844 lines
35 KiB
Python
Executable File
844 lines
35 KiB
Python
Executable File
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'
|
|
|
|
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': "cxx_compiler_path", 'default': None, 'pattern': re.compile("^CMAKE_CXX_COMPILER:FILEPATH=(.*)$")},
|
|
{'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': "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': "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 RunInfo(object):
|
|
def setCallback(self, name, callback):
|
|
setattr(self, name, callback)
|
|
|
|
def __init__(self, path, options):
|
|
self.options = options
|
|
self.path = path
|
|
self.error = None
|
|
self.setUp = None
|
|
self.tearDown = None
|
|
self.nameprefix = "opencv_" + options.mode + "_"
|
|
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
|
|
self.tests_dir = os.path.normpath(self.tests_dir)
|
|
# 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'])
|
|
if not os.path.isfile(self.adb) or not os.access(self.adb, os.X_OK):
|
|
self.adb = None
|
|
else:
|
|
self.adb = None
|
|
|
|
# detect target platform
|
|
if self.android_executable or self.android_abi or self.ndk_path:
|
|
self.targetos = "android"
|
|
else:
|
|
self.targetos = hostos
|
|
|
|
if self.targetos == "android":
|
|
# fix adb tool location
|
|
if not self.adb:
|
|
self.adb = getRunningProcessExePathByName("adb")
|
|
if not self.adb:
|
|
self.adb = "adb"
|
|
if options.adb_serial:
|
|
self.adb = [self.adb, "-s", 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 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:
|
|
options.adb_serial = connected_devices[0].split("\t")[0]
|
|
self.adb = self.adb + ["-s", options.adb_serial]
|
|
if self.adb:
|
|
print "adb command:", " ".join(self.adb)
|
|
|
|
if self.adb:
|
|
#construct name for aapt tool
|
|
self.aapt = [os.path.join(os.path.dirname(self.adb[0]), ("aapt","aapt.exe")[hostos == 'nt'])]
|
|
|
|
# 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 options.configuration:
|
|
self.tests_dir = os.path.join(self.tests_dir, 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:
|
|
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":
|
|
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)]
|
|
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_path]
|
|
cxx_flags = self.cxx_flags + " " + self.cxx_flags_release + " " + self.opencv_cxx_flags + " " + self.opencv_cxx_flags_release
|
|
if self.targetos == "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")
|
|
return "%s_%s_%s_%s%s%s.xml" % (app, self.targetos, self.targetarch, hw, rev, tstamp)
|
|
|
|
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 = []):
|
|
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 "running 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, "failed to get manifest info 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, "failed to get manifest info from", exe
|
|
return
|
|
pkg_name = re.search(r"^[ ]+A: package=\"(?P<pkg>.*?)\" \(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<runner>.*?)\" \(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<pkg>.*?)\" \(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 already 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 is not None:
|
|
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 is not None:
|
|
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 is not None:
|
|
self.setUp()
|
|
Popen(self.adb + ["shell", "export OPENCV_TEST_DATA_PATH=" + self.test_data_path + "&& cd " + andoidcwd + "&& ./" + command], stdout=_stdout, stderr=_stderr).wait()
|
|
if self.tearDown is not None:
|
|
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
|
|
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:
|
|
Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait()
|
|
except OSError:
|
|
pass
|
|
|
|
# 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)
|
|
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 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
|
|
|
|
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 __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("-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("", "--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)
|
|
|
|
(options, args) = parser.parse_args(argv)
|
|
|
|
if options.accuracy:
|
|
options.mode = "test"
|
|
else:
|
|
options.mode = "perf"
|
|
|
|
run_args = getRunArgs(args[1:] or ['.'])
|
|
|
|
if len(run_args) == 0:
|
|
print >> sys.stderr, "Usage:\n", os.path.basename(sys.argv[0]), "<build_path>"
|
|
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, options)
|
|
#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)
|