run.py 40.9 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 566 567 568
            lname = "%s_%s_%s_%s%s%s.xml" % (app, self.targetos, self.targetarch, hw, rev, tstamp)
            lname = str.replace(lname, '(', '_')
            lname = str.replace(lname, ')', '_')
            return lname
569

570 571 572 573
    def getTest(self, name):
        # full path
        if self.isTest(name):
            return name
574

575 576 577 578
        # name only
        fullname = os.path.join(self.tests_dir, name)
        if self.isTest(fullname):
            return fullname
579

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

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

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

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

648
    def runTest(self, path, workingDir, _stdout, _stderr, args = []):
649 650
        global errorCode

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

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

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

                # clean temporary files
766
                Popen(self.adb + ["shell", "rm " + tempdir + "__opencv_temp.*"], stdout=PIPE, stderr=PIPE).wait()
767 768 769 770 771
            except OSError:
                pass
            if os.path.isfile(hostlogpath):
                return hostlogpath
            return None
772
        elif path == "java":
773 774 775 776
            cmd = [self.ant_executable,
                   "-Dopencv.build.type="
                     + (self.options.configuration if self.options.configuration else self.build_type),
                   "buildAndTest"]
777 778 779

            print >> _stderr, "Run command:", " ".join(cmd)
            try:
780 781 782
                errorCode = Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = self.java_test_binary_dir + "/.build").wait()
            except:
                print "Unexpected error:", sys.exc_info()[0]
783 784

            return None
785 786
        else:
            cmd = [exe]
787 788 789 790
            if self.options.help:
                cmd.append("--help")
            else:
                cmd.extend(args)
Andrey Kamaev's avatar
Andrey Kamaev committed
791 792 793 794 795

            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

796
            print >> _stderr, "Run command:", " ".join(cmd)
797
            try:
798 799 800
                errorCode = Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait()
            except:
                print "Unexpected error:", sys.exc_info()[0]
801 802

            # clean temporary files
Andrey Kamaev's avatar
Andrey Kamaev committed
803 804 805 806
            if orig_temp_path:
                os.environ['OPENCV_TEMP_PATH'] = orig_temp_path
            else:
                del os.environ['OPENCV_TEMP_PATH']
807

808
            try:
Andrey Kamaev's avatar
Andrey Kamaev committed
809
                shutil.rmtree(temp_path)
810
                pass
811 812
            except:
                pass
813

814 815 816 817
            logpath = os.path.join(workingDir, logfile)
            if os.path.isfile(logpath):
                return logpath
            return None
818

819
    def runTests(self, tests, _stdout, _stderr, workingDir, args = []):
820 821
        if not self.isRunnable():
            print >> _stderr, "Error:", self.error
822 823
        if self.error:
            return []
824 825
        if self.adb and self.targetos == "android":
            print "adb command:", " ".join(self.adb)
826 827 828 829 830 831 832 833 834 835 836 837 838
        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

839 840 841 842 843 844 845 846 847 848 849 850 851 852
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

853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
if hostos == "nt":
    def moveTests(instance, destination):
        src = os.path.dirname(instance.tests_dir)
        # new binaries path
        newBinPath = os.path.join(destination, "bin")

        try:
            # copy binaries and CMakeCache.txt to the specified destination
            shutil.copytree(src, newBinPath)
            shutil.copy(os.path.join(instance.path, "CMakeCache.txt"), os.path.join(destination, "CMakeCache.txt"))
        except Exception, e:
            print "Copying error occurred:", str(e)
            exit(e.errno)

        # pattern of CMakeCache.txt string to be replaced
        replacePattern = re.compile("EXECUTABLE_OUTPUT_PATH:PATH=(.+)")

        with open(os.path.join(destination, "CMakeCache.txt"), "r") as cachefile:
            try:
                cachedata = cachefile.read()
                if hostos == 'nt':
                    # fix path slashes on nt systems
                    newBinPath = re.sub(r"\\", r"/", newBinPath)
                # replace old binaries path in CMakeCache.txt
                cachedata = re.sub(re.search(replacePattern, cachedata).group(1), newBinPath, cachedata)
            except Exception, e:
                print "Reading error occurred:", str(e)
                exit(e.errno)

        with open(os.path.join(destination, "CMakeCache.txt"), "w") as cachefile:
            try:
                cachefile.write(cachedata)
            except Exception, e:
                print "Writing error occurred:", str(e)
                exit(e.errno)
        exit()

890 891 892
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_"))]
893

894
    parser = OptionParser(usage="run.py [options] [build_path]", description="Note: build_path is required if running not from CMake build directory")
895
    parser.add_option("-t", "--tests", dest="tests", help="comma-separated list of modules to test", metavar="SUITS", default="")
896 897
    if hostos == "nt":
        parser.add_option("-m", "--move_tests", dest="move", help="location to move current tests build", metavar="PATH", default="")
898
    parser.add_option("-w", "--cwd", dest="cwd", help="working directory for tests", metavar="PATH", default=".")
899
    parser.add_option("-a", "--accuracy", dest="accuracy", help="look for accuracy tests instead of performance tests", action="store_true", default=False)
900
    parser.add_option("-l", "--longname", dest="useLongNames", action="store_true", help="generate log files with long names", default=False)
901
    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/")
902
    parser.add_option("", "--android_env", dest="android_env_array", help="Environment variable for Android run (NAME=VALUE)", action='append')
903
    parser.add_option("", "--android_propagate_opencv_env", dest="android_propagate_opencv_env", help="Propagate OPENCV* environment variables for Android run", action="store_true", default=False)
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
904
    parser.add_option("", "--configuration", dest="configuration", help="force Debug or Release configuration", metavar="CFG", default="")
905
    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="")
906
    parser.add_option("", "--package", dest="junit_package", help="Android: run jUnit tests for specified package", metavar="package", default="")
907
    parser.add_option("", "--help-tests", dest="help", help="Show help for test executable", action="store_true", default=False)
908
    parser.add_option("", "--check", dest="check", help="Shortcut for '--perf_min_samples=1 --perf_force_samples=1'", action="store_true", default=False)
909
    parser.add_option("", "--list", dest="list", help="List available tests", action="store_true", default=False)
910

911
    (options, args) = parser.parse_args(argv)
912 913 914 915 916

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

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

920
    if len(run_args) == 0:
921 922
        print >> sys.stderr, "Usage:", os.path.basename(sys.argv[0]), "[options] [build_path]"
        print >> sys.stderr, "Please specify build_path or run script from CMake build directory"
923
        exit(1)
924

925 926 927 928 929 930
    options.android_env = {}
    if options.android_env_array:
        for entry in options.android_env_array:
            k, v = entry.split("=", 1)
            options.android_env[k] = v

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

933
    if len(tests) != 1 or len(run_args) != 1:
934
        # remove --gtest_output from params
935
        test_args = [a for a in test_args if not a.startswith("--gtest_output=")]
936

937
    if options.check:
938 939 940 941
        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"])
942 943
        if not [a for a in test_args if a.startswith("--perf_verify_sanity")] :
            test_args.extend(["--perf_verify_sanity"])
944

945
    logs = []
946
    test_list = []
947
    for path in run_args:
948
        suite = TestSuite(options, path)
949 950 951 952

        if hostos == "nt":
            if(options.move):
                moveTests(suite, options.move)
953 954 955
        #print vars(suite),"\n"
        if options.list:
            test_list.extend(suite.tests)
956
        else:
957 958 959 960
            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"
961

962
    if logs:
963
        print >> sys.stderr, "Collected:  ", " ".join(logs)
964 965 966 967

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