Chromium Code Reviews| 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..361158e0dfe5ad0e738415dc08348ace81cc25c4 |
| --- /dev/null |
| +++ b/Source/core/workers/WorkerThreadTest.cpp |
| @@ -0,0 +1,336 @@ |
| +// 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 "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 { } |
| + |
| + using WorkerThread::threadForTesting; |
| + |
| + MOCK_METHOD1(doIdleGc, bool(double deadlineSeconds)); |
| + |
| + 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(WebThreadSupportingGC* gc, long long delay) : m_gc(gc), m_delay(delay) { } |
| + |
| + ~PostDelayedWakeupTask() override { } |
| + |
| + void run() override |
| + { |
| + m_gc->postDelayedTask(FROM_HERE, new WakeupTask(), m_delay); |
| + } |
| + |
| + WebThreadSupportingGC* m_gc; |
| + long long m_delay; |
| +}; |
| + |
| +} // 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 PostWakeUpTask(long long waitMs) |
| + { |
| + WebThreadSupportingGC* thread = m_workerThread->threadForTesting(); |
| + |
| + // 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. |
| + thread->postTask(FROM_HERE, new PostDelayedWakeupTask(thread, 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); |
| + |
| + m_workerThread->start(); |
| + |
| + 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(WebThreadSupportingGC* thread, ThreadCondition* completion, Mutex* mutex, int taskCount) |
| + : m_thread(thread) |
| + , m_completion(completion) |
| + , m_mutex(mutex) |
| + , m_taskCount(taskCount) |
|
rmcilroy
2015/04/17 16:33:22
Could we make the public constructor not take 'tas
rmcilroy
2015/04/20 20:56:50
Still to do?
|
| + { } |
| + |
| + ~RepeatingTask() override { } |
| + |
| + void run() override |
| + { |
| + m_taskCount++; |
| + if (m_taskCount == 10) { |
| + WTF::Locker<Mutex> lock(*m_mutex); |
| + m_completion->signal(); |
| + } |
| + |
| + m_thread->postDelayedTask( |
| + FROM_HERE, new RepeatingTask(m_thread, m_completion, m_mutex, m_taskCount), 50ul); |
| + m_thread->postTask(FROM_HERE, new WakeupTask()); |
| + |
| + } |
| + |
| +private: |
| + WebThreadSupportingGC* m_thread; // 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); |
| + |
| + m_workerThread->start(); |
| + |
| + WebThreadSupportingGC* thread = m_workerThread->threadForTesting(); |
| + |
| + // Post a repeating task that should prevent any GC from happening. |
| + thread->postTask(FROM_HERE, new RepeatingTask(thread, &completion, &mutex, 0)); |
| + |
| + { |
| + WTF::Locker<Mutex> lock(mutex); |
| + completion.wait(mutex); |
| + } |
| + |
| + // Make sure doIdleGc has not been called by this stage. |
| + Mock::VerifyAndClearExpectations(m_workerThread.get()); |
| + m_workerThread->terminateAndWait(); |
| +} |
| + |
| +TEST_F(WorkerThreadTest, LongGcDeadline) |
| +{ |
| + 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); |
| + |
| + m_workerThread->start(); |
| + |
| + 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, ShortGcDeadline) |
|
rmcilroy
2015/04/17 16:33:22
Could we add one more test which ensures that if t
alex clarke (OOO till 29th)
2015/04/20 10:06:54
Done but I realized this throws up an interesting
rmcilroy
2015/04/20 20:56:50
CanExceedIdleDeadlineIfRequired should only return
|
| +{ |
| + 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); |
| + |
| + m_workerThread->start(); |
| + |
| + PostWakeUpTask(310ul); |
| + PostWakeUpTask(650ul); // Task that run shortly after the idle period starts. |
| + |
| + { |
| + WTF::Locker<Mutex> lock(mutex); |
| + gcDone.wait(mutex); |
| + |
| + // The deadlin should be short if there's a task that needs to run shortly after the idle period starts. |
|
rmcilroy
2015/04/17 16:33:22
/s/deadlin/deadline
alex clarke (OOO till 29th)
2015/04/20 10:06:54
Done.
|
| + EXPECT_LT(deadlineLength, 0.05); |
| + } |
| + |
| + m_workerThread->terminateAndWait(); |
| +} |
| + |
| +} // namespace blink |