Index: Source/core/workers/WorkerThreadTest.cpp |
diff --git a/Source/core/workers/WorkerThreadTest.cpp b/Source/core/workers/WorkerThreadTest.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d86c31d7fb2587e2eaa947317ed68bb8b536d600 |
--- /dev/null |
+++ b/Source/core/workers/WorkerThreadTest.cpp |
@@ -0,0 +1,457 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "config.h" |
+#include "core/workers/WorkerThread.h" |
+ |
+#include "bindings/core/v8/ScriptWrappable.h" |
+#include "core/inspector/ConsoleMessage.h" |
+#include "core/workers/SharedWorkerGlobalScope.h" |
+#include "core/workers/SharedWorkerThread.h" |
+#include "core/workers/WorkerGlobalScope.h" |
+#include "core/workers/WorkerReportingProxy.h" |
+#include "core/workers/WorkerThreadStartupData.h" |
+#include "public/platform/WebScheduler.h" |
+#include "wtf/ThreadingPrimitives.h" |
+#include <gmock/gmock.h> |
+#include <gtest/gtest.h> |
+ |
+using testing::_; |
+using testing::Invoke; |
+using testing::Return; |
+using testing::Mock; |
+ |
+namespace blink { |
+ |
+namespace { |
+class MockWorkerLoaderProxy : public WorkerLoaderProxy { |
+public: |
+ MockWorkerLoaderProxy() : WorkerLoaderProxy(nullptr) { } |
+ ~MockWorkerLoaderProxy() override { } |
+ |
+ MOCK_METHOD1(postTaskToLoader, void(PassOwnPtr<ExecutionContextTask>)); |
+ MOCK_METHOD1(postTaskToWorkerGlobalScope, bool(PassOwnPtr<ExecutionContextTask>)); |
+}; |
+ |
+class MockWorkerReportingProxy : public WorkerReportingProxy { |
+public: |
+ MockWorkerReportingProxy() { } |
+ ~MockWorkerReportingProxy() override { } |
+ |
+ MOCK_METHOD5(reportException, void(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, int exceptionId)); |
+ MOCK_METHOD1(reportConsoleMessage, void(PassRefPtrWillBeRawPtr<ConsoleMessage>)); |
+ MOCK_METHOD1(postMessageToPageInspector, void(const String&)); |
+ MOCK_METHOD0(postWorkerConsoleAgentEnabled, void()); |
+ MOCK_METHOD1(didEvaluateWorkerScript, void(bool success)); |
+ MOCK_METHOD1(workerGlobalScopeStarted, void(WorkerGlobalScope*)); |
+ MOCK_METHOD0(workerGlobalScopeClosed, void()); |
+ MOCK_METHOD0(workerThreadTerminated, void()); |
+ MOCK_METHOD0(willDestroyWorkerGlobalScope, void()); |
+}; |
+ |
+class FakeWorkerGlobalScope : public WorkerGlobalScope { |
+public: |
+ typedef WorkerGlobalScope Base; |
+ |
+ FakeWorkerGlobalScope(const KURL& url, const String& userAgent, WorkerThread* thread, const SecurityOrigin* starterOrigin, PassOwnPtrWillBeRawPtr<WorkerClients> workerClients) |
+ : WorkerGlobalScope(url, userAgent, thread, monotonicallyIncreasingTime(), starterOrigin, workerClients) |
+ { |
+ } |
+ |
+ ~FakeWorkerGlobalScope() override |
+ { |
+ } |
+ |
+ virtual bool isSharedWorkerGlobalScope() const override { return true; } |
+ |
+ // EventTarget |
+ virtual const AtomicString& interfaceName() const override |
+ { |
+ return EventTargetNames::SharedWorkerGlobalScope; |
+ } |
+ |
+ virtual void logExceptionToConsole(const String&, int , const String&, int, int, PassRefPtrWillBeRawPtr<ScriptCallStack>) override |
+ { |
+ } |
+ |
+ // Setters/Getters for attributes in SharedWorkerGlobalScope.idl |
+ DEFINE_ATTRIBUTE_EVENT_LISTENER(connect); |
+ String name() const { return "FakeWorkerGlobalScope"; } |
+ |
+ DECLARE_VIRTUAL_TRACE(); |
+}; |
+ |
+DEFINE_TRACE(FakeWorkerGlobalScope) |
+{ |
+ WorkerGlobalScope::trace(visitor); |
+} |
+ |
+class WorkerThreadForTest : public WorkerThread { |
+public: |
+ WorkerThreadForTest( |
+ WorkerLoaderProxy* mockWorkerLoaderProxy, |
+ WorkerReportingProxy& mockWorkerReportingProxy, |
+ PassOwnPtrWillBeRawPtr<WorkerThreadStartupData> workerThreadStartupData) |
+ : WorkerThread("Test Thread", mockWorkerLoaderProxy, mockWorkerReportingProxy, workerThreadStartupData) |
+ { |
+ } |
+ |
+ ~WorkerThreadForTest() override { } |
+ |
+ // NOTE we use the WebSchduler to post tasks in this test because it's not affected by the terminated() method, which |
+ // tasks posted via the WorkerTrhead directly are. |
+ using WorkerThread::schedulerForTesting; |
+ using WorkerThread::threadForTesting; |
+ |
+ MOCK_METHOD1(doIdleGc, bool(double deadlineSeconds)); |
+ MOCK_METHOD0(terminated, bool()); |
+ |
+ PassRefPtrWillBeRawPtr<WorkerGlobalScope> createWorkerGlobalScope(PassOwnPtr<WorkerThreadStartupData> startupData) override |
+ { |
+ return adoptRefWillBeNoop(new FakeWorkerGlobalScope(startupData->m_scriptURL, startupData->m_userAgent, this, startupData->m_starterOrigin, startupData->m_workerClients.release())); |
+ } |
+}; |
+ |
+class WakeupTask : public WebThread::Task { |
+public: |
+ WakeupTask() { } |
+ |
+ ~WakeupTask() override { } |
+ |
+ void run() override { } |
+}; |
+ |
+ |
+class PostDelayedWakeupTask : public WebThread::Task { |
+public: |
+ PostDelayedWakeupTask(WebScheduler* scheduler, long long delay) : m_scheduler(scheduler), m_delay(delay) { } |
+ |
+ ~PostDelayedWakeupTask() override { } |
+ |
+ void run() override |
+ { |
+ m_scheduler->postTimerTask(FROM_HERE, new WakeupTask(), m_delay); |
+ } |
+ |
+ WebScheduler* m_scheduler; // NOT OWNED |
+ long long m_delay; |
+}; |
+ |
+class SignalTask : public WebThread::Task { |
+public: |
+ SignalTask(ThreadCondition* completion, Mutex* mutex) : m_completion(completion), m_mutex(mutex) { } |
+ |
+ ~SignalTask() override { } |
+ |
+ void run() override |
+ { |
+ WTF::Locker<Mutex> lock(*m_mutex); |
+ m_completion->signal(); |
+ } |
+ |
+private: |
+ ThreadCondition* m_completion; // NOT OWNED |
+ Mutex* m_mutex; // NOT OWNED |
+}; |
+ |
+} // namespace |
+ |
+class WorkerThreadTest : public testing::Test { |
+public: |
+ void SetUp() override |
+ { |
+ m_mockWorkerLoaderProxy = new MockWorkerLoaderProxy(); |
+ m_mockWorkerReportingProxy = adoptPtr(new MockWorkerReportingProxy()); |
+ m_securityOrigin = SecurityOrigin::create(KURL(ParsedURLString, "http://fake.url/")); |
+ m_workerThread = adoptRef(new WorkerThreadForTest( |
+ m_mockWorkerLoaderProxy.get(), |
+ *m_mockWorkerReportingProxy, |
+ WorkerThreadStartupData::create( |
+ KURL(ParsedURLString, "http://fake.url/"), |
+ "fake user agent", |
+ "//fake source code", |
+ nullptr, |
+ DontPauseWorkerGlobalScopeOnStart, |
+ "contentSecurityPolicy", |
+ ContentSecurityPolicyHeaderTypeReport, |
+ m_securityOrigin.get(), |
+ WorkerClients::create(), |
+ V8CacheOptionsDefault))); |
+ ExpectWorkerLifetimeReportingCalls(); |
+ } |
+ |
+ void StartAndWaitForInit() |
+ { |
+ ThreadCondition complete; |
+ Mutex mutex; |
+ |
+ m_workerThread->start(); |
+ m_workerThread->threadForTesting()->postTask(FROM_HERE, new SignalTask(&complete, &mutex)); |
+ |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ complete.wait(mutex); |
+ } |
+ } |
+ |
+ void PostWakeUpTask(long long waitMs) |
+ { |
+ WebScheduler* scheduler = m_workerThread->schedulerForTesting(); |
+ |
+ // The idle task will get posted on an after wake up queue, so we need another task |
+ // posted at the right time to wake the system up. We don't know the right delay here |
+ // since the thread can take a variable length of time to be responsive, however this |
+ // isn't a problem when posting a delayed task from within a task on the worker thread. |
+ scheduler->postLoadingTask(FROM_HERE, new PostDelayedWakeupTask(scheduler, waitMs)); |
+ } |
+ |
+protected: |
+ void ExpectWorkerLifetimeReportingCalls() |
+ { |
+ EXPECT_CALL(*m_mockWorkerReportingProxy, workerGlobalScopeStarted(_)).Times(1); |
+ EXPECT_CALL(*m_mockWorkerReportingProxy, didEvaluateWorkerScript(true)).Times(1); |
+ EXPECT_CALL(*m_mockWorkerReportingProxy, workerThreadTerminated()).Times(1); |
+ EXPECT_CALL(*m_mockWorkerReportingProxy, willDestroyWorkerGlobalScope()).Times(1); |
+ } |
+ |
+ RefPtr<SecurityOrigin> m_securityOrigin; |
+ RefPtr<MockWorkerLoaderProxy> m_mockWorkerLoaderProxy; |
+ OwnPtr<MockWorkerReportingProxy> m_mockWorkerReportingProxy; |
+ RefPtr<WorkerThreadForTest> m_workerThread; |
+}; |
+ |
+TEST_F(WorkerThreadTest, GcOccursWhileIdle) |
+{ |
+ ThreadCondition gcDone; |
+ Mutex mutex; |
+ |
+ ON_CALL(*m_workerThread, doIdleGc(_)).WillByDefault(Invoke( |
+ [&gcDone, &mutex](double) |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ gcDone.signal(); |
+ return false; |
+ })); |
+ |
+ EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(1); |
+ EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
+ |
+ StartAndWaitForInit(); |
+ PostWakeUpTask(310ul); // 10ms after the quiescent period ends. |
+ |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ gcDone.wait(mutex); |
+ } |
+ |
+ m_workerThread->terminateAndWait(); |
+}; |
+ |
+class RepeatingTask : public WebThread::Task { |
+public: |
+ RepeatingTask(WebScheduler* scheduler, ThreadCondition* completion, Mutex* mutex) |
+ : RepeatingTask(scheduler, completion, mutex, 0) { } |
+ |
+ ~RepeatingTask() override { } |
+ |
+ void run() override |
+ { |
+ m_taskCount++; |
+ if (m_taskCount == 10) { |
+ WTF::Locker<Mutex> lock(*m_mutex); |
+ m_completion->signal(); |
+ } |
+ |
+ m_scheduler->postTimerTask( |
+ FROM_HERE, new RepeatingTask(m_scheduler, m_completion, m_mutex, m_taskCount), 50ul); |
+ m_scheduler->postLoadingTask(FROM_HERE, new WakeupTask()); |
+ |
+ } |
+ |
+private: |
+ RepeatingTask(WebScheduler* scheduler, ThreadCondition* completion, Mutex* mutex, int taskCount) |
+ : m_scheduler(scheduler) |
+ , m_completion(completion) |
+ , m_mutex(mutex) |
+ , m_taskCount(taskCount) |
+ { } |
+ |
+ WebScheduler* m_scheduler; // NOT OWNED |
+ ThreadCondition* m_completion; |
+ Mutex* m_mutex; |
+ int m_taskCount; |
+}; |
+ |
+TEST_F(WorkerThreadTest, GcDoesNotOccurIfGapBetweenDelayedTasksIsTooSmall) |
+{ |
+ ThreadCondition completion; |
+ Mutex mutex; |
+ |
+ EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(0); |
+ EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
+ |
+ StartAndWaitForInit(); |
+ |
+ WebScheduler* scheduler = m_workerThread->schedulerForTesting(); |
+ |
+ // Post a repeating task that should prevent any GC from happening. |
+ scheduler->postLoadingTask(FROM_HERE, new RepeatingTask(scheduler, &completion, &mutex)); |
+ |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ completion.wait(mutex); |
+ } |
+ |
+ // Make sure doIdleGc has not been called by this stage. |
+ Mock::VerifyAndClearExpectations(m_workerThread.get()); |
+ |
+ EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
+ m_workerThread->terminateAndWait(); |
+} |
+ |
+TEST_F(WorkerThreadTest, LongGcDeadline_NoFutureTasks) |
+{ |
+ ThreadCondition gcDone; |
+ Mutex mutex; |
+ double deadlineLength = 0; |
+ |
+ ON_CALL(*m_workerThread, doIdleGc(_)).WillByDefault(Invoke( |
+ [&gcDone, &mutex, &deadlineLength](double deadline) |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ gcDone.signal(); |
+ deadlineLength = deadline -Platform::current()->monotonicallyIncreasingTime(); |
+ return false; |
+ })); |
+ |
+ EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(1); |
+ EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
+ |
+ StartAndWaitForInit(); |
+ PostWakeUpTask(310ul); |
+ |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ gcDone.wait(mutex); |
+ |
+ // The deadline should be close to 1s in duration if there are no tasks that need to run soon. |
+ EXPECT_GT(deadlineLength, 0.9); |
+ } |
+ |
+ m_workerThread->terminateAndWait(); |
+} |
+ |
+TEST_F(WorkerThreadTest, LongGcDeadline_NextTaskAfterIdlePeriod) |
+{ |
+ ThreadCondition gcDone; |
+ Mutex mutex; |
+ double deadlineLength = 0; |
+ |
+ ON_CALL(*m_workerThread, doIdleGc(_)).WillByDefault(Invoke( |
+ [&gcDone, &mutex, &deadlineLength](double deadline) |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ gcDone.signal(); |
+ deadlineLength = deadline -Platform::current()->monotonicallyIncreasingTime(); |
+ return false; |
+ })); |
+ |
+ EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(1); |
+ EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
+ |
+ StartAndWaitForInit(); |
+ PostWakeUpTask(310ul); |
+ PostWakeUpTask(675ul); // Task that runs shortly after the 50ms idle period ends. |
+ |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ gcDone.wait(mutex); |
+ |
+ // The worker thread calls canExceedIdleDeadlineIfRequired which only considers if |
+ // there are any delayed tasks scheduled for the current long idle period. Since the |
+ // next task is in the following idle period, a long gc deadline is allowed. |
+ EXPECT_GT(deadlineLength, 0.9); |
+ } |
+ |
+ m_workerThread->terminateAndWait(); |
+} |
+ |
+TEST_F(WorkerThreadTest, ShortGcDeadline) |
+{ |
+ ThreadCondition gcDone; |
+ Mutex mutex; |
+ double deadlineLength = 0; |
+ |
+ ON_CALL(*m_workerThread, doIdleGc(_)).WillByDefault(Invoke( |
+ [&gcDone, &mutex, &deadlineLength](double deadline) |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ gcDone.signal(); |
+ deadlineLength = deadline -Platform::current()->monotonicallyIncreasingTime(); |
+ return false; |
+ })); |
+ |
+ EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(1); |
+ EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(false)); |
+ |
+ StartAndWaitForInit(); |
+ PostWakeUpTask(310ul); |
+ PostWakeUpTask(625ul); // Task that runs during the idle period. |
+ |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ gcDone.wait(mutex); |
+ |
+ // The deadline should be < 50ms if there's a task that needs to run during the idle period. |
+ EXPECT_LT(deadlineLength, 0.025); |
+ } |
+ |
+ m_workerThread->terminateAndWait(); |
+} |
+ |
+class PostDelayedSignalTask : public WebThread::Task { |
+public: |
+ PostDelayedSignalTask(WebScheduler* scheduler, ThreadCondition* completion, Mutex* mutex, long long delay) |
+ : m_scheduler(scheduler) |
+ , m_completion(completion) |
+ , m_mutex(mutex) |
+ , m_delay(delay) { } |
+ |
+ ~PostDelayedSignalTask() override { } |
+ |
+ void run() override |
+ { |
+ m_scheduler->postTimerTask(FROM_HERE, new SignalTask(m_completion, m_mutex), m_delay); |
+ } |
+ |
+ WebScheduler* m_scheduler; // NOT OWNED |
+ ThreadCondition* m_completion; // NOT OWNED |
+ Mutex* m_mutex; // NOT OWNED |
+ long long m_delay; |
+}; |
+ |
+TEST_F(WorkerThreadTest, TerminationPreventsGc) |
+{ |
+ ThreadCondition completion; |
+ Mutex mutex; |
+ |
+ EXPECT_CALL(*m_workerThread, doIdleGc(_)).Times(0); |
+ EXPECT_CALL(*m_workerThread, terminated()).WillRepeatedly(Return(true)); |
+ |
+ StartAndWaitForInit(); |
+ PostWakeUpTask(310ul); // 10ms after the quiescent period ends. |
+ |
+ // Give pleanty of time for the GC to run before exiting. |
+ WebScheduler* scheduler = m_workerThread->schedulerForTesting(); |
+ scheduler->postLoadingTask(FROM_HERE, new PostDelayedSignalTask(scheduler, &completion, &mutex, 700ul)); |
+ |
+ { |
+ WTF::Locker<Mutex> lock(mutex); |
+ completion.wait(mutex); |
+ } |
+ |
+ m_workerThread->terminateAndWait(); |
+}; |
+ |
+} // namespace blink |