|
1 #!/usr/bin/env python |
|
2 # |
|
3 # This Source Code Form is subject to the terms of the Mozilla Public |
|
4 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
6 |
|
7 import os, sys |
|
8 import subprocess |
|
9 import tempfile |
|
10 from zipfile import ZipFile |
|
11 import runcppunittests as cppunittests |
|
12 import mozcrash, mozlog |
|
13 import mozfile |
|
14 import StringIO |
|
15 import posixpath |
|
16 from mozdevice import devicemanager, devicemanagerADB, devicemanagerSUT |
|
17 |
|
18 try: |
|
19 from mozbuild.base import MozbuildObject |
|
20 build_obj = MozbuildObject.from_environment() |
|
21 except ImportError: |
|
22 build_obj = None |
|
23 |
|
24 log = mozlog.getLogger('remotecppunittests') |
|
25 |
|
26 class RemoteCPPUnitTests(cppunittests.CPPUnitTests): |
|
27 def __init__(self, devmgr, options, progs): |
|
28 cppunittests.CPPUnitTests.__init__(self) |
|
29 self.options = options |
|
30 self.device = devmgr |
|
31 self.remote_test_root = self.device.getDeviceRoot() + "/cppunittests" |
|
32 self.remote_bin_dir = posixpath.join(self.remote_test_root, "b") |
|
33 self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp") |
|
34 self.remote_home_dir = posixpath.join(self.remote_test_root, "h") |
|
35 if options.setup: |
|
36 self.setup_bin(progs) |
|
37 |
|
38 def setup_bin(self, progs): |
|
39 if not self.device.dirExists(self.remote_test_root): |
|
40 self.device.mkDir(self.remote_test_root) |
|
41 if self.device.dirExists(self.remote_tmp_dir): |
|
42 self.device.removeDir(self.remote_tmp_dir) |
|
43 self.device.mkDir(self.remote_tmp_dir) |
|
44 if self.device.dirExists(self.remote_bin_dir): |
|
45 self.device.removeDir(self.remote_bin_dir) |
|
46 self.device.mkDir(self.remote_bin_dir) |
|
47 if self.device.dirExists(self.remote_home_dir): |
|
48 self.device.removeDir(self.remote_home_dir) |
|
49 self.device.mkDir(self.remote_home_dir) |
|
50 self.push_libs() |
|
51 self.push_progs(progs) |
|
52 self.device.chmodDir(self.remote_bin_dir) |
|
53 |
|
54 def push_libs(self): |
|
55 if self.options.local_apk: |
|
56 with mozfile.TemporaryDirectory() as tmpdir: |
|
57 apk_contents = ZipFile(self.options.local_apk) |
|
58 szip = os.path.join(self.options.local_bin, '..', 'host', 'bin', 'szip') |
|
59 if not os.path.exists(szip): |
|
60 # Tinderbox builds must run szip from the test package |
|
61 szip = os.path.join(self.options.local_bin, 'host', 'szip') |
|
62 if not os.path.exists(szip): |
|
63 # If the test package doesn't contain szip, it means files |
|
64 # are not szipped in the test package. |
|
65 szip = None |
|
66 |
|
67 for info in apk_contents.infolist(): |
|
68 if info.filename.endswith(".so"): |
|
69 print >> sys.stderr, "Pushing %s.." % info.filename |
|
70 remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(info.filename)) |
|
71 apk_contents.extract(info, tmpdir) |
|
72 file = os.path.join(tmpdir, info.filename) |
|
73 if szip: |
|
74 out = subprocess.check_output([szip, '-d', file], stderr=subprocess.STDOUT) |
|
75 self.device.pushFile(os.path.join(tmpdir, info.filename), remote_file) |
|
76 return |
|
77 |
|
78 for file in os.listdir(self.options.local_lib): |
|
79 if file.endswith(".so"): |
|
80 print >> sys.stderr, "Pushing %s.." % file |
|
81 remote_file = posixpath.join(self.remote_bin_dir, file) |
|
82 self.device.pushFile(os.path.join(self.options.local_lib, file), remote_file) |
|
83 # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a" |
|
84 local_arm_lib = os.path.join(self.options.local_lib, "lib") |
|
85 if os.path.isdir(local_arm_lib): |
|
86 for root, dirs, files in os.walk(local_arm_lib): |
|
87 for file in files: |
|
88 if (file.endswith(".so")): |
|
89 remote_file = posixpath.join(self.remote_bin_dir, file) |
|
90 self.device.pushFile(os.path.join(root, file), remote_file) |
|
91 |
|
92 def push_progs(self, progs): |
|
93 for local_file in progs: |
|
94 remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(local_file)) |
|
95 self.device.pushFile(local_file, remote_file) |
|
96 |
|
97 def build_environment(self): |
|
98 env = self.build_core_environment() |
|
99 env['LD_LIBRARY_PATH'] = self.remote_bin_dir |
|
100 env["TMPDIR"]=self.remote_tmp_dir |
|
101 env["HOME"]=self.remote_home_dir |
|
102 env["MOZILLA_FIVE_HOME"] = self.remote_home_dir |
|
103 env["MOZ_XRE_DIR"] = self.remote_bin_dir |
|
104 if self.options.add_env: |
|
105 for envdef in self.options.add_env: |
|
106 envdef_parts = envdef.split("=", 1) |
|
107 if len(envdef_parts) == 2: |
|
108 env[envdef_parts[0]] = envdef_parts[1] |
|
109 elif len(envdef_parts) == 1: |
|
110 env[envdef_parts[0]] = "" |
|
111 else: |
|
112 print >> sys.stderr, "warning: invalid --addEnv option skipped: "+envdef |
|
113 |
|
114 return env |
|
115 |
|
116 def run_one_test(self, prog, env, symbols_path=None): |
|
117 """ |
|
118 Run a single C++ unit test program remotely. |
|
119 |
|
120 Arguments: |
|
121 * prog: The path to the test program to run. |
|
122 * env: The environment to use for running the program. |
|
123 * symbols_path: A path to a directory containing Breakpad-formatted |
|
124 symbol files for producing stack traces on crash. |
|
125 |
|
126 Return True if the program exits with a zero status, False otherwise. |
|
127 """ |
|
128 basename = os.path.basename(prog) |
|
129 remote_bin = posixpath.join(self.remote_bin_dir, basename) |
|
130 log.info("Running test %s", basename) |
|
131 buf = StringIO.StringIO() |
|
132 returncode = self.device.shell([remote_bin], buf, env=env, cwd=self.remote_home_dir, |
|
133 timeout=cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT) |
|
134 print >> sys.stdout, buf.getvalue() |
|
135 with mozfile.TemporaryDirectory() as tempdir: |
|
136 self.device.getDirectory(self.remote_home_dir, tempdir) |
|
137 if mozcrash.check_for_crashes(tempdir, symbols_path, |
|
138 test_name=basename): |
|
139 log.testFail("%s | test crashed", basename) |
|
140 return False |
|
141 result = returncode == 0 |
|
142 if not result: |
|
143 log.testFail("%s | test failed with return code %s", |
|
144 basename, returncode) |
|
145 return result |
|
146 |
|
147 class RemoteCPPUnittestOptions(cppunittests.CPPUnittestOptions): |
|
148 def __init__(self): |
|
149 cppunittests.CPPUnittestOptions.__init__(self) |
|
150 defaults = {} |
|
151 |
|
152 self.add_option("--deviceIP", action="store", |
|
153 type = "string", dest = "device_ip", |
|
154 help = "ip address of remote device to test") |
|
155 defaults["device_ip"] = None |
|
156 |
|
157 self.add_option("--devicePort", action="store", |
|
158 type = "string", dest = "device_port", |
|
159 help = "port of remote device to test") |
|
160 defaults["device_port"] = 20701 |
|
161 |
|
162 self.add_option("--dm_trans", action="store", |
|
163 type = "string", dest = "dm_trans", |
|
164 help = "the transport to use to communicate with device: [adb|sut]; default=sut") |
|
165 defaults["dm_trans"] = "sut" |
|
166 |
|
167 self.add_option("--noSetup", action="store_false", |
|
168 dest = "setup", |
|
169 help = "do not copy any files to device (to be used only if device is already setup)") |
|
170 defaults["setup"] = True |
|
171 |
|
172 self.add_option("--localLib", action="store", |
|
173 type = "string", dest = "local_lib", |
|
174 help = "location of libraries to push -- preferably stripped") |
|
175 defaults["local_lib"] = None |
|
176 |
|
177 self.add_option("--apk", action="store", |
|
178 type = "string", dest = "local_apk", |
|
179 help = "local path to Fennec APK") |
|
180 defaults["local_apk"] = None |
|
181 |
|
182 self.add_option("--localBinDir", action="store", |
|
183 type = "string", dest = "local_bin", |
|
184 help = "local path to bin directory") |
|
185 defaults["local_bin"] = build_obj.bindir if build_obj is not None else None |
|
186 |
|
187 self.add_option("--remoteTestRoot", action = "store", |
|
188 type = "string", dest = "remote_test_root", |
|
189 help = "remote directory to use as test root (eg. /data/local/tests)") |
|
190 # /data/local/tests is used because it is usually not possible to set +x permissions |
|
191 # on binaries on /mnt/sdcard |
|
192 defaults["remote_test_root"] = "/data/local/tests" |
|
193 |
|
194 self.add_option("--addEnv", action = "append", |
|
195 type = "string", dest = "add_env", |
|
196 help = "additional remote environment variable definitions (eg. --addEnv \"somevar=something\")") |
|
197 defaults["add_env"] = None |
|
198 |
|
199 self.set_defaults(**defaults) |
|
200 |
|
201 def main(): |
|
202 parser = RemoteCPPUnittestOptions() |
|
203 options, args = parser.parse_args() |
|
204 if not args: |
|
205 print >>sys.stderr, """Usage: %s <test binary> [<test binary>...]""" % sys.argv[0] |
|
206 sys.exit(1) |
|
207 if options.local_lib is None and options.local_apk is None: |
|
208 print >>sys.stderr, """Error: --localLib or --apk is required""" |
|
209 sys.exit(1) |
|
210 if options.local_lib is not None and not os.path.isdir(options.local_lib): |
|
211 print >>sys.stderr, """Error: --localLib directory %s not found""" % options.local_lib |
|
212 sys.exit(1) |
|
213 if options.local_apk is not None and not os.path.isfile(options.local_apk): |
|
214 print >>sys.stderr, """Error: --apk file %s not found""" % options.local_apk |
|
215 sys.exit(1) |
|
216 if not options.xre_path: |
|
217 print >>sys.stderr, """Error: --xre-path is required""" |
|
218 sys.exit(1) |
|
219 if options.dm_trans == "adb": |
|
220 if options.device_ip: |
|
221 dm = devicemanagerADB.DeviceManagerADB(options.device_ip, options.device_port, packageName=None, deviceRoot=options.remote_test_root) |
|
222 else: |
|
223 dm = devicemanagerADB.DeviceManagerADB(packageName=None, deviceRoot=options.remote_test_root) |
|
224 else: |
|
225 dm = devicemanagerSUT.DeviceManagerSUT(options.device_ip, options.device_port, deviceRoot=options.remote_test_root) |
|
226 if not options.device_ip: |
|
227 print "Error: you must provide a device IP to connect to via the --deviceIP option" |
|
228 sys.exit(1) |
|
229 options.xre_path = os.path.abspath(options.xre_path) |
|
230 progs = cppunittests.extract_unittests_from_args(args, options.manifest_file) |
|
231 tester = RemoteCPPUnitTests(dm, options, progs) |
|
232 try: |
|
233 result = tester.run_tests(progs, options.xre_path, options.symbols_path) |
|
234 except Exception, e: |
|
235 log.error(str(e)) |
|
236 result = False |
|
237 sys.exit(0 if result else 1) |
|
238 |
|
239 if __name__ == '__main__': |
|
240 main() |