run.py 36.8 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
        # fix has_perf_tests param
        self.has_perf_tests = self.has_perf_tests == "ON"
294
        self.has_accuracy_tests = self.has_accuracy_tests == "ON"
295 296 297 298 299 300 301
        # 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:
302 303
            if self.options.configuration:
                self.tests_dir = os.path.join(self.tests_dir, self.options.configuration)
304 305
            else:
                self.tests_dir = os.path.join(self.tests_dir, self.build_type)
306 307 308
        elif not self.is_x64 and self.cxx_compiler:
            #one more attempt to detect x64 compiler
            try:
309 310 311 312
                compiler = [self.cxx_compiler]
                if self.cxx_compiler_arg1:
                    compiler.append(self.cxx_compiler_arg1)
                output = Popen(compiler + ["-v"], stdout=PIPE, stderr=PIPE).communicate()
313 314 315 316 317 318 319
                if not output[0] and "x86_64" in output[1]:
                    self.is_x64 = True
            except OSError:
                pass

        # detect target arch
        if self.targetos == "android":
320
            if "armeabi-v7a" in self.android_abi:
321
                self.targetarch = "armv7a"
322
            elif "armeabi-v6" in self.android_abi:
323
                self.targetarch = "armv6"
324
            elif "armeabi" in self.android_abi:
325
                self.targetarch = "armv5te"
326 327
            elif "x86" in self.android_abi:
                self.targetarch = "x86"
328 329
            elif "mips" in self.android_abi:
                self.targetarch = "mips"
330 331
            else:
                self.targetarch = "ARM"
332 333 334 335 336 337
        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"
338

339 340 341 342 343
        # 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"]
344

345
        self.hardware = None
346

347
        self.cmake_home_vcver = self.getVCVersion(self.cmake_home)
348
        if self.opencv_home == self.cmake_home:
349
            self.opencv_home_vcver = self.cmake_home_vcver
350
        else:
351
            self.opencv_home_vcver = self.getVCVersion(self.opencv_home)
352

353
        self.tests = self.getAvailableTestApps()
354

355
    def getVCVersion(self, root_path):
356 357
        if not root_path:
            return None
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
        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):
377
        if not path:
378 379
            val = None
        elif not self.svnversion_path and hostos == 'nt':
380
            val = self.tryGetSvnVersionWithTortoise(path)
381 382 383 384 385 386 387
        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]:
388
                    val = output[0]
389
                else:
390
                    val = None
391
            except OSError:
392 393 394
                val = None
        if val:
            val = val.replace(" ", "_")
395
        return val
396

397
    def tryGetSvnVersionWithTortoise(self, path):
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
        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()
414
            return version
415
        except:
416
            return None
417 418 419
        finally:
            if dir:
                shutil.rmtree(dir)
420

421 422 423
    def isTest(self, fullpath):
        if not os.path.isfile(fullpath):
            return False
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
424 425
        if self.targetos == "nt" and not fullpath.endswith(".exe"):
            return False
426 427
        if hostos == self.targetos:
            return os.access(fullpath, os.X_OK)
428 429
        if self.targetos == "android" and fullpath.endswith(".apk"):
            return True
430
        return True
431

432 433
    def getAvailableTestApps(self):
        if self.tests_dir and os.path.isdir(self.tests_dir):
434 435
            files = glob.glob(os.path.join(self.tests_dir, self.nameprefix + "*"))
            files = [f for f in files if self.isTest(f)]
436 437
            if self.ant_executable and self.java_test_binary_dir:
                files.append("java")
438 439
            return files
        return []
440

441 442 443
    def getLogName(self, app, timestamp):
        app = os.path.basename(app)
        if app.endswith(".exe"):
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
444 445 446 447
            if app.endswith("d.exe"):
                app = app[:-5]
            else:
                app = app[:-4]
448 449
        if app.startswith(self.nameprefix):
            app = app[len(self.nameprefix):]
450

451 452 453 454 455
        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
456
            else:
457
                rev = self.cmake_home_vcver
458 459 460
        else:
            rev = None
        if rev:
461
            rev = rev.replace(":","to")
462 463
        else:
            rev = ""
464 465 466 467 468 469 470 471 472 473 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

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

555 556 557 558
    def getTest(self, name):
        # full path
        if self.isTest(name):
            return name
559

560 561 562 563
        # name only
        fullname = os.path.join(self.tests_dir, name)
        if self.isTest(fullname):
            return fullname
564

565 566 567 568
        # name without extension
        fullname += ".exe"
        if self.isTest(fullname):
            return fullname
569 570 571 572
        if self.targetos == "android":
            fullname += ".apk"
            if self.isTest(fullname):
                return fullname
573

574 575 576 577 578 579 580
        # 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
581
            if fname.endswith(".exe") or (self.targetos == "android" and fname.endswith(".apk")):
