1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 # Integrates the xpcshell test runner with mach.
7 from __future__
import absolute_import
, division
, print_function
, unicode_literals
14 from mozlog
import structured
16 from mozbuild
.base
import (
18 MachCommandConditions
as conditions
,
19 BinaryNotFoundException
,
22 from mach
.decorators
import (
26 from multiprocessing
import cpu_count
27 from xpcshellcommandline
import parser_desktop
, parser_remote
29 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
32 # This should probably be consolidated with similar classes in other test
34 class InvalidTestPathError(Exception):
35 """Exception raised when the test path is not valid."""
38 class XPCShellRunner(MozbuildObject
):
39 """Run xpcshell tests."""
41 def run_suite(self
, **kwargs
):
42 return self
._run
_xpcshell
_harness
(**kwargs
)
44 def run_test(self
, **kwargs
):
45 """Runs an individual xpcshell test."""
47 # TODO Bug 794506 remove once mach integrates with virtualenv.
48 build_path
= os
.path
.join(self
.topobjdir
, "build")
49 if build_path
not in sys
.path
:
50 sys
.path
.append(build_path
)
52 src_build_path
= os
.path
.join(self
.topsrcdir
, "mozilla", "build")
53 if os
.path
.isdir(src_build_path
):
54 sys
.path
.append(src_build_path
)
56 return self
.run_suite(**kwargs
)
58 def _run_xpcshell_harness(self
, **kwargs
):
59 # Obtain a reference to the xpcshell test runner.
60 import runxpcshelltests
62 log
= kwargs
.pop("log")
64 xpcshell
= runxpcshelltests
.XPCShellTests(log
=log
)
65 self
.log_manager
.enable_unstructured()
67 tests_dir
= os
.path
.join(self
.topobjdir
, "_tests", "xpcshell")
68 # We want output from the test to be written immediately if we are only
69 # running a single test.
71 len(kwargs
["testPaths"]) == 1
72 and os
.path
.isfile(kwargs
["testPaths"][0])
74 and (len(kwargs
["manifest"].test_paths()) == 1)
78 kwargs
["verbose"] = True
80 if kwargs
["xpcshell"] is None:
82 kwargs
["xpcshell"] = self
.get_binary_path("xpcshell")
83 except BinaryNotFoundException
as e
:
85 logging
.ERROR
, "xpcshell-test", {"error": str(e
)}, "ERROR: {error}"
87 self
.log(logging
.INFO
, "xpcshell-test", {"help": e
.help()}, "{help}")
90 if kwargs
["mozInfo"] is None:
91 kwargs
["mozInfo"] = os
.path
.join(self
.topobjdir
, "mozinfo.json")
93 if kwargs
["symbolsPath"] is None:
94 kwargs
["symbolsPath"] = os
.path
.join(self
.distdir
, "crashreporter-symbols")
96 if kwargs
["logfiles"] is None:
97 kwargs
["logfiles"] = False
99 if kwargs
["profileName"] is None:
100 kwargs
["profileName"] = "firefox"
102 if kwargs
["pluginsPath"] is None:
103 kwargs
["pluginsPath"] = os
.path
.join(self
.distdir
, "plugins")
105 if kwargs
["testingModulesDir"] is None:
106 kwargs
["testingModulesDir"] = os
.path
.join(self
.topobjdir
, "_tests/modules")
108 if kwargs
["utility_path"] is None:
109 kwargs
["utility_path"] = self
.bindir
111 if kwargs
["manifest"] is None:
112 kwargs
["manifest"] = os
.path
.join(tests_dir
, "xpcshell.ini")
114 if kwargs
["failure_manifest"] is None:
115 kwargs
["failure_manifest"] = os
.path
.join(
116 self
.statedir
, "xpcshell.failures.ini"
119 # Use the object directory for the temp directory to minimize the chance
120 # of file scanning. The overhead from e.g. search indexers and anti-virus
121 # scanners like Windows Defender can add tons of overhead to test execution.
122 # We encourage people to disable these things in the object directory.
123 temp_dir
= os
.path
.join(self
.topobjdir
, "temp")
127 if e
.errno
!= errno
.EEXIST
:
129 kwargs
["tempDir"] = temp_dir
131 result
= xpcshell
.runTests(kwargs
)
133 self
.log_manager
.disable_unstructured()
135 if not result
and not xpcshell
.sequential
:
137 "Tests were run in parallel. Try running with --sequential "
138 "to make sure the failures were not caused by this."
140 return int(not result
)
143 class AndroidXPCShellRunner(MozbuildObject
):
144 """Run Android xpcshell tests."""
146 def run_test(self
, **kwargs
):
147 # TODO Bug 794506 remove once mach integrates with virtualenv.
148 build_path
= os
.path
.join(self
.topobjdir
, "build")
149 if build_path
not in sys
.path
:
150 sys
.path
.append(build_path
)
152 import remotexpcshelltests
154 log
= kwargs
.pop("log")
155 self
.log_manager
.enable_unstructured()
157 if kwargs
["xpcshell"] is None:
158 kwargs
["xpcshell"] = "xpcshell"
160 if not kwargs
["objdir"]:
161 kwargs
["objdir"] = self
.topobjdir
163 if not kwargs
["localBin"]:
164 kwargs
["localBin"] = os
.path
.join(self
.topobjdir
, "dist/bin")
166 if not kwargs
["testingModulesDir"]:
167 kwargs
["testingModulesDir"] = os
.path
.join(self
.topobjdir
, "_tests/modules")
169 if not kwargs
["mozInfo"]:
170 kwargs
["mozInfo"] = os
.path
.join(self
.topobjdir
, "mozinfo.json")
172 if not kwargs
["manifest"]:
173 kwargs
["manifest"] = os
.path
.join(
174 self
.topobjdir
, "_tests/xpcshell/xpcshell.ini"
177 if not kwargs
["symbolsPath"]:
178 kwargs
["symbolsPath"] = os
.path
.join(self
.distdir
, "crashreporter-symbols")
180 if self
.substs
.get("MOZ_BUILD_APP") == "b2g":
181 kwargs
["localAPK"] = None
182 elif not kwargs
["localAPK"]:
183 for root
, _
, paths
in os
.walk(os
.path
.join(kwargs
["objdir"], "gradle")):
184 for file_name
in paths
:
185 if file_name
.endswith(".apk") and file_name
.startswith(
186 "test_runner-withGeckoBinaries"
188 kwargs
["localAPK"] = os
.path
.join(root
, file_name
)
189 print("using APK: %s" % kwargs
["localAPK"])
191 if kwargs
["localAPK"]:
194 raise Exception("APK not found in objdir. You must specify an APK.")
196 xpcshell
= remotexpcshelltests
.XPCShellRemote(kwargs
, log
)
198 result
= xpcshell
.runTests(
200 testClass
=remotexpcshelltests
.RemoteXPCShellTestThread
,
201 mobileArgs
=xpcshell
.mobileArgs
,
204 self
.log_manager
.disable_unstructured()
206 return int(not result
)
210 build_obj
= MozbuildObject
.from_environment(cwd
=here
)
212 conditions
.is_android(build_obj
)
213 or build_obj
.substs
.get("MOZ_BUILD_APP") == "b2g"
215 return parser_remote()
217 return parser_desktop()
223 description
="Run XPCOM Shell tests (API direct unit testing)",
224 conditions
=[lambda *args
: True],
227 def run_xpcshell_test(command_context
, test_objects
=None, **params
):
228 from mozbuild
.controller
.building
import BuildDriver
230 if test_objects
is not None:
231 from manifestparser
import TestManifest
234 m
.tests
.extend(test_objects
)
235 params
["manifest"] = m
237 driver
= command_context
._spawn
(BuildDriver
)
238 driver
.install_tests()
240 # We should probably have a utility function to ensure the tree is
241 # ready to run tests. Until then, we just create the state dir (in
242 # case the tree wasn't built with mach).
243 command_context
._ensure
_state
_subdir
_exists
(".")
245 if not params
.get("log"):
247 command_context
._mach
_context
.settings
["test"]["format"]: sys
.stdout
250 "level": command_context
._mach
_context
.settings
["test"]["level"],
253 params
["log"] = structured
.commandline
.setup_logging(
254 "XPCShellTests", params
, log_defaults
, fmt_defaults
257 if not params
["threadCount"]:
258 # pylint --py3k W1619
259 params
["threadCount"] = int((cpu_count() * 3) / 2)
262 conditions
.is_android(command_context
)
263 or command_context
.substs
.get("MOZ_BUILD_APP") == "b2g"
265 from mozrunner
.devices
.android_device
import (
266 verify_android_device
,
271 install
= InstallIntent
.YES
if params
["setup"] else InstallIntent
.NO
272 device_serial
= params
.get("deviceSerial")
273 verify_android_device(
277 device_serial
=device_serial
,
279 if not params
["adbPath"]:
280 params
["adbPath"] = get_adb_path(command_context
)
281 xpcshell
= command_context
._spawn
(AndroidXPCShellRunner
)
283 xpcshell
= command_context
._spawn
(XPCShellRunner
)
284 xpcshell
.cwd
= command_context
._mach
_context
.cwd
287 return xpcshell
.run_test(**params
)
288 except InvalidTestPathError
as e
: