diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-06 12:48:11 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:33:43 +0000 |
commit | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (patch) | |
tree | fa14ba0ca8d2683ba2efdabd246dc9b18a1229c6 /chromium/base/test | |
parent | 79b4f909db1049fca459c07cca55af56a9b54fe3 (diff) | |
download | qtwebengine-chromium-7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3.tar.gz |
BASELINE: Update Chromium to 84.0.4147.141
Change-Id: Ib85eb4cfa1cbe2b2b81e5022c8cad5c493969535
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/base/test')
145 files changed, 17167 insertions, 2 deletions
diff --git a/chromium/base/test/BUILD.gn b/chromium/base/test/BUILD.gn index c48169c1eef..86b11128e51 100644 --- a/chromium/base/test/BUILD.gn +++ b/chromium/base/test/BUILD.gn @@ -472,7 +472,7 @@ if (is_android) { "//base:base_java_test_support", "//testing/android/native_test:native_main_runner_java", "//third_party/android_deps:androidx_annotation_annotation_java", - "//third_party/jsr-305:jsr_305_javalib", + "//third_party/android_deps:com_google_code_findbugs_jsr305_java", ] srcjar_deps = [ ":test_support_java_aidl" ] sources = [ diff --git a/chromium/base/test/DEPS b/chromium/base/test/DEPS new file mode 100644 index 00000000000..131691a74ea --- /dev/null +++ b/chromium/base/test/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/libxml/chromium", +] diff --git a/chromium/base/test/OWNERS b/chromium/base/test/OWNERS new file mode 100644 index 00000000000..08d2b4c340d --- /dev/null +++ b/chromium/base/test/OWNERS @@ -0,0 +1,15 @@ +# Metrics-related test utilites: +per-file *scoped_feature_list*=file://base/metrics/OWNERS + +# Tracing test utilities: +per-file trace_*=file://base/trace_event/OWNERS + +#For Windows-specific test utilities: +per-file *_win*=file://base/win/OWNERS + +# For Android-specific changes: +per-file *android*=file://base/test/android/OWNERS +per-file BUILD.gn=file://base/test/android/OWNERS + +# Linux fontconfig changes +per-file *fontconfig*=file://base/nix/OWNERS diff --git a/chromium/base/test/bind_test_util.cc b/chromium/base/test/bind_test_util.cc new file mode 100644 index 00000000000..9b1cef836e8 --- /dev/null +++ b/chromium/base/test/bind_test_util.cc @@ -0,0 +1,72 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/bind_test_util.h" + +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/location.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +// A helper class for MakeExpectedRunClosure() that fails if it is +// destroyed without Run() having been called. This class may be used +// from multiple threads as long as Run() is called at most once +// before destruction. +class RunChecker { + public: + explicit RunChecker(const Location& location, + StringPiece message, + bool is_repeating) + : location_(location), + message_(message.as_string()), + is_repeating_(is_repeating) {} + + ~RunChecker() { + if (!called_) { + ADD_FAILURE_AT(location_.file_name(), location_.line_number()) + << message_; + } + } + + void Run() { + DCHECK(is_repeating_ || !called_); + called_ = true; + } + + private: + const Location location_; + const std::string message_; + const bool is_repeating_; + bool called_ = false; +}; + +} // namespace + +OnceClosure MakeExpectedRunClosure(const Location& location, + StringPiece message) { + return BindOnce(&RunChecker::Run, + Owned(new RunChecker(location, message, false))); +} + +RepeatingClosure MakeExpectedRunAtLeastOnceClosure(const Location& location, + StringPiece message) { + return BindRepeating(&RunChecker::Run, + Owned(new RunChecker(location, message, true))); +} + +RepeatingClosure MakeExpectedNotRunClosure(const Location& location, + StringPiece message) { + return BindRepeating( + [](const Location& location, StringPiece message) { + ADD_FAILURE_AT(location.file_name(), location.line_number()) << message; + }, + location, message.as_string()); +} + +} // namespace base diff --git a/chromium/base/test/bind_test_util.h b/chromium/base/test/bind_test_util.h new file mode 100644 index 00000000000..85e1f918cff --- /dev/null +++ b/chromium/base/test/bind_test_util.h @@ -0,0 +1,89 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_BIND_TEST_UTIL_H_ +#define BASE_TEST_BIND_TEST_UTIL_H_ + +#include <type_traits> +#include <utility> + +#include "base/bind.h" +#include "base/strings/string_piece.h" + +namespace base { + +class Location; + +namespace internal { + +template <typename Callable, + typename Signature = decltype(&Callable::operator())> +struct HasConstCallOperatorImpl : std::false_type {}; + +template <typename Callable, typename R, typename... Args> +struct HasConstCallOperatorImpl<Callable, R (Callable::*)(Args...) const> + : std::true_type {}; + +template <typename Callable> +constexpr bool HasConstCallOperator = + HasConstCallOperatorImpl<std::decay_t<Callable>>::value; + +template <typename F, typename Signature> +struct BindLambdaHelper; + +template <typename F, typename R, typename... Args> +struct BindLambdaHelper<F, R(Args...)> { + static R Run(const std::decay_t<F>& f, Args... args) { + return f(std::forward<Args>(args)...); + } + + static R RunOnce(std::decay_t<F>&& f, Args... args) { + return f(std::forward<Args>(args)...); + } +}; + +} // namespace internal + +// A variant of BindRepeating() that can bind capturing lambdas for testing. +// This doesn't support extra arguments binding as the lambda itself can do. +template <typename Lambda, + std::enable_if_t<internal::HasConstCallOperator<Lambda>>* = nullptr> +decltype(auto) BindLambdaForTesting(Lambda&& lambda) { + using Signature = internal::ExtractCallableRunType<std::decay_t<Lambda>>; + return BindRepeating(&internal::BindLambdaHelper<Lambda, Signature>::Run, + std::forward<Lambda>(lambda)); +} + +// A variant of BindRepeating() that can bind mutable capturing lambdas for +// testing. This doesn't support extra arguments binding as the lambda itself +// can do. Since a mutable lambda potentially can invalidate its state after +// being run once, this method returns a OnceCallback instead of a +// RepeatingCallback. +template <typename Lambda, + std::enable_if_t<!internal::HasConstCallOperator<Lambda>>* = nullptr> +decltype(auto) BindLambdaForTesting(Lambda&& lambda) { + static_assert( + std::is_rvalue_reference<Lambda&&>() && + !std::is_const<std::remove_reference_t<Lambda>>(), + "BindLambdaForTesting requires non-const rvalue for mutable lambda " + "binding. I.e.: base::BindLambdaForTesting(std::move(lambda))."); + using Signature = internal::ExtractCallableRunType<std::decay_t<Lambda>>; + return BindOnce(&internal::BindLambdaHelper<Lambda, Signature>::RunOnce, + std::move(lambda)); +} + +// Returns a closure that fails on destruction if it hasn't been run. +OnceClosure MakeExpectedRunClosure(const Location& location, + StringPiece message = StringPiece()); +RepeatingClosure MakeExpectedRunAtLeastOnceClosure( + const Location& location, + StringPiece message = StringPiece()); + +// Returns a closure that fails the test if run. +RepeatingClosure MakeExpectedNotRunClosure(const Location& location, + StringPiece message = StringPiece()); + +} // namespace base + +#endif // BASE_TEST_BIND_TEST_UTIL_H_ diff --git a/chromium/base/test/clang_profiling.cc b/chromium/base/test/clang_profiling.cc new file mode 100644 index 00000000000..5681a105e60 --- /dev/null +++ b/chromium/base/test/clang_profiling.cc @@ -0,0 +1,26 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/clang_profiling.h" + +#include "base/no_destructor.h" +#include "base/synchronization/lock.h" + +extern "C" int __llvm_profile_dump(void); + +namespace base { + +void WriteClangProfilingProfile() { + // __llvm_profile_dump() guarantees that it will not dump profiling + // information if it is being called twice or more. However, it is not thread + // safe, as it is supposed to be called from atexit() handler rather than + // being called directly from random places. Since we have to call it + // ourselves, we must ensure thread safety in order to prevent duplication of + // profiling counters. + static base::NoDestructor<base::Lock> lock; + base::AutoLock auto_lock(*lock); + __llvm_profile_dump(); +} + +} // namespace base diff --git a/chromium/base/test/clang_profiling.h b/chromium/base/test/clang_profiling.h new file mode 100644 index 00000000000..52f48e59653 --- /dev/null +++ b/chromium/base/test/clang_profiling.h @@ -0,0 +1,28 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_CLANG_PROFILING_H_ +#define BASE_TEST_CLANG_PROFILING_H_ + +#include "base/clang_profiling_buildflags.h" + +#include "base/base_export.h" + +#if !BUILDFLAG(CLANG_PROFILING) +#error "Clang profiling can only be used if CLANG_PROFILING macro is defined" +#endif + +namespace base { + +// Write out the accumulated code profiling profile to the configured file. +// This is used internally by e.g. base::Process and FATAL logging, to cause +// profiling information to be stored even when performing an "immediate" exit +// (or triggering a debug crash), where the automatic at-exit writer will not +// be invoked. +// This call is thread-safe, and will write profiling data at-most-once. +BASE_EXPORT void WriteClangProfilingProfile(); + +} // namespace base + +#endif // BASE_TEST_CLANG_PROFILING_H_ diff --git a/chromium/base/test/copy_only_int.cc b/chromium/base/test/copy_only_int.cc new file mode 100644 index 00000000000..d135a861fe8 --- /dev/null +++ b/chromium/base/test/copy_only_int.cc @@ -0,0 +1,12 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/copy_only_int.h" + +namespace base { + +// static +int CopyOnlyInt::num_copies_ = 0; + +} // namespace base diff --git a/chromium/base/test/copy_only_int.h b/chromium/base/test/copy_only_int.h new file mode 100644 index 00000000000..5cd969cf1c2 --- /dev/null +++ b/chromium/base/test/copy_only_int.h @@ -0,0 +1,61 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_COPY_ONLY_INT_H_ +#define BASE_TEST_COPY_ONLY_INT_H_ + +#include "base/macros.h" + +namespace base { + +// A copy-only (not moveable) class that holds an integer. This is designed for +// testing containers. See also MoveOnlyInt. +class CopyOnlyInt { + public: + explicit CopyOnlyInt(int data = 1) : data_(data) {} + CopyOnlyInt(const CopyOnlyInt& other) : data_(other.data_) { ++num_copies_; } + ~CopyOnlyInt() { data_ = 0; } + + friend bool operator==(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) { + return lhs.data_ == rhs.data_; + } + + friend bool operator!=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) { + return !operator==(lhs, rhs); + } + + friend bool operator<(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) { + return lhs.data_ < rhs.data_; + } + + friend bool operator>(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) { + return rhs < lhs; + } + + friend bool operator<=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) { + return !(rhs < lhs); + } + + friend bool operator>=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) { + return !(lhs < rhs); + } + + int data() const { return data_; } + + static void reset_num_copies() { num_copies_ = 0; } + + static int num_copies() { return num_copies_; } + + private: + volatile int data_; + + static int num_copies_; + + CopyOnlyInt(CopyOnlyInt&&) = delete; + CopyOnlyInt& operator=(CopyOnlyInt&) = delete; +}; + +} // namespace base + +#endif // BASE_TEST_COPY_ONLY_INT_H_ diff --git a/chromium/base/test/fontconfig_util_linux.cc b/chromium/base/test/fontconfig_util_linux.cc index b7bf9e65f70..4f6c1c71349 100644 --- a/chromium/base/test/fontconfig_util_linux.cc +++ b/chromium/base/test/fontconfig_util_linux.cc @@ -9,9 +9,9 @@ #include <memory> #include "base/base_paths.h" +#include "base/check.h" #include "base/environment.h" #include "base/files/file_path.h" -#include "base/logging.h" #include "base/path_service.h" namespace base { diff --git a/chromium/base/test/generate_fontconfig_caches.cc b/chromium/base/test/generate_fontconfig_caches.cc new file mode 100644 index 00000000000..cd01d551ef0 --- /dev/null +++ b/chromium/base/test/generate_fontconfig_caches.cc @@ -0,0 +1,66 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <fontconfig/fontconfig.h> +#include <string.h> +#include <time.h> +#include <utime.h> + +#include <string> + +#include "base/base_paths.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "base/strings/strcat.h" +#include "base/test/fontconfig_util_linux.h" + +// GIANT WARNING: The point of this file is to front-load construction of the +// font cache [which takes 600ms] from test run time to compile time. This saves +// 600ms on each test shard which uses the font cache into compile time. The +// problem is that fontconfig cache construction is not intended to be +// deterministic. This executable tries to set some external state to ensure +// determinism. We have no way of guaranteeing that this produces correct +// results, or even has the intended effect. +int main() { + // fontconfig generates a random uuid and uses it to match font folders with + // the font cache. Rather than letting fontconfig generate a random uuid, + // which introduces build non-determinism, we place a fixed uuid in the font + // folder, which fontconfig will use to generate the cache. + base::FilePath dir_module; + base::PathService::Get(base::DIR_MODULE, &dir_module); + base::FilePath uuid_file_path = + dir_module.Append("test_fonts").Append(".uuid"); + const char uuid[] = "fb5c91b2895aa445d23aebf7f9e2189c"; + WriteFile(uuid_file_path, uuid); + + // fontconfig writes the mtime of the test_fonts directory into the cache. It + // presumably checks this later to ensure that the cache is still up to date. + // We set the mtime to an arbitrary, fixed time in the past. + base::FilePath test_fonts_file_path = dir_module.Append("test_fonts"); + base::stat_wrapper_t old_times; + struct utimbuf new_times; + + base::File::Stat(test_fonts_file_path.value().c_str(), &old_times); + new_times.actime = old_times.st_atime; + // Use an arbitrary, fixed time. + new_times.modtime = 123456789; + utime(test_fonts_file_path.value().c_str(), &new_times); + + base::FilePath fontconfig_caches = dir_module.Append("fontconfig_caches"); + + // Delete directory before generating fontconfig caches. This will notify + // future fontconfig_caches changes. + CHECK(base::DeleteFileRecursively(fontconfig_caches)); + + base::SetUpFontconfig(); + FcInit(); + FcFini(); + + // Check existence of intended fontconfig cache file. + CHECK(base::PathExists( + fontconfig_caches.Append(base::StrCat({uuid, "-le64.cache-7"})))); + return 0; +} diff --git a/chromium/base/test/gmock_callback_support.h b/chromium/base/test/gmock_callback_support.h new file mode 100644 index 00000000000..61b880d9475 --- /dev/null +++ b/chromium/base/test/gmock_callback_support.h @@ -0,0 +1,150 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_GMOCK_CALLBACK_SUPPORT_H_ +#define BASE_TEST_GMOCK_CALLBACK_SUPPORT_H_ + +#include <functional> +#include <tuple> +#include <utility> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace base { +namespace test { + +// TODO(crbug.com/752720): Simplify using std::apply once C++17 is available. +template <typename CallbackFunc, typename ArgTuple, size_t... I> +decltype(auto) RunOnceCallbackUnwrapped(CallbackFunc&& f, + ArgTuple&& t, + std::index_sequence<I...>) { + return std::move(f).Run(std::get<I>(t)...); +} + +// TODO(crbug.com/752720): Simplify using std::apply once C++17 is available. +template <typename CallbackFunc, typename ArgTuple, size_t... I> +decltype(auto) RunRepeatingCallbackUnwrapped(CallbackFunc&& f, + ArgTuple&& t, + std::index_sequence<I...>) { + return f.Run(std::get<I>(t)...); +} + +// Functor used for RunOnceClosure<N>() and RunOnceCallback<N>() actions. +template <size_t I, typename... Vals> +struct RunOnceCallbackAction { + std::tuple<Vals...> vals; + + template <typename... Args> + decltype(auto) operator()(Args&&... args) { + constexpr size_t size = std::tuple_size<decltype(vals)>::value; + return RunOnceCallbackUnwrapped( + std::get<I>(std::forward_as_tuple(std::forward<Args>(args)...)), + std::move(vals), std::make_index_sequence<size>{}); + } +}; + +// Functor used for RunClosure<N>() and RunCallback<N>() actions. +template <size_t I, typename... Vals> +struct RunRepeatingCallbackAction { + std::tuple<Vals...> vals; + + template <typename... Args> + decltype(auto) operator()(Args&&... args) { + constexpr size_t size = std::tuple_size<decltype(vals)>::value; + return RunRepeatingCallbackUnwrapped( + std::get<I>(std::forward_as_tuple(std::forward<Args>(args)...)), + std::move(vals), std::make_index_sequence<size>{}); + } +}; + +// Matchers for base::{Once,Repeating}Callback and +// base::{Once,Repeating}Closure. +MATCHER(IsNullCallback, "a null callback") { + return (arg.is_null()); +} + +MATCHER(IsNotNullCallback, "a non-null callback") { + return (!arg.is_null()); +} + +// The Run[Once]Closure() action invokes the Run() method on the closure +// provided when the action is constructed. Function arguments passed when the +// action is run will be ignored. +ACTION_P(RunClosure, closure) { + closure.Run(); +} + +// This action can be invoked at most once. Any further invocation will trigger +// a CHECK failure. +inline auto RunOnceClosure(base::OnceClosure cb) { + // Mock actions need to be copyable, but OnceClosure is not. Wrap the closure + // in a base::RefCountedData<> to allow it to be copied. An alternative would + // be to use AdaptCallbackForRepeating(), but that allows the closure to be + // run more than once and silently ignores any invocation after the first. + // Since this is for use by tests, it's better to crash or CHECK-fail and + // surface the incorrect usage, rather than have a silent unexpected success. + using RefCountedOnceClosure = base::RefCountedData<base::OnceClosure>; + scoped_refptr<RefCountedOnceClosure> copyable_cb = + base::MakeRefCounted<RefCountedOnceClosure>(std::move(cb)); + return [copyable_cb](auto&&...) { + CHECK(copyable_cb->data); + std::move(copyable_cb->data).Run(); + }; +} + +// The Run[Once]Closure<N>() action invokes the Run() method on the N-th +// (0-based) argument of the mock function. +template <size_t I> +RunRepeatingCallbackAction<I> RunClosure() { + return {}; +} + +template <size_t I> +RunOnceCallbackAction<I> RunOnceClosure() { + return {}; +} + +// The Run[Once]Callback<N>(p1, p2, ..., p_k) action invokes the Run() method on +// the N-th (0-based) argument of the mock function, with arguments p1, p2, ..., +// p_k. +// +// Notes: +// +// 1. The arguments are passed by value by default. If you need to +// pass an argument by reference, wrap it inside ByRef(). For example, +// +// RunCallback<1>(5, string("Hello"), ByRef(foo)) +// +// passes 5 and string("Hello") by value, and passes foo by reference. +// +// 2. If the callback takes an argument by reference but ByRef() is +// not used, it will receive the reference to a copy of the value, +// instead of the original value. For example, when the 0-th +// argument of the callback takes a const string&, the action +// +// RunCallback<0>(string("Hello")) +// +// makes a copy of the temporary string("Hello") object and passes a +// reference of the copy, instead of the original temporary object, +// to the callback. This makes it easy for a user to define an +// RunCallback action from temporary values and have it performed later. +template <size_t I, typename... Vals> +RunOnceCallbackAction<I, std::decay_t<Vals>...> RunOnceCallback( + Vals&&... vals) { + return {std::forward_as_tuple(std::forward<Vals>(vals)...)}; +} + +template <size_t I, typename... Vals> +RunRepeatingCallbackAction<I, std::decay_t<Vals>...> RunCallback( + Vals&&... vals) { + return {std::forward_as_tuple(std::forward<Vals>(vals)...)}; +} + +} // namespace test +} // namespace base + +#endif // BASE_TEST_GMOCK_CALLBACK_SUPPORT_H_ diff --git a/chromium/base/test/gmock_callback_support_unittest.cc b/chromium/base/test/gmock_callback_support_unittest.cc new file mode 100644 index 00000000000..5aa95595427 --- /dev/null +++ b/chromium/base/test/gmock_callback_support_unittest.cc @@ -0,0 +1,178 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/gmock_callback_support.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::ByRef; +using testing::MockFunction; + +namespace base { +namespace test { + +using TestCallback = base::RepeatingCallback<void(const bool& src, bool* dst)>; +using TestOnceCallback = base::OnceCallback<void(const bool& src, bool* dst)>; + +void SetBool(const bool& src, bool* dst) { + *dst = src; +} + +TEST(GmockCallbackSupportTest, IsNullCallback) { + MockFunction<void(const TestCallback&)> check; + EXPECT_CALL(check, Call(IsNullCallback())); + check.Call(TestCallback()); +} + +TEST(GmockCallbackSupportTest, IsNotNullCallback) { + MockFunction<void(const TestCallback&)> check; + EXPECT_CALL(check, Call(IsNotNullCallback())); + check.Call(base::BindRepeating(&SetBool)); +} + +TEST(GmockCallbackSupportTest, IsNullOnceCallback) { + MockFunction<void(TestOnceCallback)> mock; + EXPECT_CALL(mock, Call(IsNullCallback())); + mock.Call(TestOnceCallback()); +} + +TEST(GmockCallbackSupportTest, IsNotNullOnceCallback) { + MockFunction<void(TestOnceCallback)> mock; + EXPECT_CALL(mock, Call(IsNotNullCallback())); + mock.Call(base::BindOnce(&SetBool)); +} + +TEST(GmockCallbackSupportTest, RunClosure0) { + MockFunction<void(const base::RepeatingClosure&)> check; + bool dst = false; + EXPECT_CALL(check, Call(IsNotNullCallback())).WillOnce(RunClosure<0>()); + check.Call(base::BindRepeating(&SetBool, true, &dst)); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunClosureByRefNotReset) { + // Check that RepeatingClosure isn't reset by RunClosure<N>(). + MockFunction<void(base::RepeatingClosure&)> check; + bool dst = false; + EXPECT_CALL(check, Call(IsNotNullCallback())).WillOnce(RunClosure<0>()); + auto closure = base::BindRepeating(&SetBool, true, &dst); + check.Call(closure); + EXPECT_TRUE(dst); + EXPECT_FALSE(closure.is_null()); +} + +TEST(GmockCallbackSupportTest, RunCallback0) { + MockFunction<void(const TestCallback&)> check; + bool dst = false; + EXPECT_CALL(check, Call(IsNotNullCallback())) + .WillOnce(RunCallback<0>(true, &dst)); + check.Call(base::BindRepeating(&SetBool)); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunCallback1) { + MockFunction<void(int, const TestCallback&)> check; + bool dst = false; + EXPECT_CALL(check, Call(0, IsNotNullCallback())) + .WillOnce(RunCallback<1>(true, &dst)); + check.Call(0, base::BindRepeating(&SetBool)); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunCallbackPassByRef) { + MockFunction<void(const TestCallback&)> check; + bool dst = false; + bool src = false; + EXPECT_CALL(check, Call(IsNotNullCallback())) + .WillOnce(RunCallback<0>(ByRef(src), &dst)); + src = true; + check.Call(base::BindRepeating(&SetBool)); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunCallbackPassByValue) { + MockFunction<void(const TestCallback&)> check; + bool dst = false; + bool src = true; + EXPECT_CALL(check, Call(IsNotNullCallback())) + .WillOnce(RunCallback<0>(src, &dst)); + src = false; + check.Call(base::BindRepeating(&SetBool)); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunOnceClosure0) { + MockFunction<void(base::OnceClosure)> check; + bool dst = false; + EXPECT_CALL(check, Call(IsNotNullCallback())).WillOnce(RunOnceClosure<0>()); + check.Call(base::BindOnce(&SetBool, true, &dst)); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunOnceCallback0) { + MockFunction<void(TestOnceCallback)> check; + bool dst = false; + bool src = true; + EXPECT_CALL(check, Call(IsNotNullCallback())) + .WillOnce(RunOnceCallback<0>(src, &dst)); + src = false; + check.Call(base::BindOnce(&SetBool)); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunClosureValue) { + MockFunction<void()> check; + bool dst = false; + EXPECT_CALL(check, Call()) + .WillOnce(RunClosure(base::BindRepeating(&SetBool, true, &dst))); + check.Call(); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunClosureValueWithArgs) { + MockFunction<void(bool, int)> check; + bool dst = false; + EXPECT_CALL(check, Call(true, 42)) + .WillOnce(RunClosure(base::BindRepeating(&SetBool, true, &dst))); + check.Call(true, 42); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunOnceClosureValue) { + MockFunction<void()> check; + bool dst = false; + EXPECT_CALL(check, Call()) + .WillOnce(RunOnceClosure(base::BindOnce(&SetBool, true, &dst))); + check.Call(); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunOnceClosureValueWithArgs) { + MockFunction<void(bool, int)> check; + bool dst = false; + EXPECT_CALL(check, Call(true, 42)) + .WillOnce(RunOnceClosure(base::BindOnce(&SetBool, true, &dst))); + check.Call(true, 42); + EXPECT_TRUE(dst); +} + +TEST(GmockCallbackSupportTest, RunOnceClosureValueMultipleCall) { + MockFunction<void()> check; + bool dst = false; + EXPECT_CALL(check, Call()) + .WillRepeatedly(RunOnceClosure(base::BindOnce(&SetBool, true, &dst))); + check.Call(); + EXPECT_TRUE(dst); + + // Invoking the RunOnceClosure action more than once will trigger a + // CHECK-failure. + dst = false; + EXPECT_DEATH_IF_SUPPORTED(check.Call(), ""); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/gmock_move_support.h b/chromium/base/test/gmock_move_support.h new file mode 100644 index 00000000000..8af9b219abe --- /dev/null +++ b/chromium/base/test/gmock_move_support.h @@ -0,0 +1,20 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_GMOCK_MOVE_SUPPORT_H_ +#define BASE_TEST_GMOCK_MOVE_SUPPORT_H_ + +#include <tuple> +#include <utility> + +// A similar action as testing::SaveArg, but it does an assignment with +// std::move() instead of always performing a copy. +template <size_t I = 0, typename T> +auto MoveArg(T* out) { + return [out](auto&&... args) { + *out = std::move(std::get<I>(std::tie(args...))); + }; +} + +#endif // BASE_TEST_GMOCK_MOVE_SUPPORT_H_ diff --git a/chromium/base/test/gmock_move_support_unittest.cc b/chromium/base/test/gmock_move_support_unittest.cc new file mode 100644 index 00000000000..e72fed77ca6 --- /dev/null +++ b/chromium/base/test/gmock_move_support_unittest.cc @@ -0,0 +1,60 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/gmock_move_support.h" + +#include <memory> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using ::testing::DoAll; +using ::testing::Pointee; + +using MoveOnly = std::unique_ptr<int>; + +struct MockFoo { + MOCK_METHOD(void, ByRef, (MoveOnly&), ()); + MOCK_METHOD(void, ByVal, (MoveOnly), ()); + MOCK_METHOD(void, TwiceByRef, (MoveOnly&, MoveOnly&), ()); +}; +} // namespace + +TEST(GmockMoveSupportTest, MoveArgByRef) { + MoveOnly result; + + MockFoo foo; + EXPECT_CALL(foo, ByRef).WillOnce(MoveArg(&result)); + MoveOnly arg = std::make_unique<int>(123); + foo.ByRef(arg); + + EXPECT_THAT(result, Pointee(123)); +} + +TEST(GmockMoveSupportTest, MoveArgByVal) { + MoveOnly result; + + MockFoo foo; + EXPECT_CALL(foo, ByVal).WillOnce(MoveArg(&result)); + foo.ByVal(std::make_unique<int>(456)); + + EXPECT_THAT(result, Pointee(456)); +} + +TEST(GmockMoveSupportTest, MoveArgsTwiceByRef) { + MoveOnly result1; + MoveOnly result2; + + MockFoo foo; + EXPECT_CALL(foo, TwiceByRef) + .WillOnce(DoAll(MoveArg<0>(&result1), MoveArg<1>(&result2))); + MoveOnly arg1 = std::make_unique<int>(123); + MoveOnly arg2 = std::make_unique<int>(456); + foo.TwiceByRef(arg1, arg2); + + EXPECT_THAT(result1, Pointee(123)); + EXPECT_THAT(result2, Pointee(456)); +} diff --git a/chromium/base/test/gtest_util.cc b/chromium/base/test/gtest_util.cc new file mode 100644 index 00000000000..5eeda3958b7 --- /dev/null +++ b/chromium/base/test/gtest_util.cc @@ -0,0 +1,111 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/gtest_util.h" + +#include <stddef.h> + +#include <memory> + +#include "base/files/file_path.h" +#include "base/json/json_file_value_serializer.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TestIdentifier::TestIdentifier() = default; + +TestIdentifier::TestIdentifier(const TestIdentifier& other) = default; + +std::string FormatFullTestName(const std::string& test_case_name, + const std::string& test_name) { + return test_case_name + "." + test_name; +} + +std::string TestNameWithoutDisabledPrefix(const std::string& full_test_name) { + std::string test_name_no_disabled(full_test_name); + ReplaceSubstringsAfterOffset(&test_name_no_disabled, 0, "DISABLED_", ""); + return test_name_no_disabled; +} + +std::vector<TestIdentifier> GetCompiledInTests() { + testing::UnitTest* const unit_test = testing::UnitTest::GetInstance(); + + std::vector<TestIdentifier> tests; + for (int i = 0; i < unit_test->total_test_case_count(); ++i) { + const testing::TestCase* test_case = unit_test->GetTestCase(i); + for (int j = 0; j < test_case->total_test_count(); ++j) { + const testing::TestInfo* test_info = test_case->GetTestInfo(j); + TestIdentifier test_data; + test_data.test_case_name = test_case->name(); + test_data.test_name = test_info->name(); + test_data.file = test_info->file(); + test_data.line = test_info->line(); + tests.push_back(test_data); + } + } + return tests; +} + +bool WriteCompiledInTestsToFile(const FilePath& path) { + std::vector<TestIdentifier> tests(GetCompiledInTests()); + + ListValue root; + for (const auto& i : tests) { + std::unique_ptr<DictionaryValue> test_info(new DictionaryValue); + test_info->SetStringKey("test_case_name", i.test_case_name); + test_info->SetStringKey("test_name", i.test_name); + test_info->SetStringKey("file", i.file); + test_info->SetIntKey("line", i.line); + root.Append(std::move(test_info)); + } + + JSONFileValueSerializer serializer(path); + return serializer.Serialize(root); +} + +bool ReadTestNamesFromFile(const FilePath& path, + std::vector<TestIdentifier>* output) { + JSONFileValueDeserializer deserializer(path); + int error_code = 0; + std::string error_message; + std::unique_ptr<base::Value> value = + deserializer.Deserialize(&error_code, &error_message); + if (!value.get()) + return false; + + base::ListValue* tests = nullptr; + if (!value->GetAsList(&tests)) + return false; + + std::vector<base::TestIdentifier> result; + for (const auto& i : *tests) { + const base::DictionaryValue* test = nullptr; + if (!i.GetAsDictionary(&test)) + return false; + + TestIdentifier test_data; + + if (!test->GetStringASCII("test_case_name", &test_data.test_case_name)) + return false; + + if (!test->GetStringASCII("test_name", &test_data.test_name)) + return false; + + if (!test->GetStringASCII("file", &test_data.file)) + return false; + + if (!test->GetInteger("line", &test_data.line)) + return false; + + result.push_back(test_data); + } + + output->swap(result); + return true; +} + +} // namespace base diff --git a/chromium/base/test/gtest_util.h b/chromium/base/test/gtest_util.h new file mode 100644 index 00000000000..1db1fae1e2d --- /dev/null +++ b/chromium/base/test/gtest_util.h @@ -0,0 +1,114 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_GTEST_UTIL_H_ +#define BASE_TEST_GTEST_UTIL_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +// EXPECT/ASSERT_DCHECK_DEATH is intended to replace EXPECT/ASSERT_DEBUG_DEATH +// when the death is expected to be caused by a DCHECK. Contrary to +// EXPECT/ASSERT_DEBUG_DEATH however, it doesn't execute the statement in non- +// dcheck builds as DCHECKs are intended to catch things that should never +// happen and as such executing the statement results in undefined behavior +// (|statement| is compiled in unsupported configurations nonetheless). +// Death tests misbehave on Android. +#if DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID) + +// EXPECT/ASSERT_DCHECK_DEATH tests verify that a DCHECK is hit ("Check failed" +// is part of the error message), but intentionally do not expose the gtest +// death test's full |regex| parameter to avoid users having to verify the exact +// syntax of the error message produced by the DCHECK. + +// Official builds will eat stream parameters, so don't check the error message. +#if defined(OFFICIAL_BUILD) && defined(NDEBUG) +#define EXPECT_DCHECK_DEATH(statement) EXPECT_DEATH(statement, "") +#define ASSERT_DCHECK_DEATH(statement) ASSERT_DEATH(statement, "") +#else +#define EXPECT_DCHECK_DEATH(statement) EXPECT_DEATH(statement, "Check failed") +#define ASSERT_DCHECK_DEATH(statement) ASSERT_DEATH(statement, "Check failed") +#endif // defined(OFFICIAL_BUILD) && defined(NDEBUG) + +#else +// DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID) + +#define EXPECT_DCHECK_DEATH(statement) \ + GTEST_UNSUPPORTED_DEATH_TEST(statement, "Check failed", ) +#define ASSERT_DCHECK_DEATH(statement) \ + GTEST_UNSUPPORTED_DEATH_TEST(statement, "Check failed", return) + +#endif +// DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID) + +// As above, but for CHECK(). +#if defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID) + +// Official builds will eat stream parameters, so don't check the error message. +#if defined(OFFICIAL_BUILD) && defined(NDEBUG) +#define EXPECT_CHECK_DEATH(statement) EXPECT_DEATH(statement, "") +#define ASSERT_CHECK_DEATH(statement) ASSERT_DEATH(statement, "") +#else +#define EXPECT_CHECK_DEATH(statement) EXPECT_DEATH(statement, "Check failed") +#define ASSERT_CHECK_DEATH(statement) ASSERT_DEATH(statement, "Check failed") +#endif // defined(OFFICIAL_BUILD) && defined(NDEBUG) + +#else // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID) + +// Note GTEST_UNSUPPORTED_DEATH_TEST takes a |regex| only to see whether it is a +// valid regex. It is never evaluated. +#define EXPECT_CHECK_DEATH(statement) \ + GTEST_UNSUPPORTED_DEATH_TEST(statement, "", ) +#define ASSERT_CHECK_DEATH(statement) \ + GTEST_UNSUPPORTED_DEATH_TEST(statement, "", return ) + +#endif // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID) + +namespace base { + +class FilePath; + +struct TestIdentifier { + TestIdentifier(); + TestIdentifier(const TestIdentifier& other); + + std::string test_case_name; + std::string test_name; + std::string file; + int line; +}; + +// Constructs a full test name given a test case name and a test name, +// e.g. for test case "A" and test name "B" returns "A.B". +std::string FormatFullTestName(const std::string& test_case_name, + const std::string& test_name); + +// Returns the full test name with the "DISABLED_" prefix stripped out. +// e.g. for the full test names "A.DISABLED_B", "DISABLED_A.B", and +// "DISABLED_A.DISABLED_B", returns "A.B". +std::string TestNameWithoutDisabledPrefix(const std::string& full_test_name); + +// Returns a vector of gtest-based tests compiled into +// current executable. +std::vector<TestIdentifier> GetCompiledInTests(); + +// Writes the list of gtest-based tests compiled into +// current executable as a JSON file. Returns true on success. +bool WriteCompiledInTestsToFile(const FilePath& path) WARN_UNUSED_RESULT; + +// Reads the list of gtest-based tests from |path| into |output|. +// Returns true on success. +bool ReadTestNamesFromFile( + const FilePath& path, + std::vector<TestIdentifier>* output) WARN_UNUSED_RESULT; + +} // namespace base + +#endif // BASE_TEST_GTEST_UTIL_H_ diff --git a/chromium/base/test/gtest_xml_unittest_result_printer.cc b/chromium/base/test/gtest_xml_unittest_result_printer.cc new file mode 100644 index 00000000000..709450b5329 --- /dev/null +++ b/chromium/base/test/gtest_xml_unittest_result_printer.cc @@ -0,0 +1,165 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/gtest_xml_unittest_result_printer.h" + +#include "base/base64.h" +#include "base/check.h" +#include "base/command_line.h" +#include "base/files/file_util.h" +#include "base/test/test_switches.h" +#include "base/time/time.h" + +namespace base { + +namespace { +const int kDefaultTestPartResultsLimit = 10; + +const char kTestPartLesultsLimitExceeded[] = + "Test part results limit exceeded. Use --test-launcher-test-part-limit to " + "increase or disable limit."; +} // namespace + +XmlUnitTestResultPrinter::XmlUnitTestResultPrinter() + : output_file_(nullptr), open_failed_(false) {} + +XmlUnitTestResultPrinter::~XmlUnitTestResultPrinter() { + if (output_file_ && !open_failed_) { + fprintf(output_file_, "</testsuites>\n"); + fflush(output_file_); + CloseFile(output_file_); + } +} + +bool XmlUnitTestResultPrinter::Initialize(const FilePath& output_file_path) { + DCHECK(!output_file_); + output_file_ = OpenFile(output_file_path, "w"); + if (!output_file_) { + // If the file open fails, we set the output location to stderr. This is + // because in current usage our caller CHECKs the result of this function. + // But that in turn causes a LogMessage that comes back to this object, + // which in turn causes a (double) crash. By pointing at stderr, there might + // be some indication what's going wrong. See https://crbug.com/736783. + output_file_ = stderr; + open_failed_ = true; + return false; + } + + fprintf(output_file_, + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites>\n"); + fflush(output_file_); + + return true; +} + +void XmlUnitTestResultPrinter::OnAssert(const char* file, + int line, + const std::string& summary, + const std::string& message) { + WriteTestPartResult(file, line, testing::TestPartResult::kFatalFailure, + summary, message); +} + +void XmlUnitTestResultPrinter::OnTestCaseStart( + const testing::TestCase& test_case) { + fprintf(output_file_, " <testsuite>\n"); + fflush(output_file_); +} + +void XmlUnitTestResultPrinter::OnTestStart( + const testing::TestInfo& test_info) { + // This is our custom extension - it helps to recognize which test was + // running when the test binary crashed. Note that we cannot even open the + // <testcase> tag here - it requires e.g. run time of the test to be known. + fprintf(output_file_, + " <x-teststart name=\"%s\" classname=\"%s\" />\n", + test_info.name(), + test_info.test_case_name()); + fflush(output_file_); +} + +void XmlUnitTestResultPrinter::OnTestEnd(const testing::TestInfo& test_info) { + fprintf(output_file_, + " <testcase name=\"%s\" status=\"run\" time=\"%.3f\"" + " classname=\"%s\">\n", + test_info.name(), + static_cast<double>(test_info.result()->elapsed_time()) / + Time::kMillisecondsPerSecond, + test_info.test_case_name()); + if (test_info.result()->Failed()) { + fprintf(output_file_, + " <failure message=\"\" type=\"\"></failure>\n"); + } + + int limit = test_info.result()->total_part_count(); + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kTestLauncherTestPartResultsLimit)) { + std::string limit_str = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTestLauncherTestPartResultsLimit); + int test_part_results_limit = std::strtol(limit_str.c_str(), nullptr, 10); + if (test_part_results_limit >= 0) + limit = std::min(limit, test_part_results_limit); + } else { + limit = std::min(limit, kDefaultTestPartResultsLimit); + } + + for (int i = 0; i < limit; ++i) { + const auto& test_part_result = test_info.result()->GetTestPartResult(i); + WriteTestPartResult(test_part_result.file_name(), + test_part_result.line_number(), test_part_result.type(), + test_part_result.summary(), test_part_result.message()); + } + + if (test_info.result()->total_part_count() > limit) { + WriteTestPartResult( + "unknown", 0, testing::TestPartResult::kNonFatalFailure, + kTestPartLesultsLimitExceeded, kTestPartLesultsLimitExceeded); + } + + fprintf(output_file_, " </testcase>\n"); + fflush(output_file_); +} + +void XmlUnitTestResultPrinter::OnTestCaseEnd( + const testing::TestCase& test_case) { + fprintf(output_file_, " </testsuite>\n"); + fflush(output_file_); +} + +void XmlUnitTestResultPrinter::WriteTestPartResult( + const char* file, + int line, + testing::TestPartResult::Type result_type, + const std::string& summary, + const std::string& message) { + const char* type = "unknown"; + switch (result_type) { + case testing::TestPartResult::kSuccess: + type = "success"; + break; + case testing::TestPartResult::kNonFatalFailure: + type = "failure"; + break; + case testing::TestPartResult::kFatalFailure: + type = "fatal_failure"; + break; + case testing::TestPartResult::kSkip: + type = "skip"; + break; + } + std::string summary_encoded; + Base64Encode(summary, &summary_encoded); + std::string message_encoded; + Base64Encode(message, &message_encoded); + fprintf(output_file_, + " <x-test-result-part type=\"%s\" file=\"%s\" line=\"%d\">\n" + " <summary>%s</summary>\n" + " <message>%s</message>\n" + " </x-test-result-part>\n", + type, file, line, summary_encoded.c_str(), message_encoded.c_str()); + fflush(output_file_); +} + +} // namespace base diff --git a/chromium/base/test/gtest_xml_unittest_result_printer.h b/chromium/base/test/gtest_xml_unittest_result_printer.h new file mode 100644 index 00000000000..93403822cfa --- /dev/null +++ b/chromium/base/test/gtest_xml_unittest_result_printer.h @@ -0,0 +1,55 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_GTEST_XML_UNITTEST_RESULT_PRINTER_H_ +#define BASE_TEST_GTEST_XML_UNITTEST_RESULT_PRINTER_H_ + +#include <stdio.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class FilePath; + +// Generates an XML output file. Format is very close to GTest, but has +// extensions needed by the test launcher. +class XmlUnitTestResultPrinter : public testing::EmptyTestEventListener { + public: + XmlUnitTestResultPrinter(); + ~XmlUnitTestResultPrinter() override; + + // Must be called before adding as a listener. Returns true on success. + bool Initialize(const FilePath& output_file_path) WARN_UNUSED_RESULT; + + // CHECK/DCHECK failed. Print file/line and message to the xml. + void OnAssert(const char* file, + int line, + const std::string& summary, + const std::string& message); + + private: + // testing::EmptyTestEventListener: + void OnTestCaseStart(const testing::TestCase& test_case) override; + void OnTestStart(const testing::TestInfo& test_info) override; + void OnTestEnd(const testing::TestInfo& test_info) override; + void OnTestCaseEnd(const testing::TestCase& test_case) override; + + void WriteTestPartResult(const char* file, + int line, + testing::TestPartResult::Type type, + const std::string& summary, + const std::string& message); + + FILE* output_file_; + bool open_failed_; + + DISALLOW_COPY_AND_ASSIGN(XmlUnitTestResultPrinter); +}; + +} // namespace base + +#endif // BASE_TEST_GTEST_XML_UNITTEST_RESULT_PRINTER_H_ diff --git a/chromium/base/test/gtest_xml_util.cc b/chromium/base/test/gtest_xml_util.cc new file mode 100644 index 00000000000..1bac5a6b1d2 --- /dev/null +++ b/chromium/base/test/gtest_xml_util.cc @@ -0,0 +1,235 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/gtest_xml_util.h" + +#include <stdint.h> + +#include "base/base64.h" +#include "base/check.h" +#include "base/files/file_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/test/gtest_util.h" +#include "base/test/launcher/test_launcher.h" +#include "third_party/libxml/chromium/libxml_utils.h" +#include "third_party/libxml/chromium/xml_reader.h" + +namespace base { + +namespace { + +// This is used for the xml parser to report errors. This assumes the context +// is a pointer to a std::string where the error message should be appended. +static void XmlErrorFunc(void *context, const char *message, ...) { + va_list args; + va_start(args, message); + std::string* error = static_cast<std::string*>(context); + StringAppendV(error, message, args); + va_end(args); +} + +} // namespace + +bool ProcessGTestOutput(const base::FilePath& output_file, + std::vector<TestResult>* results, + bool* crashed) { + DCHECK(results); + + std::string xml_contents; + if (!ReadFileToString(output_file, &xml_contents)) + return false; + + // Silence XML errors - otherwise they go to stderr. + std::string xml_errors; + ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc); + + XmlReader xml_reader; + if (!xml_reader.Load(xml_contents)) + return false; + + enum { + STATE_INIT, + STATE_TESTSUITE, + STATE_TESTCASE, + STATE_TEST_RESULT, + STATE_FAILURE, + STATE_END, + } state = STATE_INIT; + + while (xml_reader.Read()) { + xml_reader.SkipToElement(); + std::string node_name(xml_reader.NodeName()); + + switch (state) { + case STATE_INIT: + if (node_name == "testsuites" && !xml_reader.IsClosingElement()) + state = STATE_TESTSUITE; + else + return false; + break; + case STATE_TESTSUITE: + if (node_name == "testsuites" && xml_reader.IsClosingElement()) + state = STATE_END; + else if (node_name == "testsuite" && !xml_reader.IsClosingElement()) + state = STATE_TESTCASE; + else + return false; + break; + case STATE_TESTCASE: + if (node_name == "testsuite" && xml_reader.IsClosingElement()) { + state = STATE_TESTSUITE; + } else if (node_name == "x-teststart" && + !xml_reader.IsClosingElement()) { + // This is our custom extension that helps recognize which test was + // running when the test binary crashed. + TestResult result; + + std::string test_case_name; + if (!xml_reader.NodeAttribute("classname", &test_case_name)) + return false; + std::string test_name; + if (!xml_reader.NodeAttribute("name", &test_name)) + return false; + result.full_name = FormatFullTestName(test_case_name, test_name); + + result.elapsed_time = TimeDelta(); + + // Assume the test crashed - we can correct that later. + result.status = TestResult::TEST_CRASH; + + results->push_back(result); + } else if (node_name == "testcase" && !xml_reader.IsClosingElement()) { + std::string test_status; + if (!xml_reader.NodeAttribute("status", &test_status)) + return false; + + if (test_status != "run" && test_status != "notrun") + return false; + if (test_status != "run") + break; + + TestResult result; + + std::string test_case_name; + if (!xml_reader.NodeAttribute("classname", &test_case_name)) + return false; + std::string test_name; + if (!xml_reader.NodeAttribute("name", &test_name)) + return false; + result.full_name = test_case_name + "." + test_name; + + std::string test_time_str; + if (!xml_reader.NodeAttribute("time", &test_time_str)) + return false; + result.elapsed_time = TimeDelta::FromMicroseconds( + static_cast<int64_t>(strtod(test_time_str.c_str(), nullptr) * + Time::kMicrosecondsPerSecond)); + + result.status = TestResult::TEST_SUCCESS; + + if (!results->empty() && + results->back().full_name == result.full_name && + results->back().status == TestResult::TEST_CRASH) { + // Erase the fail-safe "crashed" result - now we know the test did + // not crash. + results->pop_back(); + } + + results->push_back(result); + } else if (node_name == "failure" && !xml_reader.IsClosingElement()) { + std::string failure_message; + if (!xml_reader.NodeAttribute("message", &failure_message)) + return false; + + DCHECK(!results->empty()); + results->back().status = TestResult::TEST_FAILURE; + + state = STATE_FAILURE; + } else if (node_name == "testcase" && xml_reader.IsClosingElement()) { + // Deliberately empty. + } else if (node_name == "x-test-result-part" && + !xml_reader.IsClosingElement()) { + std::string result_type; + if (!xml_reader.NodeAttribute("type", &result_type)) + return false; + + std::string file_name; + if (!xml_reader.NodeAttribute("file", &file_name)) + return false; + + std::string line_number_str; + if (!xml_reader.NodeAttribute("line", &line_number_str)) + return false; + + int line_number; + if (!StringToInt(line_number_str, &line_number)) + return false; + + TestResultPart::Type type; + if (!TestResultPart::TypeFromString(result_type, &type)) + return false; + + TestResultPart test_result_part; + test_result_part.type = type; + test_result_part.file_name = file_name, + test_result_part.line_number = line_number; + DCHECK(!results->empty()); + results->back().test_result_parts.push_back(test_result_part); + + state = STATE_TEST_RESULT; + } else { + return false; + } + break; + case STATE_TEST_RESULT: + if (node_name == "summary" && !xml_reader.IsClosingElement()) { + std::string summary; + if (!xml_reader.ReadElementContent(&summary)) + return false; + + if (!Base64Decode(summary, &summary)) + return false; + + DCHECK(!results->empty()); + DCHECK(!results->back().test_result_parts.empty()); + results->back().test_result_parts.back().summary = summary; + } else if (node_name == "summary" && xml_reader.IsClosingElement()) { + } else if (node_name == "message" && !xml_reader.IsClosingElement()) { + std::string message; + if (!xml_reader.ReadElementContent(&message)) + return false; + + if (!Base64Decode(message, &message)) + return false; + + DCHECK(!results->empty()); + DCHECK(!results->back().test_result_parts.empty()); + results->back().test_result_parts.back().message = message; + } else if (node_name == "message" && xml_reader.IsClosingElement()) { + } else if (node_name == "x-test-result-part" && + xml_reader.IsClosingElement()) { + state = STATE_TESTCASE; + } else { + return false; + } + break; + case STATE_FAILURE: + if (node_name == "failure" && xml_reader.IsClosingElement()) + state = STATE_TESTCASE; + else + return false; + break; + case STATE_END: + // If we are here and there are still XML elements, the file has wrong + // format. + return false; + } + } + + *crashed = (state != STATE_END); + return true; +} + +} // namespace base diff --git a/chromium/base/test/gtest_xml_util.h b/chromium/base/test/gtest_xml_util.h new file mode 100644 index 00000000000..b023f80da18 --- /dev/null +++ b/chromium/base/test/gtest_xml_util.h @@ -0,0 +1,27 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_GTEST_XML_UTIL_H_ +#define BASE_TEST_GTEST_XML_UTIL_H_ + +#include <vector> + +#include "base/compiler_specific.h" + +namespace base { + +class FilePath; +struct TestResult; + +// Produces a vector of test results based on GTest output file. +// Returns true iff the output file exists and has been successfully parsed. +// On successful return |crashed| is set to true if the test results +// are valid but incomplete. +bool ProcessGTestOutput(const base::FilePath& output_file, + std::vector<TestResult>* results, + bool* crashed) WARN_UNUSED_RESULT; + +} // namespace base + +#endif // BASE_TEST_GTEST_XML_UTIL_H_ diff --git a/chromium/base/test/icu_test_util.cc b/chromium/base/test/icu_test_util.cc new file mode 100644 index 00000000000..c15a6df9349 --- /dev/null +++ b/chromium/base/test/icu_test_util.cc @@ -0,0 +1,49 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/icu_test_util.h" + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/i18n/icu_util.h" +#include "base/i18n/rtl.h" +#include "third_party/icu/source/common/unicode/uloc.h" +#include "third_party/icu/source/i18n/unicode/timezone.h" + +namespace base { +namespace test { + +ScopedRestoreICUDefaultLocale::ScopedRestoreICUDefaultLocale() + : ScopedRestoreICUDefaultLocale(std::string()) {} + +ScopedRestoreICUDefaultLocale::ScopedRestoreICUDefaultLocale( + const std::string& locale) + : default_locale_(uloc_getDefault()) { + if (!locale.empty()) + i18n::SetICUDefaultLocale(locale.data()); +} + +ScopedRestoreICUDefaultLocale::~ScopedRestoreICUDefaultLocale() { + i18n::SetICUDefaultLocale(default_locale_.data()); +} + +ScopedRestoreDefaultTimezone::ScopedRestoreDefaultTimezone(const char* zoneid) { + original_zone_.reset(icu::TimeZone::createDefault()); + icu::TimeZone::adoptDefault(icu::TimeZone::createTimeZone(zoneid)); +} + +ScopedRestoreDefaultTimezone::~ScopedRestoreDefaultTimezone() { + icu::TimeZone::adoptDefault(original_zone_.release()); +} + +void InitializeICUForTesting() { + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kTestDoNotInitializeIcu)) { + i18n::AllowMultipleInitializeCallsForTesting(); + i18n::InitializeICU(); + } +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/icu_test_util.h b/chromium/base/test/icu_test_util.h new file mode 100644 index 00000000000..91f44ffbd36 --- /dev/null +++ b/chromium/base/test/icu_test_util.h @@ -0,0 +1,59 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_ICU_TEST_UTIL_H_ +#define BASE_TEST_ICU_TEST_UTIL_H_ + +#include <memory> +#include <string> + +#include "base/macros.h" +#include "third_party/icu/source/common/unicode/uversion.h" + +U_NAMESPACE_BEGIN +class TimeZone; +U_NAMESPACE_END + +namespace base { +namespace test { + +// In unit tests, prefer ScopedRestoreICUDefaultLocale over +// calling base::i18n::SetICUDefaultLocale() directly. This scoper makes it +// harder to accidentally forget to reset the locale. +class ScopedRestoreICUDefaultLocale { + public: + ScopedRestoreICUDefaultLocale(); + explicit ScopedRestoreICUDefaultLocale(const std::string& locale); + ~ScopedRestoreICUDefaultLocale(); + + private: + const std::string default_locale_; + + ScopedRestoreICUDefaultLocale(const ScopedRestoreICUDefaultLocale&) = delete; + ScopedRestoreICUDefaultLocale& operator=( + const ScopedRestoreICUDefaultLocale&) = delete; +}; + +// In unit tests, prefer ScopedRestoreDefaultTimezone over +// calling icu::TimeZone::adoptDefault() directly. This scoper makes it +// harder to accidentally forget to reset the locale. +class ScopedRestoreDefaultTimezone { + public: + ScopedRestoreDefaultTimezone(const char* zoneid); + ~ScopedRestoreDefaultTimezone(); + + ScopedRestoreDefaultTimezone(const ScopedRestoreDefaultTimezone&) = delete; + ScopedRestoreDefaultTimezone& operator=(const ScopedRestoreDefaultTimezone&) = + delete; + + private: + std::unique_ptr<icu::TimeZone> original_zone_; +}; + +void InitializeICUForTesting(); + +} // namespace test +} // namespace base + +#endif // BASE_TEST_ICU_TEST_UTIL_H_ diff --git a/chromium/base/test/immediate_crash_test_helper.cc b/chromium/base/test/immediate_crash_test_helper.cc new file mode 100644 index 00000000000..676a2ba6cdb --- /dev/null +++ b/chromium/base/test/immediate_crash_test_helper.cc @@ -0,0 +1,32 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/immediate_crash.h" // nogncheck + +#if defined(WIN32) +#define IMMEDIATE_CRASH_TEST_HELPER_EXPORT __declspec(dllexport) +#else // defined(WIN32) +#define IMMEDIATE_CRASH_TEST_HELPER_EXPORT \ + __attribute__((visibility("default"))) +#endif // defined(WIN32) + +extern "C" { + +IMMEDIATE_CRASH_TEST_HELPER_EXPORT int TestFunction1(int x, int y) { + if (x < 1) + IMMEDIATE_CRASH(); + if (y < 1) + IMMEDIATE_CRASH(); + return x + y; +} + +IMMEDIATE_CRASH_TEST_HELPER_EXPORT int TestFunction2(int x, int y) { + if (x < 2) + IMMEDIATE_CRASH(); + if (y < 2) + IMMEDIATE_CRASH(); + return x * y; +} + +} // extern "C" diff --git a/chromium/base/test/malloc_wrapper.cc b/chromium/base/test/malloc_wrapper.cc new file mode 100644 index 00000000000..eb280a3eeea --- /dev/null +++ b/chromium/base/test/malloc_wrapper.cc @@ -0,0 +1,11 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "malloc_wrapper.h" + +#include <stdlib.h> + +void* MallocWrapper(size_t size) { + return malloc(size); +} diff --git a/chromium/base/test/malloc_wrapper.h b/chromium/base/test/malloc_wrapper.h new file mode 100644 index 00000000000..e15ea48dc83 --- /dev/null +++ b/chromium/base/test/malloc_wrapper.h @@ -0,0 +1,22 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_MALLOC_WRAPPER_H_ +#define BASE_TEST_MALLOC_WRAPPER_H_ + +#include <stddef.h> + +// BASE_EXPORT depends on COMPONENT_BUILD. +// This will always be a separate shared library, so don't use BASE_EXPORT here. +#if defined(WIN32) +#define MALLOC_WRAPPER_EXPORT __declspec(dllexport) +#else +#define MALLOC_WRAPPER_EXPORT __attribute__((visibility("default"))) +#endif // defined(WIN32) + +// Calls malloc directly. Defined as a C function so that the function can be +// easily referenced by dlsym() without complications from C++ name mangling. +extern "C" MALLOC_WRAPPER_EXPORT void* MallocWrapper(size_t size); + +#endif // BASE_TEST_MALLOC_WRAPPER_H_ diff --git a/chromium/base/test/mock_callback.h b/chromium/base/test/mock_callback.h new file mode 100644 index 00000000000..24ad8a3b346 --- /dev/null +++ b/chromium/base/test/mock_callback.h @@ -0,0 +1,386 @@ +// This file was GENERATED by command: +// pump.py mock_callback.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Analogous to GMock's built-in MockFunction, but for base::Callback instead of +// std::function. It takes the full callback type as a parameter, so that it can +// support both OnceCallback and RepeatingCallback. Furthermore, this file +// defines convenience typedefs in the form of MockOnceCallback<Signature>, +// MockRepeatingCallback<Signature>, MockOnceClosure and MockRepeatingClosure. +// +// Use: +// using FooCallback = base::RepeatingCallback<int(std::string)>; +// +// TEST(FooTest, RunsCallbackWithBarArgument) { +// base::MockCallback<FooCallback> callback; +// EXPECT_CALL(callback, Run("bar")).WillOnce(Return(1)); +// Foo(callback.Get()); +// } +// +// Or equivalently: +// +// TEST(FooTest, RunsCallbackWithBarArgument) { +// base::MockRepeatingCallback<int(std::string)> callback; +// EXPECT_CALL(callback, Run("bar")).WillOnce(Return(1)); +// Foo(callback.Get()); +// } +// +// +// Can be used with StrictMock and NiceMock. Caller must ensure that it outlives +// any base::Callback obtained from it. + +#ifndef BASE_TEST_MOCK_CALLBACK_H_ +#define BASE_TEST_MOCK_CALLBACK_H_ + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace base { + +// clang-format off + +template <typename F> +class MockCallback; + +template <typename Signature> +using MockOnceCallback = MockCallback<OnceCallback<Signature>>; +template <typename Signature> +using MockRepeatingCallback = MockCallback<RepeatingCallback<Signature>>; + +using MockOnceClosure = MockCallback<OnceClosure>; +using MockRepeatingClosure = MockCallback<RepeatingClosure>; + +template <typename R> +class MockCallback<RepeatingCallback<R()>> { + public: + MockCallback() = default; + MOCK_METHOD0_T(Run, R()); + + RepeatingCallback<R()> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R> +class MockCallback<OnceCallback<R()>> { + public: + MockCallback() = default; + MOCK_METHOD0_T(Run, R()); + + OnceCallback<R()> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1> +class MockCallback<RepeatingCallback<R(A1)>> { + public: + MockCallback() = default; + MOCK_METHOD1_T(Run, R(A1)); + + RepeatingCallback<R(A1)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1> +class MockCallback<OnceCallback<R(A1)>> { + public: + MockCallback() = default; + MOCK_METHOD1_T(Run, R(A1)); + + OnceCallback<R(A1)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2> +class MockCallback<RepeatingCallback<R(A1, A2)>> { + public: + MockCallback() = default; + MOCK_METHOD2_T(Run, R(A1, A2)); + + RepeatingCallback<R(A1, A2)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2> +class MockCallback<OnceCallback<R(A1, A2)>> { + public: + MockCallback() = default; + MOCK_METHOD2_T(Run, R(A1, A2)); + + OnceCallback<R(A1, A2)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3> +class MockCallback<RepeatingCallback<R(A1, A2, A3)>> { + public: + MockCallback() = default; + MOCK_METHOD3_T(Run, R(A1, A2, A3)); + + RepeatingCallback<R(A1, A2, A3)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3> +class MockCallback<OnceCallback<R(A1, A2, A3)>> { + public: + MockCallback() = default; + MOCK_METHOD3_T(Run, R(A1, A2, A3)); + + OnceCallback<R(A1, A2, A3)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4> +class MockCallback<RepeatingCallback<R(A1, A2, A3, A4)>> { + public: + MockCallback() = default; + MOCK_METHOD4_T(Run, R(A1, A2, A3, A4)); + + RepeatingCallback<R(A1, A2, A3, A4)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4> +class MockCallback<OnceCallback<R(A1, A2, A3, A4)>> { + public: + MockCallback() = default; + MOCK_METHOD4_T(Run, R(A1, A2, A3, A4)); + + OnceCallback<R(A1, A2, A3, A4)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5> +class MockCallback<RepeatingCallback<R(A1, A2, A3, A4, A5)>> { + public: + MockCallback() = default; + MOCK_METHOD5_T(Run, R(A1, A2, A3, A4, A5)); + + RepeatingCallback<R(A1, A2, A3, A4, A5)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5> +class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5)>> { + public: + MockCallback() = default; + MOCK_METHOD5_T(Run, R(A1, A2, A3, A4, A5)); + + OnceCallback<R(A1, A2, A3, A4, A5)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +class MockCallback<RepeatingCallback<R(A1, A2, A3, A4, A5, A6)>> { + public: + MockCallback() = default; + MOCK_METHOD6_T(Run, R(A1, A2, A3, A4, A5, A6)); + + RepeatingCallback<R(A1, A2, A3, A4, A5, A6)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6> +class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6)>> { + public: + MockCallback() = default; + MOCK_METHOD6_T(Run, R(A1, A2, A3, A4, A5, A6)); + + OnceCallback<R(A1, A2, A3, A4, A5, A6)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +class MockCallback<RepeatingCallback<R(A1, A2, A3, A4, A5, A6, A7)>> { + public: + MockCallback() = default; + MOCK_METHOD7_T(Run, R(A1, A2, A3, A4, A5, A6, A7)); + + RepeatingCallback<R(A1, A2, A3, A4, A5, A6, A7)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7> +class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6, A7)>> { + public: + MockCallback() = default; + MOCK_METHOD7_T(Run, R(A1, A2, A3, A4, A5, A6, A7)); + + OnceCallback<R(A1, A2, A3, A4, A5, A6, A7)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7, typename A8> +class MockCallback<RepeatingCallback<R(A1, A2, A3, A4, A5, A6, A7, A8)>> { + public: + MockCallback() = default; + MOCK_METHOD8_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8)); + + RepeatingCallback<R(A1, A2, A3, A4, A5, A6, A7, A8)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7, typename A8> +class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8)>> { + public: + MockCallback() = default; + MOCK_METHOD8_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8)); + + OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7, typename A8, typename A9> +class MockCallback<RepeatingCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9)>> { + public: + MockCallback() = default; + MOCK_METHOD9_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8, A9)); + + RepeatingCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7, typename A8, typename A9> +class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9)>> { + public: + MockCallback() = default; + MOCK_METHOD9_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8, A9)); + + OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7, typename A8, typename A9, + typename A10> +class MockCallback<RepeatingCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9, + A10)>> { + public: + MockCallback() = default; + MOCK_METHOD10_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)); + + RepeatingCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R, typename A1, typename A2, typename A3, typename A4, + typename A5, typename A6, typename A7, typename A8, typename A9, + typename A10> +class MockCallback<OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)>> { + public: + MockCallback() = default; + MOCK_METHOD10_T(Run, R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)); + + OnceCallback<R(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +// clang-format on + +} // namespace base + +#endif // BASE_TEST_MOCK_CALLBACK_H_ diff --git a/chromium/base/test/mock_callback.h.pump b/chromium/base/test/mock_callback.h.pump new file mode 100644 index 00000000000..59155276fd0 --- /dev/null +++ b/chromium/base/test/mock_callback.h.pump @@ -0,0 +1,104 @@ +$$ This is a pump file for generating file templates. Pump is a python +$$ script that is part of the Google Test suite of utilities. Description +$$ can be found here: +$$ +$$ https://github.com/google/googletest/blob/master/googletest/docs/PumpManual.md +$$ +$$ MAX_ARITY controls the number of arguments that MockCallback supports. +$$ It is choosen to match the number GMock supports. +$var MAX_ARITY = 10 +$$ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Analogous to GMock's built-in MockFunction, but for base::Callback instead of +// std::function. It takes the full callback type as a parameter, so that it can +// support both OnceCallback and RepeatingCallback. Furthermore, this file +// defines convenience typedefs in the form of MockOnceCallback<Signature>, +// MockRepeatingCallback<Signature>, MockOnceClosure and MockRepeatingClosure. +// +// Use: +// using FooCallback = base::RepeatingCallback<int(std::string)>; +// +// TEST(FooTest, RunsCallbackWithBarArgument) { +// base::MockCallback<FooCallback> callback; +// EXPECT_CALL(callback, Run("bar")).WillOnce(Return(1)); +// Foo(callback.Get()); +// } +// +// Or equivalently: +// +// TEST(FooTest, RunsCallbackWithBarArgument) { +// base::MockRepeatingCallback<int(std::string)> callback; +// EXPECT_CALL(callback, Run("bar")).WillOnce(Return(1)); +// Foo(callback.Get()); +// } +// +// +// Can be used with StrictMock and NiceMock. Caller must ensure that it outlives +// any base::Callback obtained from it. + +#ifndef BASE_TEST_MOCK_CALLBACK_H_ +#define BASE_TEST_MOCK_CALLBACK_H_ + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace base { + +// clang-format off + +template <typename F> +class MockCallback; + +template <typename Signature> +using MockOnceCallback = MockCallback<OnceCallback<Signature>>; +template <typename Signature> +using MockRepeatingCallback = MockCallback<RepeatingCallback<Signature>>; + +using MockOnceClosure = MockCallback<OnceClosure>; +using MockRepeatingClosure = MockCallback<RepeatingClosure>; + +$range i 0..MAX_ARITY +$for i [[ +$range j 1..i +$var run_type = [[R($for j, [[A$j]])]] + +template <typename R$for j [[, typename A$j]]> +class MockCallback<RepeatingCallback<$run_type>> { + public: + MockCallback() = default; + MOCK_METHOD$(i)_T(Run, $run_type); + + RepeatingCallback<$run_type> Get() { + return BindRepeating(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +template <typename R$for j [[, typename A$j]]> +class MockCallback<OnceCallback<$run_type>> { + public: + MockCallback() = default; + MOCK_METHOD$(i)_T(Run, $run_type); + + OnceCallback<$run_type> Get() { + return BindOnce(&MockCallback::Run, Unretained(this)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockCallback); +}; + +]] + +// clang-format on + +} // namespace base + +#endif // BASE_TEST_MOCK_CALLBACK_H_ diff --git a/chromium/base/test/mock_callback_unittest.cc b/chromium/base/test/mock_callback_unittest.cc new file mode 100644 index 00000000000..efab2823934 --- /dev/null +++ b/chromium/base/test/mock_callback_unittest.cc @@ -0,0 +1,81 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/mock_callback.h" + +#include "base/callback.h" +#include "testing/gmock/include/gmock/gmock.h" + +using testing::InSequence; +using testing::Return; + +namespace base { +namespace { + +TEST(MockCallbackTest, ZeroArgs) { + MockCallback<RepeatingClosure> mock_closure; + EXPECT_CALL(mock_closure, Run()); + mock_closure.Get().Run(); + + MockCallback<RepeatingCallback<int()>> mock_int_callback; + { + InSequence sequence; + EXPECT_CALL(mock_int_callback, Run()).WillOnce(Return(42)); + EXPECT_CALL(mock_int_callback, Run()).WillOnce(Return(88)); + } + EXPECT_EQ(42, mock_int_callback.Get().Run()); + EXPECT_EQ(88, mock_int_callback.Get().Run()); +} + +TEST(MockCallbackTest, WithArgs) { + MockCallback<RepeatingCallback<int(int, int)>> mock_two_int_callback; + EXPECT_CALL(mock_two_int_callback, Run(1, 2)).WillOnce(Return(42)); + EXPECT_CALL(mock_two_int_callback, Run(0, 0)).WillRepeatedly(Return(-1)); + RepeatingCallback<int(int, int)> two_int_callback = + mock_two_int_callback.Get(); + EXPECT_EQ(-1, two_int_callback.Run(0, 0)); + EXPECT_EQ(42, two_int_callback.Run(1, 2)); + EXPECT_EQ(-1, two_int_callback.Run(0, 0)); +} + +TEST(MockCallbackTest, ZeroArgsOnce) { + MockCallback<OnceClosure> mock_closure; + EXPECT_CALL(mock_closure, Run()); + mock_closure.Get().Run(); + + MockCallback<OnceCallback<int()>> mock_int_callback; + EXPECT_CALL(mock_int_callback, Run()).WillOnce(Return(88)); + EXPECT_EQ(88, mock_int_callback.Get().Run()); +} + +TEST(MockCallbackTest, WithArgsOnce) { + MockCallback<OnceCallback<int(int, int)>> mock_two_int_callback; + EXPECT_CALL(mock_two_int_callback, Run(1, 2)).WillOnce(Return(42)); + OnceCallback<int(int, int)> two_int_callback = mock_two_int_callback.Get(); + EXPECT_EQ(42, std::move(two_int_callback).Run(1, 2)); +} + +TEST(MockCallbackTest, Typedefs) { + static_assert(std::is_same<MockCallback<RepeatingCallback<int()>>, + MockRepeatingCallback<int()>>::value, + "Repeating typedef differs for zero args"); + static_assert(std::is_same<MockCallback<RepeatingCallback<int(int, int)>>, + MockRepeatingCallback<int(int, int)>>::value, + "Repeating typedef differs for multiple args"); + static_assert(std::is_same<MockCallback<RepeatingCallback<void()>>, + MockRepeatingClosure>::value, + "Repeating typedef differs for closure"); + static_assert(std::is_same<MockCallback<OnceCallback<int()>>, + MockOnceCallback<int()>>::value, + "Once typedef differs for zero args"); + static_assert(std::is_same<MockCallback<OnceCallback<int(int, int)>>, + MockOnceCallback<int(int, int)>>::value, + "Once typedef differs for multiple args"); + static_assert(std::is_same<MockCallback<RepeatingCallback<void()>>, + MockRepeatingClosure>::value, + "Once typedef differs for closure"); +} + +} // namespace +} // namespace base diff --git a/chromium/base/test/mock_chrome_application_mac.mm b/chromium/base/test/mock_chrome_application_mac.mm new file mode 100644 index 00000000000..2695bc88d4e --- /dev/null +++ b/chromium/base/test/mock_chrome_application_mac.mm @@ -0,0 +1,49 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/mock_chrome_application_mac.h" + +#include "base/auto_reset.h" +#include "base/check.h" + +@implementation MockCrApp + ++ (NSApplication*)sharedApplication { + NSApplication* app = [super sharedApplication]; + DCHECK([app conformsToProtocol:@protocol(CrAppControlProtocol)]) + << "Existing NSApp (class " << [[app className] UTF8String] + << ") does not conform to required protocol."; + DCHECK(base::MessagePumpMac::UsingCrApp()) + << "MessagePumpMac::Create() was called before " + << "+[MockCrApp sharedApplication]"; + return app; +} + +- (void)sendEvent:(NSEvent*)event { + base::AutoReset<BOOL> scoper(&_handlingSendEvent, YES); + [super sendEvent:event]; +} + +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent { + _handlingSendEvent = handlingSendEvent; +} + +- (BOOL)isHandlingSendEvent { + return _handlingSendEvent; +} + +@end + +namespace mock_cr_app { + +void RegisterMockCrApp() { + [MockCrApp sharedApplication]; + + // If there was an invocation to NSApp prior to this method, then the NSApp + // will not be a MockCrApp, but will instead be an NSApplication. + // This is undesirable and we must enforce that this doesn't happen. + CHECK([NSApp isKindOfClass:[MockCrApp class]]); +} + +} // namespace mock_cr_app diff --git a/chromium/base/test/mock_devices_changed_observer.cc b/chromium/base/test/mock_devices_changed_observer.cc new file mode 100644 index 00000000000..9fc57cd93e7 --- /dev/null +++ b/chromium/base/test/mock_devices_changed_observer.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/mock_devices_changed_observer.h" + +namespace base { + +MockDevicesChangedObserver::MockDevicesChangedObserver() = default; + +MockDevicesChangedObserver::~MockDevicesChangedObserver() = default; + +} // namespace base diff --git a/chromium/base/test/mock_devices_changed_observer.h b/chromium/base/test/mock_devices_changed_observer.h new file mode 100644 index 00000000000..42b8c3f9f24 --- /dev/null +++ b/chromium/base/test/mock_devices_changed_observer.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_ +#define BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_ + +#include <string> + +#include "base/macros.h" +#include "base/system/system_monitor.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace base { + +class MockDevicesChangedObserver + : public base::SystemMonitor::DevicesChangedObserver { + public: + MockDevicesChangedObserver(); + ~MockDevicesChangedObserver() override; + + MOCK_METHOD1(OnDevicesChanged, + void(base::SystemMonitor::DeviceType device_type)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockDevicesChangedObserver); +}; + +} // namespace base + +#endif // BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_ diff --git a/chromium/base/test/mock_entropy_provider.cc b/chromium/base/test/mock_entropy_provider.cc new file mode 100644 index 00000000000..f3fd2a481ea --- /dev/null +++ b/chromium/base/test/mock_entropy_provider.cc @@ -0,0 +1,20 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/mock_entropy_provider.h" + +namespace base { + +MockEntropyProvider::MockEntropyProvider() : entropy_value_(0.5) {} +MockEntropyProvider::MockEntropyProvider(double entropy_value) + : entropy_value_(entropy_value) {} +MockEntropyProvider::~MockEntropyProvider() = default; + +double MockEntropyProvider::GetEntropyForTrial( + const std::string& trial_name, + uint32_t randomization_seed) const { + return entropy_value_; +} + +} // namespace base diff --git a/chromium/base/test/mock_entropy_provider.h b/chromium/base/test/mock_entropy_provider.h new file mode 100644 index 00000000000..ca2b4bc8fe0 --- /dev/null +++ b/chromium/base/test/mock_entropy_provider.h @@ -0,0 +1,32 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_MOCK_ENTROPY_PROVIDER_H_ +#define BASE_TEST_MOCK_ENTROPY_PROVIDER_H_ + +#include <stdint.h> + +#include "base/metrics/field_trial.h" + +namespace base { + +class MockEntropyProvider : public base::FieldTrial::EntropyProvider { + public: + MockEntropyProvider(); + explicit MockEntropyProvider(double entropy_value); + ~MockEntropyProvider() override; + + // base::FieldTrial::EntropyProvider: + double GetEntropyForTrial(const std::string& trial_name, + uint32_t randomization_seed) const override; + + private: + double entropy_value_; + + DISALLOW_COPY_AND_ASSIGN(MockEntropyProvider); +}; + +} // namespace base + +#endif // BASE_TEST_MOCK_ENTROPY_PROVIDER_H_ diff --git a/chromium/base/test/mock_log.cc b/chromium/base/test/mock_log.cc new file mode 100644 index 00000000000..a09000d8ed7 --- /dev/null +++ b/chromium/base/test/mock_log.cc @@ -0,0 +1,68 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/mock_log.h" + +namespace base { +namespace test { + +// static +MockLog* MockLog::g_instance_ = nullptr; +Lock MockLog::g_lock; + +MockLog::MockLog() : is_capturing_logs_(false) { +} + +MockLog::~MockLog() { + if (is_capturing_logs_) { + StopCapturingLogs(); + } +} + +void MockLog::StartCapturingLogs() { + AutoLock scoped_lock(g_lock); + + // We don't use CHECK(), which can generate a new LOG message, and + // thus can confuse MockLog objects or other registered + // LogSinks. + RAW_CHECK(!is_capturing_logs_); + RAW_CHECK(!g_instance_); + + is_capturing_logs_ = true; + g_instance_ = this; + previous_handler_ = logging::GetLogMessageHandler(); + logging::SetLogMessageHandler(LogMessageHandler); +} + +void MockLog::StopCapturingLogs() { + AutoLock scoped_lock(g_lock); + + // We don't use CHECK(), which can generate a new LOG message, and + // thus can confuse MockLog objects or other registered + // LogSinks. + RAW_CHECK(is_capturing_logs_); + RAW_CHECK(g_instance_ == this); + + is_capturing_logs_ = false; + logging::SetLogMessageHandler(previous_handler_); + g_instance_ = nullptr; +} + +// static +bool MockLog::LogMessageHandler(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str) { + // gMock guarantees thread-safety for calling a mocked method + // (https://github.com/google/googlemock/blob/master/googlemock/docs/CookBook.md#using-google-mock-and-threads) + // but we also need to make sure that Start/StopCapturingLogs are synchronized + // with LogMessageHandler. + AutoLock scoped_lock(g_lock); + + return g_instance_->Log(severity, file, line, message_start, str); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/mock_log.h b/chromium/base/test/mock_log.h new file mode 100644 index 00000000000..cda2fcd6259 --- /dev/null +++ b/chromium/base/test/mock_log.h @@ -0,0 +1,100 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_MOCK_LOG_H_ +#define BASE_TEST_MOCK_LOG_H_ + +#include <stddef.h> + +#include <string> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/synchronization/lock.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace base { +namespace test { + +// A MockLog object intercepts LOG() messages issued during its lifespan. Using +// this together with gMock, it's very easy to test how a piece of code calls +// LOG(). The typical usage: +// +// TEST(FooTest, LogsCorrectly) { +// MockLog log; +// +// // We expect the WARNING "Something bad!" exactly twice. +// EXPECT_CALL(log, Log(WARNING, _, "Something bad!")) +// .Times(2); +// +// // We allow foo.cc to call LOG(INFO) any number of times. +// EXPECT_CALL(log, Log(INFO, HasSubstr("/foo.cc"), _)) +// .Times(AnyNumber()); +// +// log.StartCapturingLogs(); // Call this after done setting expectations. +// Foo(); // Exercises the code under test. +// } +// +// CAVEAT: base/logging does not allow a thread to call LOG() again when it's +// already inside a LOG() call. Doing so will cause a deadlock. Therefore, +// it's the user's responsibility to not call LOG() in an action triggered by +// MockLog::Log(). You may call RAW_LOG() instead. +class MockLog { + public: + // Creates a MockLog object that is not capturing logs. If it were to start + // to capture logs, it could be a problem if some other threads already exist + // and are logging, as the user hasn't had a chance to set up expectation on + // this object yet (calling a mock method before setting the expectation is + // UNDEFINED behavior). + MockLog(); + + // When the object is destructed, it stops intercepting logs. + ~MockLog(); + + // Starts log capturing if the object isn't already doing so. + // Otherwise crashes. + void StartCapturingLogs(); + + // Stops log capturing if the object is capturing logs. Otherwise crashes. + void StopCapturingLogs(); + + // Log method is invoked for every log message before it's sent to other log + // destinations (if any). The method should return true to signal that it + // handled the message and the message should not be sent to other log + // destinations. + MOCK_METHOD5(Log, + bool(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str)); + + private: + // The currently active mock log. + static MockLog* g_instance_; + + // Lock protecting access to g_instance_. + static Lock g_lock; + + // Static function which is set as the logging message handler. + // Called once for each message. + static bool LogMessageHandler(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str); + + // True if this object is currently capturing logs. + bool is_capturing_logs_; + + // The previous handler to restore when the MockLog is destroyed. + logging::LogMessageHandlerFunction previous_handler_; + + DISALLOW_COPY_AND_ASSIGN(MockLog); +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_MOCK_LOG_H_ diff --git a/chromium/base/test/move_only_int.h b/chromium/base/test/move_only_int.h new file mode 100644 index 00000000000..6e909836240 --- /dev/null +++ b/chromium/base/test/move_only_int.h @@ -0,0 +1,68 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_MOVE_ONLY_INT_H_ +#define BASE_TEST_MOVE_ONLY_INT_H_ + +#include "base/macros.h" + +namespace base { + +// A move-only class that holds an integer. This is designed for testing +// containers. See also CopyOnlyInt. +class MoveOnlyInt { + public: + explicit MoveOnlyInt(int data = 1) : data_(data) {} + MoveOnlyInt(MoveOnlyInt&& other) : data_(other.data_) { other.data_ = 0; } + ~MoveOnlyInt() { data_ = 0; } + + MoveOnlyInt& operator=(MoveOnlyInt&& other) { + data_ = other.data_; + other.data_ = 0; + return *this; + } + + friend bool operator==(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) { + return lhs.data_ == rhs.data_; + } + + friend bool operator!=(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) { + return !operator==(lhs, rhs); + } + + friend bool operator<(const MoveOnlyInt& lhs, int rhs) { + return lhs.data_ < rhs; + } + + friend bool operator<(int lhs, const MoveOnlyInt& rhs) { + return lhs < rhs.data_; + } + + friend bool operator<(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) { + return lhs.data_ < rhs.data_; + } + + friend bool operator>(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) { + return rhs < lhs; + } + + friend bool operator<=(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) { + return !(rhs < lhs); + } + + friend bool operator>=(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) { + return !(lhs < rhs); + } + + int data() const { return data_; } + + private: + volatile int data_; + + DISALLOW_COPY_AND_ASSIGN(MoveOnlyInt); +}; + +} // namespace base + +#endif // BASE_TEST_MOVE_ONLY_INT_H_ diff --git a/chromium/base/test/multiprocess_test.cc b/chromium/base/test/multiprocess_test.cc new file mode 100644 index 00000000000..46556f75732 --- /dev/null +++ b/chromium/base/test/multiprocess_test.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/multiprocess_test.h" + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/threading/thread_restrictions.h" +#include "build/build_config.h" + +namespace base { + +#if !defined(OS_ANDROID) +Process SpawnMultiProcessTestChild(const std::string& procname, + const CommandLine& base_command_line, + const LaunchOptions& options) { + CommandLine command_line(base_command_line); + // TODO(viettrungluu): See comment above |MakeCmdLine()| in the header file. + // This is a temporary hack, since |MakeCmdLine()| has to provide a full + // command line. + if (!command_line.HasSwitch(switches::kTestChildProcess)) + command_line.AppendSwitchASCII(switches::kTestChildProcess, procname); + + return LaunchProcess(command_line, options); +} + +bool WaitForMultiprocessTestChildExit(const Process& process, + TimeDelta timeout, + int* exit_code) { + return process.WaitForExitWithTimeout(timeout, exit_code); +} + +bool TerminateMultiProcessTestChild(const Process& process, + int exit_code, + bool wait) { + return process.Terminate(exit_code, wait); +} + +#endif // !defined(OS_ANDROID) + +CommandLine GetMultiProcessTestChildBaseCommandLine() { + base::ScopedAllowBlockingForTesting allow_blocking; + CommandLine cmd_line = *CommandLine::ForCurrentProcess(); + cmd_line.SetProgram(MakeAbsoluteFilePath(cmd_line.GetProgram())); + return cmd_line; +} + +// MultiProcessTest ------------------------------------------------------------ + +MultiProcessTest::MultiProcessTest() = default; + +Process MultiProcessTest::SpawnChild(const std::string& procname) { + LaunchOptions options; +#if defined(OS_WIN) + options.start_hidden = true; +#endif + return SpawnChildWithOptions(procname, options); +} + +Process MultiProcessTest::SpawnChildWithOptions(const std::string& procname, + const LaunchOptions& options) { + return SpawnMultiProcessTestChild(procname, MakeCmdLine(procname), options); +} + +CommandLine MultiProcessTest::MakeCmdLine(const std::string& procname) { + CommandLine command_line = GetMultiProcessTestChildBaseCommandLine(); + command_line.AppendSwitchASCII(switches::kTestChildProcess, procname); + return command_line; +} + +} // namespace base diff --git a/chromium/base/test/multiprocess_test.h b/chromium/base/test/multiprocess_test.h new file mode 100644 index 00000000000..52c35e02ef2 --- /dev/null +++ b/chromium/base/test/multiprocess_test.h @@ -0,0 +1,151 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_MULTIPROCESS_TEST_H_ +#define BASE_TEST_MULTIPROCESS_TEST_H_ + +#include <string> + +#include "base/macros.h" +#include "base/process/launch.h" +#include "base/process/process.h" +#include "build/build_config.h" +#include "testing/platform_test.h" + +namespace base { + +class CommandLine; + +// Helpers to spawn a child for a multiprocess test and execute a designated +// function. Use these when you already have another base class for your test +// fixture, but you want (some) of your tests to be multiprocess (otherwise you +// may just want to derive your fixture from |MultiProcessTest|, below). +// +// Use these helpers as follows: +// +// TEST_F(MyTest, ATest) { +// CommandLine command_line( +// base::GetMultiProcessTestChildBaseCommandLine()); +// // Maybe add our own switches to |command_line|.... +// +// LaunchOptions options; +// // Maybe set some options (e.g., |start_hidden| on Windows).... +// +// // Start a child process and run |a_test_func|. +// base::Process test_child_process = +// base::SpawnMultiProcessTestChild("a_test_func", command_line, +// options); +// +// // Do stuff involving |test_child_process| and the child process.... +// +// int rv = -1; +// ASSERT_TRUE(base::WaitForMultiprocessTestChildExit(test_child_process, +// TestTimeouts::action_timeout(), &rv)); +// EXPECT_EQ(0, rv); +// } +// +// // Note: |MULTIPROCESS_TEST_MAIN()| is defined in +// // testing/multiprocess_func_list.h. +// MULTIPROCESS_TEST_MAIN(a_test_func) { +// // Code here runs in a child process.... +// return 0; +// } +// +// If you need to terminate the child process, use the +// TerminateMultiProcessTestChild method to ensure that test will work on +// Android. + +// Spawns a child process and executes the function |procname| declared using +// |MULTIPROCESS_TEST_MAIN()| or |MULTIPROCESS_TEST_MAIN_WITH_SETUP()|. +// |command_line| should be as provided by +// |GetMultiProcessTestChildBaseCommandLine()| (below), possibly with arguments +// added. Note: On Windows, you probably want to set |options.start_hidden|. +Process SpawnMultiProcessTestChild(const std::string& procname, + const CommandLine& command_line, + const LaunchOptions& options); + +// Gets the base command line for |SpawnMultiProcessTestChild()|. To this, you +// may add any flags needed for your child process. +CommandLine GetMultiProcessTestChildBaseCommandLine(); + +// Waits for the child process to exit. Returns true if the process exited +// within |timeout| and sets |exit_code| if non null. +bool WaitForMultiprocessTestChildExit(const Process& process, + TimeDelta timeout, + int* exit_code); + +// Terminates |process| with |exit_code|. If |wait| is true, this call blocks +// until the process actually terminates. +bool TerminateMultiProcessTestChild(const Process& process, + int exit_code, + bool wait); + +#if defined(OS_ANDROID) +// Returns whether the child process exited cleanly from the main runloop. +bool MultiProcessTestChildHasCleanExit(const Process& process); +#endif + +// MultiProcessTest ------------------------------------------------------------ + +// A MultiProcessTest is a test class which makes it easier to +// write a test which requires code running out of process. +// +// To create a multiprocess test simply follow these steps: +// +// 1) Derive your test from MultiProcessTest. Example: +// +// class MyTest : public MultiProcessTest { +// }; +// +// TEST_F(MyTest, TestCaseName) { +// ... +// } +// +// 2) Create a mainline function for the child processes and include +// testing/multiprocess_func_list.h. +// See the declaration of the MULTIPROCESS_TEST_MAIN macro +// in that file for an example. +// 3) Call SpawnChild("foo"), where "foo" is the name of +// the function you wish to run in the child processes. +// That's it! +class MultiProcessTest : public PlatformTest { + public: + MultiProcessTest(); + + protected: + // Run a child process. + // 'procname' is the name of a function which the child will + // execute. It must be exported from this library in order to + // run. + // + // Example signature: + // extern "C" int __declspec(dllexport) FooBar() { + // // do client work here + // } + // + // Returns the child process. + Process SpawnChild(const std::string& procname); + + // Run a child process using the given launch options. + // + // Note: On Windows, you probably want to set |options.start_hidden|. + Process SpawnChildWithOptions(const std::string& procname, + const LaunchOptions& options); + + // Set up the command line used to spawn the child process. + // Override this to add things to the command line (calling this first in the + // override). + // Note that currently some tests rely on this providing a full command line, + // which they then use directly with |LaunchProcess()|. + // TODO(viettrungluu): Remove this and add a virtual + // |ModifyChildCommandLine()|; make the two divergent uses more sane. + virtual CommandLine MakeCmdLine(const std::string& procname); + + private: + DISALLOW_COPY_AND_ASSIGN(MultiProcessTest); +}; + +} // namespace base + +#endif // BASE_TEST_MULTIPROCESS_TEST_H_ diff --git a/chromium/base/test/multiprocess_test_android.cc b/chromium/base/test/multiprocess_test_android.cc new file mode 100644 index 00000000000..f3e3ea34bca --- /dev/null +++ b/chromium/base/test/multiprocess_test_android.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/multiprocess_test.h" + +#include <string.h> +#include <vector> + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/scoped_java_ref.h" +#include "base/base_switches.h" +#include "base/check.h" +#include "base/command_line.h" +#include "base/test/test_support_jni_headers/MainReturnCodeResult_jni.h" +#include "base/test/test_support_jni_headers/MultiprocessTestClientLauncher_jni.h" + +namespace base { + +// A very basic implementation for Android. On Android tests can run in an APK +// and we don't have an executable to exec*. This implementation does the bare +// minimum to execute the method specified by procname (in the child process). +// - All options except |fds_to_remap| are ignored. +// +// NOTE: This MUST NOT run on the main thread of the NativeTest application. +Process SpawnMultiProcessTestChild(const std::string& procname, + const CommandLine& base_command_line, + const LaunchOptions& options) { + JNIEnv* env = android::AttachCurrentThread(); + DCHECK(env); + + std::vector<int> fd_keys; + std::vector<int> fd_fds; + for (auto& iter : options.fds_to_remap) { + fd_keys.push_back(iter.second); + fd_fds.push_back(iter.first); + } + + android::ScopedJavaLocalRef<jobjectArray> fds = + android::Java_MultiprocessTestClientLauncher_makeFdInfoArray( + env, base::android::ToJavaIntArray(env, fd_keys), + base::android::ToJavaIntArray(env, fd_fds)); + + CommandLine command_line(base_command_line); + if (!command_line.HasSwitch(switches::kTestChildProcess)) { + command_line.AppendSwitchASCII(switches::kTestChildProcess, procname); + } + + android::ScopedJavaLocalRef<jobjectArray> j_argv = + android::ToJavaArrayOfStrings(env, command_line.argv()); + jint pid = android::Java_MultiprocessTestClientLauncher_launchClient( + env, j_argv, fds); + return Process(pid); +} + +bool WaitForMultiprocessTestChildExit(const Process& process, + TimeDelta timeout, + int* exit_code) { + JNIEnv* env = android::AttachCurrentThread(); + DCHECK(env); + + base::android::ScopedJavaLocalRef<jobject> result_code = + android::Java_MultiprocessTestClientLauncher_waitForMainToReturn( + env, process.Pid(), static_cast<int32_t>(timeout.InMilliseconds())); + if (result_code.is_null() || + Java_MainReturnCodeResult_hasTimedOut(env, result_code)) { + return false; + } + if (exit_code) { + *exit_code = Java_MainReturnCodeResult_getReturnCode(env, result_code); + } + return true; +} + +bool TerminateMultiProcessTestChild(const Process& process, + int exit_code, + bool wait) { + JNIEnv* env = android::AttachCurrentThread(); + DCHECK(env); + + return android::Java_MultiprocessTestClientLauncher_terminate( + env, process.Pid(), exit_code, wait); +} + +bool MultiProcessTestChildHasCleanExit(const Process& process) { + JNIEnv* env = android::AttachCurrentThread(); + DCHECK(env); + + return android::Java_MultiprocessTestClientLauncher_hasCleanExit( + env, process.Pid()); +} + +} // namespace base diff --git a/chromium/base/test/native_library_test_utils.cc b/chromium/base/test/native_library_test_utils.cc new file mode 100644 index 00000000000..adcb1b01e90 --- /dev/null +++ b/chromium/base/test/native_library_test_utils.cc @@ -0,0 +1,19 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/native_library_test_utils.h" + +namespace { + +int g_static_value = 0; + +} // namespace + +extern "C" { + +int g_native_library_exported_value = 0; + +int NativeLibraryTestIncrement() { return ++g_static_value; } + +} // extern "C" diff --git a/chromium/base/test/native_library_test_utils.h b/chromium/base/test/native_library_test_utils.h new file mode 100644 index 00000000000..e26fd1a04e6 --- /dev/null +++ b/chromium/base/test/native_library_test_utils.h @@ -0,0 +1,26 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_ +#define BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#define NATIVE_LIBRARY_TEST_ALWAYS_EXPORT __declspec(dllexport) +#else +#define NATIVE_LIBRARY_TEST_ALWAYS_EXPORT __attribute__((visibility("default"))) +#endif + +extern "C" { + +extern NATIVE_LIBRARY_TEST_ALWAYS_EXPORT int g_native_library_exported_value; + +// A function which increments an internal counter value and returns its value. +// The first call returns 1, then 2, etc. +NATIVE_LIBRARY_TEST_ALWAYS_EXPORT int NativeLibraryTestIncrement(); + +} // extern "C" + +#endif // BASE_TEST_NATIVE_LIBRARY_TEST_UTILS_H_ diff --git a/chromium/base/test/null_task_runner.cc b/chromium/base/test/null_task_runner.cc new file mode 100644 index 00000000000..dfa26fa313c --- /dev/null +++ b/chromium/base/test/null_task_runner.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/null_task_runner.h" + +namespace base { + +NullTaskRunner::NullTaskRunner() = default; + +NullTaskRunner::~NullTaskRunner() = default; + +bool NullTaskRunner::PostDelayedTask(const Location& from_here, + OnceClosure task, + base::TimeDelta delay) { + return false; +} + +bool NullTaskRunner::PostNonNestableDelayedTask(const Location& from_here, + OnceClosure task, + base::TimeDelta delay) { + return false; +} + +bool NullTaskRunner::RunsTasksInCurrentSequence() const { + return true; +} + +} // namespace base diff --git a/chromium/base/test/null_task_runner.h b/chromium/base/test/null_task_runner.h new file mode 100644 index 00000000000..8ed339526d0 --- /dev/null +++ b/chromium/base/test/null_task_runner.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_NULL_TASK_RUNNER_H_ +#define BASE_TEST_NULL_TASK_RUNNER_H_ + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/single_thread_task_runner.h" + +namespace base { + +// ATTENTION: Prefer SingleThreadTaskEnvironment or TaskEnvironment w/ +// ThreadPoolExecutionMode::QUEUED over this class. A NullTaskRunner might seem +// appealing, but not running tasks is under-testing the potential side-effects +// of the code under tests. All tests should be okay if tasks born from their +// actions are run or deleted at a later point. +// +// Helper class for tests that need to provide an implementation of a +// *TaskRunner class but don't actually care about tasks being run. +class NullTaskRunner : public base::SingleThreadTaskRunner { + public: + NullTaskRunner(); + + bool PostDelayedTask(const Location& from_here, + base::OnceClosure task, + base::TimeDelta delay) override; + bool PostNonNestableDelayedTask(const Location& from_here, + base::OnceClosure task, + base::TimeDelta delay) override; + // Always returns true to avoid triggering DCHECKs. + bool RunsTasksInCurrentSequence() const override; + + protected: + ~NullTaskRunner() override; + + DISALLOW_COPY_AND_ASSIGN(NullTaskRunner); +}; + +} // namespace base + +#endif // BASE_TEST_NULL_TASK_RUNNER_H_ diff --git a/chromium/base/test/perf_log.cc b/chromium/base/test/perf_log.cc new file mode 100644 index 00000000000..e275ac0922f --- /dev/null +++ b/chromium/base/test/perf_log.cc @@ -0,0 +1,45 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/perf_log.h" + +#include "base/files/file_util.h" +#include "base/notreached.h" + +namespace base { + +static FILE* perf_log_file = nullptr; + +bool InitPerfLog(const FilePath& log_file) { + if (perf_log_file) { + // trying to initialize twice + NOTREACHED(); + return false; + } + + perf_log_file = OpenFile(log_file, "w"); + return perf_log_file != nullptr; +} + +void FinalizePerfLog() { + if (!perf_log_file) { + // trying to cleanup without initializing + NOTREACHED(); + return; + } + base::CloseFile(perf_log_file); +} + +void LogPerfResult(const char* test_name, double value, const char* units) { + if (!perf_log_file) { + NOTREACHED(); + return; + } + + fprintf(perf_log_file, "%s\t%g\t%s\n", test_name, value, units); + printf("%s\t%g\t%s\n", test_name, value, units); + fflush(stdout); +} + +} // namespace base diff --git a/chromium/base/test/perf_log.h b/chromium/base/test/perf_log.h new file mode 100644 index 00000000000..5d6ed9f8ba4 --- /dev/null +++ b/chromium/base/test/perf_log.h @@ -0,0 +1,24 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_PERF_LOG_H_ +#define BASE_TEST_PERF_LOG_H_ + +namespace base { + +class FilePath; + +// Initializes and finalizes the perf log. These functions should be +// called at the beginning and end (respectively) of running all the +// performance tests. The init function returns true on success. +bool InitPerfLog(const FilePath& log_path); +void FinalizePerfLog(); + +// Writes to the perf result log the given 'value' resulting from the +// named 'test'. The units are to aid in reading the log by people. +void LogPerfResult(const char* test_name, double value, const char* units); + +} // namespace base + +#endif // BASE_TEST_PERF_LOG_H_ diff --git a/chromium/base/test/perf_test_suite.cc b/chromium/base/test/perf_test_suite.cc new file mode 100644 index 00000000000..2e2cdbb751e --- /dev/null +++ b/chromium/base/test/perf_test_suite.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/perf_test_suite.h" + +#include "base/command_line.h" +#include "base/debug/debugger.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/process/launch.h" +#include "base/strings/string_util.h" +#include "base/test/perf_log.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +PerfTestSuite::PerfTestSuite(int argc, char** argv) : TestSuite(argc, argv) {} + +void PerfTestSuite::Initialize() { + TestSuite::Initialize(); + + // Initialize the perf timer log + FilePath log_path = + CommandLine::ForCurrentProcess()->GetSwitchValuePath("log-file"); + if (log_path.empty()) { + PathService::Get(FILE_EXE, &log_path); +#if defined(OS_ANDROID) || defined(OS_FUCHSIA) + base::FilePath tmp_dir; + PathService::Get(base::DIR_CACHE, &tmp_dir); + log_path = tmp_dir.Append(log_path.BaseName()); +#endif + log_path = log_path.ReplaceExtension(FILE_PATH_LITERAL("log")); + log_path = log_path.InsertBeforeExtension(FILE_PATH_LITERAL("_perf")); + } + ASSERT_TRUE(InitPerfLog(log_path)); + + // Raise to high priority to have more precise measurements. Since we don't + // aim at 1% precision, it is not necessary to run at realtime level. + if (!debug::BeingDebugged()) + RaiseProcessToHighPriority(); +} + +void PerfTestSuite::Shutdown() { + TestSuite::Shutdown(); + FinalizePerfLog(); +} + +} // namespace base diff --git a/chromium/base/test/perf_test_suite.h b/chromium/base/test/perf_test_suite.h new file mode 100644 index 00000000000..52528f0ac45 --- /dev/null +++ b/chromium/base/test/perf_test_suite.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_PERF_TEST_SUITE_H_ +#define BASE_TEST_PERF_TEST_SUITE_H_ + +#include "base/test/test_suite.h" + +namespace base { + +class PerfTestSuite : public TestSuite { + public: + PerfTestSuite(int argc, char** argv); + + void Initialize() override; + void Shutdown() override; +}; + +} // namespace base + +#endif // BASE_TEST_PERF_TEST_SUITE_H_ diff --git a/chromium/base/test/perf_time_logger.cc b/chromium/base/test/perf_time_logger.cc new file mode 100644 index 00000000000..c05ba51b7d8 --- /dev/null +++ b/chromium/base/test/perf_time_logger.cc @@ -0,0 +1,27 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/perf_time_logger.h" + +#include "base/test/perf_log.h" + +namespace base { + +PerfTimeLogger::PerfTimeLogger(const char* test_name) + : logged_(false), test_name_(test_name) {} + +PerfTimeLogger::~PerfTimeLogger() { + if (!logged_) + Done(); +} + +void PerfTimeLogger::Done() { + // we use a floating-point millisecond value because it is more + // intuitive than microseconds and we want more precision than + // integer milliseconds + LogPerfResult(test_name_.c_str(), timer_.Elapsed().InMillisecondsF(), "ms"); + logged_ = true; +} + +} // namespace base diff --git a/chromium/base/test/perf_time_logger.h b/chromium/base/test/perf_time_logger.h new file mode 100644 index 00000000000..a5f3e8a70c3 --- /dev/null +++ b/chromium/base/test/perf_time_logger.h @@ -0,0 +1,37 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_PERF_TIME_LOGGER_H_ +#define BASE_TEST_PERF_TIME_LOGGER_H_ + +#include <string> + +#include "base/macros.h" +#include "base/timer/elapsed_timer.h" + +namespace base { + +// Automates calling LogPerfResult for the common case where you want +// to measure the time that something took. Call Done() when the test +// is complete if you do extra work after the test or there are stack +// objects with potentially expensive constructors. Otherwise, this +// class with automatically log on destruction. +class PerfTimeLogger { + public: + explicit PerfTimeLogger(const char* test_name); + ~PerfTimeLogger(); + + void Done(); + + private: + bool logged_; + std::string test_name_; + ElapsedTimer timer_; + + DISALLOW_COPY_AND_ASSIGN(PerfTimeLogger); +}; + +} // namespace base + +#endif // BASE_TEST_PERF_TIME_LOGGER_H_ diff --git a/chromium/base/test/power_monitor_test_base.cc b/chromium/base/test/power_monitor_test_base.cc new file mode 100644 index 00000000000..f37fb579688 --- /dev/null +++ b/chromium/base/test/power_monitor_test_base.cc @@ -0,0 +1,65 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/power_monitor_test_base.h" + +#include "base/message_loop/message_loop_current.h" +#include "base/power_monitor/power_monitor.h" +#include "base/power_monitor/power_monitor_source.h" +#include "base/run_loop.h" + +namespace base { + +PowerMonitorTestSource::PowerMonitorTestSource() + : test_on_battery_power_(false) { + DCHECK(MessageLoopCurrent::Get()) + << "PowerMonitorTestSource requires a MessageLoop."; +} + +PowerMonitorTestSource::~PowerMonitorTestSource() = default; + +void PowerMonitorTestSource::GeneratePowerStateEvent(bool on_battery_power) { + test_on_battery_power_ = on_battery_power; + ProcessPowerEvent(POWER_STATE_EVENT); + RunLoop().RunUntilIdle(); +} + +void PowerMonitorTestSource::GenerateSuspendEvent() { + ProcessPowerEvent(SUSPEND_EVENT); + RunLoop().RunUntilIdle(); +} + +void PowerMonitorTestSource::GenerateResumeEvent() { + ProcessPowerEvent(RESUME_EVENT); + RunLoop().RunUntilIdle(); +} + +bool PowerMonitorTestSource::IsOnBatteryPowerImpl() { + return test_on_battery_power_; +} + +PowerMonitorTestObserver::PowerMonitorTestObserver() + : last_power_state_(false), + power_state_changes_(0), + suspends_(0), + resumes_(0) { +} + +PowerMonitorTestObserver::~PowerMonitorTestObserver() = default; + +// PowerObserver callbacks. +void PowerMonitorTestObserver::OnPowerStateChange(bool on_battery_power) { + last_power_state_ = on_battery_power; + power_state_changes_++; +} + +void PowerMonitorTestObserver::OnSuspend() { + suspends_++; +} + +void PowerMonitorTestObserver::OnResume() { + resumes_++; +} + +} // namespace base diff --git a/chromium/base/test/power_monitor_test_base.h b/chromium/base/test/power_monitor_test_base.h new file mode 100644 index 00000000000..3086bb87496 --- /dev/null +++ b/chromium/base/test/power_monitor_test_base.h @@ -0,0 +1,53 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_POWER_MONITOR_TEST_BASE_H_ +#define BASE_TEST_POWER_MONITOR_TEST_BASE_H_ + +#include "base/power_monitor/power_monitor.h" +#include "base/power_monitor/power_monitor_source.h" + +namespace base { + +class PowerMonitorTestSource : public PowerMonitorSource { + public: + PowerMonitorTestSource(); + ~PowerMonitorTestSource() override; + + void GeneratePowerStateEvent(bool on_battery_power); + void GenerateSuspendEvent(); + void GenerateResumeEvent(); + + protected: + bool IsOnBatteryPowerImpl() override; + + bool test_on_battery_power_; +}; + +class PowerMonitorTestObserver : public PowerObserver { + public: + PowerMonitorTestObserver(); + ~PowerMonitorTestObserver() override; + + // PowerObserver callbacks. + void OnPowerStateChange(bool on_battery_power) override; + void OnSuspend() override; + void OnResume() override; + + // Test status counts. + bool last_power_state() const { return last_power_state_; } + int power_state_changes() const { return power_state_changes_; } + int suspends() const { return suspends_; } + int resumes() const { return resumes_; } + + private: + bool last_power_state_; // Last power state we were notified of. + int power_state_changes_; // Count of OnPowerStateChange notifications. + int suspends_; // Count of OnSuspend notifications. + int resumes_; // Count of OnResume notifications. +}; + +} // namespace base + +#endif // BASE_TEST_POWER_MONITOR_TEST_BASE_H_ diff --git a/chromium/base/test/reached_code_profiler_android.cc b/chromium/base/test/reached_code_profiler_android.cc new file mode 100644 index 00000000000..cfedc6b37a8 --- /dev/null +++ b/chromium/base/test/reached_code_profiler_android.cc @@ -0,0 +1,25 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/jni_android.h" +#include "base/android/reached_code_profiler.h" +#include "base/test/test_support_jni_headers/ReachedCodeProfiler_jni.h" + +// This file provides functions to query the state of the reached code profiler +// from Java. It's used only for tests. +namespace base { +namespace android { + +static jboolean JNI_ReachedCodeProfiler_IsReachedCodeProfilerEnabled( + JNIEnv* env) { + return IsReachedCodeProfilerEnabled(); +} + +static jboolean JNI_ReachedCodeProfiler_IsReachedCodeProfilerSupported( + JNIEnv* env) { + return IsReachedCodeProfilerSupported(); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/test/run_all_base_unittests.cc b/chromium/base/test/run_all_base_unittests.cc new file mode 100644 index 00000000000..aa7a9bf5bb0 --- /dev/null +++ b/chromium/base/test/run_all_base_unittests.cc @@ -0,0 +1,15 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" + +int main(int argc, char** argv) { + base::TestSuite test_suite(argc, argv); + return base::LaunchUnitTests( + argc, argv, + base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/chromium/base/test/run_all_perftests.cc b/chromium/base/test/run_all_perftests.cc new file mode 100644 index 00000000000..6e38109376a --- /dev/null +++ b/chromium/base/test/run_all_perftests.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/perf_test_suite.h" + +int main(int argc, char** argv) { + return base::PerfTestSuite(argc, argv).Run(); +} diff --git a/chromium/base/test/run_all_unittests.cc b/chromium/base/test/run_all_unittests.cc new file mode 100644 index 00000000000..0ad84ed53d2 --- /dev/null +++ b/chromium/base/test/run_all_unittests.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" + +int main(int argc, char** argv) { + base::TestSuite test_suite(argc, argv); + return base::LaunchUnitTests( + argc, argv, + base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/chromium/base/test/scoped_command_line.cc b/chromium/base/test/scoped_command_line.cc new file mode 100644 index 00000000000..c74d243f448 --- /dev/null +++ b/chromium/base/test/scoped_command_line.cc @@ -0,0 +1,22 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_command_line.h" + +namespace base { +namespace test { + +ScopedCommandLine::ScopedCommandLine() + : original_command_line_(*base::CommandLine::ForCurrentProcess()) {} + +ScopedCommandLine::~ScopedCommandLine() { + *base::CommandLine::ForCurrentProcess() = original_command_line_; +} + +CommandLine* ScopedCommandLine::GetProcessCommandLine() { + return base::CommandLine::ForCurrentProcess(); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/scoped_command_line.h b/chromium/base/test/scoped_command_line.h new file mode 100644 index 00000000000..dea0c6ac1e6 --- /dev/null +++ b/chromium/base/test/scoped_command_line.h @@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_COMMAND_LINE_H_ +#define BASE_TEST_SCOPED_COMMAND_LINE_H_ + +#include "base/command_line.h" + +namespace base { +namespace test { + +// Helper class to restore the original command line at the end of the scope. +// NOTE: In most unit tests, the command line is automatically restored per +// test, so this class is not necessary if the command line applies to +// the entire single test. +class ScopedCommandLine final { + public: + ScopedCommandLine(); + ~ScopedCommandLine(); + + // Gets the command line for the current process. + // NOTE: Do not name this GetCommandLine as this will conflict with Windows's + // GetCommandLine and get renamed to GetCommandLineW. + CommandLine* GetProcessCommandLine(); + + private: + const CommandLine original_command_line_; +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_SCOPED_COMMAND_LINE_H_ diff --git a/chromium/base/test/scoped_environment_variable_override.cc b/chromium/base/test/scoped_environment_variable_override.cc new file mode 100644 index 00000000000..4b7b3871415 --- /dev/null +++ b/chromium/base/test/scoped_environment_variable_override.cc @@ -0,0 +1,33 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_environment_variable_override.h" + +#include "base/environment.h" + +namespace base { +namespace test { + +ScopedEnvironmentVariableOverride::ScopedEnvironmentVariableOverride( + const std::string& variable_name, + const std::string& value) + : environment_(Environment::Create()), + variable_name_(variable_name), + overridden_(false), + was_set_(false) { + was_set_ = environment_->GetVar(variable_name, &old_value_); + overridden_ = environment_->SetVar(variable_name, value); +} + +ScopedEnvironmentVariableOverride::~ScopedEnvironmentVariableOverride() { + if (overridden_) { + if (was_set_) + environment_->SetVar(variable_name_, old_value_); + else + environment_->UnSetVar(variable_name_); + } +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/scoped_environment_variable_override.h b/chromium/base/test/scoped_environment_variable_override.h new file mode 100644 index 00000000000..b05b5f9a405 --- /dev/null +++ b/chromium/base/test/scoped_environment_variable_override.h @@ -0,0 +1,40 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_ENVIRONMENT_VARIABLE_OVERRIDE_H_ +#define BASE_TEST_SCOPED_ENVIRONMENT_VARIABLE_OVERRIDE_H_ + +#include <memory> +#include <string> + +namespace base { + +class Environment; + +namespace test { + +// Helper class to override |variable_name| environment variable to |value| for +// the lifetime of this class. Upon destruction, the previous value is restored. +class ScopedEnvironmentVariableOverride final { + public: + ScopedEnvironmentVariableOverride(const std::string& variable_name, + const std::string& value); + ~ScopedEnvironmentVariableOverride(); + + base::Environment* GetEnv() { return environment_.get(); } + bool IsOverridden() { return overridden_; } + bool WasSet() { return was_set_; } + + private: + std::unique_ptr<Environment> environment_; + std::string variable_name_; + bool overridden_; + bool was_set_; + std::string old_value_; +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_SCOPED_ENVIRONMENT_VARIABLE_OVERRIDE_H_ diff --git a/chromium/base/test/scoped_feature_list.cc b/chromium/base/test/scoped_feature_list.cc new file mode 100644 index 00000000000..0e5afdb2e36 --- /dev/null +++ b/chromium/base/test/scoped_feature_list.cc @@ -0,0 +1,301 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_feature_list.h" + +#include <algorithm> +#include <utility> +#include <vector> + +#include "base/memory/ptr_util.h" +#include "base/metrics/field_trial_param_associator.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/test/mock_entropy_provider.h" + +namespace base { +namespace test { + +namespace { + +constexpr char kTrialGroup[] = "scoped_feature_list_trial_group"; + +std::vector<StringPiece> GetFeatureVector( + const std::vector<Feature>& features) { + std::vector<StringPiece> output; + for (const Feature& feature : features) { + output.push_back(feature.name); + } + + return output; +} + +std::vector<StringPiece> GetFeatureVectorFromFeaturesAndParams( + const std::vector<ScopedFeatureList::FeatureAndParams>& + features_and_params) { + std::vector<StringPiece> output; + for (const auto& entry : features_and_params) { + output.push_back(entry.feature.name); + } + + return output; +} + +// Extracts a feature name from a feature state string. For example, given +// the input "*MyLovelyFeature<SomeFieldTrial", returns "MyLovelyFeature". +StringPiece GetFeatureName(StringPiece feature) { + StringPiece feature_name = feature; + + // Remove default info. + if (feature_name.starts_with("*")) + feature_name = feature_name.substr(1); + + // Remove field_trial info. + std::size_t index = feature_name.find("<"); + if (index != std::string::npos) + feature_name = feature_name.substr(0, index); + + return feature_name; +} + +struct Features { + std::vector<StringPiece> enabled_feature_list; + std::vector<StringPiece> disabled_feature_list; +}; + +// Features in |feature_vector| came from |merged_features| in +// OverrideFeatures() and contains linkage with field trial is case when they +// have parameters (with '<' simbol). In |feature_name| name is already cleared +// with GetFeatureName() and also could be without parameters. +bool ContainsFeature(const std::vector<StringPiece>& feature_vector, + StringPiece feature_name) { + auto iter = std::find_if(feature_vector.begin(), feature_vector.end(), + [&feature_name](const StringPiece& a) { + return GetFeatureName(a) == feature_name; + }); + return iter != feature_vector.end(); +} + +// Merges previously-specified feature overrides with those passed into one of +// the Init() methods. |features| should be a list of features previously +// overridden to be in the |override_state|. |merged_features| should contain +// the enabled and disabled features passed into the Init() method, plus any +// overrides merged as a result of previous calls to this function. +void OverrideFeatures(const std::string& features, + FeatureList::OverrideState override_state, + Features* merged_features) { + std::vector<StringPiece> features_list = + SplitStringPiece(features, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); + + for (StringPiece feature : features_list) { + StringPiece feature_name = GetFeatureName(feature); + + if (ContainsFeature(merged_features->enabled_feature_list, feature_name) || + ContainsFeature(merged_features->disabled_feature_list, feature_name)) { + continue; + } + + if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) { + merged_features->enabled_feature_list.push_back(feature); + } else { + DCHECK_EQ(override_state, + FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE); + merged_features->disabled_feature_list.push_back(feature); + } + } +} + +// Hex encode params so that special characters do not break formatting. +std::string HexEncodeString(const std::string& input) { + return HexEncode(input.data(), input.size()); +} + +// Inverse of HexEncodeString(). +std::string HexDecodeString(const std::string& input) { + if (input.empty()) + return std::string(); + std::string bytes; + bool result = HexStringToString(input, &bytes); + DCHECK(result); + return bytes; +} + +} // namespace + +ScopedFeatureList::FeatureAndParams::FeatureAndParams( + const Feature& feature, + const FieldTrialParams& params) + : feature(feature), params(params) {} + +ScopedFeatureList::FeatureAndParams::~FeatureAndParams() = default; + +ScopedFeatureList::FeatureAndParams::FeatureAndParams( + const FeatureAndParams& other) = default; + +ScopedFeatureList::ScopedFeatureList() = default; + +ScopedFeatureList::~ScopedFeatureList() { + Reset(); +} + +void ScopedFeatureList::Reset() { + // If one of the Init() functions was never called, don't reset anything. + if (!init_called_) + return; + + init_called_ = false; + + FeatureList::ClearInstanceForTesting(); + + if (field_trial_list_) { + field_trial_list_.reset(); + + // Restore params to how they were before. + FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting(); + AssociateFieldTrialParamsFromString(original_params_, &HexDecodeString); + + FieldTrialList::RestoreInstanceForTesting(original_field_trial_list_); + original_field_trial_list_ = nullptr; + } + if (original_feature_list_) + FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_)); +} + +void ScopedFeatureList::Init() { + InitWithFeaturesImpl({}, {}, {}); +} + +void ScopedFeatureList::InitWithFeatureList( + std::unique_ptr<FeatureList> feature_list) { + DCHECK(!original_feature_list_); + original_feature_list_ = FeatureList::ClearInstanceForTesting(); + FeatureList::SetInstance(std::move(feature_list)); + init_called_ = true; +} + +void ScopedFeatureList::InitFromCommandLine( + const std::string& enable_features, + const std::string& disable_features) { + std::unique_ptr<FeatureList> feature_list(new FeatureList); + feature_list->InitializeFromCommandLine(enable_features, disable_features); + InitWithFeatureList(std::move(feature_list)); +} + +void ScopedFeatureList::InitWithFeatures( + const std::vector<Feature>& enabled_features, + const std::vector<Feature>& disabled_features) { + InitWithFeaturesImpl(enabled_features, {}, disabled_features); +} + +void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) { + InitWithFeaturesImpl({feature}, {}, {}); +} + +void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) { + InitWithFeaturesImpl({}, {}, {feature}); +} + +void ScopedFeatureList::InitWithFeatureState(const Feature& feature, + bool enabled) { + if (enabled) { + InitAndEnableFeature(feature); + } else { + InitAndDisableFeature(feature); + } +} + +void ScopedFeatureList::InitWithFeaturesImpl( + const std::vector<Feature>& enabled_features, + const std::vector<FeatureAndParams>& enabled_features_and_params, + const std::vector<Feature>& disabled_features) { + DCHECK(!init_called_); + DCHECK(enabled_features.empty() || enabled_features_and_params.empty()); + + Features merged_features; + if (!enabled_features_and_params.empty()) { + merged_features.enabled_feature_list = + GetFeatureVectorFromFeaturesAndParams(enabled_features_and_params); + } else { + merged_features.enabled_feature_list = GetFeatureVector(enabled_features); + } + merged_features.disabled_feature_list = GetFeatureVector(disabled_features); + + std::string current_enabled_features; + std::string current_disabled_features; + FeatureList* feature_list = FeatureList::GetInstance(); + if (feature_list) { + feature_list->GetFeatureOverrides(¤t_enabled_features, + ¤t_disabled_features); + } + + // Save off the existing field trials and params. + std::string existing_trial_state; + FieldTrialList::AllStatesToString(&existing_trial_state, true); + original_params_ = FieldTrialList::AllParamsToString(true, &HexEncodeString); + + // Back up the current field trial list, to be restored in Reset(). + original_field_trial_list_ = FieldTrialList::BackupInstanceForTesting(); + + // Create a field trial list, to which we'll add trials corresponding to the + // features that have params, before restoring the field trial state from the + // previous instance, further down in this function. + field_trial_list_ = + std::make_unique<FieldTrialList>(std::make_unique<MockEntropyProvider>()); + + // Associate override params. This needs to be done before trial state gets + // restored, as that will activate trials, locking down param association. + auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance(); + std::vector<std::string> features_with_trial; + auto feature_it = merged_features.enabled_feature_list.begin(); + for (const auto& enabled_feature : enabled_features_and_params) { + const std::string feature_name = enabled_feature.feature.name; + const std::string trial_name = + "scoped_feature_list_trial_for_" + feature_name; + + scoped_refptr<FieldTrial> field_trial_override = + FieldTrialList::CreateFieldTrial(trial_name, kTrialGroup); + DCHECK(field_trial_override); + + field_trial_param_associator->ClearParamsForTesting(trial_name, + kTrialGroup); + bool success = field_trial_param_associator->AssociateFieldTrialParams( + trial_name, kTrialGroup, enabled_feature.params); + DCHECK(success); + + features_with_trial.push_back(feature_name + "<" + trial_name); + *feature_it = features_with_trial.back(); + ++feature_it; + } + // Restore other field trials. Note: We don't need to do anything for params + // here because the param associator already has the right state, which has + // been backed up via |original_params_| to be restored later. + FieldTrialList::CreateTrialsFromString(existing_trial_state, {}); + + OverrideFeatures(current_enabled_features, + FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE, + &merged_features); + OverrideFeatures(current_disabled_features, + FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE, + &merged_features); + + std::string enabled = JoinString(merged_features.enabled_feature_list, ","); + std::string disabled = JoinString(merged_features.disabled_feature_list, ","); + InitFromCommandLine(enabled, disabled); +} + +void ScopedFeatureList::InitAndEnableFeatureWithParameters( + const Feature& feature, + const FieldTrialParams& feature_parameters) { + InitWithFeaturesAndParameters({{feature, feature_parameters}}, {}); +} + +void ScopedFeatureList::InitWithFeaturesAndParameters( + const std::vector<FeatureAndParams>& enabled_features, + const std::vector<Feature>& disabled_features) { + InitWithFeaturesImpl({}, enabled_features, disabled_features); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/scoped_feature_list.h b/chromium/base/test/scoped_feature_list.h new file mode 100644 index 00000000000..1d5bc7e3304 --- /dev/null +++ b/chromium/base/test/scoped_feature_list.h @@ -0,0 +1,152 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_FEATURE_LIST_H_ +#define BASE_TEST_SCOPED_FEATURE_LIST_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/feature_list.h" +#include "base/memory/ref_counted.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/field_trial_params.h" + +namespace base { +namespace test { + +// ScopedFeatureList resets the global FeatureList instance to a new empty +// instance and restores the original instance upon destruction. When using the +// non-deprecated APIs, a corresponding FieldTrialList is also created. +// +// Note: Re-using the same object is not allowed. To reset the feature +// list and initialize it anew, destroy an existing scoped list and init +// a new one. +// +// If multiple instances of this class are used in a nested fashion, they +// should be destroyed in the opposite order of their Init*() methods being +// called. +// +// ScopedFeatureList needs to be initialized (via one of Init*() methods) +// before running code that inspects the state of features, such as in the +// constructor of the test harness. +// +// WARNING: To be clear, in multithreaded test environments (such as browser +// tests) there may background threads using FeatureList before the test body is +// even entered. In these cases it is imperative that ScopedFeatureList be +// initialized BEFORE those threads are started, hence the recommendation to do +// initialization in the test harness's constructor. +class ScopedFeatureList final { + public: + ScopedFeatureList(); + ~ScopedFeatureList(); + + struct FeatureAndParams { + FeatureAndParams(const Feature& feature, const FieldTrialParams& params); + ~FeatureAndParams(); + + FeatureAndParams(const FeatureAndParams& other); + + const Feature& feature; + const FieldTrialParams params; + }; + + // Resets the instance to a non-initialized state. + void Reset(); + + // Initializes and registers a FeatureList instance without any additional + // enabled or disabled features. Existing state, if any, will be kept. This is + // equivalent to calling InitWithFeatures({}, {}). + void Init(); + + // WARNING: This method will reset any globally configured features to their + // default values, which can hide feature interaction bugs. Please use + // sparingly. https://crbug.com/713390 + // Initializes and registers the given FeatureList instance. + void InitWithFeatureList(std::unique_ptr<FeatureList> feature_list); + + // WARNING: This method will reset any globally configured features to their + // default values, which can hide feature interaction bugs. Please use + // sparingly. https://crbug.com/713390 + // Initializes and registers a FeatureList instance with only the given + // enabled and disabled features (comma-separated names). + void InitFromCommandLine(const std::string& enable_features, + const std::string& disable_features); + + // Initializes and registers a FeatureList instance based on present + // FeatureList and overridden with the given enabled and disabled features. + // Any feature overrides already present in the global FeatureList will + // continue to apply, unless they conflict with the overrides passed into this + // method. This is important for testing potentially unexpected feature + // interactions. + void InitWithFeatures(const std::vector<Feature>& enabled_features, + const std::vector<Feature>& disabled_features); + + // Initializes and registers a FeatureList instance based on present + // FeatureList and overridden with single enabled feature. + void InitAndEnableFeature(const Feature& feature); + + // Initializes and registers a FeatureList instance based on present + // FeatureList and overridden with single enabled feature and associated field + // trial parameters. + // Note: this creates a scoped global field trial list if there is not + // currently one. + void InitAndEnableFeatureWithParameters( + const Feature& feature, + const FieldTrialParams& feature_parameters); + + // Initializes and registers a FeatureList instance based on present + // FeatureList and overridden with the given enabled features and the + // specified field trial parameters, and the given disabled features. + // Note: This creates a scoped global field trial list if there is not + // currently one. + void InitWithFeaturesAndParameters( + const std::vector<FeatureAndParams>& enabled_features, + const std::vector<Feature>& disabled_features); + + // Initializes and registers a FeatureList instance based on present + // FeatureList and overridden with single disabled feature. + void InitAndDisableFeature(const Feature& feature); + + // Initializes and registers a FeatureList instance based on present + // FeatureList and overriden with a single feature either enabled or + // disabled depending on |enabled|. + void InitWithFeatureState(const Feature& feature, bool enabled); + + private: + // Initializes and registers a FeatureList instance based on present + // FeatureList and overridden with the given enabled and disabled features. + // Any feature overrides already present in the global FeatureList will + // continue to apply, unless they conflict with the overrides passed into this + // method. + // Features to enable may be specified through either |enabled_features| or + // |enabled_feature_and_params|, but not both (i.e. one of these must be + // empty). + void InitWithFeaturesImpl( + const std::vector<Feature>& enabled_features, + const std::vector<FeatureAndParams>& enabled_features_and_params, + const std::vector<Feature>& disabled_features); + + // Initializes and registers a FeatureList instance based on present + // FeatureList and overridden with single enabled feature and associated field + // trial override. + // |trial| is expected to outlive the ScopedFeatureList. + void InitAndEnableFeatureWithFieldTrialOverride(const Feature& feature, + FieldTrial* trial); + + bool init_called_ = false; + std::unique_ptr<FeatureList> original_feature_list_; + base::FieldTrialList* original_field_trial_list_; + std::string original_params_; + std::unique_ptr<base::FieldTrialList> field_trial_list_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFeatureList); +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_SCOPED_FEATURE_LIST_H_ diff --git a/chromium/base/test/scoped_feature_list_unittest.cc b/chromium/base/test/scoped_feature_list_unittest.cc new file mode 100644 index 00000000000..53a7bde390a --- /dev/null +++ b/chromium/base/test/scoped_feature_list_unittest.cc @@ -0,0 +1,430 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_feature_list.h" + +#include <map> +#include <string> +#include <utility> + +#include "base/metrics/field_trial.h" +#include "base/metrics/field_trial_params.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace test { + +namespace { + +const Feature kTestFeature1{"TestFeature1", FEATURE_DISABLED_BY_DEFAULT}; +const Feature kTestFeature2{"TestFeature2", FEATURE_DISABLED_BY_DEFAULT}; + +void ExpectFeatures(const std::string& enabled_features, + const std::string& disabled_features) { + FeatureList* list = FeatureList::GetInstance(); + std::string actual_enabled_features; + std::string actual_disabled_features; + + list->GetFeatureOverrides(&actual_enabled_features, + &actual_disabled_features); + + EXPECT_EQ(enabled_features, actual_enabled_features); + EXPECT_EQ(disabled_features, actual_disabled_features); +} + +} // namespace + +class ScopedFeatureListTest : public testing::Test { + public: + ScopedFeatureListTest() { + // Clear default feature list. + std::unique_ptr<FeatureList> feature_list(new FeatureList); + feature_list->InitializeFromCommandLine(std::string(), std::string()); + original_feature_list_ = FeatureList::ClearInstanceForTesting(); + FeatureList::SetInstance(std::move(feature_list)); + } + + ~ScopedFeatureListTest() override { + // Restore feature list. + if (original_feature_list_) { + FeatureList::ClearInstanceForTesting(); + FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_)); + } + } + + private: + // Save the present FeatureList and restore it after test finish. + std::unique_ptr<FeatureList> original_feature_list_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFeatureListTest); +}; + +TEST_F(ScopedFeatureListTest, BasicScoped) { + ExpectFeatures(std::string(), std::string()); + EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature1)); + { + test::ScopedFeatureList feature_list1; + feature_list1.InitFromCommandLine("TestFeature1", std::string()); + ExpectFeatures("TestFeature1", std::string()); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + } + ExpectFeatures(std::string(), std::string()); + EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature1)); +} + +TEST_F(ScopedFeatureListTest, EnableWithFeatureParameters) { + const char kParam1[] = "param_1"; + const char kParam2[] = "param_2"; + const char kValue1[] = "value_1"; + const char kValue2[] = "value_2"; + std::map<std::string, std::string> parameters; + parameters[kParam1] = kValue1; + parameters[kParam2] = kValue2; + + ExpectFeatures(std::string(), std::string()); + EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam1)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam2)); + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_EQ(0u, active_groups.size()); + + { + test::ScopedFeatureList feature_list; + + feature_list.InitAndEnableFeatureWithParameters(kTestFeature1, parameters); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_EQ(kValue1, + GetFieldTrialParamValueByFeature(kTestFeature1, kParam1)); + EXPECT_EQ(kValue2, + GetFieldTrialParamValueByFeature(kTestFeature1, kParam2)); + active_groups.clear(); + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_EQ(1u, active_groups.size()); + } + + ExpectFeatures(std::string(), std::string()); + EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam1)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam2)); + active_groups.clear(); + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_EQ(0u, active_groups.size()); +} + +TEST_F(ScopedFeatureListTest, OverrideWithFeatureParameters) { + scoped_refptr<FieldTrial> trial = + FieldTrialList::CreateFieldTrial("foo", "bar"); + const char kParam[] = "param_1"; + const char kValue[] = "value_1"; + std::map<std::string, std::string> parameters; + parameters[kParam] = kValue; + + test::ScopedFeatureList feature_list1; + feature_list1.InitFromCommandLine("TestFeature1<foo,TestFeature2", + std::string()); + + // Check initial state. + ExpectFeatures("TestFeature1<foo,TestFeature2", std::string()); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ(trial.get(), FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); + + { + // Override feature with existing field trial. + test::ScopedFeatureList feature_list2; + + feature_list2.InitAndEnableFeatureWithParameters(kTestFeature1, parameters); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); + EXPECT_NE(trial.get(), FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_NE(nullptr, FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2)); + } + + // Check that initial state is restored. + ExpectFeatures("TestFeature1<foo,TestFeature2", std::string()); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ(trial.get(), FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); + + { + // Override feature with no existing field trial. + test::ScopedFeatureList feature_list2; + + feature_list2.InitAndEnableFeatureWithParameters(kTestFeature2, parameters); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); + EXPECT_EQ(trial.get()->trial_name(), + FeatureList::GetFieldTrial(kTestFeature1)->trial_name()); + EXPECT_EQ(trial.get()->group_name(), + FeatureList::GetFieldTrial(kTestFeature1)->group_name()); + EXPECT_NE(nullptr, FeatureList::GetFieldTrial(kTestFeature2)); + } + + // Check that initial state is restored. + ExpectFeatures("TestFeature1<foo,TestFeature2", std::string()); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ(trial.get(), FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); +} + +TEST_F(ScopedFeatureListTest, OverrideMultipleFeaturesWithParameters) { + scoped_refptr<FieldTrial> trial1 = + FieldTrialList::CreateFieldTrial("foo1", "bar1"); + const char kParam[] = "param_1"; + const char kValue1[] = "value_1"; + const char kValue2[] = "value_2"; + std::map<std::string, std::string> parameters1; + parameters1[kParam] = kValue1; + std::map<std::string, std::string> parameters2; + parameters2[kParam] = kValue2; + + test::ScopedFeatureList feature_list1; + feature_list1.InitFromCommandLine("TestFeature1<foo1,TestFeature2", + std::string()); + + // Check initial state. + ExpectFeatures("TestFeature1<foo1,TestFeature2", std::string()); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ(trial1.get(), FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); + + { + // Override multiple features with parameters. + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeaturesAndParameters( + {{kTestFeature1, parameters1}, {kTestFeature2, parameters2}}, {}); + + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ(kValue1, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ(kValue2, GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); + } + + { + // Override a feature with a parameter and disable another one. + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeaturesAndParameters({{kTestFeature1, parameters2}}, + {kTestFeature2}); + + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ(kValue2, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); + } + + // Check that initial state is restored. + ExpectFeatures("TestFeature1<foo1,TestFeature2", std::string()); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2)); + EXPECT_EQ(trial1.get(), FeatureList::GetFieldTrial(kTestFeature1)); + EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); +} + +TEST_F(ScopedFeatureListTest, ParamsWithSpecialCharsPreserved) { + // Check that special characters in param names and values are preserved. + const char kParam[] = ";_\\<:>/_!?"; + const char kValue[] = ",;:/'!?"; + FieldTrialParams params0 = {{kParam, kValue}}; + + test::ScopedFeatureList feature_list0; + feature_list0.InitWithFeaturesAndParameters({{kTestFeature1, params0}}, {}); + EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + + { + const char kValue1[] = "normal"; + FieldTrialParams params1 = {{kParam, kValue1}}; + test::ScopedFeatureList feature_list1; + feature_list1.InitWithFeaturesAndParameters({{kTestFeature1, params1}}, {}); + + EXPECT_EQ(kValue1, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + } + EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + + { + const char kValue2[] = "[<(2)>]"; + FieldTrialParams params2 = {{kParam, kValue2}}; + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeaturesAndParameters({{kTestFeature2, params2}}, {}); + + EXPECT_EQ(kValue2, GetFieldTrialParamValueByFeature(kTestFeature2, kParam)); + EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + } + EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); +} + +TEST_F(ScopedFeatureListTest, ParamsWithEmptyValue) { + const char kParam[] = "p"; + const char kEmptyValue[] = ""; + FieldTrialParams params = {{kParam, kEmptyValue}}; + + test::ScopedFeatureList feature_list0; + feature_list0.InitWithFeaturesAndParameters({{kTestFeature1, params}}, {}); + EXPECT_EQ(kEmptyValue, + GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + { + const char kValue1[] = "normal"; + FieldTrialParams params1 = {{kParam, kValue1}}; + test::ScopedFeatureList feature_list1; + feature_list1.InitWithFeaturesAndParameters({{kTestFeature1, params1}}, {}); + + EXPECT_EQ(kValue1, GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); + } + EXPECT_EQ(kEmptyValue, + GetFieldTrialParamValueByFeature(kTestFeature1, kParam)); +} + +TEST_F(ScopedFeatureListTest, EnableFeatureOverrideDisable) { + test::ScopedFeatureList feature_list1; + feature_list1.InitWithFeatures({}, {kTestFeature1}); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({kTestFeature1}, {}); + ExpectFeatures("TestFeature1", std::string()); + } +} + +TEST_F(ScopedFeatureListTest, FeatureOverrideNotMakeDuplicate) { + test::ScopedFeatureList feature_list1; + feature_list1.InitWithFeatures({}, {kTestFeature1}); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({}, {kTestFeature1}); + ExpectFeatures(std::string(), "TestFeature1"); + } +} + +TEST_F(ScopedFeatureListTest, FeatureOverrideFeatureWithDefault) { + test::ScopedFeatureList feature_list1; + feature_list1.InitFromCommandLine("*TestFeature1", std::string()); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({kTestFeature1}, {}); + ExpectFeatures("TestFeature1", std::string()); + } +} + +TEST_F(ScopedFeatureListTest, FeatureOverrideFeatureWithDefault2) { + test::ScopedFeatureList feature_list1; + feature_list1.InitFromCommandLine("*TestFeature1", std::string()); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({}, {kTestFeature1}); + ExpectFeatures(std::string(), "TestFeature1"); + } +} + +TEST_F(ScopedFeatureListTest, FeatureOverrideFeatureWithEnabledFieldTrial) { + test::ScopedFeatureList feature_list1; + + std::unique_ptr<FeatureList> feature_list(new FeatureList); + FieldTrial* trial = FieldTrialList::CreateFieldTrial("TrialExample", "A"); + feature_list->RegisterFieldTrialOverride( + kTestFeature1.name, FeatureList::OVERRIDE_ENABLE_FEATURE, trial); + feature_list1.InitWithFeatureList(std::move(feature_list)); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({kTestFeature1}, {}); + ExpectFeatures("TestFeature1", std::string()); + } +} + +TEST_F(ScopedFeatureListTest, FeatureOverrideFeatureWithDisabledFieldTrial) { + test::ScopedFeatureList feature_list1; + + std::unique_ptr<FeatureList> feature_list(new FeatureList); + FieldTrial* trial = FieldTrialList::CreateFieldTrial("TrialExample", "A"); + feature_list->RegisterFieldTrialOverride( + kTestFeature1.name, FeatureList::OVERRIDE_DISABLE_FEATURE, trial); + feature_list1.InitWithFeatureList(std::move(feature_list)); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({kTestFeature1}, {}); + ExpectFeatures("TestFeature1", std::string()); + } +} + +TEST_F(ScopedFeatureListTest, FeatureOverrideKeepsOtherExistingFeature) { + test::ScopedFeatureList feature_list1; + feature_list1.InitWithFeatures({}, {kTestFeature1}); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({}, {kTestFeature2}); + EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature1)); + EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature2)); + } +} + +TEST_F(ScopedFeatureListTest, FeatureOverrideKeepsOtherExistingFeature2) { + test::ScopedFeatureList feature_list1; + feature_list1.InitWithFeatures({}, {kTestFeature1}); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({kTestFeature2}, {}); + ExpectFeatures("TestFeature2", "TestFeature1"); + } +} + +TEST_F(ScopedFeatureListTest, FeatureOverrideKeepsOtherExistingDefaultFeature) { + test::ScopedFeatureList feature_list1; + feature_list1.InitFromCommandLine("*TestFeature1", std::string()); + + { + test::ScopedFeatureList feature_list2; + feature_list2.InitWithFeatures({}, {kTestFeature2}); + ExpectFeatures("*TestFeature1", "TestFeature2"); + } +} + +TEST_F(ScopedFeatureListTest, ScopedFeatureListIsNoopWhenNotInitialized) { + test::ScopedFeatureList feature_list1; + feature_list1.InitFromCommandLine("*TestFeature1", std::string()); + + // A ScopedFeatureList on which Init() is not called should not reset things + // when going out of scope. + { test::ScopedFeatureList feature_list2; } + + ExpectFeatures("*TestFeature1", std::string()); +} + +TEST(ScopedFeatureListTestWithMemberList, ScopedFeatureListLocalOverride) { + test::ScopedFeatureList initial_feature_list; + initial_feature_list.InitAndDisableFeature(kTestFeature1); + { + base::test::ScopedFeatureList scoped_features; + scoped_features.InitAndEnableFeatureWithParameters(kTestFeature1, + {{"mode", "nobugs"}}); + ASSERT_TRUE(FeatureList::IsEnabled(kTestFeature1)); + } +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/scoped_field_trial_list_resetter.cc b/chromium/base/test/scoped_field_trial_list_resetter.cc new file mode 100644 index 00000000000..0bc657b8229 --- /dev/null +++ b/chromium/base/test/scoped_field_trial_list_resetter.cc @@ -0,0 +1,21 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_field_trial_list_resetter.h" + +#include "base/metrics/field_trial.h" + +namespace base { +namespace test { + +ScopedFieldTrialListResetter::ScopedFieldTrialListResetter() + : original_field_trial_list_( + base::FieldTrialList::BackupInstanceForTesting()) {} + +ScopedFieldTrialListResetter::~ScopedFieldTrialListResetter() { + base::FieldTrialList::RestoreInstanceForTesting(original_field_trial_list_); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/scoped_field_trial_list_resetter.h b/chromium/base/test/scoped_field_trial_list_resetter.h new file mode 100644 index 00000000000..d7d2dcd35e9 --- /dev/null +++ b/chromium/base/test/scoped_field_trial_list_resetter.h @@ -0,0 +1,36 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_FIELD_TRIAL_LIST_RESETTER_H_ +#define BASE_TEST_SCOPED_FIELD_TRIAL_LIST_RESETTER_H_ + +namespace base { + +class FieldTrialList; + +namespace test { + +// DISCLAIMER: Please use ScopedFeatureList except for advanced cases where +// custom instantiation of FieldTrialList is required. +// +// ScopedFieldTrialListResetter resets the global FieldTrialList instance to +// null, and restores the original state when the class goes out of scope. This +// allows client code to initialize FieldTrialList instances in a custom +// fashion. +class ScopedFieldTrialListResetter final { + public: + ScopedFieldTrialListResetter(); + ScopedFieldTrialListResetter(const ScopedFieldTrialListResetter&) = delete; + ScopedFieldTrialListResetter(ScopedFieldTrialListResetter&&) = delete; + + ~ScopedFieldTrialListResetter(); + + private: + base::FieldTrialList* const original_field_trial_list_; +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_SCOPED_FIELD_TRIAL_LIST_RESETTER_H_ diff --git a/chromium/base/test/scoped_locale.cc b/chromium/base/test/scoped_locale.cc new file mode 100644 index 00000000000..c0182842b6d --- /dev/null +++ b/chromium/base/test/scoped_locale.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_locale.h" + +#include <locale.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +ScopedLocale::ScopedLocale(const std::string& locale) { + prev_locale_ = setlocale(LC_ALL, nullptr); + EXPECT_TRUE(setlocale(LC_ALL, locale.c_str()) != nullptr) + << "Failed to set locale: " << locale; +} + +ScopedLocale::~ScopedLocale() { + EXPECT_STREQ(prev_locale_.c_str(), setlocale(LC_ALL, prev_locale_.c_str())); +} + +} // namespace base diff --git a/chromium/base/test/scoped_locale.h b/chromium/base/test/scoped_locale.h new file mode 100644 index 00000000000..ef64e98f8eb --- /dev/null +++ b/chromium/base/test/scoped_locale.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_LOCALE_H_ +#define BASE_TEST_SCOPED_LOCALE_H_ + +#include <string> + +#include "base/macros.h" + +namespace base { + +// Sets the given |locale| on construction, and restores the previous locale +// on destruction. +class ScopedLocale { + public: + explicit ScopedLocale(const std::string& locale); + ~ScopedLocale(); + + private: + std::string prev_locale_; + + DISALLOW_COPY_AND_ASSIGN(ScopedLocale); +}; + +} // namespace base + +#endif // BASE_TEST_SCOPED_LOCALE_H_ diff --git a/chromium/base/test/scoped_mock_clock_override.cc b/chromium/base/test/scoped_mock_clock_override.cc new file mode 100644 index 00000000000..46cc88437cd --- /dev/null +++ b/chromium/base/test/scoped_mock_clock_override.cc @@ -0,0 +1,46 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_mock_clock_override.h" + +namespace base { + +ScopedMockClockOverride* ScopedMockClockOverride::scoped_mock_clock_ = nullptr; + +ScopedMockClockOverride::ScopedMockClockOverride() + : // Start the offset past zero so that it's not treated as a null value. + offset_(TimeDelta::FromDays(365)) { + DCHECK(!scoped_mock_clock_) + << "Nested ScopedMockClockOverrides are not supported."; + + scoped_mock_clock_ = this; + + time_clock_overrides_ = std::make_unique<subtle::ScopedTimeClockOverrides>( + &ScopedMockClockOverride::Now, &ScopedMockClockOverride::NowTicks, + &ScopedMockClockOverride::NowThreadTicks); +} + +ScopedMockClockOverride::~ScopedMockClockOverride() { + scoped_mock_clock_ = nullptr; +} + +Time ScopedMockClockOverride::Now() { + return Time() + scoped_mock_clock_->offset_; +} + +TimeTicks ScopedMockClockOverride::NowTicks() { + return TimeTicks() + scoped_mock_clock_->offset_; +} + +ThreadTicks ScopedMockClockOverride::NowThreadTicks() { + return ThreadTicks() + scoped_mock_clock_->offset_; +} + +void ScopedMockClockOverride::Advance(TimeDelta delta) { + DCHECK_GT(delta, base::TimeDelta()) + << "Monotonically increasing time may not go backwards"; + offset_ += delta; +} + +} // namespace base diff --git a/chromium/base/test/scoped_mock_clock_override.h b/chromium/base/test/scoped_mock_clock_override.h new file mode 100644 index 00000000000..9f7a7e5d5a6 --- /dev/null +++ b/chromium/base/test/scoped_mock_clock_override.h @@ -0,0 +1,54 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_MOCK_CLOCK_OVERRIDE_H_ +#define BASE_TEST_SCOPED_MOCK_CLOCK_OVERRIDE_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/time/time.h" +#include "base/time/time_override.h" + +namespace base { + +// Override the return value of Time::Now(), Time::NowFromSystemTime(), +// TimeTicks::Now(), and ThreadTicks::Now() through a simple advanceable clock. +// +// This utility is intended to support tests that: +// +// - Depend on large existing codebases that call TimeXYZ::Now() directly or +// - Have no ability to inject a TickClock into the code getting the time +// (e.g. integration tests in which a TickClock would be several layers +// removed from the test code) +// +// For new unit tests, developers are highly encouraged to structure new code +// around a dependency injected base::Clock, base::TickClock, etc. to be able +// to supply a mock time in tests without a global override. +// +// NOTE: ScopedMockClockOverride should be created while single-threaded and +// before the first call to Now() to avoid threading issues and inconsistencies +// in returned values. Nested overrides are not allowed. +class ScopedMockClockOverride { + public: + ScopedMockClockOverride(); + ~ScopedMockClockOverride(); + + static Time Now(); + static TimeTicks NowTicks(); + static ThreadTicks NowThreadTicks(); + + void Advance(TimeDelta delta); + + private: + std::unique_ptr<base::subtle::ScopedTimeClockOverrides> time_clock_overrides_; + TimeDelta offset_; + static ScopedMockClockOverride* scoped_mock_clock_; + + DISALLOW_COPY_AND_ASSIGN(ScopedMockClockOverride); +}; + +} // namespace base + +#endif // BASE_TEST_SCOPED_MOCK_CLOCK_OVERRIDE_H_ diff --git a/chromium/base/test/scoped_mock_clock_override_unittest.cc b/chromium/base/test/scoped_mock_clock_override_unittest.cc new file mode 100644 index 00000000000..ab935e07db9 --- /dev/null +++ b/chromium/base/test/scoped_mock_clock_override_unittest.cc @@ -0,0 +1,104 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_mock_clock_override.h" + +#include "base/build_time.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +#if defined(OS_FUCHSIA) +// TODO(https://crbug.com/1060357): Enable when RTC flake is fixed. +#define MAYBE_Time DISABLED_Time +#else +#define MAYBE_Time Time +#endif + +TEST(ScopedMockClockOverrideTest, MAYBE_Time) { + // Choose a reference time that we know to be in the past but close to now. + Time build_time = GetBuildTime(); + + // Override is not active. All Now() methods should return a time greater than + // the build time. + EXPECT_LT(build_time, Time::Now()); + EXPECT_GT(Time::Max(), Time::Now()); + EXPECT_LT(build_time, Time::NowFromSystemTime()); + EXPECT_GT(Time::Max(), Time::NowFromSystemTime()); + + { + // Set override. + ScopedMockClockOverride mock_clock; + + EXPECT_NE(Time(), Time::Now()); + Time start = Time::Now(); + mock_clock.Advance(TimeDelta::FromSeconds(1)); + EXPECT_EQ(start + TimeDelta::FromSeconds(1), Time::Now()); + } + + // All methods return real time again. + EXPECT_LT(build_time, Time::Now()); + EXPECT_GT(Time::Max(), Time::Now()); + EXPECT_LT(build_time, Time::NowFromSystemTime()); + EXPECT_GT(Time::Max(), Time::NowFromSystemTime()); +} + +TEST(ScopedMockClockOverrideTest, TimeTicks) { + // Override is not active. All Now() methods should return a sensible value. + EXPECT_LT(TimeTicks::UnixEpoch(), TimeTicks::Now()); + EXPECT_GT(TimeTicks::Max(), TimeTicks::Now()); + EXPECT_LT(TimeTicks::UnixEpoch() + TimeDelta::FromDays(365), + TimeTicks::Now()); + + { + // Set override. + ScopedMockClockOverride mock_clock; + + EXPECT_NE(TimeTicks(), TimeTicks::Now()); + TimeTicks start = TimeTicks::Now(); + mock_clock.Advance(TimeDelta::FromSeconds(1)); + EXPECT_EQ(start + TimeDelta::FromSeconds(1), TimeTicks::Now()); + } + + // All methods return real ticks again. + EXPECT_LT(TimeTicks::UnixEpoch(), TimeTicks::Now()); + EXPECT_GT(TimeTicks::Max(), TimeTicks::Now()); + EXPECT_LT(TimeTicks::UnixEpoch() + TimeDelta::FromDays(365), + TimeTicks::Now()); +} + +TEST(ScopedMockClockOverrideTest, ThreadTicks) { + if (ThreadTicks::IsSupported()) { + ThreadTicks::WaitUntilInitialized(); + + // Override is not active. All Now() methods should return a sensible value. + ThreadTicks initial_thread_ticks = ThreadTicks::Now(); + EXPECT_LE(initial_thread_ticks, ThreadTicks::Now()); + EXPECT_GT(ThreadTicks::Max(), ThreadTicks::Now()); + EXPECT_LT(ThreadTicks(), ThreadTicks::Now()); + + { + // Set override. + ScopedMockClockOverride mock_clock; + + EXPECT_NE(ThreadTicks(), ThreadTicks::Now()); + ThreadTicks start = ThreadTicks::Now(); + mock_clock.Advance(TimeDelta::FromSeconds(1)); + EXPECT_EQ(start + TimeDelta::FromSeconds(1), ThreadTicks::Now()); + } + + // All methods return real ticks again. + EXPECT_LE(initial_thread_ticks, ThreadTicks::Now()); + EXPECT_GT(ThreadTicks::Max(), ThreadTicks::Now()); + EXPECT_LT(ThreadTicks(), ThreadTicks::Now()); + } +} + +} // namespace + +} // namespace base diff --git a/chromium/base/test/scoped_mock_time_message_loop_task_runner.cc b/chromium/base/test/scoped_mock_time_message_loop_task_runner.cc new file mode 100644 index 00000000000..0ace2923566 --- /dev/null +++ b/chromium/base/test/scoped_mock_time_message_loop_task_runner.cc @@ -0,0 +1,38 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_mock_time_message_loop_task_runner.h" + +#include "base/bind.h" +#include "base/check_op.h" +#include "base/message_loop/message_loop_current.h" +#include "base/run_loop.h" +#include "base/test/test_pending_task.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" + +namespace base { + +ScopedMockTimeMessageLoopTaskRunner::ScopedMockTimeMessageLoopTaskRunner() + : task_runner_(new TestMockTimeTaskRunner), + previous_task_runner_(ThreadTaskRunnerHandle::Get()) { + DCHECK(MessageLoopCurrent::Get()); + // To ensure that we process any initialization tasks posted to the + // MessageLoop by a test fixture before replacing its TaskRunner. + RunLoop().RunUntilIdle(); + MessageLoopCurrent::Get()->SetTaskRunner(task_runner_); +} + +ScopedMockTimeMessageLoopTaskRunner::~ScopedMockTimeMessageLoopTaskRunner() { + DCHECK(previous_task_runner_->RunsTasksInCurrentSequence()); + DCHECK_EQ(task_runner_, ThreadTaskRunnerHandle::Get()); + for (auto& pending_task : task_runner_->TakePendingTasks()) { + previous_task_runner_->PostDelayedTask( + pending_task.location, std::move(pending_task.task), + pending_task.GetTimeToRun() - task_runner_->NowTicks()); + } + MessageLoopCurrent::Get()->SetTaskRunner(std::move(previous_task_runner_)); +} + +} // namespace base diff --git a/chromium/base/test/scoped_mock_time_message_loop_task_runner.h b/chromium/base/test/scoped_mock_time_message_loop_task_runner.h new file mode 100644 index 00000000000..b671304b295 --- /dev/null +++ b/chromium/base/test/scoped_mock_time_message_loop_task_runner.h @@ -0,0 +1,45 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_MOCK_TIME_MESSAGE_LOOP_TASK_RUNNER_H_ +#define BASE_TEST_SCOPED_MOCK_TIME_MESSAGE_LOOP_TASK_RUNNER_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/test/test_mock_time_task_runner.h" + +namespace base { + +class SingleThreadTaskRunner; + +// A scoped wrapper around TestMockTimeTaskRunner that replaces +// MessageLoopCurrent::Get()'s task runner (and consequently +// ThreadTaskRunnerHandle) with a TestMockTimeTaskRunner and resets it back at +// the end of its scope. +// +// Note: RunLoop() will not work in the scope of a +// ScopedMockTimeMessageLoopTaskRunner, the underlying TestMockTimeTaskRunner's +// methods must be used instead to pump tasks. +// +// Note: Use TaskEnvironment + TimeSource::MOCK_TIME instead of this in unit +// tests. In browser tests you unfortunately still need this at the moment to +// mock delayed tasks on the main thread... +class ScopedMockTimeMessageLoopTaskRunner { + public: + ScopedMockTimeMessageLoopTaskRunner(); + ~ScopedMockTimeMessageLoopTaskRunner(); + + TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); } + TestMockTimeTaskRunner* operator->() { return task_runner_.get(); } + + private: + const scoped_refptr<TestMockTimeTaskRunner> task_runner_; + scoped_refptr<SingleThreadTaskRunner> previous_task_runner_; + + DISALLOW_COPY_AND_ASSIGN(ScopedMockTimeMessageLoopTaskRunner); +}; + +} // namespace base + +#endif // BASE_TEST_SCOPED_MOCK_TIME_MESSAGE_LOOP_TASK_RUNNER_H_ diff --git a/chromium/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc b/chromium/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc new file mode 100644 index 00000000000..f2f34b802d6 --- /dev/null +++ b/chromium/base/test/scoped_mock_time_message_loop_task_runner_unittest.cc @@ -0,0 +1,125 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_mock_time_message_loop_task_runner.h" + +#include <memory> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback_forward.h" +#include "base/containers/circular_deque.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop_current.h" +#include "base/test/task_environment.h" +#include "base/test/test_mock_time_task_runner.h" +#include "base/test/test_pending_task.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TaskRunner* GetCurrentTaskRunner() { + return ThreadTaskRunnerHandle::Get().get(); +} + +void AssignTrue(bool* out) { + *out = true; +} + +// Pops a task from the front of |pending_tasks| and returns it. +TestPendingTask PopFront(base::circular_deque<TestPendingTask>* pending_tasks) { + TestPendingTask task = std::move(pending_tasks->front()); + pending_tasks->pop_front(); + return task; +} + +class ScopedMockTimeMessageLoopTaskRunnerTest : public testing::Test { + public: + ScopedMockTimeMessageLoopTaskRunnerTest() + : original_task_runner_(new TestMockTimeTaskRunner()) { + MessageLoopCurrent::Get()->SetTaskRunner(original_task_runner_); + } + + protected: + TestMockTimeTaskRunner* original_task_runner() { + return original_task_runner_.get(); + } + + private: + scoped_refptr<TestMockTimeTaskRunner> original_task_runner_; + + test::SingleThreadTaskEnvironment task_environment_; + + DISALLOW_COPY_AND_ASSIGN(ScopedMockTimeMessageLoopTaskRunnerTest); +}; + +// Verifies a new TaskRunner is installed while a +// ScopedMockTimeMessageLoopTaskRunner exists and the previous one is installed +// after destruction. +TEST_F(ScopedMockTimeMessageLoopTaskRunnerTest, CurrentTaskRunners) { + auto scoped_task_runner_ = + std::make_unique<ScopedMockTimeMessageLoopTaskRunner>(); + EXPECT_EQ(scoped_task_runner_->task_runner(), GetCurrentTaskRunner()); + scoped_task_runner_.reset(); + EXPECT_EQ(original_task_runner(), GetCurrentTaskRunner()); +} + +TEST_F(ScopedMockTimeMessageLoopTaskRunnerTest, + IncompleteTasksAreCopiedToPreviousTaskRunnerAfterDestruction) { + auto scoped_task_runner_ = + std::make_unique<ScopedMockTimeMessageLoopTaskRunner>(); + + bool task_10_has_run = false; + bool task_11_has_run = false; + + OnceClosure task_1 = DoNothing(); + OnceClosure task_2 = DoNothing(); + OnceClosure task_10 = BindOnce(&AssignTrue, &task_10_has_run); + OnceClosure task_11 = BindOnce(&AssignTrue, &task_11_has_run); + + constexpr TimeDelta task_1_delay = TimeDelta::FromSeconds(1); + constexpr TimeDelta task_2_delay = TimeDelta::FromSeconds(2); + constexpr TimeDelta task_10_delay = TimeDelta::FromSeconds(10); + constexpr TimeDelta task_11_delay = TimeDelta::FromSeconds(11); + + constexpr TimeDelta step_time_by = TimeDelta::FromSeconds(5); + + GetCurrentTaskRunner()->PostDelayedTask(FROM_HERE, std::move(task_1), + task_1_delay); + GetCurrentTaskRunner()->PostDelayedTask(FROM_HERE, std::move(task_2), + task_2_delay); + GetCurrentTaskRunner()->PostDelayedTask(FROM_HERE, std::move(task_10), + task_10_delay); + GetCurrentTaskRunner()->PostDelayedTask(FROM_HERE, std::move(task_11), + task_11_delay); + + scoped_task_runner_->task_runner()->FastForwardBy(step_time_by); + + scoped_task_runner_.reset(); + + base::circular_deque<TestPendingTask> pending_tasks = + original_task_runner()->TakePendingTasks(); + + EXPECT_EQ(2U, pending_tasks.size()); + + TestPendingTask pending_task = PopFront(&pending_tasks); + EXPECT_FALSE(task_10_has_run); + std::move(pending_task.task).Run(); + EXPECT_TRUE(task_10_has_run); + EXPECT_EQ(task_10_delay - step_time_by, pending_task.delay); + + pending_task = PopFront(&pending_tasks); + EXPECT_FALSE(task_11_has_run); + std::move(pending_task.task).Run(); + EXPECT_TRUE(task_11_has_run); + EXPECT_EQ(task_11_delay - step_time_by, pending_task.delay); +} + +} // namespace +} // namespace base diff --git a/chromium/base/test/scoped_os_info_override_win.cc b/chromium/base/test/scoped_os_info_override_win.cc new file mode 100644 index 00000000000..7415c13234c --- /dev/null +++ b/chromium/base/test/scoped_os_info_override_win.cc @@ -0,0 +1,126 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_os_info_override_win.h" + +#include <windows.h> + +#include "base/win/windows_version.h" + +namespace base { +namespace test { + +ScopedOSInfoOverride::ScopedOSInfoOverride(Type type) + : original_info_(base::win::OSInfo::GetInstance()), + overriding_info_(CreateInfoOfType(type)) { + *base::win::OSInfo::GetInstanceStorage() = overriding_info_.get(); +} + +ScopedOSInfoOverride::~ScopedOSInfoOverride() { + *base::win::OSInfo::GetInstanceStorage() = original_info_; +} + +// static +ScopedOSInfoOverride::UniqueOsInfo ScopedOSInfoOverride::CreateInfoOfType( + Type type) { + _OSVERSIONINFOEXW version_info = {sizeof(version_info)}; + _SYSTEM_INFO system_info = {}; + int os_type = 0; + + switch (type) { + case Type::kWin10Pro: + case Type::kWin10Home: + version_info.dwMajorVersion = 10; + version_info.dwMinorVersion = 0; + version_info.dwBuildNumber = 15063; + version_info.wServicePackMajor = 0; + version_info.wServicePackMinor = 0; + version_info.szCSDVersion[0] = 0; + version_info.wProductType = VER_NT_WORKSTATION; + version_info.wSuiteMask = VER_SUITE_PERSONAL; + + system_info.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64; + system_info.dwNumberOfProcessors = 1; + system_info.dwAllocationGranularity = 8; + + os_type = + type == Type::kWin10Home ? PRODUCT_HOME_BASIC : PRODUCT_PROFESSIONAL; + break; + case Type::kWinServer2016: + version_info.dwMajorVersion = 10; + version_info.dwMinorVersion = 0; + version_info.dwBuildNumber = 17134; + version_info.wServicePackMajor = 0; + version_info.wServicePackMinor = 0; + version_info.szCSDVersion[0] = 0; + version_info.wProductType = VER_NT_SERVER; + version_info.wSuiteMask = VER_SUITE_ENTERPRISE; + + system_info.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64; + system_info.dwNumberOfProcessors = 4; + system_info.dwAllocationGranularity = 64 * 1024; + + os_type = PRODUCT_STANDARD_SERVER; + break; + case Type::kWin81Pro: + version_info.dwMajorVersion = 6; + version_info.dwMinorVersion = 3; + version_info.dwBuildNumber = 9600; + version_info.wServicePackMajor = 0; + version_info.wServicePackMinor = 0; + version_info.szCSDVersion[0] = 0; + version_info.wProductType = VER_NT_WORKSTATION; + version_info.wSuiteMask = VER_SUITE_PERSONAL; + + system_info.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64; + system_info.dwNumberOfProcessors = 1; + system_info.dwAllocationGranularity = 64 * 1024; + + os_type = PRODUCT_PROFESSIONAL; + break; + case Type::kWinServer2012R2: + version_info.dwMajorVersion = 6; + version_info.dwMinorVersion = 3; + version_info.dwBuildNumber = 9600; + version_info.wServicePackMajor = 0; + version_info.wServicePackMinor = 0; + version_info.szCSDVersion[0] = 0; + version_info.wProductType = VER_NT_SERVER; + version_info.wSuiteMask = VER_SUITE_ENTERPRISE; + + system_info.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64; + system_info.dwNumberOfProcessors = 2; + system_info.dwAllocationGranularity = 64 * 1024; + + os_type = PRODUCT_STANDARD_SERVER; + break; + case Type::kWin7ProSP1: + version_info.dwMajorVersion = 6; + version_info.dwMinorVersion = 1; + version_info.dwBuildNumber = 7601; + version_info.wServicePackMajor = 1; + version_info.wServicePackMinor = 0; + wcscpy_s(version_info.szCSDVersion, L"Service Pack 1"); + version_info.wProductType = VER_NT_WORKSTATION; + version_info.wSuiteMask = VER_SUITE_PERSONAL; + + system_info.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64; + system_info.dwNumberOfProcessors = 1; + system_info.dwAllocationGranularity = 64 * 1024; + + os_type = PRODUCT_PROFESSIONAL; + break; + } + + return UniqueOsInfo(new base::win::OSInfo(version_info, system_info, os_type), + &ScopedOSInfoOverride::deleter); +} + +// static +void ScopedOSInfoOverride::deleter(base::win::OSInfo* info) { + delete info; +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/scoped_os_info_override_win.h b/chromium/base/test/scoped_os_info_override_win.h new file mode 100644 index 00000000000..07ae7a964f1 --- /dev/null +++ b/chromium/base/test/scoped_os_info_override_win.h @@ -0,0 +1,64 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_OS_INFO_OVERRIDE_WIN_H_ +#define BASE_TEST_SCOPED_OS_INFO_OVERRIDE_WIN_H_ + +#include <memory> + +#include "base/macros.h" + +namespace base { +namespace win { +class OSInfo; +} // namespace win +} // namespace base + +namespace base { +namespace test { + +// Helper class to override info returned by base::win::OSInfo::GetIntance() +// for the lifetime of this object. Upon destruction, the original info at time +// of object creation is restored. +class ScopedOSInfoOverride { + public: + // Types of windows machines that can be used for overriding. Add new + // machine types as needed. + enum class Type { + kWin10Pro, + kWin10Home, + kWinServer2016, + kWin81Pro, + kWinServer2012R2, + kWin7ProSP1, + }; + + explicit ScopedOSInfoOverride(Type type); + ~ScopedOSInfoOverride(); + + private: + using UniqueOsInfo = + std::unique_ptr<base::win::OSInfo, void (*)(base::win::OSInfo*)>; + + static UniqueOsInfo CreateInfoOfType(Type type); + + // The OSInfo taken by this instance at construction and restored at + // destruction. + base::win::OSInfo* original_info_; + + // The OSInfo owned by this scoped object and which overrides + // base::win::OSInfo::GetIntance() for the lifespan of the object. + UniqueOsInfo overriding_info_; + + // Because the dtor of OSInfo is private, a custom deleter is needed to use + // unique_ptr. + static void deleter(base::win::OSInfo* info); + + DISALLOW_COPY_AND_ASSIGN(ScopedOSInfoOverride); +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_SCOPED_OS_INFO_OVERRIDE_WIN_H_ diff --git a/chromium/base/test/scoped_path_override.cc b/chromium/base/test/scoped_path_override.cc new file mode 100644 index 00000000000..dc4a34089ce --- /dev/null +++ b/chromium/base/test/scoped_path_override.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_path_override.h" + +#include "base/check.h" +#include "base/path_service.h" + +namespace base { + +ScopedPathOverride::ScopedPathOverride(int key) : key_(key) { + bool result = temp_dir_.CreateUniqueTempDir(); + CHECK(result); + result = PathService::Override(key, temp_dir_.GetPath()); + CHECK(result); +} + +ScopedPathOverride::ScopedPathOverride(int key, const base::FilePath& dir) + : key_(key) { + bool result = PathService::Override(key, dir); + CHECK(result); +} + +ScopedPathOverride::ScopedPathOverride(int key, + const FilePath& path, + bool is_absolute, + bool create) + : key_(key) { + bool result = + PathService::OverrideAndCreateIfNeeded(key, path, is_absolute, create); + CHECK(result); +} + +ScopedPathOverride::~ScopedPathOverride() { + bool result = PathService::RemoveOverride(key_); + CHECK(result) << "The override seems to have been removed already!"; +} + +} // namespace base diff --git a/chromium/base/test/scoped_path_override.h b/chromium/base/test/scoped_path_override.h new file mode 100644 index 00000000000..f5891490b1c --- /dev/null +++ b/chromium/base/test/scoped_path_override.h @@ -0,0 +1,43 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_PATH_OVERRIDE_H_ +#define BASE_TEST_SCOPED_PATH_OVERRIDE_H_ + +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" + +namespace base { + +class FilePath; + +// Sets a path override on construction, and removes it when the object goes out +// of scope. This class is intended to be used by tests that need to override +// paths to ensure their overrides are properly handled and reverted when the +// scope of the test is left. +class ScopedPathOverride { + public: + // Contructor that initializes the override to a scoped temp directory. + explicit ScopedPathOverride(int key); + + // Constructor that would use a path provided by the user. + ScopedPathOverride(int key, const FilePath& dir); + + // See PathService::OverrideAndCreateIfNeeded. + ScopedPathOverride(int key, + const FilePath& path, + bool is_absolute, + bool create); + ~ScopedPathOverride(); + + private: + int key_; + ScopedTempDir temp_dir_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPathOverride); +}; + +} // namespace base + +#endif // BASE_TEST_SCOPED_PATH_OVERRIDE_H_ diff --git a/chromium/base/test/scoped_run_loop_timeout.cc b/chromium/base/test/scoped_run_loop_timeout.cc new file mode 100644 index 00000000000..5158c5c4c84 --- /dev/null +++ b/chromium/base/test/scoped_run_loop_timeout.cc @@ -0,0 +1,94 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_run_loop_timeout.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/strings/strcat.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace test { + +namespace { + +bool g_add_gtest_failure_on_timeout = false; + +std::string TimeoutMessage(const RepeatingCallback<std::string()>& get_log) { + std::string message = "RunLoop::Run() timed out."; + if (get_log) + StrAppend(&message, {"\n", get_log.Run()}); + return message; +} + +} // namespace + +ScopedRunLoopTimeout::ScopedRunLoopTimeout(const Location& from_here, + TimeDelta timeout) + : ScopedRunLoopTimeout(from_here, timeout, NullCallback()) {} + +ScopedRunLoopTimeout::~ScopedRunLoopTimeout() { + RunLoop::SetTimeoutForCurrentThread(nested_timeout_); +} + +ScopedRunLoopTimeout::ScopedRunLoopTimeout( + const Location& from_here, + TimeDelta timeout, + RepeatingCallback<std::string()> on_timeout_log) + : nested_timeout_(RunLoop::GetTimeoutForCurrentThread()) { + DCHECK_GT(timeout, TimeDelta()); + run_timeout_.timeout = timeout; + + if (g_add_gtest_failure_on_timeout) { + run_timeout_.on_timeout = BindRepeating( + [](const Location& from_here, + RepeatingCallback<std::string()> on_timeout_log) { + GTEST_FAIL_AT(from_here.file_name(), from_here.line_number()) + << TimeoutMessage(on_timeout_log); + }, + from_here, std::move(on_timeout_log)); + } else { + run_timeout_.on_timeout = BindRepeating( + [](const Location& from_here, + RepeatingCallback<std::string()> on_timeout_log) { + std::string message = TimeoutMessage(on_timeout_log); + logging::LogMessage(from_here.file_name(), from_here.line_number(), + message.data()); + }, + from_here, std::move(on_timeout_log)); + } + + RunLoop::SetTimeoutForCurrentThread(&run_timeout_); +} + +// static +bool ScopedRunLoopTimeout::ExistsForCurrentThread() { + return RunLoop::GetTimeoutForCurrentThread() != nullptr; +} + +// static +void ScopedRunLoopTimeout::SetAddGTestFailureOnTimeout() { + g_add_gtest_failure_on_timeout = true; +} + +// static +const RunLoop::RunLoopTimeout* +ScopedRunLoopTimeout::GetTimeoutForCurrentThread() { + return RunLoop::GetTimeoutForCurrentThread(); +} + +ScopedDisableRunLoopTimeout::ScopedDisableRunLoopTimeout() + : nested_timeout_(RunLoop::GetTimeoutForCurrentThread()) { + RunLoop::SetTimeoutForCurrentThread(nullptr); +} + +ScopedDisableRunLoopTimeout::~ScopedDisableRunLoopTimeout() { + RunLoop::SetTimeoutForCurrentThread(nested_timeout_); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/scoped_run_loop_timeout.h b/chromium/base/test/scoped_run_loop_timeout.h new file mode 100644 index 00000000000..12abd9f4e55 --- /dev/null +++ b/chromium/base/test/scoped_run_loop_timeout.h @@ -0,0 +1,107 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SCOPED_RUN_LOOP_TIMEOUT_H_ +#define BASE_TEST_SCOPED_RUN_LOOP_TIMEOUT_H_ + +#include <string> + +#include "base/callback.h" +#include "base/location.h" +#include "base/run_loop.h" +#include "base/time/time.h" + +namespace content { +FORWARD_DECLARE_TEST(ContentBrowserTest, RunTimeoutInstalled); +} + +namespace base { +namespace test { + +FORWARD_DECLARE_TEST(TaskEnvironmentTest, SetsDefaultRunTimeout); + +// Configures all RunLoop::Run() calls on the current thread to run the +// supplied |on_timeout| callback if they run for longer than |timeout|. +// +// Specifying Run() timeouts per-thread avoids the need to cope with Run()s +// executing concurrently with ScopedRunLoopTimeout initialization or +// teardown, and allows "default" timeouts to be specified by suites, rather +// than explicitly configuring them for every RunLoop, in each test. +// +// This is used by test classes including TaskEnvironment and TestSuite to +// set a default Run() timeout on the main thread of all tests which use them. +// +// Tests which have steps which need to Run() for longer than their suite's +// default (if any) allows can override the active timeout by creating a nested +// ScopedRunLoopTimeout on their stack, e.g: +// +// ScopedRunLoopTimeout default_timeout(kDefaultRunTimeout); +// ... do other test stuff ... +// RunLoop().Run(); // Run for up to kDefaultRunTimeout. +// ... +// { +// ScopedRunLoopTimeout specific_timeout(kTestSpecificTimeout); +// RunLoop().Run(); // Run for up to kTestSpecificTimeout. +// } +// ... +// RunLoop().Run(); // Run for up to kDefaultRunTimeout. +// +// The currently-active timeout can also be temporarily disabled: +// ScopedDisableRunLoopTimeout disable_timeout; +// +// By default LOG(FATAL) will be invoked on Run() timeout. Test binaries +// can opt-in to using ADD_FAILURE() instead by calling +// SetAddGTestFailureOnTimeout() during process initialization. +// +// TaskEnvironment applies a default Run() timeout. + +class ScopedRunLoopTimeout { + public: + ScopedRunLoopTimeout(const Location& from_here, TimeDelta timeout); + ~ScopedRunLoopTimeout(); + + // Invokes |on_timeout_log| if |timeout| expires, and appends it to the + // logged error message. + ScopedRunLoopTimeout(const Location& from_here, + TimeDelta timeout, + RepeatingCallback<std::string()> on_timeout_log); + + ScopedRunLoopTimeout(const ScopedRunLoopTimeout&) = delete; + ScopedRunLoopTimeout& operator=(const ScopedRunLoopTimeout&) = delete; + + // Returns true if there is a Run() timeout configured on the current thread. + static bool ExistsForCurrentThread(); + + static void SetAddGTestFailureOnTimeout(); + + protected: + FRIEND_TEST_ALL_PREFIXES(ScopedRunLoopRunTimeoutTest, TimesOut); + FRIEND_TEST_ALL_PREFIXES(ScopedRunLoopRunTimeoutTest, RunTasksUntilTimeout); + FRIEND_TEST_ALL_PREFIXES(TaskEnvironmentTest, SetsDefaultRunTimeout); + FRIEND_TEST_ALL_PREFIXES(content::ContentBrowserTest, RunTimeoutInstalled); + + // Exposes the RunLoopTimeout to the friend tests (see above). + static const RunLoop::RunLoopTimeout* GetTimeoutForCurrentThread(); + + const RunLoop::RunLoopTimeout* const nested_timeout_; + RunLoop::RunLoopTimeout run_timeout_; +}; + +class ScopedDisableRunLoopTimeout { + public: + ScopedDisableRunLoopTimeout(); + ~ScopedDisableRunLoopTimeout(); + + ScopedDisableRunLoopTimeout(const ScopedDisableRunLoopTimeout&) = delete; + ScopedDisableRunLoopTimeout& operator=(const ScopedDisableRunLoopTimeout&) = + delete; + + private: + const RunLoop::RunLoopTimeout* const nested_timeout_; +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_SCOPED_RUN_LOOP_TIMEOUT_H_ diff --git a/chromium/base/test/scoped_run_loop_timeout_unittest.cc b/chromium/base/test/scoped_run_loop_timeout_unittest.cc new file mode 100644 index 00000000000..c60bf71569e --- /dev/null +++ b/chromium/base/test/scoped_run_loop_timeout_unittest.cc @@ -0,0 +1,79 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_run_loop_timeout.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/test/bind_test_util.h" +#include "base/test/gtest_util.h" +#include "base/test/task_environment.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest-spi.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace test { + +TEST(ScopedRunLoopTimeoutTest, TimesOut) { + TaskEnvironment task_environment; + RunLoop run_loop; + + static constexpr auto kArbitraryTimeout = TimeDelta::FromMilliseconds(10); + ScopedRunLoopTimeout run_timeout(FROM_HERE, kArbitraryTimeout); + + // Since the delayed task will be posted only after the message pump starts + // running, the ScopedRunLoopTimeout will already have started to elapse, + // so if Run() exits at the correct time then our delayed task will not run. + SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + BindOnce(IgnoreResult(&SequencedTaskRunner::PostDelayedTask), + SequencedTaskRunnerHandle::Get(), FROM_HERE, + MakeExpectedNotRunClosure(FROM_HERE), kArbitraryTimeout)); + + // This task should get to run before Run() times-out. + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, MakeExpectedRunClosure(FROM_HERE), kArbitraryTimeout); + + // EXPECT_FATAL_FAILURE() can only reference globals and statics. + static RunLoop& static_loop = run_loop; + EXPECT_FATAL_FAILURE(static_loop.Run(), "Run() timed out."); +} + +TEST(ScopedRunLoopTimeoutTest, RunTasksUntilTimeout) { + TaskEnvironment task_environment; + RunLoop run_loop; + + static constexpr auto kArbitraryTimeout = TimeDelta::FromMilliseconds(10); + ScopedRunLoopTimeout run_timeout(FROM_HERE, kArbitraryTimeout); + + // Posting a task with the same delay as our timeout, immediately before + // calling Run(), means it should get to run. Since this uses QuitWhenIdle(), + // the Run() timeout callback should also get to run. + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, MakeExpectedRunClosure(FROM_HERE), kArbitraryTimeout); + + // EXPECT_FATAL_FAILURE() can only reference globals and statics. + static RunLoop& static_loop = run_loop; + EXPECT_FATAL_FAILURE(static_loop.Run(), "Run() timed out."); +} + +TEST(ScopedRunLoopTimeoutTest, OnTimeoutLog) { + TaskEnvironment task_environment; + RunLoop run_loop; + + static constexpr auto kArbitraryTimeout = TimeDelta::FromMilliseconds(10); + ScopedRunLoopTimeout run_timeout( + FROM_HERE, kArbitraryTimeout, + BindRepeating([]() -> std::string { return "I like kittens!"; })); + + // EXPECT_FATAL_FAILURE() can only reference globals and statics. + static RunLoop& static_loop = run_loop; + EXPECT_FATAL_FAILURE(static_loop.Run(), "Run() timed out.\nI like kittens!"); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/sequenced_task_runner_test_template.cc b/chromium/base/test/sequenced_task_runner_test_template.cc new file mode 100644 index 00000000000..bccc301ab7d --- /dev/null +++ b/chromium/base/test/sequenced_task_runner_test_template.cc @@ -0,0 +1,270 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/sequenced_task_runner_test_template.h" + +#include <ostream> + +#include "base/location.h" + +namespace base { + +namespace internal { + +TaskEvent::TaskEvent(int i, Type type) + : i(i), type(type) { +} + +SequencedTaskTracker::SequencedTaskTracker() + : next_post_i_(0), + task_end_count_(0), + task_end_cv_(&lock_) { +} + +void SequencedTaskTracker::PostWrappedNonNestableTask( + SequencedTaskRunner* task_runner, + OnceClosure task) { + AutoLock event_lock(lock_); + const int post_i = next_post_i_++; + auto wrapped_task = + BindOnce(&SequencedTaskTracker::RunTask, this, std::move(task), post_i); + task_runner->PostNonNestableTask(FROM_HERE, std::move(wrapped_task)); + TaskPosted(post_i); +} + +void SequencedTaskTracker::PostWrappedNestableTask( + SequencedTaskRunner* task_runner, + OnceClosure task) { + AutoLock event_lock(lock_); + const int post_i = next_post_i_++; + auto wrapped_task = + BindOnce(&SequencedTaskTracker::RunTask, this, std::move(task), post_i); + task_runner->PostTask(FROM_HERE, std::move(wrapped_task)); + TaskPosted(post_i); +} + +void SequencedTaskTracker::PostWrappedDelayedNonNestableTask( + SequencedTaskRunner* task_runner, + OnceClosure task, + TimeDelta delay) { + AutoLock event_lock(lock_); + const int post_i = next_post_i_++; + auto wrapped_task = + BindOnce(&SequencedTaskTracker::RunTask, this, std::move(task), post_i); + task_runner->PostNonNestableDelayedTask(FROM_HERE, std::move(wrapped_task), + delay); + TaskPosted(post_i); +} + +void SequencedTaskTracker::PostNonNestableTasks( + SequencedTaskRunner* task_runner, + int task_count) { + for (int i = 0; i < task_count; ++i) { + PostWrappedNonNestableTask(task_runner, OnceClosure()); + } +} + +void SequencedTaskTracker::RunTask(OnceClosure task, int task_i) { + TaskStarted(task_i); + if (!task.is_null()) + std::move(task).Run(); + TaskEnded(task_i); +} + +void SequencedTaskTracker::TaskPosted(int i) { + // Caller must own |lock_|. + events_.push_back(TaskEvent(i, TaskEvent::POST)); +} + +void SequencedTaskTracker::TaskStarted(int i) { + AutoLock lock(lock_); + events_.push_back(TaskEvent(i, TaskEvent::START)); +} + +void SequencedTaskTracker::TaskEnded(int i) { + AutoLock lock(lock_); + events_.push_back(TaskEvent(i, TaskEvent::END)); + ++task_end_count_; + task_end_cv_.Signal(); +} + +const std::vector<TaskEvent>& +SequencedTaskTracker::GetTaskEvents() const { + return events_; +} + +void SequencedTaskTracker::WaitForCompletedTasks(int count) { + AutoLock lock(lock_); + while (task_end_count_ < count) + task_end_cv_.Wait(); +} + +SequencedTaskTracker::~SequencedTaskTracker() = default; + +void PrintTo(const TaskEvent& event, std::ostream* os) { + *os << "(i=" << event.i << ", type="; + switch (event.type) { + case TaskEvent::POST: *os << "POST"; break; + case TaskEvent::START: *os << "START"; break; + case TaskEvent::END: *os << "END"; break; + } + *os << ")"; +} + +namespace { + +// Returns the task ordinals for the task event type |type| in the order that +// they were recorded. +std::vector<int> GetEventTypeOrder(const std::vector<TaskEvent>& events, + TaskEvent::Type type) { + std::vector<int> tasks; + std::vector<TaskEvent>::const_iterator event; + for (event = events.begin(); event != events.end(); ++event) { + if (event->type == type) + tasks.push_back(event->i); + } + return tasks; +} + +// Returns all task events for task |task_i|. +std::vector<TaskEvent::Type> GetEventsForTask( + const std::vector<TaskEvent>& events, + int task_i) { + std::vector<TaskEvent::Type> task_event_orders; + std::vector<TaskEvent>::const_iterator event; + for (event = events.begin(); event != events.end(); ++event) { + if (event->i == task_i) + task_event_orders.push_back(event->type); + } + return task_event_orders; +} + +// Checks that the task events for each task in |events| occur in the order +// {POST, START, END}, and that there is only one instance of each event type +// per task. +::testing::AssertionResult CheckEventOrdersForEachTask( + const std::vector<TaskEvent>& events, + int task_count) { + std::vector<TaskEvent::Type> expected_order; + expected_order.push_back(TaskEvent::POST); + expected_order.push_back(TaskEvent::START); + expected_order.push_back(TaskEvent::END); + + // This is O(n^2), but it runs fast enough currently so is not worth + // optimizing. + for (int i = 0; i < task_count; ++i) { + const std::vector<TaskEvent::Type> task_events = + GetEventsForTask(events, i); + if (task_events != expected_order) { + return ::testing::AssertionFailure() + << "Events for task " << i << " are out of order; expected: " + << ::testing::PrintToString(expected_order) << "; actual: " + << ::testing::PrintToString(task_events); + } + } + return ::testing::AssertionSuccess(); +} + +// Checks that no two tasks were running at the same time. I.e. the only +// events allowed between the START and END of a task are the POSTs of other +// tasks. +::testing::AssertionResult CheckNoTaskRunsOverlap( + const std::vector<TaskEvent>& events) { + // If > -1, we're currently inside a START, END pair. + int current_task_i = -1; + + std::vector<TaskEvent>::const_iterator event; + for (event = events.begin(); event != events.end(); ++event) { + bool spurious_event_found = false; + + if (current_task_i == -1) { // Not inside a START, END pair. + switch (event->type) { + case TaskEvent::POST: + break; + case TaskEvent::START: + current_task_i = event->i; + break; + case TaskEvent::END: + spurious_event_found = true; + break; + } + + } else { // Inside a START, END pair. + bool interleaved_task_detected = false; + + switch (event->type) { + case TaskEvent::POST: + if (event->i == current_task_i) + spurious_event_found = true; + break; + case TaskEvent::START: + interleaved_task_detected = true; + break; + case TaskEvent::END: + if (event->i != current_task_i) + interleaved_task_detected = true; + else + current_task_i = -1; + break; + } + + if (interleaved_task_detected) { + return ::testing::AssertionFailure() + << "Found event " << ::testing::PrintToString(*event) + << " between START and END events for task " << current_task_i + << "; event dump: " << ::testing::PrintToString(events); + } + } + + if (spurious_event_found) { + const int event_i = event - events.begin(); + return ::testing::AssertionFailure() + << "Spurious event " << ::testing::PrintToString(*event) + << " at position " << event_i << "; event dump: " + << ::testing::PrintToString(events); + } + } + + return ::testing::AssertionSuccess(); +} + +} // namespace + +::testing::AssertionResult CheckNonNestableInvariants( + const std::vector<TaskEvent>& events, + int task_count) { + const std::vector<int> post_order = + GetEventTypeOrder(events, TaskEvent::POST); + const std::vector<int> start_order = + GetEventTypeOrder(events, TaskEvent::START); + const std::vector<int> end_order = + GetEventTypeOrder(events, TaskEvent::END); + + if (start_order != post_order) { + return ::testing::AssertionFailure() + << "Expected START order (which equals actual POST order): \n" + << ::testing::PrintToString(post_order) + << "\n Actual START order:\n" + << ::testing::PrintToString(start_order); + } + + if (end_order != post_order) { + return ::testing::AssertionFailure() + << "Expected END order (which equals actual POST order): \n" + << ::testing::PrintToString(post_order) + << "\n Actual END order:\n" + << ::testing::PrintToString(end_order); + } + + const ::testing::AssertionResult result = + CheckEventOrdersForEachTask(events, task_count); + if (!result) + return result; + + return CheckNoTaskRunsOverlap(events); +} + +} // namespace internal + +} // namespace base diff --git a/chromium/base/test/sequenced_task_runner_test_template.h b/chromium/base/test/sequenced_task_runner_test_template.h new file mode 100644 index 00000000000..541ccae6727 --- /dev/null +++ b/chromium/base/test/sequenced_task_runner_test_template.h @@ -0,0 +1,350 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// SequencedTaskRunnerTest defines tests that implementations of +// SequencedTaskRunner should pass in order to be conformant. +// See task_runner_test_template.h for a description of how to use the +// constructs in this file; these work the same. + +#ifndef BASE_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ +#define BASE_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ + +#include <cstddef> +#include <iosfwd> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace internal { + +struct TaskEvent { + enum Type { POST, START, END }; + TaskEvent(int i, Type type); + int i; + Type type; +}; + +// Utility class used in the tests below. +class SequencedTaskTracker : public RefCountedThreadSafe<SequencedTaskTracker> { + public: + SequencedTaskTracker(); + + // Posts the non-nestable task |task|, and records its post event. + void PostWrappedNonNestableTask(SequencedTaskRunner* task_runner, + OnceClosure task); + + // Posts the nestable task |task|, and records its post event. + void PostWrappedNestableTask(SequencedTaskRunner* task_runner, + OnceClosure task); + + // Posts the delayed non-nestable task |task|, and records its post event. + void PostWrappedDelayedNonNestableTask(SequencedTaskRunner* task_runner, + OnceClosure task, + TimeDelta delay); + + // Posts |task_count| non-nestable tasks. + void PostNonNestableTasks(SequencedTaskRunner* task_runner, int task_count); + + const std::vector<TaskEvent>& GetTaskEvents() const; + + // Returns after the tracker observes a total of |count| task completions. + void WaitForCompletedTasks(int count); + + private: + friend class RefCountedThreadSafe<SequencedTaskTracker>; + + ~SequencedTaskTracker(); + + // A task which runs |task|, recording the start and end events. + void RunTask(OnceClosure task, int task_i); + + // Records a post event for task |i|. The owner is expected to be holding + // |lock_| (unlike |TaskStarted| and |TaskEnded|). + void TaskPosted(int i); + + // Records a start event for task |i|. + void TaskStarted(int i); + + // Records a end event for task |i|. + void TaskEnded(int i); + + // Protects events_, next_post_i_, task_end_count_ and task_end_cv_. + Lock lock_; + + // The events as they occurred for each task (protected by lock_). + std::vector<TaskEvent> events_; + + // The ordinal to be used for the next task-posting task (protected by + // lock_). + int next_post_i_; + + // The number of task end events we've received. + int task_end_count_; + ConditionVariable task_end_cv_; + + DISALLOW_COPY_AND_ASSIGN(SequencedTaskTracker); +}; + +void PrintTo(const TaskEvent& event, std::ostream* os); + +// Checks the non-nestable task invariants for all tasks in |events|. +// +// The invariants are: +// 1) Events started and ended in the same order that they were posted. +// 2) Events for an individual tasks occur in the order {POST, START, END}, +// and there is only one instance of each event type for a task. +// 3) The only events between a task's START and END events are the POSTs of +// other tasks. I.e. tasks were run sequentially, not interleaved. +::testing::AssertionResult CheckNonNestableInvariants( + const std::vector<TaskEvent>& events, + int task_count); + +} // namespace internal + +template <typename TaskRunnerTestDelegate> +class SequencedTaskRunnerTest : public testing::Test { + protected: + SequencedTaskRunnerTest() + : task_tracker_(new internal::SequencedTaskTracker()) {} + + const scoped_refptr<internal::SequencedTaskTracker> task_tracker_; + TaskRunnerTestDelegate delegate_; +}; + +TYPED_TEST_SUITE_P(SequencedTaskRunnerTest); + +// This test posts N non-nestable tasks in sequence, and expects them to run +// in FIFO order, with no part of any two tasks' execution +// overlapping. I.e. that each task starts only after the previously-posted +// one has finished. +TYPED_TEST_P(SequencedTaskRunnerTest, SequentialNonNestable) { + const int kTaskCount = 1000; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + this->task_tracker_->PostWrappedNonNestableTask( + task_runner.get(), + BindOnce(&PlatformThread::Sleep, TimeDelta::FromSeconds(1))); + for (int i = 1; i < kTaskCount; ++i) { + this->task_tracker_->PostWrappedNonNestableTask(task_runner.get(), + OnceClosure()); + } + + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// This test posts N nestable tasks in sequence. It has the same expectations +// as SequentialNonNestable because even though the tasks are nestable, they +// will not be run nestedly in this case. +TYPED_TEST_P(SequencedTaskRunnerTest, SequentialNestable) { + const int kTaskCount = 1000; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + this->task_tracker_->PostWrappedNestableTask( + task_runner.get(), + BindOnce(&PlatformThread::Sleep, TimeDelta::FromSeconds(1))); + for (int i = 1; i < kTaskCount; ++i) { + this->task_tracker_->PostWrappedNestableTask(task_runner.get(), + OnceClosure()); + } + + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// This test posts non-nestable tasks in order of increasing delay, and checks +// that that the tasks are run in FIFO order and that there is no execution +// overlap whatsoever between any two tasks. +TYPED_TEST_P(SequencedTaskRunnerTest, SequentialDelayedNonNestable) { + const int kTaskCount = 20; + const int kDelayIncrementMs = 50; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + for (int i = 0; i < kTaskCount; ++i) { + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner.get(), OnceClosure(), + TimeDelta::FromMilliseconds(kDelayIncrementMs * i)); + } + + this->task_tracker_->WaitForCompletedTasks(kTaskCount); + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// This test posts a fast, non-nestable task from within each of a number of +// slow, non-nestable tasks and checks that they all run in the sequence they +// were posted in and that there is no execution overlap whatsoever. +TYPED_TEST_P(SequencedTaskRunnerTest, NonNestablePostFromNonNestableTask) { + const int kParentCount = 10; + const int kChildrenPerParent = 10; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + for (int i = 0; i < kParentCount; ++i) { + auto task = BindOnce(&internal::SequencedTaskTracker::PostNonNestableTasks, + this->task_tracker_, RetainedRef(task_runner), + kChildrenPerParent); + this->task_tracker_->PostWrappedNonNestableTask(task_runner.get(), + std::move(task)); + } + + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants( + this->task_tracker_->GetTaskEvents(), + kParentCount * (kChildrenPerParent + 1))); +} + +// This test posts two tasks with the same delay, and checks that the tasks are +// run in the order in which they were posted. +// +// NOTE: This is actually an approximate test since the API only takes a +// "delay" parameter, so we are not exactly simulating two tasks that get +// posted at the exact same time. It would be nice if the API allowed us to +// specify the desired run time. +TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTasksSameDelay) { + const int kTaskCount = 2; + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + this->task_tracker_->PostWrappedDelayedNonNestableTask(task_runner.get(), + OnceClosure(), kDelay); + this->task_tracker_->PostWrappedDelayedNonNestableTask(task_runner.get(), + OnceClosure(), kDelay); + this->task_tracker_->WaitForCompletedTasks(kTaskCount); + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// This test posts a normal task and a delayed task, and checks that the +// delayed task runs after the normal task even if the normal task takes +// a long time to run. +TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskAfterLongTask) { + const int kTaskCount = 2; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + this->task_tracker_->PostWrappedNonNestableTask( + task_runner.get(), + base::BindOnce(&PlatformThread::Sleep, TimeDelta::FromMilliseconds(50))); + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner.get(), OnceClosure(), TimeDelta::FromMilliseconds(10)); + this->task_tracker_->WaitForCompletedTasks(kTaskCount); + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + +// Test that a pile of normal tasks and a delayed task run in the +// time-to-run order. +TYPED_TEST_P(SequencedTaskRunnerTest, DelayedTaskAfterManyLongTasks) { + const int kTaskCount = 11; + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + for (int i = 0; i < kTaskCount - 1; i++) { + this->task_tracker_->PostWrappedNonNestableTask( + task_runner.get(), base::BindOnce(&PlatformThread::Sleep, + TimeDelta::FromMilliseconds(50))); + } + this->task_tracker_->PostWrappedDelayedNonNestableTask( + task_runner.get(), OnceClosure(), TimeDelta::FromMilliseconds(10)); + this->task_tracker_->WaitForCompletedTasks(kTaskCount); + this->delegate_.StopTaskRunner(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); +} + + +// TODO(francoisk777@gmail.com) Add a test, similiar to the above, which runs +// some tasked nestedly (which should be implemented in the test +// delegate). Also add, to the the test delegate, a predicate which checks +// whether the implementation supports nested tasks. +// + +// The SequencedTaskRunnerTest test case verifies behaviour that is expected +// from a sequenced task runner in order to be conformant. +REGISTER_TYPED_TEST_SUITE_P(SequencedTaskRunnerTest, + SequentialNonNestable, + SequentialNestable, + SequentialDelayedNonNestable, + NonNestablePostFromNonNestableTask, + DelayedTasksSameDelay, + DelayedTaskAfterLongTask, + DelayedTaskAfterManyLongTasks); + +template <typename TaskRunnerTestDelegate> +class SequencedTaskRunnerDelayedTest + : public SequencedTaskRunnerTest<TaskRunnerTestDelegate> {}; + +TYPED_TEST_SUITE_P(SequencedTaskRunnerDelayedTest); + +// This test posts a delayed task, and checks that the task is run later than +// the specified time. +TYPED_TEST_P(SequencedTaskRunnerDelayedTest, DelayedTaskBasic) { + const int kTaskCount = 1; + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + this->delegate_.StartTaskRunner(); + const scoped_refptr<SequencedTaskRunner> task_runner = + this->delegate_.GetTaskRunner(); + + Time time_before_run = Time::Now(); + this->task_tracker_->PostWrappedDelayedNonNestableTask(task_runner.get(), + OnceClosure(), kDelay); + this->task_tracker_->WaitForCompletedTasks(kTaskCount); + this->delegate_.StopTaskRunner(); + Time time_after_run = Time::Now(); + + EXPECT_TRUE(CheckNonNestableInvariants(this->task_tracker_->GetTaskEvents(), + kTaskCount)); + EXPECT_LE(kDelay, time_after_run - time_before_run); +} + +// SequencedTaskRunnerDelayedTest tests that the |delay| parameter of +// is used to actually wait for |delay| ms before executing the task. +// This is not mandatory for a SequencedTaskRunner to be compliant. +REGISTER_TYPED_TEST_SUITE_P(SequencedTaskRunnerDelayedTest, DelayedTaskBasic); + +} // namespace base + +#endif // BASE_TEST_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_ diff --git a/chromium/base/test/simple_test_clock.cc b/chromium/base/test/simple_test_clock.cc new file mode 100644 index 00000000000..7486d793581 --- /dev/null +++ b/chromium/base/test/simple_test_clock.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/simple_test_clock.h" + +namespace base { + +SimpleTestClock::SimpleTestClock() = default; + +SimpleTestClock::~SimpleTestClock() = default; + +Time SimpleTestClock::Now() const { + AutoLock lock(lock_); + return now_; +} + +void SimpleTestClock::Advance(TimeDelta delta) { + AutoLock lock(lock_); + now_ += delta; +} + +void SimpleTestClock::SetNow(Time now) { + AutoLock lock(lock_); + now_ = now; +} + +} // namespace base diff --git a/chromium/base/test/simple_test_clock.h b/chromium/base/test/simple_test_clock.h new file mode 100644 index 00000000000..0cbcf082632 --- /dev/null +++ b/chromium/base/test/simple_test_clock.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SIMPLE_TEST_CLOCK_H_ +#define BASE_TEST_SIMPLE_TEST_CLOCK_H_ + +#include "base/compiler_specific.h" +#include "base/synchronization/lock.h" +#include "base/time/clock.h" +#include "base/time/time.h" + +namespace base { + +// SimpleTestClock is a Clock implementation that gives control over +// the returned Time objects. All methods may be called from any +// thread. +class SimpleTestClock : public Clock { + public: + // Starts off with a clock set to Time(). + SimpleTestClock(); + ~SimpleTestClock() override; + + Time Now() const override; + + // Advances the clock by |delta|. + void Advance(TimeDelta delta); + + // Sets the clock to the given time. + void SetNow(Time now); + + private: + // Protects |now_|. + mutable Lock lock_; + + Time now_; +}; + +} // namespace base + +#endif // BASE_TEST_SIMPLE_TEST_CLOCK_H_ diff --git a/chromium/base/test/simple_test_tick_clock.cc b/chromium/base/test/simple_test_tick_clock.cc new file mode 100644 index 00000000000..3efeaceeef5 --- /dev/null +++ b/chromium/base/test/simple_test_tick_clock.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/simple_test_tick_clock.h" + +#include "base/check.h" + +namespace base { + +SimpleTestTickClock::SimpleTestTickClock() = default; + +SimpleTestTickClock::~SimpleTestTickClock() = default; + +TimeTicks SimpleTestTickClock::NowTicks() const { + AutoLock lock(lock_); + return now_ticks_; +} + +void SimpleTestTickClock::Advance(TimeDelta delta) { + AutoLock lock(lock_); + DCHECK(delta >= TimeDelta()); + now_ticks_ += delta; +} + +void SimpleTestTickClock::SetNowTicks(TimeTicks ticks) { + AutoLock lock(lock_); + now_ticks_ = ticks; +} + +} // namespace base diff --git a/chromium/base/test/simple_test_tick_clock.h b/chromium/base/test/simple_test_tick_clock.h new file mode 100644 index 00000000000..923eba4a9af --- /dev/null +++ b/chromium/base/test/simple_test_tick_clock.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_SIMPLE_TEST_TICK_CLOCK_H_ +#define BASE_TEST_SIMPLE_TEST_TICK_CLOCK_H_ + +#include "base/compiler_specific.h" +#include "base/synchronization/lock.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" + +namespace base { + +// SimpleTestTickClock is a TickClock implementation that gives +// control over the returned TimeTicks objects. All methods may be +// called from any thread. +class SimpleTestTickClock : public TickClock { + public: + // Starts off with a clock set to TimeTicks(). + SimpleTestTickClock(); + ~SimpleTestTickClock() override; + + TimeTicks NowTicks() const override; + + // Advances the clock by |delta|, which must not be negative. + void Advance(TimeDelta delta); + + // Sets the clock to the given time. + void SetNowTicks(TimeTicks ticks); + + private: + // Protects |now_ticks_|. + mutable Lock lock_; + + TimeTicks now_ticks_; +}; + +} // namespace base + +#endif // BASE_TEST_SIMPLE_TEST_TICK_CLOCK_H_ diff --git a/chromium/base/test/spin_wait.h b/chromium/base/test/spin_wait.h new file mode 100644 index 00000000000..42b3b3510f1 --- /dev/null +++ b/chromium/base/test/spin_wait.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a macro ONLY for use in testing. +// DO NOT USE IN PRODUCTION CODE. There are much better ways to wait. + +// This code is very helpful in testing multi-threaded code, without depending +// on almost any primitives. This is especially helpful if you are testing +// those primitive multi-threaded constructs. + +// We provide a simple one argument spin wait (for 1 second), and a generic +// spin wait (for longer periods of time). + +#ifndef BASE_TEST_SPIN_WAIT_H_ +#define BASE_TEST_SPIN_WAIT_H_ + +#include "base/threading/platform_thread.h" +#include "base/time/time.h" + +// Provide a macro that will wait no longer than 1 second for an asynchronous +// change is the value of an expression. +// A typical use would be: +// +// SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(0 == f(x)); +// +// The expression will be evaluated repeatedly until it is true, or until +// the time (1 second) expires. +// Since tests generally have a 5 second watch dog timer, this spin loop is +// typically used to get the padding needed on a given test platform to assure +// that the test passes, even if load varies, and external events vary. + +#define SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(expression) \ + SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(base::TimeDelta::FromSeconds(1), \ + (expression)) + +#define SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(delta, expression) \ + do { \ + base::TimeTicks spin_wait_start = base::TimeTicks::Now(); \ + const base::TimeDelta kSpinWaitTimeout = delta; \ + while (!(expression)) { \ + if (kSpinWaitTimeout < base::TimeTicks::Now() - spin_wait_start) { \ + EXPECT_LE((base::TimeTicks::Now() - spin_wait_start).InMilliseconds(), \ + kSpinWaitTimeout.InMilliseconds()) \ + << "Timed out"; \ + break; \ + } \ + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); \ + } \ + } while (0) + +#endif // BASE_TEST_SPIN_WAIT_H_ diff --git a/chromium/base/test/task_environment.cc b/chromium/base/test/task_environment.cc new file mode 100644 index 00000000000..e8aa5d502a5 --- /dev/null +++ b/chromium/base/test/task_environment.cc @@ -0,0 +1,808 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/task_environment.h" + +#include <algorithm> +#include <memory> + +#include "base/bind_helpers.h" +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_pump.h" +#include "base/message_loop/message_pump_type.h" +#include "base/no_destructor.h" +#include "base/run_loop.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/task/post_task.h" +#include "base/task/sequence_manager/sequence_manager_impl.h" +#include "base/task/sequence_manager/time_domain.h" +#include "base/task/simple_task_executor.h" +#include "base/task/thread_pool/thread_pool_impl.h" +#include "base/task/thread_pool/thread_pool_instance.h" +#include "base/test/bind_test_util.h" +#include "base/test/test_mock_time_task_runner.h" +#include "base/test/test_timeouts.h" +#include "base/thread_annotations.h" +#include "base/threading/sequence_local_storage_map.h" +#include "base/threading/thread_local.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/clock.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "base/time/time_override.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_POSIX) || defined(OS_FUCHSIA) +#include "base/files/file_descriptor_watcher_posix.h" +#endif + +namespace base { +namespace test { + +namespace { + +ObserverList<TaskEnvironment::DestructionObserver>& GetDestructionObservers() { + static NoDestructor<ObserverList<TaskEnvironment::DestructionObserver>> + instance; + return *instance; +} + +base::MessagePumpType GetMessagePumpTypeForMainThreadType( + TaskEnvironment::MainThreadType main_thread_type) { + switch (main_thread_type) { + case TaskEnvironment::MainThreadType::DEFAULT: + return MessagePumpType::DEFAULT; + case TaskEnvironment::MainThreadType::UI: + return MessagePumpType::UI; + case TaskEnvironment::MainThreadType::IO: + return MessagePumpType::IO; + } + NOTREACHED(); + return MessagePumpType::DEFAULT; +} + +std::unique_ptr<sequence_manager::SequenceManager> +CreateSequenceManagerForMainThreadType( + TaskEnvironment::MainThreadType main_thread_type) { + auto type = GetMessagePumpTypeForMainThreadType(main_thread_type); + return sequence_manager::CreateSequenceManagerOnCurrentThreadWithPump( + MessagePump::Create(type), + base::sequence_manager::SequenceManager::Settings::Builder() + .SetMessagePumpType(type) + .Build()); +} + +class TickClockBasedClock : public Clock { + public: + explicit TickClockBasedClock(const TickClock* tick_clock) + : tick_clock_(*tick_clock), + start_ticks_(tick_clock_.NowTicks()), + start_time_(Time::UnixEpoch()) {} + + Time Now() const override { + return start_time_ + (tick_clock_.NowTicks() - start_ticks_); + } + + private: + const TickClock& tick_clock_; + const TimeTicks start_ticks_; + const Time start_time_; +}; + +} // namespace + +class TaskEnvironment::TestTaskTracker + : public internal::ThreadPoolImpl::TaskTrackerImpl { + public: + TestTaskTracker(); + + // Allow running tasks. Returns whether tasks were previously allowed to run. + bool AllowRunTasks(); + + // Disallow running tasks. Returns true on success; success requires there to + // be no tasks currently running. Returns false if >0 tasks are currently + // running. Prior to returning false, it will attempt to block until at least + // one task has completed (in an attempt to avoid callers busy-looping + // DisallowRunTasks() calls with the same set of slowly ongoing tasks). This + // block attempt will also have a short timeout (in an attempt to prevent the + // fallout of blocking: if the only task remaining is blocked on the main + // thread, waiting for it to complete results in a deadlock...). + bool DisallowRunTasks(); + + // Returns true if tasks are currently allowed to run. + bool TasksAllowedToRun() const; + + private: + friend class TaskEnvironment; + + // internal::ThreadPoolImpl::TaskTrackerImpl: + void RunTask(internal::Task task, + internal::TaskSource* sequence, + const TaskTraits& traits) override; + + // Synchronizes accesses to members below. + mutable Lock lock_; + + // True if running tasks is allowed. + bool can_run_tasks_ GUARDED_BY(lock_) = true; + + // Signaled when |can_run_tasks_| becomes true. + ConditionVariable can_run_tasks_cv_ GUARDED_BY(lock_); + + // Signaled when a task is completed. + ConditionVariable task_completed_cv_ GUARDED_BY(lock_); + + // Number of tasks that are currently running. + int num_tasks_running_ GUARDED_BY(lock_) = 0; + + DISALLOW_COPY_AND_ASSIGN(TestTaskTracker); +}; + +class TaskEnvironment::MockTimeDomain : public sequence_manager::TimeDomain, + public TickClock { + public: + explicit MockTimeDomain(sequence_manager::SequenceManager* sequence_manager) + : sequence_manager_(sequence_manager) { + DCHECK_EQ(nullptr, current_mock_time_domain_); + current_mock_time_domain_ = this; + } + + ~MockTimeDomain() override { + DCHECK_EQ(this, current_mock_time_domain_); + current_mock_time_domain_ = nullptr; + } + + static MockTimeDomain* current_mock_time_domain_; + + static Time GetTime() { + return Time::UnixEpoch() + (current_mock_time_domain_->Now() - TimeTicks()); + } + + static TimeTicks GetTimeTicks() { return current_mock_time_domain_->Now(); } + + using TimeDomain::NextScheduledRunTime; + + Optional<TimeTicks> NextScheduledRunTime() const { + // The TimeDomain doesn't know about immediate tasks, check if we have any. + if (!sequence_manager_->IsIdleForTesting()) + return Now(); + return TimeDomain::NextScheduledRunTime(); + } + + void AdvanceClock(TimeDelta delta) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + { + AutoLock lock(now_ticks_lock_); + now_ticks_ += delta; + } + if (thread_pool_) + thread_pool_->ProcessRipeDelayedTasksForTesting(); + } + + static std::unique_ptr<TaskEnvironment::MockTimeDomain> CreateAndRegister( + sequence_manager::SequenceManager* sequence_manager) { + auto mock_time_domain = + std::make_unique<TaskEnvironment::MockTimeDomain>(sequence_manager); + sequence_manager->RegisterTimeDomain(mock_time_domain.get()); + return mock_time_domain; + } + + void SetThreadPool(internal::ThreadPoolImpl* thread_pool, + const TestTaskTracker* thread_pool_task_tracker) { + DCHECK(!thread_pool_); + DCHECK(!thread_pool_task_tracker_); + thread_pool_ = thread_pool; + thread_pool_task_tracker_ = thread_pool_task_tracker; + } + + // sequence_manager::TimeDomain: + + sequence_manager::LazyNow CreateLazyNow() const override { + AutoLock lock(now_ticks_lock_); + return sequence_manager::LazyNow(now_ticks_); + } + + TimeTicks Now() const override { + // This can be called from any thread. + AutoLock lock(now_ticks_lock_); + return now_ticks_; + } + + Optional<TimeDelta> DelayTillNextTask( + sequence_manager::LazyNow* lazy_now) override { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Make sure TimeDomain::NextScheduledRunTime has taken canceled tasks into + // account, ReclaimMemory sweeps canceled delayed tasks. + sequence_manager()->ReclaimMemory(); + Optional<TimeTicks> run_time = NextScheduledRunTime(); + // Check if we've run out of tasks. + if (!run_time) + return base::nullopt; + + // Check if we have a task that should be running now. Reading |now_ticks_| + // from the main thread doesn't require the lock. + if (run_time <= TS_UNCHECKED_READ(now_ticks_)) + return base::TimeDelta(); + + // The next task is a future delayed task. Since we're using mock time, we + // don't want an actual OS level delayed wake up scheduled, so pretend we + // have no more work. This will result in appearing idle, TaskEnvironment + // will decide what to do based on that (return to caller or fast-forward + // time). + return base::nullopt; + } + + // This method is called when the underlying message pump has run out of + // non-delayed work. Advances time to the next task unless + // |quit_when_idle_requested| or TaskEnvironment controls mock time. + bool MaybeFastForwardToNextTask(bool quit_when_idle_requested) override { + if (quit_when_idle_requested) + return false; + + return FastForwardToNextTaskOrCap(TimeTicks::Max()) == + NextTaskSource::kMainThread; + } + + const char* GetName() const override { return "MockTimeDomain"; } + + // TickClock implementation: + TimeTicks NowTicks() const override { return Now(); } + + // Used by FastForwardToNextTaskOrCap() to return which task source time was + // advanced to. + enum class NextTaskSource { + // Out of tasks under |fast_forward_cap|. + kNone, + // There's now >=1 immediate task on the main thread. + kMainThread, + // There's now >=1 immediate task in the thread pool. + kThreadPool, + }; + + // Advances time to the first of : next main thread task, next thread pool + // task, or |fast_forward_cap| (if it's not Max()). + NextTaskSource FastForwardToNextTaskOrCap(TimeTicks fast_forward_cap) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // We don't need to call ReclaimMemory here because + // DelayTillNextTask will have dealt with cancelled delayed tasks for us. + Optional<TimeTicks> next_main_thread_task_time = NextScheduledRunTime(); + + // Consider the next thread pool tasks iff they're running. + Optional<TimeTicks> next_thread_pool_task_time; + if (thread_pool_ && thread_pool_task_tracker_->TasksAllowedToRun()) { + next_thread_pool_task_time = + thread_pool_->NextScheduledRunTimeForTesting(); + } + + // Custom comparison logic to consider nullopt the largest rather than + // smallest value. Could consider using TimeTicks::Max() instead of nullopt + // to represent out-of-tasks? + Optional<TimeTicks> next_task_time; + if (!next_main_thread_task_time) { + next_task_time = next_thread_pool_task_time; + } else if (!next_thread_pool_task_time) { + next_task_time = next_main_thread_task_time; + } else { + next_task_time = + std::min(*next_main_thread_task_time, *next_thread_pool_task_time); + } + + if (next_task_time && *next_task_time <= fast_forward_cap) { + { + AutoLock lock(now_ticks_lock_); + // It's possible for |next_task_time| to be in the past in the following + // scenario: + // Start with Now() == 100ms + // Thread A : Post 200ms delayed task T (construct and enqueue) + // Thread B : Construct 20ms delayed task U + // => |delayed_run_time| == 120ms. + // Thread A : FastForwardToNextTaskOrCap() => fast-forwards to T @ + // 300ms (task U is not yet in queue). + // Thread B : Complete enqueue of task U. + // Thread A : FastForwardToNextTaskOrCap() => must stay at 300ms and run + // U, not go back to 120ms. + // Hence we need std::max() to protect again this because construction + // and enqueuing isn't atomic in time (LazyNow support in + // base/task/thread_pool could help). + now_ticks_ = std::max(now_ticks_, *next_task_time); + } + + if (next_task_time == next_thread_pool_task_time) { + // Let the thread pool know that it should post its now ripe delayed + // tasks. + thread_pool_->ProcessRipeDelayedTasksForTesting(); + return NextTaskSource::kThreadPool; + } + return NextTaskSource::kMainThread; + } + + if (!fast_forward_cap.is_max()) { + AutoLock lock(now_ticks_lock_); + // It's possible that Now() is already beyond |fast_forward_cap| when the + // caller nests multiple FastForwardBy() calls. + now_ticks_ = std::max(now_ticks_, fast_forward_cap); + } + + return NextTaskSource::kNone; + } + + private: + SEQUENCE_CHECKER(sequence_checker_); + + sequence_manager::SequenceManager* const sequence_manager_; + + internal::ThreadPoolImpl* thread_pool_ = nullptr; + const TestTaskTracker* thread_pool_task_tracker_ = nullptr; + + // Protects |now_ticks_| + mutable Lock now_ticks_lock_; + + // Only ever written to from the main sequence. Start from real Now() instead + // of zero to give a more realistic view to tests. + TimeTicks now_ticks_ GUARDED_BY(now_ticks_lock_){ + base::subtle::TimeTicksNowIgnoringOverride()}; +}; + +TaskEnvironment::MockTimeDomain* + TaskEnvironment::MockTimeDomain::current_mock_time_domain_ = nullptr; + +TaskEnvironment::TaskEnvironment( + TimeSource time_source, + MainThreadType main_thread_type, + ThreadPoolExecutionMode thread_pool_execution_mode, + ThreadingMode threading_mode, + ThreadPoolCOMEnvironment thread_pool_com_environment, + bool subclass_creates_default_taskrunner, + trait_helpers::NotATraitTag) + : main_thread_type_(main_thread_type), + thread_pool_execution_mode_(thread_pool_execution_mode), + threading_mode_(threading_mode), + thread_pool_com_environment_(thread_pool_com_environment), + subclass_creates_default_taskrunner_(subclass_creates_default_taskrunner), + sequence_manager_( + CreateSequenceManagerForMainThreadType(main_thread_type)), + mock_time_domain_( + time_source != TimeSource::SYSTEM_TIME + ? MockTimeDomain::CreateAndRegister(sequence_manager_.get()) + : nullptr), + time_overrides_(time_source == TimeSource::MOCK_TIME + ? std::make_unique<subtle::ScopedTimeClockOverrides>( + &MockTimeDomain::GetTime, + &MockTimeDomain::GetTimeTicks, + nullptr) + : nullptr), + mock_clock_(mock_time_domain_ ? std::make_unique<TickClockBasedClock>( + mock_time_domain_.get()) + : nullptr), + scoped_lazy_task_runner_list_for_testing_( + std::make_unique<internal::ScopedLazyTaskRunnerListForTesting>()), + // TODO(https://crbug.com/922098): Enable Run() timeouts even for + // instances created with TimeSource::MOCK_TIME. + run_loop_timeout_( + mock_time_domain_ + ? nullptr + : std::make_unique<ScopedRunLoopTimeout>( + FROM_HERE, + TestTimeouts::action_timeout(), + BindRepeating(&sequence_manager::SequenceManager:: + DescribeAllPendingTasks, + Unretained(sequence_manager_.get())))) { + CHECK(!base::ThreadTaskRunnerHandle::IsSet()); + // If |subclass_creates_default_taskrunner| is true then initialization is + // deferred until DeferredInitFromSubclass(). + if (!subclass_creates_default_taskrunner) { + task_queue_ = sequence_manager_->CreateTaskQueue( + sequence_manager::TaskQueue::Spec("task_environment_default") + .SetTimeDomain(mock_time_domain_.get())); + task_runner_ = task_queue_->task_runner(); + sequence_manager_->SetDefaultTaskRunner(task_runner_); + simple_task_executor_ = std::make_unique<SimpleTaskExecutor>(task_runner_); + CHECK(base::ThreadTaskRunnerHandle::IsSet()) + << "ThreadTaskRunnerHandle should've been set now."; + CompleteInitialization(); + } + + if (threading_mode_ != ThreadingMode::MAIN_THREAD_ONLY) + InitializeThreadPool(); + + if (thread_pool_execution_mode_ == ThreadPoolExecutionMode::QUEUED && + task_tracker_) { + CHECK(task_tracker_->DisallowRunTasks()); + } +} + +void TaskEnvironment::InitializeThreadPool() { + CHECK(!ThreadPoolInstance::Get()) + << "Someone has already installed a ThreadPoolInstance. If nothing in " + "your test does so, then a test that ran earlier may have installed " + "one and leaked it. base::TestSuite will trap leaked globals, unless " + "someone has explicitly disabled it with " + "DisableCheckForLeakedGlobals()."; + + ThreadPoolInstance::InitParams init_params(kNumForegroundThreadPoolThreads); + init_params.suggested_reclaim_time = TimeDelta::Max(); +#if defined(OS_WIN) + if (thread_pool_com_environment_ == ThreadPoolCOMEnvironment::COM_MTA) { + init_params.common_thread_pool_environment = + ThreadPoolInstance::InitParams::CommonThreadPoolEnvironment::COM_MTA; + } +#endif + + auto task_tracker = std::make_unique<TestTaskTracker>(); + task_tracker_ = task_tracker.get(); + auto thread_pool = std::make_unique<internal::ThreadPoolImpl>( + std::string(), std::move(task_tracker)); + if (mock_time_domain_) + mock_time_domain_->SetThreadPool(thread_pool.get(), task_tracker_); + ThreadPoolInstance::Set(std::move(thread_pool)); + ThreadPoolInstance::Get()->Start(init_params); +} + +void TaskEnvironment::CompleteInitialization() { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + +#if defined(OS_POSIX) || defined(OS_FUCHSIA) + if (main_thread_type() == MainThreadType::IO) { + file_descriptor_watcher_ = + std::make_unique<FileDescriptorWatcher>(GetMainThreadTaskRunner()); + } +#endif // defined(OS_POSIX) || defined(OS_FUCHSIA) +} + +TaskEnvironment::TaskEnvironment(TaskEnvironment&& other) = default; + +TaskEnvironment::~TaskEnvironment() { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + // If we've been moved then bail out. + if (!owns_instance_) + return; + for (auto& observer : GetDestructionObservers()) + observer.WillDestroyCurrentTaskEnvironment(); + DestroyThreadPool(); + task_queue_ = nullptr; + NotifyDestructionObserversAndReleaseSequenceManager(); +} + +void TaskEnvironment::DestroyThreadPool() { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + if (threading_mode_ == ThreadingMode::MAIN_THREAD_ONLY) + return; + + // Ideally this would RunLoop().RunUntilIdle() here to catch any errors or + // infinite post loop in the remaining work but this isn't possible right now + // because base::~MessageLoop() didn't use to do this and adding it here would + // make the migration away from MessageLoop that much harder. + + // Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be + // skipped, resulting in memory leaks. + task_tracker_->AllowRunTasks(); + ThreadPoolInstance::Get()->FlushForTesting(); + ThreadPoolInstance::Get()->Shutdown(); + ThreadPoolInstance::Get()->JoinForTesting(); + // Destroying ThreadPoolInstance state can result in waiting on worker + // threads. Make sure this is allowed to avoid flaking tests that have + // disallowed waits on their main thread. + ScopedAllowBaseSyncPrimitivesForTesting allow_waits_to_destroy_task_tracker; + ThreadPoolInstance::Set(nullptr); +} + +sequence_manager::TimeDomain* TaskEnvironment::GetTimeDomain() const { + return mock_time_domain_ ? mock_time_domain_.get() + : sequence_manager_->GetRealTimeDomain(); +} + +sequence_manager::SequenceManager* TaskEnvironment::sequence_manager() const { + DCHECK(subclass_creates_default_taskrunner_); + return sequence_manager_.get(); +} + +void TaskEnvironment::DeferredInitFromSubclass( + scoped_refptr<base::SingleThreadTaskRunner> task_runner) { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + task_runner_ = std::move(task_runner); + sequence_manager_->SetDefaultTaskRunner(task_runner_); + CompleteInitialization(); +} + +void TaskEnvironment::NotifyDestructionObserversAndReleaseSequenceManager() { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + // A derived classes may call this method early. + if (!sequence_manager_) + return; + + if (mock_time_domain_) + sequence_manager_->UnregisterTimeDomain(mock_time_domain_.get()); + + sequence_manager_.reset(); +} + +scoped_refptr<base::SingleThreadTaskRunner> +TaskEnvironment::GetMainThreadTaskRunner() { + DCHECK(task_runner_); + return task_runner_; +} + +bool TaskEnvironment::MainThreadIsIdle() const { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + sequence_manager::internal::SequenceManagerImpl* sequence_manager_impl = + static_cast<sequence_manager::internal::SequenceManagerImpl*>( + sequence_manager_.get()); + // ReclaimMemory sweeps canceled delayed tasks. + sequence_manager_impl->ReclaimMemory(); + return sequence_manager_impl->IsIdleForTesting(); +} + +void TaskEnvironment::RunUntilIdle() { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + if (threading_mode_ == ThreadingMode::MAIN_THREAD_ONLY) { + RunLoop(RunLoop::Type::kNestableTasksAllowed).RunUntilIdle(); + return; + } + + // TODO(gab): This can be heavily simplified to essentially: + // bool HasMainThreadTasks() { + // if (message_loop_) + // return !message_loop_->IsIdleForTesting(); + // return mock_time_task_runner_->NextPendingTaskDelay().is_zero(); + // } + // while (task_tracker_->HasIncompleteTasks() || HasMainThreadTasks()) { + // base::RunLoop().RunUntilIdle(); + // // Avoid busy-looping. + // if (task_tracker_->HasIncompleteTasks()) + // PlatformThread::Sleep(TimeDelta::FromMilliSeconds(1)); + // } + // Update: This can likely be done now that MessageLoop::IsIdleForTesting() + // checks all queues. + // + // Other than that it works because once |task_tracker_->HasIncompleteTasks()| + // is false we know for sure that the only thing that can make it true is a + // main thread task (TaskEnvironment owns all the threads). As such we can't + // racily see it as false on the main thread and be wrong as if it the main + // thread sees the atomic count at zero, it's the only one that can make it go + // up. And the only thing that can make it go up on the main thread are main + // thread tasks and therefore we're done if there aren't any left. + // + // This simplification further allows simplification of DisallowRunTasks(). + // + // This can also be simplified even further once TaskTracker becomes directly + // aware of main thread tasks. https://crbug.com/660078. + + const bool could_run_tasks = task_tracker_->AllowRunTasks(); + + for (;;) { + task_tracker_->AllowRunTasks(); + + // First run as many tasks as possible on the main thread in parallel with + // tasks in ThreadPool. This increases likelihood of TSAN catching + // threading errors and eliminates possibility of hangs should a + // ThreadPool task synchronously block on a main thread task + // (ThreadPoolInstance::FlushForTesting() can't be used here for that + // reason). + RunLoop(RunLoop::Type::kNestableTasksAllowed).RunUntilIdle(); + + // Then halt ThreadPool. DisallowRunTasks() failing indicates that there + // were ThreadPool tasks currently running. In that case, try again from + // top when DisallowRunTasks() yields control back to this thread as they + // may have posted main thread tasks. + if (!task_tracker_->DisallowRunTasks()) + continue; + + // Once ThreadPool is halted. Run any remaining main thread tasks (which + // may have been posted by ThreadPool tasks that completed between the + // above main thread RunUntilIdle() and ThreadPool DisallowRunTasks()). + // Note: this assumes that no main thread task synchronously blocks on a + // ThreadPool tasks (it certainly shouldn't); this call could otherwise + // hang. + RunLoop(RunLoop::Type::kNestableTasksAllowed).RunUntilIdle(); + + // The above RunUntilIdle() guarantees there are no remaining main thread + // tasks (the ThreadPool being halted during the last RunUntilIdle() is + // key as it prevents a task being posted to it racily with it determining + // it had no work remaining). Therefore, we're done if there is no more work + // on ThreadPool either (there can be ThreadPool work remaining if + // DisallowRunTasks() preempted work and/or the last RunUntilIdle() posted + // more ThreadPool tasks). + // Note: this last |if| couldn't be turned into a |do {} while();|. A + // conditional loop makes it such that |continue;| results in checking the + // condition (not unconditionally loop again) which would be incorrect for + // the above logic as it'd then be possible for a ThreadPool task to be + // running during the DisallowRunTasks() test, causing it to fail, but then + // post to the main thread and complete before the loop's condition is + // verified which could result in HasIncompleteUndelayedTasksForTesting() + // returning false and the loop erroneously exiting with a pending task on + // the main thread. + if (!task_tracker_->HasIncompleteTaskSourcesForTesting()) + break; + } + + // The above loop always ends with running tasks being disallowed. Re-enable + // parallel execution before returning if it was allowed at the beginning of + // this call. + if (could_run_tasks) + task_tracker_->AllowRunTasks(); +} + +void TaskEnvironment::FastForwardBy(TimeDelta delta) { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + DCHECK(mock_time_domain_); + DCHECK_GE(delta, TimeDelta()); + + const bool could_run_tasks = task_tracker_ && task_tracker_->AllowRunTasks(); + + const TimeTicks fast_forward_until = mock_time_domain_->NowTicks() + delta; + do { + RunUntilIdle(); + } while (mock_time_domain_->FastForwardToNextTaskOrCap(fast_forward_until) != + MockTimeDomain::NextTaskSource::kNone); + + if (task_tracker_ && !could_run_tasks) + task_tracker_->DisallowRunTasks(); +} + +void TaskEnvironment::FastForwardUntilNoTasksRemain() { + // TimeTicks::operator+(TimeDelta) uses saturated arithmetic so it's safe to + // pass in TimeDelta::Max(). + FastForwardBy(TimeDelta::Max()); +} + +void TaskEnvironment::AdvanceClock(TimeDelta delta) { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + DCHECK(mock_time_domain_); + DCHECK_GE(delta, TimeDelta()); + mock_time_domain_->AdvanceClock(delta); +} + +const TickClock* TaskEnvironment::GetMockTickClock() const { + DCHECK(mock_time_domain_); + return mock_time_domain_.get(); +} + +base::TimeTicks TaskEnvironment::NowTicks() const { + DCHECK(mock_time_domain_); + return mock_time_domain_->Now(); +} + +const Clock* TaskEnvironment::GetMockClock() const { + DCHECK(mock_clock_); + return mock_clock_.get(); +} + +size_t TaskEnvironment::GetPendingMainThreadTaskCount() const { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + // ReclaimMemory sweeps canceled delayed tasks. + sequence_manager_->ReclaimMemory(); + return sequence_manager_->GetPendingTaskCountForTesting(); +} + +TimeDelta TaskEnvironment::NextMainThreadPendingTaskDelay() const { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + // ReclaimMemory sweeps canceled delayed tasks. + sequence_manager_->ReclaimMemory(); + DCHECK(mock_time_domain_); + Optional<TimeTicks> run_time = mock_time_domain_->NextScheduledRunTime(); + if (run_time) + return *run_time - mock_time_domain_->Now(); + return TimeDelta::Max(); +} + +bool TaskEnvironment::NextTaskIsDelayed() const { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + + TimeDelta delay = NextMainThreadPendingTaskDelay(); + return !delay.is_zero() && !delay.is_max(); +} + +void TaskEnvironment::DescribePendingMainThreadTasks() const { + DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_); + LOG(INFO) << sequence_manager_->DescribeAllPendingTasks(); +} + +// static +void TaskEnvironment::AddDestructionObserver(DestructionObserver* observer) { + GetDestructionObservers().AddObserver(observer); +} + +// static +void TaskEnvironment::RemoveDestructionObserver(DestructionObserver* observer) { + GetDestructionObservers().RemoveObserver(observer); +} + +TaskEnvironment::TestTaskTracker::TestTaskTracker() + : internal::ThreadPoolImpl::TaskTrackerImpl(std::string()), + can_run_tasks_cv_(&lock_), + task_completed_cv_(&lock_) { + // Consider threads blocked on these as idle (avoids instantiating + // ScopedBlockingCalls and confusing some //base internals tests). + can_run_tasks_cv_.declare_only_used_while_idle(); + task_completed_cv_.declare_only_used_while_idle(); +} + +bool TaskEnvironment::TestTaskTracker::AllowRunTasks() { + AutoLock auto_lock(lock_); + const bool could_run_tasks = can_run_tasks_; + can_run_tasks_ = true; + can_run_tasks_cv_.Broadcast(); + return could_run_tasks; +} + +bool TaskEnvironment::TestTaskTracker::TasksAllowedToRun() const { + AutoLock auto_lock(lock_); + return can_run_tasks_; +} + +bool TaskEnvironment::TestTaskTracker::DisallowRunTasks() { + AutoLock auto_lock(lock_); + + // Can't disallow run task if there are tasks running. + if (num_tasks_running_ > 0) { + // Attempt to wait a bit so that the caller doesn't busy-loop with the same + // set of pending work. A short wait is required to avoid deadlock + // scenarios. See DisallowRunTasks()'s declaration for more details. + task_completed_cv_.TimedWait(TimeDelta::FromMilliseconds(1)); + return false; + } + + can_run_tasks_ = false; + return true; +} + +void TaskEnvironment::TestTaskTracker::RunTask(internal::Task task, + internal::TaskSource* sequence, + const TaskTraits& traits) { + { + AutoLock auto_lock(lock_); + + while (!can_run_tasks_) + can_run_tasks_cv_.Wait(); + + ++num_tasks_running_; + } + + { + // Using TimeTicksNowIgnoringOverride() because in tests that mock time, + // Now() can advance very far very fast, and that's not a problem. This is + // watching for tests that have actually long running tasks which cause our + // test suites to run slowly. + base::TimeTicks before = base::subtle::TimeTicksNowIgnoringOverride(); + internal::ThreadPoolImpl::TaskTrackerImpl::RunTask(std::move(task), + sequence, traits); + base::TimeTicks after = base::subtle::TimeTicksNowIgnoringOverride(); + + if ((after - before) > TestTimeouts::action_max_timeout()) { + ADD_FAILURE() << "TaskEnvironment: RunTask took more than " + << TestTimeouts::action_max_timeout().InSeconds() + << " seconds. " + << "Posted from " << task.posted_from.ToString(); + } + } + + { + AutoLock auto_lock(lock_); + + CHECK_GT(num_tasks_running_, 0); + CHECK(can_run_tasks_); + + --num_tasks_running_; + + task_completed_cv_.Broadcast(); + } +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/task_environment.h b/chromium/base/test/task_environment.h new file mode 100644 index 00000000000..846f10e34ae --- /dev/null +++ b/chromium/base/test/task_environment.h @@ -0,0 +1,445 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TASK_ENVIRONMENT_H_ +#define BASE_TEST_TASK_ENVIRONMENT_H_ + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/single_thread_task_runner.h" +#include "base/task/lazy_thread_pool_task_runner.h" +#include "base/task/sequence_manager/sequence_manager.h" +#include "base/test/scoped_run_loop_timeout.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "base/traits_bag.h" +#include "build/build_config.h" + +namespace base { + +class Clock; +class FileDescriptorWatcher; +class SimpleTaskExecutor; +class TickClock; + +namespace subtle { +class ScopedTimeClockOverrides; +} + +namespace test { + +// This header exposes SingleThreadTaskEnvironment and TaskEnvironment. +// +// SingleThreadTaskEnvironment enables the following APIs within its scope: +// - (Thread|Sequenced)TaskRunnerHandle on the main thread +// - RunLoop on the main thread +// +// TaskEnvironment additionally enables: +// - posting to base::ThreadPool through base/task/thread_pool.h. +// +// Hint: For content::BrowserThreads, use content::BrowserTaskEnvironment. +// +// Tests should prefer SingleThreadTaskEnvironment over TaskEnvironment when the +// former is sufficient. +// +// Tasks posted to the (Thread|Sequenced)TaskRunnerHandle run synchronously when +// RunLoop::Run(UntilIdle) or TaskEnvironment::RunUntilIdle is called on the +// main thread. +// +// The TaskEnvironment requires TestTimeouts::Initialize() to be called in order +// to run posted tasks, so that it can watch for problematic long-running tasks. +// +// The TimeSource trait can be used to request that delayed tasks be under the +// manual control of RunLoop::Run() and TaskEnvironment::FastForward*() methods. +// +// If a TaskEnvironment's ThreadPoolExecutionMode is QUEUED, ThreadPool tasks +// run when RunUntilIdle() or ~TaskEnvironment is called. If +// ThreadPoolExecutionMode is ASYNC, they run as they are posted. +// +// All TaskEnvironment methods must be called from the main thread. +// +// Usage: +// +// class MyTestFixture : public testing::Test { +// public: +// (...) +// +// // protected rather than private visibility will allow controlling the +// // task environment (e.g. RunUntilIdle(), FastForwardBy(), etc.). from the +// // test body. +// protected: +// // Must generally be the first member to be initialized first and +// // destroyed last (some members that require single-threaded +// // initialization and tear down may need to come before -- e.g. +// // base::test::ScopedFeatureList). Extra traits, like TimeSource, are +// // best provided inline when declaring the TaskEnvironment, as +// // such: +// base::test::TaskEnvironment task_environment_{ +// base::test::TaskEnvironment::TimeSource::MOCK_TIME}; +// +// // Other members go here (or further below in private section.) +// }; +class TaskEnvironment { + protected: + // This enables a two-phase initialization for sub classes such as + // content::BrowserTaskEnvironment which need to provide the default task + // queue because they instantiate a scheduler on the same thread. Subclasses + // using this trait must invoke DeferredInitFromSubclass() before running the + // task environment. + struct SubclassCreatesDefaultTaskRunner {}; + + public: + enum class TimeSource { + // Delayed tasks and Time/TimeTicks::Now() use the real-time system clock. + SYSTEM_TIME, + + // Delayed tasks use a mock clock which only advances when reaching "idle" + // during a RunLoop::Run() call on the main thread or a FastForward*() call + // to this TaskEnvironment. "idle" is defined as the main thread and thread + // pool being out of ready tasks. In that situation : time advances to the + // soonest delay between main thread and thread pool delayed tasks, + // according to the semantics of the current Run*() or FastForward*() call. + // + // This also mocks Time/TimeTicks::Now() with the same mock clock. + // + // Warning some platform APIs are still real-time, e.g.: + // * PlatformThread::Sleep + // * WaitableEvent::TimedWait + // * ConditionVariable::TimedWait + // * Delayed tasks on unmanaged base::Thread's and other custom task + // runners. + MOCK_TIME, + + DEFAULT = SYSTEM_TIME + }; + + // This type will determine what types of messages will get pumped by the main + // thread. + // Note: If your test needs to use a custom MessagePump you should + // consider using a SingleThreadTaskExecutor instead. + enum class MainThreadType { + // The main thread doesn't pump system messages. + DEFAULT, + // The main thread pumps UI messages. + UI, + // The main thread pumps asynchronous IO messages and supports the + // FileDescriptorWatcher API on POSIX. + IO, + }; + + // Note that this is irrelevant (and ignored) under + // ThreadingMode::MAIN_THREAD_ONLY + enum class ThreadPoolExecutionMode { + // Thread pool tasks are queued and only executed when RunUntilIdle(), + // FastForwardBy(), or FastForwardUntilNoTasksRemain() are explicitly + // called. Note: RunLoop::Run() does *not* unblock the ThreadPool in this + // mode (it strictly runs only the main thread). + QUEUED, + // Thread pool tasks run as they are posted. RunUntilIdle() can still be + // used to block until done. + // Note that regardless of this trait, delayed tasks are always "queued" + // under TimeSource::MOCK_TIME mode. + ASYNC, + DEFAULT = ASYNC + }; + + enum class ThreadingMode { + // ThreadPool will be initialized, thus adding support for multi-threaded + // tests. + MULTIPLE_THREADS, + // No thread pool will be initialized. Useful for tests that want to run + // single threaded. Prefer using SingleThreadTaskEnvironment over this + // trait. + MAIN_THREAD_ONLY, + DEFAULT = MULTIPLE_THREADS + }; + + // On Windows, sets the COM environment for the ThreadPoolInstance. Ignored + // on other platforms. + enum class ThreadPoolCOMEnvironment { + // Do not initialize COM for the pool's workers. + NONE, + + // Place the pool's workers in a COM MTA. + COM_MTA, + + // Enable the MTA by default in unit tests to match the browser process's + // ThreadPoolInstance configuration. + // + // This has the adverse side-effect of enabling the MTA in non-browser unit + // tests as well but the downside there is not as bad as not having it in + // browser unit tests. It just means some COM asserts may pass in unit + // tests where they wouldn't in integration tests or prod. That's okay + // because unit tests are already generally very loose on allowing I/O, + // waits, etc. Such misuse will still be caught in later phases (and COM + // usage should already be pretty much inexistent in sandboxed processes). + DEFAULT = COM_MTA, + }; + + // List of traits that are valid inputs for the constructor below. + struct ValidTraits { + ValidTraits(TimeSource); + ValidTraits(MainThreadType); + ValidTraits(ThreadPoolExecutionMode); + ValidTraits(SubclassCreatesDefaultTaskRunner); + ValidTraits(ThreadingMode); + ValidTraits(ThreadPoolCOMEnvironment); + }; + + // Constructor accepts zero or more traits which customize the testing + // environment. + template <typename... TaskEnvironmentTraits, + class CheckArgumentsAreValid = std::enable_if_t< + trait_helpers::AreValidTraits<ValidTraits, + TaskEnvironmentTraits...>::value>> + NOINLINE explicit TaskEnvironment(TaskEnvironmentTraits... traits) + : TaskEnvironment( + trait_helpers::GetEnum<TimeSource, TimeSource::DEFAULT>(traits...), + trait_helpers::GetEnum<MainThreadType, MainThreadType::DEFAULT>( + traits...), + trait_helpers::GetEnum<ThreadPoolExecutionMode, + ThreadPoolExecutionMode::DEFAULT>(traits...), + trait_helpers::GetEnum<ThreadingMode, ThreadingMode::DEFAULT>( + traits...), + trait_helpers::GetEnum<ThreadPoolCOMEnvironment, + ThreadPoolCOMEnvironment::DEFAULT>( + traits...), + trait_helpers::HasTrait<SubclassCreatesDefaultTaskRunner, + TaskEnvironmentTraits...>(), + trait_helpers::NotATraitTag()) {} + + // Waits until no undelayed ThreadPool tasks remain. Then, unregisters the + // ThreadPoolInstance and the (Thread|Sequenced)TaskRunnerHandle. + virtual ~TaskEnvironment(); + + // Returns a TaskRunner that schedules tasks on the main thread. + scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner(); + + // Returns whether the main thread's TaskRunner has pending tasks. This will + // always return true if called right after RunUntilIdle. + bool MainThreadIsIdle() const; + + // Runs tasks until both the (Thread|Sequenced)TaskRunnerHandle and the + // ThreadPool's non-delayed queues are empty. + // While RunUntilIdle() is quite practical and sometimes even necessary -- for + // example, to flush all tasks bound to Unretained() state before destroying + // test members -- it should be used with caution per the following warnings: + // + // WARNING #1: This may run long (flakily timeout) and even never return! Do + // not use this when repeating tasks such as animated web pages + // are present. + // WARNING #2: This may return too early! For example, if used to run until an + // incoming event has occurred but that event depends on a task in + // a different queue -- e.g. a standalone base::Thread or a system + // event. + // + // As such, prefer RunLoop::Run() with an explicit RunLoop::QuitClosure() when + // possible. + void RunUntilIdle(); + + // Only valid for instances using TimeSource::MOCK_TIME. Fast-forwards + // virtual time by |delta|, causing all tasks on the main thread and thread + // pool with a remaining delay less than or equal to |delta| to be executed in + // their natural order before this returns. |delta| must be non-negative. Upon + // returning from this method, NowTicks() will be >= the initial |NowTicks() + + // delta|. It is guaranteed to be == iff tasks executed in this + // FastForwardBy() didn't result in nested calls to time-advancing-methods. + void FastForwardBy(TimeDelta delta); + + // Only valid for instances using TimeSource::MOCK_TIME. + // Short for FastForwardBy(TimeDelta::Max()). + // + // WARNING: This has the same caveat as RunUntilIdle() and is even more likely + // to spin forever (any RepeatingTimer will cause this). + void FastForwardUntilNoTasksRemain(); + + // Only valid for instances using TimeSource::MOCK_TIME. Advances virtual time + // by |delta|. Unlike FastForwardBy, this does not run tasks. Prefer + // FastForwardBy() when possible but this can be useful when testing blocked + // pending tasks where being idle (required to fast-forward) is not possible. + // + // Delayed tasks that are ripe as a result of this will be scheduled. + // RunUntilIdle() can be used after this call to ensure those tasks have run. + // Note: AdvanceClock(delta) + RunUntilIdle() is slightly different from + // FastForwardBy(delta) in that time passes instantly before running any task + // (whereas FastForwardBy() will advance the clock in the smallest increments + // possible at a time). Hence FastForwardBy() is more realistic but + // AdvanceClock() can be useful when testing edge case scenarios that + // specifically handle more time than expected to have passed. + void AdvanceClock(TimeDelta delta); + + // Only valid for instances using TimeSource::MOCK_TIME. Returns a + // TickClock whose time is updated by FastForward(By|UntilNoTasksRemain). + const TickClock* GetMockTickClock() const; + std::unique_ptr<TickClock> DeprecatedGetMockTickClock(); + + // Only valid for instances using TimeSource::MOCK_TIME. Returns a + // Clock whose time is updated by FastForward(By|UntilNoTasksRemain). The + // initial value is implementation defined and should be queried by tests that + // depend on it. + // TickClock should be used instead of Clock to measure elapsed time in a + // process. See time.h. + const Clock* GetMockClock() const; + + // Only valid for instances using TimeSource::MOCK_TIME. Returns the current + // virtual tick time (based on a realistic Now(), sampled when this + // TaskEnvironment was created, and manually advanced from that point on). + // This is always equivalent to base::TimeTicks::Now() under + // TimeSource::MOCK_TIME. + base::TimeTicks NowTicks() const; + + // Only valid for instances using TimeSource::MOCK_TIME. Returns the + // number of pending tasks (delayed and non-delayed) of the main thread's + // TaskRunner. When debugging, you can use DescribePendingMainThreadTasks() to + // see what those are. + size_t GetPendingMainThreadTaskCount() const; + + // Only valid for instances using TimeSource::MOCK_TIME. + // Returns the delay until the next pending task of the main thread's + // TaskRunner if there is one, otherwise it returns TimeDelta::Max(). + TimeDelta NextMainThreadPendingTaskDelay() const; + + // Only valid for instances using TimeSource::MOCK_TIME. + // Returns true iff the next task is delayed. Returns false if the next task + // is immediate or if there is no next task. + bool NextTaskIsDelayed() const; + + // For debugging purposes: Dumps information about pending tasks on the main + // thread. + void DescribePendingMainThreadTasks() const; + + class DestructionObserver : public CheckedObserver { + public: + DestructionObserver() = default; + ~DestructionObserver() override = default; + + DestructionObserver(const DestructionObserver&) = delete; + DestructionObserver& operator=(const DestructionObserver&) = delete; + + virtual void WillDestroyCurrentTaskEnvironment() = 0; + }; + + // Adds/removes a DestructionObserver to any TaskEnvironment. Observers are + // notified when any TaskEnvironment goes out of scope (other than with a move + // operation). Must be called on the main thread. + static void AddDestructionObserver(DestructionObserver* observer); + static void RemoveDestructionObserver(DestructionObserver* observer); + + // The number of foreground workers in the ThreadPool managed by a + // TaskEnvironment instance. This can be used to determine the maximum + // parallelism in tests that require each parallel task it spawns to be + // running at once. Having multiple threads prevents deadlocks should some + // blocking APIs not use ScopedBlockingCall. It also allows enough concurrency + // to allow TSAN to spot data races. + static constexpr int kNumForegroundThreadPoolThreads = 4; + + protected: + explicit TaskEnvironment(TaskEnvironment&& other); + + constexpr MainThreadType main_thread_type() const { + return main_thread_type_; + } + + constexpr ThreadPoolExecutionMode thread_pool_execution_mode() const { + return thread_pool_execution_mode_; + } + + // Returns the TimeDomain driving this TaskEnvironment. + sequence_manager::TimeDomain* GetTimeDomain() const; + + sequence_manager::SequenceManager* sequence_manager() const; + + void DeferredInitFromSubclass( + scoped_refptr<base::SingleThreadTaskRunner> task_runner); + + // Derived classes may need to control when the sequence manager goes away. + void NotifyDestructionObserversAndReleaseSequenceManager(); + + private: + class TestTaskTracker; + class MockTimeDomain; + + void InitializeThreadPool(); + void DestroyThreadPool(); + + void CompleteInitialization(); + + // The template constructor has to be in the header but it delegates to this + // constructor to initialize all other members out-of-line. + TaskEnvironment(TimeSource time_source, + MainThreadType main_thread_type, + ThreadPoolExecutionMode thread_pool_execution_mode, + ThreadingMode threading_mode, + ThreadPoolCOMEnvironment thread_pool_com_environment, + bool subclass_creates_default_taskrunner, + trait_helpers::NotATraitTag tag); + + const MainThreadType main_thread_type_; + const ThreadPoolExecutionMode thread_pool_execution_mode_; + const ThreadingMode threading_mode_; + const ThreadPoolCOMEnvironment thread_pool_com_environment_; + const bool subclass_creates_default_taskrunner_; + + std::unique_ptr<sequence_manager::SequenceManager> sequence_manager_; + + // Manages the clock under TimeSource::MOCK_TIME modes. Null in + // TimeSource::SYSTEM_TIME mode. + std::unique_ptr<MockTimeDomain> mock_time_domain_; + + // Overrides Time/TimeTicks::Now() under TimeSource::MOCK_TIME_AND_NOW mode. + // Null in other modes. + std::unique_ptr<subtle::ScopedTimeClockOverrides> time_overrides_; + + scoped_refptr<sequence_manager::TaskQueue> task_queue_; + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + // Only set for instances using TimeSource::MOCK_TIME. + std::unique_ptr<Clock> mock_clock_; + +#if defined(OS_POSIX) || defined(OS_FUCHSIA) + // Enables the FileDescriptorWatcher API iff running a MainThreadType::IO. + std::unique_ptr<FileDescriptorWatcher> file_descriptor_watcher_; +#endif + + // Owned by the ThreadPoolInstance. + TestTaskTracker* task_tracker_ = nullptr; + + // Ensures destruction of lazy TaskRunners when this is destroyed. + std::unique_ptr<internal::ScopedLazyTaskRunnerListForTesting> + scoped_lazy_task_runner_list_for_testing_; + + // Sets RunLoop::Run() to LOG(FATAL) if not Quit() in a timely manner. + std::unique_ptr<ScopedRunLoopTimeout> run_loop_timeout_; + + std::unique_ptr<bool> owns_instance_ = std::make_unique<bool>(true); + + // To support base::CurrentThread(). + std::unique_ptr<SimpleTaskExecutor> simple_task_executor_; + + // Used to verify thread-affinity of operations that must occur on the main + // thread. This is the case for anything that modifies or drives the + // |sequence_manager_|. + THREAD_CHECKER(main_thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(TaskEnvironment); +}; + +// SingleThreadTaskEnvironment takes the same traits as TaskEnvironment and is +// used the exact same way. It's a short-form for +// TaskEnvironment{TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY, ...}; +class SingleThreadTaskEnvironment : public TaskEnvironment { + public: + template <class... ArgTypes> + SingleThreadTaskEnvironment(ArgTypes... args) + : TaskEnvironment(ThreadingMode::MAIN_THREAD_ONLY, args...) {} +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_TASK_ENVIRONMENT_H_ diff --git a/chromium/base/test/task_environment_unittest.cc b/chromium/base/test/task_environment_unittest.cc new file mode 100644 index 00000000000..5d20d60f2c1 --- /dev/null +++ b/chromium/base/test/task_environment_unittest.cc @@ -0,0 +1,1274 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/task_environment.h" + +#include <atomic> +#include <memory> + +#include "base/atomicops.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/cancelable_callback.h" +#include "base/debug/debugger.h" +#include "base/message_loop/message_loop_current.h" +#include "base/run_loop.h" +#include "base/synchronization/atomic_flag.h" +#include "base/synchronization/waitable_event.h" +#include "base/task/sequence_manager/time_domain.h" +#include "base/task/thread_pool.h" +#include "base/task/thread_pool/thread_pool_instance.h" +#include "base/test/bind_test_util.h" +#include "base/test/gtest_util.h" +#include "base/test/mock_callback.h" +#include "base/test/mock_log.h" +#include "base/test/scoped_run_loop_timeout.h" +#include "base/test/test_timeouts.h" +#include "base/threading/platform_thread.h" +#include "base/threading/sequence_local_storage_slot.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/clock.h" +#include "base/time/default_clock.h" +#include "base/time/tick_clock.h" +#include "base/win/com_init_util.h" +#include "build/build_config.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest-spi.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_POSIX) +#include <unistd.h> + +#include "base/files/file_descriptor_watcher_posix.h" +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +#include "base/win/scoped_com_initializer.h" +#endif + +namespace base { +namespace test { + +namespace { + +using ::testing::_; +using ::testing::HasSubstr; +using ::testing::IsNull; +using ::testing::Not; +using ::testing::Return; + +class TaskEnvironmentTest : public testing::Test {}; + +void VerifyRunUntilIdleDidNotReturnAndSetFlag( + AtomicFlag* run_until_idle_returned, + AtomicFlag* task_ran) { + EXPECT_FALSE(run_until_idle_returned->IsSet()); + task_ran->Set(); +} + +void RunUntilIdleTest( + TaskEnvironment::ThreadPoolExecutionMode thread_pool_execution_mode) { + AtomicFlag run_until_idle_returned; + TaskEnvironment task_environment(thread_pool_execution_mode); + + AtomicFlag first_main_thread_task_ran; + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, + Unretained(&run_until_idle_returned), + Unretained(&first_main_thread_task_ran))); + + AtomicFlag first_thread_pool_task_ran; + ThreadPool::PostTask(FROM_HERE, + BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, + Unretained(&run_until_idle_returned), + Unretained(&first_thread_pool_task_ran))); + + AtomicFlag second_thread_pool_task_ran; + AtomicFlag second_main_thread_task_ran; + ThreadPool::PostTaskAndReply( + FROM_HERE, + BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, + Unretained(&run_until_idle_returned), + Unretained(&second_thread_pool_task_ran)), + BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, + Unretained(&run_until_idle_returned), + Unretained(&second_main_thread_task_ran))); + + task_environment.RunUntilIdle(); + run_until_idle_returned.Set(); + + EXPECT_TRUE(first_main_thread_task_ran.IsSet()); + EXPECT_TRUE(first_thread_pool_task_ran.IsSet()); + EXPECT_TRUE(second_thread_pool_task_ran.IsSet()); + EXPECT_TRUE(second_main_thread_task_ran.IsSet()); +} + +} // namespace + +TEST_F(TaskEnvironmentTest, QueuedRunUntilIdle) { + RunUntilIdleTest(TaskEnvironment::ThreadPoolExecutionMode::QUEUED); +} + +TEST_F(TaskEnvironmentTest, AsyncRunUntilIdle) { + RunUntilIdleTest(TaskEnvironment::ThreadPoolExecutionMode::ASYNC); +} + +// Verify that tasks posted to an ThreadPoolExecutionMode::QUEUED +// TaskEnvironment do not run outside of RunUntilIdle(). +TEST_F(TaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) { + TaskEnvironment task_environment( + TaskEnvironment::ThreadPoolExecutionMode::QUEUED); + + AtomicFlag run_until_idle_called; + ThreadPool::PostTask(FROM_HERE, + BindOnce( + [](AtomicFlag* run_until_idle_called) { + EXPECT_TRUE(run_until_idle_called->IsSet()); + }, + Unretained(&run_until_idle_called))); + PlatformThread::Sleep(TestTimeouts::tiny_timeout()); + run_until_idle_called.Set(); + task_environment.RunUntilIdle(); + + AtomicFlag other_run_until_idle_called; + ThreadPool::PostTask(FROM_HERE, + BindOnce( + [](AtomicFlag* other_run_until_idle_called) { + EXPECT_TRUE(other_run_until_idle_called->IsSet()); + }, + Unretained(&other_run_until_idle_called))); + PlatformThread::Sleep(TestTimeouts::tiny_timeout()); + other_run_until_idle_called.Set(); + task_environment.RunUntilIdle(); +} + +// Verify that a task posted to an ThreadPoolExecutionMode::ASYNC +// TaskEnvironment can run without a call to RunUntilIdle(). +TEST_F(TaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) { + TaskEnvironment task_environment( + TaskEnvironment::ThreadPoolExecutionMode::ASYNC); + + WaitableEvent task_ran; + ThreadPool::PostTask(FROM_HERE, + BindOnce(&WaitableEvent::Signal, Unretained(&task_ran))); + task_ran.Wait(); +} + +// Verify that a task posted to an ThreadPoolExecutionMode::ASYNC +// TaskEnvironment after a call to RunUntilIdle() can run without another +// call to RunUntilIdle(). +TEST_F(TaskEnvironmentTest, AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) { + TaskEnvironment task_environment( + TaskEnvironment::ThreadPoolExecutionMode::ASYNC); + + task_environment.RunUntilIdle(); + + WaitableEvent task_ran; + ThreadPool::PostTask(FROM_HERE, + BindOnce(&WaitableEvent::Signal, Unretained(&task_ran))); + task_ran.Wait(); +} + +void DelayedTasksTest(TaskEnvironment::TimeSource time_source) { + // Use a QUEUED execution-mode environment, so that no tasks are actually + // executed until RunUntilIdle()/FastForwardBy() are invoked. + TaskEnvironment task_environment( + time_source, TaskEnvironment::ThreadPoolExecutionMode::QUEUED); + + subtle::Atomic32 counter = 0; + + constexpr base::TimeDelta kShortTaskDelay = TimeDelta::FromDays(1); + // Should run only in MOCK_TIME environment when time is fast-forwarded. + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement(counter, 4); + }, + Unretained(&counter)), + kShortTaskDelay); + ThreadPool::PostDelayedTask(FROM_HERE, + BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement(counter, + 128); + }, + Unretained(&counter)), + kShortTaskDelay); + + constexpr base::TimeDelta kLongTaskDelay = TimeDelta::FromDays(7); + // Same as first task, longer delays to exercise + // FastForwardUntilNoTasksRemain(). + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement(counter, 8); + }, + Unretained(&counter)), + TimeDelta::FromDays(5)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement(counter, 16); + }, + Unretained(&counter)), + kLongTaskDelay); + ThreadPool::PostDelayedTask(FROM_HERE, + BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement(counter, + 256); + }, + Unretained(&counter)), + kLongTaskDelay * 2); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement(counter, 512); + }, + Unretained(&counter)), + kLongTaskDelay * 3); + ThreadPool::PostDelayedTask(FROM_HERE, + BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement(counter, + 1024); + }, + Unretained(&counter)), + kLongTaskDelay * 4); + + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement(counter, 1); + }, + Unretained(&counter))); + ThreadPool::PostTask(FROM_HERE, BindOnce( + [](subtle::Atomic32* counter) { + subtle::NoBarrier_AtomicIncrement( + counter, 2); + }, + Unretained(&counter))); + + // This expectation will fail flakily if the preceding PostTask() is executed + // asynchronously, indicating a problem with the QUEUED execution mode. + int expected_value = 0; + EXPECT_EQ(expected_value, counter); + + // RunUntilIdle() should process non-delayed tasks only in all queues. + task_environment.RunUntilIdle(); + expected_value += 1; + expected_value += 2; + EXPECT_EQ(expected_value, counter); + + if (time_source == TaskEnvironment::TimeSource::MOCK_TIME) { + const TimeTicks start_time = task_environment.NowTicks(); + + // Delay inferior to the delay of the first posted task. + constexpr base::TimeDelta kInferiorTaskDelay = TimeDelta::FromSeconds(1); + static_assert(kInferiorTaskDelay < kShortTaskDelay, + "|kInferiorTaskDelay| should be " + "set to a value inferior to the first posted task's delay."); + task_environment.FastForwardBy(kInferiorTaskDelay); + EXPECT_EQ(expected_value, counter); + + task_environment.FastForwardBy(kShortTaskDelay - kInferiorTaskDelay); + expected_value += 4; + expected_value += 128; + EXPECT_EQ(expected_value, counter); + + task_environment.FastForwardUntilNoTasksRemain(); + expected_value += 8; + expected_value += 16; + expected_value += 256; + expected_value += 512; + expected_value += 1024; + EXPECT_EQ(expected_value, counter); + + EXPECT_EQ(task_environment.NowTicks() - start_time, kLongTaskDelay * 4); + } +} + +TEST_F(TaskEnvironmentTest, DelayedTasksUnderSystemTime) { + DelayedTasksTest(TaskEnvironment::TimeSource::SYSTEM_TIME); +} + +TEST_F(TaskEnvironmentTest, DelayedTasksUnderMockTime) { + DelayedTasksTest(TaskEnvironment::TimeSource::MOCK_TIME); +} + +// Regression test for https://crbug.com/824770. +void SupportsSequenceLocalStorageOnMainThreadTest( + TaskEnvironment::TimeSource time_source) { + TaskEnvironment task_environment( + time_source, TaskEnvironment::ThreadPoolExecutionMode::ASYNC); + + SequenceLocalStorageSlot<int> sls_slot; + sls_slot.emplace(5); + EXPECT_EQ(5, *sls_slot); +} + +TEST_F(TaskEnvironmentTest, SupportsSequenceLocalStorageOnMainThread) { + SupportsSequenceLocalStorageOnMainThreadTest( + TaskEnvironment::TimeSource::SYSTEM_TIME); +} + +TEST_F(TaskEnvironmentTest, + SupportsSequenceLocalStorageOnMainThreadWithMockTime) { + SupportsSequenceLocalStorageOnMainThreadTest( + TaskEnvironment::TimeSource::MOCK_TIME); +} + +// Verify that the right MessagePump is instantiated under each MainThreadType. +// This avoids having to run all other TaskEnvironmentTests in every +// MainThreadType which is redundant (message loop and message pump tests +// otherwise cover the advanced functionality provided by UI/IO pumps). +TEST_F(TaskEnvironmentTest, MainThreadType) { + // Uses MessageLoopCurrent as a convenience accessor but could be replaced by + // different accessors when we get rid of MessageLoopCurrent. + EXPECT_FALSE(MessageLoopCurrent::IsSet()); + EXPECT_FALSE(MessageLoopCurrentForUI::IsSet()); + EXPECT_FALSE(MessageLoopCurrentForIO::IsSet()); + { + TaskEnvironment task_environment; + EXPECT_TRUE(MessageLoopCurrent::IsSet()); + EXPECT_FALSE(MessageLoopCurrentForUI::IsSet()); + EXPECT_FALSE(MessageLoopCurrentForIO::IsSet()); + } + { + TaskEnvironment task_environment(TaskEnvironment::MainThreadType::UI); + EXPECT_TRUE(MessageLoopCurrent::IsSet()); + EXPECT_TRUE(MessageLoopCurrentForUI::IsSet()); + EXPECT_FALSE(MessageLoopCurrentForIO::IsSet()); + } + { + TaskEnvironment task_environment(TaskEnvironment::MainThreadType::IO); + EXPECT_TRUE(MessageLoopCurrent::IsSet()); + EXPECT_FALSE(MessageLoopCurrentForUI::IsSet()); + EXPECT_TRUE(MessageLoopCurrentForIO::IsSet()); + } + EXPECT_FALSE(MessageLoopCurrent::IsSet()); + EXPECT_FALSE(MessageLoopCurrentForUI::IsSet()); + EXPECT_FALSE(MessageLoopCurrentForIO::IsSet()); +} + +#if defined(OS_POSIX) +TEST_F(TaskEnvironmentTest, SupportsFileDescriptorWatcherOnIOMainThread) { + TaskEnvironment task_environment(TaskEnvironment::MainThreadType::IO); + + int pipe_fds_[2]; + ASSERT_EQ(0, pipe(pipe_fds_)); + + RunLoop run_loop; + + // The write end of a newly created pipe is immediately writable. + auto controller = FileDescriptorWatcher::WatchWritable( + pipe_fds_[1], run_loop.QuitClosure()); + + // This will hang if the notification doesn't occur as expected. + run_loop.Run(); +} + +TEST_F(TaskEnvironmentTest, + SupportsFileDescriptorWatcherOnIOMockTimeMainThread) { + TaskEnvironment task_environment(TaskEnvironment::MainThreadType::IO, + TaskEnvironment::TimeSource::MOCK_TIME); + + int pipe_fds_[2]; + ASSERT_EQ(0, pipe(pipe_fds_)); + + RunLoop run_loop; + + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, BindLambdaForTesting([&]() { + int64_t x = 1; + auto ret = write(pipe_fds_[1], &x, sizeof(x)); + ASSERT_EQ(static_cast<size_t>(ret), sizeof(x)); + }), + TimeDelta::FromHours(1)); + + auto controller = FileDescriptorWatcher::WatchReadable( + pipe_fds_[0], run_loop.QuitClosure()); + + // This will hang if the notification doesn't occur as expected (Run() should + // fast-forward-time when idle). + run_loop.Run(); +} +#endif // defined(OS_POSIX) + +// Verify that the TickClock returned by +// |TaskEnvironment::GetMockTickClock| gets updated when the +// FastForward(By|UntilNoTasksRemain) functions are called. +TEST_F(TaskEnvironmentTest, FastForwardAdvancesTickClock) { + // Use a QUEUED execution-mode environment, so that no tasks are actually + // executed until RunUntilIdle()/FastForwardBy() are invoked. + TaskEnvironment task_environment( + TaskEnvironment::TimeSource::MOCK_TIME, + TaskEnvironment::ThreadPoolExecutionMode::QUEUED); + + constexpr base::TimeDelta kShortTaskDelay = TimeDelta::FromDays(1); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(), + kShortTaskDelay); + + constexpr base::TimeDelta kLongTaskDelay = TimeDelta::FromDays(7); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(), + kLongTaskDelay); + + const base::TickClock* tick_clock = task_environment.GetMockTickClock(); + base::TimeTicks tick_clock_ref = tick_clock->NowTicks(); + + // Make sure that |FastForwardBy| advances the clock. + task_environment.FastForwardBy(kShortTaskDelay); + EXPECT_EQ(kShortTaskDelay, tick_clock->NowTicks() - tick_clock_ref); + + // Make sure that |FastForwardUntilNoTasksRemain| advances the clock. + task_environment.FastForwardUntilNoTasksRemain(); + EXPECT_EQ(kLongTaskDelay, tick_clock->NowTicks() - tick_clock_ref); + + // Fast-forwarding to a time at which there's no tasks should also advance the + // clock. + task_environment.FastForwardBy(kLongTaskDelay); + EXPECT_EQ(kLongTaskDelay * 2, tick_clock->NowTicks() - tick_clock_ref); +} + +TEST_F(TaskEnvironmentTest, FastForwardAdvancesMockClock) { + constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42); + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const Clock* clock = task_environment.GetMockClock(); + const Time start_time = clock->Now(); + task_environment.FastForwardBy(kDelay); + + EXPECT_EQ(start_time + kDelay, clock->Now()); +} + +TEST_F(TaskEnvironmentTest, FastForwardAdvancesTime) { + constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42); + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const Time start_time = base::Time::Now(); + task_environment.FastForwardBy(kDelay); + EXPECT_EQ(start_time + kDelay, base::Time::Now()); +} + +TEST_F(TaskEnvironmentTest, FastForwardAdvancesTimeTicks) { + constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42); + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const TimeTicks start_time = base::TimeTicks::Now(); + task_environment.FastForwardBy(kDelay); + EXPECT_EQ(start_time + kDelay, base::TimeTicks::Now()); +} + +TEST_F(TaskEnvironmentTest, AdvanceClockAdvancesTickClock) { + constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42); + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const base::TickClock* tick_clock = task_environment.GetMockTickClock(); + const base::TimeTicks start_time = tick_clock->NowTicks(); + task_environment.AdvanceClock(kDelay); + + EXPECT_EQ(start_time + kDelay, tick_clock->NowTicks()); +} + +TEST_F(TaskEnvironmentTest, AdvanceClockAdvancesMockClock) { + constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42); + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const Clock* clock = task_environment.GetMockClock(); + const Time start_time = clock->Now(); + task_environment.AdvanceClock(kDelay); + + EXPECT_EQ(start_time + kDelay, clock->Now()); +} + +TEST_F(TaskEnvironmentTest, AdvanceClockAdvancesTime) { + constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42); + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const Time start_time = base::Time::Now(); + task_environment.AdvanceClock(kDelay); + EXPECT_EQ(start_time + kDelay, base::Time::Now()); +} + +TEST_F(TaskEnvironmentTest, AdvanceClockAdvancesTimeTicks) { + constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42); + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const TimeTicks start_time = base::TimeTicks::Now(); + task_environment.AdvanceClock(kDelay); + EXPECT_EQ(start_time + kDelay, base::TimeTicks::Now()); +} + +TEST_F(TaskEnvironmentTest, AdvanceClockDoesNotRunTasks) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + constexpr base::TimeDelta kTaskDelay = TimeDelta::FromDays(1); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(), + kTaskDelay); + + EXPECT_EQ(1U, task_environment.GetPendingMainThreadTaskCount()); + EXPECT_TRUE(task_environment.NextTaskIsDelayed()); + + task_environment.AdvanceClock(kTaskDelay); + + // The task is still pending, but is now runnable. + EXPECT_EQ(1U, task_environment.GetPendingMainThreadTaskCount()); + EXPECT_FALSE(task_environment.NextTaskIsDelayed()); +} + +TEST_F(TaskEnvironmentTest, AdvanceClockSchedulesRipeDelayedTasks) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + bool ran = false; + + constexpr base::TimeDelta kTaskDelay = TimeDelta::FromDays(1); + ThreadPool::PostDelayedTask( + FROM_HERE, base::BindLambdaForTesting([&]() { ran = true; }), kTaskDelay); + + task_environment.AdvanceClock(kTaskDelay); + EXPECT_FALSE(ran); + task_environment.RunUntilIdle(); + EXPECT_TRUE(ran); +} + +// Verify that FastForwardBy() runs existing immediate tasks before advancing, +// then advances to the next delayed task, runs it, then advances the remainder +// of time when out of tasks. +TEST_F(TaskEnvironmentTest, FastForwardOnlyAdvancesWhenIdle) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const TimeTicks start_time = base::TimeTicks::Now(); + + constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42); + constexpr base::TimeDelta kFastForwardUntil = TimeDelta::FromSeconds(100); + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindLambdaForTesting( + [&]() { EXPECT_EQ(start_time, base::TimeTicks::Now()); })); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, BindLambdaForTesting([&]() { + EXPECT_EQ(start_time + kDelay, base::TimeTicks::Now()); + }), + kDelay); + task_environment.FastForwardBy(kFastForwardUntil); + EXPECT_EQ(start_time + kFastForwardUntil, base::TimeTicks::Now()); +} + +// FastForwardBy(0) should be equivalent of RunUntilIdle(). +TEST_F(TaskEnvironmentTest, FastForwardZero) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + std::atomic_int run_count{0}; + + for (int i = 0; i < 1000; ++i) { + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindLambdaForTesting([&]() { + run_count.fetch_add(1, std::memory_order_relaxed); + })); + ThreadPool::PostTask(FROM_HERE, BindLambdaForTesting([&]() { + run_count.fetch_add(1, std::memory_order_relaxed); + })); + } + + task_environment.FastForwardBy(base::TimeDelta()); + + EXPECT_EQ(2000, run_count.load(std::memory_order_relaxed)); +} + +TEST_F(TaskEnvironmentTest, NestedFastForwardBy) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + constexpr TimeDelta kDelayPerTask = TimeDelta::FromMilliseconds(1); + const TimeTicks start_time = task_environment.NowTicks(); + + int max_nesting_level = 0; + + RepeatingClosure post_fast_forwarding_task; + post_fast_forwarding_task = BindLambdaForTesting([&]() { + if (max_nesting_level < 5) { + ++max_nesting_level; + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_fast_forwarding_task, kDelayPerTask); + task_environment.FastForwardBy(kDelayPerTask); + } + }); + post_fast_forwarding_task.Run(); + + EXPECT_EQ(max_nesting_level, 5); + EXPECT_EQ(task_environment.NowTicks(), start_time + kDelayPerTask * 5); +} + +TEST_F(TaskEnvironmentTest, NestedRunInFastForwardBy) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + constexpr TimeDelta kDelayPerTask = TimeDelta::FromMilliseconds(1); + const TimeTicks start_time = task_environment.NowTicks(); + + std::vector<RunLoop*> run_loops; + + RepeatingClosure post_and_runloop_task; + post_and_runloop_task = BindLambdaForTesting([&]() { + // Run 4 nested run loops on top of the initial FastForwardBy(). + if (run_loops.size() < 4U) { + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_and_runloop_task, kDelayPerTask); + + RunLoop run_loop(RunLoop::Type::kNestableTasksAllowed); + run_loops.push_back(&run_loop); + run_loop.Run(); + } else { + for (RunLoop* run_loop : run_loops) { + run_loop->Quit(); + } + } + }); + + // Initial task is FastForwardBy(). + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_and_runloop_task, kDelayPerTask); + task_environment.FastForwardBy(kDelayPerTask); + + EXPECT_EQ(run_loops.size(), 4U); + EXPECT_EQ(task_environment.NowTicks(), start_time + kDelayPerTask * 5); +} + +TEST_F(TaskEnvironmentTest, + CrossThreadImmediateTaskPostingDoesntAffectMockTime) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + int count = 0; + + // Post tasks delayd between 0 and 999 seconds. + for (int i = 0; i < 1000; ++i) { + const TimeDelta delay = TimeDelta::FromSeconds(i); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce( + [](TimeTicks expected_run_time, int* count) { + EXPECT_EQ(expected_run_time, TimeTicks::Now()); + ++*count; + }, + TimeTicks::Now() + delay, &count), + delay); + } + + // Having a bunch of tasks running in parallel and replying to the main thread + // shouldn't affect the rest of this test. Wait for the first task to run + // before proceeding with the test to increase the likelihood of exercising + // races. + base::WaitableEvent first_reply_is_incoming; + for (int i = 0; i < 1000; ++i) { + ThreadPool::PostTaskAndReply( + FROM_HERE, + BindOnce(&WaitableEvent::Signal, Unretained(&first_reply_is_incoming)), + DoNothing()); + } + first_reply_is_incoming.Wait(); + + task_environment.FastForwardBy(TimeDelta::FromSeconds(1000)); + + // If this test flakes it's because there's an error with MockTimeDomain. + EXPECT_EQ(count, 1000); + + // Flush any remaining asynchronous tasks with Unretained() state. + task_environment.RunUntilIdle(); +} + +TEST_F(TaskEnvironmentTest, MultiThreadedMockTime) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + constexpr TimeDelta kOneMs = TimeDelta::FromMilliseconds(1); + const TimeTicks start_time = task_environment.NowTicks(); + const TimeTicks end_time = start_time + TimeDelta::FromMilliseconds(1'000); + + // Last TimeTicks::Now() seen from either contexts. + TimeTicks last_main_thread_ticks = start_time; + TimeTicks last_thread_pool_ticks = start_time; + + RepeatingClosure post_main_thread_delayed_task; + post_main_thread_delayed_task = BindLambdaForTesting([&]() { + // Expect that time only moves forward. + EXPECT_GE(task_environment.NowTicks(), last_main_thread_ticks); + + // Post four tasks to exercise the system some more but only if this is the + // first task at its runtime (otherwise we end up with 4^10'000 tasks by + // the end!). + if (last_main_thread_ticks < task_environment.NowTicks() && + task_environment.NowTicks() < end_time) { + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_main_thread_delayed_task, kOneMs); + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_main_thread_delayed_task, kOneMs); + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_main_thread_delayed_task, kOneMs); + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_main_thread_delayed_task, kOneMs); + } + + last_main_thread_ticks = task_environment.NowTicks(); + }); + + RepeatingClosure post_thread_pool_delayed_task; + post_thread_pool_delayed_task = BindLambdaForTesting([&]() { + // Expect that time only moves forward. + EXPECT_GE(task_environment.NowTicks(), last_thread_pool_ticks); + + // Post four tasks to exercise the system some more but only if this is the + // first task at its runtime (otherwise we end up with 4^10'000 tasks by + // the end!). + if (last_thread_pool_ticks < task_environment.NowTicks() && + task_environment.NowTicks() < end_time) { + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_thread_pool_delayed_task, kOneMs); + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_thread_pool_delayed_task, kOneMs); + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_thread_pool_delayed_task, kOneMs); + SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_thread_pool_delayed_task, kOneMs); + + EXPECT_LT(task_environment.NowTicks(), end_time); + } + + last_thread_pool_ticks = task_environment.NowTicks(); + }); + + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, post_main_thread_delayed_task, kOneMs); + ThreadPool::CreateSequencedTaskRunner({})->PostDelayedTask( + FROM_HERE, post_thread_pool_delayed_task, kOneMs); + + task_environment.FastForwardUntilNoTasksRemain(); + + EXPECT_EQ(last_main_thread_ticks, end_time); + EXPECT_EQ(last_thread_pool_ticks, end_time); + EXPECT_EQ(task_environment.NowTicks(), end_time); +} + +// This test ensures the implementation of FastForwardBy() doesn't fast-forward +// beyond the cap it reaches idle with pending delayed tasks further ahead on +// the main thread. +TEST_F(TaskEnvironmentTest, MultiThreadedFastForwardBy) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const TimeTicks start_time = task_environment.NowTicks(); + + // The 1s delayed task in the pool should run but not the 5s delayed task on + // the main thread and fast-forward by should be capped at +2s. + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE), + TimeDelta::FromSeconds(5)); + ThreadPool::PostDelayedTask(FROM_HERE, {}, MakeExpectedRunClosure(FROM_HERE), + TimeDelta::FromSeconds(1)); + task_environment.FastForwardBy(TimeDelta::FromSeconds(2)); + + EXPECT_EQ(task_environment.NowTicks(), + start_time + TimeDelta::FromSeconds(2)); +} + +// Verify that ThreadPoolExecutionMode::QUEUED doesn't prevent running tasks and +// advancing time on the main thread. +TEST_F(TaskEnvironmentTest, MultiThreadedMockTimeAndThreadPoolQueuedMode) { + TaskEnvironment task_environment( + TaskEnvironment::TimeSource::MOCK_TIME, + TaskEnvironment::ThreadPoolExecutionMode::QUEUED); + + int count = 0; + const TimeTicks start_time = task_environment.NowTicks(); + + RunLoop run_loop; + + // Neither of these should run automatically per + // ThreadPoolExecutionMode::QUEUED. + ThreadPool::PostTask(FROM_HERE, + BindLambdaForTesting([&]() { count += 128; })); + ThreadPool::PostDelayedTask(FROM_HERE, {}, + BindLambdaForTesting([&]() { count += 256; }), + TimeDelta::FromSeconds(5)); + + // Time should auto-advance to +500s in RunLoop::Run() without having to run + // the above forcefully QUEUED tasks. + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindLambdaForTesting([&]() { count += 1; })); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, + BindLambdaForTesting([&]() { + count += 2; + run_loop.Quit(); + }), + TimeDelta::FromSeconds(500)); + + int expected_value = 0; + EXPECT_EQ(expected_value, count); + run_loop.Run(); + expected_value += 1; + expected_value += 2; + EXPECT_EQ(expected_value, count); + EXPECT_EQ(task_environment.NowTicks() - start_time, + TimeDelta::FromSeconds(500)); + + // Fast-forward through all remaining tasks, this should unblock QUEUED tasks + // in the thread pool but shouldn't need to advance time to process them. + task_environment.FastForwardUntilNoTasksRemain(); + expected_value += 128; + expected_value += 256; + EXPECT_EQ(expected_value, count); + EXPECT_EQ(task_environment.NowTicks() - start_time, + TimeDelta::FromSeconds(500)); + + // Test advancing time to a QUEUED task in the future. + ThreadPool::PostDelayedTask(FROM_HERE, + BindLambdaForTesting([&]() { count += 512; }), + TimeDelta::FromSeconds(5)); + task_environment.FastForwardBy(TimeDelta::FromSeconds(7)); + expected_value += 512; + EXPECT_EQ(expected_value, count); + EXPECT_EQ(task_environment.NowTicks() - start_time, + TimeDelta::FromSeconds(507)); + + // Confirm that QUEUED mode is still active after the above fast forwarding + // (only the main thread task should run from RunLoop). + ThreadPool::PostTask(FROM_HERE, + BindLambdaForTesting([&]() { count += 1024; })); + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindLambdaForTesting([&]() { count += 2048; })); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); + RunLoop().RunUntilIdle(); + expected_value += 2048; + EXPECT_EQ(expected_value, count); + EXPECT_EQ(task_environment.NowTicks() - start_time, + TimeDelta::FromSeconds(507)); + + // Run the remaining task to avoid use-after-free on |count| from + // ~TaskEnvironment(). + task_environment.RunUntilIdle(); + expected_value += 1024; + EXPECT_EQ(expected_value, count); +} + +#if defined(OS_WIN) +// Regression test to ensure that TaskEnvironment enables the MTA in the +// thread pool (so that the test environment matches that of the browser process +// and com_init_util.h's assertions are happy in unit tests). +TEST_F(TaskEnvironmentTest, ThreadPoolPoolAllowsMTA) { + TaskEnvironment task_environment; + ThreadPool::PostTask(FROM_HERE, BindOnce(&win::AssertComApartmentType, + win::ComApartmentType::MTA)); + task_environment.RunUntilIdle(); +} +#endif // defined(OS_WIN) + +TEST_F(TaskEnvironmentTest, SetsDefaultRunTimeout) { + const RunLoop::RunLoopTimeout* old_run_timeout = + ScopedRunLoopTimeout::GetTimeoutForCurrentThread(); + + { + TaskEnvironment task_environment; + + // TaskEnvironment should set a default Run() timeout that fails the + // calling test (before test_launcher_timeout()). + + const RunLoop::RunLoopTimeout* run_timeout = + ScopedRunLoopTimeout::GetTimeoutForCurrentThread(); + EXPECT_NE(run_timeout, old_run_timeout); + EXPECT_TRUE(run_timeout); + if (!debug::BeingDebugged()) { + EXPECT_LT(run_timeout->timeout, TestTimeouts::test_launcher_timeout()); + } + static const RepeatingClosure& static_on_timeout = run_timeout->on_timeout; + EXPECT_FATAL_FAILURE(static_on_timeout.Run(), "RunLoop::Run() timed out"); + } + + EXPECT_EQ(ScopedRunLoopTimeout::GetTimeoutForCurrentThread(), + old_run_timeout); +} + +TEST_F(TaskEnvironmentTest, DescribePendingMainThreadTasks) { + TaskEnvironment task_environment; + ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing()); + + test::MockLog mock_log; + mock_log.StartCapturingLogs(); + + EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, + HasSubstr("task_environment_unittest.cc"))) + .WillOnce(Return(true)); + task_environment.DescribePendingMainThreadTasks(); + + task_environment.RunUntilIdle(); + + EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, + Not(HasSubstr("task_environment_unittest.cc")))) + .WillOnce(Return(true)); + task_environment.DescribePendingMainThreadTasks(); +} + +TEST_F(TaskEnvironmentTest, Basic) { + TaskEnvironment task_environment( + TaskEnvironment::TimeSource::MOCK_TIME, + TaskEnvironment::ThreadPoolExecutionMode::QUEUED); + + int counter = 0; + + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + BindOnce([](int* counter) { *counter += 1; }, Unretained(&counter))); + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + BindOnce([](int* counter) { *counter += 32; }, Unretained(&counter))); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce([](int* counter) { *counter += 256; }, Unretained(&counter)), + TimeDelta::FromSeconds(3)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce([](int* counter) { *counter += 64; }, Unretained(&counter)), + TimeDelta::FromSeconds(1)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce([](int* counter) { *counter += 1024; }, Unretained(&counter)), + TimeDelta::FromMinutes(20)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + BindOnce([](int* counter) { *counter += 4096; }, Unretained(&counter)), + TimeDelta::FromDays(20)); + + int expected_value = 0; + EXPECT_EQ(expected_value, counter); + task_environment.RunUntilIdle(); + expected_value += 1; + expected_value += 32; + EXPECT_EQ(expected_value, counter); + + task_environment.RunUntilIdle(); + EXPECT_EQ(expected_value, counter); + + task_environment.FastForwardBy(TimeDelta::FromSeconds(1)); + expected_value += 64; + EXPECT_EQ(expected_value, counter); + + task_environment.FastForwardBy(TimeDelta::FromSeconds(5)); + expected_value += 256; + EXPECT_EQ(expected_value, counter); + + task_environment.FastForwardUntilNoTasksRemain(); + expected_value += 1024; + expected_value += 4096; + EXPECT_EQ(expected_value, counter); +} + +TEST_F(TaskEnvironmentTest, RunLoopDriveable) { + TaskEnvironment task_environment( + TaskEnvironment::TimeSource::MOCK_TIME, + TaskEnvironment::ThreadPoolExecutionMode::QUEUED); + + int counter = 0; + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce([](int* counter) { *counter += 1; }, + Unretained(&counter))); + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce([](int* counter) { *counter += 32; }, + Unretained(&counter))); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 256; }, + Unretained(&counter)), + TimeDelta::FromSeconds(3)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 64; }, + Unretained(&counter)), + TimeDelta::FromSeconds(1)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 1024; }, + Unretained(&counter)), + TimeDelta::FromMinutes(20)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 4096; }, + Unretained(&counter)), + TimeDelta::FromDays(20)); + + int expected_value = 0; + EXPECT_EQ(expected_value, counter); + RunLoop().RunUntilIdle(); + expected_value += 1; + expected_value += 32; + EXPECT_EQ(expected_value, counter); + + RunLoop().RunUntilIdle(); + EXPECT_EQ(expected_value, counter); + + { + RunLoop run_loop; + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop.QuitClosure(), TimeDelta::FromSeconds(1)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 8192; }, + Unretained(&counter)), + TimeDelta::FromSeconds(1)); + + // The QuitClosure() should be ordered between the 64 and the 8192 + // increments and should preempt the latter. + run_loop.Run(); + expected_value += 64; + EXPECT_EQ(expected_value, counter); + + // Running until idle should process the 8192 increment whose delay has + // expired in the previous Run(). + RunLoop().RunUntilIdle(); + expected_value += 8192; + EXPECT_EQ(expected_value, counter); + } + + { + RunLoop run_loop; + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromSeconds(5)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 16384; }, + Unretained(&counter)), + TimeDelta::FromSeconds(5)); + + // The QuitWhenIdleClosure() shouldn't preempt equally delayed tasks and as + // such the 16384 increment should be processed before quitting. + run_loop.Run(); + expected_value += 256; + expected_value += 16384; + EXPECT_EQ(expected_value, counter); + } + + // Process the remaining tasks (note: do not mimic this elsewhere, + // TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() is a better API to + // do this, this is just done here for the purpose of extensively testing the + // RunLoop approach). + + // Disable Run() timeout here, otherwise we'll fast-forward to it before we + // reach the quit task. + ScopedDisableRunLoopTimeout disable_timeout; + + RunLoop run_loop; + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromDays(50)); + + run_loop.Run(); + expected_value += 1024; + expected_value += 4096; + EXPECT_EQ(expected_value, counter); +} + +TEST_F(TaskEnvironmentTest, CancelPendingTask) { + TaskEnvironment task_environment( + TaskEnvironment::TimeSource::MOCK_TIME, + TaskEnvironment::ThreadPoolExecutionMode::QUEUED); + + CancelableOnceClosure task1(BindOnce([]() {})); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task1.callback(), + TimeDelta::FromSeconds(1)); + EXPECT_TRUE(task_environment.MainThreadIsIdle()); + EXPECT_EQ(1u, task_environment.GetPendingMainThreadTaskCount()); + EXPECT_EQ(TimeDelta::FromSeconds(1), + task_environment.NextMainThreadPendingTaskDelay()); + EXPECT_TRUE(task_environment.MainThreadIsIdle()); + task1.Cancel(); + EXPECT_TRUE(task_environment.MainThreadIsIdle()); + EXPECT_EQ(TimeDelta::Max(), + task_environment.NextMainThreadPendingTaskDelay()); + + CancelableClosure task2(BindRepeating([]() {})); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task2.callback(), + TimeDelta::FromSeconds(1)); + task2.Cancel(); + EXPECT_EQ(0u, task_environment.GetPendingMainThreadTaskCount()); + + CancelableClosure task3(BindRepeating([]() {})); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task3.callback(), + TimeDelta::FromSeconds(1)); + task3.Cancel(); + EXPECT_EQ(TimeDelta::Max(), + task_environment.NextMainThreadPendingTaskDelay()); + + CancelableClosure task4(BindRepeating([]() {})); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task4.callback(), + TimeDelta::FromSeconds(1)); + task4.Cancel(); + EXPECT_TRUE(task_environment.MainThreadIsIdle()); +} + +TEST_F(TaskEnvironmentTest, CancelPendingImmediateTask) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + EXPECT_TRUE(task_environment.MainThreadIsIdle()); + + CancelableOnceClosure task1(BindOnce([]() {})); + ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, task1.callback()); + EXPECT_FALSE(task_environment.MainThreadIsIdle()); + + task1.Cancel(); + EXPECT_TRUE(task_environment.MainThreadIsIdle()); +} + +TEST_F(TaskEnvironmentTest, NoFastForwardToCancelledTask) { + TaskEnvironment task_environment( + TaskEnvironment::TimeSource::MOCK_TIME, + TaskEnvironment::ThreadPoolExecutionMode::QUEUED); + + TimeTicks start_time = task_environment.NowTicks(); + CancelableClosure task(BindRepeating([]() {})); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task.callback(), + TimeDelta::FromSeconds(1)); + EXPECT_EQ(TimeDelta::FromSeconds(1), + task_environment.NextMainThreadPendingTaskDelay()); + task.Cancel(); + task_environment.FastForwardUntilNoTasksRemain(); + EXPECT_EQ(start_time, task_environment.NowTicks()); +} + +TEST_F(TaskEnvironmentTest, NextTaskIsDelayed) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + EXPECT_FALSE(task_environment.NextTaskIsDelayed()); + CancelableClosure task(BindRepeating([]() {})); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, task.callback(), + TimeDelta::FromSeconds(1)); + EXPECT_TRUE(task_environment.NextTaskIsDelayed()); + task.Cancel(); + EXPECT_FALSE(task_environment.NextTaskIsDelayed()); + + ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, BindOnce([]() {}), + TimeDelta::FromSeconds(2)); + EXPECT_TRUE(task_environment.NextTaskIsDelayed()); + task_environment.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(task_environment.NextTaskIsDelayed()); + + ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, BindOnce([]() {})); + EXPECT_FALSE(task_environment.NextTaskIsDelayed()); +} + +TEST_F(TaskEnvironmentTest, NextMainThreadPendingTaskDelayWithImmediateTask) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + EXPECT_EQ(TimeDelta::Max(), + task_environment.NextMainThreadPendingTaskDelay()); + ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, BindOnce([]() {})); + EXPECT_EQ(TimeDelta(), task_environment.NextMainThreadPendingTaskDelay()); +} + +TEST_F(TaskEnvironmentTest, TimeSourceMockTimeAlsoMocksNow) { + TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); + + const TimeTicks start_ticks = task_environment.NowTicks(); + EXPECT_EQ(TimeTicks::Now(), start_ticks); + + const Time start_time = Time::Now(); + + constexpr TimeDelta kDelay = TimeDelta::FromSeconds(10); + task_environment.FastForwardBy(kDelay); + EXPECT_EQ(TimeTicks::Now(), start_ticks + kDelay); + EXPECT_EQ(Time::Now(), start_time + kDelay); +} + +TEST_F(TaskEnvironmentTest, SingleThread) { + SingleThreadTaskEnvironment task_environment; + EXPECT_THAT(ThreadPoolInstance::Get(), IsNull()); + + bool ran = false; + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindLambdaForTesting([&]() { ran = true; })); + RunLoop().RunUntilIdle(); + EXPECT_TRUE(ran); + + EXPECT_DCHECK_DEATH(ThreadPool::PostTask(FROM_HERE, {}, DoNothing())); +} + +// Verify that traits other than ThreadingMode can be applied to +// SingleThreadTaskEnvironment. +TEST_F(TaskEnvironmentTest, SingleThreadMockTime) { + SingleThreadTaskEnvironment task_environment( + TaskEnvironment::TimeSource::MOCK_TIME); + + const TimeTicks start_time = TimeTicks::Now(); + + constexpr TimeDelta kDelay = TimeDelta::FromSeconds(100); + + int counter = 0; + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::BindLambdaForTesting([&]() { counter += 1; }), kDelay); + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindLambdaForTesting([&]() { counter += 2; })); + + int expected_value = 0; + EXPECT_EQ(expected_value, counter); + + task_environment.RunUntilIdle(); + expected_value += 2; + EXPECT_EQ(expected_value, counter); + + task_environment.FastForwardUntilNoTasksRemain(); + expected_value += 1; + EXPECT_EQ(expected_value, counter); + EXPECT_EQ(TimeTicks::Now(), start_time + kDelay); +} + +#if defined(OS_WIN) +namespace { + +enum class ApartmentType { + kSTA, + kMTA, +}; + +void InitializeSTAApartment() { + base::win::ScopedCOMInitializer initializer; + EXPECT_TRUE(initializer.Succeeded()); +} + +void InitializeMTAApartment() { + base::win::ScopedCOMInitializer initializer( + base::win::ScopedCOMInitializer::kMTA); + EXPECT_TRUE(initializer.Succeeded()); +} + +void InitializeCOMOnWorker( + TaskEnvironment::ThreadPoolCOMEnvironment com_environment, + ApartmentType apartment_type) { + TaskEnvironment task_environment(com_environment); + ThreadPool::PostTask(FROM_HERE, BindOnce(apartment_type == ApartmentType::kSTA + ? &InitializeSTAApartment + : &InitializeMTAApartment)); + task_environment.RunUntilIdle(); +} + +} // namespace + +TEST_F(TaskEnvironmentTest, DefaultCOMEnvironment) { + // Attempt to initialize an MTA COM apartment. Expect this to succeed since + // the thread is already in an MTA apartment. + InitializeCOMOnWorker(TaskEnvironment::ThreadPoolCOMEnvironment::DEFAULT, + ApartmentType::kMTA); + + // Attempt to initialize an STA COM apartment. Expect this to fail since the + // thread is already in an MTA apartment. + EXPECT_DCHECK_DEATH(InitializeCOMOnWorker( + TaskEnvironment::ThreadPoolCOMEnvironment::DEFAULT, ApartmentType::kSTA)); +} + +TEST_F(TaskEnvironmentTest, NoCOMEnvironment) { + // Attempt to initialize both MTA and STA COM apartments. Both should succeed + // when the thread is not already in an apartment. + InitializeCOMOnWorker(TaskEnvironment::ThreadPoolCOMEnvironment::NONE, + ApartmentType::kMTA); + InitializeCOMOnWorker(TaskEnvironment::ThreadPoolCOMEnvironment::NONE, + ApartmentType::kSTA); +} +#endif + +} // namespace test +} // namespace base diff --git a/chromium/base/test/task_runner_test_template.cc b/chromium/base/test/task_runner_test_template.cc new file mode 100644 index 00000000000..e92e8467c6c --- /dev/null +++ b/chromium/base/test/task_runner_test_template.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/task_runner_test_template.h" + +namespace base { + +namespace test { + +TaskTracker::TaskTracker() : task_runs_(0), task_runs_cv_(&lock_) {} + +TaskTracker::~TaskTracker() = default; + +RepeatingClosure TaskTracker::WrapTask(RepeatingClosure task, int i) { + return BindRepeating(&TaskTracker::RunTask, this, std::move(task), i); +} + +void TaskTracker::RunTask(RepeatingClosure task, int i) { + AutoLock lock(lock_); + if (!task.is_null()) { + task.Run(); + } + ++task_run_counts_[i]; + ++task_runs_; + task_runs_cv_.Signal(); +} + +std::map<int, int> TaskTracker::GetTaskRunCounts() const { + AutoLock lock(lock_); + return task_run_counts_; +} + +void TaskTracker::WaitForCompletedTasks(int count) { + AutoLock lock(lock_); + while (task_runs_ < count) + task_runs_cv_.Wait(); +} + +} // namespace test + +} // namespace base diff --git a/chromium/base/test/task_runner_test_template.h b/chromium/base/test/task_runner_test_template.h new file mode 100644 index 00000000000..37acde16686 --- /dev/null +++ b/chromium/base/test/task_runner_test_template.h @@ -0,0 +1,170 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines tests that implementations of TaskRunner should +// pass in order to be conformant, as well as test cases for optional behavior. +// Here's how you use it to test your implementation. +// +// Say your class is called MyTaskRunner. Then you need to define a +// class called MyTaskRunnerTestDelegate in my_task_runner_unittest.cc +// like this: +// +// class MyTaskRunnerTestDelegate { +// public: +// // Tasks posted to the task runner after this and before +// // StopTaskRunner() is called is called should run successfully. +// void StartTaskRunner() { +// ... +// } +// +// // Should return the task runner implementation. Only called +// // after StartTaskRunner and before StopTaskRunner. +// scoped_refptr<MyTaskRunner> GetTaskRunner() { +// ... +// } +// +// // Stop the task runner and make sure all tasks posted before +// // this is called are run. Caveat: delayed tasks are not run, + // they're simply deleted. +// void StopTaskRunner() { +// ... +// } +// }; +// +// The TaskRunnerTest test harness will have a member variable of +// this delegate type and will call its functions in the various +// tests. +// +// Then you simply #include this file as well as gtest.h and add the +// following statement to my_task_runner_unittest.cc: +// +// INSTANTIATE_TYPED_TEST_SUITE_P( +// MyTaskRunner, TaskRunnerTest, MyTaskRunnerTestDelegate); +// +// Easy! + +#ifndef BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_ +#define BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_ + +#include <cstddef> +#include <map> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace test { + +// Utility class that keeps track of how many times particular tasks +// are run. +class TaskTracker : public RefCountedThreadSafe<TaskTracker> { + public: + TaskTracker(); + + // Returns a closure that runs the given task and increments the run + // count of |i| by one. |task| may be null. It is guaranteed that + // only one task wrapped by a given tracker will be run at a time. + RepeatingClosure WrapTask(RepeatingClosure task, int i); + + std::map<int, int> GetTaskRunCounts() const; + + // Returns after the tracker observes a total of |count| task completions. + void WaitForCompletedTasks(int count); + + private: + friend class RefCountedThreadSafe<TaskTracker>; + + ~TaskTracker(); + + void RunTask(RepeatingClosure task, int i); + + mutable Lock lock_; + std::map<int, int> task_run_counts_; + int task_runs_; + ConditionVariable task_runs_cv_; + + DISALLOW_COPY_AND_ASSIGN(TaskTracker); +}; + +} // namespace test + +template <typename TaskRunnerTestDelegate> +class TaskRunnerTest : public testing::Test { + protected: + TaskRunnerTest() : task_tracker_(base::MakeRefCounted<test::TaskTracker>()) {} + + const scoped_refptr<test::TaskTracker> task_tracker_; + TaskRunnerTestDelegate delegate_; +}; + +TYPED_TEST_SUITE_P(TaskRunnerTest); + +// We can't really test much, since TaskRunner provides very few +// guarantees. + +// Post a bunch of tasks to the task runner. They should all +// complete. +TYPED_TEST_P(TaskRunnerTest, Basic) { + std::map<int, int> expected_task_run_counts; + + this->delegate_.StartTaskRunner(); + scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner(); + // Post each ith task i+1 times. + for (int i = 0; i < 20; ++i) { + RepeatingClosure ith_task = + this->task_tracker_->WrapTask(RepeatingClosure(), i); + for (int j = 0; j < i + 1; ++j) { + task_runner->PostTask(FROM_HERE, ith_task); + ++expected_task_run_counts[i]; + } + } + this->delegate_.StopTaskRunner(); + + EXPECT_EQ(expected_task_run_counts, + this->task_tracker_->GetTaskRunCounts()); +} + +// Post a bunch of delayed tasks to the task runner. They should all +// complete. +TYPED_TEST_P(TaskRunnerTest, Delayed) { + std::map<int, int> expected_task_run_counts; + int expected_total_tasks = 0; + + this->delegate_.StartTaskRunner(); + scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner(); + // Post each ith task i+1 times with delays from 0-i. + for (int i = 0; i < 20; ++i) { + RepeatingClosure ith_task = + this->task_tracker_->WrapTask(RepeatingClosure(), i); + for (int j = 0; j < i + 1; ++j) { + task_runner->PostDelayedTask( + FROM_HERE, ith_task, base::TimeDelta::FromMilliseconds(j)); + ++expected_task_run_counts[i]; + ++expected_total_tasks; + } + } + this->task_tracker_->WaitForCompletedTasks(expected_total_tasks); + this->delegate_.StopTaskRunner(); + + EXPECT_EQ(expected_task_run_counts, + this->task_tracker_->GetTaskRunCounts()); +} + +// The TaskRunnerTest test case verifies behaviour that is expected from a +// task runner in order to be conformant. +REGISTER_TYPED_TEST_SUITE_P(TaskRunnerTest, Basic, Delayed); + +} // namespace base + +#endif // BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_ diff --git a/chromium/base/test/test_child_process.cc b/chromium/base/test/test_child_process.cc new file mode 100644 index 00000000000..ce158561163 --- /dev/null +++ b/chromium/base/test/test_child_process.cc @@ -0,0 +1,43 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// Simple testing command, used to exercise child process launcher calls. +// +// Usage: +// echo_test_helper [-x exit_code] arg0 arg1 arg2... +// Prints arg0..n to stdout with space delimiters between args, +// returning "exit_code" if -x is specified. +// +// echo_test_helper -e env_var +// Prints the environmental variable |env_var| to stdout. +int main(int argc, char** argv) { + if (strcmp(argv[1], "-e") == 0) { + if (argc != 3) { + return 1; + } + + const char* env = getenv(argv[2]); + if (env != NULL) { + printf("%s", env); + } + } else { + int return_code = 0; + int start_idx = 1; + + if (strcmp(argv[1], "-x") == 0) { + return_code = atoi(argv[2]); + start_idx = 3; + } + + for (int i = start_idx; i < argc; ++i) { + printf((i < argc - 1 ? "%s " : "%s"), argv[i]); + } + + return return_code; + } +} diff --git a/chromium/base/test/test_discardable_memory_allocator.cc b/chromium/base/test/test_discardable_memory_allocator.cc new file mode 100644 index 00000000000..26bc5b036a8 --- /dev/null +++ b/chromium/base/test/test_discardable_memory_allocator.cc @@ -0,0 +1,67 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_discardable_memory_allocator.h" + +#include <cstdint> +#include <cstring> + +#include "base/check.h" +#include "base/memory/discardable_memory.h" +#include "base/memory/ptr_util.h" + +namespace base { +namespace { + +class DiscardableMemoryImpl : public DiscardableMemory { + public: + explicit DiscardableMemoryImpl(size_t size) + : data_(new uint8_t[size]), size_(size) {} + + // Overridden from DiscardableMemory: + bool Lock() override { + DCHECK(!is_locked_); + is_locked_ = true; + return false; + } + + void Unlock() override { + DCHECK(is_locked_); + is_locked_ = false; + // Force eviction to catch clients not correctly checking the return value + // of Lock(). + memset(data_.get(), 0, size_); + } + + void* data() const override { + DCHECK(is_locked_); + return data_.get(); + } + + void DiscardForTesting() override {} + + trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump( + const char* name, + trace_event::ProcessMemoryDump* pmd) const override { + return nullptr; + } + + private: + bool is_locked_ = true; + std::unique_ptr<uint8_t[]> data_; + size_t size_; +}; + +} // namespace + +std::unique_ptr<DiscardableMemory> +TestDiscardableMemoryAllocator::AllocateLockedDiscardableMemory(size_t size) { + return std::make_unique<DiscardableMemoryImpl>(size); +} + +size_t TestDiscardableMemoryAllocator::GetBytesAllocated() const { + return 0U; +} + +} // namespace base diff --git a/chromium/base/test/test_discardable_memory_allocator.h b/chromium/base/test/test_discardable_memory_allocator.h new file mode 100644 index 00000000000..ba9719ada4a --- /dev/null +++ b/chromium/base/test/test_discardable_memory_allocator.h @@ -0,0 +1,38 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_DISCARDABLE_MEMORY_ALLOCATOR_H_ +#define BASE_TEST_TEST_DISCARDABLE_MEMORY_ALLOCATOR_H_ + +#include <stddef.h> + +#include "base/macros.h" +#include "base/memory/discardable_memory_allocator.h" + +namespace base { + +// TestDiscardableMemoryAllocator is a simple DiscardableMemoryAllocator +// implementation that can be used for testing. It allocates one-shot +// DiscardableMemory instances backed by heap memory. +class TestDiscardableMemoryAllocator : public DiscardableMemoryAllocator { + public: + TestDiscardableMemoryAllocator() = default; + + // Overridden from DiscardableMemoryAllocator: + std::unique_ptr<DiscardableMemory> AllocateLockedDiscardableMemory( + size_t size) override; + + size_t GetBytesAllocated() const override; + + void ReleaseFreeMemory() override { + // Do nothing since it is backed by heap memory. + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestDiscardableMemoryAllocator); +}; + +} // namespace base + +#endif // BASE_TEST_TEST_DISCARDABLE_MEMORY_ALLOCATOR_H_ diff --git a/chromium/base/test/test_file_util.cc b/chromium/base/test/test_file_util.cc new file mode 100644 index 00000000000..8dafc58a7df --- /dev/null +++ b/chromium/base/test/test_file_util.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_file_util.h" + +#include "base/test/test_timeouts.h" +#include "base/threading/platform_thread.h" + +namespace base { + +bool EvictFileFromSystemCacheWithRetry(const FilePath& path) { + const int kCycles = 10; + const TimeDelta kDelay = TestTimeouts::action_timeout() / kCycles; + for (int i = 0; i < kCycles; i++) { + if (EvictFileFromSystemCache(path)) + return true; + PlatformThread::Sleep(kDelay); + } + return false; +} + +} // namespace base diff --git a/chromium/base/test/test_file_util.h b/chromium/base/test/test_file_util.h new file mode 100644 index 00000000000..f9951b0fb14 --- /dev/null +++ b/chromium/base/test/test_file_util.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_FILE_UTIL_H_ +#define BASE_TEST_TEST_FILE_UTIL_H_ + +// File utility functions used only by tests. + +#include <stddef.h> + +#include <string> + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "build/build_config.h" + +#if defined(OS_ANDROID) +#include <jni.h> +#endif + +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace base { + +// Clear a specific file from the system cache like EvictFileFromSystemCache, +// but on failure it will sleep and retry. On the Windows buildbots, eviction +// can fail if the file is marked in use, and this will throw off timings that +// rely on uncached files. +bool EvictFileFromSystemCacheWithRetry(const FilePath& file); + +// Wrapper over base::Delete. On Windows repeatedly invokes Delete in case +// of failure to workaround Windows file locking semantics. Returns true on +// success. +bool DieFileDie(const FilePath& file, bool recurse); + +// Synchronize all the dirty pages from the page cache to disk (on POSIX +// systems). The Windows analogy for this operation is to 'Flush file buffers'. +// Note: This is currently implemented as a no-op on Windows. +void SyncPageCacheToDisk(); + +// Clear a specific file from the system cache. After this call, trying +// to access this file will result in a cold load from the hard drive. +bool EvictFileFromSystemCache(const FilePath& file); + +#if defined(OS_WIN) +// Deny |permission| on the file |path| for the current user. |permission| is an +// ACCESS_MASK structure which is defined in +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa374892.aspx +// Refer to https://msdn.microsoft.com/en-us/library/aa822867.aspx for a list of +// possible values. +bool DenyFilePermission(const FilePath& path, DWORD permission); +#endif // defined(OS_WIN) + +// For testing, make the file unreadable or unwritable. +// In POSIX, this does not apply to the root user. +bool MakeFileUnreadable(const FilePath& path) WARN_UNUSED_RESULT; +bool MakeFileUnwritable(const FilePath& path) WARN_UNUSED_RESULT; + +// Saves the current permissions for a path, and restores it on destruction. +class FilePermissionRestorer { + public: + explicit FilePermissionRestorer(const FilePath& path); + ~FilePermissionRestorer(); + + private: + const FilePath path_; + void* info_; // The opaque stored permission information. + size_t length_; // The length of the stored permission information. + + DISALLOW_COPY_AND_ASSIGN(FilePermissionRestorer); +}; + +#if defined(OS_ANDROID) +// Insert an image file into the MediaStore, and retrieve the content URI for +// testing purpose. +FilePath InsertImageIntoMediaStore(const FilePath& path); +#endif // defined(OS_ANDROID) + +} // namespace base + +#endif // BASE_TEST_TEST_FILE_UTIL_H_ diff --git a/chromium/base/test/test_file_util_android.cc b/chromium/base/test/test_file_util_android.cc new file mode 100644 index 00000000000..fc0beb3faa8 --- /dev/null +++ b/chromium/base/test/test_file_util_android.cc @@ -0,0 +1,26 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_file_util.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/test/base_unittests_jni_headers/ContentUriTestUtils_jni.h" + +using base::android::ScopedJavaLocalRef; + +namespace base { + +FilePath InsertImageIntoMediaStore(const FilePath& path) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_path = + base::android::ConvertUTF8ToJavaString(env, path.value()); + ScopedJavaLocalRef<jstring> j_uri = + Java_ContentUriTestUtils_insertImageIntoMediaStore(env, j_path); + std::string uri = base::android::ConvertJavaStringToUTF8(j_uri); + return FilePath(uri); +} + +} // namespace base diff --git a/chromium/base/test/test_file_util_linux.cc b/chromium/base/test/test_file_util_linux.cc new file mode 100644 index 00000000000..85fb69fdfbe --- /dev/null +++ b/chromium/base/test/test_file_util_linux.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_file_util.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#if defined(OS_ANDROID) +#include <asm/unistd.h> +#include <errno.h> +#include <linux/fadvise.h> +#include <sys/syscall.h> +#endif + +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/notreached.h" + +namespace base { + +// Inconveniently, the NDK doesn't provide for posix_fadvise +// until native API level = 21, which we don't use yet, so provide a wrapper, at +// least on ARM32 +#if defined(OS_ANDROID) && __ANDROID_API__ < 21 + +namespace { +int posix_fadvise(int fd, off_t offset, off_t len, int advice) { +#if defined(ARCH_CPU_ARMEL) + // Note that the syscall argument order on ARM is different from the C + // function; this is helpfully documented in the Linux posix_fadvise manpage. + return syscall(__NR_arm_fadvise64_64, fd, advice, + 0, // Upper 32-bits for offset + offset, + 0, // Upper 32-bits for length + len); +#endif + NOTIMPLEMENTED(); + return ENOSYS; +} + +} // namespace + +#endif // OS_ANDROID + +bool EvictFileFromSystemCache(const FilePath& file) { + ScopedFD fd(open(file.value().c_str(), O_RDONLY)); + if (!fd.is_valid()) + return false; + if (fdatasync(fd.get()) != 0) + return false; + if (posix_fadvise(fd.get(), 0, 0, POSIX_FADV_DONTNEED) != 0) + return false; + return true; +} + +} // namespace base diff --git a/chromium/base/test/test_file_util_mac.cc b/chromium/base/test/test_file_util_mac.cc new file mode 100644 index 00000000000..174a31db254 --- /dev/null +++ b/chromium/base/test/test_file_util_mac.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_file_util.h" + +#include <sys/mman.h> +#include <errno.h> +#include <stdint.h> + +#include "base/files/file_util.h" +#include "base/files/memory_mapped_file.h" +#include "base/logging.h" + +namespace base { + +bool EvictFileFromSystemCache(const FilePath& file) { + // There aren't any really direct ways to purge a file from the UBC. From + // talking with Amit Singh, the safest is to mmap the file with MAP_FILE (the + // default) + MAP_SHARED, then do an msync to invalidate the memory. The next + // open should then have to load the file from disk. + + int64_t length; + if (!GetFileSize(file, &length)) { + DLOG(ERROR) << "failed to get size of " << file.value(); + return false; + } + + // When a file is empty, we do not need to evict it from cache. + // In fact, an attempt to map it to memory will result in error. + if (length == 0) { + DLOG(WARNING) << "file size is zero, will not attempt to map to memory"; + return true; + } + + MemoryMappedFile mapped_file; + if (!mapped_file.Initialize(file)) { + DLOG(WARNING) << "failed to memory map " << file.value(); + return false; + } + + if (msync(const_cast<uint8_t*>(mapped_file.data()), mapped_file.length(), + MS_INVALIDATE) != 0) { + DLOG(WARNING) << "failed to invalidate memory map of " << file.value() + << ", errno: " << errno; + return false; + } + + return true; +} + +} // namespace base diff --git a/chromium/base/test/test_file_util_posix.cc b/chromium/base/test/test_file_util_posix.cc new file mode 100644 index 00000000000..02c214a4214 --- /dev/null +++ b/chromium/base/test/test_file_util_posix.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_file_util.h" + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> + +#include "base/check_op.h" +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/notreached.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" + +namespace base { + +namespace { + +// Deny |permission| on the file |path|. +bool DenyFilePermission(const FilePath& path, mode_t permission) { + stat_wrapper_t stat_buf; + if (File::Stat(path.value().c_str(), &stat_buf) != 0) + return false; + stat_buf.st_mode &= ~permission; + + int rv = HANDLE_EINTR(chmod(path.value().c_str(), stat_buf.st_mode)); + return rv == 0; +} + +// Gets a blob indicating the permission information for |path|. +// |length| is the length of the blob. Zero on failure. +// Returns the blob pointer, or NULL on failure. +void* GetPermissionInfo(const FilePath& path, size_t* length) { + DCHECK(length); + *length = 0; + + stat_wrapper_t stat_buf; + if (File::Stat(path.value().c_str(), &stat_buf) != 0) + return nullptr; + + *length = sizeof(mode_t); + mode_t* mode = new mode_t; + *mode = stat_buf.st_mode & ~S_IFMT; // Filter out file/path kind. + + return mode; +} + +// Restores the permission information for |path|, given the blob retrieved +// using |GetPermissionInfo()|. +// |info| is the pointer to the blob. +// |length| is the length of the blob. +// Either |info| or |length| may be NULL/0, in which case nothing happens. +bool RestorePermissionInfo(const FilePath& path, void* info, size_t length) { + if (!info || (length == 0)) + return false; + + DCHECK_EQ(sizeof(mode_t), length); + mode_t* mode = reinterpret_cast<mode_t*>(info); + + int rv = HANDLE_EINTR(chmod(path.value().c_str(), *mode)); + + delete mode; + + return rv == 0; +} + +} // namespace + +bool DieFileDie(const FilePath& file, bool recurse) { + // There is no need to workaround Windows problems on POSIX. + // Just pass-through. + return DeleteFile(file, recurse); +} + +void SyncPageCacheToDisk() { + // On Linux (and Android) the sync(2) call waits for I/O completions. + sync(); +} + +#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +bool EvictFileFromSystemCache(const FilePath& file) { + // There doesn't seem to be a POSIX way to cool the disk cache. + NOTIMPLEMENTED(); + return false; +} +#endif + +bool MakeFileUnreadable(const FilePath& path) { + return DenyFilePermission(path, S_IRUSR | S_IRGRP | S_IROTH); +} + +bool MakeFileUnwritable(const FilePath& path) { + return DenyFilePermission(path, S_IWUSR | S_IWGRP | S_IWOTH); +} + +FilePermissionRestorer::FilePermissionRestorer(const FilePath& path) + : path_(path), info_(nullptr), length_(0) { + info_ = GetPermissionInfo(path_, &length_); + DCHECK(info_ != nullptr); + DCHECK_NE(0u, length_); +} + +FilePermissionRestorer::~FilePermissionRestorer() { + if (!RestorePermissionInfo(path_, info_, length_)) + NOTREACHED(); +} + +} // namespace base diff --git a/chromium/base/test/test_file_util_win.cc b/chromium/base/test/test_file_util_win.cc new file mode 100644 index 00000000000..d2a861b23aa --- /dev/null +++ b/chromium/base/test/test_file_util_win.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_file_util.h" + +#include <aclapi.h> +#include <stddef.h> +#include <wchar.h> +#include <windows.h> + +#include <memory> +#include <vector> + +#include "base/check_op.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/memory/ptr_util.h" +#include "base/notreached.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/threading/platform_thread.h" +#include "base/win/scoped_handle.h" +#include "base/win/shlwapi.h" + +namespace base { + +namespace { + +struct PermissionInfo { + PSECURITY_DESCRIPTOR security_descriptor; + ACL dacl; +}; + +// Gets a blob indicating the permission information for |path|. +// |length| is the length of the blob. Zero on failure. +// Returns the blob pointer, or NULL on failure. +void* GetPermissionInfo(const FilePath& path, size_t* length) { + DCHECK(length != NULL); + *length = 0; + PACL dacl = NULL; + PSECURITY_DESCRIPTOR security_descriptor; + if (GetNamedSecurityInfo(path.value().c_str(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, NULL, NULL, &dacl, NULL, + &security_descriptor) != ERROR_SUCCESS) { + return NULL; + } + DCHECK(dacl != NULL); + + *length = sizeof(PSECURITY_DESCRIPTOR) + dacl->AclSize; + PermissionInfo* info = reinterpret_cast<PermissionInfo*>(new char[*length]); + info->security_descriptor = security_descriptor; + memcpy(&info->dacl, dacl, dacl->AclSize); + + return info; +} + +// Restores the permission information for |path|, given the blob retrieved +// using |GetPermissionInfo()|. +// |info| is the pointer to the blob. +// |length| is the length of the blob. +// Either |info| or |length| may be NULL/0, in which case nothing happens. +bool RestorePermissionInfo(const FilePath& path, void* info, size_t length) { + if (!info || !length) + return false; + + PermissionInfo* perm = reinterpret_cast<PermissionInfo*>(info); + + DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()), + SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + NULL, NULL, &perm->dacl, NULL); + LocalFree(perm->security_descriptor); + + char* char_array = reinterpret_cast<char*>(info); + delete [] char_array; + + return rc == ERROR_SUCCESS; +} + +std::unique_ptr<wchar_t[]> ToCStr(const std::basic_string<wchar_t>& str) { + size_t size = str.size() + 1; + std::unique_ptr<wchar_t[]> ptr = std::make_unique<wchar_t[]>(size); + wcsncpy(ptr.get(), str.c_str(), size); + return ptr; +} + +} // namespace + +bool DieFileDie(const FilePath& file, bool recurse) { + // It turns out that to not induce flakiness a long timeout is needed. + const int kIterations = 25; + const TimeDelta kTimeout = TimeDelta::FromSeconds(10) / kIterations; + + if (!PathExists(file)) + return true; + + // Sometimes Delete fails, so try a few more times. Divide the timeout + // into short chunks, so that if a try succeeds, we won't delay the test + // for too long. + for (int i = 0; i < kIterations; ++i) { + if (DeleteFile(file, recurse)) + return true; + PlatformThread::Sleep(kTimeout); + } + return false; +} + +void SyncPageCacheToDisk() { + // Approximating this with noop. The proper implementation would require + // administrator privilege: + // https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-flushfilebuffers +} + +bool EvictFileFromSystemCache(const FilePath& file) { + win::ScopedHandle file_handle( + CreateFile(file.value().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL)); + if (!file_handle.IsValid()) + return false; + + // Re-write the file time information to trigger cache eviction for the file. + // This function previously overwrote the entire file without buffering, but + // local experimentation validates this simplified and *much* faster approach: + // [1] Sysinternals RamMap no longer lists these files as cached afterwards. + // [2] Telemetry performance test startup.cold.blank_page reports sane values. + BY_HANDLE_FILE_INFORMATION bhi = {0}; + CHECK(::GetFileInformationByHandle(file_handle.Get(), &bhi)); + CHECK(::SetFileTime(file_handle.Get(), &bhi.ftCreationTime, + &bhi.ftLastAccessTime, &bhi.ftLastWriteTime)); + return true; +} + +// Deny |permission| on the file |path|, for the current user. +bool DenyFilePermission(const FilePath& path, DWORD permission) { + PACL old_dacl; + PSECURITY_DESCRIPTOR security_descriptor; + + std::unique_ptr<TCHAR[]> path_ptr = ToCStr(path.value().c_str()); + if (GetNamedSecurityInfo(path_ptr.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, nullptr, nullptr, + &old_dacl, nullptr, + &security_descriptor) != ERROR_SUCCESS) { + return false; + } + + std::unique_ptr<TCHAR[]> current_user = ToCStr(std::wstring(L"CURRENT_USER")); + EXPLICIT_ACCESS new_access = { + permission, + DENY_ACCESS, + 0, + {nullptr, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_NAME, TRUSTEE_IS_USER, + current_user.get()}}; + + PACL new_dacl; + if (SetEntriesInAcl(1, &new_access, old_dacl, &new_dacl) != ERROR_SUCCESS) { + LocalFree(security_descriptor); + return false; + } + + DWORD rc = SetNamedSecurityInfo(path_ptr.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, nullptr, nullptr, + new_dacl, nullptr); + LocalFree(security_descriptor); + LocalFree(new_dacl); + + return rc == ERROR_SUCCESS; +} + +bool MakeFileUnreadable(const FilePath& path) { + return DenyFilePermission(path, GENERIC_READ); +} + +bool MakeFileUnwritable(const FilePath& path) { + return DenyFilePermission(path, GENERIC_WRITE); +} + +FilePermissionRestorer::FilePermissionRestorer(const FilePath& path) + : path_(path), info_(NULL), length_(0) { + info_ = GetPermissionInfo(path_, &length_); + DCHECK(info_ != NULL); + DCHECK_NE(0u, length_); +} + +FilePermissionRestorer::~FilePermissionRestorer() { + if (!RestorePermissionInfo(path_, info_, length_)) + NOTREACHED(); +} + +} // namespace base diff --git a/chromium/base/test/test_io_thread.cc b/chromium/base/test/test_io_thread.cc new file mode 100644 index 00000000000..15e4fe015c6 --- /dev/null +++ b/chromium/base/test/test_io_thread.cc @@ -0,0 +1,45 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_io_thread.h" + +#include "base/check.h" +#include "base/message_loop/message_pump_type.h" + +namespace base { + +TestIOThread::TestIOThread(Mode mode) + : io_thread_("test_io_thread"), io_thread_started_(false) { + switch (mode) { + case kAutoStart: + Start(); + return; + case kManualStart: + return; + } + CHECK(false) << "Invalid mode"; +} + +TestIOThread::~TestIOThread() { + Stop(); +} + +void TestIOThread::Start() { + CHECK(!io_thread_started_); + io_thread_started_ = true; + CHECK(io_thread_.StartWithOptions( + base::Thread::Options(base::MessagePumpType::IO, 0))); +} + +void TestIOThread::Stop() { + // Note: It's okay to call |Stop()| even if the thread isn't running. + io_thread_.Stop(); + io_thread_started_ = false; +} + +void TestIOThread::PostTask(const Location& from_here, base::OnceClosure task) { + task_runner()->PostTask(from_here, std::move(task)); +} + +} // namespace base diff --git a/chromium/base/test/test_io_thread.h b/chromium/base/test/test_io_thread.h new file mode 100644 index 00000000000..c72d4fa509c --- /dev/null +++ b/chromium/base/test/test_io_thread.h @@ -0,0 +1,56 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_IO_THREAD_H_ +#define BASE_TEST_TEST_IO_THREAD_H_ + +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/task_runner.h" +#include "base/threading/thread.h" +#include "base/time/time.h" + +namespace base { + +// Create and run an IO thread with a MessageLoop, and +// making the MessageLoop accessible from its client. +// It also provides some ideomatic API like PostTaskAndWait(). +// +// This API is not thread-safe: +// - Start()/Stop() should only be called from the main (creation) thread. +// - PostTask()/message_loop()/task_runner() are also safe to call from the +// underlying thread itself (to post tasks from other threads: get the +// task_runner() from the main thread first, it is then safe to pass _it_ +// around). +class TestIOThread { + public: + enum Mode { kAutoStart, kManualStart }; + explicit TestIOThread(Mode mode); + // Stops the I/O thread if necessary. + ~TestIOThread(); + + // After Stop(), Start() may be called again to start a new I/O thread. + // Stop() may be called even when the I/O thread is not started. + void Start(); + void Stop(); + + // Post |task| to the IO thread. + void PostTask(const Location& from_here, base::OnceClosure task); + + scoped_refptr<SingleThreadTaskRunner> task_runner() { + return io_thread_.task_runner(); + } + + private: + base::Thread io_thread_; + bool io_thread_started_; + + DISALLOW_COPY_AND_ASSIGN(TestIOThread); +}; + +} // namespace base + +#endif // BASE_TEST_TEST_IO_THREAD_H_ diff --git a/chromium/base/test/test_listener_ios.h b/chromium/base/test/test_listener_ios.h new file mode 100644 index 00000000000..c312250065d --- /dev/null +++ b/chromium/base/test/test_listener_ios.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_LISTENER_IOS_H_ +#define BASE_TEST_TEST_LISTENER_IOS_H_ + +namespace base { +namespace test_listener_ios { + +// Register an IOSRunLoopListener. +void RegisterTestEndListener(); + +} // namespace test_listener_ios +} // namespace base + +#endif // BASE_TEST_TEST_LISTENER_IOS_H_ diff --git a/chromium/base/test/test_listener_ios.mm b/chromium/base/test/test_listener_ios.mm new file mode 100644 index 00000000000..54aa9acb6d0 --- /dev/null +++ b/chromium/base/test/test_listener_ios.mm @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_listener_ios.h" + +#import <Foundation/Foundation.h> + +#include "testing/gtest/include/gtest/gtest.h" + +// The iOS watchdog timer will kill an app that doesn't spin the main event +// loop often enough. This uses a Gtest TestEventListener to spin the current +// loop after each test finishes. However, if any individual test takes too +// long, it is still possible that the app will get killed. + +namespace { + +class IOSRunLoopListener : public testing::EmptyTestEventListener { + public: + virtual void OnTestEnd(const testing::TestInfo& test_info); +}; + +void IOSRunLoopListener::OnTestEnd(const testing::TestInfo& test_info) { + @autoreleasepool { + // At the end of the test, spin the default loop for a moment. + NSDate* stop_date = [NSDate dateWithTimeIntervalSinceNow:0.001]; + [[NSRunLoop currentRunLoop] runUntilDate:stop_date]; + } +} + +} // namespace + + +namespace base { +namespace test_listener_ios { + +void RegisterTestEndListener() { + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new IOSRunLoopListener); +} + +} // namespace test_listener_ios +} // namespace base diff --git a/chromium/base/test/test_message_loop.cc b/chromium/base/test/test_message_loop.cc new file mode 100644 index 00000000000..14f60b0ca02 --- /dev/null +++ b/chromium/base/test/test_message_loop.cc @@ -0,0 +1,50 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_message_loop.h" + +#include "base/compiler_specific.h" +#include "base/message_loop/message_pump_type.h" +#include "base/notreached.h" +#include "base/run_loop.h" +#include "base/test/task_environment.h" +#include "build/build_config.h" + +namespace base { + +namespace { + +test::SingleThreadTaskEnvironment::MainThreadType GetMainThreadType( + MessagePumpType type) { + switch (type) { + case MessagePumpType::DEFAULT: + return test::SingleThreadTaskEnvironment::MainThreadType::DEFAULT; + case MessagePumpType::IO: + return test::SingleThreadTaskEnvironment::MainThreadType::IO; + case MessagePumpType::UI: + return test::SingleThreadTaskEnvironment::MainThreadType::UI; + case MessagePumpType::CUSTOM: +#if defined(OS_ANDROID) + case MessagePumpType::JAVA: +#elif defined(OS_MACOSX) + case MessagePumpType::NS_RUNLOOP: +#elif defined(OS_WIN) + case MessagePumpType::UI_WITH_WM_QUIT_SUPPORT: +#endif + NOTREACHED(); + return test::SingleThreadTaskEnvironment::MainThreadType::DEFAULT; + } +} +} // namespace + +TestMessageLoop::TestMessageLoop() = default; + +TestMessageLoop::TestMessageLoop(MessagePumpType type) + : task_environment_(GetMainThreadType(type)) {} + +TestMessageLoop::~TestMessageLoop() { + RunLoop().RunUntilIdle(); +} + +} // namespace base diff --git a/chromium/base/test/test_message_loop.h b/chromium/base/test/test_message_loop.h new file mode 100644 index 00000000000..3be2ea5c7a2 --- /dev/null +++ b/chromium/base/test/test_message_loop.h @@ -0,0 +1,39 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_MESSAGE_LOOP_H_ +#define BASE_TEST_TEST_MESSAGE_LOOP_H_ + +#include "base/message_loop/message_pump_type.h" +#include "base/single_thread_task_runner.h" +#include "base/test/task_environment.h" + +namespace base { + +// TestMessageLoop is a convenience class for unittests that need to create a +// message loop without a real thread backing it. For most tests, +// it is sufficient to just instantiate TestMessageLoop as a member variable. +// +// TestMessageLoop will attempt to drain the underlying MessageLoop on +// destruction for clean teardown of tests. +// +// TODO(b/891670): Get rid of this and migrate users to +// SingleThreadTaskEnvironment +class TestMessageLoop { + public: + TestMessageLoop(); + explicit TestMessageLoop(MessagePumpType type); + ~TestMessageLoop(); + + scoped_refptr<SingleThreadTaskRunner> task_runner() { + return task_environment_.GetMainThreadTaskRunner(); + } + + private: + test::SingleThreadTaskEnvironment task_environment_; +}; + +} // namespace base + +#endif // BASE_TEST_TEST_MESSAGE_LOOP_H_ diff --git a/chromium/base/test/test_mock_time_task_runner.cc b/chromium/base/test/test_mock_time_task_runner.cc new file mode 100644 index 00000000000..63aacf948d5 --- /dev/null +++ b/chromium/base/test/test_mock_time_task_runner.cc @@ -0,0 +1,501 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_mock_time_task_runner.h" + +#include <utility> + +#include "base/check_op.h" +#include "base/containers/circular_deque.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread_task_runner_handle.h" + +namespace base { +namespace { + +// LegacyMockTickClock and LegacyMockClock are used by deprecated APIs of +// TestMockTimeTaskRunner. They will be removed after updating callers of +// GetMockClock() and GetMockTickClock() to GetMockClockPtr() and +// GetMockTickClockPtr(). +class LegacyMockTickClock : public TickClock { + public: + explicit LegacyMockTickClock( + scoped_refptr<const TestMockTimeTaskRunner> task_runner) + : task_runner_(std::move(task_runner)) {} + + // TickClock: + TimeTicks NowTicks() const override { return task_runner_->NowTicks(); } + + private: + scoped_refptr<const TestMockTimeTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(LegacyMockTickClock); +}; + +class LegacyMockClock : public Clock { + public: + explicit LegacyMockClock( + scoped_refptr<const TestMockTimeTaskRunner> task_runner) + : task_runner_(std::move(task_runner)) {} + + // Clock: + Time Now() const override { return task_runner_->Now(); } + + private: + scoped_refptr<const TestMockTimeTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(LegacyMockClock); +}; + +} // namespace + +// A SingleThreadTaskRunner which forwards everything to its |target_|. This +// serves two purposes: +// 1) If a ThreadTaskRunnerHandle owned by TestMockTimeTaskRunner were to be +// set to point to that TestMockTimeTaskRunner, a reference cycle would +// result. As |target_| here is a non-refcounting raw pointer, the cycle is +// broken. +// 2) Since SingleThreadTaskRunner is ref-counted, it's quite easy for it to +// accidentally get captured between tests in a singleton somewhere. +// Indirecting via NonOwningProxyTaskRunner permits TestMockTimeTaskRunner +// to be cleaned up (removing the RunLoop::Delegate in the kBoundToThread +// mode), and to also cleanly flag any actual attempts to use the leaked +// task runner. +class TestMockTimeTaskRunner::NonOwningProxyTaskRunner + : public SingleThreadTaskRunner { + public: + explicit NonOwningProxyTaskRunner(SingleThreadTaskRunner* target) + : target_(target) { + DCHECK(target_); + } + + // Detaches this NonOwningProxyTaskRunner instance from its |target_|. It is + // invalid to post tasks after this point but RunsTasksInCurrentSequence() + // will still pass on the original thread for convenience with legacy code. + void Detach() { + AutoLock scoped_lock(lock_); + target_ = nullptr; + } + + // SingleThreadTaskRunner: + bool RunsTasksInCurrentSequence() const override { + AutoLock scoped_lock(lock_); + if (target_) + return target_->RunsTasksInCurrentSequence(); + return thread_checker_.CalledOnValidThread(); + } + + bool PostDelayedTask(const Location& from_here, + OnceClosure task, + TimeDelta delay) override { + AutoLock scoped_lock(lock_); + if (target_) + return target_->PostDelayedTask(from_here, std::move(task), delay); + + // The associated TestMockTimeTaskRunner is dead, so fail this PostTask. + return false; + } + + bool PostNonNestableDelayedTask(const Location& from_here, + OnceClosure task, + TimeDelta delay) override { + AutoLock scoped_lock(lock_); + if (target_) { + return target_->PostNonNestableDelayedTask(from_here, std::move(task), + delay); + } + + // The associated TestMockTimeTaskRunner is dead, so fail this PostTask. + return false; + } + + private: + friend class RefCountedThreadSafe<NonOwningProxyTaskRunner>; + ~NonOwningProxyTaskRunner() override = default; + + mutable Lock lock_; + SingleThreadTaskRunner* target_; // guarded by lock_ + + // Used to implement RunsTasksInCurrentSequence, without relying on |target_|. + ThreadCheckerImpl thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(NonOwningProxyTaskRunner); +}; + +// TestMockTimeTaskRunner::TestOrderedPendingTask ----------------------------- + +// Subclass of TestPendingTask which has a strictly monotonically increasing ID +// for every task, so that tasks posted with the same 'time to run' can be run +// in the order of being posted. +struct TestMockTimeTaskRunner::TestOrderedPendingTask + : public base::TestPendingTask { + TestOrderedPendingTask(); + TestOrderedPendingTask(const Location& location, + OnceClosure task, + TimeTicks post_time, + TimeDelta delay, + size_t ordinal, + TestNestability nestability); + TestOrderedPendingTask(TestOrderedPendingTask&&); + ~TestOrderedPendingTask(); + + TestOrderedPendingTask& operator=(TestOrderedPendingTask&&); + + size_t ordinal; + + private: + DISALLOW_COPY_AND_ASSIGN(TestOrderedPendingTask); +}; + +TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask() + : ordinal(0) { +} + +TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask( + TestOrderedPendingTask&&) = default; + +TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask( + const Location& location, + OnceClosure task, + TimeTicks post_time, + TimeDelta delay, + size_t ordinal, + TestNestability nestability) + : base::TestPendingTask(location, + std::move(task), + post_time, + delay, + nestability), + ordinal(ordinal) {} + +TestMockTimeTaskRunner::TestOrderedPendingTask::~TestOrderedPendingTask() = + default; + +TestMockTimeTaskRunner::TestOrderedPendingTask& +TestMockTimeTaskRunner::TestOrderedPendingTask::operator=( + TestOrderedPendingTask&&) = default; + +// TestMockTimeTaskRunner ----------------------------------------------------- + +// TODO(gab): This should also set the SequenceToken for the current thread. +// Ref. TestMockTimeTaskRunner::RunsTasksInCurrentSequence(). +TestMockTimeTaskRunner::ScopedContext::ScopedContext( + scoped_refptr<TestMockTimeTaskRunner> scope) + : on_destroy_(ThreadTaskRunnerHandle::OverrideForTesting(scope)) { + scope->RunUntilIdle(); +} + +TestMockTimeTaskRunner::ScopedContext::~ScopedContext() = default; + +bool TestMockTimeTaskRunner::TemporalOrder::operator()( + const TestOrderedPendingTask& first_task, + const TestOrderedPendingTask& second_task) const { + if (first_task.GetTimeToRun() == second_task.GetTimeToRun()) + return first_task.ordinal > second_task.ordinal; + return first_task.GetTimeToRun() > second_task.GetTimeToRun(); +} + +TestMockTimeTaskRunner::TestMockTimeTaskRunner(Type type) + : TestMockTimeTaskRunner(Time::UnixEpoch(), TimeTicks(), type) {} + +TestMockTimeTaskRunner::TestMockTimeTaskRunner(Time start_time, + TimeTicks start_ticks, + Type type) + : now_(start_time), + now_ticks_(start_ticks), + tasks_lock_cv_(&tasks_lock_), + proxy_task_runner_(MakeRefCounted<NonOwningProxyTaskRunner>(this)), + mock_clock_(this) { + if (type == Type::kBoundToThread) { + RunLoop::RegisterDelegateForCurrentThread(this); + thread_task_runner_handle_ = + std::make_unique<ThreadTaskRunnerHandle>(proxy_task_runner_); + } +} + +TestMockTimeTaskRunner::~TestMockTimeTaskRunner() { + proxy_task_runner_->Detach(); +} + +void TestMockTimeTaskRunner::FastForwardBy(TimeDelta delta) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_GE(delta, TimeDelta()); + + const TimeTicks original_now_ticks = NowTicks(); + ProcessAllTasksNoLaterThan(delta); + ForwardClocksUntilTickTime(original_now_ticks + delta); +} + +void TestMockTimeTaskRunner::AdvanceMockTickClock(TimeDelta delta) { + ForwardClocksUntilTickTime(NowTicks() + delta); +} + +void TestMockTimeTaskRunner::RunUntilIdle() { + DCHECK(thread_checker_.CalledOnValidThread()); + ProcessAllTasksNoLaterThan(TimeDelta()); +} + +void TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() { + DCHECK(thread_checker_.CalledOnValidThread()); + ProcessAllTasksNoLaterThan(TimeDelta::Max()); +} + +void TestMockTimeTaskRunner::ClearPendingTasks() { + AutoLock scoped_lock(tasks_lock_); + // This is repeated in case task destruction triggers further tasks. + while (!tasks_.empty()) { + TaskPriorityQueue cleanup_tasks; + tasks_.swap(cleanup_tasks); + + // Destroy task objects with |tasks_lock_| released. Task deletion can cause + // calls to NonOwningProxyTaskRunner::RunsTasksInCurrentSequence() + // (e.g. for DCHECKs), which causes |NonOwningProxyTaskRunner::lock_| to be + // grabbed. + // + // On the other hand, calls from NonOwningProxyTaskRunner::PostTask -> + // TestMockTimeTaskRunner::PostTask acquire locks as + // |NonOwningProxyTaskRunner::lock_| followed by |tasks_lock_|, so it's + // desirable to avoid the reverse order, for deadlock freedom. + AutoUnlock scoped_unlock(tasks_lock_); + while (!cleanup_tasks.empty()) + cleanup_tasks.pop(); + } +} + +Time TestMockTimeTaskRunner::Now() const { + AutoLock scoped_lock(tasks_lock_); + return now_; +} + +TimeTicks TestMockTimeTaskRunner::NowTicks() const { + AutoLock scoped_lock(tasks_lock_); + return now_ticks_; +} + +std::unique_ptr<Clock> TestMockTimeTaskRunner::DeprecatedGetMockClock() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return std::make_unique<LegacyMockClock>(this); +} + +Clock* TestMockTimeTaskRunner::GetMockClock() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return &mock_clock_; +} + +std::unique_ptr<TickClock> TestMockTimeTaskRunner::DeprecatedGetMockTickClock() + const { + DCHECK(thread_checker_.CalledOnValidThread()); + return std::make_unique<LegacyMockTickClock>(this); +} + +const TickClock* TestMockTimeTaskRunner::GetMockTickClock() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return &mock_clock_; +} + +base::circular_deque<TestPendingTask> +TestMockTimeTaskRunner::TakePendingTasks() { + AutoLock scoped_lock(tasks_lock_); + base::circular_deque<TestPendingTask> tasks; + while (!tasks_.empty()) { + // It's safe to remove const and consume |task| here, since |task| is not + // used for ordering the item. + if (!tasks_.top().task.IsCancelled()) { + tasks.push_back( + std::move(const_cast<TestOrderedPendingTask&>(tasks_.top()))); + } + tasks_.pop(); + } + return tasks; +} + +bool TestMockTimeTaskRunner::HasPendingTask() { + DCHECK(thread_checker_.CalledOnValidThread()); + AutoLock scoped_lock(tasks_lock_); + while (!tasks_.empty() && tasks_.top().task.IsCancelled()) + tasks_.pop(); + return !tasks_.empty(); +} + +size_t TestMockTimeTaskRunner::GetPendingTaskCount() { + DCHECK(thread_checker_.CalledOnValidThread()); + AutoLock scoped_lock(tasks_lock_); + TaskPriorityQueue preserved_tasks; + while (!tasks_.empty()) { + if (!tasks_.top().task.IsCancelled()) { + preserved_tasks.push( + std::move(const_cast<TestOrderedPendingTask&>(tasks_.top()))); + } + tasks_.pop(); + } + tasks_.swap(preserved_tasks); + return tasks_.size(); +} + +TimeDelta TestMockTimeTaskRunner::NextPendingTaskDelay() { + DCHECK(thread_checker_.CalledOnValidThread()); + AutoLock scoped_lock(tasks_lock_); + while (!tasks_.empty() && tasks_.top().task.IsCancelled()) + tasks_.pop(); + return tasks_.empty() ? TimeDelta::Max() + : tasks_.top().GetTimeToRun() - now_ticks_; +} + +// TODO(gab): Combine |thread_checker_| with a SequenceToken to differentiate +// between tasks running in the scope of this TestMockTimeTaskRunner and other +// task runners sharing this thread. http://crbug.com/631186 +bool TestMockTimeTaskRunner::RunsTasksInCurrentSequence() const { + return thread_checker_.CalledOnValidThread(); +} + +bool TestMockTimeTaskRunner::PostDelayedTask(const Location& from_here, + OnceClosure task, + TimeDelta delay) { + AutoLock scoped_lock(tasks_lock_); + tasks_.push(TestOrderedPendingTask(from_here, std::move(task), now_ticks_, + delay, next_task_ordinal_++, + TestPendingTask::NESTABLE)); + tasks_lock_cv_.Signal(); + return true; +} + +bool TestMockTimeTaskRunner::PostNonNestableDelayedTask( + const Location& from_here, + OnceClosure task, + TimeDelta delay) { + return PostDelayedTask(from_here, std::move(task), delay); +} + +void TestMockTimeTaskRunner::OnBeforeSelectingTask() { + // Empty default implementation. +} + +void TestMockTimeTaskRunner::OnAfterTimePassed() { + // Empty default implementation. +} + +void TestMockTimeTaskRunner::OnAfterTaskRun() { + // Empty default implementation. +} + +void TestMockTimeTaskRunner::ProcessAllTasksNoLaterThan(TimeDelta max_delta) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_GE(max_delta, TimeDelta()); + + // Multiple test task runners can share the same thread for determinism in + // unit tests. Make sure this TestMockTimeTaskRunner's tasks run in its scope. + ScopedClosureRunner undo_override; + if (!ThreadTaskRunnerHandle::IsSet() || + ThreadTaskRunnerHandle::Get() != proxy_task_runner_.get()) { + undo_override = + ThreadTaskRunnerHandle::OverrideForTesting(proxy_task_runner_.get()); + } + + const TimeTicks original_now_ticks = NowTicks(); + while (!quit_run_loop_) { + OnBeforeSelectingTask(); + TestPendingTask task_info; + if (!DequeueNextTask(original_now_ticks, max_delta, &task_info)) + break; + if (task_info.task.IsCancelled()) + continue; + // If tasks were posted with a negative delay, task_info.GetTimeToRun() will + // be less than |now_ticks_|. ForwardClocksUntilTickTime() takes care of not + // moving the clock backwards in this case. + ForwardClocksUntilTickTime(task_info.GetTimeToRun()); + std::move(task_info.task).Run(); + OnAfterTaskRun(); + } +} + +void TestMockTimeTaskRunner::ForwardClocksUntilTickTime(TimeTicks later_ticks) { + DCHECK(thread_checker_.CalledOnValidThread()); + { + AutoLock scoped_lock(tasks_lock_); + if (later_ticks <= now_ticks_) + return; + + now_ += later_ticks - now_ticks_; + now_ticks_ = later_ticks; + } + OnAfterTimePassed(); +} + +bool TestMockTimeTaskRunner::DequeueNextTask(const TimeTicks& reference, + const TimeDelta& max_delta, + TestPendingTask* next_task) { + DCHECK(thread_checker_.CalledOnValidThread()); + AutoLock scoped_lock(tasks_lock_); + if (!tasks_.empty() && + (tasks_.top().GetTimeToRun() - reference) <= max_delta) { + // It's safe to remove const and consume |task| here, since |task| is not + // used for ordering the item. + *next_task = std::move(const_cast<TestOrderedPendingTask&>(tasks_.top())); + tasks_.pop(); + return true; + } + return false; +} + +void TestMockTimeTaskRunner::Run(bool application_tasks_allowed, + TimeDelta timeout) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Since TestMockTimeTaskRunner doesn't process system messages: there's no + // hope for anything but an application task to call Quit(). If this RunLoop + // can't process application tasks (i.e. disallowed by default in nested + // RunLoops) it's guaranteed to hang... + DCHECK(application_tasks_allowed) + << "This is a nested RunLoop instance and needs to be of " + "Type::kNestableTasksAllowed."; + + // This computation relies on saturated arithmetic. + TimeTicks run_until = now_ticks_ + timeout; + while (!quit_run_loop_ && now_ticks_ < run_until) { + RunUntilIdle(); + if (quit_run_loop_ || ShouldQuitWhenIdle()) + break; + + // Peek into |tasks_| to perform one of two things: + // A) If there are no remaining tasks, wait until one is posted and + // restart from the top. + // B) If there is a remaining delayed task. Fast-forward to reach the next + // round of tasks. + TimeDelta auto_fast_forward_by; + { + AutoLock scoped_lock(tasks_lock_); + if (tasks_.empty()) { + while (tasks_.empty()) + tasks_lock_cv_.Wait(); + continue; + } + auto_fast_forward_by = + std::min(run_until, tasks_.top().GetTimeToRun()) - now_ticks_; + } + FastForwardBy(auto_fast_forward_by); + } + quit_run_loop_ = false; +} + +void TestMockTimeTaskRunner::Quit() { + DCHECK(thread_checker_.CalledOnValidThread()); + quit_run_loop_ = true; +} + +void TestMockTimeTaskRunner::EnsureWorkScheduled() { + // Nothing to do: TestMockTimeTaskRunner::Run() will always process tasks and + // doesn't need an extra kick on nested runs. +} + +TimeTicks TestMockTimeTaskRunner::MockClock::NowTicks() const { + return task_runner_->NowTicks(); +} + +Time TestMockTimeTaskRunner::MockClock::Now() const { + return task_runner_->Now(); +} + +} // namespace base diff --git a/chromium/base/test/test_mock_time_task_runner_unittest.cc b/chromium/base/test/test_mock_time_task_runner_unittest.cc new file mode 100644 index 00000000000..80805298ada --- /dev/null +++ b/chromium/base/test/test_mock_time_task_runner_unittest.cc @@ -0,0 +1,298 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_mock_time_task_runner.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/cancelable_callback.h" +#include "base/memory/ref_counted.h" +#include "base/run_loop.h" +#include "base/test/bind_test_util.h" +#include "base/test/gtest_util.h" +#include "base/test/test_timeouts.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +// Basic usage should work the same from default and bound +// TestMockTimeTaskRunners. +TEST(TestMockTimeTaskRunnerTest, Basic) { + static constexpr TestMockTimeTaskRunner::Type kTestCases[] = { + TestMockTimeTaskRunner::Type::kStandalone, + TestMockTimeTaskRunner::Type::kBoundToThread}; + + for (auto type : kTestCases) { + SCOPED_TRACE(static_cast<int>(type)); + + auto mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(type); + int counter = 0; + + mock_time_task_runner->PostTask( + FROM_HERE, base::BindOnce([](int* counter) { *counter += 1; }, + Unretained(&counter))); + mock_time_task_runner->PostTask( + FROM_HERE, base::BindOnce([](int* counter) { *counter += 32; }, + Unretained(&counter))); + mock_time_task_runner->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 256; }, + Unretained(&counter)), + TimeDelta::FromSeconds(3)); + mock_time_task_runner->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 64; }, + Unretained(&counter)), + TimeDelta::FromSeconds(1)); + mock_time_task_runner->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 1024; }, + Unretained(&counter)), + TimeDelta::FromMinutes(20)); + mock_time_task_runner->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 4096; }, + Unretained(&counter)), + TimeDelta::FromDays(20)); + + int expected_value = 0; + EXPECT_EQ(expected_value, counter); + mock_time_task_runner->RunUntilIdle(); + expected_value += 1; + expected_value += 32; + EXPECT_EQ(expected_value, counter); + + mock_time_task_runner->RunUntilIdle(); + EXPECT_EQ(expected_value, counter); + + mock_time_task_runner->FastForwardBy(TimeDelta::FromSeconds(1)); + expected_value += 64; + EXPECT_EQ(expected_value, counter); + + mock_time_task_runner->FastForwardBy(TimeDelta::FromSeconds(5)); + expected_value += 256; + EXPECT_EQ(expected_value, counter); + + mock_time_task_runner->FastForwardUntilNoTasksRemain(); + expected_value += 1024; + expected_value += 4096; + EXPECT_EQ(expected_value, counter); + } +} + +// A default TestMockTimeTaskRunner shouldn't result in a thread association. +TEST(TestMockTimeTaskRunnerTest, DefaultUnbound) { + auto unbound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(); + EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet()); + EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet()); + EXPECT_DEATH_IF_SUPPORTED({ RunLoop().RunUntilIdle(); }, ""); +} + +TEST(TestMockTimeTaskRunnerTest, RunLoopDriveableWhenBound) { + auto bound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>( + TestMockTimeTaskRunner::Type::kBoundToThread); + + int counter = 0; + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce([](int* counter) { *counter += 1; }, + Unretained(&counter))); + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce([](int* counter) { *counter += 32; }, + Unretained(&counter))); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 256; }, + Unretained(&counter)), + TimeDelta::FromSeconds(3)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 64; }, + Unretained(&counter)), + TimeDelta::FromSeconds(1)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 1024; }, + Unretained(&counter)), + TimeDelta::FromMinutes(20)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 4096; }, + Unretained(&counter)), + TimeDelta::FromDays(20)); + + int expected_value = 0; + EXPECT_EQ(expected_value, counter); + RunLoop().RunUntilIdle(); + expected_value += 1; + expected_value += 32; + EXPECT_EQ(expected_value, counter); + + RunLoop().RunUntilIdle(); + EXPECT_EQ(expected_value, counter); + + { + RunLoop run_loop; + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop.QuitClosure(), TimeDelta::FromSeconds(1)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 8192; }, + Unretained(&counter)), + TimeDelta::FromSeconds(1)); + + // The QuitClosure() should be ordered between the 64 and the 8192 + // increments and should preempt the latter. + run_loop.Run(); + expected_value += 64; + EXPECT_EQ(expected_value, counter); + + // Running until idle should process the 8192 increment whose delay has + // expired in the previous Run(). + RunLoop().RunUntilIdle(); + expected_value += 8192; + EXPECT_EQ(expected_value, counter); + } + + { + RunLoop run_loop; + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromSeconds(5)); + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce([](int* counter) { *counter += 16384; }, + Unretained(&counter)), + TimeDelta::FromSeconds(5)); + + // The QuitWhenIdleClosure() shouldn't preempt equally delayed tasks and as + // such the 16384 increment should be processed before quitting. + run_loop.Run(); + expected_value += 256; + expected_value += 16384; + EXPECT_EQ(expected_value, counter); + } + + // Process the remaining tasks (note: do not mimic this elsewhere, + // TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() is a better API to + // do this, this is just done here for the purpose of extensively testing the + // RunLoop approach). + RunLoop run_loop; + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromDays(50)); + + run_loop.Run(); + expected_value += 1024; + expected_value += 4096; + EXPECT_EQ(expected_value, counter); +} + +TEST(TestMockTimeTaskRunnerTest, AvoidCaptureWhenBound) { + // Make sure that capturing the active task runner --- which sometimes happens + // unknowingly due to ThreadsafeObserverList deep within some singleton --- + // does not keep the entire TestMockTimeTaskRunner alive, as in bound mode + // that's a RunLoop::Delegate, and leaking that renders any further tests that + // need RunLoop support unrunnable. + // + // (This used to happen when code run from ProcessAllTasksNoLaterThan grabbed + // the runner.). + scoped_refptr<SingleThreadTaskRunner> captured; + { + auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>( + TestMockTimeTaskRunner::Type::kBoundToThread); + + task_runner->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() { + captured = ThreadTaskRunnerHandle::Get(); + })); + task_runner->RunUntilIdle(); + } + + { + // This should not complain about RunLoop::Delegate already existing. + auto task_runner2 = MakeRefCounted<TestMockTimeTaskRunner>( + TestMockTimeTaskRunner::Type::kBoundToThread); + } +} + +// Regression test that receiving the quit-when-idle signal when already empty +// works as intended (i.e. that |TestMockTimeTaskRunner::tasks_lock_cv| is +// properly signaled). +TEST(TestMockTimeTaskRunnerTest, RunLoopQuitFromIdle) { + auto bound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>( + TestMockTimeTaskRunner::Type::kBoundToThread); + + Thread quitting_thread("quitting thread"); + quitting_thread.Start(); + + RunLoop run_loop; + quitting_thread.task_runner()->PostDelayedTask( + FROM_HERE, run_loop.QuitWhenIdleClosure(), TestTimeouts::tiny_timeout()); + run_loop.Run(); +} + +TEST(TestMockTimeTaskRunnerTest, TakePendingTasks) { + auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>(); + task_runner->PostTask(FROM_HERE, DoNothing()); + EXPECT_TRUE(task_runner->HasPendingTask()); + EXPECT_EQ(1u, task_runner->TakePendingTasks().size()); + EXPECT_FALSE(task_runner->HasPendingTask()); +} + +TEST(TestMockTimeTaskRunnerTest, CancelPendingTask) { + auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>(); + CancelableOnceClosure task1(DoNothing::Once()); + task_runner->PostDelayedTask(FROM_HERE, task1.callback(), + TimeDelta::FromSeconds(1)); + EXPECT_TRUE(task_runner->HasPendingTask()); + EXPECT_EQ(1u, task_runner->GetPendingTaskCount()); + EXPECT_EQ(TimeDelta::FromSeconds(1), task_runner->NextPendingTaskDelay()); + task1.Cancel(); + EXPECT_FALSE(task_runner->HasPendingTask()); + + CancelableOnceClosure task2(DoNothing::Once()); + task_runner->PostDelayedTask(FROM_HERE, task2.callback(), + TimeDelta::FromSeconds(1)); + task2.Cancel(); + EXPECT_EQ(0u, task_runner->GetPendingTaskCount()); + + CancelableOnceClosure task3(DoNothing::Once()); + task_runner->PostDelayedTask(FROM_HERE, task3.callback(), + TimeDelta::FromSeconds(1)); + task3.Cancel(); + EXPECT_EQ(TimeDelta::Max(), task_runner->NextPendingTaskDelay()); + + CancelableOnceClosure task4(DoNothing::Once()); + task_runner->PostDelayedTask(FROM_HERE, task4.callback(), + TimeDelta::FromSeconds(1)); + task4.Cancel(); + EXPECT_TRUE(task_runner->TakePendingTasks().empty()); +} + +TEST(TestMockTimeTaskRunnerTest, NoFastForwardToCancelledTask) { + auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>(); + TimeTicks start_time = task_runner->NowTicks(); + CancelableOnceClosure task(DoNothing::Once()); + task_runner->PostDelayedTask(FROM_HERE, task.callback(), + TimeDelta::FromSeconds(1)); + EXPECT_EQ(TimeDelta::FromSeconds(1), task_runner->NextPendingTaskDelay()); + task.Cancel(); + task_runner->FastForwardUntilNoTasksRemain(); + EXPECT_EQ(start_time, task_runner->NowTicks()); +} + +TEST(TestMockTimeTaskRunnerTest, AdvanceMockTickClockDoesNotRunTasks) { + auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>(); + TimeTicks start_time = task_runner->NowTicks(); + task_runner->PostTask(FROM_HERE, BindOnce([]() { ADD_FAILURE(); })); + task_runner->PostDelayedTask(FROM_HERE, BindOnce([]() { ADD_FAILURE(); }), + TimeDelta::FromSeconds(1)); + + task_runner->AdvanceMockTickClock(TimeDelta::FromSeconds(3)); + EXPECT_EQ(start_time + TimeDelta::FromSeconds(3), task_runner->NowTicks()); + EXPECT_EQ(2u, task_runner->GetPendingTaskCount()); +} + +} // namespace base diff --git a/chromium/base/test/test_reg_util_win.cc b/chromium/base/test/test_reg_util_win.cc new file mode 100644 index 00000000000..6bcfe60bfeb --- /dev/null +++ b/chromium/base/test/test_reg_util_win.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_reg_util_win.h" + +#include <stdint.h> + +#include "base/guid.h" +#include "base/memory/ptr_util.h" +#include "base/strings/strcat.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include <windows.h> + +namespace registry_util { + +namespace { + +constexpr base::char16 kTimestampDelimiter[] = STRING16_LITERAL("$"); +constexpr wchar_t kTempTestKeyPath[] = L"Software\\Chromium\\TempTestKeys"; + +void DeleteStaleTestKeys(const base::Time& now, + const std::wstring& test_key_root) { + base::win::RegKey test_root_key; + if (test_root_key.Open(HKEY_CURRENT_USER, + test_key_root.c_str(), + KEY_ALL_ACCESS) != ERROR_SUCCESS) { + // This will occur on first-run, but is harmless. + return; + } + + base::win::RegistryKeyIterator iterator_test_root_key(HKEY_CURRENT_USER, + test_key_root.c_str()); + for (; iterator_test_root_key.Valid(); ++iterator_test_root_key) { + std::wstring key_name = iterator_test_root_key.Name(); + std::vector<base::StringPiece16> tokens = base::SplitStringPiece( + base::AsStringPiece16(key_name), kTimestampDelimiter, + base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (tokens.empty()) + continue; + int64_t key_name_as_number = 0; + + if (!base::StringToInt64(tokens[0], &key_name_as_number)) { + test_root_key.DeleteKey(key_name.c_str()); + continue; + } + + base::Time key_time = base::Time::FromInternalValue(key_name_as_number); + base::TimeDelta age = now - key_time; + + if (age > base::TimeDelta::FromHours(24)) + test_root_key.DeleteKey(key_name.c_str()); + } +} + +std::wstring GenerateTempKeyPath(const std::wstring& test_key_root, + const base::Time& timestamp) { + return base::AsWString(base::StrCat( + {base::AsStringPiece16(test_key_root), STRING16_LITERAL("\\"), + base::NumberToString16(timestamp.ToInternalValue()), kTimestampDelimiter, + base::ASCIIToUTF16(base::GenerateGUID())})); +} + +} // namespace + +RegistryOverrideManager::ScopedRegistryKeyOverride::ScopedRegistryKeyOverride( + HKEY override, + const std::wstring& key_path) + : override_(override), key_path_(key_path) {} + +RegistryOverrideManager:: + ScopedRegistryKeyOverride::~ScopedRegistryKeyOverride() { + ::RegOverridePredefKey(override_, NULL); + base::win::RegKey(HKEY_CURRENT_USER, L"", KEY_QUERY_VALUE) + .DeleteKey(key_path_.c_str()); +} + +RegistryOverrideManager::RegistryOverrideManager() + : timestamp_(base::Time::Now()), test_key_root_(kTempTestKeyPath) { + DeleteStaleTestKeys(timestamp_, test_key_root_); +} + +RegistryOverrideManager::RegistryOverrideManager( + const base::Time& timestamp, + const std::wstring& test_key_root) + : timestamp_(timestamp), test_key_root_(test_key_root) { + DeleteStaleTestKeys(timestamp_, test_key_root_); +} + +RegistryOverrideManager::~RegistryOverrideManager() {} + +void RegistryOverrideManager::OverrideRegistry(HKEY override) { + OverrideRegistry(override, nullptr); +} + +void RegistryOverrideManager::OverrideRegistry(HKEY override, + std::wstring* override_path) { + std::wstring key_path = GenerateTempKeyPath(test_key_root_, timestamp_); + + base::win::RegKey temp_key; + ASSERT_EQ(ERROR_SUCCESS, temp_key.Create(HKEY_CURRENT_USER, key_path.c_str(), + KEY_ALL_ACCESS)); + ASSERT_EQ(ERROR_SUCCESS, ::RegOverridePredefKey(override, temp_key.Handle())); + + overrides_.push_back( + std::make_unique<ScopedRegistryKeyOverride>(override, key_path)); + if (override_path) + override_path->assign(key_path); +} + +std::wstring GenerateTempKeyPath() { + return GenerateTempKeyPath(kTempTestKeyPath, base::Time::Now()); +} + +} // namespace registry_util diff --git a/chromium/base/test/test_reg_util_win.h b/chromium/base/test/test_reg_util_win.h new file mode 100644 index 00000000000..0592ff7176c --- /dev/null +++ b/chromium/base/test/test_reg_util_win.h @@ -0,0 +1,82 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_REG_UTIL_WIN_H_ +#define BASE_TEST_TEST_REG_UTIL_WIN_H_ + +// Registry utility functions used only by tests. +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/time/time.h" +#include "base/win/registry.h" + +namespace registry_util { + +// Allows a test to easily override registry hives so that it can start from a +// known good state, or make sure to not leave any side effects once the test +// completes. This supports parallel tests. All the overrides are scoped to the +// lifetime of the override manager. Destroy the manager to undo the overrides. +// +// Overridden hives use keys stored at, for instance: +// HKCU\Software\Chromium\TempTestKeys\ +// 13028145911617809$02AB211C-CF73-478D-8D91-618E11998AED +// The key path are comprises of: +// - The test key root, HKCU\Software\Chromium\TempTestKeys\ +// - The base::Time::ToInternalValue of the creation time. This is used to +// delete stale keys left over from crashed tests. +// - A GUID used for preventing name collisions (although unlikely) between +// two RegistryOverrideManagers created with the same timestamp. +class RegistryOverrideManager { + public: + RegistryOverrideManager(); + ~RegistryOverrideManager(); + + // Override the given registry hive using a randomly generated temporary key. + // Multiple overrides to the same hive are not supported and lead to undefined + // behavior. + // Optional return of the registry override path. + // Calls to these functions must be wrapped in ASSERT_NO_FATAL_FAILURE to + // ensure that tests do not proceeed in case of failure to override. + void OverrideRegistry(HKEY override); + void OverrideRegistry(HKEY override, std::wstring* override_path); + + private: + friend class RegistryOverrideManagerTest; + + // Keeps track of one override. + class ScopedRegistryKeyOverride { + public: + ScopedRegistryKeyOverride(HKEY override, const std::wstring& key_path); + ~ScopedRegistryKeyOverride(); + + private: + HKEY override_; + std::wstring key_path_; + + DISALLOW_COPY_AND_ASSIGN(ScopedRegistryKeyOverride); + }; + + // Used for testing only. + RegistryOverrideManager(const base::Time& timestamp, + const std::wstring& test_key_root); + + base::Time timestamp_; + std::wstring guid_; + + std::wstring test_key_root_; + std::vector<std::unique_ptr<ScopedRegistryKeyOverride>> overrides_; + + DISALLOW_COPY_AND_ASSIGN(RegistryOverrideManager); +}; + +// Generates a temporary key path that will be eventually deleted +// automatically if the process crashes. +std::wstring GenerateTempKeyPath(); + +} // namespace registry_util + +#endif // BASE_TEST_TEST_REG_UTIL_WIN_H_ diff --git a/chromium/base/test/test_reg_util_win_unittest.cc b/chromium/base/test/test_reg_util_win_unittest.cc new file mode 100644 index 00000000000..12c1d6b9970 --- /dev/null +++ b/chromium/base/test/test_reg_util_win_unittest.cc @@ -0,0 +1,134 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_reg_util_win.h" + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace registry_util { + +namespace { +const wchar_t kTestKeyPath[] = L"Software\\Chromium\\Foo\\Baz\\TestKey"; +const wchar_t kTestValueName[] = L"TestValue"; +} // namespace + +class RegistryOverrideManagerTest : public testing::Test { + protected: + RegistryOverrideManagerTest() { + // We assign a fake test key path to our test RegistryOverrideManager + // so we don't interfere with any actual RegistryOverrideManagers running + // on the system. This fake path will be auto-deleted by other + // RegistryOverrideManagers in case we crash. + fake_test_key_root_ = registry_util::GenerateTempKeyPath(); + + // Ensure a clean test environment. + base::win::RegKey key(HKEY_CURRENT_USER); + key.DeleteKey(fake_test_key_root_.c_str()); + key.DeleteKey(kTestKeyPath); + } + + ~RegistryOverrideManagerTest() override { + base::win::RegKey key(HKEY_CURRENT_USER); + key.DeleteKey(fake_test_key_root_.c_str()); + } + + void AssertKeyExists(const std::wstring& key_path) { + base::win::RegKey key; + ASSERT_EQ(ERROR_SUCCESS, + key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_READ)) + << key_path << " does not exist."; + } + + void AssertKeyAbsent(const std::wstring& key_path) { + base::win::RegKey key; + ASSERT_NE(ERROR_SUCCESS, + key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_READ)) + << key_path << " exists but it should not."; + } + + void CreateKey(const std::wstring& key_path) { + base::win::RegKey key; + ASSERT_EQ(ERROR_SUCCESS, + key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_ALL_ACCESS)); + } + + std::wstring FakeOverrideManagerPath(const base::Time& time) { + return fake_test_key_root_ + L"\\" + + base::AsWString(base::NumberToString16(time.ToInternalValue())); + } + + void CreateManager(const base::Time& timestamp) { + manager_.reset(new RegistryOverrideManager(timestamp, fake_test_key_root_)); + manager_->OverrideRegistry(HKEY_CURRENT_USER); + } + + std::wstring fake_test_key_root_; + std::unique_ptr<RegistryOverrideManager> manager_; +}; + +TEST_F(RegistryOverrideManagerTest, Basic) { + ASSERT_NO_FATAL_FAILURE(CreateManager(base::Time::Now())); + + base::win::RegKey create_key; + EXPECT_EQ(ERROR_SUCCESS, + create_key.Create(HKEY_CURRENT_USER, kTestKeyPath, KEY_ALL_ACCESS)); + EXPECT_TRUE(create_key.Valid()); + EXPECT_EQ(ERROR_SUCCESS, create_key.WriteValue(kTestValueName, 42)); + create_key.Close(); + + ASSERT_NO_FATAL_FAILURE(AssertKeyExists(kTestKeyPath)); + + DWORD value; + base::win::RegKey read_key; + EXPECT_EQ(ERROR_SUCCESS, + read_key.Open(HKEY_CURRENT_USER, kTestKeyPath, KEY_READ)); + EXPECT_TRUE(read_key.Valid()); + EXPECT_EQ(ERROR_SUCCESS, read_key.ReadValueDW(kTestValueName, &value)); + EXPECT_EQ(42u, value); + read_key.Close(); + + manager_.reset(); + + ASSERT_NO_FATAL_FAILURE(AssertKeyAbsent(kTestKeyPath)); +} + +TEST_F(RegistryOverrideManagerTest, DeleteStaleKeys) { + base::Time::Exploded kTestTimeExploded = {2013, 11, 1, 4, 0, 0, 0, 0}; + base::Time kTestTime; + EXPECT_TRUE(base::Time::FromUTCExploded(kTestTimeExploded, &kTestTime)); + + std::wstring path_garbage = fake_test_key_root_ + L"\\Blah"; + std::wstring path_very_stale = + FakeOverrideManagerPath(kTestTime - base::TimeDelta::FromDays(100)); + std::wstring path_stale = + FakeOverrideManagerPath(kTestTime - base::TimeDelta::FromDays(5)); + std::wstring path_current = + FakeOverrideManagerPath(kTestTime - base::TimeDelta::FromMinutes(1)); + std::wstring path_future = + FakeOverrideManagerPath(kTestTime + base::TimeDelta::FromMinutes(1)); + + ASSERT_NO_FATAL_FAILURE(CreateKey(path_garbage)); + ASSERT_NO_FATAL_FAILURE(CreateKey(path_very_stale)); + ASSERT_NO_FATAL_FAILURE(CreateKey(path_stale)); + ASSERT_NO_FATAL_FAILURE(CreateKey(path_current)); + ASSERT_NO_FATAL_FAILURE(CreateKey(path_future)); + + ASSERT_NO_FATAL_FAILURE(CreateManager(kTestTime)); + manager_.reset(); + + ASSERT_NO_FATAL_FAILURE(AssertKeyAbsent(path_garbage)); + ASSERT_NO_FATAL_FAILURE(AssertKeyAbsent(path_very_stale)); + ASSERT_NO_FATAL_FAILURE(AssertKeyAbsent(path_stale)); + ASSERT_NO_FATAL_FAILURE(AssertKeyExists(path_current)); + ASSERT_NO_FATAL_FAILURE(AssertKeyExists(path_future)); +} + +} // namespace registry_util diff --git a/chromium/base/test/test_shared_library.cc b/chromium/base/test/test_shared_library.cc new file mode 100644 index 00000000000..99c04674cea --- /dev/null +++ b/chromium/base/test/test_shared_library.cc @@ -0,0 +1,30 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/native_library_test_utils.h" + +extern "C" { + +int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetExportedValue() { + return g_native_library_exported_value; +} + +void NATIVE_LIBRARY_TEST_ALWAYS_EXPORT SetExportedValue(int value) { + g_native_library_exported_value = value; +} + +// A test function used only to verify basic dynamic symbol resolution. +int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetSimpleTestValue() { + return 5; +} + +// When called by |NativeLibraryTest.LoadLibraryPreferOwnSymbols|, this should +// forward to the local definition of NativeLibraryTestIncrement(), even though +// the test module also links in the native_library_test_utils source library +// which exports it. +int NATIVE_LIBRARY_TEST_ALWAYS_EXPORT GetIncrementValue() { + return NativeLibraryTestIncrement(); +} + +} // extern "C" diff --git a/chromium/base/test/test_shared_memory_util.cc b/chromium/base/test/test_shared_memory_util.cc new file mode 100644 index 00000000000..43bbb7dbc2e --- /dev/null +++ b/chromium/base/test/test_shared_memory_util.cc @@ -0,0 +1,169 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_shared_memory_util.h" + +#include <gtest/gtest.h> + +#include <stddef.h> +#include <stdint.h> + +#include "base/logging.h" +#include "build/build_config.h" + +#if defined(OS_POSIX) && !defined(OS_NACL) +#include <errno.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> +#endif + +#if defined(OS_FUCHSIA) +#include <lib/zx/vmar.h> +#include <zircon/rights.h> +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include <mach/mach_vm.h> +#endif + +#if defined(OS_WIN) +#include <aclapi.h> +#endif + +namespace base { + +#if !defined(OS_NACL) + +static const size_t kDataSize = 1024; + +// Common routine used with Posix file descriptors. Check that shared memory +// file descriptor |fd| does not allow writable mappings. Return true on +// success, false otherwise. +#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS)) +static bool CheckReadOnlySharedMemoryFdPosix(int fd) { +// Note that the error on Android is EPERM, unlike other platforms where +// it will be EACCES. +#if defined(OS_ANDROID) + const int kExpectedErrno = EPERM; +#else + const int kExpectedErrno = EACCES; +#endif + errno = 0; + void* address = + mmap(nullptr, kDataSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + const bool success = (address != nullptr) && (address != MAP_FAILED); + if (success) { + LOG(ERROR) << "mmap() should have failed!"; + munmap(address, kDataSize); // Cleanup. + return false; + } + if (errno != kExpectedErrno) { + LOG(ERROR) << "Expected mmap() to return " << kExpectedErrno + << " but returned " << errno << ": " << strerror(errno) << "\n"; + return false; + } + return true; +} +#endif // OS_POSIX && !(defined(OS_MACOSX) && !defined(OS_IOS)) + +#if defined(OS_FUCHSIA) +// Fuchsia specific implementation. +bool CheckReadOnlySharedMemoryFuchsiaHandle(zx::unowned_vmo handle) { + const uint32_t flags = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; + uintptr_t addr; + const zx_status_t status = + zx::vmar::root_self()->map(0, *handle, 0U, kDataSize, flags, &addr); + if (status == ZX_OK) { + LOG(ERROR) << "zx_vmar_map() should have failed!"; + zx::vmar::root_self()->unmap(addr, kDataSize); + return false; + } + if (status != ZX_ERR_ACCESS_DENIED) { + LOG(ERROR) << "Expected zx_vmar_map() to return " << ZX_ERR_ACCESS_DENIED + << " (ZX_ERR_ACCESS_DENIED) but returned " << status << "\n"; + return false; + } + return true; +} + +#elif defined(OS_MACOSX) && !defined(OS_IOS) +bool CheckReadOnlySharedMemoryMachPort(mach_port_t memory_object) { + mach_vm_address_t memory; + const kern_return_t kr = mach_vm_map( + mach_task_self(), &memory, kDataSize, 0, VM_FLAGS_ANYWHERE, memory_object, + 0, FALSE, VM_PROT_READ | VM_PROT_WRITE, + VM_PROT_READ | VM_PROT_WRITE | VM_PROT_IS_MASK, VM_INHERIT_NONE); + if (kr == KERN_SUCCESS) { + LOG(ERROR) << "mach_vm_map() should have failed!"; + mach_vm_deallocate(mach_task_self(), memory, kDataSize); // Cleanup. + return false; + } + return true; +} + +#elif defined(OS_WIN) +bool CheckReadOnlySharedMemoryWindowsHandle(HANDLE handle) { + void* memory = + MapViewOfFile(handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, kDataSize); + if (memory != nullptr) { + LOG(ERROR) << "MapViewOfFile() should have failed!"; + UnmapViewOfFile(memory); + return false; + } + return true; +} +#endif + +bool CheckReadOnlyPlatformSharedMemoryRegionForTesting( + subtle::PlatformSharedMemoryRegion region) { + if (region.GetMode() != subtle::PlatformSharedMemoryRegion::Mode::kReadOnly) { + LOG(ERROR) << "Expected region mode is " + << static_cast<int>( + subtle::PlatformSharedMemoryRegion::Mode::kReadOnly) + << " but actual is " << static_cast<int>(region.GetMode()); + return false; + } + +#if defined(OS_MACOSX) && !defined(OS_IOS) + return CheckReadOnlySharedMemoryMachPort(region.GetPlatformHandle()); +#elif defined(OS_FUCHSIA) + return CheckReadOnlySharedMemoryFuchsiaHandle(region.GetPlatformHandle()); +#elif defined(OS_WIN) + return CheckReadOnlySharedMemoryWindowsHandle(region.GetPlatformHandle()); +#elif defined(OS_ANDROID) + return CheckReadOnlySharedMemoryFdPosix(region.GetPlatformHandle()); +#else + return CheckReadOnlySharedMemoryFdPosix(region.GetPlatformHandle().fd); +#endif +} + +#endif // !OS_NACL + +WritableSharedMemoryMapping MapForTesting( + subtle::PlatformSharedMemoryRegion* region) { + return MapAtForTesting(region, 0, region->GetSize()); +} + +WritableSharedMemoryMapping MapAtForTesting( + subtle::PlatformSharedMemoryRegion* region, + off_t offset, + size_t size) { + void* memory = nullptr; + size_t mapped_size = 0; + if (!region->MapAt(offset, size, &memory, &mapped_size)) + return {}; + + return WritableSharedMemoryMapping(memory, size, mapped_size, + region->GetGUID()); +} + +template <> +std::pair<ReadOnlySharedMemoryRegion, WritableSharedMemoryMapping> +CreateMappedRegion(size_t size) { + MappedReadOnlyRegion mapped_region = ReadOnlySharedMemoryRegion::Create(size); + return {std::move(mapped_region.region), std::move(mapped_region.mapping)}; +} + +} // namespace base diff --git a/chromium/base/test/test_shared_memory_util.h b/chromium/base/test/test_shared_memory_util.h new file mode 100644 index 00000000000..e23d83e1fee --- /dev/null +++ b/chromium/base/test/test_shared_memory_util.h @@ -0,0 +1,52 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_SHARED_MEMORY_UTIL_H_ +#define BASE_TEST_TEST_SHARED_MEMORY_UTIL_H_ + +#include "base/memory/platform_shared_memory_region.h" +#include "base/memory/read_only_shared_memory_region.h" +#include "base/memory/shared_memory_mapping.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +// Check that the shared memory |region| cannot be used to perform a writable +// mapping with low-level system APIs like mmap(). Return true in case of +// success (i.e. writable mappings are _not_ allowed), or false otherwise. +bool CheckReadOnlyPlatformSharedMemoryRegionForTesting( + subtle::PlatformSharedMemoryRegion region); + +// Creates a scoped mapping from a PlatformSharedMemoryRegion. It's useful for +// PlatformSharedMemoryRegion testing to not leak mapped memory. +// WritableSharedMemoryMapping is used for wrapping because it has max +// capabilities but the actual permission depends on the |region|'s mode. +// This must not be used in production where PlatformSharedMemoryRegion should +// be wrapped with {Writable,Unsafe,ReadOnly}SharedMemoryRegion. +WritableSharedMemoryMapping MapAtForTesting( + subtle::PlatformSharedMemoryRegion* region, + off_t offset, + size_t size); + +WritableSharedMemoryMapping MapForTesting( + subtle::PlatformSharedMemoryRegion* region); + +template <typename SharedMemoryRegionType> +std::pair<SharedMemoryRegionType, WritableSharedMemoryMapping> +CreateMappedRegion(size_t size) { + SharedMemoryRegionType region = SharedMemoryRegionType::Create(size); + WritableSharedMemoryMapping mapping = region.Map(); + return {std::move(region), std::move(mapping)}; +} + +// Template specialization of CreateMappedRegion<>() for +// the ReadOnlySharedMemoryRegion. We need this because +// ReadOnlySharedMemoryRegion::Create() has a different return type. +template <> +std::pair<ReadOnlySharedMemoryRegion, WritableSharedMemoryMapping> +CreateMappedRegion(size_t size); + +} // namespace base + +#endif // BASE_TEST_TEST_SHARED_MEMORY_UTIL_H_ diff --git a/chromium/base/test/test_shortcut_win.cc b/chromium/base/test/test_shortcut_win.cc new file mode 100644 index 00000000000..b80fd967ba7 --- /dev/null +++ b/chromium/base/test/test_shortcut_win.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_shortcut_win.h" + +#include <windows.h> +#include <objbase.h> +#include <shlobj.h> +#include <propkey.h> +#include <wrl/client.h> + +#include "base/files/file_path.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/scoped_propvariant.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace win { + +void ValidatePathsAreEqual(const FilePath& expected_path, + const FilePath& actual_path) { + wchar_t long_expected_path_chars[MAX_PATH] = {0}; + wchar_t long_actual_path_chars[MAX_PATH] = {0}; + + // If |expected_path| is empty confirm immediately that |actual_path| is also + // empty. + if (expected_path.empty()) { + EXPECT_TRUE(actual_path.empty()); + return; + } + + // Proceed with LongPathName matching which will also confirm the paths exist. + EXPECT_NE(0U, ::GetLongPathName(expected_path.value().c_str(), + long_expected_path_chars, MAX_PATH)) + << "Failed to get LongPathName of " << expected_path.value(); + EXPECT_NE(0U, ::GetLongPathName(actual_path.value().c_str(), + long_actual_path_chars, MAX_PATH)) + << "Failed to get LongPathName of " << actual_path.value(); + + FilePath long_expected_path(long_expected_path_chars); + FilePath long_actual_path(long_actual_path_chars); + EXPECT_FALSE(long_expected_path.empty()); + EXPECT_FALSE(long_actual_path.empty()); + + EXPECT_EQ(long_expected_path, long_actual_path); +} + +void ValidateShortcut(const FilePath& shortcut_path, + const ShortcutProperties& properties) { + Microsoft::WRL::ComPtr<IShellLink> i_shell_link; + Microsoft::WRL::ComPtr<IPersistFile> i_persist_file; + + wchar_t read_target[MAX_PATH] = {0}; + wchar_t read_working_dir[MAX_PATH] = {0}; + wchar_t read_arguments[MAX_PATH] = {0}; + wchar_t read_description[MAX_PATH] = {0}; + wchar_t read_icon[MAX_PATH] = {0}; + int read_icon_index = 0; + + HRESULT hr; + + // Initialize the shell interfaces. + EXPECT_TRUE(SUCCEEDED(hr = ::CoCreateInstance(CLSID_ShellLink, NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&i_shell_link)))); + if (FAILED(hr)) + return; + + EXPECT_TRUE(SUCCEEDED(hr = i_shell_link.As(&i_persist_file))); + if (FAILED(hr)) + return; + + // Load the shortcut. + EXPECT_TRUE( + SUCCEEDED(hr = i_persist_file->Load(shortcut_path.value().c_str(), 0))) + << "Failed to load shortcut at " << shortcut_path.value(); + if (FAILED(hr)) + return; + + if (properties.options & ShortcutProperties::PROPERTIES_TARGET) { + EXPECT_TRUE(SUCCEEDED( + i_shell_link->GetPath(read_target, MAX_PATH, NULL, SLGP_SHORTPATH))); + ValidatePathsAreEqual(properties.target, FilePath(read_target)); + } + + if (properties.options & ShortcutProperties::PROPERTIES_WORKING_DIR) { + EXPECT_TRUE(SUCCEEDED( + i_shell_link->GetWorkingDirectory(read_working_dir, MAX_PATH))); + ValidatePathsAreEqual(properties.working_dir, FilePath(read_working_dir)); + } + + if (properties.options & ShortcutProperties::PROPERTIES_ARGUMENTS) { + EXPECT_TRUE( + SUCCEEDED(i_shell_link->GetArguments(read_arguments, MAX_PATH))); + EXPECT_EQ(properties.arguments, read_arguments); + } + + if (properties.options & ShortcutProperties::PROPERTIES_DESCRIPTION) { + EXPECT_TRUE( + SUCCEEDED(i_shell_link->GetDescription(read_description, MAX_PATH))); + EXPECT_EQ(properties.description, read_description); + } + + if (properties.options & ShortcutProperties::PROPERTIES_ICON) { + EXPECT_TRUE(SUCCEEDED( + i_shell_link->GetIconLocation(read_icon, MAX_PATH, &read_icon_index))); + ValidatePathsAreEqual(properties.icon, FilePath(read_icon)); + EXPECT_EQ(properties.icon_index, read_icon_index); + } + + Microsoft::WRL::ComPtr<IPropertyStore> property_store; + EXPECT_TRUE(SUCCEEDED(hr = i_shell_link.As(&property_store))); + if (FAILED(hr)) + return; + + if (properties.options & ShortcutProperties::PROPERTIES_APP_ID) { + ScopedPropVariant pv_app_id; + EXPECT_EQ(S_OK, property_store->GetValue(PKEY_AppUserModel_ID, + pv_app_id.Receive())); + switch (pv_app_id.get().vt) { + case VT_EMPTY: + EXPECT_TRUE(properties.app_id.empty()); + break; + case VT_LPWSTR: + EXPECT_EQ(properties.app_id, pv_app_id.get().pwszVal); + break; + default: + ADD_FAILURE() << "Unexpected variant type: " << pv_app_id.get().vt; + } + } + + if (properties.options & ShortcutProperties::PROPERTIES_DUAL_MODE) { + ScopedPropVariant pv_dual_mode; + EXPECT_EQ(S_OK, property_store->GetValue(PKEY_AppUserModel_IsDualMode, + pv_dual_mode.Receive())); + switch (pv_dual_mode.get().vt) { + case VT_EMPTY: + EXPECT_FALSE(properties.dual_mode); + break; + case VT_BOOL: + EXPECT_EQ(properties.dual_mode, + static_cast<bool>(pv_dual_mode.get().boolVal)); + break; + default: + ADD_FAILURE() << "Unexpected variant type: " << pv_dual_mode.get().vt; + } + } +} + +} // namespace win +} // namespace base diff --git a/chromium/base/test/test_shortcut_win.h b/chromium/base/test/test_shortcut_win.h new file mode 100644 index 00000000000..b828e8bb1f3 --- /dev/null +++ b/chromium/base/test/test_shortcut_win.h @@ -0,0 +1,30 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_SHORTCUT_WIN_H_ +#define BASE_TEST_TEST_SHORTCUT_WIN_H_ + +#include "base/files/file_path.h" +#include "base/win/shortcut.h" + +// Windows shortcut functions used only by tests. + +namespace base { +namespace win { + +// Validates |actual_path|'s LongPathName case-insensitively matches +// |expected_path|'s LongPathName. +void ValidatePathsAreEqual(const base::FilePath& expected_path, + const base::FilePath& actual_path); + +// Validates that a shortcut exists at |shortcut_path| with the expected +// |properties|. +// Logs gtest failures on failed verifications. +void ValidateShortcut(const FilePath& shortcut_path, + const ShortcutProperties& properties); + +} // namespace win +} // namespace base + +#endif // BASE_TEST_TEST_SHORTCUT_WIN_H_ diff --git a/chromium/base/test/test_simple_task_runner.cc b/chromium/base/test/test_simple_task_runner.cc new file mode 100644 index 00000000000..3e5d70ee4cf --- /dev/null +++ b/chromium/base/test/test_simple_task_runner.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_simple_task_runner.h" + +#include <utility> + +#include "base/check.h" +#include "base/memory/ptr_util.h" +#include "base/threading/thread_task_runner_handle.h" + +namespace base { + +TestSimpleTaskRunner::TestSimpleTaskRunner() = default; + +TestSimpleTaskRunner::~TestSimpleTaskRunner() = default; + +bool TestSimpleTaskRunner::PostDelayedTask(const Location& from_here, + OnceClosure task, + TimeDelta delay) { + AutoLock auto_lock(lock_); + pending_tasks_.push_back(TestPendingTask(from_here, std::move(task), + TimeTicks(), delay, + TestPendingTask::NESTABLE)); + return true; +} + +bool TestSimpleTaskRunner::PostNonNestableDelayedTask(const Location& from_here, + OnceClosure task, + TimeDelta delay) { + AutoLock auto_lock(lock_); + pending_tasks_.push_back(TestPendingTask(from_here, std::move(task), + TimeTicks(), delay, + TestPendingTask::NON_NESTABLE)); + return true; +} + +// TODO(gab): Use SequenceToken here to differentiate between tasks running in +// the scope of this TestSimpleTaskRunner and other task runners sharing this +// thread. http://crbug.com/631186 +bool TestSimpleTaskRunner::RunsTasksInCurrentSequence() const { + return thread_ref_ == PlatformThread::CurrentRef(); +} + +base::circular_deque<TestPendingTask> TestSimpleTaskRunner::TakePendingTasks() { + AutoLock auto_lock(lock_); + return std::move(pending_tasks_); +} + +size_t TestSimpleTaskRunner::NumPendingTasks() const { + AutoLock auto_lock(lock_); + return pending_tasks_.size(); +} + +bool TestSimpleTaskRunner::HasPendingTask() const { + AutoLock auto_lock(lock_); + return !pending_tasks_.empty(); +} + +base::TimeDelta TestSimpleTaskRunner::NextPendingTaskDelay() const { + AutoLock auto_lock(lock_); + return pending_tasks_.front().GetTimeToRun() - base::TimeTicks(); +} + +base::TimeDelta TestSimpleTaskRunner::FinalPendingTaskDelay() const { + AutoLock auto_lock(lock_); + return pending_tasks_.back().GetTimeToRun() - base::TimeTicks(); +} + +void TestSimpleTaskRunner::ClearPendingTasks() { + AutoLock auto_lock(lock_); + pending_tasks_.clear(); +} + +void TestSimpleTaskRunner::RunPendingTasks() { + DCHECK(RunsTasksInCurrentSequence()); + + // Swap with a local variable to avoid re-entrancy problems. + base::circular_deque<TestPendingTask> tasks_to_run; + { + AutoLock auto_lock(lock_); + tasks_to_run.swap(pending_tasks_); + } + + // Multiple test task runners can share the same thread for determinism in + // unit tests. Make sure this TestSimpleTaskRunner's tasks run in its scope. + ScopedClosureRunner undo_override; + if (!ThreadTaskRunnerHandle::IsSet() || + ThreadTaskRunnerHandle::Get() != this) { + undo_override = ThreadTaskRunnerHandle::OverrideForTesting(this); + } + + for (auto& task : tasks_to_run) + std::move(task.task).Run(); +} + +void TestSimpleTaskRunner::RunUntilIdle() { + while (HasPendingTask()) + RunPendingTasks(); +} + +} // namespace base diff --git a/chromium/base/test/test_simple_task_runner.h b/chromium/base/test/test_simple_task_runner.h new file mode 100644 index 00000000000..245ca88c1ce --- /dev/null +++ b/chromium/base/test/test_simple_task_runner.h @@ -0,0 +1,97 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_SIMPLE_TASK_RUNNER_H_ +#define BASE_TEST_TEST_SIMPLE_TASK_RUNNER_H_ + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/containers/circular_deque.h" +#include "base/macros.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/test/test_pending_task.h" +#include "base/threading/platform_thread.h" + +namespace base { + +class TimeDelta; + +// ATTENTION: Prefer using base::test::TaskEnvironment and a task runner +// obtained from base/task/post_task.h over this class. This class isn't as +// "simple" as it seems specifically because it runs tasks in a surprising order +// (delays aren't respected and nesting doesn't behave as usual). Should you +// prefer to flush all tasks regardless of delays, +// TaskEnvironment::TimeSource::MOCK_TIME and +// TaskEnvironment::FastForwardUntilNoTasksRemain() have you covered. +// +// TestSimpleTaskRunner is a simple TaskRunner implementation that can +// be used for testing. It implements SingleThreadTaskRunner as that +// interface implements SequencedTaskRunner, which in turn implements +// TaskRunner, so TestSimpleTaskRunner can be passed in to a function +// that accepts any *TaskRunner object. +// +// TestSimpleTaskRunner has the following properties which make it simple: +// +// - Tasks are simply stored in a queue in FIFO order, ignoring delay +// and nestability. +// - Tasks aren't guaranteed to be destroyed immediately after +// they're run. +// +// However, TestSimpleTaskRunner allows for reentrancy, in that it +// handles the running of tasks that in turn call back into itself +// (e.g., to post more tasks). +// +// Note that, like any TaskRunner, TestSimpleTaskRunner is +// ref-counted. +class TestSimpleTaskRunner : public SingleThreadTaskRunner { + public: + TestSimpleTaskRunner(); + + // SingleThreadTaskRunner implementation. + bool PostDelayedTask(const Location& from_here, + OnceClosure task, + TimeDelta delay) override; + bool PostNonNestableDelayedTask(const Location& from_here, + OnceClosure task, + TimeDelta delay) override; + + bool RunsTasksInCurrentSequence() const override; + + base::circular_deque<TestPendingTask> TakePendingTasks(); + size_t NumPendingTasks() const; + bool HasPendingTask() const; + base::TimeDelta NextPendingTaskDelay() const; + base::TimeDelta FinalPendingTaskDelay() const; + + // Clears the queue of pending tasks without running them. + void ClearPendingTasks(); + + // Runs each current pending task in order and clears the queue. Tasks posted + // by the tasks that run within this call do not run within this call. Can + // only be called on the thread that created this TestSimpleTaskRunner. + void RunPendingTasks(); + + // Runs pending tasks until the queue is empty. Can only be called on the + // thread that created this TestSimpleTaskRunner. + void RunUntilIdle(); + + protected: + ~TestSimpleTaskRunner() override; + + private: + // Thread on which this was instantiated. + const PlatformThreadRef thread_ref_ = PlatformThread::CurrentRef(); + + // Synchronizes access to |pending_tasks_|. + mutable Lock lock_; + + base::circular_deque<TestPendingTask> pending_tasks_; + + DISALLOW_COPY_AND_ASSIGN(TestSimpleTaskRunner); +}; + +} // namespace base + +#endif // BASE_TEST_TEST_SIMPLE_TASK_RUNNER_H_ diff --git a/chromium/base/test/test_suite.cc b/chromium/base/test/test_suite.cc new file mode 100644 index 00000000000..7aefd46640a --- /dev/null +++ b/chromium/base/test/test_suite.cc @@ -0,0 +1,667 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_suite.h" + +#include <signal.h> + +#include <memory> + +#include "base/at_exit.h" +#include "base/base_paths.h" +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/debug/debugger.h" +#include "base/debug/profiler.h" +#include "base/debug/stack_trace.h" +#include "base/feature_list.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/i18n/icu_util.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/no_destructor.h" +#include "base/path_service.h" +#include "base/process/launch.h" +#include "base/process/memory.h" +#include "base/process/process.h" +#include "base/process/process_handle.h" +#include "base/task/thread_pool/thread_pool_instance.h" +#include "base/test/gtest_xml_unittest_result_printer.h" +#include "base/test/gtest_xml_util.h" +#include "base/test/icu_test_util.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/mock_entropy_provider.h" +#include "base/test/multiprocess_test.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/scoped_run_loop_timeout.h" +#include "base/test/test_switches.h" +#include "base/test/test_timeouts.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/process/port_provider_mac.h" +#if defined(OS_IOS) +#include "base/test/test_listener_ios.h" +#endif // OS_IOS +#endif // OS_MACOSX + +#include "base/i18n/rtl.h" +#if !defined(OS_IOS) +#include "base/strings/string_util.h" +#include "third_party/icu/source/common/unicode/uloc.h" +#endif + +#if defined(OS_ANDROID) +#include "base/test/test_support_android.h" +#endif + +#if defined(OS_IOS) +#include "base/test/test_support_ios.h" +#endif + +#if defined(OS_LINUX) +#include "base/test/fontconfig_util_linux.h" +#endif + +#if defined(OS_FUCHSIA) +#include "base/base_paths_fuchsia.h" +#endif + +#if defined(OS_WIN) && defined(_DEBUG) +#include <crtdbg.h> +#endif + +namespace base { + +namespace { + +// Returns true if the test is marked as "MAYBE_". +// When using different prefixes depending on platform, we use MAYBE_ and +// preprocessor directives to replace MAYBE_ with the target prefix. +bool IsMarkedMaybe(const testing::TestInfo& test) { + return strncmp(test.name(), "MAYBE_", 6) == 0; +} + +class DisableMaybeTests : public testing::EmptyTestEventListener { + public: + void OnTestStart(const testing::TestInfo& test_info) override { + ASSERT_FALSE(IsMarkedMaybe(test_info)) + << "Probably the OS #ifdefs don't include all of the necessary " + "platforms.\nPlease ensure that no tests have the MAYBE_ prefix " + "after the code is preprocessed."; + } +}; + +class ResetCommandLineBetweenTests : public testing::EmptyTestEventListener { + public: + ResetCommandLineBetweenTests() : old_command_line_(CommandLine::NO_PROGRAM) {} + + void OnTestStart(const testing::TestInfo& test_info) override { + old_command_line_ = *CommandLine::ForCurrentProcess(); + } + + void OnTestEnd(const testing::TestInfo& test_info) override { + *CommandLine::ForCurrentProcess() = old_command_line_; + } + + private: + CommandLine old_command_line_; + + DISALLOW_COPY_AND_ASSIGN(ResetCommandLineBetweenTests); +}; + +// Initializes a base::test::ScopedFeatureList for each individual test, which +// involves a FeatureList and a FieldTrialList, such that unit test don't need +// to initialize them manually. +class FeatureListScopedToEachTest : public testing::EmptyTestEventListener { + public: + FeatureListScopedToEachTest() = default; + ~FeatureListScopedToEachTest() override = default; + + FeatureListScopedToEachTest(const FeatureListScopedToEachTest&) = delete; + FeatureListScopedToEachTest& operator=(const FeatureListScopedToEachTest&) = + delete; + + void OnTestStart(const testing::TestInfo& test_info) override { + field_trial_list_ = std::make_unique<FieldTrialList>( + std::make_unique<MockEntropyProvider>()); + + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + + // Set up a FeatureList instance, so that code using that API will not hit a + // an error that it's not set. It will be cleared automatically. + // TestFeatureForBrowserTest1 and TestFeatureForBrowserTest2 used in + // ContentBrowserTestScopedFeatureListTest to ensure ScopedFeatureList keeps + // features from command line. + std::string enabled = + command_line->GetSwitchValueASCII(switches::kEnableFeatures); + std::string disabled = + command_line->GetSwitchValueASCII(switches::kDisableFeatures); + enabled += ",TestFeatureForBrowserTest1"; + disabled += ",TestFeatureForBrowserTest2"; + scoped_feature_list_.InitFromCommandLine(enabled, disabled); + + // The enable-features and disable-features flags were just slurped into a + // FeatureList, so remove them from the command line. Tests should enable + // and disable features via the ScopedFeatureList API rather than + // command-line flags. + CommandLine new_command_line(command_line->GetProgram()); + CommandLine::SwitchMap switches = command_line->GetSwitches(); + + switches.erase(switches::kEnableFeatures); + switches.erase(switches::kDisableFeatures); + + for (const auto& iter : switches) + new_command_line.AppendSwitchNative(iter.first, iter.second); + + *CommandLine::ForCurrentProcess() = new_command_line; + } + + void OnTestEnd(const testing::TestInfo& test_info) override { + scoped_feature_list_.Reset(); + field_trial_list_.reset(); + } + + private: + std::unique_ptr<FieldTrialList> field_trial_list_; + test::ScopedFeatureList scoped_feature_list_; +}; + +class CheckForLeakedGlobals : public testing::EmptyTestEventListener { + public: + CheckForLeakedGlobals() = default; + + // Check for leaks in individual tests. + void OnTestStart(const testing::TestInfo& test) override { + feature_list_set_before_test_ = FeatureList::GetInstance(); + thread_pool_set_before_test_ = ThreadPoolInstance::Get(); + } + void OnTestEnd(const testing::TestInfo& test) override { + DCHECK_EQ(feature_list_set_before_test_, FeatureList::GetInstance()) + << " in test " << test.test_case_name() << "." << test.name(); + DCHECK_EQ(thread_pool_set_before_test_, ThreadPoolInstance::Get()) + << " in test " << test.test_case_name() << "." << test.name(); + } + + // Check for leaks in test cases (consisting of one or more tests). + void OnTestCaseStart(const testing::TestCase& test_case) override { + feature_list_set_before_case_ = FeatureList::GetInstance(); + thread_pool_set_before_case_ = ThreadPoolInstance::Get(); + } + void OnTestCaseEnd(const testing::TestCase& test_case) override { + DCHECK_EQ(feature_list_set_before_case_, FeatureList::GetInstance()) + << " in case " << test_case.name(); + DCHECK_EQ(thread_pool_set_before_case_, ThreadPoolInstance::Get()) + << " in case " << test_case.name(); + } + + private: + FeatureList* feature_list_set_before_test_ = nullptr; + FeatureList* feature_list_set_before_case_ = nullptr; + ThreadPoolInstance* thread_pool_set_before_test_ = nullptr; + ThreadPoolInstance* thread_pool_set_before_case_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(CheckForLeakedGlobals); +}; + +// base::Process is not available on iOS +#if !defined(OS_IOS) +class CheckProcessPriority : public testing::EmptyTestEventListener { + public: + CheckProcessPriority() { CHECK(!IsProcessBackgrounded()); } + + void OnTestStart(const testing::TestInfo& test) override { + EXPECT_FALSE(IsProcessBackgrounded()); + } + void OnTestEnd(const testing::TestInfo& test) override { +#if !defined(OS_MACOSX) + // Flakes are found on Mac OS 10.11. See https://crbug.com/931721#c7. + EXPECT_FALSE(IsProcessBackgrounded()); +#endif + } + + private: +#if defined(OS_MACOSX) + // Returns the calling process's task port, ignoring its argument. + class CurrentProcessPortProvider : public PortProvider { + mach_port_t TaskForPid(ProcessHandle process) const override { + // This PortProvider implementation only works for the current process. + CHECK_EQ(process, base::GetCurrentProcessHandle()); + return mach_task_self(); + } + }; +#endif + + bool IsProcessBackgrounded() const { +#if defined(OS_MACOSX) + CurrentProcessPortProvider port_provider; + return Process::Current().IsProcessBackgrounded(&port_provider); +#else + return Process::Current().IsProcessBackgrounded(); +#endif + } + + DISALLOW_COPY_AND_ASSIGN(CheckProcessPriority); +}; +#endif // !defined(OS_IOS) + +class CheckThreadPriority : public testing::EmptyTestEventListener { + public: + CheckThreadPriority(bool check_thread_priority_at_test_end) + : check_thread_priority_at_test_end_(check_thread_priority_at_test_end) { + CHECK_EQ(base::PlatformThread::GetCurrentThreadPriority(), + base::ThreadPriority::NORMAL) + << " -- The thread priority of this process is not the default. This " + "usually indicates nice has been used, which is not supported."; + } + + void OnTestStart(const testing::TestInfo& test) override { + EXPECT_EQ(base::PlatformThread::GetCurrentThreadPriority(), + base::ThreadPriority::NORMAL) + << " -- The thread priority of this process is not the default. This " + "usually indicates nice has been used, which is not supported."; + } + void OnTestEnd(const testing::TestInfo& test) override { + if (check_thread_priority_at_test_end_) { + EXPECT_EQ(base::PlatformThread::GetCurrentThreadPriority(), + base::ThreadPriority::NORMAL) + << " -- The thread priority of this process is not the default. This " + "usually indicates nice has been used, which is not supported."; + } + } + + private: + const bool check_thread_priority_at_test_end_; + + DISALLOW_COPY_AND_ASSIGN(CheckThreadPriority); +}; + +const std::string& GetProfileName() { + static const NoDestructor<std::string> profile_name([]() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kProfilingFile)) + return command_line.GetSwitchValueASCII(switches::kProfilingFile); + else + return std::string("test-profile-{pid}"); + }()); + return *profile_name; +} + +void InitializeLogging() { +#if defined(OS_ANDROID) + InitAndroidTestLogging(); +#else + + FilePath log_filename; + FilePath exe; + PathService::Get(FILE_EXE, &exe); + +#if defined(OS_FUCHSIA) + // Write logfiles to /data, because the default log location alongside the + // executable (/pkg) is read-only. + FilePath data_dir; + PathService::Get(DIR_APP_DATA, &data_dir); + log_filename = data_dir.Append(exe.BaseName()) + .ReplaceExtension(FILE_PATH_LITERAL("log")); +#else + log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log")); +#endif // defined(OS_FUCHSIA) + + logging::LoggingSettings settings; + settings.log_file_path = log_filename.value().c_str(); + settings.logging_dest = logging::LOG_TO_ALL; + settings.delete_old = logging::DELETE_OLD_LOG_FILE; + logging::InitLogging(settings); + // We want process and thread IDs because we may have multiple processes. + // Note: temporarily enabled timestamps in an effort to catch bug 6361. + logging::SetLogItems(true, true, true, true); +#endif // !defined(OS_ANDROID) +} + +} // namespace + +int RunUnitTestsUsingBaseTestSuite(int argc, char** argv) { + TestSuite test_suite(argc, argv); + return LaunchUnitTests(argc, argv, + BindOnce(&TestSuite::Run, Unretained(&test_suite))); +} + +TestSuite::TestSuite(int argc, char** argv) { + PreInitialize(); + InitializeFromCommandLine(argc, argv); + // Logging must be initialized before any thread has a chance to call logging + // functions. + InitializeLogging(); +} + +#if defined(OS_WIN) +TestSuite::TestSuite(int argc, wchar_t** argv) { + PreInitialize(); + InitializeFromCommandLine(argc, argv); + // Logging must be initialized before any thread has a chance to call logging + // functions. + InitializeLogging(); +} +#endif // defined(OS_WIN) + +TestSuite::~TestSuite() { + if (initialized_command_line_) + CommandLine::Reset(); +} + +void TestSuite::InitializeFromCommandLine(int argc, char** argv) { + initialized_command_line_ = CommandLine::Init(argc, argv); + testing::InitGoogleTest(&argc, argv); + testing::InitGoogleMock(&argc, argv); + +#if defined(OS_IOS) + InitIOSRunHook(this, argc, argv); +#endif +} + +#if defined(OS_WIN) +void TestSuite::InitializeFromCommandLine(int argc, wchar_t** argv) { + // Windows CommandLine::Init ignores argv anyway. + initialized_command_line_ = CommandLine::Init(argc, NULL); + testing::InitGoogleTest(&argc, argv); + testing::InitGoogleMock(&argc, argv); +} +#endif // defined(OS_WIN) + +void TestSuite::PreInitialize() { + DCHECK(!is_initialized_); + +#if defined(OS_WIN) + testing::GTEST_FLAG(catch_exceptions) = false; +#endif + EnableTerminationOnHeapCorruption(); +#if defined(OS_LINUX) && defined(USE_AURA) + // When calling native char conversion functions (e.g wrctomb) we need to + // have the locale set. In the absence of such a call the "C" locale is the + // default. In the gtk code (below) gtk_init() implicitly sets a locale. + setlocale(LC_ALL, ""); + // We still need number to string conversions to be locale insensitive. + setlocale(LC_NUMERIC, "C"); +#endif // defined(OS_LINUX) && defined(USE_AURA) + + // On Android, AtExitManager is created in + // testing/android/native_test_wrapper.cc before main() is called. +#if !defined(OS_ANDROID) + at_exit_manager_.reset(new AtExitManager); +#endif + + // Don't add additional code to this function. Instead add it to + // Initialize(). See bug 6436. +} + +void TestSuite::AddTestLauncherResultPrinter() { + // Only add the custom printer if requested. + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kTestLauncherOutput)) { + return; + } + + FilePath output_path(CommandLine::ForCurrentProcess()->GetSwitchValuePath( + switches::kTestLauncherOutput)); + + // Do not add the result printer if output path already exists. It's an + // indicator there is a process printing to that file, and we're likely + // its child. Do not clobber the results in that case. + if (PathExists(output_path)) { + LOG(WARNING) << "Test launcher output path " << output_path.AsUTF8Unsafe() + << " exists. Not adding test launcher result printer."; + return; + } + + printer_ = new XmlUnitTestResultPrinter; + CHECK(printer_->Initialize(output_path)) + << "Output path is " << output_path.AsUTF8Unsafe() + << " and PathExists(output_path) is " << PathExists(output_path); + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + listeners.Append(printer_); +} + +// Don't add additional code to this method. Instead add it to +// Initialize(). See bug 6436. +int TestSuite::Run() { +#if defined(OS_IOS) + RunTestsFromIOSApp(); +#endif + +#if defined(OS_MACOSX) + mac::ScopedNSAutoreleasePool scoped_pool; +#endif + + Initialize(); + std::string client_func = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTestChildProcess); + + // Check to see if we are being run as a client process. + if (!client_func.empty()) + return multi_process_function_list::InvokeChildProcessTest(client_func); +#if defined(OS_IOS) + test_listener_ios::RegisterTestEndListener(); +#endif + + int result = RUN_ALL_TESTS(); + +#if defined(OS_MACOSX) + // This MUST happen before Shutdown() since Shutdown() tears down + // objects (such as NotificationService::current()) that Cocoa + // objects use to remove themselves as observers. + scoped_pool.Recycle(); +#endif + + Shutdown(); + + return result; +} + +void TestSuite::DisableCheckForLeakedGlobals() { + DCHECK(!is_initialized_); + check_for_leaked_globals_ = false; +} + +void TestSuite::DisableCheckForThreadAndProcessPriority() { + DCHECK(!is_initialized_); + check_for_thread_and_process_priority_ = false; +} + +void TestSuite::DisableCheckForThreadPriorityAtTestEnd() { + DCHECK(!is_initialized_); + check_for_thread_priority_at_test_end_ = false; +} + +void TestSuite::UnitTestAssertHandler(const char* file, + int line, + const StringPiece summary, + const StringPiece stack_trace) { +#if defined(OS_ANDROID) + // Correlating test stdio with logcat can be difficult, so we emit this + // helpful little hint about what was running. Only do this for Android + // because other platforms don't separate out the relevant logs in the same + // way. + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + if (test_info) { + LOG(ERROR) << "Currently running: " << test_info->test_case_name() << "." + << test_info->name(); + fflush(stderr); + } +#endif // defined(OS_ANDROID) + + // XmlUnitTestResultPrinter inherits gtest format, where assert has summary + // and message. In GTest, summary is just a logged text, and message is a + // logged text, concatenated with stack trace of assert. + // Concatenate summary and stack_trace here, to pass it as a message. + if (printer_) { + const std::string summary_str = summary.as_string(); + const std::string stack_trace_str = summary_str + stack_trace.as_string(); + printer_->OnAssert(file, line, summary_str, stack_trace_str); + } + + // The logging system actually prints the message before calling the assert + // handler. Just exit now to avoid printing too many stack traces. + _exit(1); +} + +#if defined(OS_WIN) +namespace { + +// Handlers for invalid parameter, pure call, and abort. They generate a +// breakpoint to ensure that we get a call stack on these failures. +// These functions should be written to be unique in order to avoid confusing +// call stacks from /OPT:ICF function folding. Printing a unique message or +// returning a unique value will do this. Note that for best results they need +// to be unique from *all* functions in Chrome. +void InvalidParameter(const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t reserved) { + // CRT printed message is sufficient. + __debugbreak(); + _exit(1); +} + +void PureCall() { + fprintf(stderr, "Pure-virtual function call. Terminating.\n"); + __debugbreak(); + _exit(1); +} + +void AbortHandler(int signal) { + // Print EOL after the CRT abort message. + fprintf(stderr, "\n"); + __debugbreak(); +} + +} // namespace +#endif + +void TestSuite::SuppressErrorDialogs() { +#if defined(OS_WIN) + UINT new_flags = + SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX; + + // Preserve existing error mode, as discussed at + // http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx + UINT existing_flags = SetErrorMode(new_flags); + SetErrorMode(existing_flags | new_flags); + +#if defined(_DEBUG) + // Suppress the "Debug Assertion Failed" dialog. + // TODO(hbono): remove this code when gtest has it. + // http://groups.google.com/d/topic/googletestframework/OjuwNlXy5ac/discussion + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); +#endif // defined(_DEBUG) + + // See crbug.com/783040 for test code to trigger all of these failures. + _set_invalid_parameter_handler(InvalidParameter); + _set_purecall_handler(PureCall); + signal(SIGABRT, AbortHandler); +#endif // defined(OS_WIN) +} + +void TestSuite::Initialize() { + DCHECK(!is_initialized_); + + test::ScopedRunLoopTimeout::SetAddGTestFailureOnTimeout(); + + const CommandLine* command_line = CommandLine::ForCurrentProcess(); +#if !defined(OS_IOS) + if (command_line->HasSwitch(switches::kWaitForDebugger)) { + debug::WaitForDebugger(60, true); + } +#endif + +#if defined(OS_IOS) + InitIOSTestMessageLoop(); +#endif // OS_IOS + +#if defined(OS_ANDROID) + InitAndroidTestMessageLoop(); +#endif // else defined(OS_ANDROID) + + CHECK(debug::EnableInProcessStackDumping()); +#if defined(OS_WIN) + RouteStdioToConsole(true); + // Make sure we run with high resolution timer to minimize differences + // between production code and test code. + Time::EnableHighResolutionTimer(true); +#endif // defined(OS_WIN) + + // In some cases, we do not want to see standard error dialogs. + if (!debug::BeingDebugged() && + !command_line->HasSwitch("show-error-dialogs")) { + SuppressErrorDialogs(); + debug::SetSuppressDebugUI(true); + assert_handler_ = std::make_unique<logging::ScopedLogAssertHandler>( + BindRepeating(&TestSuite::UnitTestAssertHandler, Unretained(this))); + } + + test::InitializeICUForTesting(); + + // A number of tests only work if the locale is en_US. This can be an issue + // on all platforms. To fix this we force the default locale to en_US. This + // does not affect tests that explicitly overrides the locale for testing. + // TODO(jshin): Should we set the locale via an OS X locale API here? + i18n::SetICUDefaultLocale("en_US"); + +#if defined(OS_LINUX) + SetUpFontconfig(); +#endif + + // Add TestEventListeners to enforce certain properties across tests. + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new DisableMaybeTests); + listeners.Append(new ResetCommandLineBetweenTests); + listeners.Append(new FeatureListScopedToEachTest); + if (check_for_leaked_globals_) + listeners.Append(new CheckForLeakedGlobals); + if (check_for_thread_and_process_priority_) { +#if !defined(OS_ANDROID) + // TODO(https://crbug.com/931706): Check thread priority on Android. + listeners.Append( + new CheckThreadPriority(check_for_thread_priority_at_test_end_)); +#endif +#if !defined(OS_IOS) + listeners.Append(new CheckProcessPriority); +#endif + } + + AddTestLauncherResultPrinter(); + + TestTimeouts::Initialize(); + + trace_to_file_.BeginTracingFromCommandLineOptions(); + + debug::StartProfiling(GetProfileName()); + + debug::VerifyDebugger(); + + is_initialized_ = true; +} + +void TestSuite::Shutdown() { + DCHECK(is_initialized_); + debug::StopProfiling(); +} + +} // namespace base diff --git a/chromium/base/test/test_suite.h b/chromium/base/test/test_suite.h new file mode 100644 index 00000000000..372c5f58a8a --- /dev/null +++ b/chromium/base/test/test_suite.h @@ -0,0 +1,110 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_SUITE_H_ +#define BASE_TEST_TEST_SUITE_H_ + +// Defines a basic test suite framework for running gtest based tests. You can +// instantiate this class in your main function and call its Run method to run +// any gtest based tests that are linked into your executable. + +#include <memory> +#include <string> + +#include "base/at_exit.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/test/trace_to_file.h" +#include "build/build_config.h" + +namespace testing { +class TestInfo; +} + +namespace base { + +class XmlUnitTestResultPrinter; + +// Instantiates TestSuite, runs it and returns exit code. +int RunUnitTestsUsingBaseTestSuite(int argc, char** argv); + +class TestSuite { + public: + // Match function used by the GetTestCount method. + typedef bool (*TestMatch)(const testing::TestInfo&); + + TestSuite(int argc, char** argv); +#if defined(OS_WIN) + TestSuite(int argc, wchar_t** argv); +#endif // defined(OS_WIN) + virtual ~TestSuite(); + + int Run(); + + // Disables checks for thread and process priority at the beginning and end of + // each test. Most tests should not use this. + void DisableCheckForThreadAndProcessPriority(); + + // Disables checks for thread priority at the end of each test (still checks + // at the beginning of each test). This should be used for tests that run in + // their own process and should start with normal priorities but are allowed + // to end with different priorities. + void DisableCheckForThreadPriorityAtTestEnd(); + + // Disables checks for certain global objects being leaked across tests. + void DisableCheckForLeakedGlobals(); + + protected: + // By default fatal log messages (e.g. from DCHECKs) result in error dialogs + // which gum up buildbots. Use a minimalistic assert handler which just + // terminates the process. + void UnitTestAssertHandler(const char* file, + int line, + const base::StringPiece summary, + const base::StringPiece stack_trace); + + // Disable crash dialogs so that it doesn't gum up the buildbot + virtual void SuppressErrorDialogs(); + + // Override these for custom initialization and shutdown handling. Use these + // instead of putting complex code in your constructor/destructor. + + virtual void Initialize(); + virtual void Shutdown(); + + // Make sure that we setup an AtExitManager so Singleton objects will be + // destroyed. + std::unique_ptr<base::AtExitManager> at_exit_manager_; + + private: + void AddTestLauncherResultPrinter(); + + void InitializeFromCommandLine(int argc, char** argv); +#if defined(OS_WIN) + void InitializeFromCommandLine(int argc, wchar_t** argv); +#endif // defined(OS_WIN) + + // Basic initialization for the test suite happens here. + void PreInitialize(); + + test::TraceToFile trace_to_file_; + + bool initialized_command_line_ = false; + + XmlUnitTestResultPrinter* printer_ = nullptr; + + std::unique_ptr<logging::ScopedLogAssertHandler> assert_handler_; + + bool check_for_leaked_globals_ = true; + bool check_for_thread_and_process_priority_ = true; + bool check_for_thread_priority_at_test_end_ = true; + + bool is_initialized_ = false; + + DISALLOW_COPY_AND_ASSIGN(TestSuite); +}; + +} // namespace base + +#endif // BASE_TEST_TEST_SUITE_H_ diff --git a/chromium/base/test/test_support_android.cc b/chromium/base/test/test_support_android.cc new file mode 100644 index 00000000000..0a8a5b2db18 --- /dev/null +++ b/chromium/base/test/test_support_android.cc @@ -0,0 +1,223 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdarg.h> +#include <string.h> + +#include "base/android/path_utils.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/message_loop/message_pump.h" +#include "base/message_loop/message_pump_android.h" +#include "base/path_service.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/multiprocess_test.h" + +namespace { + +base::FilePath* g_test_data_dir = nullptr; + +struct RunState { + RunState(base::MessagePump::Delegate* delegate, int run_depth) + : delegate(delegate), + run_depth(run_depth), + should_quit(false) { + } + + base::MessagePump::Delegate* delegate; + + // Used to count how many Run() invocations are on the stack. + int run_depth; + + // Used to flag that the current Run() invocation should return ASAP. + bool should_quit; +}; + +RunState* g_state = nullptr; + +// A singleton WaitableEvent wrapper so we avoid a busy loop in +// MessagePumpForUIStub. Other platforms use the native event loop which blocks +// when there are no pending messages. +class Waitable { + public: + static Waitable* GetInstance() { + return base::Singleton<Waitable, + base::LeakySingletonTraits<Waitable>>::get(); + } + + // Signals that there are more work to do. + void Signal() { waitable_event_.Signal(); } + + // Blocks until more work is scheduled. + void Block() { waitable_event_.Wait(); } + + void Quit() { + g_state->should_quit = true; + Signal(); + } + + private: + friend struct base::DefaultSingletonTraits<Waitable>; + + Waitable() + : waitable_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED) {} + + base::WaitableEvent waitable_event_; + + DISALLOW_COPY_AND_ASSIGN(Waitable); +}; + +// The MessagePumpForUI implementation for test purpose. +class MessagePumpForUIStub : public base::MessagePumpForUI { + public: + MessagePumpForUIStub() : base::MessagePumpForUI() { Waitable::GetInstance(); } + ~MessagePumpForUIStub() override {} + + bool IsTestImplementation() const override { return true; } + + // In tests, there isn't a native thread, as such RunLoop::Run() should be + // used to run the loop instead of attaching and delegating to the native + // loop. As such, this override ignores the Attach() request. + void Attach(base::MessagePump::Delegate* delegate) override {} + + void Run(base::MessagePump::Delegate* delegate) override { + // The following was based on message_pump_glib.cc, except we're using a + // WaitableEvent since there are no native message loop to use. + RunState state(delegate, g_state ? g_state->run_depth + 1 : 1); + + RunState* previous_state = g_state; + g_state = &state; + + // When not nested we can use the real implementation, otherwise fall back + // to the stub implementation. + if (g_state->run_depth > 1) { + RunNested(delegate); + } else { + MessagePumpForUI::Run(delegate); + } + + g_state = previous_state; + } + + void RunNested(base::MessagePump::Delegate* delegate) { + bool more_work_is_plausible = true; + + for (;;) { + if (!more_work_is_plausible) { + Waitable::GetInstance()->Block(); + if (g_state->should_quit) + break; + } + + Delegate::NextWorkInfo next_work_info = g_state->delegate->DoWork(); + more_work_is_plausible = next_work_info.is_immediate(); + if (g_state->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = g_state->delegate->DoIdleWork(); + if (g_state->should_quit) + break; + + more_work_is_plausible |= !next_work_info.delayed_run_time.is_max(); + } + } + + void Quit() override { + CHECK(g_state); + if (g_state->run_depth > 1) { + Waitable::GetInstance()->Quit(); + } else { + MessagePumpForUI::Quit(); + } + } + + void ScheduleWork() override { + if (g_state && g_state->run_depth > 1) { + Waitable::GetInstance()->Signal(); + } else { + MessagePumpForUI::ScheduleWork(); + } + } + + void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time) override { + if (g_state && g_state->run_depth > 1) { + Waitable::GetInstance()->Signal(); + } else { + MessagePumpForUI::ScheduleDelayedWork(delayed_work_time); + } + } +}; + +std::unique_ptr<base::MessagePump> CreateMessagePumpForUIStub() { + return std::unique_ptr<base::MessagePump>(new MessagePumpForUIStub()); +} + +// Provides the test path for DIR_SOURCE_ROOT and DIR_ANDROID_APP_DATA. +bool GetTestProviderPath(int key, base::FilePath* result) { + switch (key) { + // TODO(agrieve): Stop overriding DIR_ANDROID_APP_DATA. + // https://crbug.com/617734 + // Instead DIR_ASSETS should be used to discover assets file location in + // tests. + case base::DIR_ANDROID_APP_DATA: + case base::DIR_ASSETS: + case base::DIR_SOURCE_ROOT: + CHECK(g_test_data_dir != nullptr); + *result = *g_test_data_dir; + return true; + default: + return false; + } +} + +void InitPathProvider(int key) { + base::FilePath path; + // If failed to override the key, that means the way has not been registered. + if (GetTestProviderPath(key, &path) && + !base::PathService::Override(key, path)) { + base::PathService::RegisterProvider(&GetTestProviderPath, key, key + 1); + } +} + +} // namespace + +namespace base { + +void InitAndroidTestLogging() { + logging::LoggingSettings settings; + settings.logging_dest = + logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR; + logging::InitLogging(settings); + // To view log output with IDs and timestamps use "adb logcat -v threadtime". + logging::SetLogItems(false, // Process ID + false, // Thread ID + false, // Timestamp + false); // Tick count +} + +void InitAndroidTestPaths(const FilePath& test_data_dir) { + if (g_test_data_dir) { + CHECK(test_data_dir == *g_test_data_dir); + return; + } + g_test_data_dir = new FilePath(test_data_dir); + InitPathProvider(DIR_SOURCE_ROOT); + InitPathProvider(DIR_ANDROID_APP_DATA); + InitPathProvider(DIR_ASSETS); +} + +void InitAndroidTestMessageLoop() { + // NOTE something else such as a JNI call may have already overridden the UI + // factory. + if (!MessagePump::IsMessagePumpForUIFactoryOveridden()) + MessagePump::OverrideMessagePumpForUIFactory(&CreateMessagePumpForUIStub); +} + +} // namespace base diff --git a/chromium/base/test/test_support_android.h b/chromium/base/test/test_support_android.h new file mode 100644 index 00000000000..4942e546110 --- /dev/null +++ b/chromium/base/test/test_support_android.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_SUPPORT_ANDROID_H_ +#define BASE_TEST_TEST_SUPPORT_ANDROID_H_ + +#include "base/base_export.h" + +namespace base { + +class FilePath; + +// Init logging for tests on Android. Logs will be output into Android's logcat. +BASE_EXPORT void InitAndroidTestLogging(); + +// Init path providers for tests on Android. +BASE_EXPORT void InitAndroidTestPaths(const FilePath& test_data_dir); + +// Init the message loop for tests on Android. +BASE_EXPORT void InitAndroidTestMessageLoop(); + +} // namespace base + +#endif // BASE_TEST_TEST_SUPPORT_ANDROID_H_ diff --git a/chromium/base/test/test_support_ios.h b/chromium/base/test/test_support_ios.h new file mode 100644 index 00000000000..9064cb0973f --- /dev/null +++ b/chromium/base/test/test_support_ios.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_SUPPORT_IOS_H_ +#define BASE_TEST_TEST_SUPPORT_IOS_H_ + +#include "base/test/test_suite.h" + +namespace base { + +// Inits the message loop for tests on iOS. +void InitIOSTestMessageLoop(); + +// Inits the run hook for tests on iOS. +void InitIOSRunHook(TestSuite* suite, int argc, char* argv[]); + +// Launches an iOS app that runs the tests in the suite passed to +// InitIOSRunHook. +void RunTestsFromIOSApp(); + +// Returns true if unittests should be run by the XCTest runnner. +bool ShouldRunIOSUnittestsWithXCTest(); + +} // namespace base + +#endif // BASE_TEST_TEST_SUPPORT_IOS_H_ diff --git a/chromium/base/test/test_support_ios.mm b/chromium/base/test/test_support_ios.mm new file mode 100644 index 00000000000..be949a11ee8 --- /dev/null +++ b/chromium/base/test/test_support_ios.mm @@ -0,0 +1,246 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "base/test/test_support_ios.h" + +#import <UIKit/UIKit.h> + +#include "base/check.h" +#include "base/command_line.h" +#include "base/debug/debugger.h" +#include "base/mac/scoped_nsobject.h" +#include "base/message_loop/message_pump.h" +#include "base/message_loop/message_pump_mac.h" +#import "base/test/ios/google_test_runner_delegate.h" +#include "base/test/test_suite.h" +#include "base/test/test_switches.h" +#include "testing/coverage_util_ios.h" + +// Springboard will kill any iOS app that fails to check in after launch within +// a given time. Starting a UIApplication before invoking TestSuite::Run +// prevents this from happening. + +// InitIOSRunHook saves the TestSuite and argc/argv, then invoking +// RunTestsFromIOSApp calls UIApplicationMain(), providing an application +// delegate class: ChromeUnitTestDelegate. The delegate implements +// application:didFinishLaunchingWithOptions: to invoke the TestSuite's Run +// method. + +// Since the executable isn't likely to be a real iOS UI, the delegate puts up a +// window displaying the app name. If a bunch of apps using MainHook are being +// run in a row, this provides an indication of which one is currently running. + +static base::TestSuite* g_test_suite = NULL; +static int g_argc; +static char** g_argv; + +@interface UIApplication (Testing) +- (void)_terminateWithStatus:(int)status; +@end + +#if TARGET_IPHONE_SIMULATOR +// Xcode 6 introduced behavior in the iOS Simulator where the software +// keyboard does not appear if a hardware keyboard is connected. The following +// declaration allows this behavior to be overriden when the app starts up. +@interface UIKeyboardImpl ++ (instancetype)sharedInstance; +- (void)setAutomaticMinimizationEnabled:(BOOL)enabled; +- (void)setSoftwareKeyboardShownByTouch:(BOOL)enabled; +@end +#endif // TARGET_IPHONE_SIMULATOR + +@interface ChromeUnitTestDelegate : NSObject <GoogleTestRunnerDelegate> { + base::scoped_nsobject<UIWindow> _window; +} +- (void)runTests; +@end + +@implementation ChromeUnitTestDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + +#if TARGET_IPHONE_SIMULATOR + // Xcode 6 introduced behavior in the iOS Simulator where the software + // keyboard does not appear if a hardware keyboard is connected. The following + // calls override this behavior by ensuring that the software keyboard is + // always shown. + [[UIKeyboardImpl sharedInstance] setAutomaticMinimizationEnabled:NO]; + [[UIKeyboardImpl sharedInstance] setSoftwareKeyboardShownByTouch:YES]; +#endif // TARGET_IPHONE_SIMULATOR + + CGRect bounds = [[UIScreen mainScreen] bounds]; + + // Yes, this is leaked, it's just to make what's running visible. + _window.reset([[UIWindow alloc] initWithFrame:bounds]); + [_window setBackgroundColor:[UIColor whiteColor]]; + [_window makeKeyAndVisible]; + + // Add a label with the app name. + UILabel* label = [[[UILabel alloc] initWithFrame:bounds] autorelease]; + label.text = [[NSProcessInfo processInfo] processName]; + label.textAlignment = NSTextAlignmentCenter; + [_window addSubview:label]; + + // An NSInternalInconsistencyException is thrown if the app doesn't have a + // root view controller. Set an empty one here. + [_window setRootViewController:[[[UIViewController alloc] init] autorelease]]; + + if ([self shouldRedirectOutputToFile]) + [self redirectOutput]; + + // Queue up the test run. + if (!base::ShouldRunIOSUnittestsWithXCTest()) { + // When running in XCTest mode, XCTest will invoke |runGoogleTest| directly. + // Otherwise, schedule a call to |runTests|. + [self performSelector:@selector(runTests) withObject:nil afterDelay:0.1]; + } + + return YES; +} + +// Returns true if the gtest output should be redirected to a file, then sent +// to NSLog when complete. This redirection is used because gtest only writes +// output to stdout, but results must be written to NSLog in order to show up in +// the device log that is retrieved from the device by the host. +- (BOOL)shouldRedirectOutputToFile { +#if !TARGET_IPHONE_SIMULATOR + // Tests in XCTest mode don't need to redirect output to a file because the + // test result parser analyzes console output. + return !base::ShouldRunIOSUnittestsWithXCTest() && + !base::debug::BeingDebugged(); +#else + return NO; +#endif // TARGET_IPHONE_SIMULATOR +} + +// Returns the path to the directory to store gtest output files. +- (NSString*)outputPath { + NSArray* searchPath = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, + NSUserDomainMask, + YES); + CHECK([searchPath count] > 0) << "Failed to get the Documents folder"; + return [searchPath objectAtIndex:0]; +} + +// Returns the path to file that stdout is redirected to. +- (NSString*)stdoutPath { + return [[self outputPath] stringByAppendingPathComponent:@"stdout.log"]; +} + +// Returns the path to file that stderr is redirected to. +- (NSString*)stderrPath { + return [[self outputPath] stringByAppendingPathComponent:@"stderr.log"]; +} + +// Redirects stdout and stderr to files in the Documents folder in the app's +// sandbox. +- (void)redirectOutput { + freopen([[self stdoutPath] UTF8String], "w+", stdout); + freopen([[self stderrPath] UTF8String], "w+", stderr); +} + +// Reads the redirected gtest output from a file and writes it to NSLog. +- (void)writeOutputToNSLog { + // Close the redirected stdout and stderr files so that the content written to + // NSLog doesn't end up in these files. + fclose(stdout); + fclose(stderr); + for (NSString* path in @[ [self stdoutPath], [self stderrPath]]) { + NSString* content = [NSString stringWithContentsOfFile:path + encoding:NSUTF8StringEncoding + error:NULL]; + NSArray* lines = [content componentsSeparatedByCharactersInSet: + [NSCharacterSet newlineCharacterSet]]; + + NSLog(@"Writing contents of %@ to NSLog", path); + for (NSString* line in lines) { + NSLog(@"%@", line); + } + } +} + +- (BOOL)supportsRunningGoogleTests { + return base::ShouldRunIOSUnittestsWithXCTest(); +} + +- (int)runGoogleTests { + coverage_util::ConfigureCoverageReportPath(); + + int exitStatus = g_test_suite->Run(); + + if ([self shouldRedirectOutputToFile]) + [self writeOutputToNSLog]; + + return exitStatus; +} + +- (void)runTests { + DCHECK(!base::ShouldRunIOSUnittestsWithXCTest()); + + int exitStatus = [self runGoogleTests]; + + // If a test app is too fast, it will exit before Instruments has has a + // a chance to initialize and no test results will be seen. + [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; + _window.reset(); + + // Use the hidden selector to try and cleanly take down the app (otherwise + // things can think the app crashed even on a zero exit status). + UIApplication* application = [UIApplication sharedApplication]; + [application _terminateWithStatus:exitStatus]; + + exit(exitStatus); +} + +@end + +namespace { + +std::unique_ptr<base::MessagePump> CreateMessagePumpForUIForTests() { + // A basic MessagePump will do quite nicely in tests. + return std::unique_ptr<base::MessagePump>(new base::MessagePumpCFRunLoop()); +} + +} // namespace + +namespace base { + +void InitIOSTestMessageLoop() { + MessagePump::OverrideMessagePumpForUIFactory(&CreateMessagePumpForUIForTests); +} + +void InitIOSRunHook(TestSuite* suite, int argc, char* argv[]) { + g_test_suite = suite; + g_argc = argc; + g_argv = argv; +} + +void RunTestsFromIOSApp() { + // When TestSuite::Run is invoked it calls RunTestsFromIOSApp(). On the first + // invocation, this method fires up an iOS app via UIApplicationMain. Since + // UIApplicationMain does not return until the app exits, control does not + // return to the initial TestSuite::Run invocation, so the app invokes + // TestSuite::Run a second time and since |ran_hook| is true at this point, + // this method is a no-op and control returns to TestSuite:Run so that test + // are executed. Once the app exits, RunTestsFromIOSApp calls exit() so that + // control is not returned to the initial invocation of TestSuite::Run. + static bool ran_hook = false; + if (!ran_hook) { + ran_hook = true; + @autoreleasepool { + int exit_status = + UIApplicationMain(g_argc, g_argv, nil, @"ChromeUnitTestDelegate"); + exit(exit_status); + } + } +} + +bool ShouldRunIOSUnittestsWithXCTest() { + return base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableRunIOSUnittestsWithXCTest); +} + +} // namespace base diff --git a/chromium/base/test/test_switches.cc b/chromium/base/test/test_switches.cc new file mode 100644 index 00000000000..ec022ced181 --- /dev/null +++ b/chromium/base/test/test_switches.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_switches.h" + +// Flag to show the help message. +const char switches::kHelpFlag[] = "help"; + +// Flag to run all tests and the launcher in a single process. Useful for +// debugging a specific test in a debugger. +const char switches::kSingleProcessTests[] = "single-process-tests"; + +// Maximum number of tests to run in a single batch. +const char switches::kTestLauncherBatchLimit[] = "test-launcher-batch-limit"; + +// Sets defaults desirable for the continuous integration bots, e.g. parallel +// test execution and test retries. +const char switches::kTestLauncherBotMode[] = + "test-launcher-bot-mode"; + +// Makes it possible to debug the launcher itself. By default the launcher +// automatically switches to single process mode when it detects presence +// of debugger. +const char switches::kTestLauncherDebugLauncher[] = + "test-launcher-debug-launcher"; + +// Force running all requested tests and retries even if too many test errors +// occur. +const char switches::kTestLauncherForceRunBrokenTests[] = + "test-launcher-force-run-broken-tests"; + +// List of paths to files (separated by ';') containing test filters (one +// pattern per line). +const char switches::kTestLauncherFilterFile[] = "test-launcher-filter-file"; + +// Whether the test launcher should launch in "interactive mode", which disables +// timeouts (and may have other effects for specific test types). +const char switches::kTestLauncherInteractive[] = "test-launcher-interactive"; + +// Number of parallel test launcher jobs. +const char switches::kTestLauncherJobs[] = "test-launcher-jobs"; + +// Path to list of compiled in tests. +const char switches::kTestLauncherListTests[] = "test-launcher-list-tests"; + +// Path to test results file in our custom test launcher format. +const char switches::kTestLauncherOutput[] = "test-launcher-output"; + +// These two flags has the same effect, but don't use them at the same time. +// And isolated-script-test-launcher-retry-limit is preferred in the future. +// Maximum number of times to retry a test after failure. +const char switches::kTestLauncherRetryLimit[] = "test-launcher-retry-limit"; +const char switches::kIsolatedScriptTestLauncherRetryLimit[] = + "isolated-script-test-launcher-retry-limit"; + +// Path to test results file with all the info from the test launcher. +const char switches::kTestLauncherSummaryOutput[] = + "test-launcher-summary-output"; + +// Causes the test launcher to print information about leaked files and/or +// directories in child process's temporary directories. +const char switches::kTestLauncherPrintTempLeaks[] = + "test-launcher-print-temp-leaks"; + +// Flag controlling when test stdio is displayed as part of the launcher's +// standard output. +const char switches::kTestLauncherPrintTestStdio[] = + "test-launcher-print-test-stdio"; + +// Print a writable path and exit (for internal use). +const char switches::kTestLauncherPrintWritablePath[] = + "test-launcher-print-writable-path"; + +// Index of the test shard to run, starting from 0 (first shard) to total shards +// minus one (last shard). +const char switches::kTestLauncherShardIndex[] = + "test-launcher-shard-index"; + +// Limit of test part results in the output. Default limit is 10. +// Negative value will completely disable limit. +const char switches::kTestLauncherTestPartResultsLimit[] = + "test-launcher-test-part-results-limit"; + +// Total number of shards. Must be the same for all shards. +const char switches::kTestLauncherTotalShards[] = + "test-launcher-total-shards"; + +// Time (in milliseconds) that the tests should wait before timing out. +const char switches::kTestLauncherTimeout[] = "test-launcher-timeout"; + +// Path where to save a trace of test launcher's execution. +const char switches::kTestLauncherTrace[] = "test-launcher-trace"; + +// TODO(phajdan.jr): Clean up the switch names. +const char switches::kTestTinyTimeout[] = "test-tiny-timeout"; +const char switches::kUiTestActionTimeout[] = "ui-test-action-timeout"; +const char switches::kUiTestActionMaxTimeout[] = "ui-test-action-max-timeout"; + +#if defined(OS_IOS) +// If enabled, runs unittests using the XCTest test runner. +const char switches::kEnableRunIOSUnittestsWithXCTest[] = + "enable-run-ios-unittests-with-xctest"; +#endif diff --git a/chromium/base/test/test_switches.h b/chromium/base/test/test_switches.h new file mode 100644 index 00000000000..9e2e627e407 --- /dev/null +++ b/chromium/base/test/test_switches.h @@ -0,0 +1,46 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_SWITCHES_H_ +#define BASE_TEST_TEST_SWITCHES_H_ + +#include "build/build_config.h" + +namespace switches { + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +extern const char kHelpFlag[]; +extern const char kSingleProcessTests[]; +extern const char kTestLauncherBatchLimit[]; +extern const char kTestLauncherBotMode[]; +extern const char kTestLauncherDebugLauncher[]; +extern const char kTestLauncherForceRunBrokenTests[]; +extern const char kTestLauncherFilterFile[]; +extern const char kTestLauncherInteractive[]; +extern const char kTestLauncherJobs[]; +extern const char kTestLauncherListTests[]; +extern const char kTestLauncherOutput[]; +extern const char kTestLauncherRetryLimit[]; +extern const char kIsolatedScriptTestLauncherRetryLimit[]; +extern const char kTestLauncherSummaryOutput[]; +extern const char kTestLauncherPrintTempLeaks[]; +extern const char kTestLauncherPrintTestStdio[]; +extern const char kTestLauncherPrintWritablePath[]; +extern const char kTestLauncherShardIndex[]; +extern const char kTestLauncherTestPartResultsLimit[]; +extern const char kTestLauncherTotalShards[]; +extern const char kTestLauncherTimeout[]; +extern const char kTestLauncherTrace[]; +extern const char kTestTinyTimeout[]; +extern const char kUiTestActionTimeout[]; +extern const char kUiTestActionMaxTimeout[]; + +#if defined(OS_IOS) +extern const char kEnableRunIOSUnittestsWithXCTest[]; +#endif + +} // namespace switches + +#endif // BASE_TEST_TEST_SWITCHES_H_ diff --git a/chromium/base/test/test_timeouts.cc b/chromium/base/test/test_timeouts.cc new file mode 100644 index 00000000000..e77f17569c0 --- /dev/null +++ b/chromium/base/test/test_timeouts.cc @@ -0,0 +1,132 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_timeouts.h" + +#include <algorithm> +#include <string> + +#include "base/clang_profiling_buildflags.h" +#include "base/command_line.h" +#include "base/debug/debugger.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/test/test_switches.h" +#include "build/build_config.h" + +namespace { + +// Sets value to the greatest of: +// 1) value's current value multiplied by kTimeoutMultiplier (assuming +// InitializeTimeout is called only once per value). +// 2) min_value. +// 3) the numerical value given by switch_name on the command line multiplied +// by kTimeoutMultiplier. +void InitializeTimeout(const char* switch_name, int min_value, int* value) { + DCHECK(value); + int command_line_timeout = 0; + if (base::CommandLine::ForCurrentProcess()->HasSwitch(switch_name)) { + std::string string_value(base::CommandLine::ForCurrentProcess()-> + GetSwitchValueASCII(switch_name)); + if (!base::StringToInt(string_value, &command_line_timeout)) { + LOG(FATAL) << "Timeout value \"" << string_value << "\" was parsed as " + << command_line_timeout; + } + } + +#if defined(MEMORY_SANITIZER) + // ASan/TSan/MSan instrument each memory access. This may slow the execution + // down significantly. + // For MSan the slowdown depends heavily on the value of msan_track_origins + // build flag. The multiplier below corresponds to msan_track_origins = 1. +#if defined(OS_CHROMEOS) + // A handful of tests on ChromeOS run *very* close to the 6x limit used + // else where, so it's bumped to 7x. + constexpr int kTimeoutMultiplier = 7; +#else + constexpr int kTimeoutMultiplier = 6; +#endif +#elif defined(ADDRESS_SANITIZER) && defined(OS_WIN) + // ASan/Win has not been optimized yet, give it a higher + // timeout multiplier. See http://crbug.com/412471 + constexpr int kTimeoutMultiplier = 3; +#elif defined(ADDRESS_SANITIZER) && defined(OS_CHROMEOS) + // A number of tests on ChromeOS run very close to the 2x limit, so ChromeOS + // gets 3x. + constexpr int kTimeoutMultiplier = 3; +#elif defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) + constexpr int kTimeoutMultiplier = 2; +#elif BUILDFLAG(CLANG_PROFILING) + // On coverage build, tests run 3x slower. + constexpr int kTimeoutMultiplier = 3; +#elif !defined(NDEBUG) && defined(OS_CHROMEOS) + // TODO(crbug.com/1058022): reduce the multiplier back to 2x. + // A number of tests on ChromeOS run very close to the base limit, so ChromeOS + // gets 3x. + constexpr int kTimeoutMultiplier = 3; +#else + constexpr int kTimeoutMultiplier = 1; +#endif + + *value = std::max(std::max(*value, command_line_timeout) * kTimeoutMultiplier, + min_value); +} + +} // namespace + +// static +bool TestTimeouts::initialized_ = false; + +// The timeout values should increase in the order they appear in this block. +// static +int TestTimeouts::tiny_timeout_ms_ = 100; +int TestTimeouts::action_timeout_ms_ = 10000; +int TestTimeouts::action_max_timeout_ms_ = 30000; +int TestTimeouts::test_launcher_timeout_ms_ = 45000; + +// static +void TestTimeouts::Initialize() { + DCHECK(!initialized_); + initialized_ = true; + + const bool being_debugged = base::debug::BeingDebugged(); + if (being_debugged) { + fprintf(stdout, + "Detected presence of a debugger, running without test timeouts.\n"); + } + + // Note that these timeouts MUST be initialized in the correct order as + // per the CHECKS below. + + InitializeTimeout(switches::kTestTinyTimeout, 0, &tiny_timeout_ms_); + + // All timeouts other than the "tiny" one should be set to very large values + // when in a debugger or when run interactively, so that tests will not get + // auto-terminated. By setting the UI test action timeout to at least this + // value, we guarantee the subsequent timeouts will be this large also. + // Setting the "tiny" timeout to a large value as well would make some tests + // hang (because it's used as a task-posting delay). In particular this + // causes problems for some iOS device tests, which are always run inside a + // debugger (thus BeingDebugged() is true even on the bots). + int min_ui_test_action_timeout = tiny_timeout_ms_; + if (being_debugged || base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kTestLauncherInteractive)) { + constexpr int kVeryLargeTimeoutMs = 100'000'000; + min_ui_test_action_timeout = kVeryLargeTimeoutMs; + } + + InitializeTimeout(switches::kUiTestActionTimeout, min_ui_test_action_timeout, + &action_timeout_ms_); + InitializeTimeout(switches::kUiTestActionMaxTimeout, action_timeout_ms_, + &action_max_timeout_ms_); + + // Test launcher timeout is independent from anything above action timeout. + InitializeTimeout(switches::kTestLauncherTimeout, action_timeout_ms_, + &test_launcher_timeout_ms_); + + // The timeout values should be increasing in the right order. + CHECK_LE(tiny_timeout_ms_, action_timeout_ms_); + CHECK_LE(action_timeout_ms_, action_max_timeout_ms_); + CHECK_LE(action_timeout_ms_, test_launcher_timeout_ms_); +} diff --git a/chromium/base/test/test_timeouts.h b/chromium/base/test/test_timeouts.h new file mode 100644 index 00000000000..1bdda2a157b --- /dev/null +++ b/chromium/base/test/test_timeouts.h @@ -0,0 +1,63 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_TIMEOUTS_H_ +#define BASE_TEST_TEST_TIMEOUTS_H_ + +#include "base/logging.h" +#include "base/macros.h" +#include "base/time/time.h" + +// Returns common timeouts to use in tests. Makes it possible to adjust +// the timeouts for different environments (like TSan). +class TestTimeouts { + public: + // Initializes the timeouts. Non thread-safe. Should be called exactly once + // by the test suite. + static void Initialize(); + + // Timeout for actions that are expected to finish "almost instantly". This + // is used in various tests to post delayed tasks and usually functions more + // like a delay value than a timeout. + static base::TimeDelta tiny_timeout() { + DCHECK(initialized_); + return base::TimeDelta::FromMilliseconds(tiny_timeout_ms_); + } + + // Timeout to wait for something to happen. If you are not sure + // which timeout to use, this is the one you want. + static base::TimeDelta action_timeout() { + DCHECK(initialized_); + return base::TimeDelta::FromMilliseconds(action_timeout_ms_); + } + + // Timeout longer than the above, suitable to wait on success conditions which + // can take a while to achieve but still should expire on failure before + // |test_launcher_timeout()| terminates the process. Note that + // test_launcher_timeout() can be reached nonetheless when multiple such + // actions are compounded in the same test. + static base::TimeDelta action_max_timeout() { + DCHECK(initialized_); + return base::TimeDelta::FromMilliseconds(action_max_timeout_ms_); + } + + // Timeout for a single test launched used built-in test launcher. + // Do not use outside of the test launcher. + static base::TimeDelta test_launcher_timeout() { + DCHECK(initialized_); + return base::TimeDelta::FromMilliseconds(test_launcher_timeout_ms_); + } + + private: + static bool initialized_; + + static int tiny_timeout_ms_; + static int action_timeout_ms_; + static int action_max_timeout_ms_; + static int test_launcher_timeout_ms_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(TestTimeouts); +}; + +#endif // BASE_TEST_TEST_TIMEOUTS_H_ diff --git a/chromium/base/test/test_waitable_event.cc b/chromium/base/test/test_waitable_event.cc new file mode 100644 index 00000000000..09f5bb66e0e --- /dev/null +++ b/chromium/base/test/test_waitable_event.cc @@ -0,0 +1,28 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_waitable_event.h" + +#include <utility> + +namespace base { + +TestWaitableEvent::TestWaitableEvent(ResetPolicy reset_policy, + InitialState initial_state) + : WaitableEvent(reset_policy, initial_state) { + // Pretending this is only used while idle ensures this WaitableEvent is not + // instantiating a ScopedBlockingCallWithBaseSyncPrimitives in Wait(). In + // other words, test logic is considered "idle" work (not part of the tested + // logic). + declare_only_used_while_idle(); +} + +#if defined(OS_WIN) +TestWaitableEvent::TestWaitableEvent(win::ScopedHandle event_handle) + : WaitableEvent(std::move(event_handle)) { + declare_only_used_while_idle(); +} +#endif + +} // namespace base diff --git a/chromium/base/test/test_waitable_event.h b/chromium/base/test/test_waitable_event.h new file mode 100644 index 00000000000..ff3bc21529d --- /dev/null +++ b/chromium/base/test/test_waitable_event.h @@ -0,0 +1,40 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_WAITABLE_EVENT_H_ +#define BASE_TEST_TEST_WAITABLE_EVENT_H_ + +#include "base/synchronization/waitable_event.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include "base/win/scoped_handle.h" +#endif + +namespace base { + +// A WaitableEvent for use in tests, it has the same API as WaitableEvent with +// the following two distinctions: +// 1) ScopedAllowBaseSyncPrimitivesForTesting is not required to block on it. +// 2) It doesn't instantiate a ScopedBlockingCallWithBaseSyncPrimitives in +// Wait() (important in some //base tests that are thrown off when the +// WaitableEvents used to drive the test add additional ScopedBlockingCalls +// to the mix of monitored calls). +class TestWaitableEvent : public WaitableEvent { + public: + TestWaitableEvent(ResetPolicy reset_policy = ResetPolicy::MANUAL, + InitialState initial_state = InitialState::NOT_SIGNALED); + +#if defined(OS_WIN) + explicit TestWaitableEvent(win::ScopedHandle event_handle); +#endif +}; + +static_assert(sizeof(TestWaitableEvent) == sizeof(WaitableEvent), + "WaitableEvent is non-virtual, TestWaitableEvent must be usable " + "interchangeably."); + +} // namespace base + +#endif // BASE_TEST_TEST_WAITABLE_EVENT_H_ diff --git a/chromium/base/test/test_waitable_event_unittest.cc b/chromium/base/test/test_waitable_event_unittest.cc new file mode 100644 index 00000000000..8e344655916 --- /dev/null +++ b/chromium/base/test/test_waitable_event_unittest.cc @@ -0,0 +1,66 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_waitable_event.h" + +#include "base/bind.h" +#include "base/test/task_environment.h" +#include "base/threading/scoped_blocking_call_internal.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class NoInvokeBlockingObserver : public internal::BlockingObserver { + public: + void BlockingStarted(BlockingType blocking_type) override { ADD_FAILURE(); } + void BlockingTypeUpgraded() override { ADD_FAILURE(); } + void BlockingEnded() override { ADD_FAILURE(); } +}; + +TEST(TestWaitableEvent, NoBlockingCall) { + test::TaskEnvironment task_environment; + + NoInvokeBlockingObserver test_observer; + internal::SetBlockingObserverForCurrentThread(&test_observer); + + TestWaitableEvent test_waitable_event; + ThreadPool::PostTask( + FROM_HERE, {}, + BindOnce(&WaitableEvent::Signal, Unretained(&test_waitable_event))); + test_waitable_event.Wait(); + + internal::ClearBlockingObserverForCurrentThread(); +} + +TEST(TestWaitableEvent, WaitingInPoolDoesntRequireAllowance) { + test::TaskEnvironment task_environment; + + TestWaitableEvent test_waitable_event; + // MayBlock()/WithBaseSyncPrimitives()/ScopedAllowBaseSyncPrimitivesForTesting + // are required to Wait() on a TestWaitableEvent. + ThreadPool::PostTask( + FROM_HERE, {}, + BindOnce(&WaitableEvent::Wait, Unretained(&test_waitable_event))); + test_waitable_event.Signal(); + + task_environment.RunUntilIdle(); +} + +// Binding &WaitableEvent::Signal or &TestWaitableEvent::Signal is equivalent. +TEST(TestWaitableEvent, CanBindEitherType) { + test::TaskEnvironment task_environment; + TestWaitableEvent test_waitable_event(WaitableEvent::ResetPolicy::AUTOMATIC); + + ThreadPool::PostTask( + FROM_HERE, {}, + BindOnce(&WaitableEvent::Signal, Unretained(&test_waitable_event))); + test_waitable_event.Wait(); + + ThreadPool::PostTask( + FROM_HERE, {}, + BindOnce(&TestWaitableEvent::Signal, Unretained(&test_waitable_event))); + test_waitable_event.Wait(); +} + +} // namespace base diff --git a/chromium/base/test/thread_pool_test_helpers_android.cc b/chromium/base/test/thread_pool_test_helpers_android.cc new file mode 100644 index 00000000000..f2590a42c4f --- /dev/null +++ b/chromium/base/test/thread_pool_test_helpers_android.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/task/thread_pool/thread_pool_instance.h" +#include "base/test/test_support_jni_headers/ThreadPoolTestHelpers_jni.h" + +namespace base { + +// ThreadPoolTestHelpers is a friend of ThreadPoolInstance which grants access +// to SetCanRun(). +class ThreadPoolTestHelpers { + public: + // Enables/disables an execution fence that prevents tasks from running. + static void BeginFenceForTesting(); + static void EndFenceForTesting(); +}; + +// static +void ThreadPoolTestHelpers::BeginFenceForTesting() { + ThreadPoolInstance::Get()->BeginFence(); +} + +// static +void ThreadPoolTestHelpers::EndFenceForTesting() { + ThreadPoolInstance::Get()->EndFence(); +} + +} // namespace base + +void JNI_ThreadPoolTestHelpers_EnableThreadPoolExecutionForTesting( + JNIEnv* env) { + base::ThreadPoolTestHelpers::EndFenceForTesting(); +} + +void JNI_ThreadPoolTestHelpers_DisableThreadPoolExecutionForTesting( + JNIEnv* env) { + base::ThreadPoolTestHelpers::BeginFenceForTesting(); +}
\ No newline at end of file diff --git a/chromium/base/test/thread_test_helper.cc b/chromium/base/test/thread_test_helper.cc new file mode 100644 index 00000000000..03d6b2d6f3d --- /dev/null +++ b/chromium/base/test/thread_test_helper.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/thread_test_helper.h" + +#include <utility> + +#include "base/bind.h" +#include "base/location.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +ThreadTestHelper::ThreadTestHelper( + scoped_refptr<SequencedTaskRunner> target_sequence) + : test_result_(false), + target_sequence_(std::move(target_sequence)), + done_event_(WaitableEvent::ResetPolicy::AUTOMATIC, + WaitableEvent::InitialState::NOT_SIGNALED) {} + +bool ThreadTestHelper::Run() { + if (!target_sequence_->PostTask( + FROM_HERE, base::BindOnce(&ThreadTestHelper::RunOnSequence, this))) { + return false; + } + base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait; + done_event_.Wait(); + return test_result_; +} + +void ThreadTestHelper::RunTest() { set_test_result(true); } + +ThreadTestHelper::~ThreadTestHelper() = default; + +void ThreadTestHelper::RunOnSequence() { + RunTest(); + done_event_.Signal(); +} + +} // namespace base diff --git a/chromium/base/test/thread_test_helper.h b/chromium/base/test/thread_test_helper.h new file mode 100644 index 00000000000..935e7efc6b9 --- /dev/null +++ b/chromium/base/test/thread_test_helper.h @@ -0,0 +1,50 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_THREAD_TEST_HELPER_H_ +#define BASE_TEST_THREAD_TEST_HELPER_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner.h" +#include "base/synchronization/waitable_event.h" + +namespace base { + +// Helper class that executes code on a given target sequence/thread while +// blocking on the invoking sequence/thread. To use, derive from this class and +// overwrite RunTest. An alternative use of this class is to use it directly. It +// will then block until all pending tasks on a given sequence/thread have been +// executed. +class ThreadTestHelper : public RefCountedThreadSafe<ThreadTestHelper> { + public: + explicit ThreadTestHelper(scoped_refptr<SequencedTaskRunner> target_sequence); + + // True if RunTest() was successfully executed on the target sequence. + bool Run() WARN_UNUSED_RESULT; + + virtual void RunTest(); + + protected: + friend class RefCountedThreadSafe<ThreadTestHelper>; + + virtual ~ThreadTestHelper(); + + // Use this method to store the result of RunTest(). + void set_test_result(bool test_result) { test_result_ = test_result; } + + private: + void RunOnSequence(); + + bool test_result_; + scoped_refptr<SequencedTaskRunner> target_sequence_; + WaitableEvent done_event_; + + DISALLOW_COPY_AND_ASSIGN(ThreadTestHelper); +}; + +} // namespace base + +#endif // BASE_TEST_THREAD_TEST_HELPER_H_ diff --git a/chromium/base/test/trace_event_analyzer.cc b/chromium/base/test/trace_event_analyzer.cc new file mode 100644 index 00000000000..78a6b9b4cae --- /dev/null +++ b/chromium/base/test/trace_event_analyzer.cc @@ -0,0 +1,1077 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/trace_event_analyzer.h" + +#include <math.h> + +#include <algorithm> +#include <set> + +#include "base/bind.h" +#include "base/json/json_reader.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted_memory.h" +#include "base/run_loop.h" +#include "base/strings/pattern.h" +#include "base/trace_event/trace_buffer.h" +#include "base/trace_event/trace_config.h" +#include "base/trace_event/trace_log.h" +#include "base/values.h" + +namespace { +void OnTraceDataCollected(base::OnceClosure quit_closure, + base::trace_event::TraceResultBuffer* buffer, + const scoped_refptr<base::RefCountedString>& json, + bool has_more_events) { + buffer->AddFragment(json->data()); + if (!has_more_events) + std::move(quit_closure).Run(); +} +} // namespace + +namespace trace_analyzer { + +// TraceEvent + +TraceEvent::TraceEvent() + : thread(0, 0), + timestamp(0), + duration(0), + phase(TRACE_EVENT_PHASE_BEGIN), + other_event(nullptr) {} + +TraceEvent::TraceEvent(TraceEvent&& other) = default; + +TraceEvent::~TraceEvent() = default; + +TraceEvent& TraceEvent::operator=(TraceEvent&& rhs) = default; + +bool TraceEvent::SetFromJSON(const base::Value* event_value) { + if (event_value->type() != base::Value::Type::DICTIONARY) { + LOG(ERROR) << "Value must be Type::DICTIONARY"; + return false; + } + const base::DictionaryValue* dictionary = + static_cast<const base::DictionaryValue*>(event_value); + + std::string phase_str; + const base::DictionaryValue* args = nullptr; + + if (!dictionary->GetString("ph", &phase_str)) { + LOG(ERROR) << "ph is missing from TraceEvent JSON"; + return false; + } + + phase = *phase_str.data(); + + bool may_have_duration = (phase == TRACE_EVENT_PHASE_COMPLETE); + bool require_origin = (phase != TRACE_EVENT_PHASE_METADATA); + bool require_id = (phase == TRACE_EVENT_PHASE_ASYNC_BEGIN || + phase == TRACE_EVENT_PHASE_ASYNC_STEP_INTO || + phase == TRACE_EVENT_PHASE_ASYNC_STEP_PAST || + phase == TRACE_EVENT_PHASE_MEMORY_DUMP || + phase == TRACE_EVENT_PHASE_ENTER_CONTEXT || + phase == TRACE_EVENT_PHASE_LEAVE_CONTEXT || + phase == TRACE_EVENT_PHASE_CREATE_OBJECT || + phase == TRACE_EVENT_PHASE_DELETE_OBJECT || + phase == TRACE_EVENT_PHASE_SNAPSHOT_OBJECT || + phase == TRACE_EVENT_PHASE_ASYNC_END); + + if (require_origin && !dictionary->GetInteger("pid", &thread.process_id)) { + LOG(ERROR) << "pid is missing from TraceEvent JSON"; + return false; + } + if (require_origin && !dictionary->GetInteger("tid", &thread.thread_id)) { + LOG(ERROR) << "tid is missing from TraceEvent JSON"; + return false; + } + if (require_origin && !dictionary->GetDouble("ts", ×tamp)) { + LOG(ERROR) << "ts is missing from TraceEvent JSON"; + return false; + } + if (may_have_duration) { + dictionary->GetDouble("dur", &duration); + } + if (!dictionary->GetString("cat", &category)) { + LOG(ERROR) << "cat is missing from TraceEvent JSON"; + return false; + } + if (!dictionary->GetString("name", &name)) { + LOG(ERROR) << "name is missing from TraceEvent JSON"; + return false; + } + if (!dictionary->GetDictionary("args", &args)) { + std::string stripped_args; + // If argument filter is enabled, the arguments field contains a string + // value. + if (!dictionary->GetString("args", &stripped_args) || + stripped_args != "__stripped__") { + LOG(ERROR) << "args is missing from TraceEvent JSON"; + return false; + } + } + if (require_id && !dictionary->GetString("id", &id) && + !dictionary->FindKey("id2")) { + LOG(ERROR) + << "id/id2 is missing from ASYNC_BEGIN/ASYNC_END TraceEvent JSON"; + return false; + } + + dictionary->GetDouble("tdur", &thread_duration); + dictionary->GetDouble("tts", &thread_timestamp); + dictionary->GetString("scope", &scope); + dictionary->GetString("bind_id", &bind_id); + dictionary->GetBoolean("flow_out", &flow_out); + dictionary->GetBoolean("flow_in", &flow_in); + + const base::DictionaryValue* id2; + if (dictionary->GetDictionary("id2", &id2)) { + id2->GetString("global", &global_id2); + id2->GetString("local", &local_id2); + } + + // For each argument, copy the type and create a trace_analyzer::TraceValue. + if (args) { + for (base::DictionaryValue::Iterator it(*args); !it.IsAtEnd(); + it.Advance()) { + std::string str; + bool boolean = false; + int int_num = 0; + double double_num = 0.0; + if (it.value().GetAsString(&str)) { + arg_strings[it.key()] = str; + } else if (it.value().GetAsInteger(&int_num)) { + arg_numbers[it.key()] = static_cast<double>(int_num); + } else if (it.value().GetAsBoolean(&boolean)) { + arg_numbers[it.key()] = static_cast<double>(boolean ? 1 : 0); + } else if (it.value().GetAsDouble(&double_num)) { + arg_numbers[it.key()] = double_num; + } + // Record all arguments as values. + arg_values[it.key()] = it.value().CreateDeepCopy(); + } + } + + return true; +} + +double TraceEvent::GetAbsTimeToOtherEvent() const { + return fabs(other_event->timestamp - timestamp); +} + +bool TraceEvent::GetArgAsString(const std::string& name, + std::string* arg) const { + const auto it = arg_strings.find(name); + if (it != arg_strings.end()) { + *arg = it->second; + return true; + } + return false; +} + +bool TraceEvent::GetArgAsNumber(const std::string& name, + double* arg) const { + const auto it = arg_numbers.find(name); + if (it != arg_numbers.end()) { + *arg = it->second; + return true; + } + return false; +} + +bool TraceEvent::GetArgAsValue(const std::string& name, + std::unique_ptr<base::Value>* arg) const { + const auto it = arg_values.find(name); + if (it != arg_values.end()) { + *arg = it->second->CreateDeepCopy(); + return true; + } + return false; +} + +bool TraceEvent::HasStringArg(const std::string& name) const { + return (arg_strings.find(name) != arg_strings.end()); +} + +bool TraceEvent::HasNumberArg(const std::string& name) const { + return (arg_numbers.find(name) != arg_numbers.end()); +} + +bool TraceEvent::HasArg(const std::string& name) const { + return (arg_values.find(name) != arg_values.end()); +} + +std::string TraceEvent::GetKnownArgAsString(const std::string& name) const { + std::string arg_string; + bool result = GetArgAsString(name, &arg_string); + DCHECK(result); + return arg_string; +} + +double TraceEvent::GetKnownArgAsDouble(const std::string& name) const { + double arg_double = 0; + bool result = GetArgAsNumber(name, &arg_double); + DCHECK(result); + return arg_double; +} + +int TraceEvent::GetKnownArgAsInt(const std::string& name) const { + double arg_double = 0; + bool result = GetArgAsNumber(name, &arg_double); + DCHECK(result); + return static_cast<int>(arg_double); +} + +bool TraceEvent::GetKnownArgAsBool(const std::string& name) const { + double arg_double = 0; + bool result = GetArgAsNumber(name, &arg_double); + DCHECK(result); + return (arg_double != 0.0); +} + +std::unique_ptr<base::Value> TraceEvent::GetKnownArgAsValue( + const std::string& name) const { + std::unique_ptr<base::Value> arg_value; + bool result = GetArgAsValue(name, &arg_value); + DCHECK(result); + return arg_value; +} + +// QueryNode + +QueryNode::QueryNode(const Query& query) : query_(query) { +} + +QueryNode::~QueryNode() = default; + +// Query + +Query::Query(TraceEventMember member) + : type_(QUERY_EVENT_MEMBER), + operator_(OP_INVALID), + member_(member), + number_(0), + is_pattern_(false) { +} + +Query::Query(TraceEventMember member, const std::string& arg_name) + : type_(QUERY_EVENT_MEMBER), + operator_(OP_INVALID), + member_(member), + number_(0), + string_(arg_name), + is_pattern_(false) { +} + +Query::Query(const Query& query) = default; + +Query::~Query() = default; + +Query Query::String(const std::string& str) { + return Query(str); +} + +Query Query::Double(double num) { + return Query(num); +} + +Query Query::Int(int32_t num) { + return Query(static_cast<double>(num)); +} + +Query Query::Uint(uint32_t num) { + return Query(static_cast<double>(num)); +} + +Query Query::Bool(bool boolean) { + return Query(boolean ? 1.0 : 0.0); +} + +Query Query::Phase(char phase) { + return Query(static_cast<double>(phase)); +} + +Query Query::Pattern(const std::string& pattern) { + Query query(pattern); + query.is_pattern_ = true; + return query; +} + +bool Query::Evaluate(const TraceEvent& event) const { + // First check for values that can convert to bool. + + // double is true if != 0: + double bool_value = 0.0; + bool is_bool = GetAsDouble(event, &bool_value); + if (is_bool) + return (bool_value != 0.0); + + // string is true if it is non-empty: + std::string str_value; + bool is_str = GetAsString(event, &str_value); + if (is_str) + return !str_value.empty(); + + DCHECK_EQ(QUERY_BOOLEAN_OPERATOR, type_) + << "Invalid query: missing boolean expression"; + DCHECK(left_.get()); + DCHECK(right_.get() || is_unary_operator()); + + if (is_comparison_operator()) { + DCHECK(left().is_value() && right().is_value()) + << "Invalid query: comparison operator used between event member and " + "value."; + bool compare_result = false; + if (CompareAsDouble(event, &compare_result)) + return compare_result; + if (CompareAsString(event, &compare_result)) + return compare_result; + return false; + } + // It's a logical operator. + switch (operator_) { + case OP_AND: + return left().Evaluate(event) && right().Evaluate(event); + case OP_OR: + return left().Evaluate(event) || right().Evaluate(event); + case OP_NOT: + return !left().Evaluate(event); + default: + NOTREACHED(); + return false; + } +} + +bool Query::CompareAsDouble(const TraceEvent& event, bool* result) const { + double lhs, rhs; + if (!left().GetAsDouble(event, &lhs) || !right().GetAsDouble(event, &rhs)) + return false; + switch (operator_) { + case OP_EQ: + *result = (lhs == rhs); + return true; + case OP_NE: + *result = (lhs != rhs); + return true; + case OP_LT: + *result = (lhs < rhs); + return true; + case OP_LE: + *result = (lhs <= rhs); + return true; + case OP_GT: + *result = (lhs > rhs); + return true; + case OP_GE: + *result = (lhs >= rhs); + return true; + default: + NOTREACHED(); + return false; + } +} + +bool Query::CompareAsString(const TraceEvent& event, bool* result) const { + std::string lhs, rhs; + if (!left().GetAsString(event, &lhs) || !right().GetAsString(event, &rhs)) + return false; + switch (operator_) { + case OP_EQ: + if (right().is_pattern_) + *result = base::MatchPattern(lhs, rhs); + else if (left().is_pattern_) + *result = base::MatchPattern(rhs, lhs); + else + *result = (lhs == rhs); + return true; + case OP_NE: + if (right().is_pattern_) + *result = !base::MatchPattern(lhs, rhs); + else if (left().is_pattern_) + *result = !base::MatchPattern(rhs, lhs); + else + *result = (lhs != rhs); + return true; + case OP_LT: + *result = (lhs < rhs); + return true; + case OP_LE: + *result = (lhs <= rhs); + return true; + case OP_GT: + *result = (lhs > rhs); + return true; + case OP_GE: + *result = (lhs >= rhs); + return true; + default: + NOTREACHED(); + return false; + } +} + +bool Query::EvaluateArithmeticOperator(const TraceEvent& event, + double* num) const { + DCHECK_EQ(QUERY_ARITHMETIC_OPERATOR, type_); + DCHECK(left_.get()); + DCHECK(right_.get() || is_unary_operator()); + + double lhs = 0, rhs = 0; + if (!left().GetAsDouble(event, &lhs)) + return false; + if (!is_unary_operator() && !right().GetAsDouble(event, &rhs)) + return false; + + switch (operator_) { + case OP_ADD: + *num = lhs + rhs; + return true; + case OP_SUB: + *num = lhs - rhs; + return true; + case OP_MUL: + *num = lhs * rhs; + return true; + case OP_DIV: + *num = lhs / rhs; + return true; + case OP_MOD: + *num = static_cast<double>(static_cast<int64_t>(lhs) % + static_cast<int64_t>(rhs)); + return true; + case OP_NEGATE: + *num = -lhs; + return true; + default: + NOTREACHED(); + return false; + } +} + +bool Query::GetAsDouble(const TraceEvent& event, double* num) const { + switch (type_) { + case QUERY_ARITHMETIC_OPERATOR: + return EvaluateArithmeticOperator(event, num); + case QUERY_EVENT_MEMBER: + return GetMemberValueAsDouble(event, num); + case QUERY_NUMBER: + *num = number_; + return true; + default: + return false; + } +} + +bool Query::GetAsString(const TraceEvent& event, std::string* str) const { + switch (type_) { + case QUERY_EVENT_MEMBER: + return GetMemberValueAsString(event, str); + case QUERY_STRING: + *str = string_; + return true; + default: + return false; + } +} + +const TraceEvent* Query::SelectTargetEvent(const TraceEvent* event, + TraceEventMember member) { + if (member >= OTHER_FIRST_MEMBER && member <= OTHER_LAST_MEMBER) + return event->other_event; + if (member >= PREV_FIRST_MEMBER && member <= PREV_LAST_MEMBER) + return event->prev_event; + return event; +} + +bool Query::GetMemberValueAsDouble(const TraceEvent& event, + double* num) const { + DCHECK_EQ(QUERY_EVENT_MEMBER, type_); + + // This could be a request for a member of |event| or a member of |event|'s + // associated previous or next event. Store the target event in the_event: + const TraceEvent* the_event = SelectTargetEvent(&event, member_); + + // Request for member of associated event, but there is no associated event. + if (!the_event) + return false; + + switch (member_) { + case EVENT_PID: + case OTHER_PID: + case PREV_PID: + *num = static_cast<double>(the_event->thread.process_id); + return true; + case EVENT_TID: + case OTHER_TID: + case PREV_TID: + *num = static_cast<double>(the_event->thread.thread_id); + return true; + case EVENT_TIME: + case OTHER_TIME: + case PREV_TIME: + *num = the_event->timestamp; + return true; + case EVENT_DURATION: + if (!the_event->has_other_event()) + return false; + *num = the_event->GetAbsTimeToOtherEvent(); + return true; + case EVENT_COMPLETE_DURATION: + if (the_event->phase != TRACE_EVENT_PHASE_COMPLETE) + return false; + *num = the_event->duration; + return true; + case EVENT_PHASE: + case OTHER_PHASE: + case PREV_PHASE: + *num = static_cast<double>(the_event->phase); + return true; + case EVENT_HAS_STRING_ARG: + case OTHER_HAS_STRING_ARG: + case PREV_HAS_STRING_ARG: + *num = (the_event->HasStringArg(string_) ? 1.0 : 0.0); + return true; + case EVENT_HAS_NUMBER_ARG: + case OTHER_HAS_NUMBER_ARG: + case PREV_HAS_NUMBER_ARG: + *num = (the_event->HasNumberArg(string_) ? 1.0 : 0.0); + return true; + case EVENT_ARG: + case OTHER_ARG: + case PREV_ARG: { + // Search for the argument name and return its value if found. + auto num_i = the_event->arg_numbers.find(string_); + if (num_i == the_event->arg_numbers.end()) + return false; + *num = num_i->second; + return true; + } + case EVENT_HAS_OTHER: + // return 1.0 (true) if the other event exists + *num = event.other_event ? 1.0 : 0.0; + return true; + case EVENT_HAS_PREV: + *num = event.prev_event ? 1.0 : 0.0; + return true; + default: + return false; + } +} + +bool Query::GetMemberValueAsString(const TraceEvent& event, + std::string* str) const { + DCHECK_EQ(QUERY_EVENT_MEMBER, type_); + + // This could be a request for a member of |event| or a member of |event|'s + // associated previous or next event. Store the target event in the_event: + const TraceEvent* the_event = SelectTargetEvent(&event, member_); + + // Request for member of associated event, but there is no associated event. + if (!the_event) + return false; + + switch (member_) { + case EVENT_CATEGORY: + case OTHER_CATEGORY: + case PREV_CATEGORY: + *str = the_event->category; + return true; + case EVENT_NAME: + case OTHER_NAME: + case PREV_NAME: + *str = the_event->name; + return true; + case EVENT_ID: + case OTHER_ID: + case PREV_ID: + *str = the_event->id; + return true; + case EVENT_ARG: + case OTHER_ARG: + case PREV_ARG: { + // Search for the argument name and return its value if found. + auto str_i = the_event->arg_strings.find(string_); + if (str_i == the_event->arg_strings.end()) + return false; + *str = str_i->second; + return true; + } + default: + return false; + } +} + +Query::Query(const std::string& str) + : type_(QUERY_STRING), + operator_(OP_INVALID), + member_(EVENT_INVALID), + number_(0), + string_(str), + is_pattern_(false) { +} + +Query::Query(double num) + : type_(QUERY_NUMBER), + operator_(OP_INVALID), + member_(EVENT_INVALID), + number_(num), + is_pattern_(false) { +} +const Query& Query::left() const { + return left_->query(); +} + +const Query& Query::right() const { + return right_->query(); +} + +Query Query::operator==(const Query& rhs) const { + return Query(*this, rhs, OP_EQ); +} + +Query Query::operator!=(const Query& rhs) const { + return Query(*this, rhs, OP_NE); +} + +Query Query::operator<(const Query& rhs) const { + return Query(*this, rhs, OP_LT); +} + +Query Query::operator<=(const Query& rhs) const { + return Query(*this, rhs, OP_LE); +} + +Query Query::operator>(const Query& rhs) const { + return Query(*this, rhs, OP_GT); +} + +Query Query::operator>=(const Query& rhs) const { + return Query(*this, rhs, OP_GE); +} + +Query Query::operator&&(const Query& rhs) const { + return Query(*this, rhs, OP_AND); +} + +Query Query::operator||(const Query& rhs) const { + return Query(*this, rhs, OP_OR); +} + +Query Query::operator!() const { + return Query(*this, OP_NOT); +} + +Query Query::operator+(const Query& rhs) const { + return Query(*this, rhs, OP_ADD); +} + +Query Query::operator-(const Query& rhs) const { + return Query(*this, rhs, OP_SUB); +} + +Query Query::operator*(const Query& rhs) const { + return Query(*this, rhs, OP_MUL); +} + +Query Query::operator/(const Query& rhs) const { + return Query(*this, rhs, OP_DIV); +} + +Query Query::operator%(const Query& rhs) const { + return Query(*this, rhs, OP_MOD); +} + +Query Query::operator-() const { + return Query(*this, OP_NEGATE); +} + + +Query::Query(const Query& left, const Query& right, Operator binary_op) + : operator_(binary_op), + left_(new QueryNode(left)), + right_(new QueryNode(right)), + member_(EVENT_INVALID), + number_(0) { + type_ = (binary_op < OP_ADD ? + QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR); +} + +Query::Query(const Query& left, Operator unary_op) + : operator_(unary_op), + left_(new QueryNode(left)), + member_(EVENT_INVALID), + number_(0) { + type_ = (unary_op < OP_ADD ? + QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR); +} + +namespace { + +// Search |events| for |query| and add matches to |output|. +size_t FindMatchingEvents(const std::vector<TraceEvent>& events, + const Query& query, + TraceEventVector* output, + bool ignore_metadata_events) { + for (const auto& i : events) { + if (ignore_metadata_events && i.phase == TRACE_EVENT_PHASE_METADATA) + continue; + if (query.Evaluate(i)) + output->push_back(&i); + } + return output->size(); +} + +bool ParseEventsFromJson(const std::string& json, + std::vector<TraceEvent>* output) { + base::Optional<base::Value> root = base::JSONReader::Read(json); + + if (!root) + return false; + + base::Value::ListView list; + if (root->is_list()) { + list = root->GetList(); + } else if (root->is_dict()) { + base::Value* trace_events = root->FindListKey("traceEvents"); + if (!trace_events) + return false; + + list = trace_events->GetList(); + } else { + return false; + } + + for (const auto& item : list) { + TraceEvent event; + if (!event.SetFromJSON(&item)) + return false; + output->push_back(std::move(event)); + } + + return true; +} + +} // namespace + +// TraceAnalyzer + +TraceAnalyzer::TraceAnalyzer() + : ignore_metadata_events_(false), allow_association_changes_(true) {} + +TraceAnalyzer::~TraceAnalyzer() = default; + +// static +TraceAnalyzer* TraceAnalyzer::Create(const std::string& json_events) { + std::unique_ptr<TraceAnalyzer> analyzer(new TraceAnalyzer()); + if (analyzer->SetEvents(json_events)) + return analyzer.release(); + return nullptr; +} + +bool TraceAnalyzer::SetEvents(const std::string& json_events) { + raw_events_.clear(); + if (!ParseEventsFromJson(json_events, &raw_events_)) + return false; + std::stable_sort(raw_events_.begin(), raw_events_.end()); + ParseMetadata(); + return true; +} + +void TraceAnalyzer::AssociateBeginEndEvents() { + using trace_analyzer::Query; + + Query begin(Query::EventPhaseIs(TRACE_EVENT_PHASE_BEGIN)); + Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_END)); + Query match(Query::EventName() == Query::OtherName() && + Query::EventCategory() == Query::OtherCategory() && + Query::EventTid() == Query::OtherTid() && + Query::EventPid() == Query::OtherPid()); + + AssociateEvents(begin, end, match); +} + +void TraceAnalyzer::AssociateAsyncBeginEndEvents(bool match_pid) { + using trace_analyzer::Query; + + Query begin( + Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_BEGIN) || + Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_INTO) || + Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_PAST)); + Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_END) || + Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_INTO) || + Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_PAST)); + Query match(Query::EventCategory() == Query::OtherCategory() && + Query::EventId() == Query::OtherId()); + + if (match_pid) { + match = match && Query::EventPid() == Query::OtherPid(); + } + + AssociateEvents(begin, end, match); +} + +void TraceAnalyzer::AssociateEvents(const Query& first, + const Query& second, + const Query& match) { + DCHECK(allow_association_changes_) + << "AssociateEvents not allowed after FindEvents"; + + // Search for matching begin/end event pairs. When a matching end is found, + // it is associated with the begin event. + std::vector<TraceEvent*> begin_stack; + for (auto& this_event : raw_events_) { + if (second.Evaluate(this_event)) { + // Search stack for matching begin, starting from end. + for (int stack_index = static_cast<int>(begin_stack.size()) - 1; + stack_index >= 0; --stack_index) { + TraceEvent& begin_event = *begin_stack[stack_index]; + + // Temporarily set other to test against the match query. + const TraceEvent* other_backup = begin_event.other_event; + begin_event.other_event = &this_event; + if (match.Evaluate(begin_event)) { + // Found a matching begin/end pair. + // Set the associated previous event + this_event.prev_event = &begin_event; + // Erase the matching begin event index from the stack. + begin_stack.erase(begin_stack.begin() + stack_index); + break; + } + + // Not a match, restore original other and continue. + begin_event.other_event = other_backup; + } + } + // Even if this_event is a |second| event that has matched an earlier + // |first| event, it can still also be a |first| event and be associated + // with a later |second| event. + if (first.Evaluate(this_event)) { + begin_stack.push_back(&this_event); + } + } +} + +void TraceAnalyzer::MergeAssociatedEventArgs() { + for (auto& i : raw_events_) { + // Merge all associated events with the first event. + const TraceEvent* other = i.other_event; + // Avoid looping by keeping set of encountered TraceEvents. + std::set<const TraceEvent*> encounters; + encounters.insert(&i); + while (other && encounters.find(other) == encounters.end()) { + encounters.insert(other); + i.arg_numbers.insert(other->arg_numbers.begin(), + other->arg_numbers.end()); + i.arg_strings.insert(other->arg_strings.begin(), + other->arg_strings.end()); + other = other->other_event; + } + } +} + +size_t TraceAnalyzer::FindEvents(const Query& query, TraceEventVector* output) { + allow_association_changes_ = false; + output->clear(); + return FindMatchingEvents( + raw_events_, query, output, ignore_metadata_events_); +} + +const TraceEvent* TraceAnalyzer::FindFirstOf(const Query& query) { + TraceEventVector output; + if (FindEvents(query, &output) > 0) + return output.front(); + return nullptr; +} + +const TraceEvent* TraceAnalyzer::FindLastOf(const Query& query) { + TraceEventVector output; + if (FindEvents(query, &output) > 0) + return output.back(); + return nullptr; +} + +const std::string& TraceAnalyzer::GetThreadName( + const TraceEvent::ProcessThreadID& thread) { + // If thread is not found, just add and return empty string. + return thread_names_[thread]; +} + +void TraceAnalyzer::ParseMetadata() { + for (const auto& this_event : raw_events_) { + // Check for thread name metadata. + if (this_event.phase != TRACE_EVENT_PHASE_METADATA || + this_event.name != "thread_name") + continue; + std::map<std::string, std::string>::const_iterator string_it = + this_event.arg_strings.find("name"); + if (string_it != this_event.arg_strings.end()) + thread_names_[this_event.thread] = string_it->second; + } +} + +// Utility functions for collecting process-local traces and creating a +// |TraceAnalyzer| from the result. + +void Start(const std::string& category_filter_string) { + DCHECK(!base::trace_event::TraceLog::GetInstance()->IsEnabled()); + base::trace_event::TraceLog::GetInstance()->SetEnabled( + base::trace_event::TraceConfig(category_filter_string, ""), + base::trace_event::TraceLog::RECORDING_MODE); +} + +std::unique_ptr<TraceAnalyzer> Stop() { + DCHECK(base::trace_event::TraceLog::GetInstance()->IsEnabled()); + base::trace_event::TraceLog::GetInstance()->SetDisabled(); + + base::trace_event::TraceResultBuffer buffer; + base::trace_event::TraceResultBuffer::SimpleOutput trace_output; + buffer.SetOutputCallback(trace_output.GetCallback()); + base::RunLoop run_loop; + buffer.Start(); + base::trace_event::TraceLog::GetInstance()->Flush( + base::BindRepeating(&OnTraceDataCollected, run_loop.QuitClosure(), + base::Unretained(&buffer))); + run_loop.Run(); + buffer.Finish(); + + return base::WrapUnique(TraceAnalyzer::Create(trace_output.json_output)); +} + +// TraceEventVector utility functions. + +bool GetRateStats(const TraceEventVector& events, + RateStats* stats, + const RateStatsOptions* options) { + DCHECK(stats); + // Need at least 3 events to calculate rate stats. + const size_t kMinEvents = 3; + if (events.size() < kMinEvents) { + LOG(ERROR) << "Not enough events: " << events.size(); + return false; + } + + std::vector<double> deltas; + size_t num_deltas = events.size() - 1; + for (size_t i = 0; i < num_deltas; ++i) { + double delta = events.at(i + 1)->timestamp - events.at(i)->timestamp; + if (delta < 0.0) { + LOG(ERROR) << "Events are out of order"; + return false; + } + deltas.push_back(delta); + } + + std::sort(deltas.begin(), deltas.end()); + + if (options) { + if (options->trim_min + options->trim_max > events.size() - kMinEvents) { + LOG(ERROR) << "Attempt to trim too many events"; + return false; + } + deltas.erase(deltas.begin(), deltas.begin() + options->trim_min); + deltas.erase(deltas.end() - options->trim_max, deltas.end()); + } + + num_deltas = deltas.size(); + double delta_sum = 0.0; + for (size_t i = 0; i < num_deltas; ++i) + delta_sum += deltas[i]; + + stats->min_us = *std::min_element(deltas.begin(), deltas.end()); + stats->max_us = *std::max_element(deltas.begin(), deltas.end()); + stats->mean_us = delta_sum / static_cast<double>(num_deltas); + + double sum_mean_offsets_squared = 0.0; + for (size_t i = 0; i < num_deltas; ++i) { + double offset = fabs(deltas[i] - stats->mean_us); + sum_mean_offsets_squared += offset * offset; + } + stats->standard_deviation_us = + sqrt(sum_mean_offsets_squared / static_cast<double>(num_deltas - 1)); + + return true; +} + +bool FindFirstOf(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_index) { + DCHECK(return_index); + for (size_t i = position; i < events.size(); ++i) { + if (query.Evaluate(*events[i])) { + *return_index = i; + return true; + } + } + return false; +} + +bool FindLastOf(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_index) { + DCHECK(return_index); + for (size_t i = std::min(position + 1, events.size()); i != 0; --i) { + if (query.Evaluate(*events[i - 1])) { + *return_index = i - 1; + return true; + } + } + return false; +} + +bool FindClosest(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_closest, + size_t* return_second_closest) { + DCHECK(return_closest); + if (events.empty() || position >= events.size()) + return false; + size_t closest = events.size(); + size_t second_closest = events.size(); + for (size_t i = 0; i < events.size(); ++i) { + if (!query.Evaluate(*events.at(i))) + continue; + if (closest == events.size()) { + closest = i; + continue; + } + if (fabs(events.at(i)->timestamp - events.at(position)->timestamp) < + fabs(events.at(closest)->timestamp - events.at(position)->timestamp)) { + second_closest = closest; + closest = i; + } else if (second_closest == events.size()) { + second_closest = i; + } + } + + if (closest < events.size() && + (!return_second_closest || second_closest < events.size())) { + *return_closest = closest; + if (return_second_closest) + *return_second_closest = second_closest; + return true; + } + + return false; +} + +size_t CountMatches(const TraceEventVector& events, + const Query& query, + size_t begin_position, + size_t end_position) { + if (begin_position >= events.size()) + return 0u; + end_position = (end_position < events.size()) ? end_position : events.size(); + size_t count = 0u; + for (size_t i = begin_position; i < end_position; ++i) { + if (query.Evaluate(*events.at(i))) + ++count; + } + return count; +} + +} // namespace trace_analyzer diff --git a/chromium/base/test/trace_event_analyzer.h b/chromium/base/test/trace_event_analyzer.h new file mode 100644 index 00000000000..dcdd2e4b5ec --- /dev/null +++ b/chromium/base/test/trace_event_analyzer.h @@ -0,0 +1,842 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Use trace_analyzer::Query and trace_analyzer::TraceAnalyzer to search for +// specific trace events that were generated by the trace_event.h API. +// +// Basic procedure: +// - Get trace events JSON string from base::trace_event::TraceLog. +// - Create TraceAnalyzer with JSON string. +// - Call TraceAnalyzer::AssociateBeginEndEvents (optional). +// - Call TraceAnalyzer::AssociateEvents (zero or more times). +// - Call TraceAnalyzer::FindEvents with queries to find specific events. +// +// A Query is a boolean expression tree that evaluates to true or false for a +// given trace event. Queries can be combined into a tree using boolean, +// arithmetic and comparison operators that refer to data of an individual trace +// event. +// +// The events are returned as trace_analyzer::TraceEvent objects. +// TraceEvent contains a single trace event's data, as well as a pointer to +// a related trace event. The related trace event is typically the matching end +// of a begin event or the matching begin of an end event. +// +// The following examples use this basic setup code to construct TraceAnalyzer +// with the json trace string retrieved from TraceLog and construct an event +// vector for retrieving events: +// +// TraceAnalyzer analyzer(json_events); +// TraceEventVector events; +// +// EXAMPLE 1: Find events named "my_event". +// +// analyzer.FindEvents(Query(EVENT_NAME) == "my_event", &events); +// +// EXAMPLE 2: Find begin events named "my_event" with duration > 1 second. +// +// Query q = (Query(EVENT_NAME) == Query::String("my_event") && +// Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_BEGIN) && +// Query(EVENT_DURATION) > Query::Double(1000000.0)); +// analyzer.FindEvents(q, &events); +// +// EXAMPLE 3: Associating event pairs across threads. +// +// If the test needs to analyze something that starts and ends on different +// threads, the test needs to use INSTANT events. The typical procedure is to +// specify the same unique ID as a TRACE_EVENT argument on both the start and +// finish INSTANT events. Then use the following procedure to associate those +// events. +// +// Step 1: instrument code with custom begin/end trace events. +// [Thread 1 tracing code] +// TRACE_EVENT_INSTANT1("test_latency", "timing1_begin", "id", 3); +// [Thread 2 tracing code] +// TRACE_EVENT_INSTANT1("test_latency", "timing1_end", "id", 3); +// +// Step 2: associate these custom begin/end pairs. +// Query begin(Query(EVENT_NAME) == Query::String("timing1_begin")); +// Query end(Query(EVENT_NAME) == Query::String("timing1_end")); +// Query match(Query(EVENT_ARG, "id") == Query(OTHER_ARG, "id")); +// analyzer.AssociateEvents(begin, end, match); +// +// Step 3: search for "timing1_begin" events with existing other event. +// Query q = (Query(EVENT_NAME) == Query::String("timing1_begin") && +// Query(EVENT_HAS_OTHER)); +// analyzer.FindEvents(q, &events); +// +// Step 4: analyze events, such as checking durations. +// for (size_t i = 0; i < events.size(); ++i) { +// double duration; +// EXPECT_TRUE(events[i].GetAbsTimeToOtherEvent(&duration)); +// EXPECT_LT(duration, 1000000.0/60.0); // expect less than 1/60 second. +// } +// +// There are two helper functions, Start(category_filter_string) and Stop(), for +// facilitating the collection of process-local traces and building a +// TraceAnalyzer from them. A typical test, that uses the helper functions, +// looks like the following: +// +// TEST_F(...) { +// Start("*"); +// [Invoke the functions you want to test their traces] +// auto analyzer = Stop(); +// +// [Use the analyzer to verify produced traces, as explained above] +// } +// +// Note: The Stop() function needs a SingleThreadTaskRunner. + +#ifndef BASE_TEST_TRACE_EVENT_ANALYZER_H_ +#define BASE_TEST_TRACE_EVENT_ANALYZER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/trace_event/trace_event.h" + +namespace base { +class Value; +} + +namespace trace_analyzer { +class QueryNode; + +// trace_analyzer::TraceEvent is a more convenient form of the +// base::trace_event::TraceEvent class to make tracing-based tests easier to +// write. +struct TraceEvent { + // ProcessThreadID contains a Process ID and Thread ID. + struct ProcessThreadID { + ProcessThreadID() : process_id(0), thread_id(0) {} + ProcessThreadID(int process_id, int thread_id) + : process_id(process_id), thread_id(thread_id) {} + bool operator< (const ProcessThreadID& rhs) const { + if (process_id != rhs.process_id) + return process_id < rhs.process_id; + return thread_id < rhs.thread_id; + } + int process_id; + int thread_id; + }; + + TraceEvent(); + TraceEvent(TraceEvent&& other); + ~TraceEvent(); + + bool SetFromJSON(const base::Value* event_value) WARN_UNUSED_RESULT; + + bool operator< (const TraceEvent& rhs) const { + return timestamp < rhs.timestamp; + } + + TraceEvent& operator=(TraceEvent&& rhs); + + bool has_other_event() const { return other_event; } + + // Returns absolute duration in microseconds between this event and other + // event. Must have already verified that other_event exists by + // Query(EVENT_HAS_OTHER) or by calling has_other_event(). + double GetAbsTimeToOtherEvent() const; + + // Return the argument value if it exists and it is a string. + bool GetArgAsString(const std::string& name, std::string* arg) const; + // Return the argument value if it exists and it is a number. + bool GetArgAsNumber(const std::string& name, double* arg) const; + // Return the argument value if it exists. + bool GetArgAsValue(const std::string& name, + std::unique_ptr<base::Value>* arg) const; + + // Check if argument exists and is string. + bool HasStringArg(const std::string& name) const; + // Check if argument exists and is number (double, int or bool). + bool HasNumberArg(const std::string& name) const; + // Check if argument exists. + bool HasArg(const std::string& name) const; + + // Get known existing arguments as specific types. + // Useful when you have already queried the argument with + // Query(HAS_NUMBER_ARG) or Query(HAS_STRING_ARG). + std::string GetKnownArgAsString(const std::string& name) const; + double GetKnownArgAsDouble(const std::string& name) const; + int GetKnownArgAsInt(const std::string& name) const; + bool GetKnownArgAsBool(const std::string& name) const; + std::unique_ptr<base::Value> GetKnownArgAsValue( + const std::string& name) const; + + // Process ID and Thread ID. + ProcessThreadID thread; + + // Time since epoch in microseconds. + // Stored as double to match its JSON representation. + double timestamp; + double duration; + char phase; + std::string category; + std::string name; + std::string id; + double thread_duration = 0.0; + double thread_timestamp = 0.0; + std::string scope; + std::string bind_id; + bool flow_out = false; + bool flow_in = false; + std::string global_id2; + std::string local_id2; + + // All numbers and bool values from TraceEvent args are cast to double. + // bool becomes 1.0 (true) or 0.0 (false). + std::map<std::string, double> arg_numbers; + std::map<std::string, std::string> arg_strings; + std::map<std::string, std::unique_ptr<base::Value>> arg_values; + + // The other event associated with this event (or NULL). + const TraceEvent* other_event; + + // A back-link for |other_event|. That is, if other_event is not null, then + // |event->other_event->prev_event == event| is always true. + const TraceEvent* prev_event; +}; + +typedef std::vector<const TraceEvent*> TraceEventVector; + +class Query { + public: + Query(const Query& query); + + ~Query(); + + //////////////////////////////////////////////////////////////// + // Query literal values + + // Compare with the given string. + static Query String(const std::string& str); + + // Compare with the given number. + static Query Double(double num); + static Query Int(int32_t num); + static Query Uint(uint32_t num); + + // Compare with the given bool. + static Query Bool(bool boolean); + + // Compare with the given phase. + static Query Phase(char phase); + + // Compare with the given string pattern. Only works with == and != operators. + // Example: Query(EVENT_NAME) == Query::Pattern("MyEvent*") + static Query Pattern(const std::string& pattern); + + //////////////////////////////////////////////////////////////// + // Query event members + + static Query EventPid() { return Query(EVENT_PID); } + + static Query EventTid() { return Query(EVENT_TID); } + + // Return the timestamp of the event in microseconds since epoch. + static Query EventTime() { return Query(EVENT_TIME); } + + // Return the absolute time between event and other event in microseconds. + // Only works if Query::EventHasOther() == true. + static Query EventDuration() { return Query(EVENT_DURATION); } + + // Return the duration of a COMPLETE event. + static Query EventCompleteDuration() { + return Query(EVENT_COMPLETE_DURATION); + } + + static Query EventPhase() { return Query(EVENT_PHASE); } + + static Query EventCategory() { return Query(EVENT_CATEGORY); } + + static Query EventName() { return Query(EVENT_NAME); } + + static Query EventId() { return Query(EVENT_ID); } + + static Query EventPidIs(int process_id) { + return Query(EVENT_PID) == Query::Int(process_id); + } + + static Query EventTidIs(int thread_id) { + return Query(EVENT_TID) == Query::Int(thread_id); + } + + static Query EventThreadIs(const TraceEvent::ProcessThreadID& thread) { + return EventPidIs(thread.process_id) && EventTidIs(thread.thread_id); + } + + static Query EventTimeIs(double timestamp) { + return Query(EVENT_TIME) == Query::Double(timestamp); + } + + static Query EventDurationIs(double duration) { + return Query(EVENT_DURATION) == Query::Double(duration); + } + + static Query EventPhaseIs(char phase) { + return Query(EVENT_PHASE) == Query::Phase(phase); + } + + static Query EventCategoryIs(const std::string& category) { + return Query(EVENT_CATEGORY) == Query::String(category); + } + + static Query EventNameIs(const std::string& name) { + return Query(EVENT_NAME) == Query::String(name); + } + + static Query EventIdIs(const std::string& id) { + return Query(EVENT_ID) == Query::String(id); + } + + // Evaluates to true if arg exists and is a string. + static Query EventHasStringArg(const std::string& arg_name) { + return Query(EVENT_HAS_STRING_ARG, arg_name); + } + + // Evaluates to true if arg exists and is a number. + // Number arguments include types double, int and bool. + static Query EventHasNumberArg(const std::string& arg_name) { + return Query(EVENT_HAS_NUMBER_ARG, arg_name); + } + + // Evaluates to arg value (string or number). + static Query EventArg(const std::string& arg_name) { + return Query(EVENT_ARG, arg_name); + } + + // Return true if associated event exists. + static Query EventHasOther() { return Query(EVENT_HAS_OTHER); } + + // Access the associated other_event's members: + + static Query OtherPid() { return Query(OTHER_PID); } + + static Query OtherTid() { return Query(OTHER_TID); } + + static Query OtherTime() { return Query(OTHER_TIME); } + + static Query OtherPhase() { return Query(OTHER_PHASE); } + + static Query OtherCategory() { return Query(OTHER_CATEGORY); } + + static Query OtherName() { return Query(OTHER_NAME); } + + static Query OtherId() { return Query(OTHER_ID); } + + static Query OtherPidIs(int process_id) { + return Query(OTHER_PID) == Query::Int(process_id); + } + + static Query OtherTidIs(int thread_id) { + return Query(OTHER_TID) == Query::Int(thread_id); + } + + static Query OtherThreadIs(const TraceEvent::ProcessThreadID& thread) { + return OtherPidIs(thread.process_id) && OtherTidIs(thread.thread_id); + } + + static Query OtherTimeIs(double timestamp) { + return Query(OTHER_TIME) == Query::Double(timestamp); + } + + static Query OtherPhaseIs(char phase) { + return Query(OTHER_PHASE) == Query::Phase(phase); + } + + static Query OtherCategoryIs(const std::string& category) { + return Query(OTHER_CATEGORY) == Query::String(category); + } + + static Query OtherNameIs(const std::string& name) { + return Query(OTHER_NAME) == Query::String(name); + } + + static Query OtherIdIs(const std::string& id) { + return Query(OTHER_ID) == Query::String(id); + } + + // Evaluates to true if arg exists and is a string. + static Query OtherHasStringArg(const std::string& arg_name) { + return Query(OTHER_HAS_STRING_ARG, arg_name); + } + + // Evaluates to true if arg exists and is a number. + // Number arguments include types double, int and bool. + static Query OtherHasNumberArg(const std::string& arg_name) { + return Query(OTHER_HAS_NUMBER_ARG, arg_name); + } + + // Evaluates to arg value (string or number). + static Query OtherArg(const std::string& arg_name) { + return Query(OTHER_ARG, arg_name); + } + + // Access the associated prev_event's members: + + static Query PrevPid() { return Query(PREV_PID); } + + static Query PrevTid() { return Query(PREV_TID); } + + static Query PrevTime() { return Query(PREV_TIME); } + + static Query PrevPhase() { return Query(PREV_PHASE); } + + static Query PrevCategory() { return Query(PREV_CATEGORY); } + + static Query PrevName() { return Query(PREV_NAME); } + + static Query PrevId() { return Query(PREV_ID); } + + static Query PrevPidIs(int process_id) { + return Query(PREV_PID) == Query::Int(process_id); + } + + static Query PrevTidIs(int thread_id) { + return Query(PREV_TID) == Query::Int(thread_id); + } + + static Query PrevThreadIs(const TraceEvent::ProcessThreadID& thread) { + return PrevPidIs(thread.process_id) && PrevTidIs(thread.thread_id); + } + + static Query PrevTimeIs(double timestamp) { + return Query(PREV_TIME) == Query::Double(timestamp); + } + + static Query PrevPhaseIs(char phase) { + return Query(PREV_PHASE) == Query::Phase(phase); + } + + static Query PrevCategoryIs(const std::string& category) { + return Query(PREV_CATEGORY) == Query::String(category); + } + + static Query PrevNameIs(const std::string& name) { + return Query(PREV_NAME) == Query::String(name); + } + + static Query PrevIdIs(const std::string& id) { + return Query(PREV_ID) == Query::String(id); + } + + // Evaluates to true if arg exists and is a string. + static Query PrevHasStringArg(const std::string& arg_name) { + return Query(PREV_HAS_STRING_ARG, arg_name); + } + + // Evaluates to true if arg exists and is a number. + // Number arguments include types double, int and bool. + static Query PrevHasNumberArg(const std::string& arg_name) { + return Query(PREV_HAS_NUMBER_ARG, arg_name); + } + + // Evaluates to arg value (string or number). + static Query PrevArg(const std::string& arg_name) { + return Query(PREV_ARG, arg_name); + } + + //////////////////////////////////////////////////////////////// + // Common queries: + + // Find BEGIN events that have a corresponding END event. + static Query MatchBeginWithEnd() { + return (Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_BEGIN)) && + Query(EVENT_HAS_OTHER); + } + + // Find COMPLETE events. + static Query MatchComplete() { + return (Query(EVENT_PHASE) == Query::Phase(TRACE_EVENT_PHASE_COMPLETE)); + } + + // Find ASYNC_BEGIN events that have a corresponding ASYNC_END event. + static Query MatchAsyncBeginWithNext() { + return (Query(EVENT_PHASE) == + Query::Phase(TRACE_EVENT_PHASE_ASYNC_BEGIN)) && + Query(EVENT_HAS_OTHER); + } + + // Find BEGIN events of given |name| which also have associated END events. + static Query MatchBeginName(const std::string& name) { + return (Query(EVENT_NAME) == Query(name)) && MatchBeginWithEnd(); + } + + // Find COMPLETE events of given |name|. + static Query MatchCompleteName(const std::string& name) { + return (Query(EVENT_NAME) == Query(name)) && MatchComplete(); + } + + // Match given Process ID and Thread ID. + static Query MatchThread(const TraceEvent::ProcessThreadID& thread) { + return (Query(EVENT_PID) == Query::Int(thread.process_id)) && + (Query(EVENT_TID) == Query::Int(thread.thread_id)); + } + + // Match event pair that spans multiple threads. + static Query MatchCrossThread() { + return (Query(EVENT_PID) != Query(OTHER_PID)) || + (Query(EVENT_TID) != Query(OTHER_TID)); + } + + //////////////////////////////////////////////////////////////// + // Operators: + + // Boolean operators: + Query operator==(const Query& rhs) const; + Query operator!=(const Query& rhs) const; + Query operator< (const Query& rhs) const; + Query operator<=(const Query& rhs) const; + Query operator> (const Query& rhs) const; + Query operator>=(const Query& rhs) const; + Query operator&&(const Query& rhs) const; + Query operator||(const Query& rhs) const; + Query operator!() const; + + // Arithmetic operators: + // Following operators are applied to double arguments: + Query operator+(const Query& rhs) const; + Query operator-(const Query& rhs) const; + Query operator*(const Query& rhs) const; + Query operator/(const Query& rhs) const; + Query operator-() const; + // Mod operates on int64_t args (doubles are casted to int64_t beforehand): + Query operator%(const Query& rhs) const; + + // Return true if the given event matches this query tree. + // This is a recursive method that walks the query tree. + bool Evaluate(const TraceEvent& event) const; + + enum TraceEventMember { + EVENT_INVALID, + EVENT_PID, + EVENT_TID, + EVENT_TIME, + EVENT_DURATION, + EVENT_COMPLETE_DURATION, + EVENT_PHASE, + EVENT_CATEGORY, + EVENT_NAME, + EVENT_ID, + EVENT_HAS_STRING_ARG, + EVENT_HAS_NUMBER_ARG, + EVENT_ARG, + EVENT_HAS_OTHER, + EVENT_HAS_PREV, + + OTHER_PID, + OTHER_TID, + OTHER_TIME, + OTHER_PHASE, + OTHER_CATEGORY, + OTHER_NAME, + OTHER_ID, + OTHER_HAS_STRING_ARG, + OTHER_HAS_NUMBER_ARG, + OTHER_ARG, + + PREV_PID, + PREV_TID, + PREV_TIME, + PREV_PHASE, + PREV_CATEGORY, + PREV_NAME, + PREV_ID, + PREV_HAS_STRING_ARG, + PREV_HAS_NUMBER_ARG, + PREV_ARG, + + OTHER_FIRST_MEMBER = OTHER_PID, + OTHER_LAST_MEMBER = OTHER_ARG, + + PREV_FIRST_MEMBER = PREV_PID, + PREV_LAST_MEMBER = PREV_ARG, + }; + + enum Operator { + OP_INVALID, + // Boolean operators: + OP_EQ, + OP_NE, + OP_LT, + OP_LE, + OP_GT, + OP_GE, + OP_AND, + OP_OR, + OP_NOT, + // Arithmetic operators: + OP_ADD, + OP_SUB, + OP_MUL, + OP_DIV, + OP_MOD, + OP_NEGATE + }; + + enum QueryType { + QUERY_BOOLEAN_OPERATOR, + QUERY_ARITHMETIC_OPERATOR, + QUERY_EVENT_MEMBER, + QUERY_NUMBER, + QUERY_STRING + }; + + // Compare with the given member. + explicit Query(TraceEventMember member); + + // Compare with the given member argument value. + Query(TraceEventMember member, const std::string& arg_name); + + // Compare with the given string. + explicit Query(const std::string& str); + + // Compare with the given number. + explicit Query(double num); + + // Construct a boolean Query that returns (left <binary_op> right). + Query(const Query& left, const Query& right, Operator binary_op); + + // Construct a boolean Query that returns (<binary_op> left). + Query(const Query& left, Operator unary_op); + + // Try to compare left_ against right_ based on operator_. + // If either left or right does not convert to double, false is returned. + // Otherwise, true is returned and |result| is set to the comparison result. + bool CompareAsDouble(const TraceEvent& event, bool* result) const; + + // Try to compare left_ against right_ based on operator_. + // If either left or right does not convert to string, false is returned. + // Otherwise, true is returned and |result| is set to the comparison result. + bool CompareAsString(const TraceEvent& event, bool* result) const; + + // Attempt to convert this Query to a double. On success, true is returned + // and the double value is stored in |num|. + bool GetAsDouble(const TraceEvent& event, double* num) const; + + // Attempt to convert this Query to a string. On success, true is returned + // and the string value is stored in |str|. + bool GetAsString(const TraceEvent& event, std::string* str) const; + + // Evaluate this Query as an arithmetic operator on left_ and right_. + bool EvaluateArithmeticOperator(const TraceEvent& event, + double* num) const; + + // For QUERY_EVENT_MEMBER Query: attempt to get the double value of the Query. + bool GetMemberValueAsDouble(const TraceEvent& event, double* num) const; + + // For QUERY_EVENT_MEMBER Query: attempt to get the string value of the Query. + bool GetMemberValueAsString(const TraceEvent& event, std::string* num) const; + + // Does this Query represent a value? + bool is_value() const { return type_ != QUERY_BOOLEAN_OPERATOR; } + + bool is_unary_operator() const { + return operator_ == OP_NOT || operator_ == OP_NEGATE; + } + + bool is_comparison_operator() const { + return operator_ != OP_INVALID && operator_ < OP_AND; + } + + static const TraceEvent* SelectTargetEvent(const TraceEvent* ev, + TraceEventMember member); + + const Query& left() const; + const Query& right() const; + + private: + QueryType type_; + Operator operator_; + scoped_refptr<QueryNode> left_; + scoped_refptr<QueryNode> right_; + TraceEventMember member_; + double number_; + std::string string_; + bool is_pattern_; +}; + +// Implementation detail: +// QueryNode allows Query to store a ref-counted query tree. +class QueryNode : public base::RefCounted<QueryNode> { + public: + explicit QueryNode(const Query& query); + const Query& query() const { return query_; } + + private: + friend class base::RefCounted<QueryNode>; + ~QueryNode(); + + Query query_; +}; + +// TraceAnalyzer helps tests search for trace events. +class TraceAnalyzer { + public: + ~TraceAnalyzer(); + + // Use trace events from JSON string generated by tracing API. + // Returns non-NULL if the JSON is successfully parsed. + static TraceAnalyzer* Create(const std::string& json_events) + WARN_UNUSED_RESULT; + + void SetIgnoreMetadataEvents(bool ignore) { + ignore_metadata_events_ = ignore; + } + + // Associate BEGIN and END events with each other. This allows Query(OTHER_*) + // to access the associated event and enables Query(EVENT_DURATION). + // An end event will match the most recent begin event with the same name, + // category, process ID and thread ID. This matches what is shown in + // about:tracing. After association, the BEGIN event will point to the + // matching END event, but the END event will not point to the BEGIN event. + void AssociateBeginEndEvents(); + + // Associate ASYNC_BEGIN, ASYNC_STEP and ASYNC_END events with each other. + // An ASYNC_END event will match the most recent ASYNC_BEGIN or ASYNC_STEP + // event with the same name, category, and ID. This creates a singly linked + // list of ASYNC_BEGIN->ASYNC_STEP...->ASYNC_END. + // |match_pid| - If true, will only match async events which are running + // under the same process ID, otherwise will allow linking + // async events from different processes. + void AssociateAsyncBeginEndEvents(bool match_pid = true); + + // AssociateEvents can be used to customize event associations by setting the + // other_event member of TraceEvent. This should be used to associate two + // INSTANT events. + // + // The assumptions are: + // - |first| events occur before |second| events. + // - the closest matching |second| event is the correct match. + // + // |first| - Eligible |first| events match this query. + // |second| - Eligible |second| events match this query. + // |match| - This query is run on the |first| event. The OTHER_* EventMember + // queries will point to an eligible |second| event. The query + // should evaluate to true if the |first|/|second| pair is a match. + // + // When a match is found, the pair will be associated by having the first + // event's other_event member point to the other. AssociateEvents does not + // clear previous associations, so it is possible to associate multiple pairs + // of events by calling AssociateEvents more than once with different queries. + // + // NOTE: AssociateEvents will overwrite existing other_event associations if + // the queries pass for events that already had a previous association. + // + // After calling any Find* method, it is not allowed to call AssociateEvents + // again. + void AssociateEvents(const Query& first, + const Query& second, + const Query& match); + + // For each event, copy its arguments to the other_event argument map. If + // argument name already exists, it will not be overwritten. + void MergeAssociatedEventArgs(); + + // Find all events that match query and replace output vector. + size_t FindEvents(const Query& query, TraceEventVector* output); + + // Find first event that matches query or NULL if not found. + const TraceEvent* FindFirstOf(const Query& query); + + // Find last event that matches query or NULL if not found. + const TraceEvent* FindLastOf(const Query& query); + + const std::string& GetThreadName(const TraceEvent::ProcessThreadID& thread); + + private: + TraceAnalyzer(); + + bool SetEvents(const std::string& json_events) WARN_UNUSED_RESULT; + + // Read metadata (thread names, etc) from events. + void ParseMetadata(); + + std::map<TraceEvent::ProcessThreadID, std::string> thread_names_; + std::vector<TraceEvent> raw_events_; + bool ignore_metadata_events_; + bool allow_association_changes_; + + DISALLOW_COPY_AND_ASSIGN(TraceAnalyzer); +}; + +// Utility functions for collecting process-local traces and creating a +// |TraceAnalyzer| from the result. Please see comments in trace_config.h to +// understand how the |category_filter_string| works. Use "*" to enable all +// default categories. +void Start(const std::string& category_filter_string); +std::unique_ptr<TraceAnalyzer> Stop(); + +// Utility functions for TraceEventVector. + +struct RateStats { + double min_us; + double max_us; + double mean_us; + double standard_deviation_us; +}; + +struct RateStatsOptions { + RateStatsOptions() : trim_min(0u), trim_max(0u) {} + // After the times between events are sorted, the number of specified elements + // will be trimmed before calculating the RateStats. This is useful in cases + // where extreme outliers are tolerable and should not skew the overall + // average. + size_t trim_min; // Trim this many minimum times. + size_t trim_max; // Trim this many maximum times. +}; + +// Calculate min/max/mean and standard deviation from the times between +// adjacent events. +bool GetRateStats(const TraceEventVector& events, + RateStats* stats, + const RateStatsOptions* options); + +// Starting from |position|, find the first event that matches |query|. +// Returns true if found, false otherwise. +bool FindFirstOf(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_index); + +// Starting from |position|, find the last event that matches |query|. +// Returns true if found, false otherwise. +bool FindLastOf(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_index); + +// Find the closest events to |position| in time that match |query|. +// return_second_closest may be NULL. Closeness is determined by comparing +// with the event timestamp. +// Returns true if found, false otherwise. If both return parameters are +// requested, both must be found for a successful result. +bool FindClosest(const TraceEventVector& events, + const Query& query, + size_t position, + size_t* return_closest, + size_t* return_second_closest); + +// Count matches, inclusive of |begin_position|, exclusive of |end_position|. +size_t CountMatches(const TraceEventVector& events, + const Query& query, + size_t begin_position, + size_t end_position); + +// Count all matches. +static inline size_t CountMatches(const TraceEventVector& events, + const Query& query) { + return CountMatches(events, query, 0u, events.size()); +} + +} // namespace trace_analyzer + +#endif // BASE_TEST_TRACE_EVENT_ANALYZER_H_ diff --git a/chromium/base/test/trace_event_analyzer_unittest.cc b/chromium/base/test/trace_event_analyzer_unittest.cc new file mode 100644 index 00000000000..259fd95264b --- /dev/null +++ b/chromium/base/test/trace_event_analyzer_unittest.cc @@ -0,0 +1,959 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/trace_event_analyzer.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/bind.h" +#include "base/memory/ref_counted_memory.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/trace_event/trace_buffer.h" +#include "base/trace_event/traced_value.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace trace_analyzer { + +namespace { + +class TraceEventAnalyzerTest : public testing::Test { + public: + void ManualSetUp(); + void OnTraceDataCollected( + base::WaitableEvent* flush_complete_event, + const scoped_refptr<base::RefCountedString>& json_events_str, + bool has_more_events); + void BeginTracing(); + void EndTracing(); + + base::trace_event::TraceResultBuffer::SimpleOutput output_; + base::trace_event::TraceResultBuffer buffer_; +}; + +void TraceEventAnalyzerTest::ManualSetUp() { + ASSERT_TRUE(base::trace_event::TraceLog::GetInstance()); + buffer_.SetOutputCallback(output_.GetCallback()); + output_.json_output.clear(); +} + +void TraceEventAnalyzerTest::OnTraceDataCollected( + base::WaitableEvent* flush_complete_event, + const scoped_refptr<base::RefCountedString>& json_events_str, + bool has_more_events) { + buffer_.AddFragment(json_events_str->data()); + if (!has_more_events) + flush_complete_event->Signal(); +} + +void TraceEventAnalyzerTest::BeginTracing() { + output_.json_output.clear(); + buffer_.Start(); + base::trace_event::TraceLog::GetInstance()->SetEnabled( + base::trace_event::TraceConfig("*", ""), + base::trace_event::TraceLog::RECORDING_MODE); +} + +void TraceEventAnalyzerTest::EndTracing() { + base::trace_event::TraceLog::GetInstance()->SetDisabled(); + base::WaitableEvent flush_complete_event( + base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::trace_event::TraceLog::GetInstance()->Flush(base::BindRepeating( + &TraceEventAnalyzerTest::OnTraceDataCollected, base::Unretained(this), + base::Unretained(&flush_complete_event))); + flush_complete_event.Wait(); + buffer_.Finish(); +} + +} // namespace + +TEST_F(TraceEventAnalyzerTest, NoEvents) { + ManualSetUp(); + + // Create an empty JSON event string: + buffer_.Start(); + buffer_.Finish(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + + // Search for all events and verify that nothing is returned. + TraceEventVector found; + analyzer->FindEvents(Query::Bool(true), &found); + EXPECT_EQ(0u, found.size()); +} + +TEST_F(TraceEventAnalyzerTest, TraceEvent) { + ManualSetUp(); + + int int_num = 2; + double double_num = 3.5; + const char str[] = "the string"; + + TraceEvent event; + event.arg_numbers["false"] = 0.0; + event.arg_numbers["true"] = 1.0; + event.arg_numbers["int"] = static_cast<double>(int_num); + event.arg_numbers["double"] = double_num; + event.arg_strings["string"] = str; + event.arg_values["dict"] = std::make_unique<base::DictionaryValue>(); + + ASSERT_TRUE(event.HasNumberArg("false")); + ASSERT_TRUE(event.HasNumberArg("true")); + ASSERT_TRUE(event.HasNumberArg("int")); + ASSERT_TRUE(event.HasNumberArg("double")); + ASSERT_TRUE(event.HasStringArg("string")); + ASSERT_FALSE(event.HasNumberArg("notfound")); + ASSERT_FALSE(event.HasStringArg("notfound")); + ASSERT_TRUE(event.HasArg("dict")); + ASSERT_FALSE(event.HasArg("notfound")); + + EXPECT_FALSE(event.GetKnownArgAsBool("false")); + EXPECT_TRUE(event.GetKnownArgAsBool("true")); + EXPECT_EQ(int_num, event.GetKnownArgAsInt("int")); + EXPECT_EQ(double_num, event.GetKnownArgAsDouble("double")); + EXPECT_STREQ(str, event.GetKnownArgAsString("string").c_str()); + + std::unique_ptr<base::Value> arg; + EXPECT_TRUE(event.GetArgAsValue("dict", &arg)); + EXPECT_EQ(base::Value::Type::DICTIONARY, arg->type()); +} + +TEST_F(TraceEventAnalyzerTest, QueryEventMember) { + ManualSetUp(); + + TraceEvent event; + event.thread.process_id = 3; + event.thread.thread_id = 4; + event.timestamp = 1.5; + event.phase = TRACE_EVENT_PHASE_BEGIN; + event.category = "category"; + event.name = "name"; + event.id = "1"; + event.arg_numbers["num"] = 7.0; + event.arg_strings["str"] = "the string"; + + // Other event with all different members: + TraceEvent other; + other.thread.process_id = 5; + other.thread.thread_id = 6; + other.timestamp = 2.5; + other.phase = TRACE_EVENT_PHASE_END; + other.category = "category2"; + other.name = "name2"; + other.id = "2"; + other.arg_numbers["num2"] = 8.0; + other.arg_strings["str2"] = "the string 2"; + + event.other_event = &other; + ASSERT_TRUE(event.has_other_event()); + double duration = event.GetAbsTimeToOtherEvent(); + + Query event_pid = Query::EventPidIs(event.thread.process_id); + Query event_tid = Query::EventTidIs(event.thread.thread_id); + Query event_time = Query::EventTimeIs(event.timestamp); + Query event_duration = Query::EventDurationIs(duration); + Query event_phase = Query::EventPhaseIs(event.phase); + Query event_category = Query::EventCategoryIs(event.category); + Query event_name = Query::EventNameIs(event.name); + Query event_id = Query::EventIdIs(event.id); + Query event_has_arg1 = Query::EventHasNumberArg("num"); + Query event_has_arg2 = Query::EventHasStringArg("str"); + Query event_arg1 = + (Query::EventArg("num") == Query::Double(event.arg_numbers["num"])); + Query event_arg2 = + (Query::EventArg("str") == Query::String(event.arg_strings["str"])); + Query event_has_other = Query::EventHasOther(); + Query other_pid = Query::OtherPidIs(other.thread.process_id); + Query other_tid = Query::OtherTidIs(other.thread.thread_id); + Query other_time = Query::OtherTimeIs(other.timestamp); + Query other_phase = Query::OtherPhaseIs(other.phase); + Query other_category = Query::OtherCategoryIs(other.category); + Query other_name = Query::OtherNameIs(other.name); + Query other_id = Query::OtherIdIs(other.id); + Query other_has_arg1 = Query::OtherHasNumberArg("num2"); + Query other_has_arg2 = Query::OtherHasStringArg("str2"); + Query other_arg1 = + (Query::OtherArg("num2") == Query::Double(other.arg_numbers["num2"])); + Query other_arg2 = + (Query::OtherArg("str2") == Query::String(other.arg_strings["str2"])); + + EXPECT_TRUE(event_pid.Evaluate(event)); + EXPECT_TRUE(event_tid.Evaluate(event)); + EXPECT_TRUE(event_time.Evaluate(event)); + EXPECT_TRUE(event_duration.Evaluate(event)); + EXPECT_TRUE(event_phase.Evaluate(event)); + EXPECT_TRUE(event_category.Evaluate(event)); + EXPECT_TRUE(event_name.Evaluate(event)); + EXPECT_TRUE(event_id.Evaluate(event)); + EXPECT_TRUE(event_has_arg1.Evaluate(event)); + EXPECT_TRUE(event_has_arg2.Evaluate(event)); + EXPECT_TRUE(event_arg1.Evaluate(event)); + EXPECT_TRUE(event_arg2.Evaluate(event)); + EXPECT_TRUE(event_has_other.Evaluate(event)); + EXPECT_TRUE(other_pid.Evaluate(event)); + EXPECT_TRUE(other_tid.Evaluate(event)); + EXPECT_TRUE(other_time.Evaluate(event)); + EXPECT_TRUE(other_phase.Evaluate(event)); + EXPECT_TRUE(other_category.Evaluate(event)); + EXPECT_TRUE(other_name.Evaluate(event)); + EXPECT_TRUE(other_id.Evaluate(event)); + EXPECT_TRUE(other_has_arg1.Evaluate(event)); + EXPECT_TRUE(other_has_arg2.Evaluate(event)); + EXPECT_TRUE(other_arg1.Evaluate(event)); + EXPECT_TRUE(other_arg2.Evaluate(event)); + + // Evaluate event queries against other to verify the queries fail when the + // event members are wrong. + EXPECT_FALSE(event_pid.Evaluate(other)); + EXPECT_FALSE(event_tid.Evaluate(other)); + EXPECT_FALSE(event_time.Evaluate(other)); + EXPECT_FALSE(event_duration.Evaluate(other)); + EXPECT_FALSE(event_phase.Evaluate(other)); + EXPECT_FALSE(event_category.Evaluate(other)); + EXPECT_FALSE(event_name.Evaluate(other)); + EXPECT_FALSE(event_id.Evaluate(other)); + EXPECT_FALSE(event_has_arg1.Evaluate(other)); + EXPECT_FALSE(event_has_arg2.Evaluate(other)); + EXPECT_FALSE(event_arg1.Evaluate(other)); + EXPECT_FALSE(event_arg2.Evaluate(other)); + EXPECT_FALSE(event_has_other.Evaluate(other)); +} + +TEST_F(TraceEventAnalyzerTest, BooleanOperators) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_INSTANT1("cat1", "name1", TRACE_EVENT_SCOPE_THREAD, "num", 1); + TRACE_EVENT_INSTANT1("cat1", "name2", TRACE_EVENT_SCOPE_THREAD, "num", 2); + TRACE_EVENT_INSTANT1("cat2", "name3", TRACE_EVENT_SCOPE_THREAD, "num", 3); + TRACE_EVENT_INSTANT1("cat2", "name4", TRACE_EVENT_SCOPE_THREAD, "num", 4); + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer); + analyzer->SetIgnoreMetadataEvents(true); + + TraceEventVector found; + + // == + + analyzer->FindEvents(Query::EventCategory() == Query::String("cat1"), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name2", found[1]->name.c_str()); + + analyzer->FindEvents(Query::EventArg("num") == Query::Int(2), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name2", found[0]->name.c_str()); + + // != + + analyzer->FindEvents(Query::EventCategory() != Query::String("cat1"), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name3", found[0]->name.c_str()); + EXPECT_STREQ("name4", found[1]->name.c_str()); + + analyzer->FindEvents(Query::EventArg("num") != Query::Int(2), &found); + ASSERT_EQ(3u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name3", found[1]->name.c_str()); + EXPECT_STREQ("name4", found[2]->name.c_str()); + + // < + analyzer->FindEvents(Query::EventArg("num") < Query::Int(2), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + + // <= + analyzer->FindEvents(Query::EventArg("num") <= Query::Int(2), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name2", found[1]->name.c_str()); + + // > + analyzer->FindEvents(Query::EventArg("num") > Query::Int(3), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name4", found[0]->name.c_str()); + + // >= + analyzer->FindEvents(Query::EventArg("num") >= Query::Int(4), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name4", found[0]->name.c_str()); + + // && + analyzer->FindEvents(Query::EventName() != Query::String("name1") && + Query::EventArg("num") < Query::Int(3), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name2", found[0]->name.c_str()); + + // || + analyzer->FindEvents(Query::EventName() == Query::String("name1") || + Query::EventArg("num") == Query::Int(3), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name3", found[1]->name.c_str()); + + // ! + analyzer->FindEvents(!(Query::EventName() == Query::String("name1") || + Query::EventArg("num") == Query::Int(3)), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name2", found[0]->name.c_str()); + EXPECT_STREQ("name4", found[1]->name.c_str()); +} + +TEST_F(TraceEventAnalyzerTest, ArithmeticOperators) { + ManualSetUp(); + + BeginTracing(); + { + // These events are searched for: + TRACE_EVENT_INSTANT2("cat1", "math1", TRACE_EVENT_SCOPE_THREAD, + "a", 10, "b", 5); + TRACE_EVENT_INSTANT2("cat1", "math2", TRACE_EVENT_SCOPE_THREAD, + "a", 10, "b", 10); + // Extra events that never match, for noise: + TRACE_EVENT_INSTANT2("noise", "math3", TRACE_EVENT_SCOPE_THREAD, + "a", 1, "b", 3); + TRACE_EVENT_INSTANT2("noise", "math4", TRACE_EVENT_SCOPE_THREAD, + "c", 10, "d", 5); + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + + TraceEventVector found; + + // Verify that arithmetic operators function: + + // + + analyzer->FindEvents(Query::EventArg("a") + Query::EventArg("b") == + Query::Int(20), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math2", found.front()->name.c_str()); + + // - + analyzer->FindEvents(Query::EventArg("a") - Query::EventArg("b") == + Query::Int(5), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math1", found.front()->name.c_str()); + + // * + analyzer->FindEvents(Query::EventArg("a") * Query::EventArg("b") == + Query::Int(50), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math1", found.front()->name.c_str()); + + // / + analyzer->FindEvents(Query::EventArg("a") / Query::EventArg("b") == + Query::Int(2), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math1", found.front()->name.c_str()); + + // % + analyzer->FindEvents(Query::EventArg("a") % Query::EventArg("b") == + Query::Int(0), &found); + EXPECT_EQ(2u, found.size()); + + // - (negate) + analyzer->FindEvents(-Query::EventArg("b") == Query::Int(-10), &found); + EXPECT_EQ(1u, found.size()); + EXPECT_STREQ("math2", found.front()->name.c_str()); +} + +TEST_F(TraceEventAnalyzerTest, StringPattern) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_INSTANT0("cat1", "name1", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat1", "name2", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat1", "no match", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_INSTANT0("cat1", "name3x", TRACE_EVENT_SCOPE_THREAD); + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->SetIgnoreMetadataEvents(true); + + TraceEventVector found; + + analyzer->FindEvents(Query::EventName() == Query::Pattern("name?"), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name2", found[1]->name.c_str()); + + analyzer->FindEvents(Query::EventName() == Query::Pattern("name*"), &found); + ASSERT_EQ(3u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name2", found[1]->name.c_str()); + EXPECT_STREQ("name3x", found[2]->name.c_str()); + + analyzer->FindEvents(Query::EventName() != Query::Pattern("name*"), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("no match", found[0]->name.c_str()); +} + +// Test that duration queries work. +TEST_F(TraceEventAnalyzerTest, BeginEndDuration) { + ManualSetUp(); + + const base::TimeDelta kSleepTime = base::TimeDelta::FromMilliseconds(200); + // We will search for events that have a duration of greater than 90% of the + // sleep time, so that there is no flakiness. + int64_t duration_cutoff_us = (kSleepTime.InMicroseconds() * 9) / 10; + + BeginTracing(); + { + TRACE_EVENT_BEGIN0("cat1", "name1"); // found by duration query + TRACE_EVENT_BEGIN0("noise", "name2"); // not searched for, just noise + { + TRACE_EVENT_BEGIN0("cat2", "name3"); // found by duration query + // next event not searched for, just noise + TRACE_EVENT_INSTANT0("noise", "name4", TRACE_EVENT_SCOPE_THREAD); + base::PlatformThread::Sleep(kSleepTime); + TRACE_EVENT_BEGIN0("cat2", "name5"); // not found (duration too short) + TRACE_EVENT_END0("cat2", "name5"); // not found (duration too short) + TRACE_EVENT_END0("cat2", "name3"); // found by duration query + } + TRACE_EVENT_END0("noise", "name2"); // not searched for, just noise + TRACE_EVENT_END0("cat1", "name1"); // found by duration query + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents( + Query::MatchBeginWithEnd() && + Query::EventDuration() > + Query::Int(static_cast<int>(duration_cutoff_us)) && + (Query::EventCategory() == Query::String("cat1") || + Query::EventCategory() == Query::String("cat2") || + Query::EventCategory() == Query::String("cat3")), + &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name3", found[1]->name.c_str()); +} + +// Test that duration queries work. +TEST_F(TraceEventAnalyzerTest, CompleteDuration) { + ManualSetUp(); + + const base::TimeDelta kSleepTime = base::TimeDelta::FromMilliseconds(200); + // We will search for events that have a duration of greater than 90% of the + // sleep time, so that there is no flakiness. + int64_t duration_cutoff_us = (kSleepTime.InMicroseconds() * 9) / 10; + + BeginTracing(); + { + TRACE_EVENT0("cat1", "name1"); // found by duration query + TRACE_EVENT0("noise", "name2"); // not searched for, just noise + { + TRACE_EVENT0("cat2", "name3"); // found by duration query + // next event not searched for, just noise + TRACE_EVENT_INSTANT0("noise", "name4", TRACE_EVENT_SCOPE_THREAD); + base::PlatformThread::Sleep(kSleepTime); + TRACE_EVENT0("cat2", "name5"); // not found (duration too short) + } + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents( + Query::EventCompleteDuration() > + Query::Int(static_cast<int>(duration_cutoff_us)) && + (Query::EventCategory() == Query::String("cat1") || + Query::EventCategory() == Query::String("cat2") || + Query::EventCategory() == Query::String("cat3")), + &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STREQ("name1", found[0]->name.c_str()); + EXPECT_STREQ("name3", found[1]->name.c_str()); +} + +// Test AssociateBeginEndEvents +TEST_F(TraceEventAnalyzerTest, BeginEndAssocations) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_END0("cat1", "name1"); // does not match out of order begin + TRACE_EVENT_BEGIN0("cat1", "name2"); + TRACE_EVENT_INSTANT0("cat1", "name3", TRACE_EVENT_SCOPE_THREAD); + TRACE_EVENT_BEGIN0("cat1", "name1"); + TRACE_EVENT_END0("cat1", "name2"); + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents(Query::MatchBeginWithEnd(), &found); + ASSERT_EQ(1u, found.size()); + EXPECT_STREQ("name2", found[0]->name.c_str()); +} + +// Test MergeAssociatedEventArgs +TEST_F(TraceEventAnalyzerTest, MergeAssociatedEventArgs) { + ManualSetUp(); + + const char arg_string[] = "arg_string"; + BeginTracing(); + { + TRACE_EVENT_BEGIN0("cat1", "name1"); + TRACE_EVENT_END1("cat1", "name1", "arg", arg_string); + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents(Query::MatchBeginName("name1"), &found); + ASSERT_EQ(1u, found.size()); + std::string arg_actual; + EXPECT_FALSE(found[0]->GetArgAsString("arg", &arg_actual)); + + analyzer->MergeAssociatedEventArgs(); + EXPECT_TRUE(found[0]->GetArgAsString("arg", &arg_actual)); + EXPECT_STREQ(arg_string, arg_actual.c_str()); +} + +// Test AssociateAsyncBeginEndEvents +TEST_F(TraceEventAnalyzerTest, AsyncBeginEndAssocations) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xA); // no match / out of order + TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xB); + TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xC); + TRACE_EVENT_INSTANT0("cat1", "name1", TRACE_EVENT_SCOPE_THREAD); // noise + TRACE_EVENT0("cat1", "name1"); // noise + TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xB); + TRACE_EVENT_ASYNC_END0("cat1", "name1", 0xC); + TRACE_EVENT_ASYNC_BEGIN0("cat1", "name1", 0xA); // no match / out of order + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateAsyncBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents(Query::MatchAsyncBeginWithNext(), &found); + ASSERT_EQ(2u, found.size()); + EXPECT_STRCASEEQ("0xb", found[0]->id.c_str()); + EXPECT_STRCASEEQ("0xc", found[1]->id.c_str()); +} + +// Test AssociateAsyncBeginEndEvents +TEST_F(TraceEventAnalyzerTest, AsyncBeginEndAssocationsWithSteps) { + ManualSetUp(); + + BeginTracing(); + { + TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xA, "s1"); + TRACE_EVENT_ASYNC_END0("c", "n", 0xA); + TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xB); + TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xC); + TRACE_EVENT_ASYNC_STEP_PAST0("c", "n", 0xB, "s1"); + TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xC, "s1"); + TRACE_EVENT_ASYNC_STEP_INTO1("c", "n", 0xC, "s2", "a", 1); + TRACE_EVENT_ASYNC_END0("c", "n", 0xB); + TRACE_EVENT_ASYNC_END0("c", "n", 0xC); + TRACE_EVENT_ASYNC_BEGIN0("c", "n", 0xA); + TRACE_EVENT_ASYNC_STEP_INTO0("c", "n", 0xA, "s2"); + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + analyzer->AssociateAsyncBeginEndEvents(); + + TraceEventVector found; + analyzer->FindEvents(Query::MatchAsyncBeginWithNext(), &found); + ASSERT_EQ(3u, found.size()); + + EXPECT_STRCASEEQ("0xb", found[0]->id.c_str()); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_PAST, found[0]->other_event->phase); + EXPECT_EQ(found[0], found[0]->other_event->prev_event); + EXPECT_TRUE(found[0]->other_event->other_event); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END, + found[0]->other_event->other_event->phase); + EXPECT_EQ(found[0]->other_event, + found[0]->other_event->other_event->prev_event); + + EXPECT_STRCASEEQ("0xc", found[1]->id.c_str()); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, found[1]->other_event->phase); + EXPECT_EQ(found[1], found[1]->other_event->prev_event); + EXPECT_TRUE(found[1]->other_event->other_event); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, + found[1]->other_event->other_event->phase); + EXPECT_EQ(found[1]->other_event, + found[1]->other_event->other_event->prev_event); + double arg_actual = 0; + EXPECT_TRUE(found[1]->other_event->other_event->GetArgAsNumber( + "a", &arg_actual)); + EXPECT_EQ(1.0, arg_actual); + EXPECT_TRUE(found[1]->other_event->other_event->other_event); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END, + found[1]->other_event->other_event->other_event->phase); + + EXPECT_STRCASEEQ("0xa", found[2]->id.c_str()); + EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, found[2]->other_event->phase); +} + +// Test that the TraceAnalyzer custom associations work. +TEST_F(TraceEventAnalyzerTest, CustomAssociations) { + ManualSetUp(); + + // Add events that begin/end in pipelined ordering with unique ID parameter + // to match up the begin/end pairs. + BeginTracing(); + { + // no begin match + TRACE_EVENT_INSTANT1("cat1", "end", TRACE_EVENT_SCOPE_THREAD, "id", 1); + // end is cat4 + TRACE_EVENT_INSTANT1("cat2", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 2); + // end is cat5 + TRACE_EVENT_INSTANT1("cat3", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 3); + TRACE_EVENT_INSTANT1("cat4", "end", TRACE_EVENT_SCOPE_THREAD, "id", 2); + TRACE_EVENT_INSTANT1("cat5", "end", TRACE_EVENT_SCOPE_THREAD, "id", 3); + // no end match + TRACE_EVENT_INSTANT1("cat6", "begin", TRACE_EVENT_SCOPE_THREAD, "id", 1); + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + + // begin, end, and match queries to find proper begin/end pairs. + Query begin(Query::EventName() == Query::String("begin")); + Query end(Query::EventName() == Query::String("end")); + Query match(Query::EventArg("id") == Query::OtherArg("id")); + analyzer->AssociateEvents(begin, end, match); + + TraceEventVector found; + + // cat1 has no other_event. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat1") && + Query::EventHasOther(), &found); + EXPECT_EQ(0u, found.size()); + + // cat1 has no other_event. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat1") && + !Query::EventHasOther(), &found); + EXPECT_EQ(1u, found.size()); + + // cat6 has no other_event. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat6") && + !Query::EventHasOther(), &found); + EXPECT_EQ(1u, found.size()); + + // cat2 and cat4 are associated. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat2") && + Query::OtherCategory() == Query::String("cat4"), &found); + EXPECT_EQ(1u, found.size()); + + // cat4 and cat2 are not associated. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat4") && + Query::OtherCategory() == Query::String("cat2"), &found); + EXPECT_EQ(0u, found.size()); + + // cat3 and cat5 are associated. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat3") && + Query::OtherCategory() == Query::String("cat5"), &found); + EXPECT_EQ(1u, found.size()); + + // cat5 and cat3 are not associated. + analyzer->FindEvents(Query::EventCategory() == Query::String("cat5") && + Query::OtherCategory() == Query::String("cat3"), &found); + EXPECT_EQ(0u, found.size()); +} + +// Verify that Query literals and types are properly casted. +TEST_F(TraceEventAnalyzerTest, Literals) { + ManualSetUp(); + + // Since these queries don't refer to the event data, the dummy event below + // will never be accessed. + TraceEvent dummy; + char char_num = 5; + short short_num = -5; + EXPECT_TRUE((Query::Double(5.0) == Query::Int(char_num)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(-5.0) == Query::Int(short_num)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(1.0) == Query::Uint(1u)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(1.0) == Query::Int(1)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(-1.0) == Query::Int(-1)).Evaluate(dummy)); + EXPECT_TRUE((Query::Double(1.0) == Query::Double(1.0f)).Evaluate(dummy)); + EXPECT_TRUE((Query::Bool(true) == Query::Int(1)).Evaluate(dummy)); + EXPECT_TRUE((Query::Bool(false) == Query::Int(0)).Evaluate(dummy)); + EXPECT_TRUE((Query::Bool(true) == Query::Double(1.0f)).Evaluate(dummy)); + EXPECT_TRUE((Query::Bool(false) == Query::Double(0.0f)).Evaluate(dummy)); +} + +// Test GetRateStats. +TEST_F(TraceEventAnalyzerTest, RateStats) { + std::vector<TraceEvent> events; + events.reserve(100); + TraceEventVector event_ptrs; + double timestamp = 0.0; + double little_delta = 1.0; + double big_delta = 10.0; + double tiny_delta = 0.1; + RateStats stats; + RateStatsOptions options; + + // Insert 10 events, each apart by little_delta. + for (int i = 0; i < 10; ++i) { + timestamp += little_delta; + TraceEvent event; + event.timestamp = timestamp; + events.push_back(std::move(event)); + event_ptrs.push_back(&events.back()); + } + + ASSERT_TRUE(GetRateStats(event_ptrs, &stats, nullptr)); + EXPECT_EQ(little_delta, stats.mean_us); + EXPECT_EQ(little_delta, stats.min_us); + EXPECT_EQ(little_delta, stats.max_us); + EXPECT_EQ(0.0, stats.standard_deviation_us); + + // Add an event apart by big_delta. + { + timestamp += big_delta; + TraceEvent event; + event.timestamp = timestamp; + events.push_back(std::move(event)); + event_ptrs.push_back(&events.back()); + } + + ASSERT_TRUE(GetRateStats(event_ptrs, &stats, nullptr)); + EXPECT_LT(little_delta, stats.mean_us); + EXPECT_EQ(little_delta, stats.min_us); + EXPECT_EQ(big_delta, stats.max_us); + EXPECT_LT(0.0, stats.standard_deviation_us); + + // Trim off the biggest delta and verify stats. + options.trim_min = 0; + options.trim_max = 1; + ASSERT_TRUE(GetRateStats(event_ptrs, &stats, &options)); + EXPECT_EQ(little_delta, stats.mean_us); + EXPECT_EQ(little_delta, stats.min_us); + EXPECT_EQ(little_delta, stats.max_us); + EXPECT_EQ(0.0, stats.standard_deviation_us); + + // Add an event apart by tiny_delta. + { + timestamp += tiny_delta; + TraceEvent event; + event.timestamp = timestamp; + events.push_back(std::move(event)); + event_ptrs.push_back(&events.back()); + } + + // Trim off both the biggest and tiniest delta and verify stats. + options.trim_min = 1; + options.trim_max = 1; + ASSERT_TRUE(GetRateStats(event_ptrs, &stats, &options)); + EXPECT_EQ(little_delta, stats.mean_us); + EXPECT_EQ(little_delta, stats.min_us); + EXPECT_EQ(little_delta, stats.max_us); + EXPECT_EQ(0.0, stats.standard_deviation_us); + + // Verify smallest allowed number of events. + { + TraceEvent event; + TraceEventVector few_event_ptrs; + few_event_ptrs.push_back(&event); + few_event_ptrs.push_back(&event); + ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, nullptr)); + few_event_ptrs.push_back(&event); + ASSERT_TRUE(GetRateStats(few_event_ptrs, &stats, nullptr)); + + // Trim off more than allowed and verify failure. + options.trim_min = 0; + options.trim_max = 1; + ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, &options)); + } +} + +// Test FindFirstOf and FindLastOf. +TEST_F(TraceEventAnalyzerTest, FindOf) { + size_t num_events = 100; + size_t index = 0; + TraceEventVector event_ptrs; + EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(true), 0, &index)); + EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(true), 10, &index)); + EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(true), 0, &index)); + EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(true), 10, &index)); + + std::vector<TraceEvent> events; + events.resize(num_events); + for (auto& i : events) + event_ptrs.push_back(&i); + size_t bam_index = num_events/2; + events[bam_index].name = "bam"; + Query query_bam = Query::EventName() == Query::String(events[bam_index].name); + + // FindFirstOf + EXPECT_FALSE(FindFirstOf(event_ptrs, Query::Bool(false), 0, &index)); + EXPECT_TRUE(FindFirstOf(event_ptrs, Query::Bool(true), 0, &index)); + EXPECT_EQ(0u, index); + EXPECT_TRUE(FindFirstOf(event_ptrs, Query::Bool(true), 5, &index)); + EXPECT_EQ(5u, index); + + EXPECT_FALSE(FindFirstOf(event_ptrs, query_bam, bam_index + 1, &index)); + EXPECT_TRUE(FindFirstOf(event_ptrs, query_bam, 0, &index)); + EXPECT_EQ(bam_index, index); + EXPECT_TRUE(FindFirstOf(event_ptrs, query_bam, bam_index, &index)); + EXPECT_EQ(bam_index, index); + + // FindLastOf + EXPECT_FALSE(FindLastOf(event_ptrs, Query::Bool(false), 1000, &index)); + EXPECT_TRUE(FindLastOf(event_ptrs, Query::Bool(true), 1000, &index)); + EXPECT_EQ(num_events - 1, index); + EXPECT_TRUE(FindLastOf(event_ptrs, Query::Bool(true), num_events - 5, + &index)); + EXPECT_EQ(num_events - 5, index); + + EXPECT_FALSE(FindLastOf(event_ptrs, query_bam, bam_index - 1, &index)); + EXPECT_TRUE(FindLastOf(event_ptrs, query_bam, num_events, &index)); + EXPECT_EQ(bam_index, index); + EXPECT_TRUE(FindLastOf(event_ptrs, query_bam, bam_index, &index)); + EXPECT_EQ(bam_index, index); +} + +// Test FindClosest. +TEST_F(TraceEventAnalyzerTest, FindClosest) { + size_t index_1 = 0; + size_t index_2 = 0; + TraceEventVector event_ptrs; + EXPECT_FALSE(FindClosest(event_ptrs, Query::Bool(true), 0, + &index_1, &index_2)); + + size_t num_events = 5; + std::vector<TraceEvent> events; + events.resize(num_events); + for (size_t i = 0; i < events.size(); ++i) { + // timestamps go up exponentially so the lower index is always closer in + // time than the higher index. + events[i].timestamp = static_cast<double>(i) * static_cast<double>(i); + event_ptrs.push_back(&events[i]); + } + events[0].name = "one"; + events[2].name = "two"; + events[4].name = "three"; + Query query_named = Query::EventName() != Query::String(std::string()); + Query query_one = Query::EventName() == Query::String("one"); + + // Only one event matches query_one, so two closest can't be found. + EXPECT_FALSE(FindClosest(event_ptrs, query_one, 0, &index_1, &index_2)); + + EXPECT_TRUE(FindClosest(event_ptrs, query_one, 3, &index_1, nullptr)); + EXPECT_EQ(0u, index_1); + + EXPECT_TRUE(FindClosest(event_ptrs, query_named, 1, &index_1, &index_2)); + EXPECT_EQ(0u, index_1); + EXPECT_EQ(2u, index_2); + + EXPECT_TRUE(FindClosest(event_ptrs, query_named, 4, &index_1, &index_2)); + EXPECT_EQ(4u, index_1); + EXPECT_EQ(2u, index_2); + + EXPECT_TRUE(FindClosest(event_ptrs, query_named, 3, &index_1, &index_2)); + EXPECT_EQ(2u, index_1); + EXPECT_EQ(0u, index_2); +} + +// Test CountMatches. +TEST_F(TraceEventAnalyzerTest, CountMatches) { + TraceEventVector event_ptrs; + EXPECT_EQ(0u, CountMatches(event_ptrs, Query::Bool(true), 0, 10)); + + size_t num_events = 5; + size_t num_named = 3; + std::vector<TraceEvent> events; + events.resize(num_events); + for (auto& i : events) + event_ptrs.push_back(&i); + events[0].name = "one"; + events[2].name = "two"; + events[4].name = "three"; + Query query_named = Query::EventName() != Query::String(std::string()); + Query query_one = Query::EventName() == Query::String("one"); + + EXPECT_EQ(0u, CountMatches(event_ptrs, Query::Bool(false))); + EXPECT_EQ(num_events, CountMatches(event_ptrs, Query::Bool(true))); + EXPECT_EQ(num_events - 1, CountMatches(event_ptrs, Query::Bool(true), + 1, num_events)); + EXPECT_EQ(1u, CountMatches(event_ptrs, query_one)); + EXPECT_EQ(num_events - 1, CountMatches(event_ptrs, !query_one)); + EXPECT_EQ(num_named, CountMatches(event_ptrs, query_named)); +} + +TEST_F(TraceEventAnalyzerTest, ComplexArgument) { + ManualSetUp(); + + BeginTracing(); + { + std::unique_ptr<base::trace_event::TracedValue> value( + new base::trace_event::TracedValue); + value->SetString("property", "value"); + TRACE_EVENT1("cat", "name", "arg", std::move(value)); + } + EndTracing(); + + std::unique_ptr<TraceAnalyzer> analyzer( + TraceAnalyzer::Create(output_.json_output)); + ASSERT_TRUE(analyzer.get()); + + TraceEventVector events; + analyzer->FindEvents(Query::EventName() == Query::String("name"), &events); + + EXPECT_EQ(1u, events.size()); + EXPECT_EQ("cat", events[0]->category); + EXPECT_EQ("name", events[0]->name); + EXPECT_TRUE(events[0]->HasArg("arg")); + + std::unique_ptr<base::Value> arg; + events[0]->GetArgAsValue("arg", &arg); + base::DictionaryValue* arg_dict; + EXPECT_TRUE(arg->GetAsDictionary(&arg_dict)); + std::string property; + EXPECT_TRUE(arg_dict->GetString("property", &property)); + EXPECT_EQ("value", property); +} + +} // namespace trace_analyzer diff --git a/chromium/base/test/trace_to_file.cc b/chromium/base/test/trace_to_file.cc new file mode 100644 index 00000000000..5ada1d00c2a --- /dev/null +++ b/chromium/base/test/trace_to_file.cc @@ -0,0 +1,112 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/trace_to_file.h" + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_util.h" +#include "base/memory/ref_counted_memory.h" +#include "base/run_loop.h" +#include "base/test/task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/trace_event/trace_buffer.h" +#include "base/trace_event/trace_log.h" + +namespace base { +namespace test { + +TraceToFile::TraceToFile() : started_(false) { +} + +TraceToFile::~TraceToFile() { + EndTracingIfNeeded(); +} + +void TraceToFile::BeginTracingFromCommandLineOptions() { + DCHECK(CommandLine::InitializedForCurrentProcess()); + DCHECK(!started_); + + if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kTraceToFile)) + return; + + // Empty filter (i.e. just --trace-to-file) turns into default categories in + // TraceEventImpl + std::string filter = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTraceToFile); + + FilePath path; + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTraceToFileName)) { + path = FilePath(CommandLine::ForCurrentProcess() + ->GetSwitchValuePath(switches::kTraceToFileName)); + } else { + path = FilePath(FILE_PATH_LITERAL("trace.json")); + } + + BeginTracing(path, filter); +} + +void TraceToFile::BeginTracing(const FilePath& path, + const std::string& categories) { + DCHECK(!started_); + started_ = true; + path_ = path; + WriteFileHeader(); + + trace_event::TraceLog::GetInstance()->SetEnabled( + trace_event::TraceConfig(categories, trace_event::RECORD_UNTIL_FULL), + trace_event::TraceLog::RECORDING_MODE); +} + +void TraceToFile::WriteFileHeader() { + WriteFile(path_, "{\"traceEvents\": ["); +} + +void TraceToFile::AppendFileFooter() { + const char str[] = "]}"; + AppendToFile(path_, str, static_cast<int>(strlen(str))); +} + +void TraceToFile::TraceOutputCallback(const std::string& data) { + bool ret = AppendToFile(path_, data.c_str(), static_cast<int>(data.size())); + DCHECK(ret); +} + +static void OnTraceDataCollected( + OnceClosure quit_closure, + trace_event::TraceResultBuffer* buffer, + const scoped_refptr<RefCountedString>& json_events_str, + bool has_more_events) { + buffer->AddFragment(json_events_str->data()); + if (!has_more_events) + std::move(quit_closure).Run(); +} + +void TraceToFile::EndTracingIfNeeded() { + if (!started_) + return; + started_ = false; + + trace_event::TraceLog::GetInstance()->SetDisabled(); + + trace_event::TraceResultBuffer buffer; + buffer.SetOutputCallback( + BindRepeating(&TraceToFile::TraceOutputCallback, Unretained(this))); + + // In tests we might not have a TaskEnvironment, create one if needed. + std::unique_ptr<SingleThreadTaskEnvironment> task_environment; + if (!ThreadTaskRunnerHandle::IsSet()) + task_environment = std::make_unique<SingleThreadTaskEnvironment>(); + + RunLoop run_loop; + trace_event::TraceLog::GetInstance()->Flush(BindRepeating( + &OnTraceDataCollected, run_loop.QuitClosure(), Unretained(&buffer))); + run_loop.Run(); + + AppendFileFooter(); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/trace_to_file.h b/chromium/base/test/trace_to_file.h new file mode 100644 index 00000000000..43087367c30 --- /dev/null +++ b/chromium/base/test/trace_to_file.h @@ -0,0 +1,35 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TRACE_TO_FILE_H_ +#define BASE_TEST_TRACE_TO_FILE_H_ + +#include "base/files/file_path.h" + +namespace base { +namespace test { + +class TraceToFile { + public: + TraceToFile(); + ~TraceToFile(); + + void BeginTracingFromCommandLineOptions(); + void BeginTracing(const base::FilePath& path, const std::string& categories); + void EndTracingIfNeeded(); + + private: + void WriteFileHeader(); + void AppendFileFooter(); + + void TraceOutputCallback(const std::string& data); + + base::FilePath path_; + bool started_; +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_TRACE_TO_FILE_H_ diff --git a/chromium/base/test/values_test_util.cc b/chromium/base/test/values_test_util.cc new file mode 100644 index 00000000000..5016d5698a2 --- /dev/null +++ b/chromium/base/test/values_test_util.cc @@ -0,0 +1,247 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/values_test_util.h" + +#include <ostream> +#include <utility> + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +void ExpectDictBooleanValue(bool expected_value, + const DictionaryValue& value, + const std::string& key) { + bool boolean_value = false; + EXPECT_TRUE(value.GetBoolean(key, &boolean_value)) << key; + EXPECT_EQ(expected_value, boolean_value) << key; +} + +void ExpectDictDictionaryValue(const DictionaryValue& expected_value, + const DictionaryValue& value, + const std::string& key) { + const DictionaryValue* dict_value = nullptr; + EXPECT_TRUE(value.GetDictionary(key, &dict_value)) << key; + EXPECT_EQ(expected_value, *dict_value) << key; +} + +void ExpectDictIntegerValue(int expected_value, + const DictionaryValue& value, + const std::string& key) { + int integer_value = 0; + EXPECT_TRUE(value.GetInteger(key, &integer_value)) << key; + EXPECT_EQ(expected_value, integer_value) << key; +} + +void ExpectDictListValue(const ListValue& expected_value, + const DictionaryValue& value, + const std::string& key) { + const ListValue* list_value = nullptr; + EXPECT_TRUE(value.GetList(key, &list_value)) << key; + EXPECT_EQ(expected_value, *list_value) << key; +} + +void ExpectDictStringValue(const std::string& expected_value, + const DictionaryValue& value, + const std::string& key) { + std::string string_value; + EXPECT_TRUE(value.GetString(key, &string_value)) << key; + EXPECT_EQ(expected_value, string_value) << key; +} + +void ExpectStringValue(const std::string& expected_str, const Value& actual) { + EXPECT_EQ(Value::Type::STRING, actual.type()); + EXPECT_EQ(expected_str, actual.GetString()); +} + +namespace test { + +namespace { + +std::string FormatAsJSON(const base::Value& value) { + std::string json; + JSONWriter::Write(value, &json); + return json; +} + +class DictionaryHasValueMatcher + : public testing::MatcherInterface<const base::Value&> { + public: + DictionaryHasValueMatcher(const std::string& key, + const base::Value& expected_value) + : key_(key), expected_value_(expected_value.Clone()) {} + + ~DictionaryHasValueMatcher() = default; + + bool MatchAndExplain(const base::Value& value, + testing::MatchResultListener* listener) const override { + if (!value.is_dict()) { + *listener << "The value '" << FormatAsJSON(value) + << "' is not a dictionary"; + return false; + } + const base::Value* sub_value = value.FindKey(key_); + if (!sub_value) { + *listener << "Dictionary '" << FormatAsJSON(value) + << "' does not have key '" << key_ << "'"; + return false; + } + if (*sub_value != expected_value_) { + *listener << "Dictionary value under key '" << key_ << "' is '" + << FormatAsJSON(*sub_value) << "', expected '" + << FormatAsJSON(expected_value_) << "'"; + return false; + } + return true; + } + + void DescribeTo(std::ostream* os) const override { + *os << "has key '" << key_ << "' with value '" + << FormatAsJSON(expected_value_) << "'"; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not have key '" << key_ << "' with value '" + << FormatAsJSON(expected_value_) << "'"; + } + + private: + DictionaryHasValueMatcher& operator=(const DictionaryHasValueMatcher& other) = + delete; + + const std::string key_; + const base::Value expected_value_; +}; + +class DictionaryHasValuesMatcher + : public testing::MatcherInterface<const base::Value&> { + public: + DictionaryHasValuesMatcher(const base::Value& template_value) + : template_value_(template_value.Clone()) { + CHECK(template_value.is_dict()); + } + + ~DictionaryHasValuesMatcher() = default; + + bool MatchAndExplain(const base::Value& value, + testing::MatchResultListener* listener) const override { + if (!value.is_dict()) { + *listener << "The value '" << FormatAsJSON(value) + << "' is not a dictionary"; + return false; + } + + bool ok = true; + for (const auto& template_dict_item : template_value_.DictItems()) { + const base::Value* sub_value = value.FindKey(template_dict_item.first); + if (!sub_value) { + *listener << "\nDictionary does not have key '" + << template_dict_item.first << "'"; + ok = false; + continue; + } + if (*sub_value != template_dict_item.second) { + *listener << "\nDictionary value under key '" + << template_dict_item.first << "' is '" + << FormatAsJSON(*sub_value) << "', expected '" + << FormatAsJSON(template_dict_item.second) << "'"; + ok = false; + } + } + return ok; + } + + void DescribeTo(std::ostream* os) const override { + *os << "contains all key-values from '" << FormatAsJSON(template_value_) + << "'"; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not contain key-values from '" << FormatAsJSON(template_value_) + << "'"; + } + + private: + DictionaryHasValueMatcher& operator=(const DictionaryHasValueMatcher& other) = + delete; + + const base::Value template_value_; +}; + +} // namespace + +testing::Matcher<const base::Value&> DictionaryHasValue( + const std::string& key, + const base::Value& expected_value) { + return testing::MakeMatcher( + new DictionaryHasValueMatcher(key, expected_value)); +} + +testing::Matcher<const base::Value&> DictionaryHasValues( + const base::Value& template_value) { + return testing::MakeMatcher(new DictionaryHasValuesMatcher(template_value)); +} + +IsJsonMatcher::IsJsonMatcher(base::StringPiece json) + : expected_value_(test::ParseJson(json)) {} + +IsJsonMatcher::IsJsonMatcher(const base::Value& value) + : expected_value_(value.Clone()) {} + +IsJsonMatcher::IsJsonMatcher(const IsJsonMatcher& other) + : expected_value_(other.expected_value_.Clone()) {} + +IsJsonMatcher::~IsJsonMatcher() = default; + +bool IsJsonMatcher::MatchAndExplain( + base::StringPiece json, + testing::MatchResultListener* listener) const { + // This is almost the same logic as ParseJson, but the parser uses stricter + // options for JSON data that is assumed to be generated by the code under + // test rather than written by hand as part of a unit test. + JSONReader::ValueWithError ret = + JSONReader::ReadAndReturnValueWithError(json, JSON_PARSE_RFC); + if (!ret.value) { + *listener << "Failed to parse \"" << json << "\": " << ret.error_message; + return false; + } + return MatchAndExplain(*ret.value, listener); +} + +bool IsJsonMatcher::MatchAndExplain( + const base::Value& value, + testing::MatchResultListener* /* listener */) const { + return expected_value_ == value; +} + +void IsJsonMatcher::DescribeTo(std::ostream* os) const { + *os << "is the JSON value " << expected_value_; +} + +void IsJsonMatcher::DescribeNegationTo(std::ostream* os) const { + *os << "is not the JSON value " << expected_value_; +} + +Value ParseJson(StringPiece json) { + JSONReader::ValueWithError result = + JSONReader::ReadAndReturnValueWithError(json, JSON_ALLOW_TRAILING_COMMAS); + if (!result.value) { + ADD_FAILURE() << "Failed to parse \"" << json + << "\": " << result.error_message; + return Value(); + } + return std::move(result.value.value()); +} + +std::unique_ptr<Value> ParseJsonDeprecated(StringPiece json) { + return Value::ToUniquePtrValue(ParseJson(json)); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/test/values_test_util.h b/chromium/base/test/values_test_util.h new file mode 100644 index 00000000000..7f48bfd3898 --- /dev/null +++ b/chromium/base/test/values_test_util.h @@ -0,0 +1,105 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_VALUES_TEST_UTIL_H_ +#define BASE_TEST_VALUES_TEST_UTIL_H_ + +#include <iosfwd> +#include <memory> +#include <string> + +#include "base/strings/string_piece.h" +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock-matchers.h" + +namespace base { + +// All the functions below expect that the value for the given key in +// the given dictionary equals the given expected value. + +void ExpectDictBooleanValue(bool expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectDictDictionaryValue(const DictionaryValue& expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectDictIntegerValue(int expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectDictListValue(const ListValue& expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectDictStringValue(const std::string& expected_value, + const DictionaryValue& value, + const std::string& key); + +void ExpectStringValue(const std::string& expected_str, const Value& actual); + +namespace test { + +// A custom GMock matcher which matches if a base::Value is a dictionary which +// has a key |key| that is equal to |value|. +testing::Matcher<const base::Value&> DictionaryHasValue( + const std::string& key, + const base::Value& expected_value); + +// A custom GMock matcher which matches if a base::Value is a dictionary which +// contains all key/value pairs from |template_value|. +testing::Matcher<const base::Value&> DictionaryHasValues( + const base::Value& template_value); + +// A custom GMock matcher. For details, see +// https://github.com/google/googletest/blob/644319b9f06f6ca9bf69fe791be399061044bc3d/googlemock/docs/CookBook.md#writing-new-polymorphic-matchers +class IsJsonMatcher { + public: + explicit IsJsonMatcher(base::StringPiece json); + explicit IsJsonMatcher(const base::Value& value); + IsJsonMatcher(const IsJsonMatcher& other); + ~IsJsonMatcher(); + + bool MatchAndExplain(base::StringPiece json, + testing::MatchResultListener* listener) const; + bool MatchAndExplain(const base::Value& value, + testing::MatchResultListener* listener) const; + void DescribeTo(std::ostream* os) const; + void DescribeNegationTo(std::ostream* os) const; + + private: + IsJsonMatcher& operator=(const IsJsonMatcher& other) = delete; + + base::Value expected_value_; +}; + +// Creates a GMock matcher for testing equivalence of JSON values represented as +// either JSON strings or base::Value objects. Parsing of the expected value +// uses ParseJson(), which allows trailing commas for convenience. Parsing of +// the actual value follows the JSON spec strictly. +// +// Although it possible to use this matcher when the actual and expected values +// are both base::Value objects, there is no advantage in that case to using +// this matcher in place of GMock's normal equality semantics. +template <typename T> +inline testing::PolymorphicMatcher<IsJsonMatcher> IsJson(const T& value) { + return testing::MakePolymorphicMatcher(IsJsonMatcher(value)); +} + +// Parses |json| as JSON, allowing trailing commas, and returns the resulting +// value. If |json| fails to parse, causes an EXPECT failure and returns the +// Null Value. +Value ParseJson(StringPiece json); + +// DEPRECATED. +// Parses |json| as JSON, allowing trailing commas, and returns the +// resulting value. If the json fails to parse, causes an EXPECT +// failure and returns the Null Value (but never a NULL pointer). +std::unique_ptr<Value> ParseJsonDeprecated(StringPiece json); + +} // namespace test +} // namespace base + +#endif // BASE_TEST_VALUES_TEST_UTIL_H_ diff --git a/chromium/base/test/with_feature_override.cc b/chromium/base/test/with_feature_override.cc new file mode 100644 index 00000000000..7a9d78705ab --- /dev/null +++ b/chromium/base/test/with_feature_override.cc @@ -0,0 +1,32 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/with_feature_override.h" +#include "base/task/thread_pool/thread_pool_instance.h" + +namespace base { +namespace test { + +WithFeatureOverride::WithFeatureOverride(const base::Feature& feature) { + // Most other classes that tests inherit from start task environments. Verify + // that has not happened yet. + DCHECK(base::ThreadPoolInstance::Get() == nullptr) + << "WithFeatureOverride should be the first class a test inherits from " + "so it sets the features before any other setup is done."; + + if (GetParam()) { + scoped_feature_list_.InitAndEnableFeature(feature); + } else { + scoped_feature_list_.InitAndDisableFeature(feature); + } +} + +bool WithFeatureOverride::IsParamFeatureEnabled() { + return GetParam(); +} + +WithFeatureOverride::~WithFeatureOverride() = default; + +} // namespace test +} // namespace base diff --git a/chromium/base/test/with_feature_override.h b/chromium/base/test/with_feature_override.h new file mode 100644 index 00000000000..9a88253c30c --- /dev/null +++ b/chromium/base/test/with_feature_override.h @@ -0,0 +1,55 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_WITH_FEATURE_OVERRIDE_H_ +#define BASE_TEST_WITH_FEATURE_OVERRIDE_H_ + +#include "base/feature_list.h" +#include "base/macros.h" +#include "base/test/scoped_feature_list.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace test { + +#define INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(test_name) \ + INSTANTIATE_TEST_SUITE_P(All, test_name, testing::Values(false, true)) + +// Base class for a test fixture that must run with a feature enabled and +// disabled. Must be the first base class of the test fixture to take effect +// during the construction of the test fixture itself. +// +// Example usage: +// +// class MyTest : public base::WithFeatureOverride, public testing::Test { +// public: +// MyTest() : WithFeatureOverride(kMyFeature){} +// }; +// +// TEST_P(MyTest, FooBar) { +// This will run with both the kMyFeature enabled and disabled. +// } +// +// INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(MyTest); + +class WithFeatureOverride : public testing::WithParamInterface<bool> { + public: + explicit WithFeatureOverride(const base::Feature& feature); + ~WithFeatureOverride(); + + WithFeatureOverride(const WithFeatureOverride&) = delete; + WithFeatureOverride& operator=(const WithFeatureOverride&) = delete; + + // Use to know if the configured feature provided in the ctor is enabled or + // not. + bool IsParamFeatureEnabled(); + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +} // namespace test +} // namespace base + +#endif // BASE_TEST_WITH_FEATURE_OVERRIDE_H_ |