run.py 37.4 KB
Newer Older
1 2
#!/usr/bin/env python

Andrey Kamaev's avatar
Andrey Kamaev committed
3
import sys, os, platform, xml, re, tempfile, glob, datetime, getpass, shutil
4 5 6 7 8 9
from optparse import OptionParser
from subprocess import Popen, PIPE

hostos = os.name # 'nt', 'posix'
hostmachine = platform.machine() # 'x86', 'AMD64', 'x86_64'

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
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
"""

53
parse_patterns = (
54
  {'name': "has_perf_tests",           'default': "OFF",      'pattern': re.compile("^BUILD_PERF_TESTS:BOOL=(ON)$")},
55
  {'name': "has_accuracy_tests",       'default': "OFF",      'pattern': re.compile("^BUILD_TESTS:BOOL=(ON)$")},
56 57 58 59 60
  {'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=(.*)$")},
61
  {'name': "git_executable",           'default': None,       'pattern': re.compile("^GIT_EXECUTABLE:FILEPATH=(.*)$")},
62 63 64 65 66 67 68 69 70 71
  {'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.*)$")},
72 73
  {'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=(.*)$")},
74 75 76
  {'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=(.+)$")},
77
  {'name': "cxx_compiler_arg1",        'default': None,       'pattern': re.compile("^CMAKE_CXX_COMPILER_ARG1:[A-Z]+=(.+)$")},
78 79 80
  {'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=(.+)$")},
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
)

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")
104

105
def getRunningProcessExePathByName_win32(name):
106 107
    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
108

109
    class PROCESSENTRY32(Structure):
110
        _fields_ = [ ( 'dwSize' , c_uint ) ,
111 112 113 114 115 116 117 118
                    ( 'cntUsage' , c_uint) ,
                    ( 'th32ProcessID' , c_uint) ,
                    ( 'th32DefaultHeapID' , c_uint) ,
                    ( 'th32ModuleID' , c_uint) ,
                    ( 'cntThreads' , c_uint) ,
                    ( 'th32ParentProcessID' , c_uint) ,
                    ( 'pcPriClassBase' , c_long) ,
                    ( 'dwFlags' , c_uint) ,
119
                    ( 'szExeFile' , c_char * 260 ) ,
120 121
                    ( 'th32MemoryBase' , c_long) ,
                    ( 'th32AccessKey' , c_long ) ]
122

123
    class MODULEENTRY32(Structure):
124
        _fields_ = [ ( 'dwSize' , c_long ) ,
125 126 127 128 129
                    ( 'th32ModuleID' , c_long ),
                    ( 'th32ProcessID' , c_long ),
                    ( 'GlblcntUsage' , c_long ),
                    ( 'ProccntUsage' , c_long ) ,
                    ( 'modBaseAddr' , c_long ) ,
130
                    ( 'modBaseSize' , c_long ) ,
131 132 133
                    ( 'hModule' , c_void_p ) ,
                    ( 'szModule' , c_char * 256 ),
                    ( 'szExePath' , c_char * 260 ) ]
134

135 136
    TH32CS_SNAPPROCESS = 2
    TH32CS_SNAPMODULE = 0x00000008
137

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
    ## 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
158

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
    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

183 184 185 186 187
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'))
188
            if path and path.endswith(name):
189 190 191 192
                return path
        except:
            pass

193 194 195 196
def getRunningProcessExePathByName(name):
    try:
        if hostos == "nt":
            return getRunningProcessExePathByName_win32(name)
197 198
        elif hostos == "posix":
            return getRunningProcessExePathByName_posix(name)
199 200 201 202
        else:
            return None
    except:
        return None
203

204 205
class TestSuite(object):
    def __init__(self, options, path = None):
206
        self.options = options
207 208
        self.path = path
        self.error = None
209 210
        self.setUp = None
        self.tearDown = None
211 212 213
        self.adb = None
        self.targetos = None
        self.nameprefix = "opencv_" + self.options.mode + "_"
214 215
        for p in parse_patterns:
            setattr(self, p["name"], p["default"])
216

217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
        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):
243 244 245
        # fix empty tests dir
        if not self.tests_dir:
            self.tests_dir = self.path
Andrey Kamaev's avatar
Andrey Kamaev committed
246
        self.tests_dir = os.path.normpath(self.tests_dir)
247 248

        # compute path to adb
249 250
        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'])
251 252
            if not os.path.isfile(self.adb) or not os.access(self.adb, os.X_OK):
                self.adb = None
253 254 255
        else:
            self.adb = None

256 257 258 259
        if self.targetos == "android":
            # fix adb tool location
            if not self.adb:
                self.adb = getRunningProcessExePathByName("adb")
260
            if not self.adb:
261
                self.adb = "adb"
262 263
            if self.options.adb_serial:
                self.adb = [self.adb, "-s", self.options.adb_serial]
264
            else:
265 266 267 268 269 270
                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
271
            if self.adb and not self.options.adb_serial:
272 273 274 275 276
                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:
277 278
                    # 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)
279 280 281 282 283
                    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
284 285
                        self.adb = []
                    else:
286 287
                        self.options.adb_serial = connected_devices[0].split("\t")[0]
                        self.adb = self.adb + ["-s", self.options.adb_serial]
288
            if self.adb:
289
                # construct name for aapt tool
290
                self.aapt = [os.path.join(os.path.dirname(self.adb[0]), ("aapt","aapt.exe")[hostos == 'nt'])]
291 292 293 294 295 296 297 298 299 300
                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
301

302 303
        # fix has_perf_tests param
        self.has_perf_tests = self.has_perf_tests == "ON"
304
        self.has_accuracy_tests = self.has_accuracy_tests == "ON"
305 306 307 308 309 310 311
        # 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:
312 313
            if self.options.configuration:
                self.tests_dir = os.path.join(self.tests_dir, self.options.configuration)
314 315
            else:
                self.tests_dir = os.path.join(self.tests_dir, self.build_type)
316 317 318
        elif not self.is_x64 and self.cxx_compiler:
            #one more attempt to detect x64 compiler
            try:
319 320 321 322
                compiler = [self.cxx_compiler]
                if self.cxx_compiler_arg1:
                    compiler.append(self.cxx_compiler_arg1)
                output = Popen(compiler + ["-v"], stdout=PIPE, stderr=PIPE).communicate()
323 324 325 326 327 328 329
                if not output[0] and "x86_64" in output[1]:
                    self.is_x64 = True
            except OSError:
                pass

        # detect target arch
        if self.targetos == "android":
330
            if "armeabi-v7a" in self.android_abi:
331
                self.targetarch = "armv7a"
332
            elif "armeabi-v6" in self.android_abi:
333
                self.targetarch = "armv6"
334
            elif "armeabi" in self.android_abi:
335
                self.targetarch = "armv5te"
336 337
            elif "x86" in self.android_abi:
                self.targetarch = "x86"
338 339
            elif "mips" in self.android_abi:
                self.targetarch = "mips"
340 341
            else:
                self.targetarch = "ARM"
342 343 344 345 346 347
        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"
348

349 350 351 352 353
        # 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"]
354

355
        self.hardware = None
356

357
        self.cmake_home_vcver = self.getVCVersion(self.cmake_home)
358
        if self.opencv_home == self.cmake_home:
359
            self.opencv_home_vcver = self.cmake_home_vcver
360
        else:
361
            self.opencv_home_vcver = self.getVCVersion(self.opencv_home)
362

363
        self.tests = self.getAvailableTestApps()
364

365
    def getVCVersion(self, root_path):
366 367
        if not root_path:
            return None
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
        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):
387
        if not path:
388 389
            val = None
        elif not self.svnversion_path and hostos == 'nt':
390
            val = self.tryGetSvnVersionWithTortoise(path)
391 392 393 394 395 396 397
        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]:
398
                    val = output[0]
399
                else:
400
                    val = None
401
            except OSError:
402 403 404
                val = None
        if val:
            val = val.replace(" ", "_")
405
        return val
406

407
    def tryGetSvnVersionWithTortoise(self, path):
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
        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()
424
            return version
425
        except:
426
            return None
427 428 429
        finally:
            if dir:
                shutil.rmtree(dir)
430

431 432 433
    def isTest(self, fullpath):
        if not os.path.isfile(fullpath):
            return False
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
434 435
        if self.targetos == "nt" and not fullpath.endswith(".exe"):
            return False
436 437
        if hostos == self.targetos:
            return os.access(fullpath, os.X_OK)
438 439
        if self.targetos == "android" and fullpath.endswith(".apk"):
            return True
440
        return True
441

442 443
    def getAvailableTestApps(self):
        if self.tests_dir and os.path.isdir(self.tests_dir):
444 445
            files = glob.glob(os.path.join(self.tests_dir, self.nameprefix + "*"))
            files = [f for f in files if self.isTest(f)]
446 447
            if self.ant_executable and self.java_test_binary_dir:
                files.append("java")
448 449
            return files
        return []
450

451 452 453
    def getLogName(self, app, timestamp):
        app = os.path.basename(app)
        if app.endswith(".exe"):
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
454 455 456 457
            if app.endswith("d.exe"):
                app = app[:-5]
            else:
                app = app[:-4]
458 459
        if app.startswith(self.nameprefix):
            app = app[len(self.nameprefix):]
460

461 462 463 464 465
        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
466
            else:
467
                rev = self.cmake_home_vcver
468 469 470
        else:
            rev = None
        if rev:
471
            rev = rev.replace(":","to")
472 473
        else:
            rev = ""
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520

        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();
521 522 523
                options = [self.cxx_compiler]
                if self.cxx_compiler_arg1:
                    options.append(self.cxx_compiler_arg1)
524
                cxx_flags = self.cxx_flags + " " + self.cxx_flags_release + " " + self.opencv_cxx_flags + " " + self.opencv_cxx_flags_release
525
                if self.targetos == "android" and self.cxx_flags_android:
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
                    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))
553
        else:
554 555 556 557 558 559 560 561
            if rev:
                rev = rev + "_"
            if self.hardware:
                hw = str(self.hardware).replace(" ", "_") + "_"
            elif self.has_cuda:
                hw = "CUDA_"
            else:
                hw = ""
562
            tstamp = timestamp.strftime("%Y%m%d-%H%M%S")
563
            return "%s_%s_%s_%s%s%s.xml" % (app, self.targetos, self.targetarch, hw, rev, tstamp)
564

565 566 567 568
    def getTest(self, name):
        # full path
        if self.isTest(name):
            return name
569

570 571 572 573
        # name only
        fullname = os.path.join(self.tests_dir, name)
        if self.isTest(fullname):
            return fullname
574

575 576 577 578
        # name without extension
        fullname += ".exe"
        if self.isTest(fullname):
            return fullname
579 580 581 582
        if self.targetos == "android":
            fullname += ".apk"
            if self.isTest(fullname):
                return fullname
583

584 585 586 587 588 589 590
        # 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
591
            if fname.endswith(".exe") or (self.targetos == "android" and fname.endswith(".apk")):
Andrey Kamaev's avatar
Andrey Kamaev committed
592
                fname = fname[:-4]
593 594
            if fname == name:
                return t
Andrey Kamaev's avatar
Andrey Kamaev committed
595 596
            if self.options.configuration == "Debug" and fname == name + 'd':
                return t
597 598
            if fname.startswith(self.nameprefix):
                fname = fname[len(self.nameprefix):]
599 600
            if fname == name:
                return t
Andrey Kamaev's avatar
Andrey Kamaev committed
601 602
            if self.options.configuration == "Debug" and fname == name + 'd':
                return t
603
        return None
604

605
    def runAdb(self, *args):
606
        cmd = self.adb[:]
607 608 609 610 611 612 613 614 615
        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
616

617
    def isRunnable(self):
618 619
        if self.error:
            return False
620 621 622 623
        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":
624 625
            if not self.adb:
                self.error = "Could not find adb executable (for %s)" % self.path
626
                return False
627
            if "armeabi-v7a" in self.android_abi:
628 629
                adb_res = self.runAdb("shell", "cat /proc/cpuinfo")
                if not adb_res:
630
                    self.error = "Could not get info about Android platform: %s (for %s)" % (self.error, self.path)
631 632
                    return False
                if "ARMv7" not in adb_res:
633
                    self.error = "Android device does not support ARMv7 commands, but tests are built for armeabi-v7a (for %s)" % self.path
634
                    return False
635 636
                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)
637 638 639 640 641
                    return False
                hw = re.search(r"^Hardware[ \t]*:[ \t]*(.*?)$", adb_res, re.MULTILINE)
                if hw:
                    self.hardware = hw.groups()[0].strip()
        return True
642

643 644 645 646 647 648 649
    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)
650

651 652 653 654
        userlog = [a for a in args if a.startswith("--gtest_output=")]
        if len(userlog) == 0:
            args.append("--gtest_output=xml:" + logfile)
        else:
655
            logfile = userlog[0][userlog[0].find(":")+1:]
656

657
        if self.targetos == "android" and exe.endswith(".apk"):
658
            print "Run java tests:", exe
659 660 661 662
            try:
                # get package info
                output = Popen(self.aapt + ["dump", "xmltree", exe, "AndroidManifest.xml"], stdout=PIPE, stderr=_stderr).communicate()
                if not output[0]:
663
                    print >> _stderr, "fail to dump manifest from", exe
664 665
                    return
                tags = re.split(r"[ ]+E: ", output[0])
666
                # get package name
667 668
                manifest_tag = [t for t in tags if t.startswith("manifest ")]
                if not manifest_tag:
669
                    print >> _stderr, "fail to read package name from", exe
670
                    return
671
                pkg_name =  re.search(r"^[ ]+A: package=\"(?P<pkg>.*?)\" \(Raw: \"(?P=pkg)\"\)\r?$", manifest_tag[0], flags=re.MULTILINE).group("pkg")
672
                # get test instrumentation info
673 674 675 676
                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
677 678
                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")
679 680 681 682 683 684 685 686
                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
687
                # uninstall previously installed package
688
                print >> _stderr, "Uninstalling old", pkg_name, "from device..."
689 690 691 692 693 694 695 696
                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"
697 698
                    return
                print >> _stderr, "Running jUnit tests for ", pkg_target
699
                if self.setUp:
700 701
                    self.setUp()
                Popen(self.adb + ["shell", "am instrument -w -e package " + pkg_target + " " + pkg_name + "/" + pkg_runner], stdout=_stdout, stderr=_stderr).wait()
702
                if self.tearDown:
703 704 705 706 707
                    self.tearDown()
            except OSError:
                pass
            return
        elif self.targetos == "android":
708
            hostlogpath = ""
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
709
            usercolor = [a for a in args if a.startswith("--gtest_color=")]
710
            if len(usercolor) == 0 and _stdout.isatty() and hostos != "nt":
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
711
                args.append("--gtest_color=yes")
712
            try:
713 714
                tempdir = "/data/local/tmp/"
                andoidcwd = tempdir + getpass.getuser().replace(" ","") + "_" + self.options.mode +"/"
715 716
                exename = os.path.basename(exe)
                androidexe = andoidcwd + exename
717
                # upload
718
                _stderr.write("Uploading... ")
719
                output = Popen(self.adb + ["push", exe, androidexe], stdout=_stdout, stderr=_stderr).wait()
720 721 722
                if output != 0:
                    print >> _stderr, "adb finishes unexpectedly with error code", output
                    return
723
                # chmod
724
                output = Popen(self.adb + ["shell", "chmod 777 " + androidexe], stdout=_stdout, stderr=_stderr).wait()
725 726 727
                if output != 0:
                    print >> _stderr, "adb finishes unexpectedly with error code", output
                    return
728
                # run
729 730 731 732
                if self.options.help:
                    command = exename + " --help"
                else:
                    command = exename + " " + " ".join(args)
733
                print >> _stderr, "Run command:", command
734
                if self.setUp:
735
                    self.setUp()
736 737
                Popen(self.adb + ["shell", "export OPENCV_TEST_DATA_PATH=" + self.options.test_data_path + "&& cd " + andoidcwd + "&& ./" + command], stdout=_stdout, stderr=_stderr).wait()
                if self.tearDown:
738
                    self.tearDown()
739
                # try get log
740
                if not self.options.help:
741
                    #_stderr.write("Pull log...  ")
742
                    hostlogpath = os.path.join(workingDir, logfile)
743
                    output = Popen(self.adb + ["pull", andoidcwd + logfile, hostlogpath], stdout=_stdout, stderr=PIPE).wait()
744 745 746 747
                    if output != 0:
                        print >> _stderr, "adb finishes unexpectedly with error code", output
                        return
                    #rm log
748
                    Popen(self.adb + ["shell", "rm " + andoidcwd + logfile], stdout=PIPE, stderr=PIPE).wait()
749 750

                # clean temporary files
751
                Popen(self.adb + ["shell", "rm " + tempdir + "__opencv_temp.*"], stdout=PIPE, stderr=PIPE).wait()
752 753 754 755 756
            except OSError:
                pass
            if os.path.isfile(hostlogpath):
                return hostlogpath
            return None
757 758 759 760 761 762 763 764 765 766
        elif path == "java":
            cmd = [self.ant_executable, "-DjavaLibraryPath=" + self.tests_dir, "buildAndTest"]

            print >> _stderr, "Run command:", " ".join(cmd)
            try:
                Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = self.java_test_binary_dir + "/.build").wait()
            except OSError:
                pass

            return None
767 768
        else:
            cmd = [exe]
769 770 771 772
            if self.options.help:
                cmd.append("--help")
            else:
                cmd.extend(args)
Andrey Kamaev's avatar
Andrey Kamaev committed
773 774 775 776 777

            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

778
            print >> _stderr, "Run command:", " ".join(cmd)
779
            try:
780 781 782
                Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait()
            except OSError:
                pass
783 784

            # clean temporary files
Andrey Kamaev's avatar
Andrey Kamaev committed
785 786 787 788
            if orig_temp_path:
                os.environ['OPENCV_TEMP_PATH'] = orig_temp_path
            else:
                del os.environ['OPENCV_TEMP_PATH']
789

790
            try:
Andrey Kamaev's avatar
Andrey Kamaev committed
791
                shutil.rmtree(temp_path)
792
                pass
793 794
            except:
                pass
795

796 797 798 799
            logpath = os.path.join(workingDir, logfile)
            if os.path.isfile(logpath):
                return logpath
            return None
800

801
    def runTests(self, tests, _stdout, _stderr, workingDir, args = []):
802 803
        if not self.isRunnable():
            print >> _stderr, "Error:", self.error
804 805
        if self.error:
            return []
806 807
        if self.adb and self.targetos == "android":
            print "adb command:", " ".join(self.adb)
808 809 810 811 812 813 814 815 816 817 818 819 820
        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

821 822 823 824 825 826 827 828 829 830 831 832 833 834
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

835 836 837
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_"))]
838

839 840 841
    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=".")
842
    parser.add_option("-a", "--accuracy", dest="accuracy", help="look for accuracy tests instead of performance tests", action="store_true", default=False)
843
    parser.add_option("-l", "--longname", dest="useLongNames", action="store_true", help="generate log files with long names", default=False)
844
    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/")
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
845
    parser.add_option("", "--configuration", dest="configuration", help="force Debug or Release configuration", metavar="CFG", default="")
846
    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="")
847
    parser.add_option("", "--package", dest="junit_package", help="Android: run jUnit tests for specified package", metavar="package", default="")
848
    parser.add_option("", "--help-tests", dest="help", help="Show help for test executable", action="store_true", default=False)
849
    parser.add_option("", "--check", dest="check", help="Shortcut for '--perf_min_samples=1 --perf_force_samples=1'", action="store_true", default=False)
850
    parser.add_option("", "--list", dest="list", help="List available tests", action="store_true", default=False)
851

852
    (options, args) = parser.parse_args(argv)
853 854 855 856 857

    if options.accuracy:
        options.mode = "test"
    else:
        options.mode = "perf"
858

Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
859
    run_args = getRunArgs(args[1:] or ['.'])
860

861
    if len(run_args) == 0:
862
        print >> sys.stderr, "Usage:", os.path.basename(sys.argv[0]), "[options] [build_path]"
863
        exit(1)
864

865
    tests = [s.strip() for s in options.tests.split(",") if s]
866

867
    if len(tests) != 1 or len(run_args) != 1:
868
        # remove --gtest_output from params
869
        test_args = [a for a in test_args if not a.startswith("--gtest_output=")]
870

871
    if options.check:
872 873 874 875
        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=")] :
            test_args.extend(["--perf_force_samples=1"])
876 877
        if not [a for a in test_args if a.startswith("--perf_verify_sanity")] :
            test_args.extend(["--perf_verify_sanity"])
878

879
    logs = []
880
    test_list = []
881
    for path in run_args:
882 883 884 885
        suite = TestSuite(options, path)
        #print vars(suite),"\n"
        if options.list:
            test_list.extend(suite.tests)
886
        else:
887 888 889 890
            logs.extend(suite.runTests(tests, sys.stdout, sys.stderr, options.cwd, test_args))

    if options.list:
        print os.linesep.join(test_list) or "No tests found"
891

892
    if logs:
893
        print >> sys.stderr, "Collected:  ", " ".join(logs)