Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
new file mode 100644
index 0000000..add74cb
--- /dev/null
+++ b/base/test/BUILD.gn
@@ -0,0 +1,173 @@
+# 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.
+
+import("//build/config/ui.gni")
+
+if (is_android) {
+ import("//build/config/android/rules.gni")
+}
+
+# GYP: //base/base.gyp:test_support_base
+source_set("test_support") {
+ # TODO http://crbug.com/412064 enable this flag all the time.
+ testonly = !is_component_build
+ sources = [
+ "expectations/expectation.cc",
+ "expectations/expectation.h",
+ "expectations/parser.cc",
+ "expectations/parser.h",
+ "gtest_xml_util.cc",
+ "gtest_xml_util.h",
+ "histogram_tester.cc",
+ "histogram_tester.h",
+ "launcher/test_launcher.cc",
+ "launcher/test_launcher.h",
+ "launcher/test_result.cc",
+ "launcher/test_result.h",
+ "launcher/test_results_tracker.cc",
+ "launcher/test_results_tracker.h",
+ "launcher/unit_test_launcher.cc",
+ "launcher/unit_test_launcher.h",
+ "launcher/unit_test_launcher_ios.cc",
+ "mock_chrome_application_mac.h",
+ "mock_chrome_application_mac.mm",
+ "mock_devices_changed_observer.cc",
+ "mock_devices_changed_observer.h",
+ "mock_time_provider.cc",
+ "mock_time_provider.h",
+ "multiprocess_test.cc",
+ "multiprocess_test.h",
+ "multiprocess_test_android.cc",
+ "null_task_runner.cc",
+ "null_task_runner.h",
+ "perf_log.cc",
+ "perf_log.h",
+ "perf_test_suite.cc",
+ "perf_test_suite.h",
+ "perf_time_logger.cc",
+ "perf_time_logger.h",
+ "power_monitor_test_base.cc",
+ "power_monitor_test_base.h",
+ "scoped_locale.cc",
+ "scoped_locale.h",
+ "scoped_path_override.cc",
+ "scoped_path_override.h",
+ "sequenced_task_runner_test_template.cc",
+ "sequenced_task_runner_test_template.h",
+ "sequenced_worker_pool_owner.cc",
+ "sequenced_worker_pool_owner.h",
+ "simple_test_clock.cc",
+ "simple_test_clock.h",
+ "simple_test_tick_clock.cc",
+ "simple_test_tick_clock.h",
+ "task_runner_test_template.cc",
+ "task_runner_test_template.h",
+ "test_file_util.cc",
+ "test_file_util.h",
+ "test_file_util_android.cc",
+ "test_file_util_linux.cc",
+ "test_file_util_mac.cc",
+ "test_file_util_posix.cc",
+ "test_file_util_win.cc",
+ "test_io_thread.cc",
+ "test_io_thread.h",
+ "test_listener_ios.h",
+ "test_listener_ios.mm",
+ "test_pending_task.cc",
+ "test_pending_task.h",
+ "test_reg_util_win.cc",
+ "test_reg_util_win.h",
+ "test_shortcut_win.cc",
+ "test_shortcut_win.h",
+ "test_simple_task_runner.cc",
+ "test_simple_task_runner.h",
+ "test_suite.cc",
+ "test_suite.h",
+ "test_support_android.cc",
+ "test_support_android.h",
+ "test_support_ios.h",
+ "test_support_ios.mm",
+ "test_switches.cc",
+ "test_switches.h",
+ "test_timeouts.cc",
+ "test_timeouts.h",
+ "thread_test_helper.cc",
+ "thread_test_helper.h",
+ "trace_event_analyzer.cc",
+ "trace_event_analyzer.h",
+ "trace_to_file.cc",
+ "trace_to_file.h",
+ "values_test_util.cc",
+ "values_test_util.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//base:i18n",
+ "//base:base_static",
+ ]
+ deps = [
+ "//base/third_party/dynamic_annotations",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/libxml"
+ ]
+
+ if (!is_posix) {
+ sources -= [
+ "scoped_locale.cc",
+ "scoped_locale.h",
+ ]
+ }
+ if (is_ios) {
+ # iOS uses its own unit test launcher.
+ sources -= [ "launcher/unit_test_launcher.cc" ]
+
+ # Pull in specific Mac files for iOS (which have been filtered out
+ # by file name rules).
+ set_sources_assignment_filter([])
+ sources += [ "test_file_util_mac.cc" ]
+ }
+
+ if (is_android) {
+ deps += [ ":base_unittests_jni_headers" ]
+ }
+}
+
+config("perf_test_config") {
+ defines = [ "PERF_TEST" ]
+}
+
+source_set("test_support_perf") {
+ testonly = true
+ sources = [
+ "run_all_perftests.cc",
+ ]
+ deps = [
+ ":test_support",
+ "//base",
+ "//testing/gtest",
+ ]
+
+ public_configs = [ ":perf_test_config" ]
+}
+
+source_set("run_all_unittests") {
+ testonly = true
+ sources = [
+ "run_all_unittests.cc",
+ ]
+ deps = [
+ ":test_support",
+ ]
+}
+
+if (is_android) {
+ generate_jni("base_unittests_jni_headers") {
+ sources = [
+ "android/java/src/org/chromium/base/ContentUriTestUtils.java",
+ ]
+ jni_package = "base"
+ }
+}
diff --git a/base/test/DEPS b/base/test/DEPS
new file mode 100644
index 0000000..5827c26
--- /dev/null
+++ b/base/test/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/libxml",
+]
diff --git a/base/test/OWNERS b/base/test/OWNERS
new file mode 100644
index 0000000..92ecc88
--- /dev/null
+++ b/base/test/OWNERS
@@ -0,0 +1 @@
+phajdan.jr@chromium.org
diff --git a/base/test/android/OWNERS b/base/test/android/OWNERS
new file mode 100644
index 0000000..25c1d80
--- /dev/null
+++ b/base/test/android/OWNERS
@@ -0,0 +1,2 @@
+nyquist@chromium.org
+yfriedman@chromium.org
diff --git a/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java b/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java
new file mode 100644
index 0000000..4a1613b
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/ContentUriTestUtils.java
@@ -0,0 +1,48 @@
+// 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.
+
+package org.chromium.base;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+/**
+ * Utilities for testing operations on content URI.
+ */
+public class ContentUriTestUtils {
+ /**
+ * Insert an image into the MediaStore, and return the content URI. If the
+ * image already exists in the MediaStore, just retrieve the URI.
+ *
+ * @param context Application context.
+ * @param path Path to the image file.
+ * @return Content URI of the image.
+ */
+ @CalledByNative
+ private static String insertImageIntoMediaStore(Context context, String path) {
+ // Check whether the content URI exists.
+ Cursor c = context.getContentResolver().query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[] { MediaStore.Video.VideoColumns._ID },
+ MediaStore.Images.Media.DATA + " LIKE ?",
+ new String[] { path },
+ null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ int id = c.getInt(0);
+ return Uri.withAppendedPath(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id).toString();
+ }
+
+ // Insert the content URI into MediaStore.
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, path);
+ Uri uri = context.getContentResolver().insert(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ return uri.toString();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java b/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java
new file mode 100644
index 0000000..c8117f7
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/AdvancedMockContext.java
@@ -0,0 +1,118 @@
+// 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.
+
+package org.chromium.base.test.util;
+
+import android.content.ComponentCallbacks;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.SharedPreferences;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * ContextWrapper that adds functionality for SharedPreferences and a way to set and retrieve flags.
+ */
+public class AdvancedMockContext extends ContextWrapper {
+
+ private final MockContentResolver mMockContentResolver = new MockContentResolver();
+
+ private final Map<String, SharedPreferences> mSharedPreferences =
+ new HashMap<String, SharedPreferences>();
+
+ private final Map<String, Boolean> mFlags = new HashMap<String, Boolean>();
+
+ public AdvancedMockContext(Context base) {
+ super(base);
+ }
+
+ public AdvancedMockContext() {
+ super(new MockContext());
+ }
+
+ @Override
+ public String getPackageName() {
+ return getBaseContext().getPackageName();
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mMockContentResolver;
+ }
+
+ public MockContentResolver getMockContentResolver() {
+ return mMockContentResolver;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String name, int mode) {
+ synchronized (mSharedPreferences) {
+ if (!mSharedPreferences.containsKey(name)) {
+ // Auto-create shared preferences to mimic Android Context behavior
+ mSharedPreferences.put(name, new InMemorySharedPreferences());
+ }
+ return mSharedPreferences.get(name);
+ }
+ }
+
+ @Override
+ public void registerComponentCallbacks(ComponentCallbacks callback) {
+ getBaseContext().registerComponentCallbacks(callback);
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+ getBaseContext().unregisterComponentCallbacks(callback);
+ }
+
+ public void addSharedPreferences(String name, Map<String, Object> data) {
+ synchronized (mSharedPreferences) {
+ mSharedPreferences.put(name, new InMemorySharedPreferences(data));
+ }
+ }
+
+ public void setFlag(String key) {
+ mFlags.put(key, true);
+ }
+
+ public void clearFlag(String key) {
+ mFlags.remove(key);
+ }
+
+ public boolean isFlagSet(String key) {
+ return mFlags.containsKey(key) && mFlags.get(key);
+ }
+
+ /**
+ * Builder for maps of type Map<String, Object> to be used with
+ * {@link #addSharedPreferences(String, java.util.Map)}.
+ */
+ public static class MapBuilder {
+
+ private final Map<String, Object> mData = new HashMap<String, Object>();
+
+ public static MapBuilder create() {
+ return new MapBuilder();
+ }
+
+ public MapBuilder add(String key, Object value) {
+ mData.put(key, value);
+ return this;
+ }
+
+ public Map<String, Object> build() {
+ return mData;
+ }
+
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java
new file mode 100644
index 0000000..0dfb4be
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java
@@ -0,0 +1,21 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for disabled tests.
+ * <p>
+ * Tests with this annotation will not be run on any of the normal bots.
+ * Please note that they might eventually run on a special bot.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisabledTest {
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java
new file mode 100644
index 0000000..af483ec
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java
@@ -0,0 +1,24 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for enormous tests.
+ * <p>
+ * Examples of enormous tests are tests that depend on external web sites or
+ * tests that are long running.
+ * <p>
+ * Such tests are likely NOT reliable enough to run on tree closing bots and
+ * should only be run on FYI bots.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnormousTest {
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java b/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java
new file mode 100644
index 0000000..1bc9226
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java
@@ -0,0 +1,29 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The java instrumentation tests are normally fairly large (in terms of
+ * dependencies), and the test suite ends up containing a large amount of
+ * tests that are not trivial to filter / group just by their names.
+ * Instead, we use this annotation: each test should be annotated as:
+ * @Feature({"Foo", "Bar"})
+ * in order for the test runner scripts to be able to filter and group
+ * them accordingly (for instance, this enable us to run all tests that exercise
+ * feature Foo).
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Feature {
+ /**
+ * @return A list of feature names.
+ */
+ public String[] value();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/HostDrivenTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/HostDrivenTest.java
new file mode 100644
index 0000000..b52fb2c
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/HostDrivenTest.java
@@ -0,0 +1,22 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for host-driven tests.
+ * <p>
+ * Tests with these annotations are run explicitly by HostDrivenTestCase-derived
+ * python tests on the host and are excluded from regular instrumentation test runs.
+ * <p>
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HostDrivenTest {
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
new file mode 100644
index 0000000..2587d72
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/InMemorySharedPreferences.java
@@ -0,0 +1,238 @@
+// 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.
+
+package org.chromium.base.test.util;
+
+import android.content.SharedPreferences;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of SharedPreferences that can be used in tests.
+ * <p/>
+ * It keeps all state in memory, and there is no difference between apply() and commit().
+ */
+public class InMemorySharedPreferences implements SharedPreferences {
+
+ // Guarded on its own monitor.
+ private final Map<String, Object> mData;
+
+ public InMemorySharedPreferences() {
+ mData = new HashMap<String, Object>();
+ }
+
+ public InMemorySharedPreferences(Map<String, Object> data) {
+ mData = data;
+ }
+
+ @Override
+ public Map<String, ?> getAll() {
+ synchronized (mData) {
+ return Collections.unmodifiableMap(mData);
+ }
+ }
+
+ @Override
+ public String getString(String key, String defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (String) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Set<String> getStringSet(String key, Set<String> defValues) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return Collections.unmodifiableSet((Set<String>) mData.get(key));
+ }
+ }
+ return defValues;
+ }
+
+ @Override
+ public int getInt(String key, int defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Integer) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public long getLong(String key, long defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Long) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public float getFloat(String key, float defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Float) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean defValue) {
+ synchronized (mData) {
+ if (mData.containsKey(key)) {
+ return (Boolean) mData.get(key);
+ }
+ }
+ return defValue;
+ }
+
+ @Override
+ public boolean contains(String key) {
+ synchronized (mData) {
+ return mData.containsKey(key);
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor edit() {
+ return new InMemoryEditor();
+ }
+
+ @Override
+ public void registerOnSharedPreferenceChangeListener(
+ SharedPreferences.OnSharedPreferenceChangeListener
+ listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterOnSharedPreferenceChangeListener(
+ SharedPreferences.OnSharedPreferenceChangeListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ private class InMemoryEditor implements SharedPreferences.Editor {
+
+ // All guarded by |mChanges|
+ private boolean mClearCalled;
+ private volatile boolean mApplyCalled;
+ private final Map<String, Object> mChanges = new HashMap<String, Object>();
+
+ @Override
+ public SharedPreferences.Editor putString(String key, String value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putStringSet(String key, Set<String> values) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, values);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putInt(String key, int value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putLong(String key, long value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putFloat(String key, float value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor putBoolean(String key, boolean value) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mChanges.put(key, value);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor remove(String key) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ // Magic value for removes
+ mChanges.put(key, this);
+ return this;
+ }
+ }
+
+ @Override
+ public SharedPreferences.Editor clear() {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ mClearCalled = true;
+ return this;
+ }
+ }
+
+ @Override
+ public boolean commit() {
+ apply();
+ return true;
+ }
+
+ @Override
+ public void apply() {
+ synchronized (mData) {
+ synchronized (mChanges) {
+ if (mApplyCalled) throw new IllegalStateException();
+ if (mClearCalled) {
+ mData.clear();
+ }
+ for (Map.Entry<String, Object> entry : mChanges.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value == this) {
+ // Special value for removal
+ mData.remove(key);
+ } else {
+ mData.put(key, value);
+ }
+ }
+ // The real shared prefs clears out the temporaries allowing the caller to
+ // reuse the Editor instance, however this is undocumented behavior and subtle
+ // to read, so instead we just ban any future use of this instance.
+ mApplyCalled = true;
+ }
+ }
+ }
+ }
+
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java
new file mode 100644
index 0000000..20cfd9d
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/InstrumentationUtils.java
@@ -0,0 +1,32 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import android.app.Instrumentation;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Utility methods built around the android.app.Instrumentation class.
+ */
+public final class InstrumentationUtils {
+
+ private InstrumentationUtils() {
+ }
+
+ public static <R> R runOnMainSyncAndGetResult(Instrumentation instrumentation,
+ Callable<R> callable) throws Throwable {
+ FutureTask<R> task = new FutureTask<R>(callable);
+ instrumentation.runOnMainSync(task);
+ try {
+ return task.get();
+ } catch (ExecutionException e) {
+ // Unwrap the cause of the exception and re-throw it.
+ throw e.getCause();
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java b/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java
new file mode 100644
index 0000000..8b6550d
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/IntegrationTest.java
@@ -0,0 +1,26 @@
+// 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.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is for integration tests.
+ * <p>
+ * Examples of integration tests are tests that rely on real instances of the
+ * application's services and components (e.g. Search) to test the system as
+ * a whole. These tests may use additional command-line flags to configure the
+ * existing backends to use.
+ * <p>
+ * Such tests are likely NOT reliable enough to run on tree closing bots and
+ * should only be run on FYI bots.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IntegrationTest {
+}
\ No newline at end of file
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java
new file mode 100644
index 0000000..d7c45e7
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java
@@ -0,0 +1,19 @@
+// 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.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface MinAndroidSdkLevel {
+ int value() default 0;
+}
+
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java b/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java
new file mode 100644
index 0000000..11026ef
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/Restriction.java
@@ -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.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for listing restrictions for a test method. For example, if a test method is only
+ * applicable on a phone with small memory:
+ * @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_SMALL_MEMORY})
+ * Test classes are free to define restrictions and enforce them using reflection at runtime.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Restriction {
+ /** Specifies the test is only valid on phone form factors. */
+ public static final String RESTRICTION_TYPE_PHONE = "Phone";
+
+ /** Specifies the test is only valid on tablet form factors. */
+ public static final String RESTRICTION_TYPE_TABLET = "Tablet";
+
+ /** Specifies the test is only valid on low end devices that have less memory. */
+ public static final String RESTRICTION_TYPE_LOW_END_DEVICE = "Low_End_Device";
+
+ /** Specifies the test is only valid on non-low end devices. */
+ public static final String RESTRICTION_TYPE_NON_LOW_END_DEVICE = "Non_Low_End_Device";
+
+ /**
+ * @return A list of restrictions.
+ */
+ public String[] value();
+}
\ No newline at end of file
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
new file mode 100644
index 0000000..c21bff9
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
@@ -0,0 +1,28 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+/**
+ * Utility class for scaling various timeouts by a common factor.
+ * For example, to run tests under Valgrind, you might want the following:
+ * adb shell "echo 20.0 > /data/local/tmp/chrome_timeout_scale"
+ */
+public class ScalableTimeout {
+ private static Double sTimeoutScale = null;
+ private static final String PROPERTY_FILE = "/data/local/tmp/chrome_timeout_scale";
+
+ public static long scaleTimeout(long timeout) {
+ if (sTimeoutScale == null) {
+ try {
+ char[] data = TestFileUtil.readUtf8File(PROPERTY_FILE, 32);
+ sTimeoutScale = Double.parseDouble(new String(data));
+ } catch (Exception e) {
+ // NumberFormatException, FileNotFoundException, IOException
+ sTimeoutScale = 1.0;
+ }
+ }
+ return (long) (timeout * sTimeoutScale);
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java b/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java
new file mode 100644
index 0000000..ca6e73b
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java
@@ -0,0 +1,78 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Arrays;
+
+/**
+ * Utility class for dealing with files for test.
+ */
+public class TestFileUtil {
+ public static void createNewHtmlFile(String name, String title, String body)
+ throws IOException {
+ File file = new File(name);
+ if (!file.createNewFile()) {
+ throw new IOException("File \"" + name + "\" already exists");
+ }
+
+ Writer writer = null;
+ try {
+ writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
+ writer.write("<html><meta charset=\"UTF-8\" />" +
+ "<head><title>" + title + "</title></head>" +
+ "<body>" +
+ (body != null ? body : "") +
+ "</body>" +
+ "</html>");
+ } finally {
+ if (writer != null) {
+ writer.close();
+ }
+ }
+ }
+
+ public static void deleteFile(String name) {
+ File file = new File(name);
+ boolean deleted = file.delete();
+ assert (deleted || !file.exists());
+ }
+
+ /**
+ * @param fileName the file to read in.
+ * @param sizeLimit cap on the file size: will throw an exception if exceeded
+ * @return Array of chars read from the file
+ * @throws FileNotFoundException file does not exceed
+ * @throws IOException error encountered accessing the file
+ */
+ public static char[] readUtf8File(String fileName, int sizeLimit) throws
+ FileNotFoundException, IOException {
+ Reader reader = null;
+ try {
+ File f = new File(fileName);
+ if (f.length() > sizeLimit) {
+ throw new IOException("File " + fileName + " length " + f.length() +
+ " exceeds limit " + sizeLimit);
+ }
+ char[] buffer = new char[(int) f.length()];
+ reader = new InputStreamReader(new FileInputStream(f), "UTF-8");
+ int charsRead = reader.read(buffer);
+ // Debug check that we've exhausted the input stream (will fail e.g. if the
+ // file grew after we inspected its length).
+ assert !reader.ready();
+ return charsRead < buffer.length ? Arrays.copyOfRange(buffer, 0, charsRead) : buffer;
+ } finally {
+ if (reader != null) reader.close();
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java b/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
new file mode 100644
index 0000000..93c23f7
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
@@ -0,0 +1,143 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class is usefull when writing instrumentation tests that exercise code that posts tasks
+ * (to the same thread).
+ * Since the test code is run in a single thread, the posted tasks are never executed.
+ * The TestThread class lets you run that code on a specific thread synchronously and flush the
+ * message loop on that thread.
+ *
+ * Example of test using this:
+ *
+ * public void testMyAwesomeClass() {
+ * TestThread testThread = new TestThread();
+ * testThread.startAndWaitForReadyState();
+ *
+ * testThread.runOnTestThreadSyncAndProcessPendingTasks(new Runnable() {
+ * @Override
+ * public void run() {
+ * MyAwesomeClass.doStuffAsync();
+ * }
+ * });
+ * // Once we get there we know doStuffAsync has been executed and all the tasks it posted.
+ * assertTrue(MyAwesomeClass.stuffWasDone());
+ * }
+ *
+ * Notes:
+ * - this is only for tasks posted to the same thread. Anyway if you were posting to a different
+ * thread, you'd probably need to set that other thread up.
+ * - this only supports tasks posted using Handler.post(), it won't work with postDelayed and
+ * postAtTime.
+ * - if your test instanciates an object and that object is the one doing the posting of tasks, you
+ * probably want to instanciate it on the test thread as it might create the Handler it posts
+ * tasks to in the constructor.
+ */
+
+public class TestThread extends Thread {
+ private Object mThreadReadyLock;
+ private AtomicBoolean mThreadReady;
+ private Handler mMainThreadHandler;
+ private Handler mTestThreadHandler;
+
+ public TestThread() {
+ mMainThreadHandler = new Handler();
+ // We can't use the AtomicBoolean as the lock or findbugs will freak out...
+ mThreadReadyLock = new Object();
+ mThreadReady = new AtomicBoolean();
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ mTestThreadHandler = new Handler();
+ mTestThreadHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mThreadReadyLock) {
+ mThreadReady.set(true);
+ mThreadReadyLock.notify();
+ }
+ }
+ });
+ Looper.loop();
+ }
+
+ /**
+ * Starts this TestThread and blocks until it's ready to accept calls.
+ */
+ public void startAndWaitForReadyState() {
+ checkOnMainThread();
+ start();
+ synchronized (mThreadReadyLock) {
+ try {
+ // Note the mThreadReady and while are not really needed.
+ // There are there so findbugs don't report warnings.
+ while (!mThreadReady.get()) {
+ mThreadReadyLock.wait();
+ }
+ } catch (InterruptedException ie) {
+ System.err.println("Error starting TestThread.");
+ ie.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Runs the passed Runnable synchronously on the TestThread and returns when all pending
+ * runnables have been excuted.
+ * Should be called from the main thread.
+ */
+ public void runOnTestThreadSyncAndProcessPendingTasks(Runnable r) {
+ checkOnMainThread();
+
+ runOnTestThreadSync(r);
+
+ // Run another task, when it's done it means all pendings tasks have executed.
+ runOnTestThreadSync(null);
+ }
+
+ /**
+ * Runs the passed Runnable on the test thread and blocks until it has finished executing.
+ * Should be called from the main thread.
+ * @param r The runnable to be executed.
+ */
+ public void runOnTestThreadSync(final Runnable r) {
+ checkOnMainThread();
+ final Object lock = new Object();
+ // Task executed is not really needed since we are only on one thread, it is here to appease
+ // findbugs.
+ final AtomicBoolean taskExecuted = new AtomicBoolean();
+ mTestThreadHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (r != null) r.run();
+ synchronized (lock) {
+ taskExecuted.set(true);
+ lock.notify();
+ }
+ }
+ });
+ synchronized (lock) {
+ try {
+ while (!taskExecuted.get()) {
+ lock.wait();
+ }
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+
+ private void checkOnMainThread() {
+ assert Looper.myLooper() == mMainThreadHandler.getLooper();
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java
new file mode 100644
index 0000000..5aee05e
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java
@@ -0,0 +1,22 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be used to scale a specific test timeout.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TimeoutScale {
+ /**
+ * @return A number to scale the test timeout.
+ */
+ public int value();
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java
new file mode 100644
index 0000000..09e1fd6
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/UrlUtils.java
@@ -0,0 +1,53 @@
+// Copyright 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.
+
+package org.chromium.base.test.util;
+
+import junit.framework.Assert;
+
+import org.chromium.base.PathUtils;
+
+/**
+ * Collection of URL utilities.
+ */
+public class UrlUtils {
+ private static final String DATA_DIR = "/chrome/test/data/";
+
+ /**
+ * Construct the full path of a test data file.
+ * @param path Pathname relative to external/chrome/testing/data
+ */
+ public static String getTestFilePath(String path) {
+ return PathUtils.getExternalStorageDirectory() + DATA_DIR + path;
+ }
+
+ /**
+ * Construct a suitable URL for loading a test data file.
+ * @param path Pathname relative to external/chrome/testing/data
+ */
+ public static String getTestFileUrl(String path) {
+ return "file://" + getTestFilePath(path);
+ }
+
+ /**
+ * Construct a data:text/html URI for loading from an inline HTML.
+ * @param html An unencoded HTML
+ * @return String An URI that contains the given HTML
+ */
+ public static String encodeHtmlDataUri(String html) {
+ try {
+ // URLEncoder encodes into application/x-www-form-encoded, so
+ // ' '->'+' needs to be undone and replaced with ' '->'%20'
+ // to match the Data URI requirements.
+ String encoded =
+ "data:text/html;utf-8," +
+ java.net.URLEncoder.encode(html, "UTF-8");
+ encoded = encoded.replace("+", "%20");
+ return encoded;
+ } catch (java.io.UnsupportedEncodingException e) {
+ Assert.fail("Unsupported encoding: " + e.getMessage());
+ return null;
+ }
+ }
+}
diff --git a/base/test/data/file_util/binary_file.bin b/base/test/data/file_util/binary_file.bin
new file mode 100644
index 0000000..f53cc82
--- /dev/null
+++ b/base/test/data/file_util/binary_file.bin
Binary files differ
diff --git a/base/test/data/file_util/binary_file_diff.bin b/base/test/data/file_util/binary_file_diff.bin
new file mode 100644
index 0000000..103b26d
--- /dev/null
+++ b/base/test/data/file_util/binary_file_diff.bin
Binary files differ
diff --git a/base/test/data/file_util/binary_file_same.bin b/base/test/data/file_util/binary_file_same.bin
new file mode 100644
index 0000000..f53cc82
--- /dev/null
+++ b/base/test/data/file_util/binary_file_same.bin
Binary files differ
diff --git a/base/test/data/file_util/blank_line.txt b/base/test/data/file_util/blank_line.txt
new file mode 100644
index 0000000..8892069
--- /dev/null
+++ b/base/test/data/file_util/blank_line.txt
@@ -0,0 +1,3 @@
+The next line is blank.
+
+But this one isn't.
diff --git a/base/test/data/file_util/blank_line_crlf.txt b/base/test/data/file_util/blank_line_crlf.txt
new file mode 100644
index 0000000..3aefe52
--- /dev/null
+++ b/base/test/data/file_util/blank_line_crlf.txt
@@ -0,0 +1,3 @@
+The next line is blank.
+
+But this one isn't.
diff --git a/base/test/data/file_util/crlf.txt b/base/test/data/file_util/crlf.txt
new file mode 100644
index 0000000..0e62728
--- /dev/null
+++ b/base/test/data/file_util/crlf.txt
@@ -0,0 +1 @@
+This file is the same.
diff --git a/base/test/data/file_util/different.txt b/base/test/data/file_util/different.txt
new file mode 100644
index 0000000..5b9f9c4
--- /dev/null
+++ b/base/test/data/file_util/different.txt
@@ -0,0 +1 @@
+This file is different.
diff --git a/base/test/data/file_util/different_first.txt b/base/test/data/file_util/different_first.txt
new file mode 100644
index 0000000..8661d66
--- /dev/null
+++ b/base/test/data/file_util/different_first.txt
@@ -0,0 +1 @@
+this file is the same.
diff --git a/base/test/data/file_util/different_last.txt b/base/test/data/file_util/different_last.txt
new file mode 100644
index 0000000..e8b3e5a
--- /dev/null
+++ b/base/test/data/file_util/different_last.txt
@@ -0,0 +1 @@
+This file is the same.
\ No newline at end of file
diff --git a/base/test/data/file_util/empty1.txt b/base/test/data/file_util/empty1.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/base/test/data/file_util/empty1.txt
diff --git a/base/test/data/file_util/empty2.txt b/base/test/data/file_util/empty2.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/base/test/data/file_util/empty2.txt
diff --git a/base/test/data/file_util/first1.txt b/base/test/data/file_util/first1.txt
new file mode 100644
index 0000000..2c6e300
--- /dev/null
+++ b/base/test/data/file_util/first1.txt
@@ -0,0 +1,2 @@
+The first line is the same.
+The second line is different.
diff --git a/base/test/data/file_util/first2.txt b/base/test/data/file_util/first2.txt
new file mode 100644
index 0000000..e39b5ec
--- /dev/null
+++ b/base/test/data/file_util/first2.txt
@@ -0,0 +1,2 @@
+The first line is the same.
+The second line is not.
diff --git a/base/test/data/file_util/original.txt b/base/test/data/file_util/original.txt
new file mode 100644
index 0000000..4422f57
--- /dev/null
+++ b/base/test/data/file_util/original.txt
@@ -0,0 +1 @@
+This file is the same.
diff --git a/base/test/data/file_util/red.png b/base/test/data/file_util/red.png
new file mode 100644
index 0000000..0806141
--- /dev/null
+++ b/base/test/data/file_util/red.png
Binary files differ
diff --git a/base/test/data/file_util/same.txt b/base/test/data/file_util/same.txt
new file mode 100644
index 0000000..4422f57
--- /dev/null
+++ b/base/test/data/file_util/same.txt
@@ -0,0 +1 @@
+This file is the same.
diff --git a/base/test/data/file_util/same_length.txt b/base/test/data/file_util/same_length.txt
new file mode 100644
index 0000000..157405c
--- /dev/null
+++ b/base/test/data/file_util/same_length.txt
@@ -0,0 +1 @@
+This file is not same.
diff --git a/base/test/data/file_util/shortened.txt b/base/test/data/file_util/shortened.txt
new file mode 100644
index 0000000..2bee82c
--- /dev/null
+++ b/base/test/data/file_util/shortened.txt
@@ -0,0 +1 @@
+This file is the
\ No newline at end of file
diff --git a/base/test/data/file_version_info_unittest/FileVersionInfoTest1.dll b/base/test/data/file_version_info_unittest/FileVersionInfoTest1.dll
new file mode 100755
index 0000000..bdf8dc0
--- /dev/null
+++ b/base/test/data/file_version_info_unittest/FileVersionInfoTest1.dll
Binary files differ
diff --git a/base/test/data/file_version_info_unittest/FileVersionInfoTest2.dll b/base/test/data/file_version_info_unittest/FileVersionInfoTest2.dll
new file mode 100755
index 0000000..51e7966
--- /dev/null
+++ b/base/test/data/file_version_info_unittest/FileVersionInfoTest2.dll
Binary files differ
diff --git a/base/test/data/json/bom_feff.json b/base/test/data/json/bom_feff.json
new file mode 100644
index 0000000..b05ae50
--- /dev/null
+++ b/base/test/data/json/bom_feff.json
@@ -0,0 +1,10 @@
+{
+ "appName": {
+ "message": "Gmail",
+ "description": "App name."
+ },
+ "appDesc": {
+ "message": "بريد إلكتروني يوفر إمكانية البحث مع مقدار أقل من الرسائل غير المرغوب فيها.",
+ "description":"App description."
+ }
+}
\ No newline at end of file
diff --git a/base/test/data/prefs/invalid.json b/base/test/data/prefs/invalid.json
new file mode 100644
index 0000000..43392a9
--- /dev/null
+++ b/base/test/data/prefs/invalid.json
@@ -0,0 +1 @@
+!@#$%^&
\ No newline at end of file
diff --git a/base/test/data/prefs/read.json b/base/test/data/prefs/read.json
new file mode 100644
index 0000000..ea578a4
--- /dev/null
+++ b/base/test/data/prefs/read.json
@@ -0,0 +1,8 @@
+{
+ "homepage": "http://www.cnn.com",
+ "some_directory": "/usr/local/",
+ "tabs": {
+ "new_windows_in_tabs": true,
+ "max_tabs": 20
+ }
+}
diff --git a/base/test/data/prefs/write.golden.json b/base/test/data/prefs/write.golden.json
new file mode 100644
index 0000000..9a5523c
--- /dev/null
+++ b/base/test/data/prefs/write.golden.json
@@ -0,0 +1,11 @@
+{
+ "homepage": "http://www.cnn.com",
+ "long_int": {
+ "pref": "214748364842"
+ },
+ "some_directory": "/usr/sbin/",
+ "tabs": {
+ "max_tabs": 10,
+ "new_windows_in_tabs": false
+ }
+}
diff --git a/base/test/data/serializer_nested_test.json b/base/test/data/serializer_nested_test.json
new file mode 100644
index 0000000..cfea8e8
--- /dev/null
+++ b/base/test/data/serializer_nested_test.json
@@ -0,0 +1,17 @@
+{
+ "bool": true,
+ "dict": {
+ "bool": true,
+ "dict": {
+ "bees": "knees",
+ "cats": "meow"
+ },
+ "foos": "bar",
+ "list": [ 3.4, "second", null ]
+ },
+ "int": 42,
+ "list": [ 1, 2 ],
+ "null": null,
+ "real": 3.14,
+ "string": "hello"
+}
diff --git a/base/test/data/serializer_test.json b/base/test/data/serializer_test.json
new file mode 100644
index 0000000..446925e
--- /dev/null
+++ b/base/test/data/serializer_test.json
@@ -0,0 +1,8 @@
+{
+ "bool": true,
+ "int": 42,
+ "list": [ 1, 2 ],
+ "null": null,
+ "real": 3.14,
+ "string": "hello"
+}
diff --git a/base/test/data/serializer_test_nowhitespace.json b/base/test/data/serializer_test_nowhitespace.json
new file mode 100644
index 0000000..a1afdc5
--- /dev/null
+++ b/base/test/data/serializer_test_nowhitespace.json
@@ -0,0 +1 @@
+{"bool":true,"int":42,"list":[1,2],"null":null,"real":3.14,"string":"hello"}
\ No newline at end of file
diff --git a/base/test/expectations/OWNERS b/base/test/expectations/OWNERS
new file mode 100644
index 0000000..14fce2a
--- /dev/null
+++ b/base/test/expectations/OWNERS
@@ -0,0 +1 @@
+rsesek@chromium.org
diff --git a/base/test/expectations/expectation.cc b/base/test/expectations/expectation.cc
new file mode 100644
index 0000000..9b06e28
--- /dev/null
+++ b/base/test/expectations/expectation.cc
@@ -0,0 +1,161 @@
+// 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/expectations/expectation.h"
+
+#include "base/logging.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/mac_util.h"
+#elif defined(OS_LINUX)
+#include "base/sys_info.h"
+#endif
+
+namespace test_expectations {
+
+bool ResultFromString(const base::StringPiece& result, Result* out_result) {
+ if (result == "Failure")
+ *out_result = RESULT_FAILURE;
+ else if (result == "Timeout")
+ *out_result = RESULT_TIMEOUT;
+ else if (result == "Crash")
+ *out_result = RESULT_CRASH;
+ else if (result == "Skip")
+ *out_result = RESULT_SKIP;
+ else if (result == "Pass")
+ *out_result = RESULT_PASS;
+ else
+ return false;
+
+ return true;
+}
+
+static bool IsValidPlatform(const Platform* platform) {
+ const std::string& name = platform->name;
+ const std::string& variant = platform->variant;
+
+ if (name == "Win") {
+ if (variant != "" &&
+ variant != "XP" &&
+ variant != "Vista" &&
+ variant != "7" &&
+ variant != "8") {
+ return false;
+ }
+ } else if (name == "Mac") {
+ if (variant != "" &&
+ variant != "10.6" &&
+ variant != "10.7" &&
+ variant != "10.8" &&
+ variant != "10.9" &&
+ variant != "10.10") {
+ return false;
+ }
+ } else if (name == "Linux") {
+ if (variant != "" &&
+ variant != "32" &&
+ variant != "64") {
+ return false;
+ }
+ } else if (name == "ChromeOS") {
+ // TODO(rsesek): Figure out what ChromeOS needs.
+ } else if (name == "iOS") {
+ // TODO(rsesek): Figure out what iOS needs. Probably Device and Simulator.
+ } else if (name == "Android") {
+ // TODO(rsesek): Figure out what Android needs.
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool PlatformFromString(const base::StringPiece& modifier,
+ Platform* out_platform) {
+ size_t sep = modifier.find('-');
+ if (sep == std::string::npos) {
+ out_platform->name = modifier.as_string();
+ out_platform->variant.clear();
+ } else {
+ out_platform->name = modifier.substr(0, sep).as_string();
+ out_platform->variant = modifier.substr(sep + 1).as_string();
+ }
+
+ return IsValidPlatform(out_platform);
+}
+
+Platform GetCurrentPlatform() {
+ Platform platform;
+#if defined(OS_WIN)
+ platform.name = "Win";
+ base::win::Version version = base::win::GetVersion();
+ if (version == base::win::VERSION_XP)
+ platform.variant = "XP";
+ else if (version == base::win::VERSION_VISTA)
+ platform.variant = "Vista";
+ else if (version == base::win::VERSION_WIN7)
+ platform.variant = "7";
+ else if (version == base::win::VERSION_WIN8)
+ platform.variant = "8";
+#elif defined(OS_IOS)
+ platform.name = "iOS";
+#elif defined(OS_MACOSX)
+ platform.name = "Mac";
+ if (base::mac::IsOSSnowLeopard())
+ platform.variant = "10.6";
+ else if (base::mac::IsOSLion())
+ platform.variant = "10.7";
+ else if (base::mac::IsOSMountainLion())
+ platform.variant = "10.8";
+ else if (base::mac::IsOSMavericks())
+ platform.variant = "10.9";
+ else if (base::mac::IsOSYosemite())
+ platform.variant = "10.10";
+#elif defined(OS_CHROMEOS)
+ platform.name = "ChromeOS";
+#elif defined(OS_ANDROID)
+ platform.name = "Android";
+#elif defined(OS_LINUX)
+ platform.name = "Linux";
+ std::string arch = base::SysInfo::OperatingSystemArchitecture();
+ if (arch == "x86")
+ platform.variant = "32";
+ else if (arch == "x86_64")
+ platform.variant = "64";
+#else
+ NOTREACHED();
+#endif
+ return platform;
+}
+
+bool ConfigurationFromString(const base::StringPiece& modifier,
+ Configuration* out_configuration) {
+ if (modifier == "Debug")
+ *out_configuration = CONFIGURATION_DEBUG;
+ else if (modifier == "Release")
+ *out_configuration = CONFIGURATION_RELEASE;
+ else
+ return false;
+
+ return true;
+}
+
+Configuration GetCurrentConfiguration() {
+#if NDEBUG
+ return CONFIGURATION_RELEASE;
+#else
+ return CONFIGURATION_DEBUG;
+#endif
+}
+
+Expectation::Expectation()
+ : configuration(CONFIGURATION_UNSPECIFIED),
+ result(RESULT_PASS) {
+}
+
+Expectation::~Expectation() {}
+
+} // namespace test_expectations
diff --git a/base/test/expectations/expectation.h b/base/test/expectations/expectation.h
new file mode 100644
index 0000000..be5a9d7
--- /dev/null
+++ b/base/test/expectations/expectation.h
@@ -0,0 +1,94 @@
+// 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.
+
+#ifndef BASE_TEST_EXPECTATIONS_EXPECTATION_H_
+#define BASE_TEST_EXPECTATIONS_EXPECTATION_H_
+
+#include <string>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_piece.h"
+
+namespace test_expectations {
+
+// A Result is the expectation of a test's behavior.
+enum Result {
+ // The test has a failing assertion.
+ RESULT_FAILURE,
+
+ // The test does not complete within the test runner's alloted duration.
+ RESULT_TIMEOUT,
+
+ // The test crashes during the course of its execution.
+ RESULT_CRASH,
+
+ // The test should not be run ever.
+ RESULT_SKIP,
+
+ // The test passes, used to override a more general expectation.
+ RESULT_PASS,
+};
+
+// Converts a text string form of a |result| to its enum value, written to
+// |out_result|. Returns true on success and false on error.
+bool ResultFromString(const base::StringPiece& result,
+ Result* out_result) WARN_UNUSED_RESULT;
+
+// A Platform stores information about the OS environment.
+struct Platform {
+ // The name of the platform. E.g., "Win", or "Mac".
+ std::string name;
+
+ // The variant of the platform, either an OS version like "XP" or "10.8", or
+ // "Device" or "Simulator" in the case of mobile.
+ std::string variant;
+};
+
+// Converts a text string |modifier| to a Platform struct, written to
+// |out_platform|. Returns true on success and false on failure.
+bool PlatformFromString(const base::StringPiece& modifier,
+ Platform* out_platform) WARN_UNUSED_RESULT;
+
+// Returns the Platform for the currently running binary.
+Platform GetCurrentPlatform();
+
+// The build configuration.
+enum Configuration {
+ CONFIGURATION_UNSPECIFIED,
+ CONFIGURATION_DEBUG,
+ CONFIGURATION_RELEASE,
+};
+
+// Converts the |modifier| to a Configuration constant, writing the value to
+// |out_configuration|. Returns true on success or false on failure.
+bool ConfigurationFromString(const base::StringPiece& modifier,
+ Configuration* out_configuration) WARN_UNUSED_RESULT;
+
+// Returns the Configuration for the currently running binary.
+Configuration GetCurrentConfiguration();
+
+// An Expectation is records what the result for a given test name should be on
+// the specified platforms and configuration.
+struct Expectation {
+ Expectation();
+ ~Expectation();
+
+ // The name of the test, like FooBarTest.BarIsBaz.
+ std::string test_name;
+
+ // The set of platforms for which this expectation is applicable.
+ std::vector<Platform> platforms;
+
+ // The build configuration.
+ Configuration configuration;
+
+ // The expected result of this test.
+ Result result;
+};
+
+} // namespace test_expectations
+
+#endif // BASE_TEST_EXPECTATIONS_EXPECTATION_H_
diff --git a/base/test/expectations/expectation_unittest.cc b/base/test/expectations/expectation_unittest.cc
new file mode 100644
index 0000000..8a7af71
--- /dev/null
+++ b/base/test/expectations/expectation_unittest.cc
@@ -0,0 +1,120 @@
+// 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/expectations/expectation.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(TestExpectationsFunctionsTest, ResultFromString) {
+ test_expectations::Result result = test_expectations::RESULT_PASS;
+
+ EXPECT_TRUE(ResultFromString("Failure", &result));
+ EXPECT_EQ(test_expectations::RESULT_FAILURE, result);
+
+ EXPECT_TRUE(ResultFromString("Timeout", &result));
+ EXPECT_EQ(test_expectations::RESULT_TIMEOUT, result);
+
+ EXPECT_TRUE(ResultFromString("Crash", &result));
+ EXPECT_EQ(test_expectations::RESULT_CRASH, result);
+
+ EXPECT_TRUE(ResultFromString("Skip", &result));
+ EXPECT_EQ(test_expectations::RESULT_SKIP, result);
+
+ EXPECT_TRUE(ResultFromString("Pass", &result));
+ EXPECT_EQ(test_expectations::RESULT_PASS, result);
+
+ // Case sensitive.
+ EXPECT_FALSE(ResultFromString("failure", &result));
+ EXPECT_EQ(test_expectations::RESULT_PASS, result);
+}
+
+TEST(TestExpectationsFunctionsTest, ConfigurationFromString) {
+ test_expectations::Configuration config =
+ test_expectations::CONFIGURATION_UNSPECIFIED;
+
+ EXPECT_TRUE(ConfigurationFromString("Debug", &config));
+ EXPECT_EQ(test_expectations::CONFIGURATION_DEBUG, config);
+
+ EXPECT_TRUE(ConfigurationFromString("Release", &config));
+ EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE, config);
+
+ EXPECT_FALSE(ConfigurationFromString("NotAConfig", &config));
+ EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE, config);
+
+ // Case sensitive.
+ EXPECT_FALSE(ConfigurationFromString("debug", &config));
+ EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE, config);
+}
+
+TEST(TestExpectationsFunctionsTest, PlatformFromString) {
+ test_expectations::Platform platform;
+
+ EXPECT_TRUE(PlatformFromString("Win", &platform));
+ EXPECT_EQ("Win", platform.name);
+ EXPECT_EQ("", platform.variant);
+
+ EXPECT_TRUE(PlatformFromString("Mac-10.6", &platform));
+ EXPECT_EQ("Mac", platform.name);
+ EXPECT_EQ("10.6", platform.variant);
+
+ EXPECT_TRUE(PlatformFromString("ChromeOS", &platform));
+ EXPECT_EQ("ChromeOS", platform.name);
+ EXPECT_EQ("", platform.variant);
+
+ EXPECT_TRUE(PlatformFromString("Linux-", &platform));
+ EXPECT_EQ("Linux", platform.name);
+ EXPECT_EQ("", platform.variant);
+
+ EXPECT_FALSE(PlatformFromString("", &platform));
+}
+
+TEST(TestExpectationsFunctionsTest, IsValidPlatform) {
+ const char* kValidPlatforms[] = {
+ "Win",
+ "Win-XP",
+ "Win-Vista",
+ "Win-7",
+ "Win-8",
+ "Mac",
+ "Mac-10.6",
+ "Mac-10.7",
+ "Mac-10.8",
+ "Linux",
+ "Linux-32",
+ "Linux-64",
+ "ChromeOS",
+ "iOS",
+ "Android",
+ };
+
+ const char* kInvalidPlatforms[] = {
+ "Solaris",
+ "Plan9",
+ };
+
+ for (size_t i = 0; i < arraysize(kValidPlatforms); ++i) {
+ test_expectations::Platform platform;
+ EXPECT_TRUE(test_expectations::PlatformFromString(
+ kValidPlatforms[i], &platform)) << kValidPlatforms[i];
+ }
+
+ for (size_t i = 0; i < arraysize(kInvalidPlatforms); ++i) {
+ test_expectations::Platform platform;
+ EXPECT_FALSE(test_expectations::PlatformFromString(
+ kInvalidPlatforms[i], &platform)) << kInvalidPlatforms[i];
+ }
+}
+
+TEST(TestExpectationsFunctionsTest, CurrentPlatform) {
+ test_expectations::Platform current =
+ test_expectations::GetCurrentPlatform();
+ EXPECT_FALSE(current.name.empty());
+}
+
+TEST(TestExpectationsFunctionsTest, CurrentConfiguration) {
+ test_expectations::Configuration current =
+ test_expectations::GetCurrentConfiguration();
+ EXPECT_NE(test_expectations::CONFIGURATION_UNSPECIFIED, current);
+}
diff --git a/base/test/expectations/parser.cc b/base/test/expectations/parser.cc
new file mode 100644
index 0000000..c7132e5
--- /dev/null
+++ b/base/test/expectations/parser.cc
@@ -0,0 +1,201 @@
+// 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/expectations/parser.h"
+
+#include "base/strings/string_util.h"
+
+namespace test_expectations {
+
+Parser::Parser(Delegate* delegate, const std::string& input)
+ : delegate_(delegate),
+ input_(input),
+ pos_(NULL),
+ end_(NULL),
+ line_number_(0),
+ data_error_(false) {
+}
+
+Parser::~Parser() {
+}
+
+void Parser::Parse() {
+ pos_ = &input_[0];
+ end_ = pos_ + input_.length();
+
+ line_number_ = 1;
+
+ StateFuncPtr state = &Parser::Start;
+ while (state) {
+ state = (this->*state)();
+ }
+}
+
+inline bool Parser::HasNext() {
+ return pos_ < end_;
+}
+
+Parser::StateFunc Parser::Start() {
+ // If at the start of a line is whitespace, skip it and arrange to come back
+ // here.
+ if (IsAsciiWhitespace(*pos_))
+ return SkipWhitespaceAndNewLines(&Parser::Start);
+
+ // Handle comments at the start of lines.
+ if (*pos_ == '#')
+ return &Parser::ParseComment;
+
+ // After arranging to come back here from skipping whitespace and comments,
+ // the parser may be at the end of the input.
+ if (pos_ >= end_)
+ return NULL;
+
+ current_ = Expectation();
+ data_error_ = false;
+
+ return &Parser::ParseBugURL;
+}
+
+Parser::StateFunc Parser::ParseComment() {
+ if (*pos_ != '#')
+ return SyntaxError("Invalid start of comment");
+
+ do {
+ ++pos_;
+ } while (HasNext() && *pos_ != '\n');
+
+ return &Parser::Start;
+}
+
+Parser::StateFunc Parser::ParseBugURL() {
+ return SkipWhitespace(ExtractString(
+ &Parser::BeginModifiers));
+}
+
+Parser::StateFunc Parser::BeginModifiers() {
+ if (*pos_ != '[' || !HasNext())
+ return SyntaxError("Expected '[' for start of modifiers");
+
+ ++pos_;
+ return SkipWhitespace(&Parser::InModifiers);
+}
+
+Parser::StateFunc Parser::InModifiers() {
+ if (*pos_ == ']')
+ return &Parser::EndModifiers;
+
+ return ExtractString(SkipWhitespace(
+ &Parser::SaveModifier));
+}
+
+Parser::StateFunc Parser::SaveModifier() {
+ if (extracted_string_.empty())
+ return SyntaxError("Invalid modifier list");
+
+ Configuration config;
+ if (ConfigurationFromString(extracted_string_, &config)) {
+ if (current_.configuration != CONFIGURATION_UNSPECIFIED)
+ DataError("Cannot use more than one configuration modifier");
+ else
+ current_.configuration = config;
+ } else {
+ Platform platform;
+ if (PlatformFromString(extracted_string_, &platform))
+ current_.platforms.push_back(platform);
+ else
+ DataError("Invalid modifier string");
+ }
+
+ return SkipWhitespace(&Parser::InModifiers);
+}
+
+Parser::StateFunc Parser::EndModifiers() {
+ if (*pos_ != ']' || !HasNext())
+ return SyntaxError("Expected ']' for end of modifiers list");
+
+ ++pos_;
+ return SkipWhitespace(&Parser::ParseTestName);
+}
+
+Parser::StateFunc Parser::ParseTestName() {
+ return ExtractString(&Parser::SaveTestName);
+}
+
+Parser::StateFunc Parser::SaveTestName() {
+ if (extracted_string_.empty())
+ return SyntaxError("Invalid test name");
+
+ current_.test_name = extracted_string_.as_string();
+ return SkipWhitespace(&Parser::ParseExpectation);
+}
+
+Parser::StateFunc Parser::ParseExpectation() {
+ if (*pos_ != '=' || !HasNext())
+ return SyntaxError("Expected '=' for expectation result");
+
+ ++pos_;
+ return SkipWhitespace(&Parser::ParseExpectationType);
+}
+
+Parser::StateFunc Parser::ParseExpectationType() {
+ return ExtractString(&Parser::SaveExpectationType);
+}
+
+Parser::StateFunc Parser::SaveExpectationType() {
+ if (!ResultFromString(extracted_string_, ¤t_.result))
+ DataError("Unknown expectation type");
+
+ return SkipWhitespace(&Parser::End);
+}
+
+Parser::StateFunc Parser::End() {
+ if (!data_error_)
+ delegate_->EmitExpectation(current_);
+
+ if (HasNext())
+ return SkipWhitespaceAndNewLines(&Parser::Start);
+
+ return NULL;
+}
+
+Parser::StateFunc Parser::ExtractString(StateFunc success) {
+ const char* start = pos_;
+ while (!IsAsciiWhitespace(*pos_) && *pos_ != ']' && HasNext()) {
+ ++pos_;
+ if (*pos_ == '#') {
+ return SyntaxError("Unexpected start of comment");
+ }
+ }
+ extracted_string_ = base::StringPiece(start, pos_ - start);
+ return success;
+}
+
+Parser::StateFunc Parser::SkipWhitespace(Parser::StateFunc next) {
+ while ((*pos_ == ' ' || *pos_ == '\t') && HasNext()) {
+ ++pos_;
+ }
+ return next;
+}
+
+Parser::StateFunc Parser::SkipWhitespaceAndNewLines(Parser::StateFunc next) {
+ while (IsAsciiWhitespace(*pos_) && HasNext()) {
+ if (*pos_ == '\n') {
+ ++line_number_;
+ }
+ ++pos_;
+ }
+ return next;
+}
+
+Parser::StateFunc Parser::SyntaxError(const std::string& message) {
+ delegate_->OnSyntaxError(message);
+ return NULL;
+}
+
+void Parser::DataError(const std::string& error) {
+ data_error_ = true;
+ delegate_->OnDataError(error);
+}
+
+} // namespace test_expectations
diff --git a/base/test/expectations/parser.h b/base/test/expectations/parser.h
new file mode 100644
index 0000000..69a741a
--- /dev/null
+++ b/base/test/expectations/parser.h
@@ -0,0 +1,143 @@
+// 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.
+
+#ifndef BASE_TEST_EXPECTATIONS_PARSER_H_
+#define BASE_TEST_EXPECTATIONS_PARSER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "base/test/expectations/expectation.h"
+
+namespace test_expectations {
+
+// This is the internal parser for test expectations. It parses an input
+// string and reports information to its Delegate as it's processing the
+// input.
+//
+// The input format is documented here:
+// https://docs.google.com/a/chromium.org/document/d/1edhMJ5doY_dzfbKNCzeJJ-8XxPrexTbNL2Y_jVvLB8Q/view
+//
+// Basic format:
+// "http://bug/1234 [ OS-Version ] Test.Name = Result"
+//
+// The parser is implemented as a state machine, with each state returning a
+// function pointer to the next state.
+class Parser {
+ public:
+ // The parser will call these methods on its delegate during a Parse()
+ // operation.
+ class Delegate {
+ public:
+ // When a well-formed and valid Expectation has been parsed from the input,
+ // it is reported to the delegate via this method.
+ virtual void EmitExpectation(const Expectation& expectation) = 0;
+
+ // Called when the input string is not well-formed. Parsing will stop after
+ // this method is called.
+ virtual void OnSyntaxError(const std::string& message) = 0;
+
+ // Called when an Expectation has been parsed because it is well-formed but
+ // contains invalid data (i.e. the modifiers or result are not valid
+ // keywords). This Expectation will not be reported via EmitExpectation.
+ virtual void OnDataError(const std::string& message) = 0;
+ };
+
+ // Creates a new parser for |input| that will send data to |delegate|.
+ Parser(Delegate* delegate, const std::string& input);
+ ~Parser();
+
+ // Runs the parser of the input string.
+ void Parse();
+
+ private:
+ // This bit of hackery is used to implement a function pointer type that
+ // returns a pointer to a function of the same signature. Since a definition
+ // like that is inherently recursive, it's impossible to do:
+ // type StateFunc(*StateFunc)(StateData*);
+ // However, this approach works without the need to use void*. Inspired by
+ // <http://www.gotw.ca/gotw/057.htm>.
+ struct StateFunc;
+ typedef StateFunc(Parser::*StateFuncPtr)();
+ struct StateFunc {
+ StateFunc(StateFuncPtr pf) : pf_(pf) {}
+ operator StateFuncPtr() {
+ return pf_;
+ }
+ StateFuncPtr pf_;
+ };
+
+ // Tests whether there is at least one more character at pos_ before end_.
+ bool HasNext();
+
+ // The parser state functions. On entry, the parser state is at the beginning
+ // of the token. Each returns a function pointer to the next state function,
+ // or NULL to end parsing. On return, the parser is at the beginning of the
+ // next token.
+ StateFunc Start();
+ StateFunc ParseComment();
+ StateFunc ParseBugURL();
+ StateFunc BeginModifiers();
+ StateFunc InModifiers();
+ StateFunc SaveModifier();
+ StateFunc EndModifiers();
+ StateFunc ParseTestName();
+ StateFunc SaveTestName();
+ StateFunc ParseExpectation();
+ StateFunc ParseExpectationType();
+ StateFunc SaveExpectationType();
+ StateFunc End();
+
+ // A state function that collects character data from the current position
+ // to the next whitespace character. Returns the |success| function when at
+ // the end of the string, with the data stored in |extracted_string_|.
+ StateFunc ExtractString(StateFunc success);
+
+ // Function that skips over horizontal whitespace characters and then returns
+ // the |next| state.
+ StateFunc SkipWhitespace(StateFunc next);
+
+ // Does the same as SkipWhitespace but includes newlines.
+ StateFunc SkipWhitespaceAndNewLines(StateFunc next);
+
+ // State function that reports the given syntax error |message| to the
+ // delegate and then returns NULL, ending the parse loop.
+ StateFunc SyntaxError(const std::string& message);
+
+ // Function that reports the data |error| to the delegate without stopping
+ // parsing.
+ void DataError(const std::string& error);
+
+ // Parser delegate.
+ Delegate* delegate_;
+
+ // The input string.
+ std::string input_;
+
+ // Current location in the |input_|.
+ const char* pos_;
+
+ // Pointer to the end of the |input_|.
+ const char* end_;
+
+ // Current line number, as updated by SkipWhitespace().
+ int line_number_;
+
+ // The character data extracted from |input_| as a result of the
+ // ExtractString() state.
+ base::StringPiece extracted_string_;
+
+ // The Expectation object that is currently being processed by the parser.
+ // Reset in Start().
+ Expectation current_;
+
+ // If DataError() has been called during the course of parsing |current_|.
+ // If true, then |current_| will not be emitted to the Delegate.
+ bool data_error_;
+};
+
+} // namespace test_expectations
+
+#endif // BASE_TEST_EXPECTATIONS_PARSER_H_
diff --git a/base/test/expectations/parser_unittest.cc b/base/test/expectations/parser_unittest.cc
new file mode 100644
index 0000000..1c55a05
--- /dev/null
+++ b/base/test/expectations/parser_unittest.cc
@@ -0,0 +1,209 @@
+// 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/expectations/parser.h"
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using test_expectations::Parser;
+
+class TestExpectationParserTest : public testing::Test,
+ public Parser::Delegate {
+ public:
+ virtual void EmitExpectation(
+ const test_expectations::Expectation& expectation) OVERRIDE {
+ expectations_.push_back(expectation);
+ }
+
+ virtual void OnSyntaxError(const std::string& message) OVERRIDE {
+ syntax_error_ = message;
+ }
+
+ virtual void OnDataError(const std::string& error) OVERRIDE {
+ data_errors_.push_back(error);
+ }
+
+ protected:
+ std::vector<test_expectations::Expectation> expectations_;
+ std::string syntax_error_;
+ std::vector<std::string> data_errors_;
+};
+
+TEST_F(TestExpectationParserTest, Basic) {
+ Parser(this,
+ "http://crbug.com/1234 [ Win-8 ] DouglasTest.PoopsOk = Timeout").
+ Parse();
+ EXPECT_TRUE(syntax_error_.empty());
+ EXPECT_EQ(0u, data_errors_.size());
+
+ ASSERT_EQ(1u, expectations_.size());
+ EXPECT_EQ("DouglasTest.PoopsOk", expectations_[0].test_name);
+ EXPECT_EQ(test_expectations::RESULT_TIMEOUT, expectations_[0].result);
+ EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED,
+ expectations_[0].configuration);
+
+ ASSERT_EQ(1u, expectations_[0].platforms.size());
+ EXPECT_EQ("Win", expectations_[0].platforms[0].name);
+ EXPECT_EQ("8", expectations_[0].platforms[0].variant);
+}
+
+TEST_F(TestExpectationParserTest, MultiModifier) {
+ Parser(this, "BUG [ Win-XP Mac ] OhMy.MeOhMy = Failure").Parse();
+ EXPECT_TRUE(syntax_error_.empty());
+ EXPECT_EQ(0u, data_errors_.size());
+
+ ASSERT_EQ(1u, expectations_.size());
+ EXPECT_EQ("OhMy.MeOhMy", expectations_[0].test_name);
+ EXPECT_EQ(test_expectations::RESULT_FAILURE,
+ expectations_[0].result);
+ EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED,
+ expectations_[0].configuration);
+
+ ASSERT_EQ(2u, expectations_[0].platforms.size());
+
+ EXPECT_EQ("Win", expectations_[0].platforms[0].name);
+ EXPECT_EQ("XP", expectations_[0].platforms[0].variant);
+
+ EXPECT_EQ("Mac", expectations_[0].platforms[1].name);
+ EXPECT_EQ("", expectations_[0].platforms[1].variant);
+}
+
+TEST_F(TestExpectationParserTest, EmptyModifier) {
+ Parser(this,
+ "BUG [] First.Test = Failure\n"
+ "BUG2 [ ] Second.Test = Crash").Parse();
+ EXPECT_EQ(0u, data_errors_.size());
+
+ ASSERT_EQ(2u, expectations_.size());
+
+ EXPECT_EQ("First.Test", expectations_[0].test_name);
+ EXPECT_EQ(test_expectations::RESULT_FAILURE,
+ expectations_[0].result);
+ EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED,
+ expectations_[0].configuration);
+ EXPECT_EQ(0u, expectations_[0].platforms.size());
+
+ EXPECT_EQ("Second.Test", expectations_[1].test_name);
+ EXPECT_EQ(test_expectations::RESULT_CRASH,
+ expectations_[1].result);
+ EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED,
+ expectations_[1].configuration);
+ EXPECT_EQ(0u, expectations_[1].platforms.size());
+}
+
+TEST_F(TestExpectationParserTest, MultiLine) {
+ Parser(this,
+ "BUG [ Linux ] Line.First = Failure\n"
+ "\n"
+ "# A test comment.\n"
+ "BUG2 [ Release ] Line.Second = Skip").Parse();
+ EXPECT_TRUE(syntax_error_.empty());
+ EXPECT_EQ(0u, data_errors_.size());
+
+ ASSERT_EQ(2u, expectations_.size());
+ EXPECT_EQ("Line.First", expectations_[0].test_name);
+ EXPECT_EQ(test_expectations::RESULT_FAILURE, expectations_[0].result);
+ EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED,
+ expectations_[0].configuration);
+
+ ASSERT_EQ(1u, expectations_[0].platforms.size());
+ EXPECT_EQ("Linux", expectations_[0].platforms[0].name);
+ EXPECT_EQ("", expectations_[0].platforms[0].variant);
+
+ EXPECT_EQ("Line.Second", expectations_[1].test_name);
+ EXPECT_EQ(test_expectations::RESULT_SKIP, expectations_[1].result);
+ EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE,
+ expectations_[1].configuration);
+ EXPECT_EQ(0u, expectations_[1].platforms.size());
+}
+
+TEST_F(TestExpectationParserTest, MultiLineWithComments) {
+ Parser(this,
+ " # Comment for your thoughts\n"
+ " \t \n"
+ "BUG [ Mac-10.8 Debug] Foo=Bar =Skip # Why not another comment?\n"
+ "BUG2 [Win-XP\tWin-Vista ] Cow.GoesMoo =\tTimeout\n\n").Parse();
+ EXPECT_TRUE(syntax_error_.empty()) << syntax_error_;
+ EXPECT_EQ(0u, data_errors_.size());
+
+ ASSERT_EQ(2u, expectations_.size());
+ EXPECT_EQ("Foo=Bar", expectations_[0].test_name);
+ EXPECT_EQ(test_expectations::RESULT_SKIP, expectations_[0].result);
+ EXPECT_EQ(test_expectations::CONFIGURATION_DEBUG,
+ expectations_[0].configuration);
+
+ ASSERT_EQ(1u, expectations_[0].platforms.size());
+ EXPECT_EQ("Mac", expectations_[0].platforms[0].name);
+ EXPECT_EQ("10.8", expectations_[0].platforms[0].variant);
+
+ EXPECT_EQ("Cow.GoesMoo", expectations_[1].test_name);
+ EXPECT_EQ(test_expectations::RESULT_TIMEOUT, expectations_[1].result);
+ EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED,
+ expectations_[1].configuration);
+
+ ASSERT_EQ(2u, expectations_[1].platforms.size());
+ EXPECT_EQ("Win", expectations_[1].platforms[0].name);
+ EXPECT_EQ("XP", expectations_[1].platforms[0].variant);
+ EXPECT_EQ("Win", expectations_[1].platforms[0].name);
+ EXPECT_EQ("Vista", expectations_[1].platforms[1].variant);
+}
+
+TEST_F(TestExpectationParserTest, WeirdSpaces) {
+ Parser(this, " BUG [Linux] Weird = Skip ").Parse();
+ EXPECT_EQ(1u, expectations_.size());
+ EXPECT_TRUE(syntax_error_.empty());
+ EXPECT_EQ(0u, data_errors_.size());
+}
+
+TEST_F(TestExpectationParserTest, SyntaxErrors) {
+ const char* kErrors[] = {
+ "Foo [ dfasd",
+ "Foo [Linux] # This is an illegal comment",
+ "Foo [Linux] Bar # Another illegal comment.",
+ "Foo [Linux] Bar = # Another illegal comment.",
+ "Foo[Linux]Bar=Failure",
+ "Foo\n[Linux] Bar = Failure",
+ "Foo [\nLinux] Bar = Failure",
+ "Foo [Linux\n] Bar = Failure",
+ "Foo [ Linux ] \n Bar = Failure",
+ "Foo [ Linux ] Bar =\nFailure",
+ "Foo [ Linux \n ] Bar =\nFailure",
+ };
+
+ for (size_t i = 0; i < arraysize(kErrors); ++i) {
+ Parser(this, kErrors[i]).Parse();
+ EXPECT_FALSE(syntax_error_.empty())
+ << "Should have error for #" << i << ": " << kErrors[i];
+ syntax_error_.clear();
+ }
+}
+
+TEST_F(TestExpectationParserTest, DataErrors) {
+ const char* kOneError[] = {
+ "http://crbug.com/1234 [MagicBrowzR] BadModifier = Timeout",
+ "________ [Linux] BadResult = WhatNow",
+ "http://wkb.ug/1234 [Debug Release Win-7] MultipleConfigs = Skip",
+ };
+
+ for (size_t i = 0; i < arraysize(kOneError); ++i) {
+ Parser(this, kOneError[i]).Parse();
+ EXPECT_EQ(1u, data_errors_.size()) << kOneError[i];
+ data_errors_.clear();
+ }
+
+ const char* kTwoErrors[] = {
+ ". [Mac-TurningIntoiOS] BadModifierVariant.BadResult = Foobar",
+ "1234 [ Debug Release OS/2 ] MultipleConfigs.BadModifier = Pass",
+ };
+
+ for (size_t i = 0; i < arraysize(kTwoErrors); ++i) {
+ Parser(this, kTwoErrors[i]).Parse();
+ EXPECT_EQ(2u, data_errors_.size()) << kTwoErrors[i];
+ data_errors_.clear();
+ }
+}
diff --git a/base/test/gtest_xml_util.cc b/base/test/gtest_xml_util.cc
new file mode 100644
index 0000000..7a5ba8a
--- /dev/null
+++ b/base/test/gtest_xml_util.cc
@@ -0,0 +1,229 @@
+// 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 "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/launcher/test_launcher.h"
+#include "third_party/libxml/chromium/libxml_utils.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);
+ base::StringAppendV(error, message, args);
+ va_end(args);
+}
+
+} // namespace
+
+XmlUnitTestResultPrinter::XmlUnitTestResultPrinter() : output_file_(NULL) {
+}
+
+XmlUnitTestResultPrinter::~XmlUnitTestResultPrinter() {
+ if (output_file_) {
+ fprintf(output_file_, "</testsuites>\n");
+ fflush(output_file_);
+ base::CloseFile(output_file_);
+ }
+}
+
+bool XmlUnitTestResultPrinter::Initialize(const FilePath& output_file_path) {
+ DCHECK(!output_file_);
+ output_file_ = OpenFile(output_file_path, "w");
+ if (!output_file_)
+ return false;
+
+ fprintf(output_file_,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites>\n");
+ fflush(output_file_);
+
+ return true;
+}
+
+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");
+ fprintf(output_file_, " </testcase>\n");
+ fflush(output_file_);
+}
+
+void XmlUnitTestResultPrinter::OnTestCaseEnd(
+ const testing::TestCase& test_case) {
+ fprintf(output_file_, " </testsuite>\n");
+ fflush(output_file_);
+}
+
+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_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 = TestLauncher::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>(strtod(test_time_str.c_str(), NULL) *
+ Time::kMicrosecondsPerSecond));
+
+ result.status = TestResult::TEST_SUCCESS;
+
+ if (!results->empty() &&
+ results->at(results->size() - 1).full_name == result.full_name &&
+ results->at(results->size() - 1).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->at(results->size() - 1).status = TestResult::TEST_FAILURE;
+
+ state = STATE_FAILURE;
+ } else if (node_name == "testcase" && xml_reader.IsClosingElement()) {
+ // Deliberately empty.
+ } 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/base/test/gtest_xml_util.h b/base/test/gtest_xml_util.h
new file mode 100644
index 0000000..79527e5
--- /dev/null
+++ b/base/test/gtest_xml_util.h
@@ -0,0 +1,51 @@
+// 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/basictypes.h"
+#include "base/compiler_specific.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+class FilePath;
+struct TestResult;
+
+// 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();
+ virtual ~XmlUnitTestResultPrinter();
+
+ // Must be called before adding as a listener. Returns true on success.
+ bool Initialize(const FilePath& output_file_path) WARN_UNUSED_RESULT;
+
+ private:
+ // testing::EmptyTestEventListener:
+ virtual void OnTestCaseStart(const testing::TestCase& test_case) OVERRIDE;
+ virtual void OnTestStart(const testing::TestInfo& test_info) OVERRIDE;
+ virtual void OnTestEnd(const testing::TestInfo& test_info) OVERRIDE;
+ virtual void OnTestCaseEnd(const testing::TestCase& test_case) OVERRIDE;
+
+ FILE* output_file_;
+
+ DISALLOW_COPY_AND_ASSIGN(XmlUnitTestResultPrinter);
+};
+
+// 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/base/test/histogram_tester.cc b/base/test/histogram_tester.cc
new file mode 100644
index 0000000..412059a
--- /dev/null
+++ b/base/test/histogram_tester.cc
@@ -0,0 +1,122 @@
+// 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/histogram_tester.h"
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+HistogramTester::HistogramTester() {
+ StatisticsRecorder::Initialize(); // Safe to call multiple times.
+
+ // Record any histogram data that exists when the object is created so it can
+ // be subtracted later.
+ StatisticsRecorder::Histograms histograms;
+ StatisticsRecorder::GetSnapshot(std::string(), &histograms);
+ for (size_t i = 0; i < histograms.size(); ++i) {
+ histograms_snapshot_[histograms[i]->histogram_name()] =
+ histograms[i]->SnapshotSamples().release();
+ }
+}
+
+HistogramTester::~HistogramTester() {
+ STLDeleteValues(&histograms_snapshot_);
+}
+
+void HistogramTester::ExpectUniqueSample(
+ const std::string& name,
+ base::HistogramBase::Sample sample,
+ base::HistogramBase::Count expected_count) const {
+ base::HistogramBase* histogram =
+ base::StatisticsRecorder::FindHistogram(name);
+ EXPECT_NE(static_cast<base::HistogramBase*>(NULL), histogram)
+ << "Histogram \"" << name << "\" does not exist.";
+
+ if (histogram) {
+ scoped_ptr<base::HistogramSamples> samples(histogram->SnapshotSamples());
+ CheckBucketCount(name, sample, expected_count, *samples);
+ CheckTotalCount(name, expected_count, *samples);
+ }
+}
+
+void HistogramTester::ExpectBucketCount(
+ const std::string& name,
+ base::HistogramBase::Sample sample,
+ base::HistogramBase::Count expected_count) const {
+ base::HistogramBase* histogram =
+ base::StatisticsRecorder::FindHistogram(name);
+ EXPECT_NE(static_cast<base::HistogramBase*>(NULL), histogram)
+ << "Histogram \"" << name << "\" does not exist.";
+
+ if (histogram) {
+ scoped_ptr<base::HistogramSamples> samples(histogram->SnapshotSamples());
+ CheckBucketCount(name, sample, expected_count, *samples);
+ }
+}
+
+void HistogramTester::ExpectTotalCount(const std::string& name,
+ base::HistogramBase::Count count) const {
+ base::HistogramBase* histogram =
+ base::StatisticsRecorder::FindHistogram(name);
+ if (histogram) {
+ scoped_ptr<base::HistogramSamples> samples(histogram->SnapshotSamples());
+ CheckTotalCount(name, count, *samples);
+ } else {
+ // No histogram means there were zero samples.
+ EXPECT_EQ(count, 0) << "Histogram \"" << name << "\" does not exist.";
+ }
+}
+
+scoped_ptr<HistogramSamples> HistogramTester::GetHistogramSamplesSinceCreation(
+ const std::string& histogram_name) {
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(histogram_name);
+ if (!histogram)
+ return scoped_ptr<HistogramSamples>();
+ scoped_ptr<HistogramSamples> named_samples(histogram->SnapshotSamples());
+ HistogramSamples* named_original_samples =
+ histograms_snapshot_[histogram_name];
+ if (named_original_samples)
+ named_samples->Subtract(*named_original_samples);
+ return named_samples.Pass();
+}
+
+void HistogramTester::CheckBucketCount(
+ const std::string& name,
+ base::HistogramBase::Sample sample,
+ base::HistogramBase::Count expected_count,
+ base::HistogramSamples& samples) const {
+ int actual_count = samples.GetCount(sample);
+ std::map<std::string, HistogramSamples*>::const_iterator histogram_data;
+ histogram_data = histograms_snapshot_.find(name);
+ if (histogram_data != histograms_snapshot_.end())
+ actual_count -= histogram_data->second->GetCount(sample);
+
+ EXPECT_EQ(expected_count, actual_count)
+ << "Histogram \"" << name
+ << "\" does not have the right number of samples (" << expected_count
+ << ") in the expected bucket (" << sample << "). It has (" << actual_count
+ << ").";
+}
+
+void HistogramTester::CheckTotalCount(const std::string& name,
+ base::HistogramBase::Count expected_count,
+ base::HistogramSamples& samples) const {
+ int actual_count = samples.TotalCount();
+ std::map<std::string, HistogramSamples*>::const_iterator histogram_data;
+ histogram_data = histograms_snapshot_.find(name);
+ if (histogram_data != histograms_snapshot_.end())
+ actual_count -= histogram_data->second->TotalCount();
+
+ EXPECT_EQ(expected_count, actual_count)
+ << "Histogram \"" << name
+ << "\" does not have the right total number of samples ("
+ << expected_count << "). It has (" << actual_count << ").";
+}
+
+} // namespace base
diff --git a/base/test/histogram_tester.h b/base/test/histogram_tester.h
new file mode 100644
index 0000000..b672308
--- /dev/null
+++ b/base/test/histogram_tester.h
@@ -0,0 +1,81 @@
+// 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_HISTOGRAM_TESTER_H_
+#define BASE_TEST_HISTOGRAM_TESTER_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_base.h"
+
+namespace base {
+
+class HistogramSamples;
+
+// HistogramTester provides a simple interface for examining histograms, UMA
+// or otherwise. Tests can use this interface to verify that histogram data is
+// getting logged as intended.
+class HistogramTester {
+ public:
+ // The constructor will call StatisticsRecorder::Initialize() for you. Also,
+ // this takes a snapshot of all current histograms counts.
+ HistogramTester();
+ ~HistogramTester();
+
+ // We know the exact number of samples in a bucket, and that no other bucket
+ // should have samples. Measures the diff from the snapshot taken when this
+ // object was constructed.
+ void ExpectUniqueSample(const std::string& name,
+ base::HistogramBase::Sample sample,
+ base::HistogramBase::Count expected_count) const;
+
+ // We know the exact number of samples in a bucket, but other buckets may
+ // have samples as well. Measures the diff from the snapshot taken when this
+ // object was constructed.
+ void ExpectBucketCount(const std::string& name,
+ base::HistogramBase::Sample sample,
+ base::HistogramBase::Count expected_count) const;
+
+ // We don't know the values of the samples, but we know how many there are.
+ // This measures the diff from the snapshot taken when this object was
+ // constructed.
+ void ExpectTotalCount(const std::string& name,
+ base::HistogramBase::Count count) const;
+
+ // Access a modified HistogramSamples containing only what has been logged
+ // to the histogram since the creation of this object.
+ scoped_ptr<HistogramSamples> GetHistogramSamplesSinceCreation(
+ const std::string& histogram_name);
+
+ private:
+ // Verifies and asserts that value in the |sample| bucket matches the
+ // |expected_count|. The bucket's current value is determined from |samples|
+ // and is modified based on the snapshot stored for histogram |name|.
+ void CheckBucketCount(const std::string& name,
+ base::HistogramBase::Sample sample,
+ base::Histogram::Count expected_count,
+ base::HistogramSamples& samples) const;
+
+ // Verifies that the total number of values recorded for the histogram |name|
+ // is |expected_count|. This is checked against |samples| minus the snapshot
+ // that was taken for |name|.
+ void CheckTotalCount(const std::string& name,
+ base::Histogram::Count expected_count,
+ base::HistogramSamples& samples) const;
+
+ // Used to determine the histogram changes made during this instance's
+ // lifecycle. This instance takes ownership of the samples, which are deleted
+ // when the instance is destroyed.
+ std::map<std::string, HistogramSamples*> histograms_snapshot_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramTester);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_HISTOGRAM_TESTER_H_
diff --git a/base/test/histogram_tester_unittest.cc b/base/test/histogram_tester_unittest.cc
new file mode 100644
index 0000000..a03ee13
--- /dev/null
+++ b/base/test/histogram_tester_unittest.cc
@@ -0,0 +1,81 @@
+// 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/histogram_tester.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+const std::string kHistogram1 = "Test1";
+const std::string kHistogram2 = "Test2";
+const std::string kHistogram3 = "Test3";
+const std::string kHistogram4 = "Test4";
+
+typedef testing::Test HistogramTesterTest;
+
+TEST_F(HistogramTesterTest, Scope) {
+ // Record a histogram before the creation of the recorder.
+ UMA_HISTOGRAM_BOOLEAN(kHistogram1, true);
+
+ HistogramTester tester;
+
+ // Verify that no histogram is recorded.
+ scoped_ptr<HistogramSamples> samples(
+ tester.GetHistogramSamplesSinceCreation(kHistogram1));
+ EXPECT_FALSE(samples);
+
+ // Record a histogram after the creation of the recorder.
+ UMA_HISTOGRAM_BOOLEAN(kHistogram1, true);
+
+ // Verify that one histogram is recorded.
+ samples = tester.GetHistogramSamplesSinceCreation(kHistogram1);
+ EXPECT_TRUE(samples);
+ EXPECT_EQ(1, samples->TotalCount());
+}
+
+TEST_F(HistogramTesterTest, TestUniqueSample) {
+ HistogramTester tester;
+
+ // Record into a sample thrice
+ UMA_HISTOGRAM_COUNTS_100(kHistogram2, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram2, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram2, 2);
+
+ tester.ExpectUniqueSample(kHistogram2, 2, 3);
+}
+
+TEST_F(HistogramTesterTest, TestBucketsSample) {
+ HistogramTester tester;
+
+ // Record into a sample twice
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 2);
+ UMA_HISTOGRAM_COUNTS_100(kHistogram3, 3);
+
+ tester.ExpectBucketCount(kHistogram3, 2, 4);
+ tester.ExpectBucketCount(kHistogram3, 3, 1);
+
+ tester.ExpectTotalCount(kHistogram3, 5);
+}
+
+TEST_F(HistogramTesterTest, TestBucketsSampleWithScope) {
+ // Record into a sample twice, once before the tester creation and once after.
+ UMA_HISTOGRAM_COUNTS_100(kHistogram4, 2);
+
+ HistogramTester tester;
+ UMA_HISTOGRAM_COUNTS_100(kHistogram4, 3);
+
+ tester.ExpectBucketCount(kHistogram4, 2, 0);
+ tester.ExpectBucketCount(kHistogram4, 3, 1);
+
+ tester.ExpectTotalCount(kHistogram4, 1);
+}
+
+} // namespace base
diff --git a/base/test/launcher/test_launcher.cc b/base/test/launcher/test_launcher.cc
new file mode 100644
index 0000000..2a36c69
--- /dev/null
+++ b/base/test/launcher/test_launcher.cc
@@ -0,0 +1,1099 @@
+// 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/launcher/test_launcher.h"
+
+#if defined(OS_POSIX)
+#include <fcntl.h>
+#endif
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/format_macros.h"
+#include "base/hash.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringize_macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/launcher/test_results_tracker.h"
+#include "base/test/sequenced_worker_pool_owner.h"
+#include "base/test/test_switches.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/scoped_nsautorelease_pool.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace base {
+
+// See https://groups.google.com/a/chromium.org/d/msg/chromium-dev/nkdTP7sstSc/uT3FaE_sgkAJ .
+using ::operator<<;
+
+// The environment variable name for the total number of test shards.
+const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS";
+// The environment variable name for the test shard index.
+const char kTestShardIndex[] = "GTEST_SHARD_INDEX";
+
+namespace {
+
+// Global tag for test runs where the results are incomplete or unreliable
+// for any reason, e.g. early exit because of too many broken tests.
+const char kUnreliableResultsTag[] = "UNRELIABLE_RESULTS";
+
+// Maximum time of no output after which we print list of processes still
+// running. This deliberately doesn't use TestTimeouts (which is otherwise
+// a recommended solution), because they can be increased. This would defeat
+// the purpose of this timeout, which is 1) to avoid buildbot "no output for
+// X seconds" timeout killing the process 2) help communicate status of
+// the test launcher to people looking at the output (no output for a long
+// time is mysterious and gives no info about what is happening) 3) help
+// debugging in case the process hangs anyway.
+const int kOutputTimeoutSeconds = 15;
+
+// Limit of output snippet lines when printing to stdout.
+// Avoids flooding the logs with amount of output that gums up
+// the infrastructure.
+const size_t kOutputSnippetLinesLimit = 5000;
+
+// Set of live launch test processes with corresponding lock (it is allowed
+// for callers to launch processes on different threads).
+LazyInstance<std::map<ProcessHandle, CommandLine> > g_live_processes
+ = LAZY_INSTANCE_INITIALIZER;
+LazyInstance<Lock> g_live_processes_lock = LAZY_INSTANCE_INITIALIZER;
+
+#if defined(OS_POSIX)
+// Self-pipe that makes it possible to do complex shutdown handling
+// outside of the signal handler.
+int g_shutdown_pipe[2] = { -1, -1 };
+
+void ShutdownPipeSignalHandler(int signal) {
+ HANDLE_EINTR(write(g_shutdown_pipe[1], "q", 1));
+}
+
+void KillSpawnedTestProcesses() {
+ // Keep the lock until exiting the process to prevent further processes
+ // from being spawned.
+ AutoLock lock(g_live_processes_lock.Get());
+
+ fprintf(stdout,
+ "Sending SIGTERM to %" PRIuS " child processes... ",
+ g_live_processes.Get().size());
+ fflush(stdout);
+
+ for (std::map<ProcessHandle, CommandLine>::iterator i =
+ g_live_processes.Get().begin();
+ i != g_live_processes.Get().end();
+ ++i) {
+ // Send the signal to entire process group.
+ kill((-1) * (i->first), SIGTERM);
+ }
+
+ fprintf(stdout,
+ "done.\nGiving processes a chance to terminate cleanly... ");
+ fflush(stdout);
+
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(500));
+
+ fprintf(stdout, "done.\n");
+ fflush(stdout);
+
+ fprintf(stdout,
+ "Sending SIGKILL to %" PRIuS " child processes... ",
+ g_live_processes.Get().size());
+ fflush(stdout);
+
+ for (std::map<ProcessHandle, CommandLine>::iterator i =
+ g_live_processes.Get().begin();
+ i != g_live_processes.Get().end();
+ ++i) {
+ // Send the signal to entire process group.
+ kill((-1) * (i->first), SIGKILL);
+ }
+
+ fprintf(stdout, "done.\n");
+ fflush(stdout);
+}
+
+// I/O watcher for the reading end of the self-pipe above.
+// Terminates any launched child processes and exits the process.
+class SignalFDWatcher : public MessageLoopForIO::Watcher {
+ public:
+ SignalFDWatcher() {
+ }
+
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
+ fprintf(stdout, "\nCaught signal. Killing spawned test processes...\n");
+ fflush(stdout);
+
+ KillSpawnedTestProcesses();
+
+ // The signal would normally kill the process, so exit now.
+ exit(1);
+ }
+
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {
+ NOTREACHED();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SignalFDWatcher);
+};
+#endif // defined(OS_POSIX)
+
+// Parses the environment variable var as an Int32. If it is unset, returns
+// true. If it is set, unsets it then converts it to Int32 before
+// returning it in |result|. Returns true on success.
+bool TakeInt32FromEnvironment(const char* const var, int32* result) {
+ scoped_ptr<Environment> env(Environment::Create());
+ std::string str_val;
+
+ if (!env->GetVar(var, &str_val))
+ return true;
+
+ if (!env->UnSetVar(var)) {
+ LOG(ERROR) << "Invalid environment: we could not unset " << var << ".\n";
+ return false;
+ }
+
+ if (!StringToInt(str_val, result)) {
+ LOG(ERROR) << "Invalid environment: " << var << " is not an integer.\n";
+ return false;
+ }
+
+ return true;
+}
+
+// Unsets the environment variable |name| and returns true on success.
+// Also returns true if the variable just doesn't exist.
+bool UnsetEnvironmentVariableIfExists(const std::string& name) {
+ scoped_ptr<Environment> env(Environment::Create());
+ std::string str_val;
+
+ if (!env->GetVar(name.c_str(), &str_val))
+ return true;
+
+ return env->UnSetVar(name.c_str());
+}
+
+// Returns true if bot mode has been requested, i.e. defaults optimized
+// for continuous integration bots. This way developers don't have to remember
+// special command-line flags.
+bool BotModeEnabled() {
+ scoped_ptr<Environment> env(Environment::Create());
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherBotMode) ||
+ env->HasVar("CHROMIUM_TEST_LAUNCHER_BOT_MODE");
+}
+
+// Returns command line command line after gtest-specific processing
+// and applying |wrapper|.
+CommandLine PrepareCommandLineForGTest(const CommandLine& command_line,
+ const std::string& wrapper) {
+ CommandLine new_command_line(command_line.GetProgram());
+ CommandLine::SwitchMap switches = command_line.GetSwitches();
+
+ // Strip out gtest_repeat flag - this is handled by the launcher process.
+ switches.erase(kGTestRepeatFlag);
+
+ // Don't try to write the final XML report in child processes.
+ switches.erase(kGTestOutputFlag);
+
+ for (CommandLine::SwitchMap::const_iterator iter = switches.begin();
+ iter != switches.end(); ++iter) {
+ new_command_line.AppendSwitchNative((*iter).first, (*iter).second);
+ }
+
+ // Prepend wrapper after last CommandLine quasi-copy operation. CommandLine
+ // does not really support removing switches well, and trying to do that
+ // on a CommandLine with a wrapper is known to break.
+ // TODO(phajdan.jr): Give it a try to support CommandLine removing switches.
+#if defined(OS_WIN)
+ new_command_line.PrependWrapper(ASCIIToWide(wrapper));
+#elif defined(OS_POSIX)
+ new_command_line.PrependWrapper(wrapper);
+#endif
+
+ return new_command_line;
+}
+
+// Launches a child process using |command_line|. If the child process is still
+// running after |timeout|, it is terminated and |*was_timeout| is set to true.
+// Returns exit code of the process.
+int LaunchChildTestProcessWithOptions(const CommandLine& command_line,
+ const LaunchOptions& options,
+ int flags,
+ base::TimeDelta timeout,
+ bool* was_timeout) {
+#if defined(OS_POSIX)
+ // Make sure an option we rely on is present - see LaunchChildGTestProcess.
+ DCHECK(options.new_process_group);
+#endif
+
+ LaunchOptions new_options(options);
+
+#if defined(OS_WIN)
+ DCHECK(!new_options.job_handle);
+
+ win::ScopedHandle job_handle;
+ if (flags & TestLauncher::USE_JOB_OBJECTS) {
+ job_handle.Set(CreateJobObject(NULL, NULL));
+ if (!job_handle.IsValid()) {
+ LOG(ERROR) << "Could not create JobObject.";
+ return -1;
+ }
+
+ DWORD job_flags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ // Allow break-away from job since sandbox and few other places rely on it
+ // on Windows versions prior to Windows 8 (which supports nested jobs).
+ if (win::GetVersion() < win::VERSION_WIN8 &&
+ flags & TestLauncher::ALLOW_BREAKAWAY_FROM_JOB) {
+ job_flags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+ }
+
+ if (!SetJobObjectLimitFlags(job_handle.Get(), job_flags)) {
+ LOG(ERROR) << "Could not SetJobObjectLimitFlags.";
+ return -1;
+ }
+
+ new_options.job_handle = job_handle.Get();
+ }
+#endif // defined(OS_WIN)
+
+#if defined(OS_LINUX)
+ // To prevent accidental privilege sharing to an untrusted child, processes
+ // are started with PR_SET_NO_NEW_PRIVS. Do not set that here, since this
+ // new child will be privileged and trusted.
+ new_options.allow_new_privs = true;
+#endif
+
+ base::ProcessHandle process_handle;
+
+ {
+ // Note how we grab the lock before the process possibly gets created.
+ // This ensures that when the lock is held, ALL the processes are registered
+ // in the set.
+ AutoLock lock(g_live_processes_lock.Get());
+
+ if (!base::LaunchProcess(command_line, new_options, &process_handle))
+ return -1;
+
+ g_live_processes.Get().insert(std::make_pair(process_handle, command_line));
+ }
+
+ int exit_code = 0;
+ if (!base::WaitForExitCodeWithTimeout(process_handle,
+ &exit_code,
+ timeout)) {
+ *was_timeout = true;
+ exit_code = -1; // Set a non-zero exit code to signal a failure.
+
+ // Ensure that the process terminates.
+ base::KillProcess(process_handle, -1, true);
+ }
+
+ {
+ // Note how we grab the log before issuing a possibly broad process kill.
+ // Other code parts that grab the log kill processes, so avoid trying
+ // to do that twice and trigger all kinds of log messages.
+ AutoLock lock(g_live_processes_lock.Get());
+
+#if defined(OS_POSIX)
+ if (exit_code != 0) {
+ // On POSIX, in case the test does not exit cleanly, either due to a crash
+ // or due to it timing out, we need to clean up any child processes that
+ // it might have created. On Windows, child processes are automatically
+ // cleaned up using JobObjects.
+ base::KillProcessGroup(process_handle);
+ }
+#endif
+
+ g_live_processes.Get().erase(process_handle);
+ }
+
+ base::CloseProcessHandle(process_handle);
+
+ return exit_code;
+}
+
+void RunCallback(
+ const TestLauncher::LaunchChildGTestProcessCallback& callback,
+ int exit_code,
+ const TimeDelta& elapsed_time,
+ bool was_timeout,
+ const std::string& output) {
+ callback.Run(exit_code, elapsed_time, was_timeout, output);
+}
+
+void DoLaunchChildTestProcess(
+ const CommandLine& command_line,
+ base::TimeDelta timeout,
+ int flags,
+ bool redirect_stdio,
+ scoped_refptr<MessageLoopProxy> message_loop_proxy,
+ const TestLauncher::LaunchChildGTestProcessCallback& callback) {
+ TimeTicks start_time = TimeTicks::Now();
+
+ // Redirect child process output to a file.
+ base::FilePath output_file;
+ CHECK(base::CreateTemporaryFile(&output_file));
+
+ LaunchOptions options;
+#if defined(OS_WIN)
+ win::ScopedHandle handle;
+
+ if (redirect_stdio) {
+ // Make the file handle inheritable by the child.
+ SECURITY_ATTRIBUTES sa_attr;
+ sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa_attr.lpSecurityDescriptor = NULL;
+ sa_attr.bInheritHandle = TRUE;
+
+ handle.Set(CreateFile(output_file.value().c_str(),
+ GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_DELETE,
+ &sa_attr,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_TEMPORARY,
+ NULL));
+ CHECK(handle.IsValid());
+ options.inherit_handles = true;
+ options.stdin_handle = INVALID_HANDLE_VALUE;
+ options.stdout_handle = handle.Get();
+ options.stderr_handle = handle.Get();
+ }
+#elif defined(OS_POSIX)
+ options.new_process_group = true;
+
+ base::FileHandleMappingVector fds_mapping;
+ base::ScopedFD output_file_fd;
+
+ if (redirect_stdio) {
+ output_file_fd.reset(open(output_file.value().c_str(), O_RDWR));
+ CHECK(output_file_fd.is_valid());
+
+ fds_mapping.push_back(std::make_pair(output_file_fd.get(), STDOUT_FILENO));
+ fds_mapping.push_back(std::make_pair(output_file_fd.get(), STDERR_FILENO));
+ options.fds_to_remap = &fds_mapping;
+ }
+#endif
+
+ bool was_timeout = false;
+ int exit_code = LaunchChildTestProcessWithOptions(
+ command_line, options, flags, timeout, &was_timeout);
+
+ if (redirect_stdio) {
+#if defined(OS_WIN)
+ FlushFileBuffers(handle.Get());
+ handle.Close();
+#elif defined(OS_POSIX)
+ output_file_fd.reset();
+#endif
+ }
+
+ std::string output_file_contents;
+ CHECK(base::ReadFileToString(output_file, &output_file_contents));
+
+ if (!base::DeleteFile(output_file, false)) {
+ // This needs to be non-fatal at least for Windows.
+ LOG(WARNING) << "Failed to delete " << output_file.AsUTF8Unsafe();
+ }
+
+ // Run target callback on the thread it was originating from, not on
+ // a worker pool thread.
+ message_loop_proxy->PostTask(
+ FROM_HERE,
+ Bind(&RunCallback,
+ callback,
+ exit_code,
+ TimeTicks::Now() - start_time,
+ was_timeout,
+ output_file_contents));
+}
+
+} // namespace
+
+const char kGTestFilterFlag[] = "gtest_filter";
+const char kGTestHelpFlag[] = "gtest_help";
+const char kGTestListTestsFlag[] = "gtest_list_tests";
+const char kGTestRepeatFlag[] = "gtest_repeat";
+const char kGTestRunDisabledTestsFlag[] = "gtest_also_run_disabled_tests";
+const char kGTestOutputFlag[] = "gtest_output";
+
+TestLauncherDelegate::~TestLauncherDelegate() {
+}
+
+TestLauncher::TestLauncher(TestLauncherDelegate* launcher_delegate,
+ size_t parallel_jobs)
+ : launcher_delegate_(launcher_delegate),
+ total_shards_(1),
+ shard_index_(0),
+ cycles_(1),
+ test_started_count_(0),
+ test_finished_count_(0),
+ test_success_count_(0),
+ test_broken_count_(0),
+ retry_count_(0),
+ retry_limit_(0),
+ run_result_(true),
+ watchdog_timer_(FROM_HERE,
+ TimeDelta::FromSeconds(kOutputTimeoutSeconds),
+ this,
+ &TestLauncher::OnOutputTimeout),
+ parallel_jobs_(parallel_jobs) {
+}
+
+TestLauncher::~TestLauncher() {
+ if (worker_pool_owner_)
+ worker_pool_owner_->pool()->Shutdown();
+}
+
+bool TestLauncher::Run() {
+ if (!Init())
+ return false;
+
+ // Value of |cycles_| changes after each iteration. Keep track of the
+ // original value.
+ int requested_cycles = cycles_;
+
+#if defined(OS_POSIX)
+ CHECK_EQ(0, pipe(g_shutdown_pipe));
+
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ sigemptyset(&action.sa_mask);
+ action.sa_handler = &ShutdownPipeSignalHandler;
+
+ CHECK_EQ(0, sigaction(SIGINT, &action, NULL));
+ CHECK_EQ(0, sigaction(SIGQUIT, &action, NULL));
+ CHECK_EQ(0, sigaction(SIGTERM, &action, NULL));
+
+ MessageLoopForIO::FileDescriptorWatcher controller;
+ SignalFDWatcher watcher;
+
+ CHECK(MessageLoopForIO::current()->WatchFileDescriptor(
+ g_shutdown_pipe[0],
+ true,
+ MessageLoopForIO::WATCH_READ,
+ &controller,
+ &watcher));
+#endif // defined(OS_POSIX)
+
+ // Start the watchdog timer.
+ watchdog_timer_.Reset();
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&TestLauncher::RunTestIteration, Unretained(this)));
+
+ MessageLoop::current()->Run();
+
+ if (requested_cycles != 1)
+ results_tracker_.PrintSummaryOfAllIterations();
+
+ MaybeSaveSummaryAsJSON();
+
+ return run_result_;
+}
+
+void TestLauncher::LaunchChildGTestProcess(
+ const CommandLine& command_line,
+ const std::string& wrapper,
+ base::TimeDelta timeout,
+ int flags,
+ const LaunchChildGTestProcessCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Record the exact command line used to launch the child.
+ CommandLine new_command_line(
+ PrepareCommandLineForGTest(command_line, wrapper));
+
+ // When running in parallel mode we need to redirect stdio to avoid mixed-up
+ // output. We also always redirect on the bots to get the test output into
+ // JSON summary.
+ bool redirect_stdio = (parallel_jobs_ > 1) || BotModeEnabled();
+
+ worker_pool_owner_->pool()->PostWorkerTask(
+ FROM_HERE,
+ Bind(&DoLaunchChildTestProcess,
+ new_command_line,
+ timeout,
+ flags,
+ redirect_stdio,
+ MessageLoopProxy::current(),
+ Bind(&TestLauncher::OnLaunchTestProcessFinished,
+ Unretained(this),
+ callback)));
+}
+
+void TestLauncher::OnTestFinished(const TestResult& result) {
+ ++test_finished_count_;
+
+ bool print_snippet = false;
+ std::string print_test_stdio("auto");
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherPrintTestStdio)) {
+ print_test_stdio = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kTestLauncherPrintTestStdio);
+ }
+ if (print_test_stdio == "auto") {
+ print_snippet = (result.status != TestResult::TEST_SUCCESS);
+ } else if (print_test_stdio == "always") {
+ print_snippet = true;
+ } else if (print_test_stdio == "never") {
+ print_snippet = false;
+ } else {
+ LOG(WARNING) << "Invalid value of " << switches::kTestLauncherPrintTestStdio
+ << ": " << print_test_stdio;
+ }
+ if (print_snippet) {
+ std::vector<std::string> snippet_lines;
+ SplitString(result.output_snippet, '\n', &snippet_lines);
+ if (snippet_lines.size() > kOutputSnippetLinesLimit) {
+ size_t truncated_size = snippet_lines.size() - kOutputSnippetLinesLimit;
+ snippet_lines.erase(
+ snippet_lines.begin(),
+ snippet_lines.begin() + truncated_size);
+ snippet_lines.insert(snippet_lines.begin(), "<truncated>");
+ }
+ fprintf(stdout, "%s", JoinString(snippet_lines, "\n").c_str());
+ fflush(stdout);
+ }
+
+ if (result.status == TestResult::TEST_SUCCESS) {
+ ++test_success_count_;
+ } else {
+ tests_to_retry_.insert(result.full_name);
+ }
+
+ results_tracker_.AddTestResult(result);
+
+ // TODO(phajdan.jr): Align counter (padding).
+ std::string status_line(
+ StringPrintf("[%" PRIuS "/%" PRIuS "] %s ",
+ test_finished_count_,
+ test_started_count_,
+ result.full_name.c_str()));
+ if (result.completed()) {
+ status_line.append(StringPrintf("(%" PRId64 " ms)",
+ result.elapsed_time.InMilliseconds()));
+ } else if (result.status == TestResult::TEST_TIMEOUT) {
+ status_line.append("(TIMED OUT)");
+ } else if (result.status == TestResult::TEST_CRASH) {
+ status_line.append("(CRASHED)");
+ } else if (result.status == TestResult::TEST_SKIPPED) {
+ status_line.append("(SKIPPED)");
+ } else if (result.status == TestResult::TEST_UNKNOWN) {
+ status_line.append("(UNKNOWN)");
+ } else {
+ // Fail very loudly so it's not ignored.
+ CHECK(false) << "Unhandled test result status: " << result.status;
+ }
+ fprintf(stdout, "%s\n", status_line.c_str());
+ fflush(stdout);
+
+ // We just printed a status line, reset the watchdog timer.
+ watchdog_timer_.Reset();
+
+ // Do not waste time on timeouts. We include tests with unknown results here
+ // because sometimes (e.g. hang in between unit tests) that's how a timeout
+ // gets reported.
+ if (result.status == TestResult::TEST_TIMEOUT ||
+ result.status == TestResult::TEST_UNKNOWN) {
+ test_broken_count_++;
+ }
+ size_t broken_threshold =
+ std::max(static_cast<size_t>(20), test_started_count_ / 10);
+ if (test_broken_count_ >= broken_threshold) {
+ fprintf(stdout, "Too many badly broken tests (%" PRIuS "), exiting now.\n",
+ test_broken_count_);
+ fflush(stdout);
+
+#if defined(OS_POSIX)
+ KillSpawnedTestProcesses();
+#endif // defined(OS_POSIX)
+
+ results_tracker_.AddGlobalTag("BROKEN_TEST_EARLY_EXIT");
+ results_tracker_.AddGlobalTag(kUnreliableResultsTag);
+ MaybeSaveSummaryAsJSON();
+
+ exit(1);
+ }
+
+ if (test_finished_count_ != test_started_count_)
+ return;
+
+ if (tests_to_retry_.empty() || retry_count_ >= retry_limit_) {
+ OnTestIterationFinished();
+ return;
+ }
+
+ if (tests_to_retry_.size() >= broken_threshold) {
+ fprintf(stdout,
+ "Too many failing tests (%" PRIuS "), skipping retries.\n",
+ tests_to_retry_.size());
+ fflush(stdout);
+
+ results_tracker_.AddGlobalTag("BROKEN_TEST_SKIPPED_RETRIES");
+ results_tracker_.AddGlobalTag(kUnreliableResultsTag);
+
+ OnTestIterationFinished();
+ return;
+ }
+
+ retry_count_++;
+
+ std::vector<std::string> test_names(tests_to_retry_.begin(),
+ tests_to_retry_.end());
+
+ tests_to_retry_.clear();
+
+ size_t retry_started_count = launcher_delegate_->RetryTests(this, test_names);
+ if (retry_started_count == 0) {
+ // Signal failure, but continue to run all requested test iterations.
+ // With the summary of all iterations at the end this is a good default.
+ run_result_ = false;
+
+ OnTestIterationFinished();
+ return;
+ }
+
+ fprintf(stdout, "Retrying %" PRIuS " test%s (retry #%" PRIuS ")\n",
+ retry_started_count,
+ retry_started_count > 1 ? "s" : "",
+ retry_count_);
+ fflush(stdout);
+
+ test_started_count_ += retry_started_count;
+}
+
+// static
+std::string TestLauncher::FormatFullTestName(const std::string& test_case_name,
+ const std::string& test_name) {
+ return test_case_name + "." + test_name;
+}
+
+bool TestLauncher::Init() {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+
+ // Initialize sharding. Command line takes precedence over legacy environment
+ // variables.
+ if (command_line->HasSwitch(switches::kTestLauncherTotalShards) &&
+ command_line->HasSwitch(switches::kTestLauncherShardIndex)) {
+ if (!StringToInt(
+ command_line->GetSwitchValueASCII(
+ switches::kTestLauncherTotalShards),
+ &total_shards_)) {
+ LOG(ERROR) << "Invalid value for " << switches::kTestLauncherTotalShards;
+ return false;
+ }
+ if (!StringToInt(
+ command_line->GetSwitchValueASCII(
+ switches::kTestLauncherShardIndex),
+ &shard_index_)) {
+ LOG(ERROR) << "Invalid value for " << switches::kTestLauncherShardIndex;
+ return false;
+ }
+ fprintf(stdout,
+ "Using sharding settings from command line. This is shard %d/%d\n",
+ shard_index_, total_shards_);
+ fflush(stdout);
+ } else {
+ if (!TakeInt32FromEnvironment(kTestTotalShards, &total_shards_))
+ return false;
+ if (!TakeInt32FromEnvironment(kTestShardIndex, &shard_index_))
+ return false;
+ fprintf(stdout,
+ "Using sharding settings from environment. This is shard %d/%d\n",
+ shard_index_, total_shards_);
+ fflush(stdout);
+ }
+ if (shard_index_ < 0 ||
+ total_shards_ < 0 ||
+ shard_index_ >= total_shards_) {
+ LOG(ERROR) << "Invalid sharding settings: we require 0 <= "
+ << kTestShardIndex << " < " << kTestTotalShards
+ << ", but you have " << kTestShardIndex << "=" << shard_index_
+ << ", " << kTestTotalShards << "=" << total_shards_ << ".\n";
+ return false;
+ }
+
+ // Make sure we don't pass any sharding-related environment to the child
+ // processes. This test launcher implements the sharding completely.
+ CHECK(UnsetEnvironmentVariableIfExists("GTEST_TOTAL_SHARDS"));
+ CHECK(UnsetEnvironmentVariableIfExists("GTEST_SHARD_INDEX"));
+
+ if (command_line->HasSwitch(kGTestRepeatFlag) &&
+ !StringToInt(command_line->GetSwitchValueASCII(kGTestRepeatFlag),
+ &cycles_)) {
+ LOG(ERROR) << "Invalid value for " << kGTestRepeatFlag;
+ return false;
+ }
+
+ if (command_line->HasSwitch(switches::kTestLauncherRetryLimit)) {
+ int retry_limit = -1;
+ if (!StringToInt(command_line->GetSwitchValueASCII(
+ switches::kTestLauncherRetryLimit), &retry_limit) ||
+ retry_limit < 0) {
+ LOG(ERROR) << "Invalid value for " << switches::kTestLauncherRetryLimit;
+ return false;
+ }
+
+ retry_limit_ = retry_limit;
+ } else if (!command_line->HasSwitch(kGTestFilterFlag) || BotModeEnabled()) {
+ // Retry failures 3 times by default if we are running all of the tests or
+ // in bot mode.
+ retry_limit_ = 3;
+ }
+
+ if (command_line->HasSwitch(switches::kTestLauncherJobs)) {
+ int jobs = -1;
+ if (!StringToInt(command_line->GetSwitchValueASCII(
+ switches::kTestLauncherJobs), &jobs) ||
+ jobs < 0) {
+ LOG(ERROR) << "Invalid value for " << switches::kTestLauncherJobs;
+ return false;
+ }
+
+ parallel_jobs_ = jobs;
+ } else if (command_line->HasSwitch(kGTestFilterFlag) && !BotModeEnabled()) {
+ // Do not run jobs in parallel by default if we are running a subset of
+ // the tests and if bot mode is off.
+ parallel_jobs_ = 1;
+ }
+
+ fprintf(stdout, "Using %" PRIuS " parallel jobs.\n", parallel_jobs_);
+ fflush(stdout);
+ worker_pool_owner_.reset(
+ new SequencedWorkerPoolOwner(parallel_jobs_, "test_launcher"));
+
+ if (command_line->HasSwitch(switches::kTestLauncherFilterFile) &&
+ command_line->HasSwitch(kGTestFilterFlag)) {
+ LOG(ERROR) << "Only one of --test-launcher-filter-file and --gtest_filter "
+ << "at a time is allowed.";
+ return false;
+ }
+
+ if (command_line->HasSwitch(switches::kTestLauncherFilterFile)) {
+ std::string filter;
+ if (!ReadFileToString(
+ command_line->GetSwitchValuePath(switches::kTestLauncherFilterFile),
+ &filter)) {
+ LOG(ERROR) << "Failed to read the filter file.";
+ return false;
+ }
+
+ std::vector<std::string> filter_lines;
+ SplitString(filter, '\n', &filter_lines);
+ for (size_t i = 0; i < filter_lines.size(); i++) {
+ if (filter_lines[i].empty())
+ continue;
+
+ if (filter_lines[i][0] == '-')
+ negative_test_filter_.push_back(filter_lines[i].substr(1));
+ else
+ positive_test_filter_.push_back(filter_lines[i]);
+ }
+ } else {
+ // Split --gtest_filter at '-', if there is one, to separate into
+ // positive filter and negative filter portions.
+ std::string filter = command_line->GetSwitchValueASCII(kGTestFilterFlag);
+ size_t dash_pos = filter.find('-');
+ if (dash_pos == std::string::npos) {
+ SplitString(filter, ':', &positive_test_filter_);
+ } else {
+ // Everything up to the dash.
+ SplitString(filter.substr(0, dash_pos), ':', &positive_test_filter_);
+
+ // Everything after the dash.
+ SplitString(filter.substr(dash_pos + 1), ':', &negative_test_filter_);
+ }
+ }
+
+ if (!results_tracker_.Init(*command_line)) {
+ LOG(ERROR) << "Failed to initialize test results tracker.";
+ return 1;
+ }
+
+#if defined(NDEBUG)
+ results_tracker_.AddGlobalTag("MODE_RELEASE");
+#else
+ results_tracker_.AddGlobalTag("MODE_DEBUG");
+#endif
+
+ // Operating systems (sorted alphabetically).
+ // Note that they can deliberately overlap, e.g. OS_LINUX is a subset
+ // of OS_POSIX.
+#if defined(OS_ANDROID)
+ results_tracker_.AddGlobalTag("OS_ANDROID");
+#endif
+
+#if defined(OS_BSD)
+ results_tracker_.AddGlobalTag("OS_BSD");
+#endif
+
+#if defined(OS_FREEBSD)
+ results_tracker_.AddGlobalTag("OS_FREEBSD");
+#endif
+
+#if defined(OS_IOS)
+ results_tracker_.AddGlobalTag("OS_IOS");
+#endif
+
+#if defined(OS_LINUX)
+ results_tracker_.AddGlobalTag("OS_LINUX");
+#endif
+
+#if defined(OS_MACOSX)
+ results_tracker_.AddGlobalTag("OS_MACOSX");
+#endif
+
+#if defined(OS_NACL)
+ results_tracker_.AddGlobalTag("OS_NACL");
+#endif
+
+#if defined(OS_OPENBSD)
+ results_tracker_.AddGlobalTag("OS_OPENBSD");
+#endif
+
+#if defined(OS_POSIX)
+ results_tracker_.AddGlobalTag("OS_POSIX");
+#endif
+
+#if defined(OS_SOLARIS)
+ results_tracker_.AddGlobalTag("OS_SOLARIS");
+#endif
+
+#if defined(OS_WIN)
+ results_tracker_.AddGlobalTag("OS_WIN");
+#endif
+
+ // CPU-related tags.
+#if defined(ARCH_CPU_32_BITS)
+ results_tracker_.AddGlobalTag("CPU_32_BITS");
+#endif
+
+#if defined(ARCH_CPU_64_BITS)
+ results_tracker_.AddGlobalTag("CPU_64_BITS");
+#endif
+
+ return true;
+}
+
+void TestLauncher::RunTests() {
+ testing::UnitTest* const unit_test = testing::UnitTest::GetInstance();
+
+ std::vector<std::string> test_names;
+
+ 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);
+ std::string test_name = FormatFullTestName(
+ test_info->test_case_name(), test_info->name());
+
+ results_tracker_.AddTest(test_name);
+
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (test_name.find("DISABLED") != std::string::npos) {
+ results_tracker_.AddDisabledTest(test_name);
+
+ // Skip disabled tests unless explicitly requested.
+ if (!command_line->HasSwitch(kGTestRunDisabledTestsFlag))
+ continue;
+ }
+
+ if (!launcher_delegate_->ShouldRunTest(test_case, test_info))
+ continue;
+
+ // Skip the test that doesn't match the filter (if given).
+ if (!positive_test_filter_.empty()) {
+ bool found = false;
+ for (size_t k = 0; k < positive_test_filter_.size(); ++k) {
+ if (MatchPattern(test_name, positive_test_filter_[k])) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ continue;
+ }
+ bool excluded = false;
+ for (size_t k = 0; k < negative_test_filter_.size(); ++k) {
+ if (MatchPattern(test_name, negative_test_filter_[k])) {
+ excluded = true;
+ break;
+ }
+ }
+ if (excluded)
+ continue;
+
+ if (base::Hash(test_name) % total_shards_ !=
+ static_cast<uint32>(shard_index_)) {
+ continue;
+ }
+
+ test_names.push_back(test_name);
+ }
+ }
+
+ test_started_count_ = launcher_delegate_->RunTests(this, test_names);
+
+ if (test_started_count_ == 0) {
+ fprintf(stdout, "0 tests run\n");
+ fflush(stdout);
+
+ // No tests have actually been started, so kick off the next iteration.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&TestLauncher::RunTestIteration, Unretained(this)));
+ }
+}
+
+void TestLauncher::RunTestIteration() {
+ if (cycles_ == 0) {
+ MessageLoop::current()->Quit();
+ return;
+ }
+
+ // Special value "-1" means "repeat indefinitely".
+ cycles_ = (cycles_ == -1) ? cycles_ : cycles_ - 1;
+
+ test_started_count_ = 0;
+ test_finished_count_ = 0;
+ test_success_count_ = 0;
+ test_broken_count_ = 0;
+ retry_count_ = 0;
+ tests_to_retry_.clear();
+ results_tracker_.OnTestIterationStarting();
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&TestLauncher::RunTests, Unretained(this)));
+}
+
+void TestLauncher::MaybeSaveSummaryAsJSON() {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kTestLauncherSummaryOutput)) {
+ FilePath summary_path(command_line->GetSwitchValuePath(
+ switches::kTestLauncherSummaryOutput));
+ if (!results_tracker_.SaveSummaryAsJSON(summary_path)) {
+ LOG(ERROR) << "Failed to save test launcher output summary.";
+ }
+ }
+}
+
+void TestLauncher::OnLaunchTestProcessFinished(
+ const LaunchChildGTestProcessCallback& callback,
+ int exit_code,
+ const TimeDelta& elapsed_time,
+ bool was_timeout,
+ const std::string& output) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ callback.Run(exit_code, elapsed_time, was_timeout, output);
+}
+
+void TestLauncher::OnTestIterationFinished() {
+ TestResultsTracker::TestStatusMap tests_by_status(
+ results_tracker_.GetTestStatusMapForCurrentIteration());
+ if (!tests_by_status[TestResult::TEST_UNKNOWN].empty())
+ results_tracker_.AddGlobalTag(kUnreliableResultsTag);
+
+ // When we retry tests, success is determined by having nothing more
+ // to retry (everything eventually passed), as opposed to having
+ // no failures at all.
+ if (tests_to_retry_.empty()) {
+ fprintf(stdout, "SUCCESS: all tests passed.\n");
+ fflush(stdout);
+ } else {
+ // Signal failure, but continue to run all requested test iterations.
+ // With the summary of all iterations at the end this is a good default.
+ run_result_ = false;
+ }
+
+ results_tracker_.PrintSummaryOfCurrentIteration();
+
+ // Kick off the next iteration.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&TestLauncher::RunTestIteration, Unretained(this)));
+}
+
+void TestLauncher::OnOutputTimeout() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ AutoLock lock(g_live_processes_lock.Get());
+
+ fprintf(stdout, "Still waiting for the following processes to finish:\n");
+
+ for (std::map<ProcessHandle, CommandLine>::iterator i =
+ g_live_processes.Get().begin();
+ i != g_live_processes.Get().end();
+ ++i) {
+#if defined(OS_WIN)
+ fwprintf(stdout, L"\t%s\n", i->second.GetCommandLineString().c_str());
+#else
+ fprintf(stdout, "\t%s\n", i->second.GetCommandLineString().c_str());
+#endif
+ }
+
+ fflush(stdout);
+
+ // Arm the timer again - otherwise it would fire only once.
+ watchdog_timer_.Reset();
+}
+
+std::string GetTestOutputSnippet(const TestResult& result,
+ const std::string& full_output) {
+ size_t run_pos = full_output.find(std::string("[ RUN ] ") +
+ result.full_name);
+ if (run_pos == std::string::npos)
+ return std::string();
+
+ size_t end_pos = full_output.find(std::string("[ FAILED ] ") +
+ result.full_name,
+ run_pos);
+ // Only clip the snippet to the "OK" message if the test really
+ // succeeded. It still might have e.g. crashed after printing it.
+ if (end_pos == std::string::npos &&
+ result.status == TestResult::TEST_SUCCESS) {
+ end_pos = full_output.find(std::string("[ OK ] ") +
+ result.full_name,
+ run_pos);
+ }
+ if (end_pos != std::string::npos) {
+ size_t newline_pos = full_output.find("\n", end_pos);
+ if (newline_pos != std::string::npos)
+ end_pos = newline_pos + 1;
+ }
+
+ std::string snippet(full_output.substr(run_pos));
+ if (end_pos != std::string::npos)
+ snippet = full_output.substr(run_pos, end_pos - run_pos);
+
+ return snippet;
+}
+
+} // namespace base
diff --git a/base/test/launcher/test_launcher.h b/base/test/launcher/test_launcher.h
new file mode 100644
index 0000000..8c72ee7
--- /dev/null
+++ b/base/test/launcher/test_launcher.h
@@ -0,0 +1,206 @@
+// 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_LAUNCHER_TEST_LAUNCHER_H_
+#define BASE_TEST_LAUNCHER_TEST_LAUNCHER_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/test/launcher/test_result.h"
+#include "base/test/launcher/test_results_tracker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace testing {
+class TestCase;
+class TestInfo;
+}
+
+namespace base {
+
+class CommandLine;
+struct LaunchOptions;
+class SequencedWorkerPoolOwner;
+class TestLauncher;
+
+// Constants for GTest command-line flags.
+extern const char kGTestFilterFlag[];
+extern const char kGTestHelpFlag[];
+extern const char kGTestListTestsFlag[];
+extern const char kGTestRepeatFlag[];
+extern const char kGTestRunDisabledTestsFlag[];
+extern const char kGTestOutputFlag[];
+
+// Interface for use with LaunchTests that abstracts away exact details
+// which tests and how are run.
+class TestLauncherDelegate {
+ public:
+ // Called before a test is considered for running. If it returns false,
+ // the test is not run. If it returns true, the test will be run provided
+ // it is part of the current shard.
+ virtual bool ShouldRunTest(const testing::TestCase* test_case,
+ const testing::TestInfo* test_info) = 0;
+
+ // Called to make the delegate run the specified tests. The delegate must
+ // return the number of actual tests it's going to run (can be smaller,
+ // equal to, or larger than size of |test_names|). It must also call
+ // |test_launcher|'s OnTestFinished method once per every run test,
+ // regardless of its success.
+ virtual size_t RunTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) = 0;
+
+ // Called to make the delegate retry the specified tests. The delegate must
+ // return the number of actual tests it's going to retry (can be smaller,
+ // equal to, or larger than size of |test_names|). It must also call
+ // |test_launcher|'s OnTestFinished method once per every retried test,
+ // regardless of its success.
+ virtual size_t RetryTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) = 0;
+
+ protected:
+ virtual ~TestLauncherDelegate();
+};
+
+// Launches tests using a TestLauncherDelegate.
+class TestLauncher {
+ public:
+ // Flags controlling behavior of LaunchChildGTestProcess.
+ enum LaunchChildGTestProcessFlags {
+ // Allows usage of job objects on Windows. Helps properly clean up child
+ // processes.
+ USE_JOB_OBJECTS = (1 << 0),
+
+ // Allows breakaway from job on Windows. May result in some child processes
+ // not being properly terminated after launcher dies if these processes
+ // fail to cooperate.
+ ALLOW_BREAKAWAY_FROM_JOB = (1 << 1),
+ };
+
+ // Constructor. |parallel_jobs| is the limit of simultaneous parallel test
+ // jobs.
+ TestLauncher(TestLauncherDelegate* launcher_delegate, size_t parallel_jobs);
+ ~TestLauncher();
+
+ // Runs the launcher. Must be called at most once.
+ bool Run() WARN_UNUSED_RESULT;
+
+ // Callback called after a child process finishes. First argument is the exit
+ // code, second one is child process elapsed time, third one is true if
+ // the child process was terminated because of a timeout, and fourth one
+ // contains output of the child (stdout and stderr together).
+ typedef Callback<void(int, const TimeDelta&, bool, const std::string&)>
+ LaunchChildGTestProcessCallback;
+
+ // Launches a child process (assumed to be gtest-based binary) using
+ // |command_line|. If |wrapper| is not empty, it is prepended to the final
+ // command line. If the child process is still running after |timeout|, it
+ // is terminated. After the child process finishes |callback| is called
+ // on the same thread this method was called.
+ void LaunchChildGTestProcess(const CommandLine& command_line,
+ const std::string& wrapper,
+ base::TimeDelta timeout,
+ int flags,
+ const LaunchChildGTestProcessCallback& callback);
+
+ // Called when a test has finished running.
+ void OnTestFinished(const TestResult& result);
+
+ // Constructs a full test name given a test case name and a test name.
+ static std::string FormatFullTestName(const std::string& test_case_name,
+ const std::string& test_name);
+
+ private:
+ bool Init() WARN_UNUSED_RESULT;
+
+ // Runs all tests in current iteration. Uses callbacks to communicate success.
+ void RunTests();
+
+ void RunTestIteration();
+
+ // Saves test results summary as JSON if requested from command line.
+ void MaybeSaveSummaryAsJSON();
+
+ // Called on a worker thread after a child process finishes.
+ void OnLaunchTestProcessFinished(
+ const LaunchChildGTestProcessCallback& callback,
+ int exit_code,
+ const TimeDelta& elapsed_time,
+ bool was_timeout,
+ const std::string& output);
+
+ // Called when a test iteration is finished.
+ void OnTestIterationFinished();
+
+ // Called by the delay timer when no output was made for a while.
+ void OnOutputTimeout();
+
+ // Make sure we don't accidentally call the wrong methods e.g. on the worker
+ // pool thread. With lots of callbacks used this is non-trivial.
+ // Should be the first member so that it's destroyed last: when destroying
+ // other members, especially the worker pool, we may check the code is running
+ // on the correct thread.
+ ThreadChecker thread_checker_;
+
+ TestLauncherDelegate* launcher_delegate_;
+
+ // Support for outer sharding, just like gtest does.
+ int32 total_shards_; // Total number of outer shards, at least one.
+ int32 shard_index_; // Index of shard the launcher is to run.
+
+ int cycles_; // Number of remaining test itreations, or -1 for infinite.
+
+ // Test filters (empty means no filter).
+ std::vector<std::string> positive_test_filter_;
+ std::vector<std::string> negative_test_filter_;
+
+ // Number of tests started in this iteration.
+ size_t test_started_count_;
+
+ // Number of tests finished in this iteration.
+ size_t test_finished_count_;
+
+ // Number of tests successfully finished in this iteration.
+ size_t test_success_count_;
+
+ // Number of tests either timing out or having an unknown result,
+ // likely indicating a more systemic problem if widespread.
+ size_t test_broken_count_;
+
+ // Number of retries in this iteration.
+ size_t retry_count_;
+
+ // Maximum number of retries per iteration.
+ size_t retry_limit_;
+
+ // Tests to retry in this iteration.
+ std::set<std::string> tests_to_retry_;
+
+ // Result to be returned from Run.
+ bool run_result_;
+
+ TestResultsTracker results_tracker_;
+
+ // Watchdog timer to make sure we do not go without output for too long.
+ DelayTimer<TestLauncher> watchdog_timer_;
+
+ // Number of jobs to run in parallel.
+ size_t parallel_jobs_;
+
+ // Worker pool used to launch processes in parallel.
+ scoped_ptr<SequencedWorkerPoolOwner> worker_pool_owner_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLauncher);
+};
+
+// Extract part from |full_output| that applies to |result|.
+std::string GetTestOutputSnippet(const TestResult& result,
+ const std::string& full_output);
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_TEST_LAUNCHER_H_
diff --git a/base/test/launcher/test_result.cc b/base/test/launcher/test_result.cc
new file mode 100644
index 0000000..70d7a80
--- /dev/null
+++ b/base/test/launcher/test_result.cc
@@ -0,0 +1,52 @@
+// 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/launcher/test_result.h"
+
+#include "base/logging.h"
+
+namespace base {
+
+TestResult::TestResult() : status(TEST_UNKNOWN) {
+}
+
+TestResult::~TestResult() {
+}
+
+std::string TestResult::StatusAsString() const {
+ switch (status) {
+ case TEST_UNKNOWN:
+ return "UNKNOWN";
+ case TEST_SUCCESS:
+ return "SUCCESS";
+ case TEST_FAILURE:
+ return "FAILURE";
+ case TEST_FAILURE_ON_EXIT:
+ return "FAILURE_ON_EXIT";
+ case TEST_CRASH:
+ return "CRASH";
+ case TEST_TIMEOUT:
+ return "TIMEOUT";
+ case TEST_SKIPPED:
+ return "SKIPPED";
+ // Rely on compiler warnings to ensure all possible values are handled.
+ }
+
+ NOTREACHED();
+ return std::string();
+}
+
+std::string TestResult::GetTestName() const {
+ size_t dot_pos = full_name.find('.');
+ CHECK_NE(dot_pos, std::string::npos);
+ return full_name.substr(dot_pos + 1);
+}
+
+std::string TestResult::GetTestCaseName() const {
+ size_t dot_pos = full_name.find('.');
+ CHECK_NE(dot_pos, std::string::npos);
+ return full_name.substr(0, dot_pos);
+}
+
+} // namespace base
diff --git a/base/test/launcher/test_result.h b/base/test/launcher/test_result.h
new file mode 100644
index 0000000..b61cdd4
--- /dev/null
+++ b/base/test/launcher/test_result.h
@@ -0,0 +1,61 @@
+// 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_LAUNCHER_TEST_RESULT_H_
+#define BASE_TEST_LAUNCHER_TEST_RESULT_H_
+
+#include <string>
+
+#include "base/time/time.h"
+
+namespace base {
+
+// Structure containing result of a single test.
+struct TestResult {
+ enum Status {
+ TEST_UNKNOWN, // Status not set.
+ TEST_SUCCESS, // Test passed.
+ TEST_FAILURE, // Assertion failure (think EXPECT_TRUE, not DCHECK).
+ TEST_FAILURE_ON_EXIT, // Test passed but executable exit code was non-zero.
+ TEST_TIMEOUT, // Test timed out and was killed.
+ TEST_CRASH, // Test crashed (includes CHECK/DCHECK failures).
+ TEST_SKIPPED, // Test skipped (not run at all).
+ };
+
+ TestResult();
+ ~TestResult();
+
+ // Returns the test status as string (e.g. for display).
+ std::string StatusAsString() const;
+
+ // Returns the test name (e.g. "B" for "A.B").
+ std::string GetTestName() const;
+
+ // Returns the test case name (e.g. "A" for "A.B").
+ std::string GetTestCaseName() const;
+
+ // Returns true if the test has completed (i.e. the test binary exited
+ // normally, possibly with an exit code indicating failure, but didn't crash
+ // or time out in the middle of the test).
+ bool completed() const {
+ return status == TEST_SUCCESS ||
+ status == TEST_FAILURE ||
+ status == TEST_FAILURE_ON_EXIT;
+ }
+
+ // Full name of the test (e.g. "A.B").
+ std::string full_name;
+
+ Status status;
+
+ // Time it took to run the test.
+ base::TimeDelta elapsed_time;
+
+ // Output of just this test (optional).
+ std::string output_snippet;
+};
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_TEST_RESULT_H_
diff --git a/base/test/launcher/test_results_tracker.cc b/base/test/launcher/test_results_tracker.cc
new file mode 100644
index 0000000..b553fd6
--- /dev/null
+++ b/base/test/launcher/test_results_tracker.cc
@@ -0,0 +1,360 @@
+// 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/launcher/test_results_tracker.h"
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/string_escape.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/launcher/test_launcher.h"
+#include "base/values.h"
+
+namespace base {
+
+namespace {
+
+// The default output file for XML output.
+const FilePath::CharType kDefaultOutputFile[] = FILE_PATH_LITERAL(
+ "test_detail.xml");
+
+// Utility function to print a list of test names. Uses iterator to be
+// compatible with different containers, like vector and set.
+template<typename InputIterator>
+void PrintTests(InputIterator first,
+ InputIterator last,
+ const std::string& description) {
+ size_t count = std::distance(first, last);
+ if (count == 0)
+ return;
+
+ fprintf(stdout,
+ "%" PRIuS " test%s %s:\n",
+ count,
+ count != 1 ? "s" : "",
+ description.c_str());
+ for (InputIterator i = first; i != last; ++i)
+ fprintf(stdout, " %s\n", (*i).c_str());
+ fflush(stdout);
+}
+
+std::string TestNameWithoutDisabledPrefix(const std::string& test_name) {
+ std::string test_name_no_disabled(test_name);
+ ReplaceSubstringsAfterOffset(&test_name_no_disabled, 0, "DISABLED_", "");
+ return test_name_no_disabled;
+}
+
+} // namespace
+
+TestResultsTracker::TestResultsTracker() : iteration_(-1), out_(NULL) {
+}
+
+TestResultsTracker::~TestResultsTracker() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!out_)
+ return;
+ fprintf(out_, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ fprintf(out_, "<testsuites name=\"AllTests\" tests=\"\" failures=\"\""
+ " disabled=\"\" errors=\"\" time=\"\">\n");
+
+ // Maps test case names to test results.
+ typedef std::map<std::string, std::vector<TestResult> > TestCaseMap;
+ TestCaseMap test_case_map;
+
+ for (PerIterationData::ResultsMap::iterator i =
+ per_iteration_data_[iteration_].results.begin();
+ i != per_iteration_data_[iteration_].results.end();
+ ++i) {
+ // Use the last test result as the final one.
+ TestResult result = i->second.test_results.back();
+ test_case_map[result.GetTestCaseName()].push_back(result);
+ }
+ for (TestCaseMap::iterator i = test_case_map.begin();
+ i != test_case_map.end();
+ ++i) {
+ fprintf(out_, " <testsuite name=\"%s\" tests=\"%" PRIuS "\" failures=\"\""
+ " disabled=\"\" errors=\"\" time=\"\">\n",
+ i->first.c_str(), i->second.size());
+ for (size_t j = 0; j < i->second.size(); ++j) {
+ const TestResult& result = i->second[j];
+ fprintf(out_, " <testcase name=\"%s\" status=\"run\" time=\"%.3f\""
+ " classname=\"%s\">\n",
+ result.GetTestName().c_str(),
+ result.elapsed_time.InSecondsF(),
+ result.GetTestCaseName().c_str());
+ if (result.status != TestResult::TEST_SUCCESS)
+ fprintf(out_, " <failure message=\"\" type=\"\"></failure>\n");
+ fprintf(out_, " </testcase>\n");
+ }
+ fprintf(out_, " </testsuite>\n");
+ }
+ fprintf(out_, "</testsuites>\n");
+ fclose(out_);
+}
+
+bool TestResultsTracker::Init(const CommandLine& command_line) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Prevent initializing twice.
+ if (out_) {
+ NOTREACHED();
+ return false;
+ }
+
+ if (!command_line.HasSwitch(kGTestOutputFlag))
+ return true;
+
+ std::string flag = command_line.GetSwitchValueASCII(kGTestOutputFlag);
+ size_t colon_pos = flag.find(':');
+ FilePath path;
+ if (colon_pos != std::string::npos) {
+ FilePath flag_path =
+ command_line.GetSwitchValuePath(kGTestOutputFlag);
+ FilePath::StringType path_string = flag_path.value();
+ path = FilePath(path_string.substr(colon_pos + 1));
+ // If the given path ends with '/', consider it is a directory.
+ // Note: This does NOT check that a directory (or file) actually exists
+ // (the behavior is same as what gtest does).
+ if (path.EndsWithSeparator()) {
+ FilePath executable = command_line.GetProgram().BaseName();
+ path = path.Append(executable.ReplaceExtension(
+ FilePath::StringType(FILE_PATH_LITERAL("xml"))));
+ }
+ }
+ if (path.value().empty())
+ path = FilePath(kDefaultOutputFile);
+ FilePath dir_name = path.DirName();
+ if (!DirectoryExists(dir_name)) {
+ LOG(WARNING) << "The output directory does not exist. "
+ << "Creating the directory: " << dir_name.value();
+ // Create the directory if necessary (because the gtest does the same).
+ if (!base::CreateDirectory(dir_name)) {
+ LOG(ERROR) << "Failed to created directory " << dir_name.value();
+ return false;
+ }
+ }
+ out_ = OpenFile(path, "w");
+ if (!out_) {
+ LOG(ERROR) << "Cannot open output file: "
+ << path.value() << ".";
+ return false;
+ }
+
+ return true;
+}
+
+void TestResultsTracker::OnTestIterationStarting() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Start with a fresh state for new iteration.
+ iteration_++;
+ per_iteration_data_.push_back(PerIterationData());
+}
+
+void TestResultsTracker::AddTest(const std::string& test_name) {
+ // Record disabled test names without DISABLED_ prefix so that they are easy
+ // to compare with regular test names, e.g. before or after disabling.
+ all_tests_.insert(TestNameWithoutDisabledPrefix(test_name));
+}
+
+void TestResultsTracker::AddDisabledTest(const std::string& test_name) {
+ // Record disabled test names without DISABLED_ prefix so that they are easy
+ // to compare with regular test names, e.g. before or after disabling.
+ disabled_tests_.insert(TestNameWithoutDisabledPrefix(test_name));
+}
+
+void TestResultsTracker::AddTestResult(const TestResult& result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ per_iteration_data_[iteration_].results[
+ result.full_name].test_results.push_back(result);
+}
+
+void TestResultsTracker::PrintSummaryOfCurrentIteration() const {
+ TestStatusMap tests_by_status(GetTestStatusMapForCurrentIteration());
+
+ PrintTests(tests_by_status[TestResult::TEST_FAILURE].begin(),
+ tests_by_status[TestResult::TEST_FAILURE].end(),
+ "failed");
+ PrintTests(tests_by_status[TestResult::TEST_FAILURE_ON_EXIT].begin(),
+ tests_by_status[TestResult::TEST_FAILURE_ON_EXIT].end(),
+ "failed on exit");
+ PrintTests(tests_by_status[TestResult::TEST_TIMEOUT].begin(),
+ tests_by_status[TestResult::TEST_TIMEOUT].end(),
+ "timed out");
+ PrintTests(tests_by_status[TestResult::TEST_CRASH].begin(),
+ tests_by_status[TestResult::TEST_CRASH].end(),
+ "crashed");
+ PrintTests(tests_by_status[TestResult::TEST_SKIPPED].begin(),
+ tests_by_status[TestResult::TEST_SKIPPED].end(),
+ "skipped");
+ PrintTests(tests_by_status[TestResult::TEST_UNKNOWN].begin(),
+ tests_by_status[TestResult::TEST_UNKNOWN].end(),
+ "had unknown result");
+}
+
+void TestResultsTracker::PrintSummaryOfAllIterations() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ TestStatusMap tests_by_status(GetTestStatusMapForAllIterations());
+
+ fprintf(stdout, "Summary of all test iterations:\n");
+ fflush(stdout);
+
+ PrintTests(tests_by_status[TestResult::TEST_FAILURE].begin(),
+ tests_by_status[TestResult::TEST_FAILURE].end(),
+ "failed");
+ PrintTests(tests_by_status[TestResult::TEST_FAILURE_ON_EXIT].begin(),
+ tests_by_status[TestResult::TEST_FAILURE_ON_EXIT].end(),
+ "failed on exit");
+ PrintTests(tests_by_status[TestResult::TEST_TIMEOUT].begin(),
+ tests_by_status[TestResult::TEST_TIMEOUT].end(),
+ "timed out");
+ PrintTests(tests_by_status[TestResult::TEST_CRASH].begin(),
+ tests_by_status[TestResult::TEST_CRASH].end(),
+ "crashed");
+ PrintTests(tests_by_status[TestResult::TEST_SKIPPED].begin(),
+ tests_by_status[TestResult::TEST_SKIPPED].end(),
+ "skipped");
+ PrintTests(tests_by_status[TestResult::TEST_UNKNOWN].begin(),
+ tests_by_status[TestResult::TEST_UNKNOWN].end(),
+ "had unknown result");
+
+ fprintf(stdout, "End of the summary.\n");
+ fflush(stdout);
+}
+
+void TestResultsTracker::AddGlobalTag(const std::string& tag) {
+ global_tags_.insert(tag);
+}
+
+bool TestResultsTracker::SaveSummaryAsJSON(const FilePath& path) const {
+ scoped_ptr<DictionaryValue> summary_root(new DictionaryValue);
+
+ ListValue* global_tags = new ListValue;
+ summary_root->Set("global_tags", global_tags);
+
+ for (std::set<std::string>::const_iterator i = global_tags_.begin();
+ i != global_tags_.end();
+ ++i) {
+ global_tags->AppendString(*i);
+ }
+
+ ListValue* all_tests = new ListValue;
+ summary_root->Set("all_tests", all_tests);
+
+ for (std::set<std::string>::const_iterator i = all_tests_.begin();
+ i != all_tests_.end();
+ ++i) {
+ all_tests->AppendString(*i);
+ }
+
+ ListValue* disabled_tests = new ListValue;
+ summary_root->Set("disabled_tests", disabled_tests);
+
+ for (std::set<std::string>::const_iterator i = disabled_tests_.begin();
+ i != disabled_tests_.end();
+ ++i) {
+ disabled_tests->AppendString(*i);
+ }
+
+ ListValue* per_iteration_data = new ListValue;
+ summary_root->Set("per_iteration_data", per_iteration_data);
+
+ for (int i = 0; i <= iteration_; i++) {
+ DictionaryValue* current_iteration_data = new DictionaryValue;
+ per_iteration_data->Append(current_iteration_data);
+
+ for (PerIterationData::ResultsMap::const_iterator j =
+ per_iteration_data_[i].results.begin();
+ j != per_iteration_data_[i].results.end();
+ ++j) {
+ ListValue* test_results = new ListValue;
+ current_iteration_data->SetWithoutPathExpansion(j->first, test_results);
+
+ for (size_t k = 0; k < j->second.test_results.size(); k++) {
+ const TestResult& test_result = j->second.test_results[k];
+
+ DictionaryValue* test_result_value = new DictionaryValue;
+ test_results->Append(test_result_value);
+
+ test_result_value->SetString("status", test_result.StatusAsString());
+ test_result_value->SetInteger(
+ "elapsed_time_ms",
+ static_cast<int>(test_result.elapsed_time.InMilliseconds()));
+
+ // There are no guarantees about character encoding of the output
+ // snippet. Escape it and record whether it was losless.
+ // It's useful to have the output snippet as string in the summary
+ // for easy viewing.
+ std::string escaped_output_snippet;
+ bool losless_snippet = EscapeJSONString(
+ test_result.output_snippet, false, &escaped_output_snippet);
+ test_result_value->SetString("output_snippet",
+ escaped_output_snippet);
+ test_result_value->SetBoolean("losless_snippet", losless_snippet);
+
+ // Also include the raw version (base64-encoded so that it can be safely
+ // JSON-serialized - there are no guarantees about character encoding
+ // of the snippet). This can be very useful piece of information when
+ // debugging a test failure related to character encoding.
+ std::string base64_output_snippet;
+ Base64Encode(test_result.output_snippet, &base64_output_snippet);
+ test_result_value->SetString("output_snippet_base64",
+ base64_output_snippet);
+ }
+ }
+ }
+
+ JSONFileValueSerializer serializer(path);
+ return serializer.Serialize(*summary_root);
+}
+
+TestResultsTracker::TestStatusMap
+ TestResultsTracker::GetTestStatusMapForCurrentIteration() const {
+ TestStatusMap tests_by_status;
+ GetTestStatusForIteration(iteration_, &tests_by_status);
+ return tests_by_status;
+}
+
+TestResultsTracker::TestStatusMap
+ TestResultsTracker::GetTestStatusMapForAllIterations() const {
+ TestStatusMap tests_by_status;
+ for (int i = 0; i <= iteration_; i++)
+ GetTestStatusForIteration(i, &tests_by_status);
+ return tests_by_status;
+}
+
+void TestResultsTracker::GetTestStatusForIteration(
+ int iteration, TestStatusMap* map) const {
+ for (PerIterationData::ResultsMap::const_iterator j =
+ per_iteration_data_[iteration].results.begin();
+ j != per_iteration_data_[iteration].results.end();
+ ++j) {
+ // Use the last test result as the final one.
+ const TestResult& result = j->second.test_results.back();
+ (*map)[result.status].insert(result.full_name);
+ }
+}
+
+TestResultsTracker::AggregateTestResult::AggregateTestResult() {
+}
+
+TestResultsTracker::AggregateTestResult::~AggregateTestResult() {
+}
+
+TestResultsTracker::PerIterationData::PerIterationData() {
+}
+
+TestResultsTracker::PerIterationData::~PerIterationData() {
+}
+
+} // namespace base
diff --git a/base/test/launcher/test_results_tracker.h b/base/test/launcher/test_results_tracker.h
new file mode 100644
index 0000000..2bddebc
--- /dev/null
+++ b/base/test/launcher/test_results_tracker.h
@@ -0,0 +1,123 @@
+// 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_LAUNCHER_TEST_RESULTS_TRACKER_H_
+#define BASE_TEST_LAUNCHER_TEST_RESULTS_TRACKER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/test/launcher/test_result.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+
+class CommandLine;
+class FilePath;
+
+// A helper class to output results.
+// Note: as currently XML is the only supported format by gtest, we don't
+// check output format (e.g. "xml:" prefix) here and output an XML file
+// unconditionally.
+// Note: we don't output per-test-case or total summary info like
+// total failed_test_count, disabled_test_count, elapsed_time and so on.
+// Only each test (testcase element in the XML) will have the correct
+// failed/disabled/elapsed_time information. Each test won't include
+// detailed failure messages either.
+class TestResultsTracker {
+ public:
+ TestResultsTracker();
+ ~TestResultsTracker();
+
+ // Initialize the result tracker. Must be called exactly once before
+ // calling any other methods. Returns true on success.
+ bool Init(const CommandLine& command_line) WARN_UNUSED_RESULT;
+
+ // Called when a test iteration is starting.
+ void OnTestIterationStarting();
+
+ // Adds |test_name| to the set of discovered tests (this includes all tests
+ // present in the executable, not necessarily run).
+ void AddTest(const std::string& test_name);
+
+ // Adds |test_name| to the set of disabled tests.
+ void AddDisabledTest(const std::string& test_name);
+
+ // Adds |result| to the stored test results.
+ void AddTestResult(const TestResult& result);
+
+ // Prints a summary of current test iteration to stdout.
+ void PrintSummaryOfCurrentIteration() const;
+
+ // Prints a summary of all test iterations (not just the last one) to stdout.
+ void PrintSummaryOfAllIterations() const;
+
+ // Adds a string tag to the JSON summary. This is intended to indicate
+ // conditions that affect the entire test run, as opposed to individual tests.
+ void AddGlobalTag(const std::string& tag);
+
+ // Saves a JSON summary of all test iterations results to |path|. Returns
+ // true on success.
+ bool SaveSummaryAsJSON(const FilePath& path) const WARN_UNUSED_RESULT;
+
+ // Map where keys are test result statuses, and values are sets of tests
+ // which finished with that status.
+ typedef std::map<TestResult::Status, std::set<std::string> > TestStatusMap;
+
+ // Returns a test status map (see above) for current test iteration.
+ TestStatusMap GetTestStatusMapForCurrentIteration() const;
+
+ // Returns a test status map (see above) for all test iterations.
+ TestStatusMap GetTestStatusMapForAllIterations() const;
+
+ private:
+ void GetTestStatusForIteration(int iteration, TestStatusMap* map) const;
+
+ struct AggregateTestResult {
+ AggregateTestResult();
+ ~AggregateTestResult();
+
+ std::vector<TestResult> test_results;
+ };
+
+ struct PerIterationData {
+ PerIterationData();
+ ~PerIterationData();
+
+ // Aggregate test results grouped by full test name.
+ typedef std::map<std::string, AggregateTestResult> ResultsMap;
+ ResultsMap results;
+ };
+
+ ThreadChecker thread_checker_;
+
+ // Set of global tags, i.e. strings indicating conditions that apply to
+ // the entire test run.
+ std::set<std::string> global_tags_;
+
+ // Set of all test names discovered in the current executable.
+ std::set<std::string> all_tests_;
+
+ // Set of all disabled tests in the current executable.
+ std::set<std::string> disabled_tests_;
+
+ // Store test results for each iteration.
+ std::vector<PerIterationData> per_iteration_data_;
+
+ // Index of current iteration (starting from 0). -1 before the first
+ // iteration.
+ int iteration_;
+
+ // File handle of output file (can be NULL if no file).
+ FILE* out_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestResultsTracker);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_TEST_RESULTS_TRACKER_H_
diff --git a/base/test/launcher/unit_test_launcher.cc b/base/test/launcher/unit_test_launcher.cc
new file mode 100644
index 0000000..0cbae2f
--- /dev/null
+++ b/base/test/launcher/unit_test_launcher.cc
@@ -0,0 +1,586 @@
+// 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/launcher/unit_test_launcher.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/debug/debugger.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/format_macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/sys_info.h"
+#include "base/test/gtest_xml_util.h"
+#include "base/test/launcher/test_launcher.h"
+#include "base/test/test_switches.h"
+#include "base/test/test_timeouts.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/threading/thread_checker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+// This constant controls how many tests are run in a single batch by default.
+const size_t kDefaultTestBatchLimit = 10;
+
+const char kHelpFlag[] = "help";
+
+// Flag to run all tests in a single process.
+const char kSingleProcessTestsFlag[] = "single-process-tests";
+
+void PrintUsage() {
+ fprintf(stdout,
+ "Runs tests using the gtest framework, each batch of tests being\n"
+ "run in their own process. Supported command-line flags:\n"
+ "\n"
+ " Common flags:\n"
+ " --gtest_filter=...\n"
+ " Runs a subset of tests (see --gtest_help for more info).\n"
+ "\n"
+ " --help\n"
+ " Shows this message.\n"
+ "\n"
+ " --gtest_help\n"
+ " Shows the gtest help message.\n"
+ "\n"
+ " --test-launcher-jobs=N\n"
+ " Sets the number of parallel test jobs to N.\n"
+ "\n"
+ " --single-process-tests\n"
+ " Runs the tests and the launcher in the same process. Useful\n"
+ " for debugging a specific test in a debugger.\n"
+ "\n"
+ " Other flags:\n"
+ " --test-launcher-batch-limit=N\n"
+ " Sets the limit of test batch to run in a single process to N.\n"
+ "\n"
+ " --test-launcher-debug-launcher\n"
+ " Disables autodetection of debuggers and similar tools,\n"
+ " making it possible to use them to debug launcher itself.\n"
+ "\n"
+ " --test-launcher-retry-limit=N\n"
+ " Sets the limit of test retries on failures to N.\n"
+ "\n"
+ " --test-launcher-summary-output=PATH\n"
+ " Saves a JSON machine-readable summary of the run.\n"
+ "\n"
+ " --test-launcher-print-test-stdio=auto|always|never\n"
+ " Controls when full test output is printed.\n"
+ " auto means to print it when the test failed.\n"
+ "\n"
+ " --test-launcher-total-shards=N\n"
+ " Sets the total number of shards to N.\n"
+ "\n"
+ " --test-launcher-shard-index=N\n"
+ " Sets the shard index to run to N (from 0 to TOTAL - 1).\n");
+ fflush(stdout);
+}
+
+// Returns command line for child GTest process based on the command line
+// of current process. |test_names| is a vector of test full names
+// (e.g. "A.B"), |output_file| is path to the GTest XML output file.
+CommandLine GetCommandLineForChildGTestProcess(
+ const std::vector<std::string>& test_names,
+ const base::FilePath& output_file) {
+ CommandLine new_cmd_line(*CommandLine::ForCurrentProcess());
+
+ new_cmd_line.AppendSwitchPath(switches::kTestLauncherOutput, output_file);
+ new_cmd_line.AppendSwitchASCII(kGTestFilterFlag, JoinString(test_names, ":"));
+ new_cmd_line.AppendSwitch(kSingleProcessTestsFlag);
+
+ return new_cmd_line;
+}
+
+class UnitTestLauncherDelegate : public TestLauncherDelegate {
+ public:
+ explicit UnitTestLauncherDelegate(size_t batch_limit, bool use_job_objects)
+ : batch_limit_(batch_limit),
+ use_job_objects_(use_job_objects) {
+ }
+
+ virtual ~UnitTestLauncherDelegate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ }
+
+ private:
+ struct GTestCallbackState {
+ TestLauncher* test_launcher;
+ std::vector<std::string> test_names;
+ FilePath output_file;
+ };
+
+ virtual bool ShouldRunTest(const testing::TestCase* test_case,
+ const testing::TestInfo* test_info) OVERRIDE {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // There is no additional logic to disable specific tests.
+ return true;
+ }
+
+ virtual size_t RunTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) OVERRIDE {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::vector<std::string> batch;
+ for (size_t i = 0; i < test_names.size(); i++) {
+ batch.push_back(test_names[i]);
+
+ if (batch.size() >= batch_limit_) {
+ RunBatch(test_launcher, batch);
+ batch.clear();
+ }
+ }
+
+ RunBatch(test_launcher, batch);
+
+ return test_names.size();
+ }
+
+ virtual size_t RetryTests(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) OVERRIDE {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&UnitTestLauncherDelegate::RunSerially,
+ Unretained(this),
+ test_launcher,
+ test_names));
+ return test_names.size();
+ }
+
+ void RunSerially(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) {
+ if (test_names.empty())
+ return;
+
+ std::vector<std::string> new_test_names(test_names);
+ std::string test_name(new_test_names.back());
+ new_test_names.pop_back();
+
+ // Create a dedicated temporary directory to store the xml result data
+ // per run to ensure clean state and make it possible to launch multiple
+ // processes in parallel.
+ base::FilePath output_file;
+ CHECK(CreateNewTempDirectory(FilePath::StringType(), &output_file));
+ output_file = output_file.AppendASCII("test_results.xml");
+
+ std::vector<std::string> current_test_names;
+ current_test_names.push_back(test_name);
+ CommandLine cmd_line(
+ GetCommandLineForChildGTestProcess(current_test_names, output_file));
+
+ GTestCallbackState callback_state;
+ callback_state.test_launcher = test_launcher;
+ callback_state.test_names = current_test_names;
+ callback_state.output_file = output_file;
+
+ test_launcher->LaunchChildGTestProcess(
+ cmd_line,
+ std::string(),
+ TestTimeouts::test_launcher_timeout(),
+ use_job_objects_ ? TestLauncher::USE_JOB_OBJECTS : 0,
+ Bind(&UnitTestLauncherDelegate::SerialGTestCallback,
+ Unretained(this),
+ callback_state,
+ new_test_names));
+ }
+
+ void RunBatch(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (test_names.empty())
+ return;
+
+ // Create a dedicated temporary directory to store the xml result data
+ // per run to ensure clean state and make it possible to launch multiple
+ // processes in parallel.
+ base::FilePath output_file;
+ CHECK(CreateNewTempDirectory(FilePath::StringType(), &output_file));
+ output_file = output_file.AppendASCII("test_results.xml");
+
+ CommandLine cmd_line(
+ GetCommandLineForChildGTestProcess(test_names, output_file));
+
+ // Adjust the timeout depending on how many tests we're running
+ // (note that e.g. the last batch of tests will be smaller).
+ // TODO(phajdan.jr): Consider an adaptive timeout, which can change
+ // depending on how many tests ran and how many remain.
+ // Note: do NOT parse child's stdout to do that, it's known to be
+ // unreliable (e.g. buffering issues can mix up the output).
+ base::TimeDelta timeout =
+ test_names.size() * TestTimeouts::test_launcher_timeout();
+
+ GTestCallbackState callback_state;
+ callback_state.test_launcher = test_launcher;
+ callback_state.test_names = test_names;
+ callback_state.output_file = output_file;
+
+ test_launcher->LaunchChildGTestProcess(
+ cmd_line,
+ std::string(),
+ timeout,
+ use_job_objects_ ? TestLauncher::USE_JOB_OBJECTS : 0,
+ Bind(&UnitTestLauncherDelegate::GTestCallback,
+ Unretained(this),
+ callback_state));
+ }
+
+ void GTestCallback(const GTestCallbackState& callback_state,
+ int exit_code,
+ const TimeDelta& elapsed_time,
+ bool was_timeout,
+ const std::string& output) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<std::string> tests_to_relaunch;
+ ProcessTestResults(callback_state.test_launcher,
+ callback_state.test_names,
+ callback_state.output_file,
+ output,
+ exit_code,
+ was_timeout,
+ &tests_to_relaunch);
+
+ // Relaunch requested tests in parallel, but only use single
+ // test per batch for more precise results (crashes, test passes
+ // but non-zero exit codes etc).
+ for (size_t i = 0; i < tests_to_relaunch.size(); i++) {
+ std::vector<std::string> batch;
+ batch.push_back(tests_to_relaunch[i]);
+ RunBatch(callback_state.test_launcher, batch);
+ }
+
+ // The temporary file's directory is also temporary.
+ DeleteFile(callback_state.output_file.DirName(), true);
+ }
+
+ void SerialGTestCallback(const GTestCallbackState& callback_state,
+ const std::vector<std::string>& test_names,
+ int exit_code,
+ const TimeDelta& elapsed_time,
+ bool was_timeout,
+ const std::string& output) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::vector<std::string> tests_to_relaunch;
+ bool called_any_callbacks =
+ ProcessTestResults(callback_state.test_launcher,
+ callback_state.test_names,
+ callback_state.output_file,
+ output,
+ exit_code,
+ was_timeout,
+ &tests_to_relaunch);
+
+ // There is only one test, there cannot be other tests to relaunch
+ // due to a crash.
+ DCHECK(tests_to_relaunch.empty());
+
+ // There is only one test, we should have called back with its result.
+ DCHECK(called_any_callbacks);
+
+ // The temporary file's directory is also temporary.
+ DeleteFile(callback_state.output_file.DirName(), true);
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&UnitTestLauncherDelegate::RunSerially,
+ Unretained(this),
+ callback_state.test_launcher,
+ test_names));
+ }
+
+ static bool ProcessTestResults(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names,
+ const base::FilePath& output_file,
+ const std::string& output,
+ int exit_code,
+ bool was_timeout,
+ std::vector<std::string>* tests_to_relaunch) {
+ std::vector<TestResult> test_results;
+ bool crashed = false;
+ bool have_test_results =
+ ProcessGTestOutput(output_file, &test_results, &crashed);
+
+ bool called_any_callback = false;
+
+ if (have_test_results) {
+ // TODO(phajdan.jr): Check for duplicates and mismatches between
+ // the results we got from XML file and tests we intended to run.
+ std::map<std::string, TestResult> results_map;
+ for (size_t i = 0; i < test_results.size(); i++)
+ results_map[test_results[i].full_name] = test_results[i];
+
+ bool had_interrupted_test = false;
+
+ // Results to be reported back to the test launcher.
+ std::vector<TestResult> final_results;
+
+ for (size_t i = 0; i < test_names.size(); i++) {
+ if (ContainsKey(results_map, test_names[i])) {
+ TestResult test_result = results_map[test_names[i]];
+ if (test_result.status == TestResult::TEST_CRASH) {
+ had_interrupted_test = true;
+
+ if (was_timeout) {
+ // Fix up the test status: we forcibly kill the child process
+ // after the timeout, so from XML results it looks just like
+ // a crash.
+ test_result.status = TestResult::TEST_TIMEOUT;
+ }
+ } else if (test_result.status == TestResult::TEST_SUCCESS ||
+ test_result.status == TestResult::TEST_FAILURE) {
+ // We run multiple tests in a batch with a timeout applied
+ // to the entire batch. It is possible that with other tests
+ // running quickly some tests take longer than the per-test timeout.
+ // For consistent handling of tests independent of order and other
+ // factors, mark them as timing out.
+ if (test_result.elapsed_time >
+ TestTimeouts::test_launcher_timeout()) {
+ test_result.status = TestResult::TEST_TIMEOUT;
+ }
+ }
+ test_result.output_snippet =
+ GetTestOutputSnippet(test_result, output);
+ final_results.push_back(test_result);
+ } else if (had_interrupted_test) {
+ tests_to_relaunch->push_back(test_names[i]);
+ } else {
+ // TODO(phajdan.jr): Explicitly pass the info that the test didn't
+ // run for a mysterious reason.
+ LOG(ERROR) << "no test result for " << test_names[i];
+ TestResult test_result;
+ test_result.full_name = test_names[i];
+ test_result.status = TestResult::TEST_UNKNOWN;
+ test_result.output_snippet =
+ GetTestOutputSnippet(test_result, output);
+ final_results.push_back(test_result);
+ }
+ }
+
+ // TODO(phajdan.jr): Handle the case where processing XML output
+ // indicates a crash but none of the test results is marked as crashing.
+
+ if (final_results.empty())
+ return false;
+
+ bool has_non_success_test = false;
+ for (size_t i = 0; i < final_results.size(); i++) {
+ if (final_results[i].status != TestResult::TEST_SUCCESS) {
+ has_non_success_test = true;
+ break;
+ }
+ }
+
+ if (!has_non_success_test && exit_code != 0) {
+ // This is a bit surprising case: all tests are marked as successful,
+ // but the exit code was not zero. This can happen e.g. under memory
+ // tools that report leaks this way.
+
+ if (final_results.size() == 1) {
+ // Easy case. One test only so we know the non-zero exit code
+ // was caused by that one test.
+ final_results[0].status = TestResult::TEST_FAILURE_ON_EXIT;
+ } else {
+ // Harder case. Discard the results and request relaunching all
+ // tests without batching. This will trigger above branch on
+ // relaunch leading to more precise results.
+ LOG(WARNING) << "Not sure which test caused non-zero exit code, "
+ << "relaunching all of them without batching.";
+
+ for (size_t i = 0; i < final_results.size(); i++)
+ tests_to_relaunch->push_back(final_results[i].full_name);
+
+ return false;
+ }
+ }
+
+ for (size_t i = 0; i < final_results.size(); i++) {
+ // Fix the output snippet after possible changes to the test result.
+ final_results[i].output_snippet =
+ GetTestOutputSnippet(final_results[i], output);
+ test_launcher->OnTestFinished(final_results[i]);
+ called_any_callback = true;
+ }
+ } else {
+ fprintf(stdout,
+ "Failed to get out-of-band test success data, "
+ "dumping full stdio below:\n%s\n",
+ output.c_str());
+ fflush(stdout);
+
+ // We do not have reliable details about test results (parsing test
+ // stdout is known to be unreliable), apply the executable exit code
+ // to all tests.
+ // TODO(phajdan.jr): Be smarter about this, e.g. retry each test
+ // individually.
+ for (size_t i = 0; i < test_names.size(); i++) {
+ TestResult test_result;
+ test_result.full_name = test_names[i];
+ test_result.status = TestResult::TEST_UNKNOWN;
+ test_launcher->OnTestFinished(test_result);
+ called_any_callback = true;
+ }
+ }
+
+ return called_any_callback;
+ }
+
+ ThreadChecker thread_checker_;
+
+ // Maximum number of tests to run in a single batch.
+ size_t batch_limit_;
+
+ // Determines whether we use job objects on Windows.
+ bool use_job_objects_;
+};
+
+bool GetSwitchValueAsInt(const std::string& switch_name, int* result) {
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(switch_name))
+ return true;
+
+ std::string switch_value =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switch_name);
+ if (!StringToInt(switch_value, result) || *result < 1) {
+ LOG(ERROR) << "Invalid value for " << switch_name << ": " << switch_value;
+ return false;
+ }
+
+ return true;
+}
+
+int LaunchUnitTestsInternal(const RunTestSuiteCallback& run_test_suite,
+ int default_jobs,
+ bool use_job_objects,
+ const Closure& gtest_init) {
+#if defined(OS_ANDROID)
+ // We can't easily fork on Android, just run the test suite directly.
+ return run_test_suite.Run();
+#else
+ bool force_single_process = false;
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherDebugLauncher)) {
+ fprintf(stdout, "Forcing test launcher debugging mode.\n");
+ fflush(stdout);
+ } else {
+ if (base::debug::BeingDebugged()) {
+ fprintf(stdout,
+ "Debugger detected, switching to single process mode.\n"
+ "Pass --test-launcher-debug-launcher to debug the launcher "
+ "itself.\n");
+ fflush(stdout);
+ force_single_process = true;
+ }
+
+ if (RunningOnValgrind()) {
+ fprintf(stdout,
+ "Valgrind detected, switching to single process mode.\n"
+ "Pass --test-launcher-debug-launcher to valgrind the launcher "
+ "itself.\n");
+ fflush(stdout);
+ force_single_process = true;
+ }
+ }
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(kGTestHelpFlag) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(kGTestListTestsFlag) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(kSingleProcessTestsFlag) ||
+ force_single_process) {
+ return run_test_suite.Run();
+ }
+#endif
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(kHelpFlag)) {
+ PrintUsage();
+ return 0;
+ }
+
+ base::TimeTicks start_time(base::TimeTicks::Now());
+
+ gtest_init.Run();
+ TestTimeouts::Initialize();
+
+ int batch_limit = kDefaultTestBatchLimit;
+ if (!GetSwitchValueAsInt(switches::kTestLauncherBatchLimit, &batch_limit))
+ return 1;
+
+ fprintf(stdout,
+ "IMPORTANT DEBUGGING NOTE: batches of tests are run inside their\n"
+ "own process. For debugging a test inside a debugger, use the\n"
+ "--gtest_filter=<your_test_name> flag along with\n"
+ "--single-process-tests.\n");
+ fflush(stdout);
+
+ MessageLoopForIO message_loop;
+
+ UnitTestLauncherDelegate delegate(batch_limit, use_job_objects);
+ base::TestLauncher launcher(&delegate, default_jobs);
+ bool success = launcher.Run();
+
+ fprintf(stdout,
+ "Tests took %" PRId64 " seconds.\n",
+ (base::TimeTicks::Now() - start_time).InSeconds());
+ fflush(stdout);
+
+ return (success ? 0 : 1);
+}
+
+void InitGoogleTestChar(int* argc, char** argv) {
+ testing::InitGoogleTest(argc, argv);
+}
+
+#if defined(OS_WIN)
+void InitGoogleTestWChar(int* argc, wchar_t** argv) {
+ testing::InitGoogleTest(argc, argv);
+}
+#endif // defined(OS_WIN)
+
+} // namespace
+
+int LaunchUnitTests(int argc,
+ char** argv,
+ const RunTestSuiteCallback& run_test_suite) {
+ CommandLine::Init(argc, argv);
+ return LaunchUnitTestsInternal(
+ run_test_suite,
+ SysInfo::NumberOfProcessors(),
+ true,
+ Bind(&InitGoogleTestChar, &argc, argv));
+}
+
+int LaunchUnitTestsSerially(int argc,
+ char** argv,
+ const RunTestSuiteCallback& run_test_suite) {
+ CommandLine::Init(argc, argv);
+ return LaunchUnitTestsInternal(
+ run_test_suite,
+ 1,
+ true,
+ Bind(&InitGoogleTestChar, &argc, argv));
+}
+
+#if defined(OS_WIN)
+int LaunchUnitTests(int argc,
+ wchar_t** argv,
+ bool use_job_objects,
+ const RunTestSuiteCallback& run_test_suite) {
+ // Windows CommandLine::Init ignores argv anyway.
+ CommandLine::Init(argc, NULL);
+ return LaunchUnitTestsInternal(
+ run_test_suite,
+ SysInfo::NumberOfProcessors(),
+ use_job_objects,
+ Bind(&InitGoogleTestWChar, &argc, argv));
+}
+#endif // defined(OS_WIN)
+
+} // namespace base
diff --git a/base/test/launcher/unit_test_launcher.h b/base/test/launcher/unit_test_launcher.h
new file mode 100644
index 0000000..5682ed9
--- /dev/null
+++ b/base/test/launcher/unit_test_launcher.h
@@ -0,0 +1,36 @@
+// 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_LAUNCHER_UNIT_TEST_LAUNCHER_H_
+#define BASE_TEST_LAUNCHER_UNIT_TEST_LAUNCHER_H_
+
+#include "base/callback.h"
+
+namespace base {
+
+// Callback that runs a test suite and returns exit code.
+typedef base::Callback<int(void)> RunTestSuiteCallback;
+
+// Launches unit tests in given test suite. Returns exit code.
+int LaunchUnitTests(int argc,
+ char** argv,
+ const RunTestSuiteCallback& run_test_suite);
+
+// Same as above, but always runs tests serially.
+int LaunchUnitTestsSerially(int argc,
+ char** argv,
+ const RunTestSuiteCallback& run_test_suite);
+
+#if defined(OS_WIN)
+// Launches unit tests in given test suite. Returns exit code.
+// |use_job_objects| determines whether to use job objects.
+int LaunchUnitTests(int argc,
+ wchar_t** argv,
+ bool use_job_objects,
+ const RunTestSuiteCallback& run_test_suite);
+#endif // defined(OS_WIN)
+
+} // namespace base
+
+#endif // BASE_TEST_LAUNCHER_UNIT_TEST_LAUNCHER_H_
diff --git a/base/test/launcher/unit_test_launcher_ios.cc b/base/test/launcher/unit_test_launcher_ios.cc
new file mode 100644
index 0000000..ae08acd
--- /dev/null
+++ b/base/test/launcher/unit_test_launcher_ios.cc
@@ -0,0 +1,17 @@
+// 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/launcher/unit_test_launcher.h"
+
+namespace base {
+
+int LaunchUnitTests(int argc,
+ char** argv,
+ const RunTestSuiteCallback& run_test_suite) {
+ // Stub implementation - iOS doesn't support features we need for
+ // the full test launcher (e.g. process_util).
+ return run_test_suite.Run();
+}
+
+} // namespace base
diff --git a/base/test/mock_chrome_application_mac.h b/base/test/mock_chrome_application_mac.h
new file mode 100644
index 0000000..ffa3080
--- /dev/null
+++ b/base/test/mock_chrome_application_mac.h
@@ -0,0 +1,33 @@
+// 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_MOCK_CHROME_APPLICATION_MAC_H_
+#define BASE_TEST_MOCK_CHROME_APPLICATION_MAC_H_
+
+#if defined(__OBJC__)
+
+#import <AppKit/AppKit.h>
+
+#include "base/mac/scoped_sending_event.h"
+#include "base/message_loop/message_pump_mac.h"
+
+// A basic implementation of CrAppProtocol and
+// CrAppControlProtocol. This can be used in tests that need an
+// NSApplication and use a runloop, or which need a ScopedSendingEvent
+// when handling a nested event loop.
+@interface MockCrApp : NSApplication<CrAppProtocol,
+ CrAppControlProtocol> {
+ @private
+ BOOL handlingSendEvent_;
+}
+@end
+
+#endif
+
+// To be used to instantiate MockCrApp from C++ code.
+namespace mock_cr_app {
+void RegisterMockCrApp();
+} // namespace mock_cr_app
+
+#endif // BASE_TEST_MOCK_CHROME_APPLICATION_MAC_H_
diff --git a/base/test/mock_chrome_application_mac.mm b/base/test/mock_chrome_application_mac.mm
new file mode 100644
index 0000000..0890553
--- /dev/null
+++ b/base/test/mock_chrome_application_mac.mm
@@ -0,0 +1,44 @@
+// 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/logging.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];
+}
+
+} // namespace mock_cr_app
diff --git a/base/test/mock_devices_changed_observer.cc b/base/test/mock_devices_changed_observer.cc
new file mode 100644
index 0000000..c05f26a
--- /dev/null
+++ b/base/test/mock_devices_changed_observer.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/test/mock_devices_changed_observer.h"
+
+namespace base {
+
+MockDevicesChangedObserver::MockDevicesChangedObserver() {
+}
+
+MockDevicesChangedObserver::~MockDevicesChangedObserver() {
+}
+
+} // namespace base
diff --git a/base/test/mock_devices_changed_observer.h b/base/test/mock_devices_changed_observer.h
new file mode 100644
index 0000000..3ada16b
--- /dev/null
+++ b/base/test/mock_devices_changed_observer.h
@@ -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.
+
+#ifndef BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_
+#define BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_
+
+#include <string>
+
+#include "base/system_monitor/system_monitor.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+
+class MockDevicesChangedObserver
+ : public base::SystemMonitor::DevicesChangedObserver {
+ public:
+ MockDevicesChangedObserver();
+ ~MockDevicesChangedObserver();
+
+ MOCK_METHOD1(OnDevicesChanged,
+ void(base::SystemMonitor::DeviceType device_type));
+
+ DISALLOW_COPY_AND_ASSIGN(MockDevicesChangedObserver);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_MOCK_DEVICES_CHANGED_OBSERVER_H_
diff --git a/base/test/mock_time_provider.cc b/base/test/mock_time_provider.cc
new file mode 100644
index 0000000..9e5547f
--- /dev/null
+++ b/base/test/mock_time_provider.cc
@@ -0,0 +1,31 @@
+// 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/logging.h"
+#include "base/test/mock_time_provider.h"
+
+using ::testing::DefaultValue;
+
+namespace base {
+
+MockTimeProvider* MockTimeProvider::instance_ = NULL;
+
+MockTimeProvider::MockTimeProvider() {
+ DCHECK(!instance_) << "Only one instance of MockTimeProvider can exist";
+ DCHECK(!DefaultValue<Time>::IsSet());
+ instance_ = this;
+ DefaultValue<Time>::Set(Time::FromInternalValue(0));
+}
+
+MockTimeProvider::~MockTimeProvider() {
+ instance_ = NULL;
+ DefaultValue<Time>::Clear();
+}
+
+// static
+Time MockTimeProvider::StaticNow() {
+ return instance_->Now();
+}
+
+} // namespace base
diff --git a/base/test/mock_time_provider.h b/base/test/mock_time_provider.h
new file mode 100644
index 0000000..7c58648
--- /dev/null
+++ b/base/test/mock_time_provider.h
@@ -0,0 +1,69 @@
+// 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.
+
+// TODO(akalin): Change all users of this class to use SimpleTestClock
+// or SimpleTestTickClock and remove this class.
+
+// A helper class used to mock out calls to the static method base::Time::Now.
+//
+// Example usage:
+//
+// typedef base::Time(TimeProvider)();
+// class StopWatch {
+// public:
+// StopWatch(TimeProvider* time_provider);
+// void Start();
+// base::TimeDelta Stop();
+// private:
+// TimeProvider* time_provider_;
+// ...
+// }
+//
+// Normally, you would instantiate a StopWatch with the real Now function:
+//
+// StopWatch watch(&base::Time::Now);
+//
+// But when testing, you want to instantiate it with
+// MockTimeProvider::StaticNow, which calls an internally mocked out member.
+// This allows you to set expectations on the Now method. For example:
+//
+// TEST_F(StopWatchTest, BasicTest) {
+// InSequence s;
+// StrictMock<MockTimeProvider> mock_time;
+// EXPECT_CALL(mock_time, Now())
+// .WillOnce(Return(Time::FromDoubleT(4)));
+// EXPECT_CALL(mock_time, Now())
+// .WillOnce(Return(Time::FromDoubleT(10)));
+//
+// StopWatch sw(&MockTimeProvider::StaticNow);
+// sw.Start(); // First call to Now.
+// TimeDelta elapsed = sw.stop(); // Second call to Now.
+// ASSERT_EQ(elapsed, TimeDelta::FromSeconds(6));
+// }
+
+#ifndef BASE_TEST_MOCK_TIME_PROVIDER_H_
+#define BASE_TEST_MOCK_TIME_PROVIDER_H_
+
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+
+class MockTimeProvider {
+ public:
+ MockTimeProvider();
+ ~MockTimeProvider();
+
+ MOCK_METHOD0(Now, Time());
+
+ static Time StaticNow();
+
+ private:
+ static MockTimeProvider* instance_;
+ DISALLOW_COPY_AND_ASSIGN(MockTimeProvider);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_MOCK_TIME_PROVIDER_H_
diff --git a/base/test/multiprocess_test.cc b/base/test/multiprocess_test.cc
new file mode 100644
index 0000000..306c109
--- /dev/null
+++ b/base/test/multiprocess_test.cc
@@ -0,0 +1,59 @@
+// 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"
+
+namespace base {
+
+#if !defined(OS_ANDROID)
+ProcessHandle 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);
+
+ ProcessHandle handle = kNullProcessHandle;
+ LaunchProcess(command_line, options, &handle);
+ return handle;
+}
+#endif // !defined(OS_ANDROID)
+
+CommandLine GetMultiProcessTestChildBaseCommandLine() {
+ return *CommandLine::ForCurrentProcess();
+}
+
+// MultiProcessTest ------------------------------------------------------------
+
+MultiProcessTest::MultiProcessTest() {
+}
+
+ProcessHandle MultiProcessTest::SpawnChild(const std::string& procname) {
+ LaunchOptions options;
+#if defined(OS_WIN)
+ options.start_hidden = true;
+#endif
+ return SpawnChildWithOptions(procname, options);
+}
+
+ProcessHandle 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/base/test/multiprocess_test.h b/base/test/multiprocess_test.h
new file mode 100644
index 0000000..b830f73
--- /dev/null
+++ b/base/test/multiprocess_test.h
@@ -0,0 +1,132 @@
+// 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/basictypes.h"
+#include "base/process/launch.h"
+#include "base/process/process_handle.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::ProcessHandle test_child_handle =
+// base::SpawnMultiProcessTestChild("a_test_func", command_line,
+// options);
+//
+// // Do stuff involving |test_child_handle| and the child process....
+//
+// int rv = -1;
+// ASSERT_TRUE(base::WaitForExitCodeWithTimeout(
+// test_child_handle, &rv, TestTimeouts::action_timeout()));
+// base::CloseProcessHandle(test_child_handle);
+// EXPECT_EQ(0, rv);
+// }
+//
+// // Note: |MULTIPROCESS_TEST_MAIN()| is defined in
+// // testing/multi_process_function_list.h.
+// MULTIPROCESS_TEST_MAIN(a_test_func) {
+// // Code here runs in a child process....
+// return 0;
+// }
+
+// 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|.
+ProcessHandle 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();
+
+// 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 handle to the child, or NULL on failure
+ ProcessHandle 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|.
+ ProcessHandle 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/base/test/multiprocess_test_android.cc b/base/test/multiprocess_test_android.cc
new file mode 100644
index 0000000..6eb30b8
--- /dev/null
+++ b/base/test/multiprocess_test_android.cc
@@ -0,0 +1,67 @@
+// 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/posix/global_descriptors.h"
+#include "base/test/multiprocess_test.h"
+
+#include <unistd.h>
+
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "testing/multiprocess_func_list.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).
+// - |base_command_line| is ignored.
+// - All options except |fds_to_remap| are ignored.
+ProcessHandle SpawnMultiProcessTestChild(const std::string& procname,
+ const CommandLine& base_command_line,
+ const LaunchOptions& options) {
+ // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of
+ // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576
+ FileHandleMappingVector empty;
+ const FileHandleMappingVector* fds_to_remap =
+ options.fds_to_remap ? options.fds_to_remap : ∅
+
+ pid_t pid = fork();
+
+ if (pid < 0) {
+ PLOG(ERROR) << "fork";
+ return kNullProcessHandle;
+ }
+ if (pid > 0) {
+ // Parent process.
+ return pid;
+ }
+ // Child process.
+ std::hash_set<int> fds_to_keep_open;
+ for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin();
+ it != fds_to_remap->end(); ++it) {
+ fds_to_keep_open.insert(it->first);
+ }
+ // Keep standard FDs (stdin, stdout, stderr, etc.) open since this
+ // is not meant to spawn a daemon.
+ int base = GlobalDescriptors::kBaseDescriptor;
+ for (int fd = base; fd < sysconf(_SC_OPEN_MAX); ++fd) {
+ if (fds_to_keep_open.find(fd) == fds_to_keep_open.end()) {
+ close(fd);
+ }
+ }
+ for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin();
+ it != fds_to_remap->end(); ++it) {
+ int old_fd = it->first;
+ int new_fd = it->second;
+ if (dup2(old_fd, new_fd) < 0) {
+ PLOG(FATAL) << "dup2";
+ }
+ close(old_fd);
+ }
+ _exit(multi_process_function_list::InvokeChildProcessTest(procname));
+ return 0;
+}
+
+} // namespace base
diff --git a/base/test/null_task_runner.cc b/base/test/null_task_runner.cc
new file mode 100644
index 0000000..bf43e6d
--- /dev/null
+++ b/base/test/null_task_runner.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/null_task_runner.h"
+
+namespace base {
+
+NullTaskRunner::NullTaskRunner() {}
+
+NullTaskRunner::~NullTaskRunner() {}
+
+bool NullTaskRunner::PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) {
+ return false;
+}
+
+bool NullTaskRunner::PostNonNestableDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) {
+ return false;
+}
+
+bool NullTaskRunner::RunsTasksOnCurrentThread() const {
+ return true;
+}
+
+} // namespace
diff --git a/base/test/null_task_runner.h b/base/test/null_task_runner.h
new file mode 100644
index 0000000..d6390e5
--- /dev/null
+++ b/base/test/null_task_runner.h
@@ -0,0 +1,34 @@
+// 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/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/single_thread_task_runner.h"
+
+namespace base {
+
+// 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();
+
+ virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) OVERRIDE;
+ virtual bool PostNonNestableDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) OVERRIDE;
+ // Always returns true to avoid triggering DCHECKs.
+ virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
+
+ protected:
+ virtual ~NullTaskRunner();
+
+ DISALLOW_COPY_AND_ASSIGN(NullTaskRunner);
+};
+
+} // namespace
diff --git a/base/test/perf_log.cc b/base/test/perf_log.cc
new file mode 100644
index 0000000..22884b8
--- /dev/null
+++ b/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/logging.h"
+
+namespace base {
+
+static FILE* perf_log_file = NULL;
+
+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 != NULL;
+}
+
+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/base/test/perf_log.h b/base/test/perf_log.h
new file mode 100644
index 0000000..5d6ed9f
--- /dev/null
+++ b/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/base/test/perf_test_suite.cc b/base/test/perf_test_suite.cc
new file mode 100644
index 0000000..415aaef
--- /dev/null
+++ b/base/test/perf_test_suite.cc
@@ -0,0 +1,49 @@
+// 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 "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)
+ 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/base/test/perf_test_suite.h b/base/test/perf_test_suite.h
new file mode 100644
index 0000000..85bfc41
--- /dev/null
+++ b/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);
+
+ virtual void Initialize() OVERRIDE;
+ virtual void Shutdown() OVERRIDE;
+};
+
+} // namespace base
+
+#endif // BASE_TEST_PERF_TEST_SUITE_H_
diff --git a/base/test/perf_time_logger.cc b/base/test/perf_time_logger.cc
new file mode 100644
index 0000000..c05ba51
--- /dev/null
+++ b/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/base/test/perf_time_logger.h b/base/test/perf_time_logger.h
new file mode 100644
index 0000000..403b272
--- /dev/null
+++ b/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/basictypes.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/base/test/power_monitor_test_base.cc b/base/test/power_monitor_test_base.cc
new file mode 100644
index 0000000..73438ea
--- /dev/null
+++ b/base/test/power_monitor_test_base.cc
@@ -0,0 +1,64 @@
+// 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.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/power_monitor/power_monitor_source.h"
+
+namespace base {
+
+PowerMonitorTestSource::PowerMonitorTestSource()
+ : test_on_battery_power_(false) {
+}
+
+PowerMonitorTestSource::~PowerMonitorTestSource() {
+}
+
+void PowerMonitorTestSource::GeneratePowerStateEvent(bool on_battery_power) {
+ test_on_battery_power_ = on_battery_power;
+ ProcessPowerEvent(POWER_STATE_EVENT);
+ message_loop_.RunUntilIdle();
+}
+
+void PowerMonitorTestSource::GenerateSuspendEvent() {
+ ProcessPowerEvent(SUSPEND_EVENT);
+ message_loop_.RunUntilIdle();
+}
+
+void PowerMonitorTestSource::GenerateResumeEvent() {
+ ProcessPowerEvent(RESUME_EVENT);
+ message_loop_.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() {
+}
+
+// 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/base/test/power_monitor_test_base.h b/base/test/power_monitor_test_base.h
new file mode 100644
index 0000000..6e37f3d
--- /dev/null
+++ b/base/test/power_monitor_test_base.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_POWER_MONITOR_TEST_BASE_H_
+#define BASE_TEST_POWER_MONITOR_TEST_BASE_H_
+
+#include "base/message_loop/message_loop.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/power_monitor/power_monitor_source.h"
+
+namespace base {
+
+class PowerMonitorTestSource : public PowerMonitorSource {
+ public:
+ PowerMonitorTestSource();
+ virtual ~PowerMonitorTestSource();
+
+ void GeneratePowerStateEvent(bool on_battery_power);
+ void GenerateSuspendEvent();
+ void GenerateResumeEvent();
+
+ protected:
+ virtual bool IsOnBatteryPowerImpl() OVERRIDE;
+
+ bool test_on_battery_power_;
+ MessageLoop message_loop_;
+};
+
+class PowerMonitorTestObserver : public PowerObserver {
+ public:
+ PowerMonitorTestObserver();
+ virtual ~PowerMonitorTestObserver();
+
+ // PowerObserver callbacks.
+ virtual void OnPowerStateChange(bool on_battery_power) OVERRIDE;
+ virtual void OnSuspend() OVERRIDE;
+ virtual void OnResume() OVERRIDE;
+
+ // Test status counts.
+ bool last_power_state() { return last_power_state_; }
+ int power_state_changes() { return power_state_changes_; }
+ int suspends() { return suspends_; }
+ int resumes() { 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/base/test/run_all_perftests.cc b/base/test/run_all_perftests.cc
new file mode 100644
index 0000000..6e38109
--- /dev/null
+++ b/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/base/test/run_all_unittests.cc b/base/test/run_all_unittests.cc
new file mode 100644
index 0000000..93cb8cb
--- /dev/null
+++ b/base/test/run_all_unittests.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/at_exit.h"
+#include "base/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "base/test/test_file_util.h"
+#endif
+
+namespace {
+
+class NoAtExitBaseTestSuite : public base::TestSuite {
+ public:
+ NoAtExitBaseTestSuite(int argc, char** argv)
+ : base::TestSuite(argc, argv, false) {
+ }
+};
+
+int RunTestSuite(int argc, char** argv) {
+ return NoAtExitBaseTestSuite(argc, argv).Run();
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+#if defined(OS_ANDROID)
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::RegisterContentUriTestUtils(env);
+#else
+ base::AtExitManager at_exit;
+#endif
+ return base::LaunchUnitTests(argc,
+ argv,
+ base::Bind(&RunTestSuite, argc, argv));
+}
diff --git a/base/test/scoped_locale.cc b/base/test/scoped_locale.cc
new file mode 100644
index 0000000..35b3fbe
--- /dev/null
+++ b/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, NULL);
+ EXPECT_TRUE(setlocale(LC_ALL, locale.c_str()) != NULL) <<
+ "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/base/test/scoped_locale.h b/base/test/scoped_locale.h
new file mode 100644
index 0000000..a9f9348
--- /dev/null
+++ b/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/basictypes.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/base/test/scoped_path_override.cc b/base/test/scoped_path_override.cc
new file mode 100644
index 0000000..495ba2f
--- /dev/null
+++ b/base/test/scoped_path_override.cc
@@ -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.
+
+#include "base/test/scoped_path_override.h"
+
+#include "base/logging.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_.path());
+ CHECK(result);
+}
+
+ScopedPathOverride::ScopedPathOverride(int key, const base::FilePath& dir)
+ : key_(key) {
+ bool result = PathService::Override(key, dir);
+ CHECK(result);
+}
+
+ScopedPathOverride::~ScopedPathOverride() {
+ bool result = PathService::RemoveOverride(key_);
+ CHECK(result) << "The override seems to have been removed already!";
+}
+
+} // namespace base
diff --git a/base/test/scoped_path_override.h b/base/test/scoped_path_override.h
new file mode 100644
index 0000000..a1e18e3
--- /dev/null
+++ b/base/test/scoped_path_override.h
@@ -0,0 +1,36 @@
+// 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/basictypes.h"
+#include "base/files/scoped_temp_dir.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);
+ ~ScopedPathOverride();
+
+ private:
+ int key_;
+ ScopedTempDir temp_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPathOverride);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_SCOPED_PATH_OVERRIDE_H_
diff --git a/base/test/sequenced_task_runner_test_template.cc b/base/test/sequenced_task_runner_test_template.cc
new file mode 100644
index 0000000..010f439
--- /dev/null
+++ b/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(
+ const scoped_refptr<SequencedTaskRunner>& task_runner,
+ const Closure& task) {
+ AutoLock event_lock(lock_);
+ const int post_i = next_post_i_++;
+ Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this,
+ task, post_i);
+ task_runner->PostNonNestableTask(FROM_HERE, wrapped_task);
+ TaskPosted(post_i);
+}
+
+void SequencedTaskTracker::PostWrappedNestableTask(
+ const scoped_refptr<SequencedTaskRunner>& task_runner,
+ const Closure& task) {
+ AutoLock event_lock(lock_);
+ const int post_i = next_post_i_++;
+ Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this,
+ task, post_i);
+ task_runner->PostTask(FROM_HERE, wrapped_task);
+ TaskPosted(post_i);
+}
+
+void SequencedTaskTracker::PostWrappedDelayedNonNestableTask(
+ const scoped_refptr<SequencedTaskRunner>& task_runner,
+ const Closure& task,
+ TimeDelta delay) {
+ AutoLock event_lock(lock_);
+ const int post_i = next_post_i_++;
+ Closure wrapped_task = Bind(&SequencedTaskTracker::RunTask, this,
+ task, post_i);
+ task_runner->PostNonNestableDelayedTask(FROM_HERE, wrapped_task, delay);
+ TaskPosted(post_i);
+}
+
+void SequencedTaskTracker::PostNonNestableTasks(
+ const scoped_refptr<SequencedTaskRunner>& task_runner,
+ int task_count) {
+ for (int i = 0; i < task_count; ++i) {
+ PostWrappedNonNestableTask(task_runner, Closure());
+ }
+}
+
+void SequencedTaskTracker::RunTask(const Closure& task, int task_i) {
+ TaskStarted(task_i);
+ if (!task.is_null())
+ 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() {
+}
+
+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/base/test/sequenced_task_runner_test_template.h b/base/test/sequenced_task_runner_test_template.h
new file mode 100644
index 0000000..48f5354
--- /dev/null
+++ b/base/test/sequenced_task_runner_test_template.h
@@ -0,0 +1,341 @@
+// 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 class 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_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_
+#define BASE_SEQUENCED_TASK_RUNNER_TEST_TEMPLATE_H_
+
+#include <cstddef>
+#include <iosfwd>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.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(
+ const scoped_refptr<SequencedTaskRunner>& task_runner,
+ const Closure& task);
+
+ // Posts the nestable task |task|, and records its post event.
+ void PostWrappedNestableTask(
+ const scoped_refptr<SequencedTaskRunner>& task_runner,
+ const Closure& task);
+
+ // Posts the delayed non-nestable task |task|, and records its post event.
+ void PostWrappedDelayedNonNestableTask(
+ const scoped_refptr<SequencedTaskRunner>& task_runner,
+ const Closure& task,
+ TimeDelta delay);
+
+ // Posts |task_count| non-nestable tasks.
+ void PostNonNestableTasks(
+ const scoped_refptr<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(const Closure& 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_CASE_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, Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1)));
+ for (int i = 1; i < kTaskCount; ++i) {
+ this->task_tracker_->PostWrappedNonNestableTask(task_runner, Closure());
+ }
+
+ 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,
+ Bind(&PlatformThread::Sleep, TimeDelta::FromSeconds(1)));
+ for (int i = 1; i < kTaskCount; ++i) {
+ this->task_tracker_->PostWrappedNestableTask(task_runner, Closure());
+ }
+
+ 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,
+ Closure(),
+ 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) {
+ Closure task = Bind(
+ &internal::SequencedTaskTracker::PostNonNestableTasks,
+ this->task_tracker_,
+ task_runner,
+ kChildrenPerParent);
+ this->task_tracker_->PostWrappedNonNestableTask(task_runner, task);
+ }
+
+ this->delegate_.StopTaskRunner();
+
+ EXPECT_TRUE(CheckNonNestableInvariants(
+ this->task_tracker_->GetTaskEvents(),
+ kParentCount * (kChildrenPerParent + 1)));
+}
+
+// This test posts a delayed task, and checks that the task is run later than
+// the specified time.
+TYPED_TEST_P(SequencedTaskRunnerTest, 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, Closure(), 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);
+}
+
+// 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, Closure(), kDelay);
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(
+ task_runner, Closure(), 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, base::Bind(&PlatformThread::Sleep,
+ TimeDelta::FromMilliseconds(50)));
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(
+ task_runner, Closure(), 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, base::Bind(&PlatformThread::Sleep,
+ TimeDelta::FromMilliseconds(50)));
+ }
+ this->task_tracker_->PostWrappedDelayedNonNestableTask(
+ task_runner, Closure(), 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.
+//
+
+REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerTest,
+ SequentialNonNestable,
+ SequentialNestable,
+ SequentialDelayedNonNestable,
+ NonNestablePostFromNonNestableTask,
+ DelayedTaskBasic,
+ DelayedTasksSameDelay,
+ DelayedTaskAfterLongTask,
+ DelayedTaskAfterManyLongTasks);
+
+} // namespace base
+
+#endif // BASE_TASK_RUNNER_TEST_TEMPLATE_H_
diff --git a/base/test/sequenced_worker_pool_owner.cc b/base/test/sequenced_worker_pool_owner.cc
new file mode 100644
index 0000000..f6a0d01
--- /dev/null
+++ b/base/test/sequenced_worker_pool_owner.cc
@@ -0,0 +1,55 @@
+// 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_worker_pool_owner.h"
+
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+
+namespace base {
+
+SequencedWorkerPoolOwner::SequencedWorkerPoolOwner(
+ size_t max_threads,
+ const std::string& thread_name_prefix)
+ : constructor_message_loop_(MessageLoop::current()),
+ pool_(new SequencedWorkerPool(max_threads, thread_name_prefix, this)),
+ has_work_call_count_(0) {}
+
+SequencedWorkerPoolOwner::~SequencedWorkerPoolOwner() {
+ pool_ = NULL;
+ MessageLoop::current()->Run();
+}
+
+const scoped_refptr<SequencedWorkerPool>& SequencedWorkerPoolOwner::pool() {
+ return pool_;
+}
+
+void SequencedWorkerPoolOwner::SetWillWaitForShutdownCallback(
+ const Closure& callback) {
+ will_wait_for_shutdown_callback_ = callback;
+}
+
+int SequencedWorkerPoolOwner::has_work_call_count() const {
+ AutoLock lock(has_work_lock_);
+ return has_work_call_count_;
+}
+
+void SequencedWorkerPoolOwner::OnHasWork() {
+ AutoLock lock(has_work_lock_);
+ ++has_work_call_count_;
+}
+
+void SequencedWorkerPoolOwner::WillWaitForShutdown() {
+ if (!will_wait_for_shutdown_callback_.is_null()) {
+ will_wait_for_shutdown_callback_.Run();
+ }
+}
+
+void SequencedWorkerPoolOwner::OnDestruct() {
+ constructor_message_loop_->PostTask(
+ FROM_HERE,
+ constructor_message_loop_->QuitWhenIdleClosure());
+}
+
+} // namespace base
diff --git a/base/test/sequenced_worker_pool_owner.h b/base/test/sequenced_worker_pool_owner.h
new file mode 100644
index 0000000..1cc3fd6
--- /dev/null
+++ b/base/test/sequenced_worker_pool_owner.h
@@ -0,0 +1,61 @@
+// 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_THREADING_SEQUENCED_WORKER_POOL_UNITTEST_H_
+#define BASE_THREADING_SEQUENCED_WORKER_POOL_UNITTEST_H_
+
+#include <cstddef>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_worker_pool.h"
+
+namespace base {
+
+class MessageLoop;
+
+// Wrapper around SequencedWorkerPool for testing that blocks destruction
+// until the pool is actually destroyed. This is so that a
+// SequencedWorkerPool from one test doesn't outlive its test and cause
+// strange races with other tests that touch global stuff (like histograms and
+// logging). However, this requires that nothing else on this thread holds a
+// ref to the pool when the SequencedWorkerPoolOwner is destroyed.
+class SequencedWorkerPoolOwner : public SequencedWorkerPool::TestingObserver {
+ public:
+ SequencedWorkerPoolOwner(size_t max_threads,
+ const std::string& thread_name_prefix);
+
+ virtual ~SequencedWorkerPoolOwner();
+
+ // Don't change the returned pool's testing observer.
+ const scoped_refptr<SequencedWorkerPool>& pool();
+
+ // The given callback will be called on WillWaitForShutdown().
+ void SetWillWaitForShutdownCallback(const Closure& callback);
+
+ int has_work_call_count() const;
+
+ private:
+ // SequencedWorkerPool::TestingObserver implementation.
+ virtual void OnHasWork() OVERRIDE;
+ virtual void WillWaitForShutdown() OVERRIDE;
+ virtual void OnDestruct() OVERRIDE;
+
+ MessageLoop* const constructor_message_loop_;
+ scoped_refptr<SequencedWorkerPool> pool_;
+ Closure will_wait_for_shutdown_callback_;
+
+ mutable Lock has_work_lock_;
+ int has_work_call_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(SequencedWorkerPoolOwner);
+};
+
+} // namespace base
+
+#endif // BASE_THREADING_SEQUENCED_WORKER_POOL_UNITTEST_H_
diff --git a/base/test/simple_test_clock.cc b/base/test/simple_test_clock.cc
new file mode 100644
index 0000000..a2bdc2a
--- /dev/null
+++ b/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() {}
+
+SimpleTestClock::~SimpleTestClock() {}
+
+Time SimpleTestClock::Now() {
+ 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/base/test/simple_test_clock.h b/base/test/simple_test_clock.h
new file mode 100644
index 0000000..2056aab
--- /dev/null
+++ b/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_SIMPLE_TEST_CLOCK_H_
+#define BASE_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();
+ virtual ~SimpleTestClock();
+
+ virtual Time Now() OVERRIDE;
+
+ // Advances the clock by |delta|.
+ void Advance(TimeDelta delta);
+
+ // Sets the clock to the given time.
+ void SetNow(Time now);
+
+ private:
+ // Protects |now_|.
+ Lock lock_;
+
+ Time now_;
+};
+
+} // namespace base
+
+#endif // BASE_SIMPLE_TEST_CLOCK_H_
diff --git a/base/test/simple_test_tick_clock.cc b/base/test/simple_test_tick_clock.cc
new file mode 100644
index 0000000..1b4696f
--- /dev/null
+++ b/base/test/simple_test_tick_clock.cc
@@ -0,0 +1,26 @@
+// 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/logging.h"
+
+namespace base {
+
+SimpleTestTickClock::SimpleTestTickClock() {}
+
+SimpleTestTickClock::~SimpleTestTickClock() {}
+
+TimeTicks SimpleTestTickClock::NowTicks() {
+ AutoLock lock(lock_);
+ return now_ticks_;
+}
+
+void SimpleTestTickClock::Advance(TimeDelta delta) {
+ AutoLock lock(lock_);
+ DCHECK(delta >= TimeDelta());
+ now_ticks_ += delta;
+}
+
+} // namespace base
diff --git a/base/test/simple_test_tick_clock.h b/base/test/simple_test_tick_clock.h
new file mode 100644
index 0000000..867de80
--- /dev/null
+++ b/base/test/simple_test_tick_clock.h
@@ -0,0 +1,38 @@
+// 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_SIMPLE_TEST_TICK_CLOCK_H_
+#define BASE_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();
+ virtual ~SimpleTestTickClock();
+
+ virtual TimeTicks NowTicks() OVERRIDE;
+
+ // Advances the clock by |delta|, which must not be negative.
+ void Advance(TimeDelta delta);
+
+ private:
+ // Protects |now_ticks_|.
+ Lock lock_;
+
+ TimeTicks now_ticks_;
+};
+
+} // namespace base
+
+#endif // BASE_SIMPLE_TEST_TICK_CLOCK_H_
diff --git a/base/test/task_runner_test_template.cc b/base/test/task_runner_test_template.cc
new file mode 100644
index 0000000..b756203
--- /dev/null
+++ b/base/test/task_runner_test_template.cc
@@ -0,0 +1,48 @@
+// 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 internal {
+
+TaskTracker::TaskTracker() : task_runs_(0), task_runs_cv_(&lock_) {}
+
+TaskTracker::~TaskTracker() {}
+
+Closure TaskTracker::WrapTask(const Closure& task, int i) {
+ return Bind(&TaskTracker::RunTask, this, task, i);
+}
+
+void TaskTracker::RunTask(const Closure& 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();
+}
+
+void ExpectRunsTasksOnCurrentThread(
+ bool expected_value,
+ const scoped_refptr<TaskRunner>& task_runner) {
+ EXPECT_EQ(expected_value, task_runner->RunsTasksOnCurrentThread());
+}
+
+} // namespace internal
+
+} // namespace base
diff --git a/base/test/task_runner_test_template.h b/base/test/task_runner_test_template.h
new file mode 100644
index 0000000..73aa1f4
--- /dev/null
+++ b/base/test/task_runner_test_template.h
@@ -0,0 +1,215 @@
+// 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 class defines tests that implementations of TaskRunner should
+// pass in order to be conformant. 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_CASE_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/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "base/threading/thread.h"
+#include "base/tracked_objects.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace internal {
+
+// 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.
+ Closure WrapTask(const Closure& 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(const Closure& 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 internal
+
+template <typename TaskRunnerTestDelegate>
+class TaskRunnerTest : public testing::Test {
+ protected:
+ TaskRunnerTest() : task_tracker_(new internal::TaskTracker()) {}
+
+ const scoped_refptr<internal::TaskTracker> task_tracker_;
+ TaskRunnerTestDelegate delegate_;
+};
+
+TYPED_TEST_CASE_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) {
+ const Closure& ith_task = this->task_tracker_->WrapTask(Closure(), 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) {
+ const Closure& ith_task = this->task_tracker_->WrapTask(Closure(), 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());
+}
+
+namespace internal {
+
+// Calls RunsTasksOnCurrentThread() on |task_runner| and expects it to
+// equal |expected_value|.
+void ExpectRunsTasksOnCurrentThread(
+ bool expected_value,
+ const scoped_refptr<TaskRunner>& task_runner);
+
+} // namespace internal
+
+// Post a bunch of tasks to the task runner as well as to a separate
+// thread, each checking the value of RunsTasksOnCurrentThread(),
+// which should return true for the tasks posted on the task runner
+// and false for the tasks posted on the separate thread.
+TYPED_TEST_P(TaskRunnerTest, RunsTasksOnCurrentThread) {
+ std::map<int, int> expected_task_run_counts;
+
+ Thread thread("Non-task-runner thread");
+ ASSERT_TRUE(thread.Start());
+ this->delegate_.StartTaskRunner();
+
+ scoped_refptr<TaskRunner> task_runner = this->delegate_.GetTaskRunner();
+ // Post each ith task i+1 times on the task runner and i+1 times on
+ // the non-task-runner thread.
+ for (int i = 0; i < 20; ++i) {
+ const Closure& ith_task_runner_task =
+ this->task_tracker_->WrapTask(
+ Bind(&internal::ExpectRunsTasksOnCurrentThread,
+ true, task_runner),
+ i);
+ const Closure& ith_non_task_runner_task =
+ this->task_tracker_->WrapTask(
+ Bind(&internal::ExpectRunsTasksOnCurrentThread,
+ false, task_runner),
+ i);
+ for (int j = 0; j < i + 1; ++j) {
+ task_runner->PostTask(FROM_HERE, ith_task_runner_task);
+ thread.message_loop()->PostTask(FROM_HERE, ith_non_task_runner_task);
+ expected_task_run_counts[i] += 2;
+ }
+ }
+
+ this->delegate_.StopTaskRunner();
+ thread.Stop();
+
+ EXPECT_EQ(expected_task_run_counts,
+ this->task_tracker_->GetTaskRunCounts());
+}
+
+REGISTER_TYPED_TEST_CASE_P(
+ TaskRunnerTest, Basic, Delayed, RunsTasksOnCurrentThread);
+
+} // namespace base
+
+#endif //#define BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_
diff --git a/base/test/test_file_util.cc b/base/test/test_file_util.cc
new file mode 100644
index 0000000..8dafc58
--- /dev/null
+++ b/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/base/test/test_file_util.h b/base/test/test_file_util.h
new file mode 100644
index 0000000..27197f2
--- /dev/null
+++ b/base/test/test_file_util.h
@@ -0,0 +1,80 @@
+// 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 <string>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+
+#if defined(OS_ANDROID)
+#include <jni.h>
+#include "base/basictypes.h"
+#endif
+
+namespace base {
+
+class FilePath;
+
+// 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);
+
+// 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)
+// Returns true if the volume supports Alternate Data Streams.
+bool VolumeSupportsADS(const FilePath& path);
+
+// Returns true if the ZoneIdentifier is correctly set to "Internet" (3).
+// Note that this function must be called from the same process as
+// the one that set the zone identifier. I.e. don't use it in UI/automation
+// based tests.
+bool HasInternetZoneIdentifier(const FilePath& full_path);
+#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)
+// Register the ContentUriTestUrils JNI bindings.
+bool RegisterContentUriTestUtils(JNIEnv* env);
+
+// 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/base/test/test_file_util_android.cc b/base/test/test_file_util_android.cc
new file mode 100644
index 0000000..b8fd50c
--- /dev/null
+++ b/base/test/test_file_util_android.cc
@@ -0,0 +1,29 @@
+// 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 "jni/ContentUriTestUtils_jni.h"
+
+namespace base {
+
+bool RegisterContentUriTestUtils(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+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, base::android::GetApplicationContext(), j_path.obj());
+ std::string uri = base::android::ConvertJavaStringToUTF8(j_uri);
+ return FilePath(uri);
+}
+
+} // namespace base
diff --git a/base/test/test_file_util_linux.cc b/base/test/test_file_util_linux.cc
new file mode 100644
index 0000000..0ef5c0a
--- /dev/null
+++ b/base/test/test_file_util_linux.cc
@@ -0,0 +1,28 @@
+// 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>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_file.h"
+
+namespace base {
+
+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/base/test/test_file_util_mac.cc b/base/test/test_file_util_mac.cc
new file mode 100644
index 0000000..11592c3
--- /dev/null
+++ b/base/test/test_file_util_mac.cc
@@ -0,0 +1,51 @@
+// 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 "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 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*>(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/base/test/test_file_util_posix.cc b/base/test/test_file_util_posix.cc
new file mode 100644
index 0000000..12b892c
--- /dev/null
+++ b/base/test/test_file_util_posix.cc
@@ -0,0 +1,108 @@
+// 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 <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace base {
+
+namespace {
+
+// Deny |permission| on the file |path|.
+bool DenyFilePermission(const FilePath& path, mode_t permission) {
+ struct stat stat_buf;
+ if (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;
+
+ struct stat stat_buf;
+ if (stat(path.value().c_str(), &stat_buf) != 0)
+ return NULL;
+
+ *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);
+}
+
+#if !defined(OS_LINUX) && !defined(OS_MACOSX)
+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_(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/base/test/test_file_util_win.cc b/base/test/test_file_util_win.cc
new file mode 100644
index 0000000..fd22a63
--- /dev/null
+++ b/base/test/test_file_util_win.cc
@@ -0,0 +1,282 @@
+// 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 <windows.h>
+#include <aclapi.h>
+#include <shlwapi.h>
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/threading/platform_thread.h"
+#include "base/win/scoped_handle.h"
+
+namespace base {
+
+static const ptrdiff_t kOneMB = 1024 * 1024;
+
+namespace {
+
+struct PermissionInfo {
+ PSECURITY_DESCRIPTOR security_descriptor;
+ ACL dacl;
+};
+
+// Deny |permission| on the file |path|, for the current user.
+bool DenyFilePermission(const FilePath& path, DWORD permission) {
+ PACL old_dacl;
+ PSECURITY_DESCRIPTOR security_descriptor;
+ if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
+ SE_FILE_OBJECT,
+ DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl,
+ NULL, &security_descriptor) != ERROR_SUCCESS) {
+ return false;
+ }
+
+ EXPLICIT_ACCESS change;
+ change.grfAccessPermissions = permission;
+ change.grfAccessMode = DENY_ACCESS;
+ change.grfInheritance = 0;
+ change.Trustee.pMultipleTrustee = NULL;
+ change.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+ change.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
+ change.Trustee.TrusteeType = TRUSTEE_IS_USER;
+ change.Trustee.ptstrName = const_cast<wchar_t*>(L"CURRENT_USER");
+
+ PACL new_dacl;
+ if (SetEntriesInAcl(1, &change, old_dacl, &new_dacl) != ERROR_SUCCESS) {
+ LocalFree(security_descriptor);
+ return false;
+ }
+
+ DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()),
+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
+ NULL, NULL, new_dacl, NULL);
+ LocalFree(security_descriptor);
+ LocalFree(new_dacl);
+
+ return rc == ERROR_SUCCESS;
+}
+
+// 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(const_cast<wchar_t*>(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;
+}
+
+} // 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;
+}
+
+bool EvictFileFromSystemCache(const FilePath& file) {
+ // Request exclusive access to the file and overwrite it with no buffering.
+ base::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;
+
+ // Get some attributes to restore later.
+ BY_HANDLE_FILE_INFORMATION bhi = {0};
+ CHECK(::GetFileInformationByHandle(file_handle.Get(), &bhi));
+
+ // Execute in chunks. It could be optimized. We want to do few of these since
+ // these operations will be slow without the cache.
+
+ // Allocate a buffer for the reads and the writes.
+ char* buffer = reinterpret_cast<char*>(VirtualAlloc(NULL,
+ kOneMB,
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE));
+
+ // If the file size isn't a multiple of kOneMB, we'll need special
+ // processing.
+ bool file_is_aligned = true;
+ int total_bytes = 0;
+ DWORD bytes_read, bytes_written;
+ for (;;) {
+ bytes_read = 0;
+ ::ReadFile(file_handle.Get(), buffer, kOneMB, &bytes_read, NULL);
+ if (bytes_read == 0)
+ break;
+
+ if (bytes_read < kOneMB) {
+ // Zero out the remaining part of the buffer.
+ // WriteFile will fail if we provide a buffer size that isn't a
+ // sector multiple, so we'll have to write the entire buffer with
+ // padded zeros and then use SetEndOfFile to truncate the file.
+ ZeroMemory(buffer + bytes_read, kOneMB - bytes_read);
+ file_is_aligned = false;
+ }
+
+ // Move back to the position we just read from.
+ // Note that SetFilePointer will also fail if total_bytes isn't sector
+ // aligned, but that shouldn't happen here.
+ DCHECK((total_bytes % kOneMB) == 0);
+ SetFilePointer(file_handle.Get(), total_bytes, NULL, FILE_BEGIN);
+ if (!::WriteFile(file_handle.Get(), buffer, kOneMB, &bytes_written, NULL) ||
+ bytes_written != kOneMB) {
+ BOOL freed = VirtualFree(buffer, 0, MEM_RELEASE);
+ DCHECK(freed);
+ NOTREACHED();
+ return false;
+ }
+
+ total_bytes += bytes_read;
+
+ // If this is false, then we just processed the last portion of the file.
+ if (!file_is_aligned)
+ break;
+ }
+
+ BOOL freed = VirtualFree(buffer, 0, MEM_RELEASE);
+ DCHECK(freed);
+
+ if (!file_is_aligned) {
+ // The size of the file isn't a multiple of 1 MB, so we'll have
+ // to open the file again, this time without the FILE_FLAG_NO_BUFFERING
+ // flag and use SetEndOfFile to mark EOF.
+ file_handle.Set(NULL);
+ file_handle.Set(CreateFile(file.value().c_str(), GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, 0, NULL));
+ CHECK_NE(SetFilePointer(file_handle.Get(), total_bytes, NULL, FILE_BEGIN),
+ INVALID_SET_FILE_POINTER);
+ CHECK(::SetEndOfFile(file_handle.Get()));
+ }
+
+ // Restore the file attributes.
+ CHECK(::SetFileTime(file_handle.Get(), &bhi.ftCreationTime,
+ &bhi.ftLastAccessTime, &bhi.ftLastWriteTime));
+
+ return true;
+}
+
+// Checks if the volume supports Alternate Data Streams. This is required for
+// the Zone Identifier implementation.
+bool VolumeSupportsADS(const FilePath& path) {
+ wchar_t drive[MAX_PATH] = {0};
+ wcscpy_s(drive, MAX_PATH, path.value().c_str());
+
+ if (!PathStripToRootW(drive))
+ return false;
+
+ DWORD fs_flags = 0;
+ if (!GetVolumeInformationW(drive, NULL, 0, 0, NULL, &fs_flags, NULL, 0))
+ return false;
+
+ if (fs_flags & FILE_NAMED_STREAMS)
+ return true;
+
+ return false;
+}
+
+// Return whether the ZoneIdentifier is correctly set to "Internet" (3)
+// Only returns a valid result when called from same process as the
+// one that (was supposed to have) set the zone identifier.
+bool HasInternetZoneIdentifier(const FilePath& full_path) {
+ FilePath zone_path(full_path.value() + L":Zone.Identifier");
+ std::string zone_path_contents;
+ if (!ReadFileToString(zone_path, &zone_path_contents))
+ return false;
+
+ std::vector<std::string> lines;
+ // This call also trims whitespaces, including carriage-returns (\r).
+ SplitString(zone_path_contents, '\n', &lines);
+
+ switch (lines.size()) {
+ case 3:
+ // optional empty line at end of file:
+ if (lines[2] != "")
+ return false;
+ // fall through:
+ case 2:
+ return lines[0] == "[ZoneTransfer]" && lines[1] == "ZoneId=3";
+ default:
+ return false;
+ }
+}
+
+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/base/test/test_io_thread.cc b/base/test/test_io_thread.cc
new file mode 100644
index 0000000..48c1e16
--- /dev/null
+++ b/base/test/test_io_thread.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/test_io_thread.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace {
+
+void PostTaskAndWaitHelper(base::WaitableEvent* event,
+ const base::Closure& task) {
+ task.Run();
+ event->Signal();
+}
+
+} // namespace
+
+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::MessageLoop::TYPE_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 tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ task_runner()->PostTask(from_here, task);
+}
+
+void TestIOThread::PostTaskAndWait(const tracked_objects::Location& from_here,
+ const base::Closure& task) {
+ base::WaitableEvent event(false, false);
+ task_runner()->PostTask(from_here,
+ base::Bind(&PostTaskAndWaitHelper, &event, task));
+ event.Wait();
+}
+
+} // namespace base
diff --git a/base/test/test_io_thread.h b/base/test/test_io_thread.h
new file mode 100644
index 0000000..c2ed187
--- /dev/null
+++ b/base/test/test_io_thread.h
@@ -0,0 +1,59 @@
+// 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().
+class TestIOThread {
+ public:
+ enum Mode { kAutoStart, kManualStart };
+ explicit TestIOThread(Mode mode);
+ // Stops the I/O thread if necessary.
+ ~TestIOThread();
+
+ // |Start()|/|Stop()| should only be called from the main (creation) thread.
+ // 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 tracked_objects::Location& from_here,
+ const base::Closure& task);
+ // Posts |task| to the IO-thread with an WaitableEvent associated blocks on
+ // it until the posted |task| is executed, then returns.
+ void PostTaskAndWait(const tracked_objects::Location& from_here,
+ const base::Closure& task);
+
+ base::MessageLoopForIO* message_loop() {
+ return static_cast<base::MessageLoopForIO*>(io_thread_.message_loop());
+ }
+
+ scoped_refptr<SingleThreadTaskRunner> task_runner() {
+ return message_loop()->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/base/test/test_listener_ios.h b/base/test/test_listener_ios.h
new file mode 100644
index 0000000..c312250
--- /dev/null
+++ b/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/base/test/test_listener_ios.mm b/base/test/test_listener_ios.mm
new file mode 100644
index 0000000..12cf5bb
--- /dev/null
+++ b/base/test/test_listener_ios.mm
@@ -0,0 +1,45 @@
+// 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 "base/mac/scoped_nsautorelease_pool.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) {
+ base::mac::ScopedNSAutoreleasePool scoped_pool;
+
+ // 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/base/test/test_pending_task.cc b/base/test/test_pending_task.cc
new file mode 100644
index 0000000..94989de
--- /dev/null
+++ b/base/test/test_pending_task.cc
@@ -0,0 +1,77 @@
+// 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 <string>
+
+#include "base/test/test_pending_task.h"
+
+namespace base {
+
+TestPendingTask::TestPendingTask() : nestability(NESTABLE) {}
+
+TestPendingTask::TestPendingTask(
+ const tracked_objects::Location& location,
+ const Closure& task,
+ TimeTicks post_time,
+ TimeDelta delay,
+ TestNestability nestability)
+ : location(location),
+ task(task),
+ post_time(post_time),
+ delay(delay),
+ nestability(nestability) {}
+
+TimeTicks TestPendingTask::GetTimeToRun() const {
+ return post_time + delay;
+}
+
+bool TestPendingTask::ShouldRunBefore(const TestPendingTask& other) const {
+ if (nestability != other.nestability)
+ return (nestability == NESTABLE);
+ return GetTimeToRun() < other.GetTimeToRun();
+}
+
+TestPendingTask::~TestPendingTask() {}
+
+void TestPendingTask::AsValueInto(base::debug::TracedValue* state) const {
+ state->SetInteger("run_at", GetTimeToRun().ToInternalValue());
+ state->SetString("posting_function", location.ToString());
+ state->SetInteger("post_time", post_time.ToInternalValue());
+ state->SetInteger("delay", delay.ToInternalValue());
+ switch (nestability) {
+ case NESTABLE:
+ state->SetString("nestability", "NESTABLE");
+ break;
+ case NON_NESTABLE:
+ state->SetString("nestability", "NON_NESTABLE");
+ break;
+ }
+ state->SetInteger("delay", delay.ToInternalValue());
+}
+
+scoped_refptr<base::debug::ConvertableToTraceFormat> TestPendingTask::AsValue()
+ const {
+ scoped_refptr<base::debug::TracedValue> state =
+ new base::debug::TracedValue();
+ AsValueInto(state.get());
+ return state;
+}
+
+std::string TestPendingTask::ToString() const {
+ std::string output("TestPendingTask(");
+ AsValue()->AppendAsTraceFormat(&output);
+ output += ")";
+ return output;
+}
+
+std::ostream& operator<<(std::ostream& os, const TestPendingTask& task) {
+ PrintTo(task, &os);
+ return os;
+}
+
+void PrintTo(const TestPendingTask& task, std::ostream* os) {
+ *os << task.ToString();
+}
+
+} // namespace base
diff --git a/base/test/test_pending_task.h b/base/test/test_pending_task.h
new file mode 100644
index 0000000..fa06800
--- /dev/null
+++ b/base/test/test_pending_task.h
@@ -0,0 +1,72 @@
+// 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_PENDING_TASK_H_
+#define BASE_TEST_TEST_PENDING_TASK_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/debug/trace_event_argument.h"
+#include "base/location.h"
+#include "base/time/time.h"
+
+namespace base {
+
+// TestPendingTask is a helper class for test TaskRunner
+// implementations. See test_simple_task_runner.h for example usage.
+
+struct TestPendingTask {
+ enum TestNestability { NESTABLE, NON_NESTABLE };
+
+ TestPendingTask();
+ TestPendingTask(const tracked_objects::Location& location,
+ const Closure& task,
+ TimeTicks post_time,
+ TimeDelta delay,
+ TestNestability nestability);
+ ~TestPendingTask();
+
+ // Returns post_time + delay.
+ TimeTicks GetTimeToRun() const;
+
+ // Returns true if this task is nestable and |other| isn't, or if
+ // this task's time to run is strictly earlier than |other|'s time
+ // to run.
+ //
+ // Note that two tasks may both have the same nestability and delay.
+ // In that case, the caller must use some other criterion (probably
+ // the position in some queue) to break the tie. Conveniently, the
+ // following STL functions already do so:
+ //
+ // - std::min_element
+ // - std::stable_sort
+ //
+ // but the following STL functions don't:
+ //
+ // - std::max_element
+ // - std::sort.
+ bool ShouldRunBefore(const TestPendingTask& other) const;
+
+ tracked_objects::Location location;
+ Closure task;
+ TimeTicks post_time;
+ TimeDelta delay;
+ TestNestability nestability;
+
+ // Functions for using test pending task with tracing, useful in unit
+ // testing.
+ void AsValueInto(base::debug::TracedValue* state) const;
+ scoped_refptr<base::debug::ConvertableToTraceFormat> AsValue() const;
+ std::string ToString() const;
+};
+
+// gtest helpers which allow pretty printing of the tasks, very useful in unit
+// testing.
+std::ostream& operator<<(std::ostream& os, const TestPendingTask& task);
+void PrintTo(const TestPendingTask& task, std::ostream* os);
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_PENDING_TASK_H_
diff --git a/base/test/test_pending_task_unittest.cc b/base/test/test_pending_task_unittest.cc
new file mode 100644
index 0000000..4a6bd24
--- /dev/null
+++ b/base/test/test_pending_task_unittest.cc
@@ -0,0 +1,55 @@
+// 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_pending_task.h"
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest-spi.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(TestPendingTaskTest, TraceSupport) {
+ base::TestPendingTask task;
+
+ // Check that TestPendingTask can be sent to the trace subsystem.
+ TRACE_EVENT1("test", "TestPendingTask::TraceSupport", "task", task.AsValue());
+
+ // Just a basic check that the trace output has *something* in it.
+ EXPECT_THAT(task.AsValue()->ToString(), ::testing::HasSubstr("post_time"));
+}
+
+TEST(TestPendingTaskTest, ToString) {
+ base::TestPendingTask task;
+
+ // Just a basic check that ToString has *something* in it.
+ EXPECT_THAT(task.ToString(), ::testing::StartsWith("TestPendingTask("));
+}
+
+TEST(TestPendingTaskTest, GTestPrettyPrint) {
+ base::TestPendingTask task;
+
+ // Check that gtest is calling the TestPendingTask's PrintTo method.
+ EXPECT_THAT(::testing::PrintToString(task),
+ ::testing::StartsWith("TestPendingTask("));
+
+ // Check that pretty printing works with the gtest iostreams operator.
+ EXPECT_NONFATAL_FAILURE(EXPECT_TRUE(false) << task, "TestPendingTask(");
+}
+
+TEST(TestPendingTaskTest, ShouldRunBefore) {
+ base::TestPendingTask task_first;
+ task_first.delay = base::TimeDelta::FromMilliseconds(1);
+ base::TestPendingTask task_after;
+ task_after.delay = base::TimeDelta::FromMilliseconds(2);
+
+ EXPECT_FALSE(task_after.ShouldRunBefore(task_first))
+ << task_after << ".ShouldRunBefore(" << task_first << ")\n";
+ EXPECT_TRUE(task_first.ShouldRunBefore(task_after))
+ << task_first << ".ShouldRunBefore(" << task_after << ")\n";
+}
+
+} // namespace
diff --git a/base/test/test_reg_util_win.cc b/base/test/test_reg_util_win.cc
new file mode 100644
index 0000000..2cbafef
--- /dev/null
+++ b/base/test/test_reg_util_win.cc
@@ -0,0 +1,105 @@
+// 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 "base/guid.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace registry_util {
+
+namespace {
+
+const wchar_t kTimestampDelimiter[] = L"$";
+const wchar_t kTempTestKeyPath[] = L"Software\\Chromium\\TempTestKeys";
+
+void DeleteStaleTestKeys(const base::Time& now,
+ const base::string16& 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) {
+ base::string16 key_name = iterator_test_root_key.Name();
+ std::vector<base::string16> tokens;
+ if (!Tokenize(key_name, base::string16(kTimestampDelimiter), &tokens))
+ continue;
+ int64 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());
+ }
+}
+
+base::string16 GenerateTempKeyPath(const base::string16& test_key_root,
+ const base::Time& timestamp) {
+ base::string16 key_path = test_key_root;
+ key_path += L"\\" + base::Int64ToString16(timestamp.ToInternalValue());
+ key_path += kTimestampDelimiter + base::ASCIIToWide(base::GenerateGUID());
+
+ return key_path;
+}
+
+} // namespace
+
+RegistryOverrideManager::ScopedRegistryKeyOverride::ScopedRegistryKeyOverride(
+ HKEY override,
+ const base::string16& key_path)
+ : override_(override) {
+ EXPECT_EQ(
+ ERROR_SUCCESS,
+ temp_key_.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_ALL_ACCESS));
+ EXPECT_EQ(ERROR_SUCCESS,
+ ::RegOverridePredefKey(override_, temp_key_.Handle()));
+}
+
+RegistryOverrideManager::
+ ScopedRegistryKeyOverride::~ScopedRegistryKeyOverride() {
+ ::RegOverridePredefKey(override_, NULL);
+ temp_key_.DeleteKey(L"");
+}
+
+RegistryOverrideManager::RegistryOverrideManager()
+ : timestamp_(base::Time::Now()), test_key_root_(kTempTestKeyPath) {
+ DeleteStaleTestKeys(timestamp_, test_key_root_);
+}
+
+RegistryOverrideManager::RegistryOverrideManager(
+ const base::Time& timestamp,
+ const base::string16& test_key_root)
+ : timestamp_(timestamp), test_key_root_(test_key_root) {
+ DeleteStaleTestKeys(timestamp_, test_key_root_);
+}
+
+RegistryOverrideManager::~RegistryOverrideManager() {}
+
+void RegistryOverrideManager::OverrideRegistry(HKEY override) {
+ base::string16 key_path = GenerateTempKeyPath(test_key_root_, timestamp_);
+ overrides_.push_back(new ScopedRegistryKeyOverride(override, key_path));
+}
+
+base::string16 GenerateTempKeyPath() {
+ return GenerateTempKeyPath(base::string16(kTempTestKeyPath),
+ base::Time::Now());
+}
+
+} // namespace registry_util
diff --git a/base/test/test_reg_util_win.h b/base/test/test_reg_util_win.h
new file mode 100644
index 0000000..db71838
--- /dev/null
+++ b/base/test/test_reg_util_win.h
@@ -0,0 +1,77 @@
+// 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_H_
+#define BASE_TEST_TEST_REG_UTIL_H_
+
+// Registry utility functions used only by tests.
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string16.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.
+ void OverrideRegistry(HKEY override);
+
+ private:
+ friend class RegistryOverrideManagerTest;
+
+ // Keeps track of one override.
+ class ScopedRegistryKeyOverride {
+ public:
+ ScopedRegistryKeyOverride(HKEY override, const base::string16& key_path);
+ ~ScopedRegistryKeyOverride();
+
+ private:
+ HKEY override_;
+ base::win::RegKey temp_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedRegistryKeyOverride);
+ };
+
+ // Used for testing only.
+ RegistryOverrideManager(const base::Time& timestamp,
+ const base::string16& test_key_root);
+
+ base::Time timestamp_;
+ base::string16 guid_;
+
+ base::string16 test_key_root_;
+ ScopedVector<ScopedRegistryKeyOverride> overrides_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegistryOverrideManager);
+};
+
+// Generates a temporary key path that will be eventually deleted
+// automatically if the process crashes.
+base::string16 GenerateTempKeyPath();
+
+} // namespace registry_util
+
+#endif // BASE_TEST_TEST_REG_UTIL_H_
diff --git a/base/test/test_reg_util_win_unittest.cc b/base/test/test_reg_util_win_unittest.cc
new file mode 100644
index 0000000..11abe5d
--- /dev/null
+++ b/base/test/test_reg_util_win_unittest.cc
@@ -0,0 +1,130 @@
+// 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/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_reg_util_win.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);
+ }
+
+ virtual ~RegistryOverrideManagerTest() {
+ base::win::RegKey key(HKEY_CURRENT_USER);
+ key.DeleteKey(fake_test_key_root_.c_str());
+ }
+
+ void AssertKeyExists(const base::string16& 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 base::string16& 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 base::string16& key_path) {
+ base::win::RegKey key;
+ EXPECT_EQ(ERROR_SUCCESS,
+ key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_ALL_ACCESS));
+ }
+
+ base::string16 FakeOverrideManagerPath(const base::Time& time) {
+ return fake_test_key_root_ + L"\\" +
+ base::Int64ToString16(time.ToInternalValue());
+ }
+
+ void CreateManager(const base::Time& timestamp) {
+ manager_.reset(new RegistryOverrideManager(timestamp, fake_test_key_root_));
+ manager_->OverrideRegistry(HKEY_CURRENT_USER);
+ }
+
+ base::string16 fake_test_key_root_;
+ scoped_ptr<RegistryOverrideManager> manager_;
+};
+
+TEST_F(RegistryOverrideManagerTest, Basic) {
+ 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();
+
+ 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(42, value);
+ read_key.Close();
+
+ manager_.reset();
+
+ AssertKeyAbsent(kTestKeyPath);
+}
+
+TEST_F(RegistryOverrideManagerTest, DeleteStaleKeys) {
+ base::Time::Exploded kTestTimeExploded = {2013, 11, 1, 4, 0, 0, 0, 0};
+ base::Time kTestTime = base::Time::FromUTCExploded(kTestTimeExploded);
+
+ base::string16 path_garbage = fake_test_key_root_ + L"\\Blah";
+ base::string16 path_very_stale =
+ FakeOverrideManagerPath(kTestTime - base::TimeDelta::FromDays(100));
+ base::string16 path_stale =
+ FakeOverrideManagerPath(kTestTime - base::TimeDelta::FromDays(5));
+ base::string16 path_current =
+ FakeOverrideManagerPath(kTestTime - base::TimeDelta::FromMinutes(1));
+ base::string16 path_future =
+ FakeOverrideManagerPath(kTestTime + base::TimeDelta::FromMinutes(1));
+
+ CreateKey(path_garbage);
+ CreateKey(path_very_stale);
+ CreateKey(path_stale);
+ CreateKey(path_current);
+ CreateKey(path_future);
+
+ CreateManager(kTestTime);
+ manager_.reset();
+
+ AssertKeyAbsent(path_garbage);
+ AssertKeyAbsent(path_very_stale);
+ AssertKeyAbsent(path_stale);
+ AssertKeyExists(path_current);
+ AssertKeyExists(path_future);
+}
+
+} // namespace registry_util
diff --git a/base/test/test_shortcut_win.cc b/base/test/test_shortcut_win.cc
new file mode 100644
index 0000000..c772d87
--- /dev/null
+++ b/base/test/test_shortcut_win.cc
@@ -0,0 +1,155 @@
+// 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 <shlobj.h>
+#include <propkey.h>
+
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_comptr.h"
+#include "base/win/scoped_propvariant.h"
+#include "base/win/windows_version.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace win {
+
+void ValidatePathsAreEqual(const base::FilePath& expected_path,
+ const base::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();
+
+ base::FilePath long_expected_path(long_expected_path_chars);
+ base::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 base::FilePath& shortcut_path,
+ const ShortcutProperties& properties) {
+ ScopedComPtr<IShellLink> i_shell_link;
+ ScopedComPtr<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 = i_shell_link.CreateInstance(
+ CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER)));
+ if (FAILED(hr))
+ return;
+
+ EXPECT_TRUE(SUCCEEDED(hr = i_persist_file.QueryFrom(i_shell_link)));
+ 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, base::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,
+ base::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, base::FilePath(read_icon));
+ EXPECT_EQ(properties.icon_index, read_icon_index);
+ }
+
+ if (GetVersion() >= VERSION_WIN7) {
+ ScopedComPtr<IPropertyStore> property_store;
+ EXPECT_TRUE(SUCCEEDED(hr = property_store.QueryFrom(i_shell_link)));
+ 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/base/test/test_shortcut_win.h b/base/test/test_shortcut_win.h
new file mode 100644
index 0000000..b828e8b
--- /dev/null
+++ b/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/base/test/test_simple_task_runner.cc b/base/test/test_simple_task_runner.cc
new file mode 100644
index 0000000..cc39fab
--- /dev/null
+++ b/base/test/test_simple_task_runner.cc
@@ -0,0 +1,82 @@
+// 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 "base/logging.h"
+
+namespace base {
+
+TestSimpleTaskRunner::TestSimpleTaskRunner() {}
+
+TestSimpleTaskRunner::~TestSimpleTaskRunner() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+bool TestSimpleTaskRunner::PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const Closure& task,
+ TimeDelta delay) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ pending_tasks_.push_back(
+ TestPendingTask(from_here, task, TimeTicks(), delay,
+ TestPendingTask::NESTABLE));
+ return true;
+}
+
+bool TestSimpleTaskRunner::PostNonNestableDelayedTask(
+ const tracked_objects::Location& from_here,
+ const Closure& task,
+ TimeDelta delay) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ pending_tasks_.push_back(
+ TestPendingTask(from_here, task, TimeTicks(), delay,
+ TestPendingTask::NON_NESTABLE));
+ return true;
+}
+
+bool TestSimpleTaskRunner::RunsTasksOnCurrentThread() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return true;
+}
+
+const std::deque<TestPendingTask>&
+TestSimpleTaskRunner::GetPendingTasks() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return pending_tasks_;
+}
+
+bool TestSimpleTaskRunner::HasPendingTask() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return !pending_tasks_.empty();
+}
+
+base::TimeDelta TestSimpleTaskRunner::NextPendingTaskDelay() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return pending_tasks_.front().GetTimeToRun() - base::TimeTicks();
+}
+
+void TestSimpleTaskRunner::ClearPendingTasks() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ pending_tasks_.clear();
+}
+
+void TestSimpleTaskRunner::RunPendingTasks() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Swap with a local variable to avoid re-entrancy problems.
+ std::deque<TestPendingTask> tasks_to_run;
+ tasks_to_run.swap(pending_tasks_);
+ for (std::deque<TestPendingTask>::iterator it = tasks_to_run.begin();
+ it != tasks_to_run.end(); ++it) {
+ it->task.Run();
+ }
+}
+
+void TestSimpleTaskRunner::RunUntilIdle() {
+ while (!pending_tasks_.empty()) {
+ RunPendingTasks();
+ }
+}
+
+} // namespace base
diff --git a/base/test/test_simple_task_runner.h b/base/test/test_simple_task_runner.h
new file mode 100644
index 0000000..af4f4eb
--- /dev/null
+++ b/base/test/test_simple_task_runner.h
@@ -0,0 +1,86 @@
+// 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 <deque>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/test_pending_task.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+
+class TimeDelta;
+
+// 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:
+//
+// - It is non-thread safe; all member functions must be called on
+// the same thread.
+// - 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).
+//
+// If you need more complicated properties, consider using this class
+// as a template for writing a test TaskRunner implementation using
+// TestPendingTask.
+//
+// Note that, like any TaskRunner, TestSimpleTaskRunner is
+// ref-counted.
+class TestSimpleTaskRunner : public SingleThreadTaskRunner {
+ public:
+ TestSimpleTaskRunner();
+
+ // SingleThreadTaskRunner implementation.
+ virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
+ const Closure& task,
+ TimeDelta delay) OVERRIDE;
+ virtual bool PostNonNestableDelayedTask(
+ const tracked_objects::Location& from_here,
+ const Closure& task,
+ TimeDelta delay) OVERRIDE;
+
+ virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
+
+ const std::deque<TestPendingTask>& GetPendingTasks() const;
+ bool HasPendingTask() const;
+ base::TimeDelta NextPendingTaskDelay() const;
+
+ // Clears the queue of pending tasks without running them.
+ void ClearPendingTasks();
+
+ // Runs each current pending task in order and clears the queue.
+ // Any tasks posted by the tasks are not run.
+ virtual void RunPendingTasks();
+
+ // Runs pending tasks until the queue is empty.
+ void RunUntilIdle();
+
+ protected:
+ virtual ~TestSimpleTaskRunner();
+
+ std::deque<TestPendingTask> pending_tasks_;
+ ThreadChecker thread_checker_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestSimpleTaskRunner);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_SIMPLE_TASK_RUNNER_H_
diff --git a/base/test/test_suite.cc b/base/test/test_suite.cc
new file mode 100644
index 0000000..45b02f9
--- /dev/null
+++ b/base/test/test_suite.cc
@@ -0,0 +1,345 @@
+// 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 "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/stack_trace.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/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/process/memory.h"
+#include "base/test/gtest_xml_util.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_switches.h"
+#include "base/test/test_timeouts.h"
+#include "base/time/time.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"
+#if defined(OS_IOS)
+#include "base/test/test_listener_ios.h"
+#else
+#include "base/test/mock_chrome_application_mac.h"
+#endif // OS_IOS
+#endif // OS_MACOSX
+
+#if defined(OS_ANDROID)
+#include "base/test/test_support_android.h"
+#endif
+
+#if defined(OS_IOS)
+#include "base/test/test_support_ios.h"
+#endif
+
+namespace {
+
+class MaybeTestDisabler : public testing::EmptyTestEventListener {
+ public:
+ virtual void OnTestStart(const testing::TestInfo& test_info) OVERRIDE {
+ ASSERT_FALSE(TestSuite::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 TestClientInitializer : public testing::EmptyTestEventListener {
+ public:
+ TestClientInitializer()
+ : old_command_line_(CommandLine::NO_PROGRAM) {
+ }
+
+ virtual void OnTestStart(const testing::TestInfo& test_info) OVERRIDE {
+ old_command_line_ = *CommandLine::ForCurrentProcess();
+ }
+
+ virtual void OnTestEnd(const testing::TestInfo& test_info) OVERRIDE {
+ *CommandLine::ForCurrentProcess() = old_command_line_;
+ }
+
+ private:
+ CommandLine old_command_line_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestClientInitializer);
+};
+
+} // namespace
+
+namespace base {
+
+int RunUnitTestsUsingBaseTestSuite(int argc, char **argv) {
+ TestSuite test_suite(argc, argv);
+ return base::LaunchUnitTests(
+ argc, argv, Bind(&TestSuite::Run, Unretained(&test_suite)));
+}
+
+} // namespace base
+
+TestSuite::TestSuite(int argc, char** argv) : initialized_command_line_(false) {
+ PreInitialize(true);
+ InitializeFromCommandLine(argc, argv);
+}
+
+#if defined(OS_WIN)
+TestSuite::TestSuite(int argc, wchar_t** argv)
+ : initialized_command_line_(false) {
+ PreInitialize(true);
+ InitializeFromCommandLine(argc, argv);
+}
+#endif // defined(OS_WIN)
+
+TestSuite::TestSuite(int argc, char** argv, bool create_at_exit_manager)
+ : initialized_command_line_(false) {
+ PreInitialize(create_at_exit_manager);
+ InitializeFromCommandLine(argc, argv);
+}
+
+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(bool create_at_exit_manager) {
+#if defined(OS_WIN)
+ testing::GTEST_FLAG(catch_exceptions) = false;
+#endif
+ base::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, "");
+#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)
+ if (create_at_exit_manager)
+ at_exit_manager_.reset(new base::AtExitManager);
+#endif
+
+ // Don't add additional code to this function. Instead add it to
+ // Initialize(). See bug 6436.
+}
+
+
+// static
+bool TestSuite::IsMarkedMaybe(const testing::TestInfo& test) {
+ return strncmp(test.name(), "MAYBE_", 6) == 0;
+}
+
+void TestSuite::CatchMaybeTests() {
+ testing::TestEventListeners& listeners =
+ testing::UnitTest::GetInstance()->listeners();
+ listeners.Append(new MaybeTestDisabler);
+}
+
+void TestSuite::ResetCommandLine() {
+ testing::TestEventListeners& listeners =
+ testing::UnitTest::GetInstance()->listeners();
+ listeners.Append(new TestClientInitializer);
+}
+
+#if !defined(OS_IOS)
+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;
+ }
+
+ XmlUnitTestResultPrinter* printer = new XmlUnitTestResultPrinter;
+ CHECK(printer->Initialize(output_path));
+ testing::TestEventListeners& listeners =
+ testing::UnitTest::GetInstance()->listeners();
+ listeners.Append(printer);
+}
+#endif // !defined(OS_IOS)
+
+// 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)
+ base::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)
+ base::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;
+}
+
+// static
+void TestSuite::UnitTestAssertHandler(const std::string& str) {
+#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)
+
+ // The logging system actually prints the message before calling the assert
+ // handler. Just exit now to avoid printing too many stack traces.
+ _exit(1);
+}
+
+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) && defined(_HAS_EXCEPTIONS) && (_HAS_EXCEPTIONS == 1)
+ // Suppress the "Debug Assertion Failed" dialog.
+ // TODO(hbono): remove this code when gtest has it.
+ // http://groups.google.com/d/topic/googletestframework/OjuwNlXy5ac/discussion
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+ _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+#endif // defined(_DEBUG) && defined(_HAS_EXCEPTIONS) && (_HAS_EXCEPTIONS == 1)
+#endif // defined(OS_WIN)
+}
+
+void TestSuite::Initialize() {
+#if !defined(OS_IOS)
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kWaitForDebugger)) {
+ base::debug::WaitForDebugger(60, true);
+ }
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Some of the app unit tests spin runloops.
+ mock_cr_app::RegisterMockCrApp();
+#endif
+
+#if defined(OS_IOS)
+ InitIOSTestMessageLoop();
+#endif // OS_IOS
+
+#if defined(OS_ANDROID)
+ InitAndroidTest();
+#else
+ // Initialize logging.
+ base::FilePath exe;
+ PathService::Get(base::FILE_EXE, &exe);
+ base::FilePath log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log"));
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_ALL;
+ settings.log_file = log_filename.value().c_str();
+ 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 // else defined(OS_ANDROID)
+
+ CHECK(base::debug::EnableInProcessStackDumping());
+#if defined(OS_WIN)
+ // Make sure we run with high resolution timer to minimize differences
+ // between production code and test code.
+ base::Time::EnableHighResolutionTimer(true);
+#endif // defined(OS_WIN)
+
+ // In some cases, we do not want to see standard error dialogs.
+ if (!base::debug::BeingDebugged() &&
+ !CommandLine::ForCurrentProcess()->HasSwitch("show-error-dialogs")) {
+ SuppressErrorDialogs();
+ base::debug::SetSuppressDebugUI(true);
+ logging::SetLogAssertHandler(UnitTestAssertHandler);
+ }
+
+ base::i18n::InitializeICU();
+
+ CatchMaybeTests();
+ ResetCommandLine();
+#if !defined(OS_IOS)
+ AddTestLauncherResultPrinter();
+#endif // !defined(OS_IOS)
+
+ TestTimeouts::Initialize();
+
+ trace_to_file_.BeginTracingFromCommandLineOptions();
+}
+
+void TestSuite::Shutdown() {
+}
diff --git a/base/test/test_suite.h b/base/test/test_suite.h
new file mode 100644
index 0000000..fa0ab6c
--- /dev/null
+++ b/base/test/test_suite.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_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 <string>
+
+#include "base/at_exit.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/test/trace_to_file.h"
+
+namespace testing {
+class TestInfo;
+}
+
+namespace base {
+
+// 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();
+
+ // 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.
+ static bool IsMarkedMaybe(const testing::TestInfo& test);
+
+ void CatchMaybeTests();
+
+ void ResetCommandLine();
+
+ void AddTestLauncherResultPrinter();
+
+ int Run();
+
+ protected:
+ // This constructor is only accessible to specialized test suite
+ // implementations which need to control the creation of an AtExitManager
+ // instance for the duration of the test.
+ TestSuite(int argc, char** argv, bool create_at_exit_manager);
+
+ // 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.
+ static void UnitTestAssertHandler(const std::string& str);
+
+ // 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.
+ scoped_ptr<base::AtExitManager> at_exit_manager_;
+
+ private:
+ 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(bool create_at_exit_manager);
+
+ test::TraceToFile trace_to_file_;
+
+ bool initialized_command_line_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSuite);
+};
+
+} // namespace base
+
+// TODO(brettw) remove this. This is a temporary hack to allow WebKit to compile
+// until we can update it to use "base::" (preventing a two-sided patch).
+using base::TestSuite;
+
+#endif // BASE_TEST_TEST_SUITE_H_
diff --git a/base/test/test_support_android.cc b/base/test/test_support_android.cc
new file mode 100644
index 0000000..4ada567
--- /dev/null
+++ b/base/test/test_support_android.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 <stdarg.h>
+#include <string.h>
+
+#include "base/android/path_utils.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_pump_android.h"
+#include "base/path_service.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace {
+
+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 = NULL;
+
+// 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 Singleton<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 DefaultSingletonTraits<Waitable>;
+
+ Waitable()
+ : waitable_event_(false, false) {
+ }
+
+ base::WaitableEvent waitable_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(Waitable);
+};
+
+// The MessagePumpForUI implementation for test purpose.
+class MessagePumpForUIStub : public base::MessagePumpForUI {
+ virtual ~MessagePumpForUIStub() {}
+
+ virtual void Start(base::MessagePump::Delegate* delegate) OVERRIDE {
+ NOTREACHED() << "The Start() method shouldn't be called in test, using"
+ " Run() method should be used.";
+ }
+
+ virtual 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;
+
+ bool more_work_is_plausible = true;
+
+ for (;;) {
+ if (!more_work_is_plausible) {
+ Waitable::GetInstance()->Block();
+ if (g_state->should_quit)
+ break;
+ }
+
+ more_work_is_plausible = g_state->delegate->DoWork();
+ if (g_state->should_quit)
+ break;
+
+ base::TimeTicks delayed_work_time;
+ more_work_is_plausible |=
+ g_state->delegate->DoDelayedWork(&delayed_work_time);
+ 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 |= !delayed_work_time.is_null();
+ }
+
+ g_state = previous_state;
+ }
+
+ virtual void Quit() OVERRIDE {
+ Waitable::GetInstance()->Quit();
+ }
+
+ virtual void ScheduleWork() OVERRIDE {
+ Waitable::GetInstance()->Signal();
+ }
+
+ virtual void ScheduleDelayedWork(
+ const base::TimeTicks& delayed_work_time) OVERRIDE {
+ Waitable::GetInstance()->Signal();
+ }
+};
+
+scoped_ptr<base::MessagePump> CreateMessagePumpForUIStub() {
+ return scoped_ptr<base::MessagePump>(new MessagePumpForUIStub());
+};
+
+// Provides the test path for DIR_MODULE and DIR_ANDROID_APP_DATA.
+bool GetTestProviderPath(int key, base::FilePath* result) {
+ switch (key) {
+ case base::DIR_ANDROID_APP_DATA: {
+ // For tests, app data is put in external storage.
+ return base::android::GetExternalStorageDirectory(result);
+ }
+ 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) && !PathService::Override(key, path))
+ PathService::RegisterProvider(&GetTestProviderPath, key, key + 1);
+}
+
+} // namespace
+
+namespace base {
+
+void InitAndroidTestLogging() {
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ 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() {
+ InitPathProvider(DIR_MODULE);
+ InitPathProvider(DIR_ANDROID_APP_DATA);
+}
+
+void InitAndroidTestMessageLoop() {
+ if (!MessageLoop::InitMessagePumpForUIFactory(&CreateMessagePumpForUIStub))
+ LOG(INFO) << "MessagePumpForUIFactory already set, unable to override.";
+}
+
+void InitAndroidTest() {
+ InitAndroidTestLogging();
+ InitAndroidTestPaths();
+ InitAndroidTestMessageLoop();
+}
+} // namespace base
diff --git a/base/test/test_support_android.h b/base/test/test_support_android.h
new file mode 100644
index 0000000..062785e
--- /dev/null
+++ b/base/test/test_support_android.h
@@ -0,0 +1,26 @@
+// 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 {
+
+// 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();
+
+// Init the message loop for tests on Android.
+BASE_EXPORT void InitAndroidTestMessageLoop();
+
+// Do all of the initializations above.
+BASE_EXPORT void InitAndroidTest();
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_SUPPORT_ANDROID_H_
diff --git a/base/test/test_support_ios.h b/base/test/test_support_ios.h
new file mode 100644
index 0000000..c71cf0d
--- /dev/null
+++ b/base/test/test_support_ios.h
@@ -0,0 +1,24 @@
+// 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();
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_SUPPORT_IOS_H_
diff --git a/base/test/test_support_ios.mm b/base/test/test_support_ios.mm
new file mode 100644
index 0000000..80a4caf
--- /dev/null
+++ b/base/test/test_support_ios.mm
@@ -0,0 +1,212 @@
+// 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 <UIKit/UIKit.h>
+
+#include "base/debug/debugger.h"
+#include "base/logging.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_pump_default.h"
+#include "base/test/test_suite.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 {
+ @private
+ 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_ 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];
+
+ if ([self shouldRedirectOutputToFile])
+ [self redirectOutput];
+
+ // Queue up the test run.
+ [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 compleete. 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
+ return !base::debug::BeingDebugged();
+#endif // TARGET_IPHONE_SIMULATOR
+ return NO;
+}
+
+// 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);
+ }
+ }
+}
+
+- (void)runTests {
+ int exitStatus = g_test_suite->Run();
+
+ if ([self shouldRedirectOutputToFile])
+ [self writeOutputToNSLog];
+
+ // 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.
+ // TODO(ios): crbug.com/137010 Figure out how much time is actually needed,
+ // and sleep only to make sure that much time has elapsed since launch.
+ [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 {
+
+scoped_ptr<base::MessagePump> CreateMessagePumpForUIForTests() {
+ // A default MessagePump will do quite nicely in tests.
+ return scoped_ptr<base::MessagePump>(new base::MessagePumpDefault());
+}
+
+} // namespace
+
+namespace base {
+
+void InitIOSTestMessageLoop() {
+ MessageLoop::InitMessagePumpForUIFactory(&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;
+ mac::ScopedNSAutoreleasePool pool;
+ int exit_status = UIApplicationMain(g_argc, g_argv, nil,
+ @"ChromeUnitTestDelegate");
+ exit(exit_status);
+ }
+}
+
+} // namespace base
diff --git a/base/test/test_switches.cc b/base/test/test_switches.cc
new file mode 100644
index 0000000..c970fd2
--- /dev/null
+++ b/base/test/test_switches.cc
@@ -0,0 +1,56 @@
+// 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"
+
+// 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";
+
+// Path to file containing test filter (one pattern per line).
+const char switches::kTestLauncherFilterFile[] = "test-launcher-filter-file";
+
+// Number of parallel test launcher jobs.
+const char switches::kTestLauncherJobs[] = "test-launcher-jobs";
+
+// Path to test results file in our custom test launcher format.
+const char switches::kTestLauncherOutput[] = "test-launcher-output";
+
+// Maximum number of times to retry a test after failure.
+const char switches::kTestLauncherRetryLimit[] = "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";
+
+// Flag controlling when test stdio is displayed as part of the launcher's
+// standard output.
+const char switches::kTestLauncherPrintTestStdio[] =
+ "test-launcher-print-test-stdio";
+
+// 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";
+
+// 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";
+// 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";
diff --git a/base/test/test_switches.h b/base/test/test_switches.h
new file mode 100644
index 0000000..c228cf0
--- /dev/null
+++ b/base/test/test_switches.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_SWITCHES_H_
+#define BASE_TEST_TEST_SWITCHES_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 kTestLauncherBatchLimit[];
+extern const char kTestLauncherBotMode[];
+extern const char kTestLauncherDebugLauncher[];
+extern const char kTestLauncherFilterFile[];
+extern const char kTestLauncherJobs[];
+extern const char kTestLauncherOutput[];
+extern const char kTestLauncherRetryLimit[];
+extern const char kTestLauncherSummaryOutput[];
+extern const char kTestLauncherPrintTestStdio[];
+extern const char kTestLauncherShardIndex[];
+extern const char kTestLauncherTotalShards[];
+extern const char kTestLauncherTimeout[];
+extern const char kTestTinyTimeout[];
+extern const char kUiTestActionTimeout[];
+extern const char kUiTestActionMaxTimeout[];
+
+} // namespace switches
+
+#endif // BASE_TEST_TEST_SWITCHES_H_
diff --git a/base/test/test_timeouts.cc b/base/test/test_timeouts.cc
new file mode 100644
index 0000000..c84cd38
--- /dev/null
+++ b/base/test/test_timeouts.cc
@@ -0,0 +1,113 @@
+// 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 "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"
+
+namespace {
+
+// ASan/TSan/MSan instrument each memory access. This may slow the execution
+// down significantly.
+#if defined(MEMORY_SANITIZER)
+// For MSan the slowdown depends heavily on the value of msan_track_origins GYP
+// flag. The multiplier below corresponds to msan_track_origins=1.
+static const int kTimeoutMultiplier = 6;
+#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
+static const int kTimeoutMultiplier = 8;
+#elif defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \
+ defined(SYZYASAN)
+static const int kTimeoutMultiplier = 2;
+#else
+static const int kTimeoutMultiplier = 1;
+#endif
+
+const int kAlmostInfiniteTimeoutMs = 100000000;
+
+// 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);
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switch_name)) {
+ std::string string_value(
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switch_name));
+ int timeout;
+ base::StringToInt(string_value, &timeout);
+ *value = std::max(*value, timeout);
+ }
+ *value *= kTimeoutMultiplier;
+ *value = std::max(*value, min_value);
+}
+
+// Sets value to the greatest of:
+// 1) value's current value multiplied by kTimeoutMultiplier.
+// 2) 0
+// 3) the numerical value given by switch_name on the command line multiplied
+// by kTimeoutMultiplier.
+void InitializeTimeout(const char* switch_name, int* value) {
+ InitializeTimeout(switch_name, 0, 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;
+#ifndef NDEBUG
+int TestTimeouts::action_max_timeout_ms_ = 45000;
+#else
+int TestTimeouts::action_max_timeout_ms_ = 30000;
+#endif // NDEBUG
+
+int TestTimeouts::test_launcher_timeout_ms_ = 45000;
+
+// static
+void TestTimeouts::Initialize() {
+ if (initialized_) {
+ NOTREACHED();
+ return;
+ }
+ initialized_ = true;
+
+ if (base::debug::BeingDebugged()) {
+ 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, &tiny_timeout_ms_);
+ InitializeTimeout(switches::kUiTestActionTimeout,
+ base::debug::BeingDebugged() ? kAlmostInfiniteTimeoutMs
+ : tiny_timeout_ms_,
+ &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(tiny_timeout_ms_ <= action_timeout_ms_);
+ CHECK(action_timeout_ms_ <= action_max_timeout_ms_);
+
+ CHECK(action_timeout_ms_ <= test_launcher_timeout_ms_);
+}
diff --git a/base/test/test_timeouts.h b/base/test/test_timeouts.h
new file mode 100644
index 0000000..2819e4a
--- /dev/null
+++ b/base/test/test_timeouts.h
@@ -0,0 +1,59 @@
+// 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/basictypes.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+
+// Returns common timeouts to use in tests. Makes it possible to adjust
+// the timeouts for different environments (like Valgrind).
+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".
+ 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, but still suitable to use
+ // multiple times in a single test. Use if the timeout above
+ // is not sufficient.
+ 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/base/test/thread_test_helper.cc b/base/test/thread_test_helper.cc
new file mode 100644
index 0000000..2bd3ba5
--- /dev/null
+++ b/base/test/thread_test_helper.cc
@@ -0,0 +1,39 @@
+// 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 "base/bind.h"
+#include "base/location.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace base {
+
+ThreadTestHelper::ThreadTestHelper(
+ const scoped_refptr<MessageLoopProxy>& target_thread)
+ : test_result_(false),
+ target_thread_(target_thread),
+ done_event_(false, false) {
+}
+
+bool ThreadTestHelper::Run() {
+ if (!target_thread_->PostTask(
+ FROM_HERE, base::Bind(&ThreadTestHelper::RunInThread, this))) {
+ return false;
+ }
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ done_event_.Wait();
+ return test_result_;
+}
+
+void ThreadTestHelper::RunTest() { set_test_result(true); }
+
+ThreadTestHelper::~ThreadTestHelper() {}
+
+void ThreadTestHelper::RunInThread() {
+ RunTest();
+ done_event_.Signal();
+}
+
+} // namespace base
diff --git a/base/test/thread_test_helper.h b/base/test/thread_test_helper.h
new file mode 100644
index 0000000..f6817b5
--- /dev/null
+++ b/base/test/thread_test_helper.h
@@ -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.
+
+#ifndef BASE_TEST_THREAD_TEST_HELPER_H_
+#define BASE_TEST_THREAD_TEST_HELPER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace base {
+
+// Helper class that executes code on a given thread while blocking on the
+// invoking 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 thread have been executed.
+class ThreadTestHelper : public RefCountedThreadSafe<ThreadTestHelper> {
+ public:
+ explicit ThreadTestHelper(
+ const scoped_refptr<MessageLoopProxy>& target_thread);
+
+ // True if RunTest() was successfully executed on the target thread.
+ 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 RunInThread();
+
+ bool test_result_;
+ scoped_refptr<MessageLoopProxy> target_thread_;
+ WaitableEvent done_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadTestHelper);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_THREAD_TEST_HELPER_H_
diff --git a/base/test/trace_event_analyzer.cc b/base/test/trace_event_analyzer.cc
new file mode 100644
index 0000000..3de58e7
--- /dev/null
+++ b/base/test/trace_event_analyzer.cc
@@ -0,0 +1,968 @@
+// 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 <algorithm>
+#include <math.h>
+#include <set>
+
+#include "base/json/json_reader.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+
+namespace trace_analyzer {
+
+// TraceEvent
+
+TraceEvent::TraceEvent()
+ : thread(0, 0),
+ timestamp(0),
+ duration(0),
+ phase(TRACE_EVENT_PHASE_BEGIN),
+ other_event(NULL) {
+}
+
+TraceEvent::~TraceEvent() {
+}
+
+bool TraceEvent::SetFromJSON(const base::Value* event_value) {
+ if (event_value->GetType() != 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 = NULL;
+
+ 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_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)) {
+ LOG(ERROR) << "args is missing from TraceEvent JSON";
+ return false;
+ }
+ if (require_id && !dictionary->GetString("id", &id)) {
+ LOG(ERROR) << "id is missing from ASYNC_BEGIN/ASYNC_END TraceEvent JSON";
+ return false;
+ }
+
+ // For each argument, copy the type and create a trace_analyzer::TraceValue.
+ 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;
+ } else {
+ LOG(WARNING) << "Value type of argument is not supported: " <<
+ static_cast<int>(it.value().GetType());
+ continue; // Skip non-supported arguments.
+ }
+ }
+
+ return true;
+}
+
+double TraceEvent::GetAbsTimeToOtherEvent() const {
+ return fabs(other_event->timestamp - timestamp);
+}
+
+bool TraceEvent::GetArgAsString(const std::string& name,
+ std::string* arg) const {
+ std::map<std::string, std::string>::const_iterator i = arg_strings.find(name);
+ if (i != arg_strings.end()) {
+ *arg = i->second;
+ return true;
+ }
+ return false;
+}
+
+bool TraceEvent::GetArgAsNumber(const std::string& name,
+ double* arg) const {
+ std::map<std::string, double>::const_iterator i = arg_numbers.find(name);
+ if (i != arg_numbers.end()) {
+ *arg = i->second;
+ 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());
+}
+
+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);
+}
+
+// QueryNode
+
+QueryNode::QueryNode(const Query& query) : query_(query) {
+}
+
+QueryNode::~QueryNode() {
+}
+
+// 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)
+ : type_(query.type_),
+ operator_(query.operator_),
+ left_(query.left_),
+ right_(query.right_),
+ member_(query.member_),
+ number_(query.number_),
+ string_(query.string_),
+ is_pattern_(query.is_pattern_) {
+}
+
+Query::~Query() {
+}
+
+Query Query::String(const std::string& str) {
+ return Query(str);
+}
+
+Query Query::Double(double num) {
+ return Query(num);
+}
+
+Query Query::Int(int32 num) {
+ return Query(static_cast<double>(num));
+}
+
+Query Query::Uint(uint32 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 = MatchPattern(lhs, rhs);
+ else if (left().is_pattern_)
+ *result = MatchPattern(rhs, lhs);
+ else
+ *result = (lhs == rhs);
+ return true;
+ case OP_NE:
+ if (right().is_pattern_)
+ *result = !MatchPattern(lhs, rhs);
+ else if (left().is_pattern_)
+ *result = !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>(lhs) %
+ static_cast<int64>(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;
+ }
+}
+
+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 event. Store the target event in the_event:
+ const TraceEvent* the_event = (member_ < OTHER_PID) ?
+ &event : event.other_event;
+
+ // 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:
+ *num = static_cast<double>(the_event->thread.process_id);
+ return true;
+ case EVENT_TID:
+ case OTHER_TID:
+ *num = static_cast<double>(the_event->thread.thread_id);
+ return true;
+ case EVENT_TIME:
+ case OTHER_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:
+ *num = static_cast<double>(the_event->phase);
+ return true;
+ case EVENT_HAS_STRING_ARG:
+ case OTHER_HAS_STRING_ARG:
+ *num = (the_event->HasStringArg(string_) ? 1.0 : 0.0);
+ return true;
+ case EVENT_HAS_NUMBER_ARG:
+ case OTHER_HAS_NUMBER_ARG:
+ *num = (the_event->HasNumberArg(string_) ? 1.0 : 0.0);
+ return true;
+ case EVENT_ARG:
+ case OTHER_ARG: {
+ // Search for the argument name and return its value if found.
+ std::map<std::string, double>::const_iterator 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;
+ 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 event. Store the target event in the_event:
+ const TraceEvent* the_event = (member_ < OTHER_PID) ?
+ &event : event.other_event;
+
+ // 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:
+ *str = the_event->category;
+ return true;
+ case EVENT_NAME:
+ case OTHER_NAME:
+ *str = the_event->name;
+ return true;
+ case EVENT_ID:
+ case OTHER_ID:
+ *str = the_event->id;
+ return true;
+ case EVENT_ARG:
+ case OTHER_ARG: {
+ // Search for the argument name and return its value if found.
+ std::map<std::string, std::string>::const_iterator 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 (size_t i = 0; i < events.size(); ++i) {
+ if (ignore_metadata_events && events[i].phase == TRACE_EVENT_PHASE_METADATA)
+ continue;
+ if (query.Evaluate(events[i]))
+ output->push_back(&events[i]);
+ }
+ return output->size();
+}
+
+bool ParseEventsFromJson(const std::string& json,
+ std::vector<TraceEvent>* output) {
+ scoped_ptr<base::Value> root;
+ root.reset(base::JSONReader::Read(json));
+
+ base::ListValue* root_list = NULL;
+ if (!root.get() || !root->GetAsList(&root_list))
+ return false;
+
+ for (size_t i = 0; i < root_list->GetSize(); ++i) {
+ base::Value* item = NULL;
+ if (root_list->Get(i, &item)) {
+ TraceEvent event;
+ if (event.SetFromJSON(item))
+ output->push_back(event);
+ else
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+// TraceAnalyzer
+
+TraceAnalyzer::TraceAnalyzer()
+ : ignore_metadata_events_(false),
+ allow_assocation_changes_(true) {}
+
+TraceAnalyzer::~TraceAnalyzer() {
+}
+
+// static
+TraceAnalyzer* TraceAnalyzer::Create(const std::string& json_events) {
+ scoped_ptr<TraceAnalyzer> analyzer(new TraceAnalyzer());
+ if (analyzer->SetEvents(json_events))
+ return analyzer.release();
+ return NULL;
+}
+
+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() {
+ 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::EventName() == Query::OtherName() &&
+ Query::EventCategory() == Query::OtherCategory() &&
+ Query::EventId() == Query::OtherId());
+
+ AssociateEvents(begin, end, match);
+}
+
+void TraceAnalyzer::AssociateEvents(const Query& first,
+ const Query& second,
+ const Query& match) {
+ DCHECK(allow_assocation_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 (size_t event_index = 0; event_index < raw_events_.size();
+ ++event_index) {
+
+ TraceEvent& this_event = raw_events_[event_index];
+
+ 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.
+ // 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 (size_t i = 0; i < raw_events_.size(); ++i) {
+ // Merge all associated events with the first event.
+ const TraceEvent* other = raw_events_[i].other_event;
+ // Avoid looping by keeping set of encountered TraceEvents.
+ std::set<const TraceEvent*> encounters;
+ encounters.insert(&raw_events_[i]);
+ while (other && encounters.find(other) == encounters.end()) {
+ encounters.insert(other);
+ raw_events_[i].arg_numbers.insert(
+ other->arg_numbers.begin(),
+ other->arg_numbers.end());
+ raw_events_[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_assocation_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 NULL;
+}
+
+const TraceEvent* TraceAnalyzer::FindLastOf(const Query& query) {
+ TraceEventVector output;
+ if (FindEvents(query, &output) > 0)
+ return output.back();
+ return NULL;
+}
+
+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 (size_t i = 0; i < raw_events_.size(); ++i) {
+ TraceEvent& this_event = raw_events_[i];
+ // 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;
+ }
+}
+
+// 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/base/test/trace_event_analyzer.h b/base/test/trace_event_analyzer.h
new file mode 100644
index 0000000..2d3692d
--- /dev/null
+++ b/base/test/trace_event_analyzer.h
@@ -0,0 +1,705 @@
+// 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::debug::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.
+// }
+
+
+#ifndef BASE_TEST_TRACE_EVENT_ANALYZER_H_
+#define BASE_TEST_TRACE_EVENT_ANALYZER_H_
+
+#include <map>
+
+#include "base/debug/trace_event.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class Value;
+}
+
+namespace trace_analyzer {
+class QueryNode;
+
+// trace_analyzer::TraceEvent is a more convenient form of the
+// base::debug::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();
+
+ bool SetFromJSON(const base::Value* event_value) WARN_UNUSED_RESULT;
+
+ bool operator< (const TraceEvent& rhs) const {
+ return timestamp < rhs.timestamp;
+ }
+
+ 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;
+
+ // 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;
+
+ // 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;
+
+ // 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;
+
+ // 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;
+
+ // The other event associated with this event (or NULL).
+ const TraceEvent* other_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 num);
+ static Query Uint(uint32 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);
+ }
+
+ ////////////////////////////////////////////////////////////////
+ // 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 args (doubles are casted to int64 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;
+
+ private:
+ 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,
+ OTHER_PID,
+ OTHER_TID,
+ OTHER_TIME,
+ OTHER_PHASE,
+ OTHER_CATEGORY,
+ OTHER_NAME,
+ OTHER_ID,
+ OTHER_HAS_STRING_ARG,
+ OTHER_HAS_NUMBER_ARG,
+ OTHER_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;
+ }
+
+ const Query& left() const;
+ const Query& right() const;
+
+ 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_ = true; }
+
+ // 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.
+ void AssociateAsyncBeginEndEvents();
+
+ // 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_assocation_changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceAnalyzer);
+};
+
+// 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/base/test/trace_event_analyzer_unittest.cc b/base/test/trace_event_analyzer_unittest.cc
new file mode 100644
index 0000000..5604508
--- /dev/null
+++ b/base/test/trace_event_analyzer_unittest.cc
@@ -0,0 +1,895 @@
+// 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/debug/trace_event_unittest.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/trace_event_analyzer.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::debug::TraceResultBuffer::SimpleOutput output_;
+ base::debug::TraceResultBuffer buffer_;
+};
+
+void TraceEventAnalyzerTest::ManualSetUp() {
+ ASSERT_TRUE(base::debug::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::debug::TraceLog::GetInstance()->SetEnabled(
+ base::debug::CategoryFilter("*"),
+ base::debug::TraceLog::RECORDING_MODE,
+ base::debug::TraceOptions());
+}
+
+void TraceEventAnalyzerTest::EndTracing() {
+ base::debug::TraceLog::GetInstance()->SetDisabled();
+ base::WaitableEvent flush_complete_event(false, false);
+ base::debug::TraceLog::GetInstance()->Flush(
+ base::Bind(&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();
+
+ scoped_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;
+
+ 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"));
+
+ 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());
+}
+
+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();
+
+ scoped_ptr<TraceAnalyzer>
+ analyzer(TraceAnalyzer::Create(output_.json_output));
+ ASSERT_TRUE(!!analyzer.get());
+ 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();
+
+ scoped_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();
+
+ scoped_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 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::debug::HighResSleepForTraceTest(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();
+
+ scoped_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 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::debug::HighResSleepForTraceTest(kSleepTime);
+ TRACE_EVENT0("cat2", "name5"); // not found (duration too short)
+ }
+ }
+ EndTracing();
+
+ scoped_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();
+
+ scoped_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();
+
+ scoped_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();
+
+ scoped_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();
+
+ scoped_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_TRUE(found[0]->other_event->other_event);
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_END,
+ found[0]->other_event->other_event->phase);
+
+ EXPECT_STRCASEEQ("0xc", found[1]->id.c_str());
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, found[1]->other_event->phase);
+ EXPECT_TRUE(found[1]->other_event->other_event);
+ EXPECT_EQ(TRACE_EVENT_PHASE_ASYNC_STEP_INTO,
+ found[1]->other_event->other_event->phase);
+ 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();
+
+ scoped_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;
+ TraceEvent event;
+ event.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) {
+ event.timestamp += little_delta;
+ events.push_back(event);
+ event_ptrs.push_back(&events.back());
+ }
+
+ ASSERT_TRUE(GetRateStats(event_ptrs, &stats, NULL));
+ 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.
+ event.timestamp += big_delta;
+ events.push_back(event);
+ event_ptrs.push_back(&events.back());
+
+ ASSERT_TRUE(GetRateStats(event_ptrs, &stats, NULL));
+ 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.
+ event.timestamp += tiny_delta;
+ events.push_back(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.
+ TraceEventVector few_event_ptrs;
+ few_event_ptrs.push_back(&event);
+ few_event_ptrs.push_back(&event);
+ ASSERT_FALSE(GetRateStats(few_event_ptrs, &stats, NULL));
+ few_event_ptrs.push_back(&event);
+ ASSERT_TRUE(GetRateStats(few_event_ptrs, &stats, NULL));
+
+ // 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 (size_t i = 0; i < events.size(); ++i)
+ event_ptrs.push_back(&events[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, NULL));
+ 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 (size_t i = 0; i < events.size(); ++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");
+
+ 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));
+}
+
+
+} // namespace trace_analyzer
diff --git a/base/test/trace_to_file.cc b/base/test/trace_to_file.cc
new file mode 100644
index 0000000..6caaf47
--- /dev/null
+++ b/base/test/trace_to_file.cc
@@ -0,0 +1,104 @@
+// 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/command_line.h"
+#include "base/debug/trace_event_impl.h"
+#include "base/files/file_util.h"
+#include "base/run_loop.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();
+
+ debug::TraceLog::GetInstance()->SetEnabled(
+ debug::CategoryFilter(categories),
+ debug::TraceLog::RECORDING_MODE,
+ debug::TraceOptions(debug::RECORD_UNTIL_FULL));
+}
+
+void TraceToFile::WriteFileHeader() {
+ const char str[] = "{\"traceEvents\": [";
+ WriteFile(path_, str, static_cast<int>(strlen(str)));
+}
+
+void TraceToFile::AppendFileFooter() {
+ const char str[] = "]}";
+ AppendToFile(path_, str, static_cast<int>(strlen(str)));
+}
+
+void TraceToFile::TraceOutputCallback(const std::string& data) {
+ int ret = AppendToFile(path_, data.c_str(), static_cast<int>(data.size()));
+ DCHECK_NE(-1, ret);
+}
+
+static void OnTraceDataCollected(
+ Closure quit_closure,
+ debug::TraceResultBuffer* buffer,
+ const scoped_refptr<RefCountedString>& json_events_str,
+ bool has_more_events) {
+ buffer->AddFragment(json_events_str->data());
+ if (!has_more_events)
+ quit_closure.Run();
+}
+
+void TraceToFile::EndTracingIfNeeded() {
+ if (!started_)
+ return;
+ started_ = false;
+
+ debug::TraceLog::GetInstance()->SetDisabled();
+
+ debug::TraceResultBuffer buffer;
+ buffer.SetOutputCallback(
+ Bind(&TraceToFile::TraceOutputCallback, Unretained(this)));
+
+ RunLoop run_loop;
+ debug::TraceLog::GetInstance()->Flush(
+ Bind(&OnTraceDataCollected, run_loop.QuitClosure(), Unretained(&buffer)));
+ run_loop.Run();
+
+ AppendFileFooter();
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/trace_to_file.h b/base/test/trace_to_file.h
new file mode 100644
index 0000000..4308736
--- /dev/null
+++ b/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/base/test/values_test_util.cc b/base/test/values_test_util.cc
new file mode 100644
index 0000000..c5dfd79
--- /dev/null
+++ b/base/test/values_test_util.cc
@@ -0,0 +1,78 @@
+// 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 "base/json/json_reader.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.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 = NULL;
+ EXPECT_TRUE(value.GetDictionary(key, &dict_value)) << key;
+ EXPECT_TRUE(Value::Equals(dict_value, &expected_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 = NULL;
+ EXPECT_TRUE(value.GetList(key, &list_value)) << key;
+ EXPECT_TRUE(Value::Equals(list_value, &expected_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,
+ StringValue* actual) {
+ scoped_ptr<StringValue> scoped_actual(actual);
+ std::string actual_str;
+ EXPECT_TRUE(scoped_actual->GetAsString(&actual_str));
+ EXPECT_EQ(expected_str, actual_str);
+}
+
+namespace test {
+
+scoped_ptr<Value> ParseJson(base::StringPiece json) {
+ std::string error_msg;
+ scoped_ptr<Value> result(base::JSONReader::ReadAndReturnError(
+ json, base::JSON_ALLOW_TRAILING_COMMAS,
+ NULL, &error_msg));
+ if (!result) {
+ ADD_FAILURE() << "Failed to parse \"" << json << "\": " << error_msg;
+ result.reset(Value::CreateNullValue());
+ }
+ return result.Pass();
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/values_test_util.h b/base/test/values_test_util.h
new file mode 100644
index 0000000..86d91c3
--- /dev/null
+++ b/base/test/values_test_util.h
@@ -0,0 +1,56 @@
+// 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 <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class StringValue;
+class Value;
+
+// 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);
+
+// Takes ownership of |actual|.
+void ExpectStringValue(const std::string& expected_str,
+ StringValue* actual);
+
+namespace test {
+
+// 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).
+scoped_ptr<Value> ParseJson(base::StringPiece json);
+
+} // namespace test
+} // namespace base
+
+#endif // BASE_TEST_VALUES_TEST_UTIL_H_