summaryrefslogtreecommitdiffstats
path: root/testing/gtest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/gtest
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/gtest')
-rw-r--r--testing/gtest/MozGtestFriend.h15
-rwxr-xr-xtesting/gtest/bench.py24
-rw-r--r--testing/gtest/benchmark/AUTHORS39
-rw-r--r--testing/gtest/benchmark/BlackBox.cpp25
-rw-r--r--testing/gtest/benchmark/BlackBox.h61
-rw-r--r--testing/gtest/benchmark/LICENSE202
-rw-r--r--testing/gtest/benchmark/README.txt5
-rw-r--r--testing/gtest/benchmark/moz.build11
-rw-r--r--testing/gtest/mach_test_package_commands.py118
-rw-r--r--testing/gtest/moz.build21
-rw-r--r--testing/gtest/mozilla/GTestRunner.cpp179
-rw-r--r--testing/gtest/mozilla/GTestRunner.h10
-rw-r--r--testing/gtest/mozilla/MozAssertions.cpp38
-rw-r--r--testing/gtest/mozilla/MozAssertions.h32
-rw-r--r--testing/gtest/mozilla/MozGTestBench.cpp58
-rw-r--r--testing/gtest/mozilla/MozGTestBench.h25
-rw-r--r--testing/gtest/mozilla/MozHelpers.cpp73
-rw-r--r--testing/gtest/mozilla/MozHelpers.h119
-rw-r--r--testing/gtest/mozilla/SanityTest.cpp30
-rw-r--r--testing/gtest/mozilla/gmock-custom/README.md16
-rw-r--r--testing/gtest/mozilla/gmock-custom/gmock-generated-actions.h10
-rw-r--r--testing/gtest/mozilla/gmock-custom/gmock-matchers.h36
-rw-r--r--testing/gtest/mozilla/gmock-custom/gmock-port.h39
-rw-r--r--testing/gtest/mozilla/gtest-custom/README.md56
-rw-r--r--testing/gtest/mozilla/gtest-custom/gtest-port.h39
-rw-r--r--testing/gtest/mozilla/gtest-custom/gtest-printers.h42
-rw-r--r--testing/gtest/mozilla/gtest-custom/gtest.h37
-rw-r--r--testing/gtest/mozilla/moz.build38
-rw-r--r--testing/gtest/remotegtests.py478
-rw-r--r--testing/gtest/rungtests.py269
30 files changed, 2145 insertions, 0 deletions
diff --git a/testing/gtest/MozGtestFriend.h b/testing/gtest/MozGtestFriend.h
new file mode 100644
index 0000000000..de8a154183
--- /dev/null
+++ b/testing/gtest/MozGtestFriend.h
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTEST_MOZGTESTFRIEND_H
+#define GTEST_MOZGTESTFRIEND_H
+
+#ifdef ENABLE_TESTS
+# include "gtest_prod.h"
+#else
+# define FRIEND_TEST(a, b)
+#endif
+
+#endif // GTEST_MOZGTESTFRIEND_H
diff --git a/testing/gtest/bench.py b/testing/gtest/bench.py
new file mode 100755
index 0000000000..e63e491080
--- /dev/null
+++ b/testing/gtest/bench.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+
+import json
+import statistics
+import subprocess
+import sys
+
+proc = subprocess.Popen(["./mach", "gtest", sys.argv[1]], stdout=subprocess.PIPE)
+for line in proc.stdout:
+ if line.startswith(b"PERFHERDER_DATA:"):
+ data = json.loads(line[len("PERFHERDER_DATA:") :].decode("utf8"))
+ for suite in data["suites"]:
+ for subtest in suite["subtests"]:
+ # pylint --py3k W1619
+ print(
+ "%4d.%03d ± %6s ms %s.%s"
+ % (
+ subtest["value"] / 1000.0,
+ subtest["value"] % 1000,
+ "%.3f" % (statistics.stdev(subtest["replicates"]) / 1000),
+ suite["name"],
+ subtest["name"],
+ )
+ )
diff --git a/testing/gtest/benchmark/AUTHORS b/testing/gtest/benchmark/AUTHORS
new file mode 100644
index 0000000000..767f85d512
--- /dev/null
+++ b/testing/gtest/benchmark/AUTHORS
@@ -0,0 +1,39 @@
+# This is the official list of benchmark authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+#
+# Names should be added to this file as:
+# Name or Organization <email address>
+# The email address is not required for organizations.
+#
+# Please keep the list sorted.
+
+Albert Pretorius <pretoalb@gmail.com>
+Arne Beer <arne@twobeer.de>
+Christopher Seymour <chris.j.seymour@hotmail.com>
+David Coeurjolly <david.coeurjolly@liris.cnrs.fr>
+Dirac Research
+Dominik Czarnota <dominik.b.czarnota@gmail.com>
+Eric Fiselier <eric@efcs.ca>
+Eugene Zhuk <eugene.zhuk@gmail.com>
+Evgeny Safronov <division494@gmail.com>
+Felix Homann <linuxaudio@showlabor.de>
+Google Inc.
+International Business Machines Corporation
+Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
+Jern-Kuan Leong <jernkuan@gmail.com>
+JianXiong Zhou <zhoujianxiong2@gmail.com>
+Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>
+Jussi Knuuttila <jussi.knuuttila@gmail.com>
+Kaito Udagawa <umireon@gmail.com>
+Lei Xu <eddyxu@gmail.com>
+Matt Clarkson <mattyclarkson@gmail.com>
+Maxim Vafin <maxvafin@gmail.com>
+Nick Hutchinson <nshutchinson@gmail.com>
+Oleksandr Sochka <sasha.sochka@gmail.com>
+Paul Redmond <paul.redmond@gmail.com>
+Radoslav Yovchev <radoslav.tm@gmail.com>
+Shuo Chen <chenshuo@chenshuo.com>
+Yixuan Qiu <yixuanq@gmail.com>
+Yusuke Suzuki <utatane.tea@gmail.com>
+Zbigniew Skowron <zbychs@gmail.com>
diff --git a/testing/gtest/benchmark/BlackBox.cpp b/testing/gtest/benchmark/BlackBox.cpp
new file mode 100644
index 0000000000..8c77ee6d3c
--- /dev/null
+++ b/testing/gtest/benchmark/BlackBox.cpp
@@ -0,0 +1,25 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if defined(_MSC_VER)
+
+# include "gtest/BlackBox.h"
+
+namespace mozilla {
+
+char volatile* UseCharPointer(char volatile* aPtr) { return aPtr; }
+
+} // namespace mozilla
+
+#endif
diff --git a/testing/gtest/benchmark/BlackBox.h b/testing/gtest/benchmark/BlackBox.h
new file mode 100644
index 0000000000..568f383735
--- /dev/null
+++ b/testing/gtest/benchmark/BlackBox.h
@@ -0,0 +1,61 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GTEST_BLACKBOX_H
+#define GTEST_BLACKBOX_H
+
+#include "mozilla/Attributes.h"
+#if defined(_MSC_VER)
+# include <intrin.h>
+#endif // _MSC_VER
+
+namespace mozilla {
+
+#if defined(_MSC_VER)
+
+char volatile* UseCharPointer(char volatile*);
+
+MOZ_ALWAYS_INLINE_EVEN_DEBUG void* BlackBoxVoidPtr(void* aPtr) {
+ aPtr = const_cast<char*>(UseCharPointer(reinterpret_cast<char*>(aPtr)));
+ _ReadWriteBarrier();
+ return aPtr;
+}
+
+#else
+
+// See: https://youtu.be/nXaxk27zwlk?t=2441
+MOZ_ALWAYS_INLINE_EVEN_DEBUG void* BlackBoxVoidPtr(void* aPtr) {
+ // "g" is what we want here, but the comment in the Google
+ // benchmark code suggests that GCC likes "i,r,m" better.
+ // However, on Mozilla try server i,r,m breaks GCC but g
+ // works in GCC, so using g for both clang and GCC.
+ // godbolt.org indicates that g works already in GCC 4.9,
+ // which is the oldest GCC we support at the time of this
+ // code landing. godbolt.org suggests that this clearly
+ // works is LLVM 5, but it's unclear if this inhibits
+ // all relevant optimizations properly on earlier LLVM.
+ asm volatile("" : "+g"(aPtr) : "g"(aPtr) : "memory");
+ return aPtr;
+}
+
+#endif // _MSC_VER
+
+template <class T>
+MOZ_ALWAYS_INLINE_EVEN_DEBUG T* BlackBox(T* aPtr) {
+ return static_cast<T*>(BlackBoxVoidPtr(aPtr));
+}
+
+} // namespace mozilla
+
+#endif // GTEST_BLACKBOX_H
diff --git a/testing/gtest/benchmark/LICENSE b/testing/gtest/benchmark/LICENSE
new file mode 100644
index 0000000000..d645695673
--- /dev/null
+++ b/testing/gtest/benchmark/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/testing/gtest/benchmark/README.txt b/testing/gtest/benchmark/README.txt
new file mode 100644
index 0000000000..0fdf66011e
--- /dev/null
+++ b/testing/gtest/benchmark/README.txt
@@ -0,0 +1,5 @@
+The files in this directory are adapted from the Google benchmark library
+(https://github.com/google/benchmark) at git revision
+a96ff121b34532bb007c51ffd8e626e38decd732.
+
+The AUTHORS and LICENSE files in this directory are copied from there.
diff --git a/testing/gtest/benchmark/moz.build b/testing/gtest/benchmark/moz.build
new file mode 100644
index 0000000000..19cd3aee45
--- /dev/null
+++ b/testing/gtest/benchmark/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+if CONFIG["ENABLE_TESTS"]:
+ SOURCES += [
+ "BlackBox.cpp",
+ ]
+
+ FINAL_LIBRARY = "xul-gtest"
diff --git a/testing/gtest/mach_test_package_commands.py b/testing/gtest/mach_test_package_commands.py
new file mode 100644
index 0000000000..0b11db8997
--- /dev/null
+++ b/testing/gtest/mach_test_package_commands.py
@@ -0,0 +1,118 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import sys
+from argparse import Namespace
+
+from mach.decorators import Command
+
+here = os.path.abspath(os.path.dirname(__file__))
+parser = None
+logger = None
+
+
+def run_gtest(context, **kwargs):
+ from mozlog.commandline import setup_logging
+
+ if not kwargs.get("log"):
+ kwargs["log"] = setup_logging("gtest", kwargs, {"mach": sys.stdout})
+ global logger
+ logger = kwargs["log"]
+
+ args = Namespace(**kwargs)
+
+ import mozinfo
+
+ if mozinfo.info.get("buildapp") == "mobile/android":
+ return run_gtest_android(context, args)
+ return run_gtest_desktop(context, args)
+
+
+def run_gtest_desktop(context, args):
+ prog = context.firefox_bin
+ xre_path = os.path.dirname(context.firefox_bin)
+ if sys.platform == "darwin":
+ xre_path = os.path.join(xre_path, "Resources")
+ utility_path = context.bin_dir
+ cwd = os.path.join(context.package_root, "gtest")
+
+ logger.info(
+ "mach calling run_gtest with prog=%s xre_path=%s cwd=%s utility_path=%s"
+ % (prog, xre_path, cwd, utility_path)
+ )
+ # The gtest option parser ignores some options normally passed to the mozharness
+ # command, so some hacking is required, for now:
+ extra_args = [arg for arg in args.args if not arg.startswith("-")]
+ if extra_args:
+ os.environ["GTEST_FILTER"] = extra_args[0]
+ logger.info("GTEST_FILTER=%s" % extra_args[0])
+
+ import rungtests
+
+ tester = rungtests.GTests()
+ return tester.run_gtest(prog, xre_path, cwd, utility_path=utility_path)
+
+
+def run_gtest_android(context, args):
+ config = context.mozharness_config
+ if config:
+ args.adb_path = config["exes"]["adb"] % {
+ "abs_work_dir": context.mozharness_workdir
+ }
+ cwd = os.path.join(context.package_root, "gtest")
+ libxul_path = os.path.join(cwd, "gtest_bin", "gtest", "libxul.so")
+
+ logger.info(
+ "mach calling android run_gtest with package=%s cwd=%s libxul=%s"
+ % (args.package, cwd, libxul_path)
+ )
+ # The remote gtest option parser ignores some options normally passed to the mozharness
+ # command, so some hacking is required, for now:
+ extra_args = [arg for arg in args.args if not arg.startswith("-")]
+ test_filter = extra_args[0] if extra_args else None
+ logger.info("test filter=%s" % test_filter)
+
+ import remotegtests
+
+ tester = remotegtests.RemoteGTests()
+ return tester.run_gtest(
+ cwd,
+ args.shuffle,
+ test_filter,
+ args.package,
+ args.adb_path,
+ args.device_serial,
+ args.remote_test_root,
+ libxul_path,
+ args.symbols_path,
+ )
+
+
+def setup_argument_parser():
+ import mozinfo
+
+ mozinfo.find_and_update_from_json(here)
+ global parser
+ if mozinfo.info.get("buildapp") == "mobile/android":
+ import remotegtests
+
+ parser = remotegtests.remoteGtestOptions()
+ else:
+ import rungtests
+
+ parser = rungtests.gtestOptions()
+ return parser
+
+
+@Command(
+ "gtest",
+ category="testing",
+ description="Run the gtest harness.",
+ parser=setup_argument_parser,
+)
+def gtest(command_context, **kwargs):
+ command_context._mach_context.activate_mozharness_venv()
+ result = run_gtest(command_context._mach_context, **kwargs)
+ return 0 if result else 1
diff --git a/testing/gtest/moz.build b/testing/gtest/moz.build
new file mode 100644
index 0000000000..5d51f208eb
--- /dev/null
+++ b/testing/gtest/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.gtest += [
+ "MozGtestFriend.h",
+]
+
+with Files("**"):
+ BUG_COMPONENT = ("Testing", "GTest")
+ SCHEDULES.exclusive = ["gtest"]
+
+if CONFIG["ENABLE_TESTS"]:
+ EXPORTS.gtest += [
+ "benchmark/BlackBox.h",
+ "mozilla/MozGTestBench.h",
+ ]
+
+ DIRS += ["benchmark", "mozilla", "../../third_party/googletest"]
diff --git a/testing/gtest/mozilla/GTestRunner.cpp b/testing/gtest/mozilla/GTestRunner.cpp
new file mode 100644
index 0000000000..269adc39b3
--- /dev/null
+++ b/testing/gtest/mozilla/GTestRunner.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * * This Source Code Form is subject to the terms of the Mozilla Public
+ * * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GTestRunner.h"
+#include "gtest/gtest.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FOG.h"
+#include "mozilla/Preferences.h"
+#include "nsICrashReporter.h"
+#include "nsString.h"
+#include "testing/TestHarness.h"
+#include "prenv.h"
+#ifdef ANDROID
+# include <android/log.h>
+#endif
+#ifdef XP_WIN
+# include "mozilla/ipc/WindowsMessageLoop.h"
+#endif
+
+using ::testing::EmptyTestEventListener;
+using ::testing::InitGoogleTest;
+using ::testing::TestEventListeners;
+using ::testing::TestInfo;
+using ::testing::TestPartResult;
+using ::testing::UnitTest;
+
+namespace mozilla {
+
+#ifdef ANDROID
+# define MOZ_STDOUT_PRINT(...) \
+ __android_log_print(ANDROID_LOG_INFO, "gtest", __VA_ARGS__);
+#else
+# define MOZ_STDOUT_PRINT(...) printf(__VA_ARGS__);
+#endif
+
+#define MOZ_PRINT(...) \
+ MOZ_STDOUT_PRINT(__VA_ARGS__); \
+ if (mLogFile) { \
+ fprintf(mLogFile, __VA_ARGS__); \
+ }
+
+// See gtest.h for method documentation
+class MozillaPrinter : public EmptyTestEventListener {
+ public:
+ MozillaPrinter() : mLogFile(nullptr) {
+ char* path = PR_GetEnv("MOZ_GTEST_LOG_PATH");
+ if (path) {
+ mLogFile = fopen(path, "w");
+ }
+ }
+ virtual void OnTestProgramStart(const UnitTest& /* aUnitTest */) override {
+ MOZ_PRINT("TEST-INFO | GTest unit test starting\n");
+ }
+ virtual void OnTestProgramEnd(const UnitTest& aUnitTest) override {
+ MOZ_PRINT("TEST-%s | GTest unit test: %s\n",
+ aUnitTest.Passed() ? "PASS" : "UNEXPECTED-FAIL",
+ aUnitTest.Passed() ? "passed" : "failed");
+ MOZ_PRINT("Passed: %d\n", aUnitTest.successful_test_count());
+ MOZ_PRINT("Failed: %d\n", aUnitTest.failed_test_count());
+ if (mLogFile) {
+ fclose(mLogFile);
+ mLogFile = nullptr;
+ }
+ }
+ virtual void OnTestStart(const TestInfo& aTestInfo) override {
+ mTestInfo = &aTestInfo;
+ MOZ_PRINT("TEST-START | %s.%s\n", mTestInfo->test_case_name(),
+ mTestInfo->name());
+ }
+ virtual void OnTestPartResult(
+ const TestPartResult& aTestPartResult) override {
+ MOZ_PRINT("TEST-%s | %s.%s | %s @ %s:%i\n",
+ !aTestPartResult.failed() ? "PASS" : "UNEXPECTED-FAIL",
+ mTestInfo ? mTestInfo->test_case_name() : "?",
+ mTestInfo ? mTestInfo->name() : "?", aTestPartResult.summary(),
+ aTestPartResult.file_name(), aTestPartResult.line_number());
+ }
+ virtual void OnTestEnd(const TestInfo& aTestInfo) override {
+ MOZ_PRINT("TEST-%s | %s.%s | test completed (time: %" PRIi64 "ms)\n",
+ aTestInfo.result()->Passed() ? "PASS" : "UNEXPECTED-FAIL",
+ aTestInfo.test_case_name(), aTestInfo.name(),
+ aTestInfo.result()->elapsed_time());
+ MOZ_ASSERT(&aTestInfo == mTestInfo);
+ mTestInfo = nullptr;
+ }
+
+ const TestInfo* mTestInfo;
+ FILE* mLogFile;
+};
+
+static void ReplaceGTestLogger() {
+ // Replace the GTest logger so that it can be passed
+ // by the mozilla test parsers.
+ // Code is based on:
+ // http://googletest.googlecode.com/svn/trunk/samples/sample9_unittest.cc
+ UnitTest& unitTest = *UnitTest::GetInstance();
+ TestEventListeners& listeners = unitTest.listeners();
+ delete listeners.Release(listeners.default_result_printer());
+
+ listeners.Append(new MozillaPrinter);
+}
+
+int RunGTestFunc(int* argc, char** argv) {
+ InitGoogleTest(argc, argv);
+
+ if (getenv("MOZ_TBPL_PARSER")) {
+ ReplaceGTestLogger();
+ }
+
+ PR_SetEnv("XPCOM_DEBUG_BREAK=stack-and-abort");
+
+ ScopedXPCOM xpcom("GTest");
+
+#ifdef XP_WIN
+ mozilla::ipc::windows::InitUIThread();
+#endif
+#ifdef ANDROID
+ // On Android, gtest is running in an application, which uses a
+ // current working directory of '/' by default. Desktop tests
+ // sometimes assume that support files are in the current
+ // working directory. For compatibility with desktop, the Android
+ // harness pushes test support files to the device at the location
+ // specified by MOZ_GTEST_CWD and gtest changes the cwd to that
+ // location.
+ char* path = PR_GetEnv("MOZ_GTEST_CWD");
+ chdir(path);
+#endif
+ nsCOMPtr<nsICrashReporter> crashreporter;
+ char* crashreporterStr = PR_GetEnv("MOZ_CRASHREPORTER");
+ if (crashreporterStr && !strcmp(crashreporterStr, "1")) {
+ // TODO: move this to an even-more-common location to use in all
+ // C++ unittests
+ crashreporter = do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ if (crashreporter) {
+ printf_stderr("Setting up crash reporting\n");
+ char* path = PR_GetEnv("MOZ_GTEST_MINIDUMPS_PATH");
+ nsCOMPtr<nsIFile> file;
+ if (path) {
+ nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(path), true,
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ printf_stderr("Ignoring invalid MOZ_GTEST_MINIDUMPS_PATH\n");
+ }
+ }
+ if (!file) {
+ nsCOMPtr<nsIProperties> dirsvc =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+ nsresult rv = dirsvc->Get(NS_OS_CURRENT_WORKING_DIR,
+ NS_GET_IID(nsIFile), getter_AddRefs(file));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ }
+ crashreporter->SetEnabled(true);
+ crashreporter->SetMinidumpPath(file);
+ }
+ }
+
+ // FOG should init exactly once, as early into running as possible, to enable
+ // instrumentation tests to work properly.
+ // However, at init, Glean may decide to send a ping. So let's first tell FOG
+ // that these pings shouldn't actually be uploaded.
+ Preferences::SetInt("telemetry.fog.test.localhost_port", -1);
+ const nsCString empty;
+ RefPtr<FOG>(FOG::GetSingleton())->InitializeFOG(empty, empty);
+
+ return RUN_ALL_TESTS();
+}
+
+// We use a static var 'RunGTest' defined in nsAppRunner.cpp.
+// RunGTest is initialized to nullptr but if GTest (this file)
+// is linked in then RunGTest will be set here indicating
+// GTest is supported.
+class _InitRunGTest {
+ public:
+ _InitRunGTest() { RunGTest = RunGTestFunc; }
+} InitRunGTest;
+
+} // namespace mozilla
diff --git a/testing/gtest/mozilla/GTestRunner.h b/testing/gtest/mozilla/GTestRunner.h
new file mode 100644
index 0000000000..648a7959fa
--- /dev/null
+++ b/testing/gtest/mozilla/GTestRunner.h
@@ -0,0 +1,10 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * * This Source Code Form is subject to the terms of the Mozilla Public
+ * * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+namespace mozilla {
+
+extern int (*RunGTest)(int*, char**);
+
+} // namespace mozilla
diff --git a/testing/gtest/mozilla/MozAssertions.cpp b/testing/gtest/mozilla/MozAssertions.cpp
new file mode 100644
index 0000000000..ed9c68cb83
--- /dev/null
+++ b/testing/gtest/mozilla/MozAssertions.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MozAssertions.h"
+#include "mozilla/ErrorNames.h"
+#include "nsString.h"
+
+namespace mozilla::gtest {
+
+static testing::AssertionResult NsresultFailureHelper(const char* expr,
+ const char* expected,
+ nsresult rv) {
+ nsAutoCString name;
+ GetErrorName(rv, name);
+
+ return testing::AssertionFailure()
+ << "Expected: " << expr << " " << expected << ".\n"
+ << " Actual: " << name << "\n";
+}
+
+testing::AssertionResult IsNsresultSuccess(const char* expr, nsresult rv) {
+ if (NS_SUCCEEDED(rv)) {
+ return testing::AssertionSuccess();
+ }
+ return NsresultFailureHelper(expr, "succeeds", rv);
+}
+
+testing::AssertionResult IsNsresultFailure(const char* expr, nsresult rv) {
+ if (NS_FAILED(rv)) {
+ return testing::AssertionSuccess();
+ }
+ return NsresultFailureHelper(expr, "failed", rv);
+}
+
+} // namespace mozilla::gtest
diff --git a/testing/gtest/mozilla/MozAssertions.h b/testing/gtest/mozilla/MozAssertions.h
new file mode 100644
index 0000000000..e65231692a
--- /dev/null
+++ b/testing/gtest/mozilla/MozAssertions.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_gtest_MozAssertions_h__
+#define mozilla_gtest_MozAssertions_h__
+
+#include "gtest/gtest.h"
+#include "nsError.h"
+
+namespace mozilla::gtest {
+
+testing::AssertionResult IsNsresultSuccess(const char* expr, nsresult rv);
+testing::AssertionResult IsNsresultFailure(const char* expr, nsresult rv);
+
+} // namespace mozilla::gtest
+
+#define EXPECT_NS_SUCCEEDED(expr) \
+ EXPECT_PRED_FORMAT1(::mozilla::gtest::IsNsresultSuccess, (expr))
+
+#define ASSERT_NS_SUCCEEDED(expr) \
+ ASSERT_PRED_FORMAT1(::mozilla::gtest::IsNsresultSuccess, (expr))
+
+#define EXPECT_NS_FAILED(expr) \
+ EXPECT_PRED_FORMAT1(::mozilla::gtest::IsNsresultFailure, (expr))
+
+#define ASSERT_NS_FAILED(expr) \
+ ASSERT_PRED_FORMAT1(::mozilla::gtest::IsNsresultFailure, (expr))
+
+#endif // mozilla_gtest_MozAssertions_h__
diff --git a/testing/gtest/mozilla/MozGTestBench.cpp b/testing/gtest/mozilla/MozGTestBench.cpp
new file mode 100644
index 0000000000..cc6898099e
--- /dev/null
+++ b/testing/gtest/mozilla/MozGTestBench.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MozGTestBench.h"
+#include "mozilla/TimeStamp.h"
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#define MOZ_GTEST_BENCH_FRAMEWORK "platform_microbench"
+#define MOZ_GTEST_NUM_ITERATIONS 5
+
+namespace mozilla {
+void GTestBench(const char* aSuite, const char* aName,
+ const std::function<void()>& aTest) {
+#if defined(DEBUG) || defined(MOZ_ASAN)
+ // Run the test to make sure that it doesn't fail but don't log
+ // any measurements since it's not an optimized build.
+ aTest();
+#else
+ bool shouldAlert = bool(getenv("PERFHERDER_ALERTING_ENABLED"));
+ std::vector<int> durations;
+
+ for (int i = 0; i < MOZ_GTEST_NUM_ITERATIONS; i++) {
+ mozilla::TimeStamp start = TimeStamp::Now();
+
+ aTest();
+
+ durations.push_back((TimeStamp::Now() - start).ToMicroseconds());
+ }
+
+ std::string replicatesStr = "[" + std::to_string(durations[0]);
+ for (int i = 1; i < MOZ_GTEST_NUM_ITERATIONS; i++) {
+ replicatesStr += "," + std::to_string(durations[i]);
+ }
+ replicatesStr += "]";
+
+ // median is at index floor(i/2) if number of replicates is odd,
+ // (i/2-1) if even
+ std::sort(durations.begin(), durations.end());
+ int medianIndex =
+ (MOZ_GTEST_NUM_ITERATIONS / 2) + ((durations.size() % 2 == 0) ? (-1) : 0);
+
+ // Print the result for each test. Let perfherder aggregate for us
+ printf(
+ "PERFHERDER_DATA: {\"framework\": {\"name\": \"%s\"}, "
+ "\"suites\": [{\"name\": \"%s\", \"subtests\": "
+ "[{\"name\": \"%s\", \"value\": %i, \"replicates\": %s, "
+ "\"lowerIsBetter\": true, \"shouldAlert\": %s}]"
+ "}]}\n",
+ MOZ_GTEST_BENCH_FRAMEWORK, aSuite, aName, durations[medianIndex],
+ replicatesStr.c_str(), shouldAlert ? "true" : "false");
+#endif
+}
+
+} // namespace mozilla
diff --git a/testing/gtest/mozilla/MozGTestBench.h b/testing/gtest/mozilla/MozGTestBench.h
new file mode 100644
index 0000000000..438b977e4b
--- /dev/null
+++ b/testing/gtest/mozilla/MozGTestBench.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTEST_MOZGTESTBENCH_H
+#define GTEST_MOZGTESTBENCH_H
+
+#include <functional>
+
+namespace mozilla {
+
+void GTestBench(const char* aSuite, const char* aName,
+ const std::function<void()>& aTest);
+
+} // namespace mozilla
+
+#define MOZ_GTEST_BENCH(suite, test, lambdaOrFunc) \
+ TEST(suite, test) \
+ { mozilla::GTestBench(#suite, #test, lambdaOrFunc); }
+
+#define MOZ_GTEST_BENCH_F(suite, test, lambdaOrFunc) \
+ TEST_F(suite, test) { mozilla::GTestBench(#suite, #test, lambdaOrFunc); }
+
+#endif // GTEST_MOZGTESTBENCH_H
diff --git a/testing/gtest/mozilla/MozHelpers.cpp b/testing/gtest/mozilla/MozHelpers.cpp
new file mode 100644
index 0000000000..78fe614964
--- /dev/null
+++ b/testing/gtest/mozilla/MozHelpers.cpp
@@ -0,0 +1,73 @@
+#include "MozHelpers.h"
+
+#include <iostream>
+
+#include "gtest/gtest-spi.h"
+#include "mozilla/Mutex.h"
+#include "nsDebug.h"
+
+namespace mozilla::gtest {
+
+void DisableCrashReporter() {
+ nsCOMPtr<nsICrashReporter> crashreporter =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ if (crashreporter) {
+ crashreporter->SetEnabled(false);
+ }
+}
+
+class ScopedTestResultReporterImpl
+ : public ScopedTestResultReporter,
+ public testing::ScopedFakeTestPartResultReporter {
+ public:
+ explicit ScopedTestResultReporterImpl(ExitMode aExitMode)
+ : testing::ScopedFakeTestPartResultReporter(INTERCEPT_ALL_THREADS,
+ nullptr),
+ mExitMode(aExitMode) {}
+
+ ~ScopedTestResultReporterImpl() {
+ switch (mExitMode) {
+ case ExitMode::ExitOnDtor:
+ exit(ExitCode(Status()));
+ case ExitMode::NoExit:
+ break;
+ }
+ }
+
+ void ReportTestPartResult(const testing::TestPartResult& aResult) override {
+ {
+ MutexAutoLock lock(mMutex);
+ if (aResult.nonfatally_failed() &&
+ mStatus < TestResultStatus::NonFatalFailure) {
+ mStatus = TestResultStatus::NonFatalFailure;
+ }
+
+ if (aResult.fatally_failed() &&
+ mStatus < TestResultStatus::FatalFailure) {
+ mStatus = TestResultStatus::FatalFailure;
+ }
+ }
+
+ std::ostringstream stream;
+ stream << aResult;
+ printf_stderr("%s\n", stream.str().c_str());
+ }
+
+ TestResultStatus Status() const override {
+ MutexAutoLock lock(mMutex);
+ return mStatus;
+ }
+
+ private:
+ const ExitMode mExitMode;
+
+ mutable Mutex mMutex{"ScopedTestResultReporterImpl::mMutex"};
+ TestResultStatus mStatus MOZ_GUARDED_BY(mMutex) = TestResultStatus::Pass;
+};
+
+UniquePtr<ScopedTestResultReporter> ScopedTestResultReporter::Create(
+ ExitMode aExitMode) {
+ return MakeUnique<ScopedTestResultReporterImpl>(aExitMode);
+}
+
+} // namespace mozilla::gtest
diff --git a/testing/gtest/mozilla/MozHelpers.h b/testing/gtest/mozilla/MozHelpers.h
new file mode 100644
index 0000000000..0251f43634
--- /dev/null
+++ b/testing/gtest/mozilla/MozHelpers.h
@@ -0,0 +1,119 @@
+#ifndef TESTING_GTEST_MOZILLA_HELPERS_H_
+#define TESTING_GTEST_MOZILLA_HELPERS_H_
+
+#include "gtest/gtest.h"
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsICrashReporter.h"
+
+#if defined(DEBUG) && !defined(XP_WIN) && !defined(ANDROID)
+# define HAS_GDB_SLEEP_DURATION 1
+extern unsigned int _gdb_sleep_duration;
+#endif
+
+namespace mozilla::gtest {
+
+#if defined(HAS_GDB_SLEEP_DURATION)
+# define ZERO_GDB_SLEEP() _gdb_sleep_duration = 0;
+
+# define SAVE_GDB_SLEEP(v) \
+ v = _gdb_sleep_duration; \
+ ZERO_GDB_SLEEP();
+# define RESTORE_GDB_SLEEP(v) _gdb_sleep_duration = v;
+
+// Some use needs to be in the global namespace
+# define SAVE_GDB_SLEEP_GLOBAL(v) \
+ v = ::_gdb_sleep_duration; \
+ ZERO_GDB_SLEEP();
+# define RESTORE_GDB_SLEEP_GLOBAL(v) ::_gdb_sleep_duration = v;
+
+# define SAVE_GDB_SLEEP_LOCAL() \
+ unsigned int _old_gdb_sleep_duration; \
+ SAVE_GDB_SLEEP(_old_gdb_sleep_duration);
+# define RESTORE_GDB_SLEEP_LOCAL() RESTORE_GDB_SLEEP(_old_gdb_sleep_duration);
+
+#else // defined(HAS_GDB_SLEEP_DURATION)
+
+# define ZERO_GDB_SLEEP() ;
+
+# define SAVE_GDB_SLEEP(v)
+# define SAVE_GDB_SLEEP_GLOBAL(v)
+# define SAVE_GDB_SLEEP_LOCAL()
+# define RESTORE_GDB_SLEEP(v)
+# define RESTORE_GDB_SLEEP_GLOBAL(v)
+# define RESTORE_GDB_SLEEP_LOCAL()
+#endif // defined(HAS_GDB_SLEEP_DURATION)
+
+// Death tests are too slow on OSX because of the system crash reporter.
+#if !defined(XP_DARWIN)
+// Wrap ASSERT_DEATH_IF_SUPPORTED to disable the crash reporter
+// when entering the subprocess, so that the expected crashes don't
+// create a minidump that the gtest harness will interpret as an error.
+# define ASSERT_DEATH_WRAP(a, b) \
+ ASSERT_DEATH_IF_SUPPORTED( \
+ { \
+ mozilla::gtest::DisableCrashReporter(); \
+ a; \
+ }, \
+ b)
+#else
+# define ASSERT_DEATH_WRAP(a, b)
+#endif
+
+void DisableCrashReporter();
+
+/**
+ * Exit mode used for ScopedTestResultReporter.
+ */
+enum class ExitMode {
+ // The user of the reporter handles exit.
+ NoExit,
+ // As the reporter goes out of scope, exit with ExitCode().
+ ExitOnDtor,
+};
+
+/**
+ * Status used by ScopedTestResultReporter.
+ */
+enum class TestResultStatus : int {
+ Pass = 0,
+ NonFatalFailure = 1,
+ FatalFailure = 2,
+};
+
+inline int ExitCode(TestResultStatus aStatus) {
+ return static_cast<int>(aStatus);
+}
+
+/**
+ * This is a helper that reports test results to stderr in death test child
+ * processes, since that is normally disabled by default (with no way of
+ * enabling).
+ *
+ * Note that for this to work as intended the death test child has to, on
+ * failure, exit with an exit code that is unexpected to the death test parent,
+ * so the parent can mark the test case as failed.
+ *
+ * If the parent expects a graceful exit (code 0), ExitCode() can be used with
+ * Status() to exit the child process.
+ *
+ * For simplicity the reporter can exit automatically as it goes out of scope,
+ * when created with ExitMode::ExitOnDtor.
+ */
+class ScopedTestResultReporter {
+ public:
+ virtual ~ScopedTestResultReporter() = default;
+
+ /**
+ * The aggregate status of all observed test results.
+ */
+ virtual TestResultStatus Status() const = 0;
+
+ static UniquePtr<ScopedTestResultReporter> Create(ExitMode aExitMode);
+};
+
+} // namespace mozilla::gtest
+
+#endif // TESTING_GTEST_MOZILLA_HELPERS_H_
diff --git a/testing/gtest/mozilla/SanityTest.cpp b/testing/gtest/mozilla/SanityTest.cpp
new file mode 100644
index 0000000000..7274c43932
--- /dev/null
+++ b/testing/gtest/mozilla/SanityTest.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * * This Source Code Form is subject to the terms of the Mozilla Public
+ * * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+using ::testing::AtLeast;
+
+// Sanity test to make sure that GTest is hooked into
+// the mozilla build system correctly
+TEST(MozillaGTestSanity, Runs)
+{ EXPECT_EQ(1, 1); }
+namespace {
+class TestMock {
+ public:
+ TestMock() {}
+ MOCK_METHOD0(MockedCall, void());
+};
+} // namespace
+TEST(MozillaGMockSanity, Runs)
+{
+ TestMock mockedClass;
+ EXPECT_CALL(mockedClass, MockedCall()).Times(AtLeast(3));
+
+ mockedClass.MockedCall();
+ mockedClass.MockedCall();
+ mockedClass.MockedCall();
+}
diff --git a/testing/gtest/mozilla/gmock-custom/README.md b/testing/gtest/mozilla/gmock-custom/README.md
new file mode 100644
index 0000000000..f6c93f616d
--- /dev/null
+++ b/testing/gtest/mozilla/gmock-custom/README.md
@@ -0,0 +1,16 @@
+# Customization Points
+
+The custom directory is an injection point for custom user configurations.
+
+## Header `gmock-port.h`
+
+The following macros can be defined:
+
+### Flag related macros:
+
+* `GMOCK_DECLARE_bool_(name)`
+* `GMOCK_DECLARE_int32_(name)`
+* `GMOCK_DECLARE_string_(name)`
+* `GMOCK_DEFINE_bool_(name, default_val, doc)`
+* `GMOCK_DEFINE_int32_(name, default_val, doc)`
+* `GMOCK_DEFINE_string_(name, default_val, doc)`
diff --git a/testing/gtest/mozilla/gmock-custom/gmock-generated-actions.h b/testing/gtest/mozilla/gmock-custom/gmock-generated-actions.h
new file mode 100644
index 0000000000..92d910cf06
--- /dev/null
+++ b/testing/gtest/mozilla/gmock-custom/gmock-generated-actions.h
@@ -0,0 +1,10 @@
+// This file was GENERATED by command:
+// pump.py gmock-generated-actions.h.pump
+// DO NOT EDIT BY HAND!!!
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
+#define GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
+
+#endif // GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
diff --git a/testing/gtest/mozilla/gmock-custom/gmock-matchers.h b/testing/gtest/mozilla/gmock-custom/gmock-matchers.h
new file mode 100644
index 0000000000..14aafaabe6
--- /dev/null
+++ b/testing/gtest/mozilla/gmock-custom/gmock-matchers.h
@@ -0,0 +1,36 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Injection point for custom user configurations. See README for details
+//
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
+#define GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
+#endif // GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
diff --git a/testing/gtest/mozilla/gmock-custom/gmock-port.h b/testing/gtest/mozilla/gmock-custom/gmock-port.h
new file mode 100644
index 0000000000..0030fe9111
--- /dev/null
+++ b/testing/gtest/mozilla/gmock-custom/gmock-port.h
@@ -0,0 +1,39 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
+#define GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
+
+#endif // GMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
diff --git a/testing/gtest/mozilla/gtest-custom/README.md b/testing/gtest/mozilla/gtest-custom/README.md
new file mode 100644
index 0000000000..ff391fb4e2
--- /dev/null
+++ b/testing/gtest/mozilla/gtest-custom/README.md
@@ -0,0 +1,56 @@
+# Customization Points
+
+The custom directory is an injection point for custom user configurations.
+
+## Header `gtest.h`
+
+### The following macros can be defined:
+
+* `GTEST_OS_STACK_TRACE_GETTER_` - The name of an implementation of
+ `OsStackTraceGetterInterface`.
+* `GTEST_CUSTOM_TEMPDIR_FUNCTION_` - An override for `testing::TempDir()`. See
+ `testing::TempDir` for semantics and signature.
+
+## Header `gtest-port.h`
+
+The following macros can be defined:
+
+### Flag related macros:
+
+* `GTEST_FLAG(flag_name)`
+* `GTEST_USE_OWN_FLAGFILE_FLAG_` - Define to 0 when the system provides its
+ own flagfile flag parsing.
+* `GTEST_DECLARE_bool_(name)`
+* `GTEST_DECLARE_int32_(name)`
+* `GTEST_DECLARE_string_(name)`
+* `GTEST_DEFINE_bool_(name, default_val, doc)`
+* `GTEST_DEFINE_int32_(name, default_val, doc)`
+* `GTEST_DEFINE_string_(name, default_val, doc)`
+
+### Logging:
+
+* `GTEST_LOG_(severity)`
+* `GTEST_CHECK_(condition)`
+* Functions `LogToStderr()` and `FlushInfoLog()` have to be provided too.
+
+### Threading:
+
+* `GTEST_HAS_NOTIFICATION_` - Enabled if Notification is already provided.
+* `GTEST_HAS_MUTEX_AND_THREAD_LOCAL_` - Enabled if `Mutex` and `ThreadLocal`
+ are already provided. Must also provide `GTEST_DECLARE_STATIC_MUTEX_(mutex)`
+ and `GTEST_DEFINE_STATIC_MUTEX_(mutex)`
+* `GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks)`
+* `GTEST_LOCK_EXCLUDED_(locks)`
+
+### Underlying library support features
+
+* `GTEST_HAS_CXXABI_H_`
+
+### Exporting API symbols:
+
+* `GTEST_API_` - Specifier for exported symbols.
+
+## Header `gtest-printers.h`
+
+* See documentation at `gtest/gtest-printers.h` for details on how to define a
+ custom printer.
diff --git a/testing/gtest/mozilla/gtest-custom/gtest-port.h b/testing/gtest/mozilla/gtest-custom/gtest-port.h
new file mode 100644
index 0000000000..3159a6671a
--- /dev/null
+++ b/testing/gtest/mozilla/gtest-custom/gtest-port.h
@@ -0,0 +1,39 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
+
+#define GTEST_API_ /* nothing */
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
diff --git a/testing/gtest/mozilla/gtest-custom/gtest-printers.h b/testing/gtest/mozilla/gtest-custom/gtest-printers.h
new file mode 100644
index 0000000000..eb4467abca
--- /dev/null
+++ b/testing/gtest/mozilla/gtest-custom/gtest-printers.h
@@ -0,0 +1,42 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This file provides an injection point for custom printers in a local
+// installation of gTest.
+// It will be included from gtest-printers.h and the overrides in this file
+// will be visible to everyone.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
diff --git a/testing/gtest/mozilla/gtest-custom/gtest.h b/testing/gtest/mozilla/gtest-custom/gtest.h
new file mode 100644
index 0000000000..4c8e07be23
--- /dev/null
+++ b/testing/gtest/mozilla/gtest-custom/gtest.h
@@ -0,0 +1,37 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_
+#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_
+
+#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_H_
diff --git a/testing/gtest/mozilla/moz.build b/testing/gtest/mozilla/moz.build
new file mode 100644
index 0000000000..570d377533
--- /dev/null
+++ b/testing/gtest/mozilla/moz.build
@@ -0,0 +1,38 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+if CONFIG["ENABLE_TESTS"]:
+ # Export the gtest-custom files so we can override configuration options as
+ # recommended by gtest.
+ EXPORTS.gtest.internal.custom += [
+ "gtest-custom/gtest-port.h",
+ "gtest-custom/gtest-printers.h",
+ "gtest-custom/gtest.h",
+ ]
+ EXPORTS.gmock.internal.custom += [
+ "gmock-custom/gmock-generated-actions.h",
+ "gmock-custom/gmock-matchers.h",
+ "gmock-custom/gmock-port.h",
+ ]
+
+ EXPORTS.mozilla.gtest += [
+ "MozAssertions.h",
+ "MozHelpers.h",
+ ]
+
+ SOURCES += [
+ "GTestRunner.cpp",
+ "MozAssertions.cpp",
+ "MozGTestBench.cpp",
+ "MozHelpers.cpp",
+ "SanityTest.cpp",
+ ]
+
+ if CONFIG["OS_ARCH"] == "WINNT":
+ LOCAL_INCLUDES += [
+ "/security/sandbox/chromium",
+ ]
+
+ FINAL_LIBRARY = "xul-gtest"
diff --git a/testing/gtest/remotegtests.py b/testing/gtest/remotegtests.py
new file mode 100644
index 0000000000..e2073b6719
--- /dev/null
+++ b/testing/gtest/remotegtests.py
@@ -0,0 +1,478 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import argparse
+import datetime
+import glob
+import os
+import posixpath
+import shutil
+import sys
+import tempfile
+import time
+import traceback
+
+import mozcrash
+import mozdevice
+import mozinfo
+import mozlog
+import six
+
+LOGGER_NAME = "gtest"
+log = mozlog.unstructured.getLogger(LOGGER_NAME)
+
+
+class RemoteGTests(object):
+ """
+ A test harness to run gtest on Android.
+ """
+
+ def __init__(self):
+ self.device = None
+
+ def build_environment(self, shuffle, test_filter):
+ """
+ Create and return a dictionary of all the appropriate env variables
+ and values.
+ """
+ env = {}
+ env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
+ env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
+ env["MOZ_CRASHREPORTER"] = "1"
+ env["MOZ_RUN_GTEST"] = "1"
+ # custom output parser is mandatory on Android
+ env["MOZ_TBPL_PARSER"] = "1"
+ env["MOZ_GTEST_LOG_PATH"] = self.remote_log
+ env["MOZ_GTEST_CWD"] = self.remote_profile
+ env["MOZ_GTEST_MINIDUMPS_PATH"] = self.remote_minidumps
+ env["MOZ_IN_AUTOMATION"] = "1"
+ env["MOZ_ANDROID_LIBDIR_OVERRIDE"] = posixpath.join(
+ self.remote_libdir, "libxul.so"
+ )
+ if shuffle:
+ env["GTEST_SHUFFLE"] = "True"
+ if test_filter:
+ env["GTEST_FILTER"] = test_filter
+
+ # webrender needs gfx.webrender.all=true, gtest doesn't use prefs
+ env["MOZ_WEBRENDER"] = "1"
+
+ return env
+
+ def run_gtest(
+ self,
+ test_dir,
+ shuffle,
+ test_filter,
+ package,
+ adb_path,
+ device_serial,
+ remote_test_root,
+ libxul_path,
+ symbols_path,
+ ):
+ """
+ Launch the test app, run gtest, collect test results and wait for completion.
+ Return False if a crash or other failure is detected, else True.
+ """
+ update_mozinfo()
+ self.device = mozdevice.ADBDeviceFactory(
+ adb=adb_path,
+ device=device_serial,
+ test_root=remote_test_root,
+ logger_name=LOGGER_NAME,
+ verbose=False,
+ run_as_package=package,
+ )
+ root = self.device.test_root
+ self.remote_profile = posixpath.join(root, "gtest-profile")
+ self.remote_minidumps = posixpath.join(root, "gtest-minidumps")
+ self.remote_log = posixpath.join(root, "gtest.log")
+ self.remote_libdir = posixpath.join(root, "gtest")
+
+ self.package = package
+ self.cleanup()
+ self.device.mkdir(self.remote_profile)
+ self.device.mkdir(self.remote_minidumps)
+ self.device.mkdir(self.remote_libdir)
+
+ log.info("Running Android gtest")
+ if not self.device.is_app_installed(self.package):
+ raise Exception("%s is not installed on this device" % self.package)
+
+ # Push the gtest libxul.so to the device. The harness assumes an architecture-
+ # appropriate library is specified and pushes it to the arch-agnostic remote
+ # directory.
+ # TODO -- consider packaging the gtest libxul.so in an apk
+ self.device.push(libxul_path, self.remote_libdir)
+
+ # Push support files to device. Avoid sub-directories so that libxul.so
+ # is not included.
+ for f in glob.glob(os.path.join(test_dir, "*")):
+ if not os.path.isdir(f):
+ self.device.push(f, self.remote_profile)
+
+ if test_filter is not None:
+ test_filter = six.ensure_text(test_filter)
+ env = self.build_environment(shuffle, test_filter)
+ args = [
+ "-unittest",
+ "--gtest_death_test_style=threadsafe",
+ "-profile %s" % self.remote_profile,
+ ]
+ if "geckoview" in self.package:
+ activity = "TestRunnerActivity"
+ self.device.launch_activity(
+ self.package,
+ activity_name=activity,
+ e10s=False, # gtest is non-e10s on desktop
+ moz_env=env,
+ extra_args=args,
+ wait=False,
+ )
+ else:
+ self.device.launch_fennec(self.package, moz_env=env, extra_args=args)
+ waiter = AppWaiter(self.device, self.remote_log)
+ timed_out = waiter.wait(self.package)
+ self.shutdown(use_kill=True if timed_out else False)
+ if self.check_for_crashes(symbols_path):
+ return False
+ return True
+
+ def shutdown(self, use_kill):
+ """
+ Stop the remote application.
+ If use_kill is specified, a multi-stage kill procedure is used,
+ attempting to trigger ANR and minidump reports before ending
+ the process.
+ """
+ if not use_kill:
+ self.device.stop_application(self.package)
+ else:
+ # Trigger an ANR report with "kill -3" (SIGQUIT)
+ try:
+ self.device.pkill(self.package, sig=3, attempts=1)
+ except mozdevice.ADBTimeoutError:
+ raise
+ except: # NOQA: E722
+ pass
+ time.sleep(3)
+ # Trigger a breakpad dump with "kill -6" (SIGABRT)
+ try:
+ self.device.pkill(self.package, sig=6, attempts=1)
+ except mozdevice.ADBTimeoutError:
+ raise
+ except: # NOQA: E722
+ pass
+ # Wait for process to end
+ retries = 0
+ while retries < 3:
+ if self.device.process_exist(self.package):
+ log.info("%s still alive after SIGABRT: waiting..." % self.package)
+ time.sleep(5)
+ else:
+ break
+ retries += 1
+ if self.device.process_exist(self.package):
+ try:
+ self.device.pkill(self.package, sig=9, attempts=1)
+ except mozdevice.ADBTimeoutError:
+ raise
+ except: # NOQA: E722
+ log.warning("%s still alive after SIGKILL!" % self.package)
+ if self.device.process_exist(self.package):
+ self.device.stop_application(self.package)
+ # Test harnesses use the MOZ_CRASHREPORTER environment variables to suppress
+ # the interactive crash reporter, but that may not always be effective;
+ # check for and cleanup errant crashreporters.
+ crashreporter = "%s.CrashReporter" % self.package
+ if self.device.process_exist(crashreporter):
+ log.warning("%s unexpectedly found running. Killing..." % crashreporter)
+ try:
+ self.device.pkill(crashreporter)
+ except mozdevice.ADBTimeoutError:
+ raise
+ except: # NOQA: E722
+ pass
+ if self.device.process_exist(crashreporter):
+ log.error("%s still running!!" % crashreporter)
+
+ def check_for_crashes(self, symbols_path):
+ """
+ Pull minidumps from the remote device and generate crash reports.
+ Returns True if a crash was detected, or suspected.
+ """
+ try:
+ dump_dir = tempfile.mkdtemp()
+ remote_dir = self.remote_minidumps
+ if not self.device.is_dir(remote_dir):
+ return False
+ self.device.pull(remote_dir, dump_dir)
+ crashed = mozcrash.check_for_crashes(
+ dump_dir, symbols_path, test_name="gtest"
+ )
+ except Exception as e:
+ log.error("unable to check for crashes: %s" % str(e))
+ crashed = True
+ finally:
+ try:
+ shutil.rmtree(dump_dir)
+ except Exception:
+ log.warning("unable to remove directory: %s" % dump_dir)
+ return crashed
+
+ def cleanup(self):
+ if self.device:
+ self.device.stop_application(self.package)
+ self.device.rm(self.remote_log, force=True)
+ self.device.rm(self.remote_profile, recursive=True, force=True)
+ self.device.rm(self.remote_minidumps, recursive=True, force=True)
+ self.device.rm(self.remote_libdir, recursive=True, force=True)
+
+
+class AppWaiter(object):
+ def __init__(
+ self,
+ device,
+ remote_log,
+ test_proc_timeout=1200,
+ test_proc_no_output_timeout=300,
+ test_proc_start_timeout=60,
+ output_poll_interval=10,
+ ):
+ self.device = device
+ self.remote_log = remote_log
+ self.start_time = datetime.datetime.now()
+ self.timeout_delta = datetime.timedelta(seconds=test_proc_timeout)
+ self.output_timeout_delta = datetime.timedelta(
+ seconds=test_proc_no_output_timeout
+ )
+ self.start_timeout_delta = datetime.timedelta(seconds=test_proc_start_timeout)
+ self.output_poll_interval = output_poll_interval
+ self.last_output_time = datetime.datetime.now()
+ self.remote_log_len = 0
+
+ def start_timed_out(self):
+ if datetime.datetime.now() - self.start_time > self.start_timeout_delta:
+ return True
+ return False
+
+ def timed_out(self):
+ if datetime.datetime.now() - self.start_time > self.timeout_delta:
+ return True
+ return False
+
+ def output_timed_out(self):
+ if datetime.datetime.now() - self.last_output_time > self.output_timeout_delta:
+ return True
+ return False
+
+ def get_top(self):
+ top = self.device.get_top_activity(timeout=60)
+ if top is None:
+ log.info("Failed to get top activity, retrying, once...")
+ top = self.device.get_top_activity(timeout=60)
+ return top
+
+ def wait_for_start(self, package):
+ top = None
+ while top != package and not self.start_timed_out():
+ if self.update_log():
+ # if log content is available, assume the app started; otherwise,
+ # a short run (few tests) might complete without ever being detected
+ # in the foreground
+ return package
+ time.sleep(1)
+ top = self.get_top()
+ return top
+
+ def wait(self, package):
+ """
+ Wait until:
+ - the app loses foreground, or
+ - no new output is observed for the output timeout, or
+ - the timeout is exceeded.
+ While waiting, update the log every periodically: pull the gtest log from
+ device and log any new content.
+ """
+ top = self.wait_for_start(package)
+ if top != package:
+ log.testFail("gtest | %s failed to start" % package)
+ return
+ while not self.timed_out():
+ if not self.update_log():
+ top = self.get_top()
+ if top != package or self.output_timed_out():
+ time.sleep(self.output_poll_interval)
+ break
+ time.sleep(self.output_poll_interval)
+ self.update_log()
+ if self.timed_out():
+ log.testFail(
+ "gtest | timed out after %d seconds", self.timeout_delta.seconds
+ )
+ elif self.output_timed_out():
+ log.testFail(
+ "gtest | timed out after %d seconds without output",
+ self.output_timeout_delta.seconds,
+ )
+ else:
+ log.info("gtest | wait for %s complete; top activity=%s" % (package, top))
+ return True if top == package else False
+
+ def update_log(self):
+ """
+ Pull the test log from the remote device and display new content.
+ """
+ if not self.device.is_file(self.remote_log):
+ log.info("gtest | update_log %s is not a file." % self.remote_log)
+ return False
+ try:
+ new_content = self.device.get_file(
+ self.remote_log, offset=self.remote_log_len
+ )
+ except mozdevice.ADBTimeoutError:
+ raise
+ except Exception as e:
+ log.info("gtest | update_log : exception reading log: %s" % str(e))
+ return False
+ if not new_content:
+ log.info("gtest | update_log : no new content")
+ return False
+ new_content = six.ensure_text(new_content)
+ last_full_line_pos = new_content.rfind("\n")
+ if last_full_line_pos <= 0:
+ # wait for a full line
+ return False
+ # trim partial line
+ new_content = new_content[:last_full_line_pos]
+ self.remote_log_len += len(new_content)
+ for line in new_content.lstrip("\n").split("\n"):
+ print(line)
+ self.last_output_time = datetime.datetime.now()
+ return True
+
+
+class remoteGtestOptions(argparse.ArgumentParser):
+ def __init__(self):
+ super(remoteGtestOptions, self).__init__(
+ usage="usage: %prog [options] test_filter"
+ )
+ self.add_argument(
+ "--package",
+ dest="package",
+ default="org.mozilla.geckoview.test_runner",
+ help="Package name of test app.",
+ )
+ self.add_argument(
+ "--adbpath",
+ action="store",
+ type=str,
+ dest="adb_path",
+ default="adb",
+ help="Path to adb binary.",
+ )
+ self.add_argument(
+ "--deviceSerial",
+ action="store",
+ type=str,
+ dest="device_serial",
+ help="adb serial number of remote device. This is required "
+ "when more than one device is connected to the host. "
+ "Use 'adb devices' to see connected devices. ",
+ )
+ self.add_argument(
+ "--remoteTestRoot",
+ action="store",
+ type=str,
+ dest="remote_test_root",
+ help="Remote directory to use as test root "
+ "(eg. /data/local/tmp/test_root).",
+ )
+ self.add_argument(
+ "--libxul",
+ action="store",
+ type=str,
+ dest="libxul_path",
+ default=None,
+ help="Path to gtest libxul.so.",
+ )
+ self.add_argument(
+ "--symbols-path",
+ dest="symbols_path",
+ default=None,
+ help="absolute path to directory containing breakpad "
+ "symbols, or the URL of a zip file containing symbols",
+ )
+ self.add_argument(
+ "--shuffle",
+ action="store_true",
+ default=False,
+ help="Randomize the execution order of tests.",
+ )
+ self.add_argument(
+ "--tests-path",
+ default=None,
+ help="Path to gtest directory containing test support files.",
+ )
+ self.add_argument("args", nargs=argparse.REMAINDER)
+
+
+def update_mozinfo():
+ """
+ Walk up directories to find mozinfo.json and update the info.
+ """
+ path = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
+ dirs = set()
+ while path != os.path.expanduser("~"):
+ if path in dirs:
+ break
+ dirs.add(path)
+ path = os.path.split(path)[0]
+ mozinfo.find_and_update_from_json(*dirs)
+
+
+def main():
+ parser = remoteGtestOptions()
+ options = parser.parse_args()
+ args = options.args
+ if not options.libxul_path:
+ parser.error("--libxul is required")
+ sys.exit(1)
+ if len(args) > 1:
+ parser.error("only one test_filter is allowed")
+ sys.exit(1)
+ test_filter = args[0] if args else None
+ tester = RemoteGTests()
+ result = False
+ try:
+ device_exception = False
+ result = tester.run_gtest(
+ options.tests_path,
+ options.shuffle,
+ test_filter,
+ options.package,
+ options.adb_path,
+ options.device_serial,
+ options.remote_test_root,
+ options.libxul_path,
+ options.symbols_path,
+ )
+ except KeyboardInterrupt:
+ log.info("gtest | Received keyboard interrupt")
+ except Exception as e:
+ log.error(str(e))
+ traceback.print_exc()
+ if isinstance(e, mozdevice.ADBTimeoutError):
+ device_exception = True
+ finally:
+ if not device_exception:
+ tester.cleanup()
+ sys.exit(0 if result else 1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/testing/gtest/rungtests.py b/testing/gtest/rungtests.py
new file mode 100644
index 0000000000..a6bdc92c6d
--- /dev/null
+++ b/testing/gtest/rungtests.py
@@ -0,0 +1,269 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import argparse
+import os
+import sys
+
+import mozcrash
+import mozinfo
+import mozlog
+import mozprocess
+from mozrunner.utils import get_stack_fixer_function
+
+log = mozlog.unstructured.getLogger("gtest")
+
+
+class GTests(object):
+ # Time (seconds) to wait for test process to complete
+ TEST_PROC_TIMEOUT = 2400
+ # Time (seconds) in which process will be killed if it produces no output.
+ TEST_PROC_NO_OUTPUT_TIMEOUT = 300
+
+ def run_gtest(
+ self,
+ prog,
+ xre_path,
+ cwd,
+ symbols_path=None,
+ utility_path=None,
+ ):
+ """
+ Run a single C++ unit test program.
+
+ Arguments:
+ * prog: The path to the test program to run.
+ * env: The environment to use for running the program.
+ * cwd: The directory to run tests from (support files will be found
+ in this direcotry).
+ * symbols_path: A path to a directory containing Breakpad-formatted
+ symbol files for producing stack traces on crash.
+ * utility_path: A path to a directory containing utility programs.
+ currently used to locate a stack fixer to provide
+ symbols symbols for assertion stacks.
+
+ Return True if the program exits with a zero status, False otherwise.
+ """
+ self.xre_path = xre_path
+ env = self.build_environment()
+ log.info("Running gtest")
+
+ if cwd and not os.path.isdir(cwd):
+ os.makedirs(cwd)
+
+ stream_output = mozprocess.StreamOutput(sys.stdout)
+ process_output = stream_output
+ if utility_path:
+ stack_fixer = get_stack_fixer_function(utility_path, symbols_path)
+ if stack_fixer:
+
+ def f(line):
+ return stream_output(stack_fixer(line))
+
+ process_output = f
+
+ proc = None
+
+ def timeout_handler():
+ mozcrash.kill_and_get_minidump(proc.pid, cwd, utility_path)
+
+ proc = mozprocess.ProcessHandler(
+ [prog, "-unittest", "--gtest_death_test_style=threadsafe"],
+ cwd=cwd,
+ env=env,
+ kill_on_timeout=False,
+ onTimeout=(timeout_handler,),
+ processOutputLine=process_output,
+ )
+
+ proc.run(
+ timeout=GTests.TEST_PROC_TIMEOUT,
+ outputTimeout=GTests.TEST_PROC_NO_OUTPUT_TIMEOUT,
+ )
+ proc.wait()
+ log.info("gtest | process wait complete, returncode=%s" % proc.proc.returncode)
+ if proc.timedOut:
+ if proc.outputTimedOut:
+ log.testFail(
+ "gtest | timed out after %d seconds without output",
+ GTests.TEST_PROC_NO_OUTPUT_TIMEOUT,
+ )
+ else:
+ log.testFail(
+ "gtest | timed out after %d seconds", GTests.TEST_PROC_TIMEOUT
+ )
+ mozcrash.check_for_crashes(cwd, symbols_path, test_name="gtest")
+ return False
+ if mozcrash.check_for_crashes(cwd, symbols_path, test_name="gtest"):
+ # mozcrash will output the log failure line for us.
+ return False
+ result = proc.proc.returncode == 0
+ if not result:
+ log.testFail(
+ "gtest | test failed with return code %d", proc.proc.returncode
+ )
+ return result
+
+ def build_core_environment(self, env={}):
+ """
+ Add environment variables likely to be used across all platforms, including remote systems.
+ """
+ env["MOZ_XRE_DIR"] = self.xre_path
+ env["MOZ_GMP_PATH"] = os.pathsep.join(
+ os.path.join(self.xre_path, p, "1.0")
+ for p in ("gmp-fake", "gmp-fakeopenh264")
+ )
+ env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
+ env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
+ env["MOZ_CRASHREPORTER"] = "1"
+ env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
+ env["MOZ_RUN_GTEST"] = "1"
+ # Normally we run with GTest default output, override this to use the TBPL test format.
+ env["MOZ_TBPL_PARSER"] = "1"
+
+ if not mozinfo.has_sandbox:
+ # Bug 1082193 - This is horrible. Our linux build boxes run CentOS 6,
+ # which is too old to support sandboxing. Disable sandbox for gtests
+ # on machines which don't support sandboxing until they can be
+ # upgraded, or gtests are run on test machines instead.
+ env["MOZ_DISABLE_GMP_SANDBOX"] = "1"
+
+ return env
+
+ def build_environment(self):
+ """
+ Create and return a dictionary of all the appropriate env variables
+ and values. On a remote system, we overload this to set different
+ values and are missing things like os.environ and PATH.
+ """
+ if not os.path.isdir(self.xre_path):
+ raise Exception("xre_path does not exist: %s", self.xre_path)
+ env = dict(os.environ)
+ env = self.build_core_environment(env)
+ env["PERFHERDER_ALERTING_ENABLED"] = "1"
+ pathvar = ""
+ if mozinfo.os == "linux":
+ pathvar = "LD_LIBRARY_PATH"
+ # disable alerts for unstable tests (Bug 1369807)
+ del env["PERFHERDER_ALERTING_ENABLED"]
+ elif mozinfo.os == "mac":
+ pathvar = "DYLD_LIBRARY_PATH"
+ elif mozinfo.os == "win":
+ pathvar = "PATH"
+ if pathvar:
+ if pathvar in env:
+ env[pathvar] = "%s%s%s" % (self.xre_path, os.pathsep, env[pathvar])
+ else:
+ env[pathvar] = self.xre_path
+
+ symbolizer_path = None
+ if mozinfo.info["asan"]:
+ symbolizer_path = "ASAN_SYMBOLIZER_PATH"
+ elif mozinfo.info["tsan"]:
+ symbolizer_path = "TSAN_SYMBOLIZER_PATH"
+
+ if symbolizer_path is not None:
+ # Use llvm-symbolizer for ASan/TSan if available/required
+ if symbolizer_path in env and os.path.isfile(env[symbolizer_path]):
+ llvmsym = env[symbolizer_path]
+ else:
+ llvmsym = os.path.join(
+ self.xre_path, "llvm-symbolizer" + mozinfo.info["bin_suffix"]
+ )
+ if os.path.isfile(llvmsym):
+ env[symbolizer_path] = llvmsym
+ log.info("Using LLVM symbolizer at %s", llvmsym)
+ else:
+ # This should be |testFail| instead of |info|. See bug 1050891.
+ log.info("Failed to find LLVM symbolizer at %s", llvmsym)
+
+ # webrender needs gfx.webrender.all=true, gtest doesn't use prefs
+ env["MOZ_WEBRENDER"] = "1"
+ env["MOZ_ACCELERATED"] = "1"
+
+ return env
+
+
+class gtestOptions(argparse.ArgumentParser):
+ def __init__(self):
+ super(gtestOptions, self).__init__()
+
+ self.add_argument(
+ "--cwd",
+ dest="cwd",
+ default=os.getcwd(),
+ help="absolute path to directory from which " "to run the binary",
+ )
+ self.add_argument(
+ "--xre-path",
+ dest="xre_path",
+ default=None,
+ help="absolute path to directory containing XRE " "(probably xulrunner)",
+ )
+ self.add_argument(
+ "--symbols-path",
+ dest="symbols_path",
+ default=None,
+ help="absolute path to directory containing breakpad "
+ "symbols, or the URL of a zip file containing "
+ "symbols",
+ )
+ self.add_argument(
+ "--utility-path",
+ dest="utility_path",
+ default=None,
+ help="path to a directory containing utility program binaries",
+ )
+ self.add_argument("args", nargs=argparse.REMAINDER)
+
+
+def update_mozinfo():
+ """walk up directories to find mozinfo.json update the info"""
+ path = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
+ dirs = set()
+ while path != os.path.expanduser("~"):
+ if path in dirs:
+ break
+ dirs.add(path)
+ path = os.path.split(path)[0]
+ mozinfo.find_and_update_from_json(*dirs)
+
+
+def main():
+ parser = gtestOptions()
+ options = parser.parse_args()
+ args = options.args
+ if not args:
+ print("Usage: %s <binary>" % sys.argv[0])
+ sys.exit(1)
+ if not options.xre_path:
+ print("Error: --xre-path is required")
+ sys.exit(1)
+ if not options.utility_path:
+ print("Warning: --utility-path is required to process assertion stacks")
+
+ update_mozinfo()
+ prog = os.path.abspath(args[0])
+ options.xre_path = os.path.abspath(options.xre_path)
+ tester = GTests()
+ try:
+ result = tester.run_gtest(
+ prog,
+ options.xre_path,
+ options.cwd,
+ symbols_path=options.symbols_path,
+ utility_path=options.utility_path,
+ )
+ except Exception as e:
+ log.error(str(e))
+ result = False
+ exit_code = 0 if result else 1
+ log.info("rungtests.py exits with code %s" % exit_code)
+ sys.exit(exit_code)
+
+
+if __name__ == "__main__":
+ main()