Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
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/.
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
18 try:
19 from mozbuild.base import MozbuildObject
20 build_obj = MozbuildObject.from_environment()
21 except ImportError:
22 build_obj = None
24 log = mozlog.getLogger('remotecppunittests')
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)
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)
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
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
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)
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)
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
114 return env
116 def run_one_test(self, prog, env, symbols_path=None):
117 """
118 Run a single C++ unit test program remotely.
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.
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
147 class RemoteCPPUnittestOptions(cppunittests.CPPUnittestOptions):
148 def __init__(self):
149 cppunittests.CPPUnittestOptions.__init__(self)
150 defaults = {}
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
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
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"
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
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
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
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
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"
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
199 self.set_defaults(**defaults)
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)
239 if __name__ == '__main__':
240 main()