run.py 34.7 KB
Newer Older
Andrey Kamaev's avatar
Andrey Kamaev committed
1
import sys, os, platform, xml, re, tempfile, glob, datetime, getpass, shutil
2 3 4 5 6 7
from optparse import OptionParser
from subprocess import Popen, PIPE

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

8 9 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
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
"""

51
parse_patterns = (
52
  {'name': "has_perf_tests",           'default': "OFF",      'pattern': re.compile("^BUILD_PERF_TESTS:BOOL=(ON)$")},
53
  {'name': "has_accuracy_tests",       'default': "OFF",      'pattern': re.compile("^BUILD_TESTS:BOOL=(ON)$")},
54 55 56 57 58
  {'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=(.*)$")},
59
  {'name': "git_executable",           'default': None,       'pattern': re.compile("^GIT_EXECUTABLE:FILEPATH=(.*)$")},
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
  {'name': "cxx_flags",                'default': "",         'pattern': re.compile("^CMAKE_CXX_FLAGS:STRING=(.*)$")},
  {'name': "cxx_flags_debug",          'default': "",         'pattern': re.compile("^CMAKE_CXX_FLAGS_DEBUG:STRING=(.*)$")},
  {'name': "cxx_flags_release",        'default': "",         'pattern': re.compile("^CMAKE_CXX_FLAGS_RELEASE:STRING=(.*)$")},
  {'name': "opencv_cxx_flags",         'default': "",         'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS:INTERNAL=(.*)$")},
  {'name': "opencv_cxx_flags_debug",   'default': "",         'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS_DEBUG:INTERNAL=(.*)$")},
  {'name': "opencv_cxx_flags_release", 'default': "",         'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS_RELEASE:INTERNAL=(.*)$")},
  {'name': "cxx_flags_android",        'default': None,       'pattern': re.compile("^ANDROID_CXX_FLAGS:INTERNAL=(.*)$")},
  {'name': "cxx_compiler_path",        'default': None,       'pattern': re.compile("^CMAKE_CXX_COMPILER:FILEPATH=(.*)$")},
  {'name': "ndk_path",                 'default': None,       'pattern': re.compile("^(?:ANDROID_NDK|ANDROID_STANDALONE_TOOLCHAIN)?:PATH=(.*)$")},
  {'name': "android_abi",              'default': None,       'pattern': re.compile("^ANDROID_ABI:STRING=(.*)$")},
  {'name': "android_executable",       'default': None,       'pattern': re.compile("^ANDROID_EXECUTABLE:FILEPATH=(.*android.*)$")},
  {'name': "is_x64",                   'default': "OFF",      'pattern': re.compile("^CUDA_64_BIT_DEVICE_CODE:BOOL=(ON)$")},#ugly(
  {'name': "cmake_generator",          'default': None,       'pattern': re.compile("^CMAKE_GENERATOR:INTERNAL=(.+)$")},
  {'name': "cxx_compiler",             'default': None,       'pattern': re.compile("^CMAKE_CXX_COMPILER:FILEPATH=(.+)$")},
  {'name': "with_cuda",                'default': "OFF",      'pattern': re.compile("^WITH_CUDA:BOOL=(ON)$")},
  {'name': "cuda_library",             'default': None,       'pattern': re.compile("^CUDA_CUDA_LIBRARY:FILEPATH=(.+)$")},
  {'name': "core_dependencies",        'default': None,       'pattern': re.compile("^opencv_core_LIB_DEPENDS:STATIC=(.+)$")},
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
)

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

101
def getRunningProcessExePathByName_win32(name):
102 103
    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
104

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

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

131 132
    TH32CS_SNAPPROCESS = 2
    TH32CS_SNAPMODULE = 0x00000008
133

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    ## 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
154

155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
    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

179 180 181 182 183
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'))
184
            if path and path.endswith(name):
185 186 187 188
                return path
        except:
            pass

189 190 191 192
def getRunningProcessExePathByName(name):
    try:
        if hostos == "nt":
            return getRunningProcessExePathByName_win32(name)
193 194
        elif hostos == "posix":
            return getRunningProcessExePathByName_posix(name)
195 196 197 198
        else:
            return None
    except:
        return None
199

200
class RunInfo(object):
201 202 203
    def setCallback(self, name, callback):
        setattr(self, name, callback)

204
    def __init__(self, path, options):
205
        self.options = options
206 207
        self.path = path
        self.error = None
208 209 210
        self.setUp = None
        self.tearDown = None
        self.nameprefix = "opencv_" + options.mode + "_"
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
        for p in parse_patterns:
            setattr(self, p["name"], p["default"])
        cachefile = open(os.path.join(path, "CMakeCache.txt"), "rt")
        try:
            for l in cachefile.readlines():
                ll = l.strip()
                if not ll or ll.startswith("#"):
                    continue
                for p in parse_patterns:
                    match = p["pattern"].match(ll)
                    if match:
                        value = match.groups()[0]
                        if value and not value.endswith("-NOTFOUND"):
                            setattr(self, p["name"], value)
        except:
            pass
        cachefile.close()
228

229 230 231
        # fix empty tests dir
        if not self.tests_dir:
            self.tests_dir = self.path
Andrey Kamaev's avatar
Andrey Kamaev committed
232
        self.tests_dir = os.path.normpath(self.tests_dir)
233 234 235
        # add path to adb
        if self.android_executable:
            self.adb = os.path.join(os.path.dirname(os.path.dirname(self.android_executable)), ("platform-tools/adb","platform-tools/adb.exe")[hostos == 'nt'])
236 237
            if not os.path.isfile(self.adb) or not os.access(self.adb, os.X_OK):
                self.adb = None
238 239 240
        else:
            self.adb = None

241
        # detect target platform
242
        if self.android_executable or self.android_abi or self.ndk_path:
243
            self.targetos = "android"
244 245
        else:
            self.targetos = hostos
246

247 248 249 250
        if self.targetos == "android":
            # fix adb tool location
            if not self.adb:
                self.adb = getRunningProcessExePathByName("adb")
251
            if not self.adb:
252 253 254
                self.adb = "adb"
            if options.adb_serial:
                self.adb = [self.adb, "-s", options.adb_serial]
255
            else:
256 257 258 259 260 261 262 263 264 265 266 267
                self.adb = [self.adb]
            try:
                output = Popen(self.adb + ["shell", "ls"], stdout=PIPE, stderr=PIPE).communicate()
            except OSError:
                self.adb = []
            # remember current device serial. Needed if another device is connected while this script runs
            if self.adb and not options.adb_serial:
                adb_res = self.runAdb("devices")
                if not adb_res:
                    self.error = "Could not run adb command: %s (for %s)" % (self.error, self.path)
                    self.adb = []
                else:
268 269
                    # 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)
270 271 272 273 274 275 276
                    if len(connected_devices) != 1:
                        self.error = "Too many (%s) devices are connected. Please specify single device using --serial option" % (len(connected_devices))
                        self.adb = []
                    else:
                        adb_serial = connected_devices[0].split("\t")[0]
                        self.adb = self.adb + ["-s", adb_serial]
            print "adb command:", " ".join(self.adb)
277

278 279 280 281
        if self.adb:
            #construct name for aapt tool
            self.aapt = [os.path.join(os.path.dirname(self.adb[0]), ("aapt","aapt.exe")[hostos == 'nt'])]

282 283
        # fix has_perf_tests param
        self.has_perf_tests = self.has_perf_tests == "ON"
284
        self.has_accuracy_tests = self.has_accuracy_tests == "ON"
285 286 287 288 289 290 291
        # 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:
292 293
            if options.configuration:
                self.tests_dir = os.path.join(self.tests_dir, options.configuration)
294 295
            else:
                self.tests_dir = os.path.join(self.tests_dir, self.build_type)
296 297 298 299 300 301 302 303 304 305 306
        elif not self.is_x64 and self.cxx_compiler:
            #one more attempt to detect x64 compiler
            try:
                output = Popen([self.cxx_compiler, "-v"], stdout=PIPE, stderr=PIPE).communicate()
                if not output[0] and "x86_64" in output[1]:
                    self.is_x64 = True
            except OSError:
                pass

        # detect target arch
        if self.targetos == "android":
307
            if "armeabi-v7a" in self.android_abi:
308
                self.targetarch = "armv7a"
309
            elif "armeabi-v6" in self.android_abi:
310
                self.targetarch = "armv6"
311
            elif "armeabi" in self.android_abi:
312
                self.targetarch = "armv5te"
313 314
            elif "x86" in self.android_abi:
                self.targetarch = "x86"
315 316
            elif "mips" in self.android_abi:
                self.targetarch = "mips"
317 318
            else:
                self.targetarch = "ARM"
319 320 321 322 323 324
        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"
325

326 327 328 329 330
        # 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"]
331

332
        self.hardware = None
333

334
        self.cmake_home_vcver = self.getVCVersion(self.cmake_home)
335
        if self.opencv_home == self.cmake_home:
336
            self.opencv_home_vcver = self.cmake_home_vcver
337
        else:
338
            self.opencv_home_vcver = self.getVCVersion(self.opencv_home)
339

340
        self.tests = self.getAvailableTestApps()
341

342
    def getVCVersion(self, root_path):
343 344
        if not root_path:
            return None
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
        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):
364
        if not path:
365 366
            val = None
        elif not self.svnversion_path and hostos == 'nt':
367
            val = self.tryGetSvnVersionWithTortoise(path)
368 369 370 371 372 373 374
        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]:
375
                    val = output[0]
376
                else:
377
                    val = None
378
            except OSError:
379 380 381
                val = None
        if val:
            val = val.replace(" ", "_")
382
        return val
383

384
    def tryGetSvnVersionWithTortoise(self, path):
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
        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()
401
            return version
402
        except:
403
            return None
404 405 406
        finally:
            if dir:
                shutil.rmtree(dir)
407

408 409 410
    def isTest(self, fullpath):
        if not os.path.isfile(fullpath):
            return False
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
411 412
        if self.targetos == "nt" and not fullpath.endswith(".exe"):
            return False
413 414
        if hostos == self.targetos:
            return os.access(fullpath, os.X_OK)
415 416
        if self.targetos == "android" and fullpath.endswith(".apk"):
            return True
417
        return True
418

419 420
    def getAvailableTestApps(self):
        if self.tests_dir and os.path.isdir(self.tests_dir):
421 422
            files = glob.glob(os.path.join(self.tests_dir, self.nameprefix + "*"))
            files = [f for f in files if self.isTest(f)]
423 424
            return files
        return []
425

426 427 428
    def getLogName(self, app, timestamp):
        app = os.path.basename(app)
        if app.endswith(".exe"):
Vadim Pisarevsky's avatar
Vadim Pisarevsky committed
429 430 431 432
            if app.endswith("d.exe"):
                app = app[:-5]
            else:
                app = app[:-4]
433 434
        if app.startswith(self.nameprefix):
            app = app[len(self.nameprefix):]
435

436 437 438 439 440
        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
441
            else:
442
                rev = self.cmake_home_vcver
443 444 445
        else:
            rev = None
        if rev:
446
            rev = rev.replace(":","to")
447 448
        else:
            rev = ""
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 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

        if self.options.useLongNames:
            if not rev:
                rev = "unknown"
            tstamp = timestamp.strftime("%Y%m%d-%H%M%S")

            features = []
            #OS
            _os = ""
            if self.targetos == "android":
                _os = "Android" + self.runAdb("shell", "getprop ro.build.version.release").strip()
            else:
                mv = platform.mac_ver()
                if mv[0]:
                    _os = "Darwin" + mv[0]
                else:
                    wv = platform.win32_ver()
                    if wv[0]:
                        _os = "Windows" + wv[0]
                    else:
                        lv = platform.linux_distribution()
                        if lv[0]:
                            _os = lv[0] + lv[1]
                        else:
                            _os = self.targetos
            features.append(_os)

            #HW(x86, x64, ARMv7a)
            if self.targetarch:
                features.append(self.targetarch)

            #TBB
            if ";tbb;" in self.core_dependencies:
                features.append("TBB")

            #CUDA
            if self.has_cuda:
                #TODO: determine compute capability
                features.append("CUDA")

            #SIMD
            compiler_output = ""
            try:
                tmpfile = tempfile.mkstemp(suffix=".cpp", text = True)
                fd = os.fdopen(tmpfile[0], "w+b")
                fd.write(SIMD_DETECTION_PROGRAM)
                fd.close();
                options = [self.cxx_compiler_path]
497
                cxx_flags = self.cxx_flags + " " + self.cxx_flags_release + " " + self.opencv_cxx_flags + " " + self.opencv_cxx_flags_release
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 523 524 525
                if self.targetos == "android":
                    cxx_flags = self.cxx_flags_android + " " + cxx_flags

                prev_option = None
                for opt in cxx_flags.split(" "):
                    if opt.count('\"') % 2 == 1:
                        if prev_option is None:
                             prev_option = opt
                        else:
                             options.append(prev_option + " " + opt)
                             prev_option = None
                    elif prev_option is None:
                        options.append(opt)
                    else:
                        prev_option = prev_option + " " + opt
                options.append(tmpfile[1])
                output = Popen(options, stdout=PIPE, stderr=PIPE).communicate()
                compiler_output = output[1]
                os.remove(tmpfile[1])
            except OSError:
                pass
            if compiler_output:
                m = re.search("#error\W+(\w+)", compiler_output)
                if m:
                    features.append(m.group(1))

            #fin
            return "%s__%s__%s__%s.xml" % (app, rev, tstamp, "_".join(features))
526
        else:
527 528 529 530 531 532 533 534
            if rev:
                rev = rev + "_"
            if self.hardware:
                hw = str(self.hardware).replace(" ", "_") + "_"
            elif self.has_cuda:
                hw = "CUDA_"
            else:
                hw = ""
535
            tstamp = timestamp.strftime("%Y%m%d-%H%M%S")
536
            return "%s_%s_%s_%s%s%s.xml" % (app, self.targetos, self.targetarch, hw, rev, tstamp)
537

538 539 540 541
    def getTest(self, name):
        # full path
        if self.isTest(name):
            return name
542

543 544 545 546
        # name only
        fullname = os.path.join(self.tests_dir, name)
        if self.isTest(fullname):
            return fullname
547

548 549 550 551
        # name without extension
        fullname += ".exe"
        if self.isTest(fullname):
            return fullname
552 553 554 555
        if self.targetos == "android":
            fullname += ".apk"
            if self.isTest(fullname):
                return fullname
556

557 558 559 560 561 562 563
        # 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
564
            if fname.endswith(".exe") or (self.targetos == "android" and fname.endswith(".apk")):
Andrey Kamaev's avatar
Andrey Kamaev committed
565
                fname = fname[:-4]
566 567
            if fname == name:
                return t
Andrey Kamaev's avatar
Andrey Kamaev committed
568 569
            if self.options.configuration == "Debug" and fname == name + 'd':
                return t
570 571
            if fname.startswith(self.nameprefix):
                fname = fname[len(self.nameprefix):]
572 573
            if fname == name:
                return t
Andrey Kamaev's avatar
Andrey Kamaev committed
574 575
            if self.options.configuration == "Debug" and fname == name + 'd':
                return t
576
        return None
577

578
    def runAdb(self, *args):
579
        cmd = self.adb[:]
580 581 582 583 584 585 586 587 588
        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
589

590
    def isRunnable(self):
591 592
        if self.error:
            return False
593 594 595 596
        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":
597 598
            if not self.adb:
                self.error = "Could not find adb executable (for %s)" % self.path
599
                return False
600
            if "armeabi-v7a" in self.android_abi:
601 602
                adb_res = self.runAdb("shell", "cat /proc/cpuinfo")
                if not adb_res:
603
                    self.error = "Could not get info about Android platform: %s (for %s)" % (self.error, self.path)
604 605
                    return False
                if "ARMv7" not in adb_res:
606
                    self.error = "Android device does not support ARMv7 commands, but tests are built for armeabi-v7a (for %s)" % self.path
607
                    return False
608 609
                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)
610 611 612 613 614
                    return False
                hw = re.search(r"^Hardware[ \t]*:[ \t]*(.*?)$", adb_res, re.MULTILINE)
                if hw:
                    self.hardware = hw.groups()[0].strip()
        return True
615

616 617 618 619 620 621 622
    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)
623

624 625 626 627
        userlog = [a for a in args if a.startswith("--gtest_output=")]
        if len(userlog) == 0:
            args.append("--gtest_output=xml:" + logfile)
        else:
628
            logfile = userlog[0][userlog[0].find(":")+1:]
629

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

                # clean temporary files
                Popen(self.adb + ["shell", "rm " + tempdir + "__opencv_temp.*"], stdout=_stdout, stderr=_stderr).wait()
726 727 728 729 730 731 732
            except OSError:
                pass
            if os.path.isfile(hostlogpath):
                return hostlogpath
            return None
        else:
            cmd = [exe]
733 734 735 736
            if self.options.help:
                cmd.append("--help")
            else:
                cmd.extend(args)
Andrey Kamaev's avatar
Andrey Kamaev committed
737 738 739 740 741

            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

742
            print >> _stderr, "Running:", " ".join(cmd)
743
            try:
744 745 746
                Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait()
            except OSError:
                pass
747 748

            # clean temporary files
Andrey Kamaev's avatar
Andrey Kamaev committed
749 750 751 752
            if orig_temp_path:
                os.environ['OPENCV_TEMP_PATH'] = orig_temp_path
            else:
                del os.environ['OPENCV_TEMP_PATH']
753

754
            try:
Andrey Kamaev's avatar
Andrey Kamaev committed
755
                shutil.rmtree(temp_path)
756 757
            except:
                pass
758

759 760 761 762
            logpath = os.path.join(workingDir, logfile)
            if os.path.isfile(logpath):
                return logpath
            return None
763

764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
    def runTests(self, tests, _stdout, _stderr, workingDir, args = []):
        if self.error:
            return []
        if not tests:
            tests = self.tests
        logs = []
        for test in tests:
            t = self.getTest(test)
            if t:
                logfile = self.runTest(t, workingDir, _stdout, _stderr, args)
                if logfile:
                    logs.append(os.path.relpath(logfile, "."))
            else:
                print >> _stderr, "Error: Test \"%s\" is not found in %s" % (test, self.tests_dir)
        return logs

780 781 782 783 784 785 786 787 788 789 790 791 792 793
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

794 795 796
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_"))]
797

798 799
    parser = OptionParser()
    parser.add_option("-t", "--tests", dest="tests", help="comma-separated list of modules to test", metavar="SUITS", default="")
800

801
    parser.add_option("-w", "--cwd", dest="cwd", help="working directory for tests", metavar="PATH", default=".")
802
    parser.add_option("-a", "--accuracy", dest="accuracy", help="look for accuracy tests instead of performance tests", action="store_true", default=False)
803
    parser.add_option("-l", "--longname", dest="useLongNames", action="store_true", help="generate log files with long names", default=False)
804
    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
805
    parser.add_option("", "--configuration", dest="configuration", help="force Debug or Release configuration", metavar="CFG", default="")
806
    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="")
807
    parser.add_option("", "--package", dest="junit_package", help="Android: run jUnit tests for specified package", metavar="package", default="")
808
    parser.add_option("", "--help-tests", dest="help", help="Show help for test executable", action="store_true", default=False)
809

810
    (options, args) = parser.parse_args(argv)
811 812 813 814 815

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

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

819 820 821
    if len(run_args) == 0:
        print >> sys.stderr, "Usage:\n", os.path.basename(sys.argv[0]), "<build_path>"
        exit(1)
822

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

825 826 827
    if len(tests) != 1 or len(run_args) != 1:
        #remove --gtest_output from params
        test_args = [a for a in test_args if not a.startswith("--gtest_output=")]
828

829 830
    logs = []
    for path in run_args:
831
        info = RunInfo(path, options)
832 833 834 835 836 837 838
        #print vars(info),"\n"
        if not info.isRunnable():
            print >> sys.stderr, "Error:", info.error
        else:
            info.test_data_path = options.test_data_path
            logs.extend(info.runTests(tests, sys.stdout, sys.stderr, options.cwd, test_args))

839
    if logs:
840
        print >> sys.stderr, "Collected:", " ".join(logs)