• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3import sys, os, platform, xml, re, tempfile, glob, datetime, getpass, shutil
4from optparse import OptionParser
5from subprocess import Popen, PIPE
6
7hostos = os.name # 'nt', 'posix'
8hostmachine = platform.machine() # 'x86', 'AMD64', 'x86_64'
9
10errorCode = 0
11
12SIMD_DETECTION_PROGRAM="""
13#if __SSE5__
14# error SSE5
15#endif
16#if __AVX2__
17# error AVX2
18#endif
19#if __AVX__
20# error AVX
21#endif
22#if __SSE4_2__
23# error SSE4.2
24#endif
25#if __SSE4_1__
26# error SSE4.1
27#endif
28#if __SSSE3__
29# error SSSE3
30#endif
31#if __SSE3__
32# error SSE3
33#endif
34#if __AES__
35# error AES
36#endif
37#if __SSE2__
38# error SSE2
39#endif
40#if __SSE__
41# error SSE
42#endif
43#if __3dNOW__
44# error 3dNOW
45#endif
46#if __MMX__
47# error MMX
48#endif
49#if __ARM_NEON__
50# error NEON
51#endif
52#error NOSIMD
53"""
54
55parse_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  {'name': "cmake_home",               'default': None,       'pattern': re.compile("^CMAKE_HOME_DIRECTORY:INTERNAL=(.+)$")},
59  {'name': "opencv_home",              'default': None,       'pattern': re.compile("^OpenCV_SOURCE_DIR:STATIC=(.+)$")},
60  {'name': "tests_dir",                'default': None,       'pattern': re.compile("^EXECUTABLE_OUTPUT_PATH:PATH=(.+)$")},
61  {'name': "build_type",               'default': "Release",  'pattern': re.compile("^CMAKE_BUILD_TYPE:STRING=(.*)$")},
62  {'name': "svnversion_path",          'default': None,       'pattern': re.compile("^SVNVERSION_PATH:FILEPATH=(.*)$")},
63  {'name': "git_executable",           'default': None,       'pattern': re.compile("^GIT_EXECUTABLE:FILEPATH=(.*)$")},
64  {'name': "cxx_flags",                'default': "",         'pattern': re.compile("^CMAKE_CXX_FLAGS:STRING=(.*)$")},
65  {'name': "cxx_flags_debug",          'default': "",         'pattern': re.compile("^CMAKE_CXX_FLAGS_DEBUG:STRING=(.*)$")},
66  {'name': "cxx_flags_release",        'default': "",         'pattern': re.compile("^CMAKE_CXX_FLAGS_RELEASE:STRING=(.*)$")},
67  {'name': "opencv_cxx_flags",         'default': "",         'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS:INTERNAL=(.*)$")},
68  {'name': "opencv_cxx_flags_debug",   'default': "",         'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS_DEBUG:INTERNAL=(.*)$")},
69  {'name': "opencv_cxx_flags_release", 'default': "",         'pattern': re.compile("^OPENCV_EXTRA_C_FLAGS_RELEASE:INTERNAL=(.*)$")},
70  {'name': "cxx_flags_android",        'default': None,       'pattern': re.compile("^ANDROID_CXX_FLAGS:INTERNAL=(.*)$")},
71  {'name': "ndk_path",                 'default': None,       'pattern': re.compile("^(?:ANDROID_NDK|ANDROID_STANDALONE_TOOLCHAIN)?:PATH=(.*)$")},
72  {'name': "android_abi",              'default': None,       'pattern': re.compile("^ANDROID_ABI:STRING=(.*)$")},
73  {'name': "android_executable",       'default': None,       'pattern': re.compile("^ANDROID_EXECUTABLE:FILEPATH=(.*android.*)$")},
74  {'name': "ant_executable",           'default': None,       'pattern': re.compile("^ANT_EXECUTABLE:FILEPATH=(.*ant.*)$")},
75  {'name': "java_test_binary_dir",     'default': None,       'pattern': re.compile("^opencv_test_java_BINARY_DIR:STATIC=(.*)$")},
76  {'name': "is_x64",                   'default': "OFF",      'pattern': re.compile("^CUDA_64_BIT_DEVICE_CODE:BOOL=(ON)$")},#ugly(
77  {'name': "cmake_generator",          'default': None,       'pattern': re.compile("^CMAKE_GENERATOR:INTERNAL=(.+)$")},
78  {'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  {'name': "with_cuda",                'default': "OFF",      'pattern': re.compile("^WITH_CUDA:BOOL=(ON)$")},
81  {'name': "cuda_library",             'default': None,       'pattern': re.compile("^CUDA_CUDA_LIBRARY:FILEPATH=(.+)$")},
82  {'name': "core_dependencies",        'default': None,       'pattern': re.compile("^opencv_core_LIB_DEPENDS:STATIC=(.+)$")},
83)
84
85def query_yes_no(stdout, question, default="yes"):
86    valid = {"yes":True, "y":True, "ye":True, "no":False, "n":False}
87    if default == None:
88        prompt = " [y/n] "
89    elif default == "yes":
90        prompt = " [Y/n] "
91    elif default == "no":
92        prompt = " [y/N] "
93    else:
94        raise ValueError("invalid default answer: '%s'" % default)
95
96    while True:
97        stdout.write(os.linesep + question + prompt)
98        choice = raw_input().lower()
99        if default is not None and choice == '':
100            return valid[default]
101        elif choice in valid:
102            return valid[choice]
103        else:
104            stdout.write("Please respond with 'yes' or 'no' "\
105                             "(or 'y' or 'n').\n")
106
107def getRunningProcessExePathByName_win32(name):
108    from ctypes import windll, POINTER, pointer, Structure, sizeof
109    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                    ( 'cntUsage' , c_uint) ,
114                    ( 'th32ProcessID' , c_uint) ,
115                    ( 'th32DefaultHeapID' , c_uint) ,
116                    ( 'th32ModuleID' , c_uint) ,
117                    ( 'cntThreads' , c_uint) ,
118                    ( 'th32ParentProcessID' , c_uint) ,
119                    ( 'pcPriClassBase' , c_long) ,
120                    ( 'dwFlags' , c_uint) ,
121                    ( 'szExeFile' , c_char * 260 ) ,
122                    ( 'th32MemoryBase' , c_long) ,
123                    ( 'th32AccessKey' , c_long ) ]
124
125    class MODULEENTRY32(Structure):
126        _fields_ = [ ( 'dwSize' , c_long ) ,
127                    ( 'th32ModuleID' , c_long ),
128                    ( 'th32ProcessID' , c_long ),
129                    ( 'GlblcntUsage' , c_long ),
130                    ( 'ProccntUsage' , c_long ) ,
131                    ( 'modBaseAddr' , c_long ) ,
132                    ( 'modBaseSize' , c_long ) ,
133                    ( 'hModule' , c_void_p ) ,
134                    ( 'szModule' , c_char * 256 ),
135                    ( 'szExePath' , c_char * 260 ) ]
136
137    TH32CS_SNAPPROCESS = 2
138    TH32CS_SNAPMODULE = 0x00000008
139
140    ## CreateToolhelp32Snapshot
141    CreateToolhelp32Snapshot= windll.kernel32.CreateToolhelp32Snapshot
142    CreateToolhelp32Snapshot.reltype = c_long
143    CreateToolhelp32Snapshot.argtypes = [ c_int , c_int ]
144    ## Process32First
145    Process32First = windll.kernel32.Process32First
146    Process32First.argtypes = [ c_void_p , POINTER( PROCESSENTRY32 ) ]
147    Process32First.rettype = c_int
148    ## Process32Next
149    Process32Next = windll.kernel32.Process32Next
150    Process32Next.argtypes = [ c_void_p , POINTER(PROCESSENTRY32) ]
151    Process32Next.rettype = c_int
152    ## CloseHandle
153    CloseHandle = windll.kernel32.CloseHandle
154    CloseHandle.argtypes = [ c_void_p ]
155    CloseHandle.rettype = c_int
156    ## Module32First
157    Module32First = windll.kernel32.Module32First
158    Module32First.argtypes = [ c_void_p , POINTER(MODULEENTRY32) ]
159    Module32First.rettype = c_int
160
161    hProcessSnap = c_void_p(0)
162    hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS , 0 )
163
164    pe32 = PROCESSENTRY32()
165    pe32.dwSize = sizeof( PROCESSENTRY32 )
166    ret = Process32First( hProcessSnap , pointer( pe32 ) )
167    path = None
168
169    while ret :
170        if name + ".exe" == pe32.szExeFile:
171            hModuleSnap = c_void_p(0)
172            me32 = MODULEENTRY32()
173            me32.dwSize = sizeof( MODULEENTRY32 )
174            hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, pe32.th32ProcessID )
175
176            ret = Module32First( hModuleSnap, pointer(me32) )
177            path = me32.szExePath
178            CloseHandle( hModuleSnap )
179            if path:
180                break
181        ret = Process32Next( hProcessSnap, pointer(pe32) )
182    CloseHandle( hProcessSnap )
183    return path
184
185def getRunningProcessExePathByName_posix(name):
186    pids= [pid for pid in os.listdir('/proc') if pid.isdigit()]
187    for pid in pids:
188        try:
189            path = os.readlink(os.path.join('/proc', pid, 'exe'))
190            if path and path.endswith(name):
191                return path
192        except:
193            pass
194
195def getRunningProcessExePathByName(name):
196    try:
197        if hostos == "nt":
198            return getRunningProcessExePathByName_win32(name)
199        elif hostos == "posix":
200            return getRunningProcessExePathByName_posix(name)
201        else:
202            return None
203    except:
204        return None
205
206class TestSuite(object):
207    def __init__(self, options, path = None):
208        self.options = options
209        self.path = path
210        self.error = None
211        self.setUp = None
212        self.tearDown = None
213        self.adb = None
214        self.targetos = None
215        self.nameprefix = "opencv_" + self.options.mode + "_"
216        for p in parse_patterns:
217            setattr(self, p["name"], p["default"])
218
219        if self.path:
220            cachefile = open(os.path.join(self.path, "CMakeCache.txt"), "rt")
221            try:
222                for l in cachefile.readlines():
223                    ll = l.strip()
224                    if not ll or ll.startswith("#"):
225                        continue
226                    for p in parse_patterns:
227                        match = p["pattern"].match(ll)
228                        if match:
229                            value = match.groups()[0]
230                            if value and not value.endswith("-NOTFOUND"):
231                                setattr(self, p["name"], value)
232            except:
233                pass
234            cachefile.close()
235
236            # detect target platform
237            if self.android_executable or self.android_abi or self.ndk_path:
238                self.targetos = "android"
239            else:
240                self.targetos = hostos
241
242            self.initialize()
243
244    def initialize(self):
245        # fix empty tests dir
246        if not self.tests_dir:
247            self.tests_dir = self.path
248        self.tests_dir = os.path.normpath(self.tests_dir)
249
250        # compute path to adb
251        if self.android_executable:
252            self.adb = os.path.join(os.path.dirname(os.path.dirname(self.android_executable)), ("platform-tools/adb","platform-tools/adb.exe")[hostos == 'nt'])
253            if not os.path.isfile(self.adb) or not os.access(self.adb, os.X_OK):
254                self.adb = None
255        else:
256            self.adb = None
257
258        if self.targetos == "android":
259            # fix adb tool location
260            if not self.adb:
261                self.adb = getRunningProcessExePathByName("adb")
262            if not self.adb:
263                self.adb = "adb"
264            if self.options.adb_serial:
265                self.adb = [self.adb, "-s", self.options.adb_serial]
266            else:
267                self.adb = [self.adb]
268            try:
269                output = Popen(self.adb + ["shell", "ls"], stdout=PIPE, stderr=PIPE).communicate()
270            except OSError:
271                self.adb = []
272            # 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                adb_res = self.runAdb("devices")
275                if not adb_res:
276                    self.error = "Could not run adb command: %s (for %s)" % (self.error, self.path)
277                    self.adb = []
278                else:
279                    # assume here that device name may consists of any characters except newline
280                    connected_devices = re.findall(r"^[^\n]+[ \t]+device\r?$", adb_res, re.MULTILINE)
281                    if not connected_devices:
282                        self.error = "Android device not found"
283                        self.adb = []
284                    elif len(connected_devices) != 1:
285                        self.error = "Too many (%s) devices are connected. Please specify single device using --serial option:\n\n" % (len(connected_devices)) + adb_res
286                        self.adb = []
287                    else:
288                        self.options.adb_serial = connected_devices[0].split("\t")[0]
289                        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                if not os.path.isfile(self.aapt[0]):
294                    # it's moved in SDK r22
295                    sdk_dir = os.path.dirname( os.path.dirname(self.adb[0]) )
296                    aapt_fn = ("aapt", "aapt.exe")[hostos == 'nt']
297                    for r, ds, fs in os.walk( os.path.join(sdk_dir, 'build-tools') ):
298                        if aapt_fn in fs:
299                            self.aapt = [ os.path.join(r, aapt_fn) ]
300                            break
301                    else:
302                        self.error = "Can't find '%s' tool!" % aapt_fn
303
304        # fix has_perf_tests param
305        self.has_perf_tests = self.has_perf_tests == "ON"
306        self.has_accuracy_tests = self.has_accuracy_tests == "ON"
307        # fix is_x64 flag
308        self.is_x64 = self.is_x64 == "ON"
309        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):
310            self.is_x64 = True
311
312        # fix test path
313        if "Visual Studio" in self.cmake_generator:
314            if self.options.configuration:
315                self.tests_dir = os.path.join(self.tests_dir, self.options.configuration)
316            else:
317                self.tests_dir = os.path.join(self.tests_dir, self.build_type)
318        elif not self.is_x64 and self.cxx_compiler:
319            #one more attempt to detect x64 compiler
320            try:
321                compiler = [self.cxx_compiler]
322                if self.cxx_compiler_arg1:
323                    compiler.append(self.cxx_compiler_arg1)
324                output = Popen(compiler + ["-v"], stdout=PIPE, stderr=PIPE).communicate()
325                if not output[0] and "x86_64" in output[1]:
326                    self.is_x64 = True
327            except OSError:
328                pass
329
330        # detect target arch
331        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            elif "x86" in self.android_abi:
339                self.targetarch = "x86"
340            elif "mips" in self.android_abi:
341                self.targetarch = "mips"
342            else:
343                self.targetarch = "ARM"
344        elif self.is_x64 and hostmachine in ["AMD64", "x86_64"]:
345            self.targetarch = "x64"
346        elif hostmachine in ["x86", "AMD64", "x86_64"]:
347            self.targetarch = "x86"
348        else:
349            self.targetarch = "unknown"
350
351        # fix CUDA attributes
352        self.with_cuda = self.with_cuda == "ON"
353        if self.cuda_library and self.cuda_library.endswith("-NOTFOUND"):
354            self.cuda_library = None
355        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        if not root_path:
369            return None
370        if os.path.isdir(os.path.join(root_path, ".svn")):
371            return self.getSvnVersion(root_path)
372        elif os.path.isdir(os.path.join(root_path, ".git")):
373            return self.getGitHash(root_path)
374        return None
375
376    def getGitHash(self, path):
377        if not path or not self.git_executable:
378            return None
379        try:
380            output = Popen([self.git_executable, "rev-parse", "--short", "HEAD"], stdout=PIPE, stderr=PIPE, cwd = path).communicate()
381            if not output[1]:
382                return output[0].strip()
383            else:
384                return None
385        except OSError:
386            return None
387
388    def getSvnVersion(self, path):
389        if not path:
390            val = None
391        elif not self.svnversion_path and hostos == 'nt':
392            val = self.tryGetSvnVersionWithTortoise(path)
393        else:
394            svnversion = self.svnversion_path
395            if not svnversion:
396                svnversion = "svnversion"
397            try:
398                output = Popen([svnversion, "-n", path], stdout=PIPE, stderr=PIPE).communicate()
399                if not output[1]:
400                    val = output[0]
401                else:
402                    val = None
403            except OSError:
404                val = None
405        if val:
406            val = val.replace(" ", "_")
407        return val
408
409    def tryGetSvnVersionWithTortoise(self, path):
410        try:
411            wcrev = "SubWCRev.exe"
412            dir = tempfile.mkdtemp()
413            #print dir
414            tmpfilename = os.path.join(dir, "svn.tmp")
415            tmpfilename2 = os.path.join(dir, "svn_out.tmp")
416            tmpfile = open(tmpfilename, "w")
417            tmpfile.write("$WCRANGE$$WCMODS?M:$")
418            tmpfile.close();
419            output = Popen([wcrev, path, tmpfilename, tmpfilename2, "-f"], stdout=PIPE, stderr=PIPE).communicate()
420            if "is not a working copy" in output[0]:
421                version = "exported"
422            else:
423                tmpfile = open(tmpfilename2, "r")
424                version = tmpfile.read()
425                tmpfile.close()
426            return version
427        except:
428            return None
429        finally:
430            if dir:
431                shutil.rmtree(dir)
432
433    def isTest(self, fullpath):
434        if not os.path.isfile(fullpath):
435            return False
436        if self.targetos == "nt" and not fullpath.endswith(".exe"):
437            return False
438        if hostos == self.targetos:
439            return os.access(fullpath, os.X_OK)
440        if self.targetos == "android" and fullpath.endswith(".apk"):
441            return True
442        return True
443
444    def getAvailableTestApps(self):
445        if self.tests_dir and os.path.isdir(self.tests_dir):
446            files = glob.glob(os.path.join(self.tests_dir, self.nameprefix + "*"))
447            files = [f for f in files if self.isTest(f)]
448            if self.ant_executable and self.java_test_binary_dir:
449                files.append("java")
450            return files
451        return []
452
453    def getLogName(self, app, timestamp):
454        app = os.path.basename(app)
455        if app.endswith(".exe"):
456            if app.endswith("d.exe"):
457                app = app[:-5]
458            else:
459                app = app[:-4]
460        if app.startswith(self.nameprefix):
461            app = app[len(self.nameprefix):]
462
463        if self.cmake_home_vcver:
464            if self.cmake_home_vcver == self.opencv_home_vcver:
465                rev = self.cmake_home_vcver
466            elif self.opencv_home_vcver:
467                rev = self.cmake_home_vcver + "-" + self.opencv_home_vcver
468            else:
469                rev = self.cmake_home_vcver
470        else:
471            rev = None
472        if rev:
473            rev = rev.replace(":","to")
474        else:
475            rev = ""
476
477        if self.options.useLongNames:
478            if not rev:
479                rev = "unknown"
480            tstamp = timestamp.strftime("%Y%m%d-%H%M%S")
481
482            features = []
483            #OS
484            _os = ""
485            if self.targetos == "android":
486                _os = "Android" + self.runAdb("shell", "getprop ro.build.version.release").strip()
487            else:
488                mv = platform.mac_ver()
489                if mv[0]:
490                    _os = "Darwin" + mv[0]
491                else:
492                    wv = platform.win32_ver()
493                    if wv[0]:
494                        _os = "Windows" + wv[0]
495                    else:
496                        lv = platform.linux_distribution()
497                        if lv[0]:
498                            _os = lv[0] + lv[1]
499                        else:
500                            _os = self.targetos
501            features.append(_os)
502
503            #HW(x86, x64, ARMv7a)
504            if self.targetarch:
505                features.append(self.targetarch)
506
507            #TBB
508            if ";tbb;" in self.core_dependencies:
509                features.append("TBB")
510
511            #CUDA
512            if self.has_cuda:
513                #TODO: determine compute capability
514                features.append("CUDA")
515
516            #SIMD
517            compiler_output = ""
518            try:
519                tmpfile = tempfile.mkstemp(suffix=".cpp", text = True)
520                fd = os.fdopen(tmpfile[0], "w+b")
521                fd.write(SIMD_DETECTION_PROGRAM)
522                fd.close();
523                options = [self.cxx_compiler]
524                if self.cxx_compiler_arg1:
525                    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                    cxx_flags = self.cxx_flags_android + " " + cxx_flags
529
530                prev_option = None
531                for opt in cxx_flags.split(" "):
532                    if opt.count('\"') % 2 == 1:
533                        if prev_option is None:
534                             prev_option = opt
535                        else:
536                             options.append(prev_option + " " + opt)
537                             prev_option = None
538                    elif prev_option is None:
539                        options.append(opt)
540                    else:
541                        prev_option = prev_option + " " + opt
542                options.append(tmpfile[1])
543                output = Popen(options, stdout=PIPE, stderr=PIPE).communicate()
544                compiler_output = output[1]
545                os.remove(tmpfile[1])
546            except OSError:
547                pass
548            if compiler_output:
549                m = re.search("#error\W+(\w+)", compiler_output)
550                if m:
551                    features.append(m.group(1))
552
553            #fin
554            return "%s__%s__%s__%s.xml" % (app, rev, tstamp, "_".join(features))
555        else:
556            if rev:
557                rev = rev + "_"
558            if self.hardware:
559                hw = str(self.hardware).replace(" ", "_") + "_"
560            elif self.has_cuda:
561                hw = "CUDA_"
562            else:
563                hw = ""
564            tstamp = timestamp.strftime("%Y%m%d-%H%M%S")
565            lname = "%s_%s_%s_%s%s%s.xml" % (app, self.targetos, self.targetarch, hw, rev, tstamp)
566            lname = str.replace(lname, '(', '_')
567            lname = str.replace(lname, ')', '_')
568            return lname
569
570    def getTest(self, name):
571        # full path
572        if self.isTest(name):
573            return name
574
575        # name only
576        fullname = os.path.join(self.tests_dir, name)
577        if self.isTest(fullname):
578            return fullname
579
580        # name without extension
581        fullname += ".exe"
582        if self.isTest(fullname):
583            return fullname
584        if self.targetos == "android":
585            fullname += ".apk"
586            if self.isTest(fullname):
587                return fullname
588
589        # short name for OpenCV tests
590        for t in self.tests:
591            if t == name:
592                return t
593            fname = os.path.basename(t)
594            if fname == name:
595                return t
596            if fname.endswith(".exe") or (self.targetos == "android" and fname.endswith(".apk")):
597                fname = fname[:-4]
598            if fname == name:
599                return t
600            if self.options.configuration == "Debug" and fname == name + 'd':
601                return t
602            if fname.startswith(self.nameprefix):
603                fname = fname[len(self.nameprefix):]
604            if fname == name:
605                return t
606            if self.options.configuration == "Debug" and fname == name + 'd':
607                return t
608        return None
609
610    def runAdb(self, *args):
611        cmd = self.adb[:]
612        cmd.extend(args)
613        try:
614            output = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
615            if not output[1]:
616                return output[0]
617            self.error = output[1]
618        except OSError:
619            pass
620        return None
621
622    def isRunnable(self):
623        if self.error:
624            return False
625        if self.targetarch == "x64" and hostmachine == "x86":
626            self.error = "Target architecture is incompatible with current platform (at %s)" % self.path
627            return False
628        if self.targetos == "android":
629            if not self.adb:
630                self.error = "Could not find adb executable (for %s)" % self.path
631                return False
632            if "armeabi-v7a" in self.android_abi:
633                adb_res = self.runAdb("shell", "cat /proc/cpuinfo")
634                if not adb_res:
635                    self.error = "Could not get info about Android platform: %s (for %s)" % (self.error, self.path)
636                    return False
637                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                if "NEON" in self.android_abi and "neon" not in adb_res:
641                    self.error = "Android device has no NEON, but tests are built for %s (for %s)" % (self.android_abi, self.path)
642                    return False
643                hw = re.search(r"^Hardware[ \t]*:[ \t]*(.*?)$", adb_res, re.MULTILINE)
644                if hw:
645                    self.hardware = hw.groups()[0].strip()
646        return True
647
648    def runTest(self, path, workingDir, _stdout, _stderr, args = []):
649        global errorCode
650
651        if self.error:
652            return
653        args = args[:]
654        timestamp = datetime.datetime.now()
655        logfile = self.getLogName(path, timestamp)
656        exe = os.path.abspath(path)
657
658        userlog = [a for a in args if a.startswith("--gtest_output=")]
659        if len(userlog) == 0:
660            args.append("--gtest_output=xml:" + logfile)
661        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            try:
667                # get package info
668                output = Popen(self.aapt + ["dump", "xmltree", exe, "AndroidManifest.xml"], stdout=PIPE, stderr=_stderr).communicate()
669                if not output[0]:
670                    print >> _stderr, "fail to dump manifest from", exe
671                    return
672                tags = re.split(r"[ ]+E: ", output[0])
673                # get package name
674                manifest_tag = [t for t in tags if t.startswith("manifest ")]
675                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                instrumentation_tag = [t for t in tags if t.startswith("instrumentation ")]
681                if not instrumentation_tag:
682                    print >> _stderr, "can not find instrumentation detials in", exe
683                    return
684                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")
685                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                if not pkg_name or not pkg_runner or not pkg_target:
687                    print >> _stderr, "can not find instrumentation detials in", exe
688                    return
689                if self.options.junit_package:
690                    if self.options.junit_package.startswith("."):
691                        pkg_target += self.options.junit_package
692                    else:
693                        pkg_target = self.options.junit_package
694                # uninstall previously installed package
695                print >> _stderr, "Uninstalling old", pkg_name, "from device..."
696                Popen(self.adb + ["uninstall", pkg_name], stdout=PIPE, stderr=_stderr).communicate()
697                print >> _stderr, "Installing new", exe, "to device...",
698                output = Popen(self.adb + ["install", exe], stdout=PIPE, stderr=PIPE).communicate()
699                if output[0] and output[0].strip().endswith("Success"):
700                    print >> _stderr, "Success"
701                else:
702                    print >> _stderr, "Failure"
703                    print >> _stderr, "Failed to install", exe, "to device"
704                    return
705                print >> _stderr, "Running jUnit tests for ", pkg_target
706                if self.setUp:
707                    self.setUp()
708                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                    self.tearDown()
711            except OSError:
712                pass
713            return
714        elif self.targetos == "android":
715            hostlogpath = ""
716            usercolor = [a for a in args if a.startswith("--gtest_color=")]
717            if len(usercolor) == 0 and _stdout.isatty() and hostos != "nt":
718                args.append("--gtest_color=yes")
719            try:
720                tempdir = "/data/local/tmp/"
721                andoidcwd = tempdir + getpass.getuser().replace(" ","") + "_" + self.options.mode +"/"
722                exename = os.path.basename(exe)
723                androidexe = andoidcwd + exename
724                # upload
725                _stderr.write("Uploading... ")
726                output = Popen(self.adb + ["push", exe, androidexe], stdout=_stdout, stderr=_stderr).wait()
727                if output != 0:
728                    print >> _stderr, "adb finishes unexpectedly with error code", output
729                    return
730                # chmod
731                output = Popen(self.adb + ["shell", "chmod 777 " + androidexe], stdout=_stdout, stderr=_stderr).wait()
732                if output != 0:
733                    print >> _stderr, "adb finishes unexpectedly with error code", output
734                    return
735                # run
736                if self.options.help:
737                    command = exename + " --help"
738                else:
739                    command = exename + " " + " ".join(args)
740                print >> _stderr, "Run command:", command
741                if self.setUp:
742                    self.setUp()
743                env = self.options.android_env.copy()
744                env['OPENCV_TEST_DATA_PATH'] = self.options.test_data_path
745                if self.options.android_propagate_opencv_env:
746                    for k, v in os.environ.items():
747                        if k.startswith('OPENCV') and not k in env:
748                            env[k] = v
749                print >> _stderr, "Android environment variables: \n", '\n'.join(['    %s=%s' % (k, v) for k, v in env.items()])
750                commandPrefix = ''.join(['export %s=%s && ' % (k, v) for k, v in env.items()])
751                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                    if output != 0:
760                        print >> _stderr, "adb finishes unexpectedly with error code", output
761                        return
762                    #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            except OSError:
768                pass
769            if os.path.isfile(hostlogpath):
770                return hostlogpath
771            return None
772        elif path == "java":
773            cmd = [self.ant_executable,
774                   "-Dopencv.build.type="
775                     + (self.options.configuration if self.options.configuration else self.build_type),
776                   "buildAndTest"]
777
778            print >> _stderr, "Run command:", " ".join(cmd)
779            try:
780                errorCode = Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = self.java_test_binary_dir + "/.build").wait()
781            except:
782                print "Unexpected error:", sys.exc_info()[0]
783
784            return None
785        else:
786            cmd = [exe]
787            if self.options.help:
788                cmd.append("--help")
789            else:
790                cmd.extend(args)
791
792            orig_temp_path = os.environ.get('OPENCV_TEMP_PATH')
793            temp_path = tempfile.mkdtemp(prefix="__opencv_temp.", dir=orig_temp_path or None)
794            os.environ['OPENCV_TEMP_PATH'] = temp_path
795
796            print >> _stderr, "Run command:", " ".join(cmd)
797            try:
798                errorCode = Popen(cmd, stdout=_stdout, stderr=_stderr, cwd = workingDir).wait()
799            except:
800                print "Unexpected error:", sys.exc_info()[0]
801
802            # clean temporary files
803            if orig_temp_path:
804                os.environ['OPENCV_TEMP_PATH'] = orig_temp_path
805            else:
806                del os.environ['OPENCV_TEMP_PATH']
807
808            try:
809                shutil.rmtree(temp_path)
810                pass
811            except:
812                pass
813
814            logpath = os.path.join(workingDir, logfile)
815            if os.path.isfile(logpath):
816                return logpath
817            return None
818
819    def runTests(self, tests, _stdout, _stderr, workingDir, args = []):
820        if not self.isRunnable():
821            print >> _stderr, "Error:", self.error
822        if self.error:
823            return []
824        if self.adb and self.targetos == "android":
825            print "adb command:", " ".join(self.adb)
826        if not tests:
827            tests = self.tests
828        logs = []
829        for test in tests:
830            t = self.getTest(test)
831            if t:
832                logfile = self.runTest(t, workingDir, _stdout, _stderr, args)
833                if logfile:
834                    logs.append(os.path.relpath(logfile, "."))
835            else:
836                print >> _stderr, "Error: Test \"%s\" is not found in %s" % (test, self.tests_dir)
837        return logs
838
839def getRunArgs(args):
840    run_args = []
841    for path in args:
842        path = os.path.abspath(path)
843        while (True):
844            if os.path.isdir(path) and os.path.isfile(os.path.join(path, "CMakeCache.txt")):
845                run_args.append(path)
846                break
847            npath = os.path.dirname(path)
848            if npath == path:
849                break
850            path = npath
851    return run_args
852
853if hostos == "nt":
854    def moveTests(instance, destination):
855        src = os.path.dirname(instance.tests_dir)
856        # new binaries path
857        newBinPath = os.path.join(destination, "bin")
858
859        try:
860            # copy binaries and CMakeCache.txt to the specified destination
861            shutil.copytree(src, newBinPath)
862            shutil.copy(os.path.join(instance.path, "CMakeCache.txt"), os.path.join(destination, "CMakeCache.txt"))
863        except Exception, e:
864            print "Copying error occurred:", str(e)
865            exit(e.errno)
866
867        # pattern of CMakeCache.txt string to be replaced
868        replacePattern = re.compile("EXECUTABLE_OUTPUT_PATH:PATH=(.+)")
869
870        with open(os.path.join(destination, "CMakeCache.txt"), "r") as cachefile:
871            try:
872                cachedata = cachefile.read()
873                if hostos == 'nt':
874                    # fix path slashes on nt systems
875                    newBinPath = re.sub(r"\\", r"/", newBinPath)
876                # replace old binaries path in CMakeCache.txt
877                cachedata = re.sub(re.search(replacePattern, cachedata).group(1), newBinPath, cachedata)
878            except Exception, e:
879                print "Reading error occurred:", str(e)
880                exit(e.errno)
881
882        with open(os.path.join(destination, "CMakeCache.txt"), "w") as cachefile:
883            try:
884                cachefile.write(cachedata)
885            except Exception, e:
886                print "Writing error occurred:", str(e)
887                exit(e.errno)
888        exit()
889
890if __name__ == "__main__":
891    test_args = [a for a in sys.argv if a.startswith("--perf_") or a.startswith("--gtest_")]
892    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    if hostos == "nt":
897        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)
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    if options.accuracy:
914        options.mode = "test"
915    else:
916        options.mode = "perf"
917
918    run_args = getRunArgs(args[1:] or ['.'])
919
920    if len(run_args) == 0:
921        print >> sys.stderr, "Usage:", os.path.basename(sys.argv[0]), "[options] [build_path]"
922        print >> sys.stderr, "Please specify build_path or run script from CMake build directory"
923        exit(1)
924
925    options.android_env = {}
926    if options.android_env_array:
927        for entry in options.android_env_array:
928            k, v = entry.split("=", 1)
929            options.android_env[k] = v
930
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        if not [a for a in test_args if a.startswith("--perf_min_samples=")] :
939            test_args.extend(["--perf_min_samples=1"])
940        if not [a for a in test_args if a.startswith("--perf_force_samples=")] :
941            test_args.extend(["--perf_force_samples=1"])
942        if not [a for a in test_args if a.startswith("--perf_verify_sanity")] :
943            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        if hostos == "nt":
951            if(options.move):
952                moveTests(suite, options.move)
953        #print vars(suite),"\n"
954        if options.list:
955            test_list.extend(suite.tests)
956        else:
957            logs.extend(suite.runTests(tests, sys.stdout, sys.stderr, options.cwd, test_args))
958
959    if options.list:
960        print os.linesep.join(test_list) or "No tests found"
961
962    if logs:
963        print >> sys.stderr, "Collected:  ", " ".join(logs)
964
965    if errorCode != 0:
966        print "Error code: ", errorCode, (" (0x%x)" % (errorCode & 0xffffffff))
967    exit(errorCode)
968