blob: 1d650348d1e4d3953d4cd13ef28d6cdbdd88bc3b [file] [log] [blame]
// 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 "core/workers/WorkerThread.h"
#include "core/workers/WorkerThreadTestHelper.h"
#include "platform/testing/UnitTestHelpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "wtf/PtrUtil.h"
#include <memory>
using testing::_;
using testing::AtMost;
namespace blink {
namespace {
// Called from WorkerThread::startRunningDebuggerTasksOnPauseOnWorkerThread as a
// debugger task.
void waitForTermination(WorkerThread* workerThread)
{
EXPECT_TRUE(workerThread->isCurrentThread());
// Notify the main thread that the debugger task is waiting for termination.
Platform::current()->mainThread()->getWebTaskRunner()->postTask(BLINK_FROM_HERE, threadSafeBind(&testing::exitRunLoop));
workerThread->terminationEvent()->wait();
}
} // namespace
class WorkerThreadTest : public ::testing::Test {
public:
void SetUp() override
{
m_mockWorkerLoaderProxyProvider = wrapUnique(new MockWorkerLoaderProxyProvider());
m_mockWorkerReportingProxy = wrapUnique(new MockWorkerReportingProxy());
m_securityOrigin = SecurityOrigin::create(KURL(ParsedURLString, "http://fake.url/"));
m_workerThread = wrapUnique(new WorkerThreadForTest(
m_mockWorkerLoaderProxyProvider.get(),
*m_mockWorkerReportingProxy));
m_mockWorkerThreadLifecycleObserver = new MockWorkerThreadLifecycleObserver(m_workerThread->getWorkerThreadLifecycleContext());
}
void TearDown() override
{
m_workerThread->workerLoaderProxy()->detachProvider(m_mockWorkerLoaderProxyProvider.get());
}
void start()
{
m_workerThread->startWithSourceCode(m_securityOrigin.get(), "//fake source code");
}
void startWithSourceCodeNotToFinish()
{
// Use a JavaScript source code that makes an infinite loop so that we
// can catch some kind of issues as a timeout.
m_workerThread->startWithSourceCode(m_securityOrigin.get(), "while(true) {}");
}
void setForceTerminationDelayInMs(long long forceTerminationDelayInMs)
{
m_workerThread->m_forceTerminationDelayInMs = forceTerminationDelayInMs;
}
bool isForceTerminationTaskScheduled()
{
return m_workerThread->m_scheduledForceTerminationTask.get();
}
protected:
void expectReportingCalls()
{
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);
EXPECT_CALL(*m_mockWorkerThreadLifecycleObserver, contextDestroyed()).Times(1);
}
void expectReportingCallsForWorkerPossiblyTerminatedBeforeInitialization()
{
EXPECT_CALL(*m_mockWorkerReportingProxy, workerGlobalScopeStarted(_)).Times(AtMost(1));
EXPECT_CALL(*m_mockWorkerReportingProxy, didEvaluateWorkerScript(_)).Times(AtMost(1));
EXPECT_CALL(*m_mockWorkerReportingProxy, workerThreadTerminated()).Times(1);
EXPECT_CALL(*m_mockWorkerReportingProxy, willDestroyWorkerGlobalScope()).Times(AtMost(1));
EXPECT_CALL(*m_mockWorkerThreadLifecycleObserver, contextDestroyed()).Times(1);
}
void expectReportingCallsForWorkerForciblyTerminated()
{
EXPECT_CALL(*m_mockWorkerReportingProxy, workerGlobalScopeStarted(_)).Times(1);
EXPECT_CALL(*m_mockWorkerReportingProxy, didEvaluateWorkerScript(false)).Times(1);
EXPECT_CALL(*m_mockWorkerReportingProxy, workerThreadTerminated()).Times(1);
EXPECT_CALL(*m_mockWorkerReportingProxy, willDestroyWorkerGlobalScope()).Times(1);
EXPECT_CALL(*m_mockWorkerThreadLifecycleObserver, contextDestroyed()).Times(1);
}
RefPtr<SecurityOrigin> m_securityOrigin;
std::unique_ptr<MockWorkerLoaderProxyProvider> m_mockWorkerLoaderProxyProvider;
std::unique_ptr<MockWorkerReportingProxy> m_mockWorkerReportingProxy;
std::unique_ptr<WorkerThreadForTest> m_workerThread;
Persistent<MockWorkerThreadLifecycleObserver> m_mockWorkerThreadLifecycleObserver;
};
TEST_F(WorkerThreadTest, StartAndTerminate_AsyncTerminate)
{
expectReportingCalls();
start();
m_workerThread->waitForInit();
// The worker thread is not being blocked, so the worker thread should be
// gracefully shut down.
m_workerThread->terminate();
EXPECT_TRUE(isForceTerminationTaskScheduled());
m_workerThread->waitForShutdownForTesting();
EXPECT_EQ(WorkerThread::ExitCode::GracefullyTerminated, m_workerThread->getExitCode());
}
TEST_F(WorkerThreadTest, StartAndTerminate_SyncTerminate)
{
expectReportingCalls();
start();
m_workerThread->waitForInit();
m_workerThread->terminateAndWait();
EXPECT_EQ(WorkerThread::ExitCode::SyncForciblyTerminated, m_workerThread->getExitCode());
}
TEST_F(WorkerThreadTest, StartAndTerminateImmediately_AsyncTerminate)
{
expectReportingCallsForWorkerPossiblyTerminatedBeforeInitialization();
start();
// There are two possible cases depending on timing:
// (1) If the thread hasn't been initialized on the worker thread yet,
// terminate() should not attempt to shut down the thread.
// (2) If the thread has already been initialized on the worker thread,
// terminate() should gracefully shut down the thread.
m_workerThread->terminate();
m_workerThread->waitForShutdownForTesting();
WorkerThread::ExitCode exitCode = m_workerThread->getExitCode();
EXPECT_EQ(WorkerThread::ExitCode::GracefullyTerminated, exitCode);
}
TEST_F(WorkerThreadTest, StartAndTerminateImmediately_SyncTerminate)
{
expectReportingCallsForWorkerPossiblyTerminatedBeforeInitialization();
start();
// There are two possible cases depending on timing:
// (1) If the thread hasn't been initialized on the worker thread yet,
// terminateAndWait() should not attempt to shut down the thread.
// (2) If the thread has already been initialized on the worker thread,
// terminateAndWait() should synchronously forcibly terminates the worker
// execution.
m_workerThread->terminateAndWait();
WorkerThread::ExitCode exitCode = m_workerThread->getExitCode();
EXPECT_TRUE(WorkerThread::ExitCode::GracefullyTerminated == exitCode || WorkerThread::ExitCode::SyncForciblyTerminated == exitCode);
}
TEST_F(WorkerThreadTest, StartAndTerminateOnInitialization_TerminateWhileDebuggerTaskIsRunning)
{
EXPECT_CALL(*m_mockWorkerReportingProxy, workerGlobalScopeStarted(_)).Times(1);
EXPECT_CALL(*m_mockWorkerReportingProxy, workerThreadTerminated()).Times(1);
EXPECT_CALL(*m_mockWorkerReportingProxy, willDestroyWorkerGlobalScope()).Times(1);
EXPECT_CALL(*m_mockWorkerThreadLifecycleObserver, contextDestroyed()).Times(1);
std::unique_ptr<Vector<CSPHeaderAndType>> headers = wrapUnique(new Vector<CSPHeaderAndType>());
CSPHeaderAndType headerAndType("contentSecurityPolicy", ContentSecurityPolicyHeaderTypeReport);
headers->append(headerAndType);
// Specify PauseWorkerGlobalScopeOnStart so that the worker thread can pause
// on initialziation to run debugger tasks.
std::unique_ptr<WorkerThreadStartupData> startupData =
WorkerThreadStartupData::create(
KURL(ParsedURLString, "http://fake.url/"),
"fake user agent",
"//fake source code",
nullptr, /* cachedMetaData */
PauseWorkerGlobalScopeOnStart,
headers.get(),
m_securityOrigin.get(),
nullptr, /* workerClients */
WebAddressSpaceLocal,
nullptr /* originTrialToken */,
V8CacheOptionsDefault);
m_workerThread->start(std::move(startupData));
m_workerThread->appendDebuggerTask(threadSafeBind(&waitForTermination, AllowCrossThreadAccess(m_workerThread.get())));
// Wait for the debugger task.
testing::enterRunLoop();
// Start termination while the debugger task is running.
EXPECT_TRUE(m_workerThread->m_runningDebuggerTask);
m_workerThread->terminateAndWait();
EXPECT_EQ(WorkerThread::ExitCode::GracefullyTerminated, m_workerThread->getExitCode());
}
TEST_F(WorkerThreadTest, StartAndTerminateOnScriptLoaded_SyncForciblyTerminate)
{
expectReportingCallsForWorkerForciblyTerminated();
startWithSourceCodeNotToFinish();
m_workerThread->waitUntilScriptLoaded();
// terminateAndWait() synchronously terminates the worker execution.
m_workerThread->terminateAndWait();
EXPECT_EQ(WorkerThread::ExitCode::SyncForciblyTerminated, m_workerThread->getExitCode());
}
TEST_F(WorkerThreadTest, StartAndTerminateOnScriptLoaded_AsyncForciblyTerminate)
{
const long long kForceTerminationDelayInMs = 10;
setForceTerminationDelayInMs(kForceTerminationDelayInMs);
expectReportingCallsForWorkerForciblyTerminated();
startWithSourceCodeNotToFinish();
m_workerThread->waitUntilScriptLoaded();
// terminate() schedules a force termination task.
m_workerThread->terminate();
EXPECT_TRUE(isForceTerminationTaskScheduled());
EXPECT_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->getExitCode());
// Wait until the force termination task runs.
testing::runDelayedTasks(kForceTerminationDelayInMs);
m_workerThread->waitForShutdownForTesting();
EXPECT_EQ(WorkerThread::ExitCode::AsyncForciblyTerminated, m_workerThread->getExitCode());
}
TEST_F(WorkerThreadTest, StartAndTerminateOnScriptLoaded_AsyncForciblyTerminate_MultipleTimes)
{
const long long kForceTerminationDelayInMs = 10;
setForceTerminationDelayInMs(kForceTerminationDelayInMs);
expectReportingCallsForWorkerForciblyTerminated();
startWithSourceCodeNotToFinish();
m_workerThread->waitUntilScriptLoaded();
// terminate() schedules a force termination task.
m_workerThread->terminate();
EXPECT_TRUE(isForceTerminationTaskScheduled());
EXPECT_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->getExitCode());
// Multiple terminate() calls should not take effect.
m_workerThread->terminate();
m_workerThread->terminate();
EXPECT_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->getExitCode());
// Wait until the force termination task runs.
testing::runDelayedTasks(kForceTerminationDelayInMs);
m_workerThread->waitForShutdownForTesting();
EXPECT_EQ(WorkerThread::ExitCode::AsyncForciblyTerminated, m_workerThread->getExitCode());
}
TEST_F(WorkerThreadTest, StartAndTerminateOnScriptLoaded_SyncForciblyTerminateAfterTerminationTaskIsScheduled)
{
const long long kForceTerminationDelayInMs = 10;
setForceTerminationDelayInMs(kForceTerminationDelayInMs);
expectReportingCallsForWorkerForciblyTerminated();
startWithSourceCodeNotToFinish();
m_workerThread->waitUntilScriptLoaded();
// terminate() schedules a force termination task.
m_workerThread->terminate();
EXPECT_TRUE(isForceTerminationTaskScheduled());
EXPECT_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->getExitCode());
// terminateAndWait() should overtake the scheduled force termination task.
m_workerThread->terminateAndWait();
EXPECT_FALSE(isForceTerminationTaskScheduled());
EXPECT_EQ(WorkerThread::ExitCode::SyncForciblyTerminated, m_workerThread->getExitCode());
}
TEST_F(WorkerThreadTest, StartAndTerminateOnScriptLoaded_TerminateWhileDebuggerTaskIsRunning)
{
expectReportingCallsForWorkerForciblyTerminated();
startWithSourceCodeNotToFinish();
m_workerThread->waitUntilScriptLoaded();
// Simulate that a debugger task is running.
m_workerThread->m_runningDebuggerTask = true;
// terminate() should not schedule a force termination task because there is
// a running debugger task.
m_workerThread->terminate();
EXPECT_FALSE(isForceTerminationTaskScheduled());
EXPECT_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->getExitCode());
// Multiple terminate() calls should not take effect.
m_workerThread->terminate();
m_workerThread->terminate();
EXPECT_FALSE(isForceTerminationTaskScheduled());
EXPECT_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->getExitCode());
// Focible termination request should also respect the running debugger
// task.
m_workerThread->terminateInternal(WorkerThread::TerminationMode::Forcible);
EXPECT_FALSE(isForceTerminationTaskScheduled());
EXPECT_EQ(WorkerThread::ExitCode::NotTerminated, m_workerThread->getExitCode());
// Clean up in order to satisfy DCHECKs in dtors.
m_workerThread->m_runningDebuggerTask = false;
m_workerThread->forciblyTerminateExecution();
m_workerThread->waitForShutdownForTesting();
}
// TODO(nhiroki): Add tests for terminateAndWaitForAllWorkers.
} // namespace blink