From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- testing/gtest/MozGtestFriend.h | 15 + testing/gtest/bench.py | 24 ++ testing/gtest/benchmark/AUTHORS | 39 ++ testing/gtest/benchmark/BlackBox.cpp | 25 ++ testing/gtest/benchmark/BlackBox.h | 61 +++ testing/gtest/benchmark/LICENSE | 202 +++++++++ testing/gtest/benchmark/README.txt | 5 + testing/gtest/benchmark/moz.build | 11 + testing/gtest/mach_test_package_commands.py | 118 +++++ testing/gtest/moz.build | 21 + testing/gtest/mozilla/GTestRunner.cpp | 179 ++++++++ testing/gtest/mozilla/GTestRunner.h | 10 + testing/gtest/mozilla/MozAssertions.cpp | 38 ++ testing/gtest/mozilla/MozAssertions.h | 32 ++ testing/gtest/mozilla/MozGTestBench.cpp | 58 +++ testing/gtest/mozilla/MozGTestBench.h | 25 ++ testing/gtest/mozilla/MozHelpers.cpp | 73 ++++ testing/gtest/mozilla/MozHelpers.h | 119 +++++ testing/gtest/mozilla/SanityTest.cpp | 30 ++ testing/gtest/mozilla/WaitFor.cpp | 19 + testing/gtest/mozilla/WaitFor.h | 136 ++++++ testing/gtest/mozilla/gmock-custom/README.md | 16 + .../mozilla/gmock-custom/gmock-generated-actions.h | 10 + .../gtest/mozilla/gmock-custom/gmock-matchers.h | 36 ++ testing/gtest/mozilla/gmock-custom/gmock-port.h | 39 ++ testing/gtest/mozilla/gtest-custom/README.md | 56 +++ testing/gtest/mozilla/gtest-custom/gtest-port.h | 39 ++ .../gtest/mozilla/gtest-custom/gtest-printers.h | 42 ++ testing/gtest/mozilla/gtest-custom/gtest.h | 37 ++ testing/gtest/mozilla/moz.build | 40 ++ testing/gtest/remotegtests.py | 478 +++++++++++++++++++++ testing/gtest/rungtests.py | 263 ++++++++++++ 32 files changed, 2296 insertions(+) create mode 100644 testing/gtest/MozGtestFriend.h create mode 100755 testing/gtest/bench.py create mode 100644 testing/gtest/benchmark/AUTHORS create mode 100644 testing/gtest/benchmark/BlackBox.cpp create mode 100644 testing/gtest/benchmark/BlackBox.h create mode 100644 testing/gtest/benchmark/LICENSE create mode 100644 testing/gtest/benchmark/README.txt create mode 100644 testing/gtest/benchmark/moz.build create mode 100644 testing/gtest/mach_test_package_commands.py create mode 100644 testing/gtest/moz.build create mode 100644 testing/gtest/mozilla/GTestRunner.cpp create mode 100644 testing/gtest/mozilla/GTestRunner.h create mode 100644 testing/gtest/mozilla/MozAssertions.cpp create mode 100644 testing/gtest/mozilla/MozAssertions.h create mode 100644 testing/gtest/mozilla/MozGTestBench.cpp create mode 100644 testing/gtest/mozilla/MozGTestBench.h create mode 100644 testing/gtest/mozilla/MozHelpers.cpp create mode 100644 testing/gtest/mozilla/MozHelpers.h create mode 100644 testing/gtest/mozilla/SanityTest.cpp create mode 100644 testing/gtest/mozilla/WaitFor.cpp create mode 100644 testing/gtest/mozilla/WaitFor.h create mode 100644 testing/gtest/mozilla/gmock-custom/README.md create mode 100644 testing/gtest/mozilla/gmock-custom/gmock-generated-actions.h create mode 100644 testing/gtest/mozilla/gmock-custom/gmock-matchers.h create mode 100644 testing/gtest/mozilla/gmock-custom/gmock-port.h create mode 100644 testing/gtest/mozilla/gtest-custom/README.md create mode 100644 testing/gtest/mozilla/gtest-custom/gtest-port.h create mode 100644 testing/gtest/mozilla/gtest-custom/gtest-printers.h create mode 100644 testing/gtest/mozilla/gtest-custom/gtest.h create mode 100644 testing/gtest/mozilla/moz.build create mode 100644 testing/gtest/remotegtests.py create mode 100644 testing/gtest/rungtests.py (limited to 'testing/gtest') 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 +# The email address is not required for organizations. +# +# Please keep the list sorted. + +Albert Pretorius +Arne Beer +Christopher Seymour +David Coeurjolly +Dirac Research +Dominik Czarnota +Eric Fiselier +Eugene Zhuk +Evgeny Safronov +Felix Homann +Google Inc. +International Business Machines Corporation +Ismael Jimenez Martinez +Jern-Kuan Leong +JianXiong Zhou +Joao Paulo Magalhaes +Jussi Knuuttila +Kaito Udagawa +Lei Xu +Matt Clarkson +Maxim Vafin +Nick Hutchinson +Oleksandr Sochka +Paul Redmond +Radoslav Yovchev +Shuo Chen +Yixuan Qiu +Yusuke Suzuki +Zbigniew Skowron 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 +#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(UseCharPointer(reinterpret_cast(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 +MOZ_ALWAYS_INLINE_EVEN_DEBUG T* BlackBox(T* aPtr) { + return static_cast(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 +#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 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 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 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::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 +#include +#include + +#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& 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 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 + +namespace mozilla { + +void GTestBench(const char* aSuite, const char* aName, + const std::function& 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 + +#include "gtest/gtest-spi.h" +#include "mozilla/Mutex.h" +#include "nsDebug.h" + +namespace mozilla::gtest { + +void DisableCrashReporter() { + nsCOMPtr 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::Create( + ExitMode aExitMode) { + return MakeUnique(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(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 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/WaitFor.cpp b/testing/gtest/mozilla/WaitFor.cpp new file mode 100644 index 0000000000..ab96fa9ae5 --- /dev/null +++ b/testing/gtest/mozilla/WaitFor.cpp @@ -0,0 +1,19 @@ +/* -*- 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 "WaitFor.h" + +namespace mozilla { + +void WaitFor(MediaEventSource& aEvent) { + bool done = false; + MediaEventListener listener = + aEvent.Connect(AbstractThread::GetCurrent(), [&] { done = true; }); + SpinEventLoopUntil( + "WaitFor(MediaEventSource& aEvent)"_ns, [&] { return done; }); + listener.Disconnect(); +} + +} // namespace mozilla diff --git a/testing/gtest/mozilla/WaitFor.h b/testing/gtest/mozilla/WaitFor.h new file mode 100644 index 0000000000..86df72fb10 --- /dev/null +++ b/testing/gtest/mozilla/WaitFor.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */ + +#ifndef TESTING_GTEST_MOZILLA_WAITFOR_H_ +#define TESTING_GTEST_MOZILLA_WAITFOR_H_ + +#include "MediaEventSource.h" +#include "mozilla/media/MediaUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" +#include "mozilla/ResultVariant.h" +#include "mozilla/SpinEventLoopUntil.h" + +namespace mozilla { + +/** + * Waits for an occurrence of aEvent on the current thread (by blocking it, + * except tasks added to the event loop may run) and returns the event's + * templated value, if it's non-void. + * + * The caller must be wary of eventloop issues, in + * particular cases where we rely on a stable state runnable, but there is never + * a task to trigger stable state. In such cases it is the responsibility of the + * caller to create the needed tasks, as JS would. A noteworthy API that relies + * on stable state is MediaTrackGraph::GetInstance. + */ +template +T WaitFor(MediaEventSource& aEvent) { + Maybe value; + MediaEventListener listener = aEvent.Connect( + AbstractThread::GetCurrent(), [&](T aValue) { value = Some(aValue); }); + SpinEventLoopUntil( + "WaitFor(MediaEventSource& aEvent)"_ns, + [&] { return value.isSome(); }); + listener.Disconnect(); + return value.value(); +} + +/** + * Specialization of WaitFor for void. + */ +void WaitFor(MediaEventSource& aEvent); + +/** + * Variant of WaitFor that blocks the caller until a MozPromise has either been + * resolved or rejected. + */ +template +Result WaitFor(const RefPtr>& aPromise) { + Maybe success; + Maybe error; + aPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](R aResult) { success = Some(aResult); }, + [&](E aError) { error = Some(aError); }); + SpinEventLoopUntil( + "WaitFor(const RefPtr>& aPromise)"_ns, + [&] { return success.isSome() || error.isSome(); }); + if (success.isSome()) { + return success.extract(); + } + return Err(error.extract()); +} + +/** + * A variation of WaitFor that takes a callback to be called each time aEvent is + * raised. Blocks the caller until the callback function returns true. + */ +template +void WaitUntil(MediaEventSource& aEvent, CallbackFunction&& aF) { + bool done = false; + MediaEventListener listener = + aEvent.Connect(AbstractThread::GetCurrent(), [&](Args... aValue) { + if (!done) { + done = aF(std::forward(aValue)...); + } + }); + SpinEventLoopUntil( + "WaitUntil(MediaEventSource& aEvent, CallbackFunction&& aF)"_ns, + [&] { return done; }); + listener.Disconnect(); +} + +template +using TakeNPromise = MozPromise>, bool, true>; + +template +auto TakeN(MediaEventSourceImpl& aEvent, size_t aN) + -> RefPtr> { + using Storage = std::vector>; + using Promise = TakeNPromise; + using Values = media::Refcountable; + using Listener = media::Refcountable; + RefPtr values = MakeRefPtr(); + values->reserve(aN); + RefPtr listener = MakeRefPtr(); + auto promise = InvokeAsync( + AbstractThread::GetCurrent(), __func__, [values, aN]() mutable { + SpinEventLoopUntil( + "TakeN(MediaEventSourceImpl& aEvent, size_t aN)"_ns, + [&] { return values->size() == aN; }); + return Promise::CreateAndResolve(std::move(*values), __func__); + }); + *listener = aEvent.Connect(AbstractThread::GetCurrent(), + [values, listener, aN](Args... aValue) { + values->push_back({aValue...}); + if (values->size() == aN) { + listener->Disconnect(); + } + }); + return promise; +} + +/** + * Helper that, given that canonicals have just been updated on the current + * thread, will block its execution until mirrors and their watchers have + * executed on aTarget. + */ +inline void WaitForMirrors(const RefPtr& aTarget) { + Unused << WaitFor(InvokeAsync(aTarget, __func__, [] { + return GenericPromise::CreateAndResolve(true, "WaitForMirrors resolver"); + })); +} + +/** + * Short form of WaitForMirrors that assumes mirrors are on the current thread + * (like canonicals). + */ +inline void WaitForMirrors() { WaitForMirrors(GetCurrentSerialEventTarget()); } + +} // namespace mozilla + +#endif // TESTING_GTEST_MOZILLA_WAITFOR_H_ 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..e5cccadd18 --- /dev/null +++ b/testing/gtest/mozilla/moz.build @@ -0,0 +1,40 @@ +# -*- 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", + "WaitFor.h", + ] + + SOURCES += [ + "GTestRunner.cpp", + "MozAssertions.cpp", + "MozGTestBench.cpp", + "MozHelpers.cpp", + "SanityTest.cpp", + "WaitFor.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..212a562abb --- /dev/null +++ b/testing/gtest/rungtests.py @@ -0,0 +1,263 @@ +#!/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) + + stack_fixer = None + if utility_path: + stack_fixer = get_stack_fixer_function(utility_path, symbols_path) + + GTests.run_gtest.timed_out = False + + def output_line_handler(proc, line): + if stack_fixer: + print(stack_fixer(line)) + else: + print(line) + + def proc_timeout_handler(proc): + GTests.run_gtest.timed_out = True + log.testFail("gtest | timed out after %d seconds", GTests.TEST_PROC_TIMEOUT) + mozcrash.kill_and_get_minidump(proc.pid, cwd, utility_path) + + def output_timeout_handler(proc): + GTests.run_gtest.timed_out = True + log.testFail( + "gtest | timed out after %d seconds without output", + GTests.TEST_PROC_NO_OUTPUT_TIMEOUT, + ) + mozcrash.kill_and_get_minidump(proc.pid, cwd, utility_path) + + proc = mozprocess.run_and_wait( + [prog, "-unittest", "--gtest_death_test_style=threadsafe"], + cwd=cwd, + env=env, + output_line_handler=output_line_handler, + timeout=GTests.TEST_PROC_TIMEOUT, + timeout_handler=proc_timeout_handler, + output_timeout=GTests.TEST_PROC_NO_OUTPUT_TIMEOUT, + output_timeout_handler=output_timeout_handler, + ) + + log.info("gtest | process wait complete, returncode=%s" % proc.returncode) + if mozcrash.check_for_crashes(cwd, symbols_path, test_name="gtest"): + # mozcrash will output the log failure line for us. + return False + if GTests.run_gtest.timed_out: + return False + result = proc.returncode == 0 + if not result: + log.testFail("gtest | test failed with return code %d", 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 " % 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() -- cgit v1.2.3