run.py 37.7 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
errorCode = 0

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 53 54
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
"""

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

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

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

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

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

137 138
    TH32CS_SNAPPROCESS = 2
    TH32CS_SNAPMODULE = 0x00000008
139

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

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

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

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

206 207
class TestSuite(object):
    def __init__(self, options, path = None):
208
        self.options = options
209 210
        self.path = path
        self.error = None
211 212
        self.setUp = None
        self.tearDown = None
213 214 215
        self.adb = None
        self.targetos = None
        self.nameprefix = "opencv_" + self.options.mode + "_"
216 217
        for p in parse_patterns:
            setattr(self, p["name"], p["default"])
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 243 244
        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):
245 246 247
        # fix empty tests dir
        if not self.tests_dir:
            self.tests_dir = self.path
Andrey Kamaev's avatar
Andrey Kamaev committed
248
        self.tests_dir = os.path.normpath(self.tests_dir)
249 250

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

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

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

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

351 352 353 354 355
        # 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"]
356

357
        self.hardware = None
358

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

365
        self.tests = self.getAvailableTestApps()
366

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

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

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

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

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

463 464 465 466 467
        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
468
            else:
469
                rev = self.cmake_home_vcver
470 471 472
        else:
            rev = None
        if rev:
473
            rev = rev.replace(":","to")
474 475
        else:
            rev = ""
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 521 522

        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();
523 524 525
                options = [self.cxx_compiler]
                if self.cxx_compiler_arg1:
                    options.append(self.cxx_compiler_arg1)
526
                cxx_flags = self.cxx_flags + " " + self.cxx_flags_release + " " + self.opencv_cxx_flags + " " + self.opencv_cxx_flags_release
527
                if self.targetos == "android" and self.cxx_flags_android:
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 553 554
                    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))
555
        else:
556 557 558 559 560 561 562 563
            if rev:
                rev = rev + "_"
            if self.hardware:
                hw = str(self.hardware).replace(" ", "_") + "_"
            elif self.has_cuda:
                hw = "CUDA_"
            else:
                hw = ""
564
            tstamp = timestamp.strftime("%Y%m%d-%H%M%S")
565
            return "%s_%s_%s_%s%s%s.xml" % (app, self.targetos, self.targetarch, hw, rev, tstamp)
566

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

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

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

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

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

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

645
    def runTest(self, path, workingDir, _stdout, _stderr, args = []):
646 647
        global errorCode

648 649 650 651 652 653
        if self.error:
            return
        args = args[:]
        timestamp = datetime.datetime.now()
        logfile = self.getLogName(path, timestamp)
        exe = os.path.abspath(path)
654

655 656 657 658
        userlog = [a for a in args if a.startswith("--gtest_output=")]
        if len(userlog) == 0:
            args.append("--gtest_output=xml:" + logfile)
        else:
659
            logfile = userlog[0][userlog[0].find(":")+1:]
660

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

                # clean temporary files
755
                Popen(self.adb + ["shell", "rm " + tempdir + "__opencv_temp.*"], stdout=PIPE, stderr=PIPE).wait()
756 757 758 759 760
            except OSError:
                pass
            if os.path.isfile(hostlogpath):
                return hostlogpath
            return None
761
        elif path == "java":
762 763 764 765
            cmd = [self.ant_executable,
                   "-Dopencv.build.type="
                     + (self.options.configuration if self.options.configuration else self.build_type),
                   "buildAndTest"]
766 767 768

            print >> _stderr, "Run command:", " ".join(cmd)
            try:
769 770 771
                errorCode = Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = self.java_test_binary_dir + "/.build").wait()
            except:
                print "Unexpected error:", sys.exc_info()[0]
772 773

            return None
774 775
        else:
            cmd = [exe]
776 777 778 779
            if self.options.help:
                cmd.append("--help")
            else:
                cmd.extend(args)
Andrey Kamaev's avatar
Andrey Kamaev committed
780 781 782 783 784

            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

785
            print >> _stderr, "Run command:", " ".join(cmd)
786
            try:
787 788 789
                errorCode = Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait()
            except:
                print "Unexpected error:", sys.exc_info()[0]
790 791

            # clean temporary files
Andrey Kamaev's avatar
Andrey Kamaev committed
792 793 794 795
            if orig_temp_path:
                os.environ['OPENCV_TEMP_PATH'] = orig_temp_path
            else:
                del os.environ['OPENCV_TEMP_PATH']
796

797
            try:
Andrey Kamaev's avatar
Andrey Kamaev committed
798
                shutil.rmtree(temp_path)
799
                pass
800 801
            except:
                pass
802

803 804 805 806
            logpath = os.path.join(workingDir, logfile)
            if os.path.isfile(logpath):
                return logpath
            return None
807

808
    def runTests(self, tests, _stdout, _stderr, workingDir, args = []):
809 810
        if not self.isRunnable():
            print >> _stderr, "Error:", self.error
811 812
        if self.error:
            return []
813 814
        if self.adb and self.targetos == "android":
            print "adb command:", " ".join(self.adb)
815 816 817 818 819 820 821 822 823 824 825 826 827
        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

828 829 830 831 832 833 834 835 836 837 838 839 840 841
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

842 843 844
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_"))]
845

846 847 848
    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=".")
849
    parser.add_option("-a", "--accuracy", dest="accuracy", help="look for accuracy tests instead of performance tests", action="store_true", default=False)
850
    parser.add_option("-l", "--longname", dest="useLongNames", action="store_true", help="generate log files with long names", default=False)
851
    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
852
    parser.add_option("", "--configuration", dest="configuration", help="force Debug or Release configuration", metavar="CFG", default="")
853
    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="")
854
    parser.add_option("", "--package", dest="junit_package", help="Android: run jUnit tests for specified package", metavar="package", default="")
855
    parser.add_option("", "--help-tests", dest="help", help="Show help for test executable", action="store_true", default=False)
856
    parser.add_option("", "--check", dest="check", help="Shortcut for '--perf_min_samples=1 --perf_force_samples=1'", action="store_true", default=False)
857
    parser.add_option("", "--list", dest="list", help="List available tests", action="store_true", default=False)
858

859
    (options, args) = parser.parse_args(argv)
860 861 862 863 864

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

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

868
    if len(run_args) == 0:
869
        print >> sys.stderr, "Usage:", os.path.basename(sys.argv[0]), "[options] [build_path]"
870
        exit(1)
871

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

874
    if len(tests) != 1 or len(run_args) != 1:
875
        # remove --gtest_output from params
876
        test_args = [a for a in test_args if not a.startswith("--gtest_output=")]
877

878
    if options.check:
879 880 881 882
        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"])
883 884
        if not [a for a in test_args if a.startswith("--perf_verify_sanity")] :
            test_args.extend(["--perf_verify_sanity"])
885

886
    logs = []
887
    test_list = []
888
    for path in run_args:
889 890 891 892
        suite = TestSuite(options, path)
        #print vars(suite),"\n"
        if options.list:
            test_list.extend(suite.tests)
893
        else:
894 895 896 897
            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"
898

899
    if logs:
900
        print >> sys.stderr, "Collected:  ", " ".join(logs)
901 902 903 904

    if errorCode != 0:
        print "Error code: ", errorCode, (" (0x%x)" % (errorCode & 0xffffffff))
    exit(errorCode)