Andrey Kamaev's avatar
Andrey Kamaev committed
582
                fname = fname[:-4]
583 584
            if fname == name:
                return t
Andrey Kamaev's avatar
Andrey Kamaev committed
585 586
            if self.options.configuration == "Debug" and fname == name + 'd':
                return t
587 588
            if fname.startswith(self.nameprefix):
                fname = fname[len(self.nameprefix):]
589 590
            if fname == name:
                return t
Andrey Kamaev's avatar
Andrey Kamaev committed
591 592
            if self.options.configuration == "Debug" and fname == name + 'd':
                return t
593
        return None
594

595
    def runAdb(self, *args):
596
        cmd = self.adb[:]
597 598 599 600 601 602 603 604 605
        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
606

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

633 634 635 636 637 638 639
    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)
640

641 642 643 644
        userlog = [a for a in args if a.startswith("--gtest_output=")]
        if len(userlog) == 0:
            args.append("--gtest_output=xml:" + logfile)
        else:
645
            logfile = userlog[0][userlog[0].find(":")+1:]
646

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

                # clean temporary files
741
                Popen(self.adb + ["shell", "rm " + tempdir + "__opencv_temp.*"], stdout=PIPE, stderr=PIPE).wait()
742 743 744 745 746
            except OSError:
                pass
            if os.path.isfile(hostlogpath):
                return hostlogpath
            return None
747 748 749 750 751 752 753 754 755 756
        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
757 758
        else:
            cmd = [exe]
759 760 761 762
            if self.options.help:
                cmd.append("--help")
            else:
                cmd.extend(args)
Andrey Kamaev's avatar
Andrey Kamaev committed
763 764 765 766 767

            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

768
            print >> _stderr, "Run command:", " ".join(cmd)
769
            try:
770 771 772
                Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait()
            except OSError:
                pass
773 774

            # clean temporary files
Andrey Kamaev's avatar
Andrey Kamaev committed
775 776 777 778
            if orig_temp_path:
                os.environ['OPENCV_TEMP_PATH'] = orig_temp_path
            else:
                del os.environ['OPENCV_TEMP_PATH']
779

780
            try:
Andrey Kamaev's avatar
Andrey Kamaev committed
781
                shutil.rmtree(temp_path)
782
                pass
783 784
            except:
                pass
785

786 787 788 789
            logpath = os.path.join(workingDir, logfile)
            if os.path.isfile(logpath):
                return logpath
            return None
790

791
    def runTests(self, tests, _stdout, _stderr, workingDir, args = []):
792 793
        if not self.isRunnable():
            print >> _stderr, "Error:", self.error
794 795
        if self.error:
            return []
796 797
        if self.adb and self.targetos == "android":
            print "adb command:", " ".join(self.adb)
798 799 800 801 802 803 804 805 806 807 808 809 810
        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

811 812 813 814 815 816 817 818 819 820 821 822 823 824
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

825 826 827
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_"))]
828

829 830 831
    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=".")
832
    parser.add_option("-a", "--accuracy", dest="accuracy", help="look for accuracy tests instead of performance tests", action="store_true", default=False)
833
    parser.add_option("-l", "--longname", dest="useLongNames", action="store_true", help="generate log files with long names", default=False)
834
    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
835
    parser.add_option("", "--configuration", dest="configuration", help="force Debug or Release configuration", metavar="CFG", default="")
836
    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="")
837
    parser.add_option("", "--package", dest="junit_package", help="Android: run jUnit tests for specified package", metavar="package", default="")
838
    parser.add_option("", "--help-tests", dest="help", help="Show help for test executable", action="store_true", default=False)
839
    parser.add_option("", "--check", dest="check", help="Shortcut for '--perf_min_samples=1 --perf_force_samples=1'", action="store_true", default=False)
840
    parser.add_option("", "--list", dest="list", help="List available tests", action="store_true", default=False)
841

842
    (options, args) = parser.parse_args(argv)
843 844 845 846 847

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

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

851
    if len(run_args) == 0:
852
        print >> sys.stderr, "Usage:", os.path.basename(sys.argv[0]), "[options] [build_path]"
853
        exit(1)
854

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

857
    if len(tests) != 1 or len(run_args) != 1:
858
        # remove --gtest_output from params
859
        test_args = [a for a in test_args if not a.startswith("--gtest_output=")]
860

861
    if options.check:
862 863 864 865
        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"])
866 867
        if not [a for a in test_args if a.startswith("--perf_verify_sanity")] :
            test_args.extend(["--perf_verify_sanity"])
868

869
    logs = []
870
    test_list = []
871
    for path in run_args:
872 873 874 875
        suite = TestSuite(options, path)
        #print vars(suite),"\n"
        if options.list:
            test_list.extend(suite.tests)
876
        else:
877 878 879 880
            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"
881

882
    if logs:
883
        print >> sys.stderr, "Collected:  ", " ".join(logs)