| 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
|
|
|