Update from https://crrev.com/312398
Involves adding many //testing/test.gni imports, fixing one
SkSurface::NewRenderTarget invocation inside sky, and fixing up
base::Process usage in the shell.
Review URL: https://codereview.chromium.org/862133002
diff --git a/BUILD.gn b/BUILD.gn
index cb914be..065f871 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//build/config/ui.gni")
+import("//testing/test.gni")
if (is_android) {
import("//build/config/android/rules.gni")
@@ -909,7 +910,10 @@
# Linux.
if (is_linux) {
# TODO(brettw) this will need to be parameterized at some point.
- linux_configs = [ "//build/config/linux:glib" ]
+ linux_configs = []
+ if (use_glib) {
+ linux_configs += [ "//build/config/linux:glib" ]
+ }
configs += linux_configs
all_dependent_configs = linux_configs
@@ -1374,6 +1378,13 @@
"//third_party/icu",
]
+ if (is_android) {
+ apk_deps = [
+ ":base_java",
+ ":base_java_unittest_support",
+ ]
+ }
+
if (is_ios) {
sources -= [
"metrics/stats_table_uinittest.cc", # Requires spawning a process.
@@ -1403,10 +1414,12 @@
sources -= [ "file_version_info_unittest.cc" ]
sources += [ "nix/xdg_util_unittest.cc" ]
defines = [ "USE_SYMBOLIZE" ]
- configs += [ "//build/config/linux:glib" ]
+ if (use_glib) {
+ configs += [ "//build/config/linux:glib" ]
+ }
}
- if (!is_linux) {
+ if (!is_linux || use_ozone) {
sources -= [ "message_loop/message_pump_glib_unittest.cc" ]
}
@@ -1526,14 +1539,4 @@
java_files =
[ "test/android/java/src/org/chromium/base/ContentUriTestUtils.java" ]
}
-
- # GYP: //base.gyp:base_unittests_apk
- unittest_apk("base_unittests_apk") {
- deps = [
- ":base_java",
- ":base_java_unittest_support",
- ":base_unittests",
- ]
- unittests_dep = ":base_unittests"
- }
}
diff --git a/DEPS b/DEPS
index 2407baa..c632e35 100644
--- a/DEPS
+++ b/DEPS
@@ -4,7 +4,6 @@
"+third_party/apple_apsl",
"+third_party/libevent",
"+third_party/dmg_fp",
- "+third_party/google_toolbox_for_mac/src",
"+third_party/mach_override",
"+third_party/modp_b64",
"+third_party/tcmalloc",
diff --git a/allocator/generic_allocators.cc b/allocator/generic_allocators.cc
index 2726903..ae65f77 100644
--- a/allocator/generic_allocators.cc
+++ b/allocator/generic_allocators.cc
@@ -28,7 +28,7 @@
return generic_cpp_alloc(size, false);
}
-void operator delete(void* p) {
+void operator delete(void* p) throw() {
free(p);
}
@@ -36,7 +36,7 @@
return generic_cpp_alloc(size, false);
}
-void operator delete[](void* p) {
+void operator delete[](void* p) throw() {
free(p);
}
@@ -44,7 +44,7 @@
return generic_cpp_alloc(size, true);
}
-void operator delete(void* p, const std::nothrow_t& nt) {
+void operator delete(void* p, const std::nothrow_t& nt) throw() {
free(p);
}
@@ -52,7 +52,7 @@
return generic_cpp_alloc(size, true);
}
-void operator delete[](void* p, const std::nothrow_t& nt) {
+void operator delete[](void* p, const std::nothrow_t& nt) throw() {
free(p);
}
@@ -83,10 +83,6 @@
return result;
}
-void cfree(void* p) {
- free(p);
-}
-
#ifdef WIN32
void* _recalloc(void* p, size_t n, size_t elem_size) {
diff --git a/android/java/src/org/chromium/base/PerfTraceEvent.java b/android/java/src/org/chromium/base/PerfTraceEvent.java
index ca22042..c0e4b21 100644
--- a/android/java/src/org/chromium/base/PerfTraceEvent.java
+++ b/android/java/src/org/chromium/base/PerfTraceEvent.java
@@ -8,6 +8,8 @@
import android.os.Debug.MemoryInfo;
import android.util.Log;
+import org.chromium.base.annotations.SuppressFBWarnings;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -36,6 +38,7 @@
* the @TracePerf annotation. Thus, unlike TraceEvent, we do not
* support an implicit trace name based on the callstack.
*/
+@SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
public class PerfTraceEvent {
private static final int MAX_NAME_LENGTH = 40;
private static final String MEMORY_TRACE_NAME_SUFFIX = "_BZR_PSS";
diff --git a/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java b/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java
new file mode 100644
index 0000000..89068ac
--- /dev/null
+++ b/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java
@@ -0,0 +1,20 @@
+// 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.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @SuppressFBWarnings is used to suppress FindBugs warnings.
+ *
+ * The long name of FindBugs warnings can be found at
+ * http://findbugs.sourceforge.net/bugDescriptions.html
+ */
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressFBWarnings {
+ String[] value() default {};
+ String justification() default "";
+}
diff --git a/android/java/src/org/chromium/base/library_loader/Linker.java b/android/java/src/org/chromium/base/library_loader/Linker.java
index d58d1fc..dfcc141 100644
--- a/android/java/src/org/chromium/base/library_loader/Linker.java
+++ b/android/java/src/org/chromium/base/library_loader/Linker.java
@@ -845,6 +845,16 @@
*/
public static boolean checkMapExecSupport(String apkFile) {
assert apkFile != null;
+
+ // https://code.google.com/p/chromium/issues/detail?id=448084
+ // Do not check if the device is Samsung Mega.
+ final String model = android.os.Build.MODEL;
+ if (model != null && model.equals("GT-I9205")) {
+ if (DEBUG) Log.i(TAG, "checkMapExecSupport: model is '" + model
+ + "', returning false");
+ return false;
+ }
+
synchronized (Linker.class) {
ensureInitializedLocked();
diff --git a/android/java/templates/NativeLibraries.template b/android/java/templates/NativeLibraries.template
index f52acb4..d9f6a55 100644
--- a/android/java/templates/NativeLibraries.template
+++ b/android/java/templates/NativeLibraries.template
@@ -4,6 +4,9 @@
package org.chromium.base.library_loader;
+import org.chromium.base.annotations.SuppressFBWarnings;
+
+@SuppressFBWarnings
public class NativeLibraries {
/**
* IMPORTANT NOTE: The variables defined here must _not_ be 'final'.
diff --git a/atomicops.h b/atomicops.h
index 833e170..6a5371c 100644
--- a/atomicops.h
+++ b/atomicops.h
@@ -28,10 +28,14 @@
#ifndef BASE_ATOMICOPS_H_
#define BASE_ATOMICOPS_H_
-#include <cassert> // Small C++ header which defines implementation specific
- // macros used to identify the STL implementation.
#include <stdint.h>
+// Small C++ header which defines implementation specific macros used to
+// identify the STL implementation.
+// - libc++: captures __config for _LIBCPP_VERSION
+// - libstdc++: captures bits/c++config.h for __GLIBCXX__
+#include <cstddef>
+
#include "base/base_export.h"
#include "build/build_config.h"
diff --git a/base.gyp b/base.gyp
index 4d5ac00..5d26fc4 100644
--- a/base.gyp
+++ b/base.gyp
@@ -757,6 +757,11 @@
'message_loop/message_pump_glib_unittest.cc',
]
}],
+ ['use_ozone == 1', {
+ 'sources!': [
+ 'message_loop/message_pump_glib_unittest.cc',
+ ]
+ }],
['OS == "linux" and use_allocator!="none"', {
'dependencies': [
'allocator/allocator.gyp:allocator',
diff --git a/base.gypi b/base.gypi
index 14a632d..349308c 100644
--- a/base.gypi
+++ b/base.gypi
@@ -941,6 +941,11 @@
'strings/string16.cc',
],
},],
+ ['<(use_ozone) == 1', {
+ 'sources!': [
+ 'message_loop/message_pump_glib.cc',
+ ]
+ }],
['OS == "linux" and >(nacl_untrusted_build)==0', {
'sources!': [
'files/file_path_watcher_fsevents.cc',
diff --git a/cancelable_callback.h b/cancelable_callback.h
index 91eb046..2b9d260 100644
--- a/cancelable_callback.h
+++ b/cancelable_callback.h
@@ -62,8 +62,7 @@
// |callback| must not be null.
explicit CancelableCallback(const base::Callback<void(A...)>& callback)
- : weak_factory_(this),
- callback_(callback) {
+ : callback_(callback), weak_factory_(this) {
DCHECK(!callback.is_null());
InitializeForwarder();
}
@@ -113,17 +112,15 @@
weak_factory_.GetWeakPtr());
}
- // Used to ensure Forward() is not run when this object is destroyed.
- // TODO(ckehoe): This should be the last class member.
- // Move it there when crbug.com/433583 is fixed.
- base::WeakPtrFactory<CancelableCallback<void(A...)> > weak_factory_;
-
// The wrapper closure.
base::Callback<void(A...)> forwarder_;
// The stored closure that may be cancelled.
base::Callback<void(A...)> callback_;
+ // Used to ensure Forward() is not run when this object is destroyed.
+ base::WeakPtrFactory<CancelableCallback<void(A...)>> weak_factory_;
+
DISALLOW_COPY_AND_ASSIGN(CancelableCallback);
};
diff --git a/files/file_path.h b/files/file_path.h
index 6890866..67bbb4b 100644
--- a/files/file_path.h
+++ b/files/file_path.h
@@ -238,7 +238,7 @@
// ASSERT(new_path == path.value());
// NOTE: this is different from the original file_util implementation which
// returned the extension without a leading "." ("jpg" instead of ".jpg")
- StringType Extension() const;
+ StringType Extension() const WARN_UNUSED_RESULT;
// Returns the path's file extension, as in Extension(), but will
// never return a double extension.
@@ -247,7 +247,7 @@
// we can rename this to Extension() and the other to something like
// LongExtension(), defaulting to short extensions and leaving the
// long "extensions" to logic like base::GetUniquePathNumber().
- StringType FinalExtension() const;
+ StringType FinalExtension() const WARN_UNUSED_RESULT;
// Returns "C:\pics\jojo" for path "C:\pics\jojo.jpg"
// NOTE: this is slightly different from the similar file_util implementation
diff --git a/i18n/rtl.cc b/i18n/rtl.cc
index 392cb13..1cccae2 100644
--- a/i18n/rtl.cc
+++ b/i18n/rtl.cc
@@ -74,8 +74,8 @@
}
// Convert the ICU canonicalized locale to a string.
-std::string GetCanonicalLocale(const char* locale) {
- return GetLocaleString(icu::Locale::createCanonical(locale));
+std::string GetCanonicalLocale(const std::string& locale) {
+ return GetLocaleString(icu::Locale::createCanonical(locale.c_str()));
}
// Convert Chrome locale name to ICU locale name
diff --git a/i18n/rtl.h b/i18n/rtl.h
index aa5f681..9b9a0dc 100644
--- a/i18n/rtl.h
+++ b/i18n/rtl.h
@@ -40,7 +40,7 @@
BASE_I18N_EXPORT std::string GetConfiguredLocale();
// Canonicalize a string (eg. a POSIX locale string) to a Chrome locale name.
-BASE_I18N_EXPORT std::string GetCanonicalLocale(const char* locale);
+BASE_I18N_EXPORT std::string GetCanonicalLocale(const std::string& locale);
// Sets the default locale of ICU.
// Once the application locale of Chrome in GetApplicationLocale is determined,
diff --git a/ios/weak_nsobject.h b/ios/weak_nsobject.h
index 46aecb5..a1984bb 100644
--- a/ios/weak_nsobject.h
+++ b/ios/weak_nsobject.h
@@ -36,17 +36,28 @@
// NSObject, this relationship is maintained via the ObjectiveC associated
// object API, indirectly via an ObjectiveC CRBWeakNSProtocolSentinel class.
//
-// The implementation assumes that the tracked object will be released on the
-// same thread that the WeakNSObject is created on.
-//
+// Threading restrictions:
+// - Several WeakNSObject pointing to the same underlying object must all be
+// created and dereferenced on the same thread;
+// - thread safety is enforced by the implementation, except in two cases:
+// (1) it is allowed to copy a WeakNSObject on a different thread. However,
+// that copy must return to the original thread before being dereferenced,
+// (2) it is allowed to destroy a WeakNSObject on any thread;
+// - the implementation assumes that the tracked object will be released on the
+// same thread that the WeakNSObject is created on.
namespace base {
// WeakContainer keeps a weak pointer to an object and clears it when it
// receives nullify() from the object's sentinel.
class WeakContainer : public base::RefCountedThreadSafe<WeakContainer> {
public:
- WeakContainer(id object) : object_(object) {}
- id object() { return object_; }
+ explicit WeakContainer(id object) : object_(object) {}
+
+ id object() {
+ DCHECK(checker_.CalledOnValidThread());
+ return object_;
+ }
+
void nullify() {
DCHECK(checker_.CalledOnValidThread());
object_ = nil;
@@ -74,53 +85,63 @@
// Base class for all WeakNSObject derivatives.
template <typename NST>
-class WeakNSProtocol : public base::NonThreadSafe {
+class WeakNSProtocol {
public:
explicit WeakNSProtocol(NST object = nil) {
container_ = [CRBWeakNSProtocolSentinel containerForObject:object];
}
WeakNSProtocol(const WeakNSProtocol<NST>& that) {
+ // A WeakNSProtocol object can be copied on one thread and used on
+ // another.
+ checker_.DetachFromThread();
container_ = that.container_;
}
~WeakNSProtocol() {
- // A WeakNSProtocol object can be allocated on one thread and released on
+ // A WeakNSProtocol object can be used on one thread and released on
// another. This is not the case for the contained object.
- DetachFromThread();
+ checker_.DetachFromThread();
}
void reset(NST object = nil) {
- DCHECK(CalledOnValidThread());
+ DCHECK(checker_.CalledOnValidThread());
container_ = [CRBWeakNSProtocolSentinel containerForObject:object];
}
NST get() const {
- DCHECK(CalledOnValidThread());
+ DCHECK(checker_.CalledOnValidThread());
if (!container_.get())
return nil;
return container_->object();
}
WeakNSProtocol& operator=(const WeakNSProtocol<NST>& that) {
- DCHECK(CalledOnValidThread());
+ DCHECK(checker_.CalledOnValidThread());
container_ = that.container_;
return *this;
}
bool operator==(NST that) const {
- DCHECK(CalledOnValidThread());
+ DCHECK(checker_.CalledOnValidThread());
return get() == that;
}
- bool operator!=(NST that) const { return get() != that; }
+ bool operator!=(NST that) const {
+ DCHECK(checker_.CalledOnValidThread());
+ return get() != that;
+ }
- operator NST() const { return get(); }
+ operator NST() const {
+ DCHECK(checker_.CalledOnValidThread());
+ return get();
+ }
private:
// Refecounted reference to the container tracking the ObjectiveC object this
// class encapsulates.
scoped_refptr<base::WeakContainer> container_;
+ base::ThreadChecker checker_;
};
// Free functions
diff --git a/ios/weak_nsobject_unittest.mm b/ios/weak_nsobject_unittest.mm
index 9758aed..325dcd2 100644
--- a/ios/weak_nsobject_unittest.mm
+++ b/ios/weak_nsobject_unittest.mm
@@ -3,16 +3,19 @@
// found in the LICENSE file.
#include "base/basictypes.h"
+#include "base/bind.h"
#include "base/ios/weak_nsobject.h"
#include "base/mac/scoped_nsobject.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
-using base::WeakNSObject;
-
+namespace base {
namespace {
TEST(WeakNSObjectTest, WeakNSObject) {
- base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
WeakNSObject<NSObject> w1(p1);
EXPECT_TRUE(w1);
p1.reset();
@@ -20,7 +23,7 @@
}
TEST(WeakNSObjectTest, MultipleWeakNSObject) {
- base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
WeakNSObject<NSObject> w1(p1);
WeakNSObject<NSObject> w2(w1);
EXPECT_TRUE(w1);
@@ -32,7 +35,7 @@
}
TEST(WeakNSObjectTest, WeakNSObjectDies) {
- base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
{
WeakNSObject<NSObject> w1(p1);
EXPECT_TRUE(w1);
@@ -40,7 +43,7 @@
}
TEST(WeakNSObjectTest, WeakNSObjectReset) {
- base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
WeakNSObject<NSObject> w1(p1);
EXPECT_TRUE(w1);
w1.reset();
@@ -50,8 +53,8 @@
}
TEST(WeakNSObjectTest, WeakNSObjectResetWithObject) {
- base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
- base::scoped_nsobject<NSObject> p2([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p2([[NSObject alloc] init]);
WeakNSObject<NSObject> w1(p1);
EXPECT_TRUE(w1);
w1.reset(p2);
@@ -61,7 +64,7 @@
}
TEST(WeakNSObjectTest, WeakNSObjectEmpty) {
- base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
WeakNSObject<NSObject> w1;
EXPECT_FALSE(w1);
w1.reset(p1);
@@ -71,7 +74,7 @@
}
TEST(WeakNSObjectTest, WeakNSObjectCopy) {
- base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
WeakNSObject<NSObject> w1(p1);
WeakNSObject<NSObject> w2(w1);
EXPECT_TRUE(w1);
@@ -82,7 +85,7 @@
}
TEST(WeakNSObjectTest, WeakNSObjectAssignment) {
- base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
+ scoped_nsobject<NSObject> p1([[NSObject alloc] init]);
WeakNSObject<NSObject> w1(p1);
WeakNSObject<NSObject> w2;
EXPECT_FALSE(w2);
@@ -94,4 +97,40 @@
EXPECT_FALSE(w2);
}
+// Touches |weak_data| by increasing its length by 1. Used to check that the
+// weak object can be dereferenced.
+void TouchWeakData(const WeakNSObject<NSMutableData>& weak_data) {
+ if (!weak_data)
+ return;
+ [weak_data increaseLengthBy:1];
+}
+
+// Makes a copy of |weak_object| on the current thread and posts a task to touch
+// the weak object on its original thread.
+void CopyWeakNSObjectAndPost(const WeakNSObject<NSMutableData>& weak_object,
+ scoped_refptr<SingleThreadTaskRunner> runner) {
+ WeakNSObject<NSMutableData> weak_copy(weak_object);
+ runner->PostTask(FROM_HERE, Bind(&TouchWeakData, weak_copy));
+}
+
+// Tests that the weak object can be copied on a different thread.
+TEST(WeakNSObjectTest, WeakNSObjectCopyOnOtherThread) {
+ MessageLoop loop;
+ Thread other_thread("WeakNSObjectCopyOnOtherThread");
+ other_thread.Start();
+
+ scoped_nsobject<NSMutableData> data([[NSMutableData alloc] init]);
+ WeakNSObject<NSMutableData> weak(data);
+
+ scoped_refptr<SingleThreadTaskRunner> runner = loop.task_runner();
+ other_thread.task_runner()->PostTask(
+ FROM_HERE, Bind(&CopyWeakNSObjectAndPost, weak, runner));
+ other_thread.Stop();
+ loop.RunUntilIdle();
+
+ // Check that TouchWeakData was called.
+ EXPECT_EQ(1u, [data length]);
+}
+
} // namespace
+} // namespace base
diff --git a/mac/cocoa_protocols.h b/mac/cocoa_protocols.h
index e10001f..ab34a19 100644
--- a/mac/cocoa_protocols.h
+++ b/mac/cocoa_protocols.h
@@ -7,10 +7,6 @@
#import <Cocoa/Cocoa.h>
-// GTM also maintains a list of empty protocols, but only the ones the library
-// requires. Augment that below.
-#import "third_party/google_toolbox_for_mac/src/GTMDefines.h"
-
// New Mac OS X SDKs introduce new protocols used for delegates. These
// protocol defintions aren't not present in earlier releases of the Mac OS X
// SDK. In order to support building against the new SDK, which requires
diff --git a/mac/scoped_nsobject.h b/mac/scoped_nsobject.h
index 8d7bd4a..8814b51 100644
--- a/mac/scoped_nsobject.h
+++ b/mac/scoped_nsobject.h
@@ -40,6 +40,11 @@
: object_([that.object_ retain]) {
}
+ template <typename NSU>
+ scoped_nsprotocol(const scoped_nsprotocol<NSU>& that)
+ : object_([that.get() retain]) {
+ }
+
~scoped_nsprotocol() {
[object_ release];
}
@@ -119,6 +124,11 @@
: scoped_nsprotocol<NST*>(that) {
}
+ template<typename NSU>
+ scoped_nsobject(const scoped_nsobject<NSU>& that)
+ : scoped_nsprotocol<NST*>(that) {
+ }
+
scoped_nsobject& operator=(const scoped_nsobject<NST>& that) {
scoped_nsprotocol<NST*>::operator=(that);
return *this;
@@ -135,6 +145,11 @@
: scoped_nsprotocol<id>(that) {
}
+ template<typename NSU>
+ scoped_nsobject(const scoped_nsobject<NSU>& that)
+ : scoped_nsprotocol<id>(that) {
+ }
+
scoped_nsobject& operator=(const scoped_nsobject<id>& that) {
scoped_nsprotocol<id>::operator=(that);
return *this;
diff --git a/mac/sdk_forward_declarations.h b/mac/sdk_forward_declarations.h
index f606dab..faa36ea 100644
--- a/mac/sdk_forward_declarations.h
+++ b/mac/sdk_forward_declarations.h
@@ -105,6 +105,7 @@
- (void)setAnimationBehavior:(NSWindowAnimationBehavior)newAnimationBehavior;
- (void)toggleFullScreen:(id)sender;
- (void)setRestorable:(BOOL)flag;
+- (NSRect)convertRectFromScreen:(NSRect)aRect;
@end
@interface NSCursor (LionSDKDeclarations)
diff --git a/memory/scoped_vector.h b/memory/scoped_vector.h
index 1b30f63..173ea5a 100644
--- a/memory/scoped_vector.h
+++ b/memory/scoped_vector.h
@@ -9,6 +9,7 @@
#include "base/basictypes.h"
#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
#include "base/move.h"
#include "base/stl_util.h"
@@ -64,6 +65,7 @@
reference back() { return v_.back(); }
void push_back(T* elem) { v_.push_back(elem); }
+ void push_back(scoped_ptr<T> elem) { v_.push_back(elem.release()); }
void pop_back() {
DCHECK(!empty());
diff --git a/memory/scoped_vector_unittest.cc b/memory/scoped_vector_unittest.cc
index b60ca14..220cfb0 100644
--- a/memory/scoped_vector_unittest.cc
+++ b/memory/scoped_vector_unittest.cc
@@ -308,4 +308,17 @@
EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state());
}
+// Assertions for push_back(scoped_ptr).
+TEST(ScopedVectorTest, PushBackScopedPtr) {
+ int delete_counter = 0;
+ scoped_ptr<DeleteCounter> elem(new DeleteCounter(&delete_counter));
+ EXPECT_EQ(0, delete_counter);
+ {
+ ScopedVector<DeleteCounter> v;
+ v.push_back(elem.Pass());
+ EXPECT_EQ(0, delete_counter);
+ }
+ EXPECT_EQ(1, delete_counter);
+}
+
} // namespace
diff --git a/message_loop/message_pump_io_ios.h b/message_loop/message_pump_io_ios.h
index 18af4a8..317a59c 100644
--- a/message_loop/message_pump_io_ios.h
+++ b/message_loop/message_pump_io_ios.h
@@ -97,7 +97,7 @@
};
MessagePumpIOSForIO();
- virtual ~MessagePumpIOSForIO();
+ ~MessagePumpIOSForIO() override;
// Have the current thread's message loop watch for a a situation in which
// reading/writing to the FD can be performed without blocking.
diff --git a/message_loop/message_pump_io_ios_unittest.cc b/message_loop/message_pump_io_ios_unittest.cc
index 0bf8c08..ba96f83 100644
--- a/message_loop/message_pump_io_ios_unittest.cc
+++ b/message_loop/message_pump_io_ios_unittest.cc
@@ -18,9 +18,9 @@
MessagePumpIOSForIOTest()
: ui_loop_(MessageLoop::TYPE_UI),
io_thread_("MessagePumpIOSForIOTestIOThread") {}
- virtual ~MessagePumpIOSForIOTest() {}
+ ~MessagePumpIOSForIOTest() override {}
- virtual void SetUp() override {
+ void SetUp() override {
Thread::Options options(MessageLoop::TYPE_IO, 0);
ASSERT_TRUE(io_thread_.StartWithOptions(options));
ASSERT_EQ(MessageLoop::TYPE_IO, io_thread_.message_loop()->type());
@@ -30,7 +30,7 @@
ASSERT_EQ(0, ret);
}
- virtual void TearDown() override {
+ void TearDown() override {
if (IGNORE_EINTR(close(pipefds_[0])) < 0)
PLOG(ERROR) << "close";
if (IGNORE_EINTR(close(pipefds_[1])) < 0)
@@ -64,11 +64,11 @@
// nothing useful.
class StupidWatcher : public MessagePumpIOSForIO::Watcher {
public:
- virtual ~StupidWatcher() {}
+ ~StupidWatcher() override {}
// base:MessagePumpIOSForIO::Watcher interface
- virtual void OnFileCanReadWithoutBlocking(int fd) override {}
- virtual void OnFileCanWriteWithoutBlocking(int fd) override {}
+ void OnFileCanReadWithoutBlocking(int fd) override {}
+ void OnFileCanWriteWithoutBlocking(int fd) override {}
};
#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
@@ -93,16 +93,12 @@
: controller_(controller) {
DCHECK(controller_);
}
- virtual ~BaseWatcher() {}
+ ~BaseWatcher() override {}
// MessagePumpIOSForIO::Watcher interface
- virtual void OnFileCanReadWithoutBlocking(int /* fd */) override {
- NOTREACHED();
- }
+ void OnFileCanReadWithoutBlocking(int /* fd */) override { NOTREACHED(); }
- virtual void OnFileCanWriteWithoutBlocking(int /* fd */) override {
- NOTREACHED();
- }
+ void OnFileCanWriteWithoutBlocking(int /* fd */) override { NOTREACHED(); }
protected:
MessagePumpIOSForIO::FileDescriptorWatcher* controller_;
@@ -114,11 +110,9 @@
MessagePumpIOSForIO::FileDescriptorWatcher* controller)
: BaseWatcher(controller) {}
- virtual ~DeleteWatcher() {
- DCHECK(!controller_);
- }
+ ~DeleteWatcher() override { DCHECK(!controller_); }
- virtual void OnFileCanWriteWithoutBlocking(int /* fd */) override {
+ void OnFileCanWriteWithoutBlocking(int /* fd */) override {
DCHECK(controller_);
delete controller_;
controller_ = NULL;
@@ -146,9 +140,9 @@
pump_(pump),
fd_to_start_watching_(fd_to_start_watching) {}
- virtual ~StopWatcher() {}
+ ~StopWatcher() override {}
- virtual void OnFileCanWriteWithoutBlocking(int /* fd */) override {
+ void OnFileCanWriteWithoutBlocking(int /* fd */) override {
controller_->StopWatchingFileDescriptor();
if (fd_to_start_watching_ >= 0) {
pump_->WatchFileDescriptor(fd_to_start_watching_,
diff --git a/message_loop/message_pump_mac.h b/message_loop/message_pump_mac.h
index 55ab2c6..c853202 100644
--- a/message_loop/message_pump_mac.h
+++ b/message_loop/message_pump_mac.h
@@ -263,9 +263,9 @@
class MessagePumpUIApplication : public MessagePumpCFRunLoopBase {
public:
MessagePumpUIApplication();
- virtual ~MessagePumpUIApplication();
- virtual void DoRun(Delegate* delegate) override;
- virtual void Quit() override;
+ ~MessagePumpUIApplication() override;
+ void DoRun(Delegate* delegate) override;
+ void Quit() override;
// This message pump can not spin the main message loop directly. Instead,
// call |Attach()| to set up a delegate. It is an error to call |Run()|.
diff --git a/message_loop/message_pump_win.cc b/message_loop/message_pump_win.cc
index c140691..a7a1485 100644
--- a/message_loop/message_pump_win.cc
+++ b/message_loop/message_pump_win.cc
@@ -162,6 +162,11 @@
// static
LRESULT CALLBACK MessagePumpForUI::WndProcThunk(
HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
+ // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
+ tracked_objects::ScopedTracker tracking_profile(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION(
+ "440919 MessagePumpForUI::WndProcThunk"));
+
switch (message) {
case kMsgHaveWork:
reinterpret_cast<MessagePumpForUI*>(wparam)->HandleWorkMessage();
diff --git a/metrics/field_trial.cc b/metrics/field_trial.cc
index 7efca7a..e03c94c 100644
--- a/metrics/field_trial.cc
+++ b/metrics/field_trial.cc
@@ -223,6 +223,20 @@
return true;
}
+bool FieldTrial::GetState(FieldTrialState* field_trial_state) const {
+ if (!enable_field_trial_)
+ return false;
+ field_trial_state->trial_name = trial_name_;
+ // If the group name is empty (hasn't been finalized yet), use the default
+ // group name instead.
+ if (!group_name_.empty())
+ field_trial_state->group_name = group_name_;
+ else
+ field_trial_state->group_name = default_group_name_;
+ field_trial_state->activated = group_reported_;
+ return true;
+}
+
//------------------------------------------------------------------------------
// FieldTrialList methods and members.
@@ -387,6 +401,29 @@
}
// static
+void FieldTrialList::AllStatesToString(std::string* output) {
+ if (!global_)
+ return;
+ AutoLock auto_lock(global_->lock_);
+
+ for (const auto& registered : global_->registered_) {
+ FieldTrial::FieldTrialState trial;
+ if (!registered.second->GetState(&trial))
+ continue;
+ DCHECK_EQ(std::string::npos,
+ trial.trial_name.find(kPersistentStringSeparator));
+ DCHECK_EQ(std::string::npos,
+ trial.group_name.find(kPersistentStringSeparator));
+ if (trial.activated)
+ output->append(1, kActivationMarker);
+ output->append(trial.trial_name);
+ output->append(1, kPersistentStringSeparator);
+ output->append(trial.group_name);
+ output->append(1, kPersistentStringSeparator);
+ }
+}
+
+// static
void FieldTrialList::GetActiveFieldTrialGroups(
FieldTrial::ActiveGroups* active_groups) {
DCHECK(active_groups->empty());
diff --git a/metrics/field_trial.h b/metrics/field_trial.h
index e2e5439..26257ab 100644
--- a/metrics/field_trial.h
+++ b/metrics/field_trial.h
@@ -106,6 +106,14 @@
std::string group_name;
};
+ // A triplet representing a FieldTrial, its selected group and whether it's
+ // active.
+ struct FieldTrialState {
+ std::string trial_name;
+ std::string group_name;
+ bool activated;
+ };
+
typedef std::vector<ActiveGroup> ActiveGroups;
// A return value to indicate that a given instance has not yet had a group
@@ -180,8 +188,10 @@
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, OneWinner);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DisableProbability);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroups);
+ FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, AllGroups);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroupsNotFinalized);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Save);
+ FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SaveAll);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DuplicateRestore);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOff);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOn);
@@ -230,6 +240,13 @@
// untouched.
bool GetActiveGroup(ActiveGroup* active_group) const;
+ // Returns the trial name and selected group name for this field trial via
+ // the output parameter |field_trial_state|, but only if the trial has not
+ // been disabled. In that case, true is returned and |field_trial_state| is
+ // filled in; otherwise, the result is false and |field_trial_state| is left
+ // untouched.
+ bool GetState(FieldTrialState* field_trial_state) const;
+
// Returns the group_name. A winner need not have been chosen.
std::string group_name_internal() const { return group_name_; }
@@ -404,6 +421,16 @@
// by |CreateTrialsFromString()|.
static void StatesToString(std::string* output);
+ // Creates a persistent representation of all FieldTrial instances for
+ // resurrection in another process. This allows randomization to be done in
+ // one process, and secondary processes can be synchronized on the result.
+ // The resulting string contains the name and group name pairs of all
+ // registered FieldTrials which have not been disabled, with "/" used
+ // to separate all names and to terminate the string. All activated trials
+ // have their name prefixed with "*". This string is parsed by
+ // |CreateTrialsFromString()|.
+ static void AllStatesToString(std::string* output);
+
// Fills in the supplied vector |active_groups| (which must be empty when
// called) with a snapshot of all registered FieldTrials for which the group
// has been chosen and externally observed (via |group()|) and which have
diff --git a/metrics/field_trial_unittest.cc b/metrics/field_trial_unittest.cc
index ce95c2a..f1a1042 100644
--- a/metrics/field_trial_unittest.cc
+++ b/metrics/field_trial_unittest.cc
@@ -311,6 +311,36 @@
}
}
+TEST_F(FieldTrialTest, AllGroups) {
+ FieldTrial::FieldTrialState field_trial_state;
+ std::string one_winner("One Winner");
+ scoped_refptr<FieldTrial> trial =
+ CreateFieldTrial(one_winner, 10, "Default", NULL);
+ std::string winner("Winner");
+ trial->AppendGroup(winner, 10);
+ EXPECT_TRUE(trial->GetState(&field_trial_state));
+ EXPECT_EQ(one_winner, field_trial_state.trial_name);
+ EXPECT_EQ(winner, field_trial_state.group_name);
+ trial->group();
+ EXPECT_TRUE(trial->GetState(&field_trial_state));
+ EXPECT_EQ(one_winner, field_trial_state.trial_name);
+ EXPECT_EQ(winner, field_trial_state.group_name);
+
+ std::string multi_group("MultiGroup");
+ scoped_refptr<FieldTrial> multi_group_trial =
+ CreateFieldTrial(multi_group, 9, "Default", NULL);
+
+ multi_group_trial->AppendGroup("Me", 3);
+ multi_group_trial->AppendGroup("You", 3);
+ multi_group_trial->AppendGroup("Them", 3);
+ EXPECT_TRUE(multi_group_trial->GetState(&field_trial_state));
+ // Finalize the group selection by accessing the selected group.
+ multi_group_trial->group();
+ EXPECT_TRUE(multi_group_trial->GetState(&field_trial_state));
+ EXPECT_EQ(multi_group, field_trial_state.trial_name);
+ EXPECT_EQ(multi_group_trial->group_name(), field_trial_state.group_name);
+}
+
TEST_F(FieldTrialTest, ActiveGroupsNotFinalized) {
const char kTrialName[] = "TestTrial";
const char kSecondaryGroupName[] = "SecondaryGroup";
@@ -388,6 +418,44 @@
EXPECT_EQ("Some name/Winner/xxx/yyyy/zzz/default/", save_string);
}
+TEST_F(FieldTrialTest, SaveAll) {
+ std::string save_string;
+
+ scoped_refptr<FieldTrial> trial =
+ CreateFieldTrial("Some name", 10, "Default some name", NULL);
+ EXPECT_EQ("", trial->group_name_internal());
+ FieldTrialList::AllStatesToString(&save_string);
+ EXPECT_EQ("Some name/Default some name/", save_string);
+ save_string.clear();
+
+ // Create a winning group.
+ trial->AppendGroup("Winner", 10);
+ // Finalize the group selection by accessing the selected group.
+ trial->group();
+ FieldTrialList::AllStatesToString(&save_string);
+ EXPECT_EQ("*Some name/Winner/", save_string);
+ save_string.clear();
+
+ // Create a second trial and winning group.
+ scoped_refptr<FieldTrial> trial2 =
+ CreateFieldTrial("xxx", 10, "Default xxx", NULL);
+ trial2->AppendGroup("yyyy", 10);
+ // Finalize the group selection by accessing the selected group.
+ trial2->group();
+
+ FieldTrialList::AllStatesToString(&save_string);
+ // We assume names are alphabetized... though this is not critical.
+ EXPECT_EQ("*Some name/Winner/*xxx/yyyy/", save_string);
+ save_string.clear();
+
+ // Create a third trial with only the default group.
+ scoped_refptr<FieldTrial> trial3 =
+ CreateFieldTrial("zzz", 10, "default", NULL);
+
+ FieldTrialList::AllStatesToString(&save_string);
+ EXPECT_EQ("*Some name/Winner/*xxx/yyyy/zzz/default/", save_string);
+}
+
TEST_F(FieldTrialTest, Restore) {
ASSERT_FALSE(FieldTrialList::TrialExists("Some_name"));
ASSERT_FALSE(FieldTrialList::TrialExists("xxx"));
@@ -1014,6 +1082,43 @@
}
}
+TEST(FieldTrialTestWithoutList, StatesStringFormat) {
+ std::string save_string;
+
+ // Scoping the first FieldTrialList, as we need another one to test the
+ // importing function.
+ {
+ FieldTrialList field_trial_list(NULL);
+ scoped_refptr<FieldTrial> trial =
+ CreateFieldTrial("Abc", 10, "Default some name", NULL);
+ trial->AppendGroup("cba", 10);
+ trial->group();
+ scoped_refptr<FieldTrial> trial2 =
+ CreateFieldTrial("Xyz", 10, "Default xxx", NULL);
+ trial2->AppendGroup("zyx", 10);
+ trial2->group();
+ scoped_refptr<FieldTrial> trial3 =
+ CreateFieldTrial("zzz", 10, "default", NULL);
+
+ FieldTrialList::AllStatesToString(&save_string);
+ }
+
+ // Starting with a new blank FieldTrialList.
+ FieldTrialList field_trial_list(NULL);
+ ASSERT_TRUE(field_trial_list.CreateTrialsFromString(
+ save_string, FieldTrialList::DONT_ACTIVATE_TRIALS,
+ std::set<std::string>()));
+
+ FieldTrial::ActiveGroups active_groups;
+ field_trial_list.GetActiveFieldTrialGroups(&active_groups);
+ ASSERT_EQ(2U, active_groups.size());
+ EXPECT_EQ("Abc", active_groups[0].trial_name);
+ EXPECT_EQ("cba", active_groups[0].group_name);
+ EXPECT_EQ("Xyz", active_groups[1].trial_name);
+ EXPECT_EQ("zyx", active_groups[1].group_name);
+ EXPECT_TRUE(field_trial_list.TrialExists("zzz"));
+}
+
#if GTEST_HAS_DEATH_TEST
TEST(FieldTrialDeathTest, OneTimeRandomizedTrialWithoutFieldTrialList) {
// Trying to instantiate a one-time randomized field trial before the
diff --git a/power_monitor/power_monitor_device_source_win.cc b/power_monitor/power_monitor_device_source_win.cc
index b8b16e1..0e199dc 100644
--- a/power_monitor/power_monitor_device_source_win.cc
+++ b/power_monitor/power_monitor_device_source_win.cc
@@ -5,6 +5,7 @@
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_device_source.h"
#include "base/power_monitor/power_monitor_source.h"
+#include "base/profiler/scoped_tracker.h"
#include "base/win/wrapped_window_proc.h"
namespace base {
@@ -98,6 +99,11 @@
UINT message,
WPARAM wparam,
LPARAM lparam) {
+ // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
+ tracked_objects::ScopedTracker tracking_profile(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION(
+ "440919 PowerMonitorDeviceSource::PowerMessageWindow::WndProcThunk"));
+
switch (message) {
case WM_POWERBROADCAST:
ProcessWmPowerBroadcastMessage(wparam);
diff --git a/process/kill.h b/process/kill.h
index f697701..e8ce334 100644
--- a/process/kill.h
+++ b/process/kill.h
@@ -154,8 +154,8 @@
#if defined(OS_POSIX) && !defined(OS_MACOSX)
// The nicer version of EnsureProcessTerminated() that is patient and will
-// wait for |process_handle| to finish and then reap it.
-BASE_EXPORT void EnsureProcessGetsReaped(ProcessHandle process_handle);
+// wait for |pid| to finish and then reap it.
+BASE_EXPORT void EnsureProcessGetsReaped(ProcessId pid);
#endif
} // namespace base
diff --git a/process/kill_posix.cc b/process/kill_posix.cc
index 3f304ca..9e9b5fb 100644
--- a/process/kill_posix.cc
+++ b/process/kill_posix.cc
@@ -473,12 +473,12 @@
PlatformThread::CreateNonJoinable(0, reaper);
}
-void EnsureProcessGetsReaped(ProcessHandle process) {
+void EnsureProcessGetsReaped(ProcessId pid) {
// If the child is already dead, then there's nothing to do.
- if (IsChildDead(process))
+ if (IsChildDead(pid))
return;
- BackgroundReaper* reaper = new BackgroundReaper(process, 0);
+ BackgroundReaper* reaper = new BackgroundReaper(pid, 0);
PlatformThread::CreateNonJoinable(0, reaper);
}
diff --git a/process/launch.cc b/process/launch.cc
index a1c4d21..1c752bd 100644
--- a/process/launch.cc
+++ b/process/launch.cc
@@ -28,6 +28,9 @@
, clone_flags(0)
, allow_new_privs(false)
#endif // OS_LINUX
+#if defined(OS_POSIX)
+ , pre_exec_delegate(NULL)
+#endif // OS_POSIX
#if defined(OS_CHROMEOS)
, ctrl_terminal_fd(-1)
#endif // OS_CHROMEOS
diff --git a/process/launch.h b/process/launch.h
index 6cd02d6..eca4f8f 100644
--- a/process/launch.h
+++ b/process/launch.h
@@ -37,6 +37,24 @@
// Options for launching a subprocess that are passed to LaunchProcess().
// The default constructor constructs the object with default options.
struct BASE_EXPORT LaunchOptions {
+#if defined(OS_POSIX)
+ // Delegate to be run in between fork and exec in the subprocess (see
+ // pre_exec_delegate below)
+ class BASE_EXPORT PreExecDelegate {
+ public:
+ PreExecDelegate() {}
+ virtual ~PreExecDelegate() {}
+
+ // Since this is to be run between fork and exec, and fork may have happened
+ // while multiple threads were running, this function needs to be async
+ // safe.
+ virtual void RunAsyncSafe() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PreExecDelegate);
+ };
+#endif // defined(OS_POSIX)
+
LaunchOptions();
~LaunchOptions();
@@ -115,6 +133,9 @@
#if defined(OS_LINUX)
// If non-zero, start the process using clone(), using flags as provided.
+ // Unlike in clone, clone_flags may not contain a custom termination signal
+ // that is sent to the parent when the child dies. The termination signal will
+ // always be set to SIGCHLD.
int clone_flags;
// By default, child processes will have the PR_SET_NO_NEW_PRIVS bit set. If
@@ -122,6 +143,16 @@
bool allow_new_privs;
#endif // defined(OS_LINUX)
+#if defined(OS_POSIX)
+ // If non-null, a delegate to be run immediately prior to executing the new
+ // program in the child process.
+ //
+ // WARNING: If LaunchProcess is called in the presence of multiple threads,
+ // code running in this delegate essentially needs to be async-signal safe
+ // (see man 7 signal for a list of allowed functions).
+ PreExecDelegate* pre_exec_delegate;
+#endif // defined(OS_POSIX)
+
#if defined(OS_CHROMEOS)
// If non-negative, the specified file descriptor will be set as the launched
// process' controlling terminal.
@@ -156,12 +187,6 @@
BASE_EXPORT Process LaunchProcess(const CommandLine& cmdline,
const LaunchOptions& options);
-// Deprecated version.
-// TODO(rvargas) crbug.com/417532: Remove this after migrating all consumers.
-BASE_EXPORT bool LaunchProcess(const CommandLine& cmdline,
- const LaunchOptions& options,
- ProcessHandle* process_handle);
-
#if defined(OS_WIN)
// Windows-specific LaunchProcess that takes the command line as a
// string. Useful for situations where you need to control the
@@ -192,12 +217,6 @@
BASE_EXPORT Process LaunchProcess(const std::vector<std::string>& argv,
const LaunchOptions& options);
-// Deprecated version.
-// TODO(rvargas) crbug.com/417532: Remove this after migrating all consumers.
-BASE_EXPORT bool LaunchProcess(const std::vector<std::string>& argv,
- const LaunchOptions& options,
- ProcessHandle* process_handle);
-
// Close all file descriptors, except those which are a destination in the
// given multimap. Only call this function in a child process where you know
// that there aren't any other threads.
diff --git a/process/launch_posix.cc b/process/launch_posix.cc
index c60cfdc..6c9ed3f 100644
--- a/process/launch_posix.cc
+++ b/process/launch_posix.cc
@@ -277,9 +277,13 @@
}
}
-bool LaunchProcess(const std::vector<std::string>& argv,
- const LaunchOptions& options,
- ProcessHandle* process_handle) {
+Process LaunchProcess(const CommandLine& cmdline,
+ const LaunchOptions& options) {
+ return LaunchProcess(cmdline.argv(), options);
+}
+
+Process LaunchProcess(const std::vector<std::string>& argv,
+ const LaunchOptions& options) {
size_t fd_shuffle_size = 0;
if (options.fds_to_remap) {
fd_shuffle_size = options.fds_to_remap->size();
@@ -311,7 +315,17 @@
// and that signal handling follows the process-creation rules.
RAW_CHECK(
!(options.clone_flags & (CLONE_SIGHAND | CLONE_THREAD | CLONE_VM)));
- pid = syscall(__NR_clone, options.clone_flags, 0, 0, 0);
+
+ // We specify a null ptid and ctid.
+ RAW_CHECK(
+ !(options.clone_flags &
+ (CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | CLONE_PARENT_SETTID)));
+
+ // Since we use waitpid, we do not support custom termination signals in the
+ // clone flags.
+ RAW_CHECK((options.clone_flags & 0xff) == 0);
+
+ pid = ForkWithFlags(options.clone_flags | SIGCHLD, nullptr, nullptr);
} else
#endif
{
@@ -325,7 +339,7 @@
if (pid < 0) {
DPLOG(ERROR) << "fork";
- return false;
+ return Process();
} else if (pid == 0) {
// Child process
@@ -448,6 +462,12 @@
}
#endif
+#if defined(OS_POSIX)
+ if (options.pre_exec_delegate != nullptr) {
+ options.pre_exec_delegate->RunAsyncSafe();
+ }
+#endif
+
for (size_t i = 0; i < argv.size(); i++)
argv_cstr[i] = const_cast<char*>(argv[i].c_str());
argv_cstr[argv.size()] = NULL;
@@ -465,37 +485,9 @@
pid_t ret = HANDLE_EINTR(waitpid(pid, 0, 0));
DPCHECK(ret > 0);
}
-
- if (process_handle)
- *process_handle = pid;
}
- return true;
-}
-
-Process LaunchProcess(const std::vector<std::string>& argv,
- const LaunchOptions& options) {
- ProcessHandle process_handle;
- if (LaunchProcess(argv, options, &process_handle))
- return Process(process_handle);
-
- return Process();
-}
-
-
-bool LaunchProcess(const CommandLine& cmdline,
- const LaunchOptions& options,
- ProcessHandle* process_handle) {
- return LaunchProcess(cmdline.argv(), options, process_handle);
-}
-
-Process LaunchProcess(const CommandLine& cmdline,
- const LaunchOptions& options) {
- ProcessHandle process_handle;
- if (LaunchProcess(cmdline, options, &process_handle))
- return Process(process_handle);
-
- return Process();
+ return Process(pid);
}
void RaiseProcessToHighPriority() {
diff --git a/process/launch_win.cc b/process/launch_win.cc
index 1d83ef9..c2bd295 100644
--- a/process/launch_win.cc
+++ b/process/launch_win.cc
@@ -105,9 +105,13 @@
std::ios::sync_with_stdio();
}
-bool LaunchProcess(const string16& cmdline,
- const LaunchOptions& options,
- win::ScopedHandle* process_handle) {
+Process LaunchProcess(const CommandLine& cmdline,
+ const LaunchOptions& options) {
+ return LaunchProcess(cmdline.GetCommandLineString(), options);
+}
+
+Process LaunchProcess(const string16& cmdline,
+ const LaunchOptions& options) {
win::StartupInformation startup_info_wrapper;
STARTUPINFO* startup_info = startup_info_wrapper.startup_info();
@@ -119,18 +123,18 @@
} else {
if (base::win::GetVersion() < base::win::VERSION_VISTA) {
DLOG(ERROR) << "Specifying handles to inherit requires Vista or later.";
- return false;
+ return Process();
}
if (options.handles_to_inherit->size() >
std::numeric_limits<DWORD>::max() / sizeof(HANDLE)) {
DLOG(ERROR) << "Too many handles to inherit.";
- return false;
+ return Process();
}
if (!startup_info_wrapper.InitializeProcThreadAttributeList(1)) {
DPLOG(ERROR);
- return false;
+ return Process();
}
if (!startup_info_wrapper.UpdateProcThreadAttribute(
@@ -139,7 +143,7 @@
static_cast<DWORD>(options.handles_to_inherit->size() *
sizeof(HANDLE)))) {
DPLOG(ERROR);
- return false;
+ return Process();
}
inherit_handles = true;
@@ -184,7 +188,7 @@
if (!CreateEnvironmentBlock(&enviroment_block, options.as_user, FALSE)) {
DPLOG(ERROR);
- return false;
+ return Process();
}
BOOL launched =
@@ -197,7 +201,7 @@
if (!launched) {
DPLOG(ERROR) << "Command line:" << std::endl << UTF16ToUTF8(cmdline)
<< std::endl;;
- return false;
+ return Process();
}
} else {
if (!CreateProcess(NULL,
@@ -206,7 +210,7 @@
startup_info, &temp_process_info)) {
DPLOG(ERROR) << "Command line:" << std::endl << UTF16ToUTF8(cmdline)
<< std::endl;;
- return false;
+ return Process();
}
}
base::win::ScopedProcessInformation process_info(temp_process_info);
@@ -216,7 +220,7 @@
process_info.process_handle())) {
DLOG(ERROR) << "Could not AssignProcessToObject.";
KillProcess(process_info.process_handle(), kProcessKilledExitCode, true);
- return false;
+ return Process();
}
ResumeThread(process_info.thread_handle());
@@ -225,43 +229,7 @@
if (options.wait)
WaitForSingleObject(process_info.process_handle(), INFINITE);
- // If the caller wants the process handle, we won't close it.
- if (process_handle)
- process_handle->Set(process_info.TakeProcessHandle());
-
- return true;
-}
-
-// TODO(rvargas) crbug.com/416721: Remove this stub after LaunchProcess is
-// fully migrated to use Process.
-Process LaunchProcess(const string16& cmdline,
- const LaunchOptions& options) {
- win::ScopedHandle process_handle;
- if (LaunchProcess(cmdline, options, &process_handle))
- return Process(process_handle.Take());
-
- return Process();
-}
-
-bool LaunchProcess(const CommandLine& cmdline,
- const LaunchOptions& options,
- ProcessHandle* process_handle) {
- if (!process_handle)
- return LaunchProcess(cmdline.GetCommandLineString(), options, NULL);
-
- win::ScopedHandle process;
- bool rv = LaunchProcess(cmdline.GetCommandLineString(), options, &process);
- *process_handle = process.Take();
- return rv;
-}
-
-Process LaunchProcess(const CommandLine& cmdline,
- const LaunchOptions& options) {
- ProcessHandle process_handle;
- if (LaunchProcess(cmdline, options, &process_handle))
- return Process(process_handle);
-
- return Process();
+ return Process(process_info.TakeProcessHandle());
}
Process LaunchElevatedProcess(const CommandLine& cmdline,
diff --git a/process/process.h b/process/process.h
index ad8f8ee..b0fd8d9 100644
--- a/process/process.h
+++ b/process/process.h
@@ -49,6 +49,11 @@
// Returns an object for the current process.
static Process Current();
+ // Returns a Process for the given |pid|. On Windows the handle is opened
+ // with more access rights and must only be used by trusted code (can read the
+ // address space and duplicate handles).
+ static Process OpenWithExtraPriviles(ProcessId pid);
+
// Creates an object from a |handle| owned by someone else.
// Don't use this for new code. It is only intended to ease the migration to
// a strict ownership model.
@@ -112,6 +117,25 @@
#endif
};
+#if defined(OS_LINUX)
+// A wrapper for clone with fork-like behavior, meaning that it returns the
+// child's pid in the parent and 0 in the child. |flags|, |ptid|, and |ctid| are
+// as in the clone system call (the CLONE_VM flag is not supported).
+//
+// This function uses the libc clone wrapper (which updates libc's pid cache)
+// internally, so callers may expect things like getpid() to work correctly
+// after in both the child and parent. An exception is when this code is run
+// under Valgrind. Valgrind does not support the libc clone wrapper, so the libc
+// pid cache may be incorrect after this function is called under Valgrind.
+//
+// As with fork(), callers should be extremely careful when calling this while
+// multiple threads are running, since at the time the fork happened, the
+// threads could have been in any state (potentially holding locks, etc.).
+// Callers should most likely call execve() in the child soon after calling
+// this.
+BASE_EXPORT pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid);
+#endif
+
} // namespace base
#endif // BASE_PROCESS_PROCESS_PROCESS_H_
diff --git a/process/process_handle.h b/process/process_handle.h
index 6f8094e..368f952 100644
--- a/process/process_handle.h
+++ b/process/process_handle.h
@@ -44,15 +44,6 @@
// CloseProcessHandle when you are done with it. Returns true on success.
BASE_EXPORT bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle);
-// Converts a PID to a process handle. On Windows the handle is opened
-// with more access rights and must only be used by trusted code.
-// You have to close returned handle using CloseProcessHandle. Returns true
-// on success.
-// TODO(sanjeevr): Replace all calls to OpenPrivilegedProcessHandle with the
-// more specific OpenProcessHandleWithAccess method and delete this.
-BASE_EXPORT bool OpenPrivilegedProcessHandle(ProcessId pid,
- ProcessHandle* handle);
-
// Converts a PID to a process handle using the desired access flags. Use a
// combination of the kProcessAccess* flags defined above for |access_flags|.
BASE_EXPORT bool OpenProcessHandleWithAccess(ProcessId pid,
diff --git a/process/process_handle_posix.cc b/process/process_handle_posix.cc
index 4013254..94b2c2f 100644
--- a/process/process_handle_posix.cc
+++ b/process/process_handle_posix.cc
@@ -23,12 +23,6 @@
return true;
}
-bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) {
- // On POSIX permissions are checked for each operation on process,
- // not when opening a "handle".
- return OpenProcessHandle(pid, handle);
-}
-
bool OpenProcessHandleWithAccess(ProcessId pid,
uint32 access_flags,
ProcessHandle* handle) {
diff --git a/process/process_handle_win.cc b/process/process_handle_win.cc
index 3bc3a12..595d5b0 100644
--- a/process/process_handle_win.cc
+++ b/process/process_handle_win.cc
@@ -36,21 +36,6 @@
return true;
}
-bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) {
- ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE |
- PROCESS_TERMINATE |
- PROCESS_QUERY_INFORMATION |
- PROCESS_VM_READ |
- SYNCHRONIZE,
- FALSE, pid);
-
- if (result == NULL)
- return false;
-
- *handle = result;
- return true;
-}
-
bool OpenProcessHandleWithAccess(ProcessId pid,
uint32 access_flags,
ProcessHandle* handle) {
diff --git a/process/process_linux.cc b/process/process_linux.cc
index 59ee288..cfa3ed5 100644
--- a/process/process_linux.cc
+++ b/process/process_linux.cc
@@ -5,14 +5,21 @@
#include "base/process/process.h"
#include <errno.h>
+#include <pthread.h>
+#include <sched.h>
+#include <setjmp.h>
#include <sys/resource.h>
+#include <sys/syscall.h>
+#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
+#include "base/third_party/valgrind/valgrind.h"
+#include "build/build_config.h"
namespace base {
@@ -78,6 +85,52 @@
bool can_reraise_priority;
};
+bool IsRunningOnValgrind() {
+ return RUNNING_ON_VALGRIND;
+}
+
+// This function runs on the stack specified on the clone call. It uses longjmp
+// to switch back to the original stack so the child can return from sys_clone.
+int CloneHelper(void* arg) {
+ jmp_buf* env_ptr = reinterpret_cast<jmp_buf*>(arg);
+ longjmp(*env_ptr, 1);
+
+ // Should not be reached.
+ RAW_CHECK(false);
+ return 1;
+}
+
+// This function is noinline to ensure that stack_buf is below the stack pointer
+// that is saved when setjmp is called below. This is needed because when
+// compiled with FORTIFY_SOURCE, glibc's longjmp checks that the stack is moved
+// upwards. See crbug.com/442912 for more details.
+#if defined(ADDRESS_SANITIZER)
+// Disable AddressSanitizer instrumentation for this function to make sure
+// |stack_buf| is allocated on thread stack instead of ASan's fake stack.
+// Under ASan longjmp() will attempt to clean up the area between the old and
+// new stack pointers and print a warning that may confuse the user.
+__attribute__((no_sanitize_address))
+#endif
+NOINLINE pid_t CloneAndLongjmpInChild(unsigned long flags,
+ pid_t* ptid,
+ pid_t* ctid,
+ jmp_buf* env) {
+ // We use the libc clone wrapper instead of making the syscall
+ // directly because making the syscall may fail to update the libc's
+ // internal pid cache. The libc interface unfortunately requires
+ // specifying a new stack, so we use setjmp/longjmp to emulate
+ // fork-like behavior.
+ char stack_buf[PTHREAD_STACK_MIN];
+#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \
+ defined(ARCH_CPU_MIPS64_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY)
+ // The stack grows downward.
+ void* stack = stack_buf + sizeof(stack_buf);
+#else
+#error "Unsupported architecture"
+#endif
+ return clone(&CloneHelper, stack, flags, env, ptid, nullptr, ctid);
+}
+
} // namespace
// static
@@ -136,4 +189,43 @@
return result == 0;
}
+pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid) {
+ const bool clone_tls_used = flags & CLONE_SETTLS;
+ const bool invalid_ctid =
+ (flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) && !ctid;
+ const bool invalid_ptid = (flags & CLONE_PARENT_SETTID) && !ptid;
+
+ // We do not support CLONE_VM.
+ const bool clone_vm_used = flags & CLONE_VM;
+
+ if (clone_tls_used || invalid_ctid || invalid_ptid || clone_vm_used) {
+ RAW_LOG(FATAL, "Invalid usage of ForkWithFlags");
+ }
+
+ // Valgrind's clone implementation does not support specifiying a child_stack
+ // without CLONE_VM, so we cannot use libc's clone wrapper when running under
+ // Valgrind. As a result, the libc pid cache may be incorrect under Valgrind.
+ // See crbug.com/442817 for more details.
+ if (IsRunningOnValgrind()) {
+ // See kernel/fork.c in Linux. There is different ordering of sys_clone
+ // parameters depending on CONFIG_CLONE_BACKWARDS* configuration options.
+#if defined(ARCH_CPU_X86_64)
+ return syscall(__NR_clone, flags, nullptr, ptid, ctid, nullptr);
+#elif defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARM_FAMILY) || \
+ defined(ARCH_CPU_MIPS_FAMILY) || defined(ARCH_CPU_MIPS64_FAMILY)
+ // CONFIG_CLONE_BACKWARDS defined.
+ return syscall(__NR_clone, flags, nullptr, ptid, nullptr, ctid);
+#else
+#error "Unsupported architecture"
+#endif
+ }
+
+ jmp_buf env;
+ if (setjmp(env) == 0) {
+ return CloneAndLongjmpInChild(flags, ptid, ctid, &env);
+ }
+
+ return 0;
+}
+
} // namespace base
diff --git a/process/process_posix.cc b/process/process_posix.cc
index 58852bc..93dec98 100644
--- a/process/process_posix.cc
+++ b/process/process_posix.cc
@@ -38,6 +38,16 @@
}
// static
+Process Process::OpenWithExtraPriviles(ProcessId pid) {
+ if (pid == GetCurrentProcId())
+ return Current();
+
+ // On POSIX process handles are the same as PIDs, and there are no privileges
+ // to set.
+ return Process(pid);
+}
+
+// static
Process Process::DeprecatedGetProcessFromHandle(ProcessHandle handle) {
DCHECK_NE(handle, GetCurrentProcessHandle());
return Process(handle);
diff --git a/process/process_unittest.cc b/process/process_unittest.cc
index 5180f64..8130726 100644
--- a/process/process_unittest.cc
+++ b/process/process_unittest.cc
@@ -4,13 +4,27 @@
#include "base/process/process.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/posix/eintr_wrapper.h"
#include "base/process/kill.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
+#if defined(OS_LINUX)
+#include <errno.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
namespace {
@@ -201,4 +215,105 @@
EXPECT_EQ(old_priority, new_priority);
}
+#if defined(OS_LINUX)
+const int kSuccess = 0;
+
+MULTIPROCESS_TEST_MAIN(CheckPidProcess) {
+ const pid_t kInitPid = 1;
+ const pid_t pid = syscall(__NR_getpid);
+ CHECK(pid == kInitPid);
+ CHECK(getpid() == pid);
+ return kSuccess;
+}
+
+TEST_F(ProcessTest, CloneFlags) {
+ if (RunningOnValgrind() || !PathExists(FilePath("/proc/self/ns/user")) ||
+ !PathExists(FilePath("/proc/self/ns/pid"))) {
+ // User or PID namespaces are not supported.
+ return;
+ }
+
+ LaunchOptions options;
+ options.clone_flags = CLONE_NEWUSER | CLONE_NEWPID;
+
+ Process process(SpawnChildWithOptions("CheckPidProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+
+TEST(ForkWithFlagsTest, UpdatesPidCache) {
+ // The libc clone function, which allows ForkWithFlags to keep the pid cache
+ // up to date, does not work on Valgrind.
+ if (RunningOnValgrind()) {
+ return;
+ }
+
+ // Warm up the libc pid cache, if there is one.
+ ASSERT_EQ(syscall(__NR_getpid), getpid());
+
+ pid_t ctid = 0;
+ const pid_t pid = ForkWithFlags(SIGCHLD | CLONE_CHILD_SETTID, nullptr, &ctid);
+ if (pid == 0) {
+ // In child. Check both the raw getpid syscall and the libc getpid wrapper
+ // (which may rely on a pid cache).
+ RAW_CHECK(syscall(__NR_getpid) == ctid);
+ RAW_CHECK(getpid() == ctid);
+ _exit(kSuccess);
+ }
+
+ ASSERT_NE(-1, pid);
+ int status = 42;
+ ASSERT_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0)));
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(kSuccess, WEXITSTATUS(status));
+}
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_ANDROID)
+const char kPipeValue = '\xcc';
+
+class ReadFromPipeDelegate : public LaunchOptions::PreExecDelegate {
+ public:
+ explicit ReadFromPipeDelegate(int fd) : fd_(fd) {}
+ ~ReadFromPipeDelegate() override {}
+ void RunAsyncSafe() override {
+ char c;
+ RAW_CHECK(HANDLE_EINTR(read(fd_, &c, 1)) == 1);
+ RAW_CHECK(IGNORE_EINTR(close(fd_)) == 0);
+ RAW_CHECK(c == kPipeValue);
+ }
+
+ private:
+ int fd_;
+ DISALLOW_COPY_AND_ASSIGN(ReadFromPipeDelegate);
+};
+
+TEST_F(ProcessTest, PreExecHook) {
+ int pipe_fds[2];
+ ASSERT_EQ(0, pipe(pipe_fds));
+
+ ScopedFD read_fd(pipe_fds[0]);
+ ScopedFD write_fd(pipe_fds[1]);
+ base::FileHandleMappingVector fds_to_remap;
+ fds_to_remap.push_back(std::make_pair(read_fd.get(), read_fd.get()));
+
+ ReadFromPipeDelegate read_from_pipe_delegate(read_fd.get());
+ LaunchOptions options;
+ options.fds_to_remap = &fds_to_remap;
+ options.pre_exec_delegate = &read_from_pipe_delegate;
+ Process process(SpawnChildWithOptions("SimpleChildProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ read_fd.reset();
+ ASSERT_EQ(1, HANDLE_EINTR(write(write_fd.get(), &kPipeValue, 1)));
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+#endif // defined(OS_POSIX) && !defined(OS_ANDROID)
+
} // namespace base
diff --git a/process/process_util_unittest.cc b/process/process_util_unittest.cc
index 80e103d..4b2a575 100644
--- a/process/process_util_unittest.cc
+++ b/process/process_util_unittest.cc
@@ -607,7 +607,7 @@
#else
CHECK_EQ(0, clone_flags);
#endif // OS_LINUX
- EXPECT_TRUE(base::LaunchProcess(args, options, NULL));
+ EXPECT_TRUE(base::LaunchProcess(args, options).IsValid());
PCHECK(IGNORE_EINTR(close(fds[1])) == 0);
char buf[512];
@@ -679,10 +679,8 @@
// Test a non-trival value for clone_flags.
// Don't test on Valgrind as it has limited support for clone().
if (!RunningOnValgrind()) {
- EXPECT_EQ(
- "wibble\n",
- TestLaunchProcess(
- echo_base_test, env_changes, no_clear_environ, CLONE_FS | SIGCHLD));
+ EXPECT_EQ("wibble\n", TestLaunchProcess(echo_base_test, env_changes,
+ no_clear_environ, CLONE_FS));
}
EXPECT_EQ(
diff --git a/process/process_win.cc b/process/process_win.cc
index 96556a9..2c267aa 100644
--- a/process/process_win.cc
+++ b/process/process_win.cc
@@ -9,6 +9,13 @@
#include "base/process/kill.h"
#include "base/win/windows_version.h"
+namespace {
+
+DWORD kBasicProcessAccess =
+ PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | SYNCHRONIZE;
+
+} // namespace
+
namespace base {
Process::Process(ProcessHandle handle)
@@ -40,6 +47,13 @@
}
// static
+Process Process::OpenWithExtraPriviles(ProcessId pid) {
+ DWORD access = kBasicProcessAccess | PROCESS_DUP_HANDLE | PROCESS_VM_READ;
+ ProcessHandle handle = ::OpenProcess(access, FALSE, pid);
+ return Process(handle);
+}
+
+// static
Process Process::DeprecatedGetProcessFromHandle(ProcessHandle handle) {
DCHECK_NE(handle, ::GetCurrentProcess());
ProcessHandle out_handle;
diff --git a/stl_util_unittest.cc b/stl_util_unittest.cc
index a3f8e16..06ea7cd 100644
--- a/stl_util_unittest.cc
+++ b/stl_util_unittest.cc
@@ -238,5 +238,30 @@
EXPECT_TRUE(STLIncludes<std::set<int> >(a3, a2));
}
+TEST(StringAsArrayTest, Empty) {
+ std::string empty;
+ EXPECT_EQ(nullptr, string_as_array(&empty));
+}
+
+TEST(StringAsArrayTest, NullTerminated) {
+ // If any std::string implementation is not null-terminated, this should
+ // fail. All compilers we use return a null-terminated buffer, but please do
+ // not rely on this fact in your code.
+ std::string str("abcde");
+ str.resize(3);
+ EXPECT_STREQ("abc", string_as_array(&str));
+}
+
+TEST(StringAsArrayTest, WriteCopy) {
+ // With a COW implementation, this test will fail if
+ // string_as_array(&str) is implemented as
+ // const_cast<char*>(str->data()).
+ std::string s1("abc");
+ const std::string s2(s1);
+ string_as_array(&s1)[1] = 'x';
+ EXPECT_EQ("axc", s1);
+ EXPECT_EQ("abc", s2);
+}
+
} // namespace
} // namespace base
diff --git a/test/gtest_util.cc b/test/gtest_util.cc
index 47fd639..f98320c 100644
--- a/test/gtest_util.cc
+++ b/test/gtest_util.cc
@@ -40,4 +40,39 @@
return serializer.Serialize(root);
}
+bool ReadTestNamesFromFile(const FilePath& path,
+ std::vector<SplitTestName>* output) {
+ JSONFileValueSerializer deserializer(path);
+ int error_code = 0;
+ std::string error_message;
+ scoped_ptr<base::Value> value(
+ deserializer.Deserialize(&error_code, &error_message));
+ if (!value.get())
+ return false;
+
+ base::ListValue* tests = nullptr;
+ if (!value->GetAsList(&tests))
+ return false;
+
+ std::vector<base::SplitTestName> result;
+ for (base::ListValue::iterator i = tests->begin(); i != tests->end(); ++i) {
+ base::DictionaryValue* test = nullptr;
+ if (!(*i)->GetAsDictionary(&test))
+ return false;
+
+ std::string test_case_name;
+ if (!test->GetStringASCII("test_case_name", &test_case_name))
+ return false;
+
+ std::string test_name;
+ if (!test->GetStringASCII("test_name", &test_name))
+ return false;
+
+ result.push_back(std::make_pair(test_case_name, test_name));
+ }
+
+ output->swap(result);
+ return true;
+}
+
} // namespace
diff --git a/test/gtest_util.h b/test/gtest_util.h
index 93382f8..d10c72f 100644
--- a/test/gtest_util.h
+++ b/test/gtest_util.h
@@ -26,6 +26,12 @@
// current executable as a JSON file. Returns true on success.
bool WriteCompiledInTestsToFile(const FilePath& path) WARN_UNUSED_RESULT;
+// Reads the list of gtest-based tests from |path| into |output|.
+// Returns true on success.
+bool ReadTestNamesFromFile(
+ const FilePath& path,
+ std::vector<SplitTestName>* output) WARN_UNUSED_RESULT;
+
} // namespace base
#endif // BASE_TEST_GTEST_UTIL_H_
diff --git a/test/launcher/test_launcher.cc b/test/launcher/test_launcher.cc
index af2e461..b5316ad 100644
--- a/test/launcher/test_launcher.cc
+++ b/test/launcher/test_launcher.cc
@@ -829,6 +829,11 @@
}
}
+ if (!launcher_delegate_->GetTests(&tests_)) {
+ LOG(ERROR) << "Failed to get list of tests.";
+ return false;
+ }
+
if (!results_tracker_.Init(*command_line)) {
LOG(ERROR) << "Failed to initialize test results tracker.";
return 1;
@@ -900,12 +905,10 @@
}
void TestLauncher::RunTests() {
- std::vector<SplitTestName> tests(GetCompiledInTests());
-
std::vector<std::string> test_names;
-
- for (size_t i = 0; i < tests.size(); i++) {
- std::string test_name = FormatFullTestName(tests[i].first, tests[i].second);
+ for (size_t i = 0; i < tests_.size(); i++) {
+ std::string test_name = FormatFullTestName(
+ tests_[i].first, tests_[i].second);
results_tracker_.AddTest(test_name);
@@ -918,7 +921,7 @@
continue;
}
- if (!launcher_delegate_->ShouldRunTest(tests[i].first, tests[i].second))
+ if (!launcher_delegate_->ShouldRunTest(tests_[i].first, tests_[i].second))
continue;
// Skip the test that doesn't match the filter (if given).
diff --git a/test/launcher/test_launcher.h b/test/launcher/test_launcher.h
index 78a854b..27909d4 100644
--- a/test/launcher/test_launcher.h
+++ b/test/launcher/test_launcher.h
@@ -11,6 +11,7 @@
#include "base/basictypes.h"
#include "base/callback_forward.h"
#include "base/compiler_specific.h"
+#include "base/test/gtest_util.h"
#include "base/test/launcher/test_result.h"
#include "base/test/launcher/test_results_tracker.h"
#include "base/time/time.h"
@@ -40,6 +41,10 @@
// which tests and how are run.
class TestLauncherDelegate {
public:
+ // Called to get names of tests available for running. The delegate
+ // must put the result in |output| and return true on success.
+ virtual bool GetTests(std::vector<SplitTestName>* output) = 0;
+
// 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.
@@ -158,6 +163,9 @@
std::vector<std::string> positive_test_filter_;
std::vector<std::string> negative_test_filter_;
+ // Tests to use (cached result of TestLauncherDelegate::GetTests).
+ std::vector<SplitTestName> tests_;
+
// Number of tests started in this iteration.
size_t test_started_count_;
diff --git a/test/launcher/test_launcher_ios.cc b/test/launcher/test_launcher_ios.cc
index 39051ca..82353cc 100644
--- a/test/launcher/test_launcher_ios.cc
+++ b/test/launcher/test_launcher_ios.cc
@@ -5,18 +5,152 @@
#include "base/test/launcher/test_launcher.h"
#include "base/at_exit.h"
+#include "base/base_paths.h"
#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.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/path_service.h"
+#include "base/process/launch.h"
#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_switches.h"
+#include "base/test/test_timeouts.h"
namespace {
-int DummyRunTestSuite(void) {
- return -1;
+const char kHelpFlag[] = "help";
+
+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"
+ " Other flags:\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);
}
+class IOSUnitTestLauncherDelegate : public base::UnitTestLauncherDelegate {
+ public:
+ IOSUnitTestLauncherDelegate() : base::UnitTestLauncherDelegate(0, false) {
+ }
+
+ bool Init() WARN_UNUSED_RESULT {
+ if (!PathService::Get(base::DIR_EXE, &dir_exe_)) {
+ LOG(ERROR) << "Failed to get directory of current executable.";
+ return false;
+ }
+
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ std::vector<std::string> args(command_line->GetArgs());
+ if (args.size() < 1) {
+ LOG(ERROR) << "Arguments expected.";
+ return false;
+ }
+ test_name_ = args[0];
+
+ base::CommandLine cmd_line(dir_exe_.AppendASCII(test_name_ + ".app"));
+ cmd_line.AppendSwitch(switches::kTestLauncherPrintWritablePath);
+ cmd_line.PrependWrapper(dir_exe_.AppendASCII("iossim").value());
+
+ std::string raw_output;
+ if (!base::GetAppOutput(cmd_line, &raw_output)) {
+ LOG(ERROR) << "GetAppOutput failed.";
+ return false;
+ }
+ writable_path_ = base::FilePath(raw_output);
+
+ return true;
+ }
+
+ bool GetTests(std::vector<base::SplitTestName>* output) override {
+ base::ScopedTempDir temp_dir;
+ if (!temp_dir.CreateUniqueTempDirUnderPath(writable_path_))
+ return false;
+ base::FilePath test_list_path(
+ temp_dir.path().AppendASCII("test_list.json"));
+
+ base::CommandLine cmd_line(dir_exe_.AppendASCII(test_name_ + ".app"));
+ cmd_line.AppendSwitchPath(switches::kTestLauncherListTests, test_list_path);
+ cmd_line.PrependWrapper(dir_exe_.AppendASCII("iossim").value());
+
+ base::LaunchOptions launch_options;
+ launch_options.wait = true;
+
+ if (!base::LaunchProcess(cmd_line, launch_options).IsValid())
+ return false;
+
+ return base::ReadTestNamesFromFile(test_list_path, output);
+ }
+
+ private:
+ // Directory containing test launcher's executable.
+ base::FilePath dir_exe_;
+
+ // Name of the test executable to run.
+ std::string test_name_;
+
+ // Path that launched test binary can write to.
+ base::FilePath writable_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(IOSUnitTestLauncherDelegate);
+};
+
} // namespace
int main(int argc, char** argv) {
base::AtExitManager at_exit;
- return base::LaunchUnitTests(argc, argv, base::Bind(&DummyRunTestSuite));
+
+ base::CommandLine::Init(argc, argv);
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(kHelpFlag)) {
+ PrintUsage();
+ return 0;
+ }
+
+ base::TimeTicks start_time(base::TimeTicks::Now());
+
+ TestTimeouts::Initialize();
+
+ base::MessageLoopForIO message_loop;
+
+ IOSUnitTestLauncherDelegate delegate;
+ if (!delegate.Init()) {
+ fprintf(stderr, "Failed to intialize test launcher delegate.\n");
+ fflush(stderr);
+ return 1;
+ }
+ // Force one job since we can't run multiple simulators in parallel.
+ base::TestLauncher launcher(&delegate, 1);
+ bool success = launcher.Run();
+
+ fprintf(stdout, "Tests took %" PRId64 " seconds.\n",
+ (base::TimeTicks::Now() - start_time).InSeconds());
+ fflush(stdout);
+
+ return (success ? 0 : 1);
}
diff --git a/test/launcher/unit_test_launcher.cc b/test/launcher/unit_test_launcher.cc
index 6309cde..9a8b41f 100644
--- a/test/launcher/unit_test_launcher.cc
+++ b/test/launcher/unit_test_launcher.cc
@@ -100,34 +100,159 @@
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) {
+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;
}
- ~UnitTestLauncherDelegate() override {
- DCHECK(thread_checker_.CalledOnValidThread());
+ 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;
+ }
}
- private:
- struct GTestCallbackState {
- TestLauncher* test_launcher;
- std::vector<std::string> test_names;
- FilePath output_file;
- };
+ if (CommandLine::ForCurrentProcess()->HasSwitch(kGTestHelpFlag) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(kGTestListTestsFlag) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(kSingleProcessTestsFlag) ||
+ force_single_process) {
+ return run_test_suite.Run();
+ }
+#endif
- bool ShouldRunTest(const std::string& test_case_name,
- const std::string& test_name) override {
+ 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)
+
+UnitTestLauncherDelegate::UnitTestLauncherDelegate(size_t batch_limit,
+ bool use_job_objects)
+ : batch_limit_(batch_limit), use_job_objects_(use_job_objects) {
+}
+
+UnitTestLauncherDelegate::~UnitTestLauncherDelegate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+UnitTestLauncherDelegate::GTestCallbackState::GTestCallbackState() {
+}
+
+UnitTestLauncherDelegate::GTestCallbackState::~GTestCallbackState() {
+}
+
+bool UnitTestLauncherDelegate::GetTests(std::vector<SplitTestName>* output) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ *output = GetCompiledInTests();
+ return true;
+}
+
+bool UnitTestLauncherDelegate::ShouldRunTest(const std::string& test_case_name,
+ const std::string& test_name) {
DCHECK(thread_checker_.CalledOnValidThread());
// There is no additional logic to disable specific tests.
return true;
}
- size_t RunTests(TestLauncher* test_launcher,
- const std::vector<std::string>& test_names) override {
+ size_t UnitTestLauncherDelegate::RunTests(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<std::string> batch;
@@ -145,8 +270,9 @@
return test_names.size();
}
- size_t RetryTests(TestLauncher* test_launcher,
- const std::vector<std::string>& test_names) override {
+ size_t UnitTestLauncherDelegate::RetryTests(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) {
MessageLoop::current()->PostTask(
FROM_HERE,
Bind(&UnitTestLauncherDelegate::RunSerially,
@@ -156,8 +282,9 @@
return test_names.size();
}
- void RunSerially(TestLauncher* test_launcher,
- const std::vector<std::string>& test_names) {
+ void UnitTestLauncherDelegate::RunSerially(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) {
if (test_names.empty())
return;
@@ -193,8 +320,9 @@
new_test_names));
}
- void RunBatch(TestLauncher* test_launcher,
- const std::vector<std::string>& test_names) {
+ void UnitTestLauncherDelegate::RunBatch(
+ TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) {
DCHECK(thread_checker_.CalledOnValidThread());
if (test_names.empty())
@@ -234,11 +362,12 @@
callback_state));
}
- void GTestCallback(const GTestCallbackState& callback_state,
- int exit_code,
- const TimeDelta& elapsed_time,
- bool was_timeout,
- const std::string& output) {
+ void UnitTestLauncherDelegate::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,
@@ -262,12 +391,13 @@
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) {
+ void UnitTestLauncherDelegate::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 =
@@ -297,7 +427,8 @@
test_names));
}
- static bool ProcessTestResults(
+ // static
+ bool UnitTestLauncherDelegate::ProcessTestResults(
TestLauncher* test_launcher,
const std::vector<std::string>& test_names,
const base::FilePath& output_file,
@@ -434,143 +565,4 @@
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 (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/test/launcher/unit_test_launcher.h b/test/launcher/unit_test_launcher.h
index 5682ed9..0f661db 100644
--- a/test/launcher/unit_test_launcher.h
+++ b/test/launcher/unit_test_launcher.h
@@ -6,6 +6,8 @@
#define BASE_TEST_LAUNCHER_UNIT_TEST_LAUNCHER_H_
#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/test/launcher/test_launcher.h"
namespace base {
@@ -31,6 +33,75 @@
const RunTestSuiteCallback& run_test_suite);
#endif // defined(OS_WIN)
+// Test launcher delegate for unit tests (mostly to support batching).
+class UnitTestLauncherDelegate : public TestLauncherDelegate {
+ public:
+ explicit UnitTestLauncherDelegate(size_t batch_limit, bool use_job_objects);
+ ~UnitTestLauncherDelegate() override;
+
+ private:
+ struct GTestCallbackState {
+ GTestCallbackState();
+ ~GTestCallbackState();
+
+ TestLauncher* test_launcher;
+ std::vector<std::string> test_names;
+ FilePath output_file;
+ };
+
+ // TestLauncherDelegate:
+ bool GetTests(std::vector<SplitTestName>* output) override;
+ bool ShouldRunTest(const std::string& test_case_name,
+ const std::string& test_name) override;
+ size_t RunTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) override;
+ size_t RetryTests(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names) override;
+
+ // Runs tests serially, each in its own process.
+ void RunSerially(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names);
+
+ // Runs tests in batches (each batch in its own process).
+ void RunBatch(TestLauncher* test_launcher,
+ const std::vector<std::string>& test_names);
+
+ // Callback for batched tests.
+ void GTestCallback(const GTestCallbackState& callback_state,
+ int exit_code,
+ const TimeDelta& elapsed_time,
+ bool was_timeout,
+ const std::string& output);
+
+ // Callback for serialized tests.
+ 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);
+
+ // Interprets test results and reports to the test launcher. Returns true
+ // on success.
+ 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);
+
+ 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnitTestLauncherDelegate);
+};
+
} // namespace base
#endif // BASE_TEST_LAUNCHER_UNIT_TEST_LAUNCHER_H_
diff --git a/test/launcher/unit_test_launcher_ios.cc b/test/launcher/unit_test_launcher_ios.cc
index d4276c8..acb6c71 100644
--- a/test/launcher/unit_test_launcher_ios.cc
+++ b/test/launcher/unit_test_launcher_ios.cc
@@ -6,7 +6,9 @@
#include "base/command_line.h"
#include "base/files/file_path.h"
+#include "base/files/file_util.h"
#include "base/logging.h"
+#include "base/mac/foundation_util.h"
#include "base/test/gtest_util.h"
#include "base/test/test_switches.h"
@@ -27,6 +29,11 @@
LOG(ERROR) << "Failed to write list of tests.";
return 1;
}
+ } else if (command_line->HasSwitch(
+ switches::kTestLauncherPrintWritablePath)) {
+ fprintf(stdout, "%s", mac::GetUserLibraryPath().value().c_str());
+ fflush(stdout);
+ return 0;
}
return run_test_suite.Run();
diff --git a/test/test_suite.cc b/test/test_suite.cc
index 383b82c..a6fc201 100644
--- a/test/test_suite.cc
+++ b/test/test_suite.cc
@@ -135,12 +135,12 @@
testing::GTEST_FLAG(catch_exceptions) = false;
#endif
base::EnableTerminationOnHeapCorruption();
-#if defined(OS_LINUX) && !defined(OS_ANDROID)
+#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(OS_ANDROID)
+#endif // defined(OS_LINUX) && defined(USE_AURA)
// On Android, AtExitManager is created in
// testing/android/native_test_wrapper.cc before main() is called.
diff --git a/test/test_switches.cc b/test/test_switches.cc
index 4d8331d..84aa53c 100644
--- a/test/test_switches.cc
+++ b/test/test_switches.cc
@@ -42,6 +42,10 @@
const char switches::kTestLauncherPrintTestStdio[] =
"test-launcher-print-test-stdio";
+// Print a writable path and exit (for internal use).
+const char switches::kTestLauncherPrintWritablePath[] =
+ "test-launcher-print-writable-path";
+
// Index of the test shard to run, starting from 0 (first shard) to total shards
// minus one (last shard).
const char switches::kTestLauncherShardIndex[] =
diff --git a/test/test_switches.h b/test/test_switches.h
index 336d036..f145f1e 100644
--- a/test/test_switches.h
+++ b/test/test_switches.h
@@ -19,6 +19,7 @@
extern const char kTestLauncherRetryLimit[];
extern const char kTestLauncherSummaryOutput[];
extern const char kTestLauncherPrintTestStdio[];
+extern const char kTestLauncherPrintWritablePath[];
extern const char kTestLauncherShardIndex[];
extern const char kTestLauncherTotalShards[];
extern const char kTestLauncherTimeout[];
diff --git a/test/test_timeouts.cc b/test/test_timeouts.cc
index 66ae85e..b30d6c3 100644
--- a/test/test_timeouts.cc
+++ b/test/test_timeouts.cc
@@ -23,7 +23,7 @@
#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;
+static const int kTimeoutMultiplier = 3;
#elif defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \
defined(SYZYASAN)
static const int kTimeoutMultiplier = 2;
diff --git a/time/time.h b/time/time.h
index d61a351..915eac8 100644
--- a/time/time.h
+++ b/time/time.h
@@ -100,6 +100,15 @@
return delta_;
}
+ // Returns the magnitude (absolute value) of this TimeDelta.
+ TimeDelta magnitude() const {
+ // Some toolchains provide an incomplete C++11 implementation and lack an
+ // int64 overload for std::abs(). The following is a simple branchless
+ // implementation:
+ const int64 mask = delta_ >> (sizeof(delta_) * 8 - 1);
+ return TimeDelta((delta_ + mask) ^ mask);
+ }
+
// Returns true if the time delta is the maximum time delta.
bool is_max() const {
return delta_ == std::numeric_limits<int64>::max();
@@ -588,18 +597,26 @@
TimeTicks() : ticks_(0) {
}
- // Platform-dependent tick count representing "right now."
- // The resolution of this clock is ~1-15ms. Resolution varies depending
- // on hardware/operating system configuration.
+ // Platform-dependent tick count representing "right now." When
+ // IsHighResolution() returns false, the resolution of the clock could be
+ // as coarse as ~15.6ms. Otherwise, the resolution should be no worse than one
+ // microsecond.
static TimeTicks Now();
- // Returns a platform-dependent high-resolution tick count. Implementation
- // is hardware dependent and may or may not return sub-millisecond
- // resolution. THIS CALL IS GENERALLY MUCH MORE EXPENSIVE THAN Now() AND
- // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED.
- static TimeTicks HighResNow();
+ // DEPRECATED
+ // TODO(miu): Remove this function, and all callpoints should call Now().
+ static TimeTicks HighResNow() { return TimeTicks::Now(); }
- static bool IsHighResNowFastAndReliable();
+ // Returns true if the high resolution clock is working on this system and
+ // Now() will return high resolution values. Note that, on systems where the
+ // high resolution clock works but is deemed inefficient, the low resolution
+ // clock will be used instead.
+ static bool IsHighResolution();
+
+ // DEPRECATED
+ // TODO(miu): Remove this function, and all callpoints should call
+ // IsHighResolution().
+ static bool IsHighResNowFastAndReliable() { return IsHighResolution(); }
// Returns true if ThreadNow() is supported on this system.
static bool IsThreadNowSupported() {
@@ -616,24 +633,33 @@
// to (approximately) measure how much time the calling thread spent doing
// actual work vs. being de-scheduled. May return bogus results if the thread
// migrates to another CPU between two calls.
+ //
+ // WARNING: The returned value might NOT have the same origin as Now(). Do not
+ // perform math between TimeTicks values returned by Now() and ThreadNow() and
+ // expect meaningful results.
+ // TODO(miu): Since the timeline of these values is different, the values
+ // should be of a different type.
static TimeTicks ThreadNow();
- // Returns the current system trace time or, if none is defined, the current
- // high-res time (i.e. HighResNow()). On systems where a global trace clock
- // is defined, timestamping TraceEvents's with this value guarantees
- // synchronization between events collected inside chrome and events
- // collected outside (e.g. kernel, X server).
+ // Returns the current system trace time or, if not available on this
+ // platform, a high-resolution time value; or a low-resolution time value if
+ // neither are avalable. On systems where a global trace clock is defined,
+ // timestamping TraceEvents's with this value guarantees synchronization
+ // between events collected inside chrome and events collected outside
+ // (e.g. kernel, X server).
+ //
+ // WARNING: The returned value might NOT have the same origin as Now(). Do not
+ // perform math between TimeTicks values returned by Now() and
+ // NowFromSystemTraceTime() and expect meaningful results.
+ // TODO(miu): Since the timeline of these values is different, the values
+ // should be of a different type.
static TimeTicks NowFromSystemTraceTime();
#if defined(OS_WIN)
- // Get the absolute value of QPC time drift. For testing.
- static int64 GetQPCDriftMicroseconds();
-
+ // Translates an absolute QPC timestamp into a TimeTicks value. The returned
+ // value has the same origin as Now(). Do NOT attempt to use this if
+ // IsHighResolution() returns false.
static TimeTicks FromQPCValue(LONGLONG qpc_value);
-
- // Returns true if the high resolution clock is working on this system.
- // This is only for testing.
- static bool IsHighResClockWorking();
#endif
// Returns true if this object has not been initialized.
diff --git a/time/time_mac.cc b/time/time_mac.cc
index 26c5d7a..3944d06 100644
--- a/time/time_mac.cc
+++ b/time/time_mac.cc
@@ -219,12 +219,7 @@
}
// static
-TimeTicks TimeTicks::HighResNow() {
- return Now();
-}
-
-// static
-bool TimeTicks::IsHighResNowFastAndReliable() {
+bool TimeTicks::IsHighResolution() {
return true;
}
diff --git a/time/time_posix.cc b/time/time_posix.cc
index ad00c51..34b2f5a 100644
--- a/time/time_posix.cc
+++ b/time/time_posix.cc
@@ -314,12 +314,7 @@
}
// static
-TimeTicks TimeTicks::HighResNow() {
- return Now();
-}
-
-// static
-bool TimeTicks::IsHighResNowFastAndReliable() {
+bool TimeTicks::IsHighResolution() {
return true;
}
diff --git a/time/time_unittest.cc b/time/time_unittest.cc
index 16d6fe9..07b1e6b 100644
--- a/time/time_unittest.cc
+++ b/time/time_unittest.cc
@@ -6,6 +6,7 @@
#include <time.h>
+#include <limits>
#include <string>
#include "base/compiler_specific.h"
@@ -642,12 +643,10 @@
}
static void HighResClockTest(TimeTicks (*GetTicks)()) {
-#if defined(OS_WIN)
// HighResNow doesn't work on some systems. Since the product still works
// even if it doesn't work, it makes this entire test questionable.
- if (!TimeTicks::IsHighResClockWorking())
+ if (!TimeTicks::IsHighResolution())
return;
-#endif
// Why do we loop here?
// We're trying to measure that intervals increment in a VERY small amount
@@ -792,6 +791,26 @@
return oss.str();
}
+TEST(TimeDelta, Magnitude) {
+ const int64 zero = 0;
+ EXPECT_EQ(TimeDelta::FromMicroseconds(zero),
+ TimeDelta::FromMicroseconds(zero).magnitude());
+
+ const int64 one = 1;
+ const int64 negative_one = -1;
+ EXPECT_EQ(TimeDelta::FromMicroseconds(one),
+ TimeDelta::FromMicroseconds(one).magnitude());
+ EXPECT_EQ(TimeDelta::FromMicroseconds(one),
+ TimeDelta::FromMicroseconds(negative_one).magnitude());
+
+ const int64 max_int64_minus_one = std::numeric_limits<int64>::max() - 1;
+ const int64 min_int64_plus_two = std::numeric_limits<int64>::min() + 2;
+ EXPECT_EQ(TimeDelta::FromMicroseconds(max_int64_minus_one),
+ TimeDelta::FromMicroseconds(max_int64_minus_one).magnitude());
+ EXPECT_EQ(TimeDelta::FromMicroseconds(max_int64_minus_one),
+ TimeDelta::FromMicroseconds(min_int64_plus_two).magnitude());
+}
+
TEST(TimeDeltaLogging, DCheckEqCompiles) {
DCHECK_EQ(TimeDelta(), TimeDelta());
}
diff --git a/time/time_win.cc b/time/time_win.cc
index 9b4f17d..8ae7640 100644
--- a/time/time_win.cc
+++ b/time/time_win.cc
@@ -307,13 +307,13 @@
return timeGetTime();
}
-DWORD (*tick_function)(void) = &timeGetTimeWrapper;
+DWORD (*g_tick_function)(void) = &timeGetTimeWrapper;
// Accumulation of time lost due to rollover (in milliseconds).
-int64 rollover_ms = 0;
+int64 g_rollover_ms = 0;
// The last timeGetTime value we saw, to detect rollover.
-DWORD last_seen_now = 0;
+DWORD g_last_seen_now = 0;
// Lock protecting rollover_ms and last_seen_now.
// Note: this is a global object, and we usually avoid these. However, the time
@@ -321,169 +321,161 @@
// easy to use a Singleton without even knowing it, and that may lead to many
// gotchas). Its impact on startup time should be negligible due to low-level
// nature of time code.
-base::Lock rollover_lock;
+base::Lock g_rollover_lock;
// We use timeGetTime() to implement TimeTicks::Now(). This can be problematic
// because it returns the number of milliseconds since Windows has started,
// which will roll over the 32-bit value every ~49 days. We try to track
// rollover ourselves, which works if TimeTicks::Now() is called at least every
// 49 days.
-TimeDelta RolloverProtectedNow() {
- base::AutoLock locked(rollover_lock);
+TimeTicks RolloverProtectedNow() {
+ base::AutoLock locked(g_rollover_lock);
// We should hold the lock while calling tick_function to make sure that
// we keep last_seen_now stay correctly in sync.
- DWORD now = tick_function();
- if (now < last_seen_now)
- rollover_ms += 0x100000000I64; // ~49.7 days.
- last_seen_now = now;
- return TimeDelta::FromMilliseconds(now + rollover_ms);
+ DWORD now = g_tick_function();
+ if (now < g_last_seen_now)
+ g_rollover_ms += 0x100000000I64; // ~49.7 days.
+ g_last_seen_now = now;
+ return TimeTicks() + TimeDelta::FromMilliseconds(now + g_rollover_ms);
}
-bool IsBuggyAthlon(const base::CPU& cpu) {
- // On Athlon X2 CPUs (e.g. model 15) QueryPerformanceCounter is
- // unreliable. Fallback to low-res clock.
- return cpu.vendor_name() == "AuthenticAMD" && cpu.family() == 15;
-}
-
-// Overview of time counters:
+// Discussion of tick counter options on Windows:
+//
// (1) CPU cycle counter. (Retrieved via RDTSC)
// The CPU counter provides the highest resolution time stamp and is the least
-// expensive to retrieve. However, the CPU counter is unreliable and should not
-// be used in production. Its biggest issue is that it is per processor and it
-// is not synchronized between processors. Also, on some computers, the counters
-// will change frequency due to thermal and power changes, and stop in some
-// states.
+// expensive to retrieve. However, on older CPUs, two issues can affect its
+// reliability: First it is maintained per processor and not synchronized
+// between processors. Also, the counters will change frequency due to thermal
+// and power changes, and stop in some states.
//
// (2) QueryPerformanceCounter (QPC). The QPC counter provides a high-
-// resolution (100 nanoseconds) time stamp but is comparatively more expensive
-// to retrieve. What QueryPerformanceCounter actually does is up to the HAL.
-// (with some help from ACPI).
-// According to http://blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx
-// in the worst case, it gets the counter from the rollover interrupt on the
+// resolution (<1 microsecond) time stamp. On most hardware running today, it
+// auto-detects and uses the constant-rate RDTSC counter to provide extremely
+// efficient and reliable time stamps.
+//
+// On older CPUs where RDTSC is unreliable, it falls back to using more
+// expensive (20X to 40X more costly) alternate clocks, such as HPET or the ACPI
+// PM timer, and can involve system calls; and all this is up to the HAL (with
+// some help from ACPI). According to
+// http://blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx, in the
+// worst case, it gets the counter from the rollover interrupt on the
// programmable interrupt timer. In best cases, the HAL may conclude that the
// RDTSC counter runs at a constant frequency, then it uses that instead. On
// multiprocessor machines, it will try to verify the values returned from
// RDTSC on each processor are consistent with each other, and apply a handful
// of workarounds for known buggy hardware. In other words, QPC is supposed to
-// give consistent result on a multiprocessor computer, but it is unreliable in
-// reality due to bugs in BIOS or HAL on some, especially old computers.
-// With recent updates on HAL and newer BIOS, QPC is getting more reliable but
-// it should be used with caution.
+// give consistent results on a multiprocessor computer, but for older CPUs it
+// can be unreliable due bugs in BIOS or HAL.
//
-// (3) System time. The system time provides a low-resolution (typically 10ms
-// to 55 milliseconds) time stamp but is comparatively less expensive to
-// retrieve and more reliable.
-class HighResNowSingleton {
- public:
- HighResNowSingleton()
- : ticks_per_second_(0),
- skew_(0) {
+// (3) System time. The system time provides a low-resolution (from ~1 to ~15.6
+// milliseconds) time stamp but is comparatively less expensive to retrieve and
+// more reliable. Time::EnableHighResolutionTimer() and
+// Time::ActivateHighResolutionTimer() can be called to alter the resolution of
+// this timer; and also other Windows applications can alter it, affecting this
+// one.
- base::CPU cpu;
- if (IsBuggyAthlon(cpu))
- return;
+using NowFunction = TimeTicks (*)(void);
- // Synchronize the QPC clock with GetSystemTimeAsFileTime.
- LARGE_INTEGER ticks_per_sec = {0};
- if (!QueryPerformanceFrequency(&ticks_per_sec))
- return; // QPC is not available.
- ticks_per_second_ = ticks_per_sec.QuadPart;
+TimeTicks InitialNowFunction();
+TimeTicks InitialSystemTraceNowFunction();
- skew_ = UnreliableNow() - ReliableNow();
+// See "threading notes" in InitializeNowFunctionPointers() for details on how
+// concurrent reads/writes to these globals has been made safe.
+NowFunction g_now_function = &InitialNowFunction;
+NowFunction g_system_trace_now_function = &InitialSystemTraceNowFunction;
+int64 g_qpc_ticks_per_second = 0;
+
+// As of January 2015, use of <atomic> is forbidden in Chromium code. This is
+// what std::atomic_thread_fence does on Windows on all Intel architectures when
+// the memory_order argument is anything but std::memory_order_seq_cst:
+#define ATOMIC_THREAD_FENCE(memory_order) _ReadWriteBarrier();
+
+TimeDelta QPCValueToTimeDelta(LONGLONG qpc_value) {
+ // Ensure that the assignment to |g_qpc_ticks_per_second|, made in
+ // InitializeNowFunctionPointers(), has happened by this point.
+ ATOMIC_THREAD_FENCE(memory_order_acquire);
+
+ DCHECK_GT(g_qpc_ticks_per_second, 0);
+
+ // If the QPC Value is below the overflow threshold, we proceed with
+ // simple multiply and divide.
+ if (qpc_value < Time::kQPCOverflowThreshold) {
+ return TimeDelta::FromMicroseconds(
+ qpc_value * Time::kMicrosecondsPerSecond / g_qpc_ticks_per_second);
}
-
- bool IsUsingHighResClock() {
- return ticks_per_second_ != 0;
- }
-
- TimeDelta Now() {
- if (IsUsingHighResClock())
- return TimeDelta::FromMicroseconds(UnreliableNow());
-
- // Just fallback to the slower clock.
- return RolloverProtectedNow();
- }
-
- int64 GetQPCDriftMicroseconds() {
- if (!IsUsingHighResClock())
- return 0;
- return abs((UnreliableNow() - ReliableNow()) - skew_);
- }
-
- int64 QPCValueToMicroseconds(LONGLONG qpc_value) {
- if (!ticks_per_second_)
- return 0;
- // If the QPC Value is below the overflow threshold, we proceed with
- // simple multiply and divide.
- if (qpc_value < Time::kQPCOverflowThreshold)
- return qpc_value * Time::kMicrosecondsPerSecond / ticks_per_second_;
- // Otherwise, calculate microseconds in a round about manner to avoid
- // overflow and precision issues.
- int64 whole_seconds = qpc_value / ticks_per_second_;
- int64 leftover_ticks = qpc_value - (whole_seconds * ticks_per_second_);
- int64 microseconds = (whole_seconds * Time::kMicrosecondsPerSecond) +
- ((leftover_ticks * Time::kMicrosecondsPerSecond) /
- ticks_per_second_);
- return microseconds;
- }
-
- private:
- // Get the number of microseconds since boot in an unreliable fashion.
- int64 UnreliableNow() {
- LARGE_INTEGER now;
- QueryPerformanceCounter(&now);
- return QPCValueToMicroseconds(now.QuadPart);
- }
-
- // Get the number of microseconds since boot in a reliable fashion.
- int64 ReliableNow() {
- return RolloverProtectedNow().InMicroseconds();
- }
-
- int64 ticks_per_second_; // 0 indicates QPF failed and we're broken.
- int64 skew_; // Skew between lo-res and hi-res clocks (for debugging).
-};
-
-static base::LazyInstance<HighResNowSingleton>::Leaky
- leaky_high_res_now_singleton = LAZY_INSTANCE_INITIALIZER;
-
-HighResNowSingleton* GetHighResNowSingleton() {
- return leaky_high_res_now_singleton.Pointer();
+ // Otherwise, calculate microseconds in a round about manner to avoid
+ // overflow and precision issues.
+ int64 whole_seconds = qpc_value / g_qpc_ticks_per_second;
+ int64 leftover_ticks = qpc_value - (whole_seconds * g_qpc_ticks_per_second);
+ return TimeDelta::FromMicroseconds(
+ (whole_seconds * Time::kMicrosecondsPerSecond) +
+ ((leftover_ticks * Time::kMicrosecondsPerSecond) /
+ g_qpc_ticks_per_second));
}
-TimeDelta HighResNowWrapper() {
- return GetHighResNowSingleton()->Now();
+TimeTicks QPCNow() {
+ LARGE_INTEGER now;
+ QueryPerformanceCounter(&now);
+ return TimeTicks() + QPCValueToTimeDelta(now.QuadPart);
}
-typedef TimeDelta (*NowFunction)(void);
+bool IsBuggyAthlon(const base::CPU& cpu) {
+ // On Athlon X2 CPUs (e.g. model 15) QueryPerformanceCounter is unreliable.
+ return cpu.vendor_name() == "AuthenticAMD" && cpu.family() == 15;
+}
-bool CPUReliablySupportsHighResTime() {
+void InitializeNowFunctionPointers() {
+ LARGE_INTEGER ticks_per_sec = {0};
+ if (!QueryPerformanceFrequency(&ticks_per_sec))
+ ticks_per_sec.QuadPart = 0;
+
+ // If Windows cannot provide a QPC implementation, both Now() and
+ // NowFromSystemTraceTime() must use the low-resolution clock.
+ //
+ // If the QPC implementation is expensive and/or unreliable, Now() will use
+ // the low-resolution clock, but NowFromSystemTraceTime() will use the QPC (in
+ // the hope that it is still useful for tracing purposes). A CPU lacking a
+ // non-stop time counter will cause Windows to provide an alternate QPC
+ // implementation that works, but is expensive to use. Certain Athlon CPUs are
+ // known to make the QPC implementation unreliable.
+ //
+ // Otherwise, both Now functions can use the high-resolution QPC clock. As of
+ // 4 January 2015, ~68% of users fall within this category.
+ NowFunction now_function;
+ NowFunction system_trace_now_function;
base::CPU cpu;
- if (!cpu.has_non_stop_time_stamp_counter() ||
- !GetHighResNowSingleton()->IsUsingHighResClock())
- return false;
+ if (ticks_per_sec.QuadPart <= 0) {
+ now_function = system_trace_now_function = &RolloverProtectedNow;
+ } else if (!cpu.has_non_stop_time_stamp_counter() || IsBuggyAthlon(cpu)) {
+ now_function = &RolloverProtectedNow;
+ system_trace_now_function = &QPCNow;
+ } else {
+ now_function = system_trace_now_function = &QPCNow;
+ }
- if (IsBuggyAthlon(cpu))
- return false;
-
- return true;
+ // Threading note 1: In an unlikely race condition, it's possible for two or
+ // more threads to enter InitializeNowFunctionPointers() in parallel. This is
+ // not a problem since all threads should end up writing out the same values
+ // to the global variables.
+ //
+ // Threading note 2: A release fence is placed here to ensure, from the
+ // perspective of other threads using the function pointers, that the
+ // assignment to |g_qpc_ticks_per_second| happens before the function pointers
+ // are changed.
+ g_qpc_ticks_per_second = ticks_per_sec.QuadPart;
+ ATOMIC_THREAD_FENCE(memory_order_release);
+ g_now_function = now_function;
+ g_system_trace_now_function = system_trace_now_function;
}
-TimeDelta InitialNowFunction();
+TimeTicks InitialNowFunction() {
+ InitializeNowFunctionPointers();
+ return g_now_function();
+}
-volatile NowFunction now_function = InitialNowFunction;
-
-TimeDelta InitialNowFunction() {
- if (!CPUReliablySupportsHighResTime()) {
- InterlockedExchangePointer(
- reinterpret_cast<void* volatile*>(&now_function),
- &RolloverProtectedNow);
- return RolloverProtectedNow();
- }
- InterlockedExchangePointer(
- reinterpret_cast<void* volatile*>(&now_function),
- &HighResNowWrapper);
- return HighResNowWrapper();
+TimeTicks InitialSystemTraceNowFunction() {
+ InitializeNowFunctionPointers();
+ return g_system_trace_now_function();
}
} // namespace
@@ -491,27 +483,24 @@
// static
TimeTicks::TickFunctionType TimeTicks::SetMockTickFunction(
TickFunctionType ticker) {
- base::AutoLock locked(rollover_lock);
- TickFunctionType old = tick_function;
- tick_function = ticker;
- rollover_ms = 0;
- last_seen_now = 0;
+ base::AutoLock locked(g_rollover_lock);
+ TickFunctionType old = g_tick_function;
+ g_tick_function = ticker;
+ g_rollover_ms = 0;
+ g_last_seen_now = 0;
return old;
}
// static
TimeTicks TimeTicks::Now() {
- return TimeTicks() + now_function();
+ return g_now_function();
}
// static
-TimeTicks TimeTicks::HighResNow() {
- return TimeTicks() + HighResNowWrapper();
-}
-
-// static
-bool TimeTicks::IsHighResNowFastAndReliable() {
- return CPUReliablySupportsHighResTime();
+bool TimeTicks::IsHighResolution() {
+ if (g_now_function == &InitialNowFunction)
+ InitializeNowFunctionPointers();
+ return g_now_function == &QPCNow;
}
// static
@@ -522,27 +511,17 @@
// static
TimeTicks TimeTicks::NowFromSystemTraceTime() {
- return HighResNow();
-}
-
-// static
-int64 TimeTicks::GetQPCDriftMicroseconds() {
- return GetHighResNowSingleton()->GetQPCDriftMicroseconds();
+ return g_system_trace_now_function();
}
// static
TimeTicks TimeTicks::FromQPCValue(LONGLONG qpc_value) {
- return TimeTicks(GetHighResNowSingleton()->QPCValueToMicroseconds(qpc_value));
-}
-
-// static
-bool TimeTicks::IsHighResClockWorking() {
- return GetHighResNowSingleton()->IsUsingHighResClock();
+ return TimeTicks() + QPCValueToTimeDelta(qpc_value);
}
// TimeDelta ------------------------------------------------------------------
// static
TimeDelta TimeDelta::FromQPCValue(LONGLONG qpc_value) {
- return TimeDelta(GetHighResNowSingleton()->QPCValueToMicroseconds(qpc_value));
+ return QPCValueToTimeDelta(qpc_value);
}
diff --git a/time/time_win_unittest.cc b/time/time_win_unittest.cc
index 058dfd7..71cd29e 100644
--- a/time/time_win_unittest.cc
+++ b/time/time_win_unittest.cc
@@ -6,6 +6,10 @@
#include <mmsystem.h>
#include <process.h>
+#include <cmath>
+#include <limits>
+#include <vector>
+
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -114,7 +118,7 @@
TEST(TimeTicks, SubMillisecondTimers) {
// HighResNow doesn't work on some systems. Since the product still works
// even if it doesn't work, it makes this entire test questionable.
- if (!TimeTicks::IsHighResClockWorking())
+ if (!TimeTicks::IsHighResolution())
return;
const int kRetries = 1000;
@@ -183,7 +187,7 @@
TestCase cases[] = {
{ reinterpret_cast<TestFunc>(Time::Now), "Time::Now" },
{ TimeTicks::Now, "TimeTicks::Now" },
- { TimeTicks::HighResNow, "TimeTicks::HighResNow" },
+ { TimeTicks::NowFromSystemTraceTime, "TimeTicks::NowFromSystemTraceTime" },
{ NULL, "" }
};
@@ -207,65 +211,57 @@
}
}
-// http://crbug.com/396384
-TEST(TimeTicks, DISABLED_Drift) {
- // If QPC is disabled, this isn't measuring anything.
- if (!TimeTicks::IsHighResClockWorking())
- return;
-
- const int kIterations = 100;
- int64 total_drift = 0;
-
- for (int i = 0; i < kIterations; ++i) {
- int64 drift_microseconds = TimeTicks::GetQPCDriftMicroseconds();
-
- // Make sure the drift never exceeds our limit.
- EXPECT_LT(drift_microseconds, 50000);
-
- // Sleep for a few milliseconds (note that it means 1000 microseconds).
- // If we check the drift too frequently, it's going to increase
- // monotonically, making our measurement less realistic.
- base::PlatformThread::Sleep(
- base::TimeDelta::FromMilliseconds((i % 2 == 0) ? 1 : 2));
-
- total_drift += drift_microseconds;
- }
-
- // Sanity check. We expect some time drift to occur, especially across
- // the number of iterations we do.
- EXPECT_LT(0, total_drift);
-
- printf("average time drift in microseconds: %lld\n",
- total_drift / kIterations);
-}
-
-int64 QPCValueToMicrosecondsSafely(LONGLONG qpc_value,
- int64 ticks_per_second) {
- int64 whole_seconds = qpc_value / ticks_per_second;
- int64 leftover_ticks = qpc_value % ticks_per_second;
- int64 microseconds = (whole_seconds * Time::kMicrosecondsPerSecond) +
- ((leftover_ticks * Time::kMicrosecondsPerSecond) /
- ticks_per_second);
- return microseconds;
-}
-
TEST(TimeTicks, FromQPCValue) {
- if (!TimeTicks::IsHighResClockWorking())
+ if (!TimeTicks::IsHighResolution())
return;
+
LARGE_INTEGER frequency;
- QueryPerformanceFrequency(&frequency);
- int64 ticks_per_second = frequency.QuadPart;
- LONGLONG qpc_value = Time::kQPCOverflowThreshold;
- TimeTicks expected_value = TimeTicks::FromInternalValue(
- QPCValueToMicrosecondsSafely(qpc_value + 1, ticks_per_second));
- EXPECT_EQ(expected_value,
- TimeTicks::FromQPCValue(qpc_value + 1));
- expected_value = TimeTicks::FromInternalValue(
- QPCValueToMicrosecondsSafely(qpc_value, ticks_per_second));
- EXPECT_EQ(expected_value,
- TimeTicks::FromQPCValue(qpc_value));
- expected_value = TimeTicks::FromInternalValue(
- QPCValueToMicrosecondsSafely(qpc_value - 1, ticks_per_second));
- EXPECT_EQ(expected_value,
- TimeTicks::FromQPCValue(qpc_value - 1));
+ ASSERT_TRUE(QueryPerformanceFrequency(&frequency));
+ const int64 ticks_per_second = frequency.QuadPart;
+ ASSERT_GT(ticks_per_second, 0);
+
+ // Generate the tick values to convert, advancing the tick count by varying
+ // amounts. These values will ensure that both the fast and overflow-safe
+ // conversion logic in FromQPCValue() is tested, and across the entire range
+ // of possible QPC tick values.
+ std::vector<int64> test_cases;
+ test_cases.push_back(0);
+ const int kNumAdvancements = 100;
+ int64 ticks = 0;
+ int64 ticks_increment = 10;
+ for (int i = 0; i < kNumAdvancements; ++i) {
+ test_cases.push_back(ticks);
+ ticks += ticks_increment;
+ ticks_increment = ticks_increment * 6 / 5;
+ }
+ test_cases.push_back(Time::kQPCOverflowThreshold - 1);
+ test_cases.push_back(Time::kQPCOverflowThreshold);
+ test_cases.push_back(Time::kQPCOverflowThreshold + 1);
+ ticks = Time::kQPCOverflowThreshold + 10;
+ ticks_increment = 10;
+ for (int i = 0; i < kNumAdvancements; ++i) {
+ test_cases.push_back(ticks);
+ ticks += ticks_increment;
+ ticks_increment = ticks_increment * 6 / 5;
+ }
+ test_cases.push_back(std::numeric_limits<int64>::max());
+
+ // Test that the conversions using FromQPCValue() match those computed here
+ // using simple floating-point arithmetic. The floating-point math provides
+ // enough precision to confirm the implementation is correct to the
+ // microsecond for all |test_cases| (though it would be insufficient to
+ // confirm many "very large" tick values which are not being tested here).
+ for (int64 ticks : test_cases) {
+ const double expected_microseconds_since_origin =
+ (static_cast<double>(ticks) * Time::kMicrosecondsPerSecond) /
+ ticks_per_second;
+ const TimeTicks converted_value = TimeTicks::FromQPCValue(ticks);
+ const double converted_microseconds_since_origin =
+ static_cast<double>((converted_value - TimeTicks()).InMicroseconds());
+ EXPECT_NEAR(expected_microseconds_since_origin,
+ converted_microseconds_since_origin,
+ 1.0)
+ << "ticks=" << ticks << ", to be converted via logic path: "
+ << (ticks < Time::kQPCOverflowThreshold ? "FAST" : "SAFE");
+ }
}
diff --git a/values.cc b/values.cc
index b478b62..061b7a1 100644
--- a/values.cc
+++ b/values.cc
@@ -89,6 +89,10 @@
return new Value(TYPE_NULL);
}
+bool Value::GetAsBinary(const BinaryValue** out_value) const {
+ return false;
+}
+
bool Value::GetAsBoolean(bool* out_value) const {
return false;
}
@@ -319,6 +323,12 @@
return new BinaryValue(scoped_buffer_copy.Pass(), size);
}
+bool BinaryValue::GetAsBinary(const BinaryValue** out_value) const {
+ if (out_value)
+ *out_value = this;
+ return true;
+}
+
BinaryValue* BinaryValue::DeepCopy() const {
return CreateWithCopiedBuffer(buffer_.get(), size_);
}
diff --git a/values.h b/values.h
index 4c22f3d..4648283 100644
--- a/values.h
+++ b/values.h
@@ -33,6 +33,7 @@
namespace base {
+class BinaryValue;
class DictionaryValue;
class FundamentalValue;
class ListValue;
@@ -85,6 +86,7 @@
virtual bool GetAsString(std::string* out_value) const;
virtual bool GetAsString(string16* out_value) const;
virtual bool GetAsString(const StringValue** out_value) const;
+ virtual bool GetAsBinary(const BinaryValue** out_value) const;
virtual bool GetAsList(ListValue** out_value);
virtual bool GetAsList(const ListValue** out_value) const;
virtual bool GetAsDictionary(DictionaryValue** out_value);
@@ -188,6 +190,7 @@
const char* GetBuffer() const { return buffer_.get(); }
// Overridden from Value:
+ bool GetAsBinary(const BinaryValue** out_value) const override;
BinaryValue* DeepCopy() const override;
bool Equals(const Value* other) const override;
diff --git a/values_unittest.cc b/values_unittest.cc
index 296880a..b66730b 100644
--- a/values_unittest.cc
+++ b/values_unittest.cc
@@ -127,6 +127,12 @@
ASSERT_NE(stack_buffer, binary->GetBuffer());
ASSERT_EQ(42U, binary->GetSize());
ASSERT_EQ(0, memcmp(stack_buffer, binary->GetBuffer(), binary->GetSize()));
+
+ // Test overloaded GetAsBinary.
+ Value* narrow_value = binary.get();
+ const BinaryValue* narrow_binary = NULL;
+ ASSERT_TRUE(narrow_value->GetAsBinary(&narrow_binary));
+ EXPECT_EQ(binary.get(), narrow_binary);
}
TEST(ValuesTest, StringValue) {
diff --git a/win/message_window.cc b/win/message_window.cc
index 57fe64c..0b4b29f 100644
--- a/win/message_window.cc
+++ b/win/message_window.cc
@@ -7,6 +7,7 @@
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/process/memory.h"
+#include "base/profiler/scoped_tracker.h"
#include "base/win/wrapped_window_proc.h"
const wchar_t kMessageWindowClassName[] = L"Chrome_MessageWindow";
@@ -120,6 +121,10 @@
UINT message,
WPARAM wparam,
LPARAM lparam) {
+ // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
+ tracked_objects::ScopedTracker tracking_profile(
+ FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 MessageWindow::WindowProc"));
+
MessageWindow* self = reinterpret_cast<MessageWindow*>(
GetWindowLongPtr(hwnd, GWLP_USERDATA));