Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / content / browser / service_worker / service_worker_job_unittest.cc
index efa8bd1..0a074c5 100644 (file)
@@ -9,18 +9,30 @@
 #include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
+#include "content/browser/service_worker/service_worker_disk_cache.h"
 #include "content/browser/service_worker/service_worker_job_coordinator.h"
 #include "content/browser/service_worker/service_worker_registration.h"
 #include "content/browser/service_worker/service_worker_registration_status.h"
+#include "content/browser/service_worker/service_worker_test_utils.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "ipc/ipc_test_sink.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_response_headers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using net::IOBuffer;
+using net::TestCompletionCallback;
+using net::WrappedIOBuffer;
+
 // Unit tests for testing all job registration tasks.
 namespace content {
 
 namespace {
 
+int kMockRenderProcessId = 88;
+
 void SaveRegistrationCallback(
     ServiceWorkerStatusCode expected_status,
     bool* called,
@@ -91,7 +103,7 @@ class ServiceWorkerJobTest : public testing::Test {
  public:
   ServiceWorkerJobTest()
       : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
-        render_process_id_(88) {}
+        render_process_id_(kMockRenderProcessId) {}
 
   virtual void SetUp() OVERRIDE {
     helper_.reset(new EmbeddedWorkerTestHelper(render_process_id_));
@@ -111,7 +123,6 @@ class ServiceWorkerJobTest : public testing::Test {
  protected:
   TestBrowserThreadBundle browser_thread_bundle_;
   scoped_ptr<EmbeddedWorkerTestHelper> helper_;
-
   int render_process_id_;
 };
 
@@ -119,7 +130,7 @@ TEST_F(ServiceWorkerJobTest, SameDocumentSameRegistration) {
   scoped_refptr<ServiceWorkerRegistration> original_registration;
   bool called;
   job_coordinator()->Register(
-      GURL("http://www.example.com/*"),
+      GURL("http://www.example.com/"),
       GURL("http://www.example.com/service_worker.js"),
       render_process_id_,
       SaveRegistration(SERVICE_WORKER_OK, &called, &original_registration));
@@ -146,7 +157,7 @@ TEST_F(ServiceWorkerJobTest, SameMatchSameRegistration) {
   bool called;
   scoped_refptr<ServiceWorkerRegistration> original_registration;
   job_coordinator()->Register(
-      GURL("http://www.example.com/*"),
+      GURL("http://www.example.com/"),
       GURL("http://www.example.com/service_worker.js"),
       render_process_id_,
       SaveRegistration(SERVICE_WORKER_OK, &called, &original_registration));
@@ -177,7 +188,7 @@ TEST_F(ServiceWorkerJobTest, DifferentMatchDifferentRegistration) {
   bool called1;
   scoped_refptr<ServiceWorkerRegistration> original_registration1;
   job_coordinator()->Register(
-      GURL("http://www.example.com/one/*"),
+      GURL("http://www.example.com/one/"),
       GURL("http://www.example.com/service_worker.js"),
       render_process_id_,
       SaveRegistration(SERVICE_WORKER_OK, &called1, &original_registration1));
@@ -185,7 +196,7 @@ TEST_F(ServiceWorkerJobTest, DifferentMatchDifferentRegistration) {
   bool called2;
   scoped_refptr<ServiceWorkerRegistration> original_registration2;
   job_coordinator()->Register(
-      GURL("http://www.example.com/two/*"),
+      GURL("http://www.example.com/two/"),
       GURL("http://www.example.com/service_worker.js"),
       render_process_id_,
       SaveRegistration(SERVICE_WORKER_OK, &called2, &original_registration2));
@@ -216,7 +227,7 @@ TEST_F(ServiceWorkerJobTest, Register) {
   bool called = false;
   scoped_refptr<ServiceWorkerRegistration> registration;
   job_coordinator()->Register(
-      GURL("http://www.example.com/*"),
+      GURL("http://www.example.com/"),
       GURL("http://www.example.com/service_worker.js"),
       render_process_id_,
       SaveRegistration(SERVICE_WORKER_OK, &called, &registration));
@@ -230,7 +241,7 @@ TEST_F(ServiceWorkerJobTest, Register) {
 
 // Make sure registrations are cleaned up when they are unregistered.
 TEST_F(ServiceWorkerJobTest, Unregister) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
 
   bool called;
   scoped_refptr<ServiceWorkerRegistration> registration;
@@ -266,7 +277,7 @@ TEST_F(ServiceWorkerJobTest, Unregister) {
 }
 
 TEST_F(ServiceWorkerJobTest, Unregister_NothingRegistered) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
 
   bool called;
   job_coordinator()->Unregister(pattern,
@@ -280,7 +291,7 @@ TEST_F(ServiceWorkerJobTest, Unregister_NothingRegistered) {
 // Make sure that when a new registration replaces an existing
 // registration, that the old one is cleaned up.
 TEST_F(ServiceWorkerJobTest, RegisterNewScript) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
 
   bool called;
   scoped_refptr<ServiceWorkerRegistration> old_registration;
@@ -338,7 +349,7 @@ TEST_F(ServiceWorkerJobTest, RegisterNewScript) {
 // Make sure that when registering a duplicate pattern+script_url
 // combination, that the same registration is used.
 TEST_F(ServiceWorkerJobTest, RegisterDuplicateScript) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
   GURL script_url("http://www.example.com/service_worker.js");
 
   bool called;
@@ -394,14 +405,14 @@ TEST_F(ServiceWorkerJobTest, RegisterDuplicateScript) {
 
 class FailToStartWorkerTestHelper : public EmbeddedWorkerTestHelper {
  public:
-  FailToStartWorkerTestHelper(int mock_render_process_id)
+  explicit FailToStartWorkerTestHelper(int mock_render_process_id)
       : EmbeddedWorkerTestHelper(mock_render_process_id) {}
 
   virtual void OnStartWorker(int embedded_worker_id,
                              int64 service_worker_version_id,
                              const GURL& scope,
-                             const GURL& script_url) OVERRIDE {
-    // Simulate failure by sending worker stopped instead of started.
+                             const GURL& script_url,
+                             bool pause_after_download) OVERRIDE {
     EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
     registry()->OnWorkerStopped(worker->process_id(), embedded_worker_id);
   }
@@ -413,7 +424,7 @@ TEST_F(ServiceWorkerJobTest, Register_FailToStartWorker) {
   bool called = false;
   scoped_refptr<ServiceWorkerRegistration> registration;
   job_coordinator()->Register(
-      GURL("http://www.example.com/*"),
+      GURL("http://www.example.com/"),
       GURL("http://www.example.com/service_worker.js"),
       render_process_id_,
       SaveRegistration(
@@ -429,7 +440,7 @@ TEST_F(ServiceWorkerJobTest, Register_FailToStartWorker) {
 // Register and then unregister the pattern, in parallel. Job coordinator should
 // process jobs until the last job.
 TEST_F(ServiceWorkerJobTest, ParallelRegUnreg) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
   GURL script_url("http://www.example.com/service_worker.js");
 
   bool registration_called = false;
@@ -466,7 +477,7 @@ TEST_F(ServiceWorkerJobTest, ParallelRegUnreg) {
 // registration should win, and the old registration should have been
 // shutdown.
 TEST_F(ServiceWorkerJobTest, ParallelRegNewScript) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
 
   GURL script_url1("http://www.example.com/service_worker1.js");
   bool registration1_called = false;
@@ -510,7 +521,7 @@ TEST_F(ServiceWorkerJobTest, ParallelRegNewScript) {
 // coalesced such that both callers get the exact same registration
 // object.
 TEST_F(ServiceWorkerJobTest, ParallelRegSameScript) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
 
   GURL script_url("http://www.example.com/service_worker1.js");
   bool registration1_called = false;
@@ -552,7 +563,7 @@ TEST_F(ServiceWorkerJobTest, ParallelRegSameScript) {
 
 // Call simulataneous unregister calls.
 TEST_F(ServiceWorkerJobTest, ParallelUnreg) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
 
   GURL script_url("http://www.example.com/service_worker.js");
   bool unregistration1_called = false;
@@ -585,8 +596,8 @@ TEST_F(ServiceWorkerJobTest, ParallelUnreg) {
 }
 
 TEST_F(ServiceWorkerJobTest, AbortAll_Register) {
-  GURL pattern1("http://www1.example.com/*");
-  GURL pattern2("http://www2.example.com/*");
+  GURL pattern1("http://www1.example.com/");
+  GURL pattern2("http://www2.example.com/");
   GURL script_url1("http://www1.example.com/service_worker.js");
   GURL script_url2("http://www2.example.com/service_worker.js");
 
@@ -636,8 +647,8 @@ TEST_F(ServiceWorkerJobTest, AbortAll_Register) {
 }
 
 TEST_F(ServiceWorkerJobTest, AbortAll_Unregister) {
-  GURL pattern1("http://www1.example.com/*");
-  GURL pattern2("http://www2.example.com/*");
+  GURL pattern1("http://www1.example.com/");
+  GURL pattern2("http://www2.example.com/");
 
   bool unregistration_called1 = false;
   scoped_refptr<ServiceWorkerRegistration> registration1;
@@ -662,7 +673,7 @@ TEST_F(ServiceWorkerJobTest, AbortAll_Unregister) {
 }
 
 TEST_F(ServiceWorkerJobTest, AbortAll_RegUnreg) {
-  GURL pattern("http://www.example.com/*");
+  GURL pattern("http://www.example.com/");
   GURL script_url("http://www.example.com/service_worker.js");
 
   bool registration_called = false;
@@ -699,4 +710,401 @@ TEST_F(ServiceWorkerJobTest, AbortAll_RegUnreg) {
   EXPECT_EQ(scoped_refptr<ServiceWorkerRegistration>(), registration);
 }
 
+// Tests that the waiting worker enters the 'redundant' state upon
+// unregistration.
+TEST_F(ServiceWorkerJobTest, UnregisterWaitingSetsRedundant) {
+  scoped_refptr<ServiceWorkerRegistration> registration;
+  bool called = false;
+  job_coordinator()->Register(
+      GURL("http://www.example.com/"),
+      GURL("http://www.example.com/service_worker.js"),
+      render_process_id_,
+      SaveRegistration(SERVICE_WORKER_OK, &called, &registration));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(called);
+  ASSERT_TRUE(registration);
+
+  // Manually create the waiting worker since there is no way to become a
+  // waiting worker until Update is implemented.
+  scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
+      registration, 1L, helper_->context()->AsWeakPtr());
+  ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
+  version->StartWorker(CreateReceiverOnCurrentThread(&status));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(SERVICE_WORKER_OK, status);
+
+  version->SetStatus(ServiceWorkerVersion::INSTALLED);
+  registration->SetWaitingVersion(version);
+  EXPECT_EQ(ServiceWorkerVersion::RUNNING,
+            version->running_status());
+  EXPECT_EQ(ServiceWorkerVersion::INSTALLED, version->status());
+
+  called = false;
+  job_coordinator()->Unregister(GURL("http://www.example.com/"),
+                                SaveUnregistration(SERVICE_WORKER_OK, &called));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(called);
+
+  // The version should be stopped since there is no controllee after
+  // unregistration.
+  EXPECT_EQ(ServiceWorkerVersion::STOPPED, version->running_status());
+  EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, version->status());
+}
+
+// Tests that the active worker enters the 'redundant' state upon
+// unregistration.
+TEST_F(ServiceWorkerJobTest, UnregisterActiveSetsRedundant) {
+  scoped_refptr<ServiceWorkerRegistration> registration;
+  bool called = false;
+  job_coordinator()->Register(
+      GURL("http://www.example.com/"),
+      GURL("http://www.example.com/service_worker.js"),
+      render_process_id_,
+      SaveRegistration(SERVICE_WORKER_OK, &called, &registration));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(called);
+  ASSERT_TRUE(registration);
+
+  scoped_refptr<ServiceWorkerVersion> version = registration->active_version();
+  EXPECT_EQ(ServiceWorkerVersion::RUNNING, version->running_status());
+  EXPECT_EQ(ServiceWorkerVersion::ACTIVATED, version->status());
+
+  called = false;
+  job_coordinator()->Unregister(GURL("http://www.example.com/"),
+                                SaveUnregistration(SERVICE_WORKER_OK, &called));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(called);
+
+  // The version should be stopped since there is no controllee after
+  // unregistration.
+  EXPECT_EQ(ServiceWorkerVersion::STOPPED, version->running_status());
+  EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, version->status());
+}
+
+// Tests that the active worker enters the 'redundant' state upon
+// unregistration.
+TEST_F(ServiceWorkerJobTest,
+       UnregisterActiveSetsRedundant_WaitForNoControllee) {
+  scoped_refptr<ServiceWorkerRegistration> registration;
+  bool called = false;
+  job_coordinator()->Register(
+      GURL("http://www.example.com/"),
+      GURL("http://www.example.com/service_worker.js"),
+      render_process_id_,
+      SaveRegistration(SERVICE_WORKER_OK, &called, &registration));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(called);
+  ASSERT_TRUE(registration);
+
+  scoped_ptr<ServiceWorkerProviderHost> host(
+      new ServiceWorkerProviderHost(33 /* dummy render process id */,
+                                    1 /* dummy provider_id */,
+                                    context()->AsWeakPtr(),
+                                    NULL));
+  registration->active_version()->AddControllee(host.get());
+
+  scoped_refptr<ServiceWorkerVersion> version = registration->active_version();
+  EXPECT_EQ(ServiceWorkerVersion::RUNNING, version->running_status());
+  EXPECT_EQ(ServiceWorkerVersion::ACTIVATED, version->status());
+
+  called = false;
+  job_coordinator()->Unregister(GURL("http://www.example.com/"),
+                                SaveUnregistration(SERVICE_WORKER_OK, &called));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(called);
+
+  // The version should be running since there is still a controllee.
+  EXPECT_EQ(ServiceWorkerVersion::RUNNING, version->running_status());
+  EXPECT_EQ(ServiceWorkerVersion::ACTIVATED, version->status());
+
+  registration->active_version()->RemoveControllee(host.get());
+  base::RunLoop().RunUntilIdle();
+
+  // The version should be stopped since there is no controllee.
+  EXPECT_EQ(ServiceWorkerVersion::STOPPED, version->running_status());
+  EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, version->status());
+}
+
+namespace {  // Helpers for the update job tests.
+
+const GURL kNoChangeOrigin("http://nochange/");
+const GURL kNewVersionOrigin("http://newversion/");
+const std::string kScope("scope/");
+const std::string kScript("script.js");
+
+void RunNestedUntilIdle() {
+  base::MessageLoop::ScopedNestableTaskAllower allow(
+      base::MessageLoop::current());
+  base::MessageLoop::current()->RunUntilIdle();
+}
+
+void OnIOComplete(int* rv_out, int rv) {
+  *rv_out = rv;
+}
+
+void WriteResponse(
+    ServiceWorkerStorage* storage, int64 id,
+    const std::string& headers,
+    IOBuffer* body, int length) {
+  scoped_ptr<ServiceWorkerResponseWriter> writer =
+      storage->CreateResponseWriter(id);
+
+  scoped_ptr<net::HttpResponseInfo> info(new net::HttpResponseInfo);
+  info->request_time = base::Time::Now();
+  info->response_time = base::Time::Now();
+  info->was_cached = false;
+  info->headers = new net::HttpResponseHeaders(headers);
+  scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
+      new HttpResponseInfoIOBuffer(info.release());
+
+  int rv = -1234;
+  writer->WriteInfo(info_buffer, base::Bind(&OnIOComplete, &rv));
+  RunNestedUntilIdle();
+  EXPECT_LT(0, rv);
+
+  rv = -1234;
+  writer->WriteData(body, length,
+                    base::Bind(&OnIOComplete, &rv));
+  RunNestedUntilIdle();
+  EXPECT_EQ(length, rv);
+}
+
+void WriteStringResponse(
+    ServiceWorkerStorage* storage, int64 id,
+    const std::string& body) {
+  scoped_refptr<IOBuffer> body_buffer(new WrappedIOBuffer(body.data()));
+  const char kHttpHeaders[] = "HTTP/1.0 200 HONKYDORY\0\0";
+  std::string headers(kHttpHeaders, arraysize(kHttpHeaders));
+  WriteResponse(storage, id, headers, body_buffer, body.length());
+}
+
+class UpdateJobTestHelper
+    : public EmbeddedWorkerTestHelper,
+      public ServiceWorkerRegistration::Listener,
+      public ServiceWorkerVersion::Listener {
+ public:
+  struct AttributeChangeLogEntry {
+    int64 registration_id;
+    ChangedVersionAttributesMask mask;
+    ServiceWorkerRegistrationInfo info;
+  };
+
+  struct StateChangeLogEntry {
+    int64 version_id;
+    ServiceWorkerVersion::Status status;
+  };
+
+  UpdateJobTestHelper(int mock_render_process_id)
+      : EmbeddedWorkerTestHelper(mock_render_process_id) {}
+  virtual ~UpdateJobTestHelper() {
+    if (registration_)
+      registration_->RemoveListener(this);
+  }
+
+  ServiceWorkerStorage* storage() { return context()->storage(); }
+  ServiceWorkerJobCoordinator* job_coordinator() {
+    return context()->job_coordinator();
+  }
+
+  scoped_refptr<ServiceWorkerRegistration> SetupInitialRegistration(
+      const GURL& test_origin) {
+    scoped_refptr<ServiceWorkerRegistration> registration;
+    bool called = false;
+    job_coordinator()->Register(
+        test_origin.Resolve(kScope),
+        test_origin.Resolve(kScript),
+        mock_render_process_id(),
+        SaveRegistration(SERVICE_WORKER_OK, &called, &registration));
+    base::RunLoop().RunUntilIdle();
+    EXPECT_TRUE(called);
+    EXPECT_TRUE(registration);
+    EXPECT_TRUE(registration->active_version());
+    EXPECT_FALSE(registration->installing_version());
+    EXPECT_FALSE(registration->waiting_version());
+    registration_ = registration;
+    return registration;
+  }
+
+  // EmbeddedWorkerTestHelper overrides
+  virtual void OnStartWorker(int embedded_worker_id,
+                             int64 version_id,
+                             const GURL& scope,
+                             const GURL& script,
+                             bool pause_after_download) OVERRIDE {
+    const std::string kMockScriptBody = "mock_script";
+    ServiceWorkerVersion* version = context()->GetLiveVersion(version_id);
+    ASSERT_TRUE(version);
+    version->AddListener(this);
+
+    if (!pause_after_download) {
+      // Spoof caching the script for the initial version.
+      int64 resource_id = storage()->NewResourceId();
+      version->script_cache_map()->NotifyStartedCaching(script, resource_id);
+      WriteStringResponse(storage(), resource_id, kMockScriptBody);
+      version->script_cache_map()->NotifyFinishedCaching(script, true);
+    } else {
+      // Spoof caching the script for the new version.
+      int64 resource_id = storage()->NewResourceId();
+      version->script_cache_map()->NotifyStartedCaching(script, resource_id);
+      if (script.GetOrigin() == kNoChangeOrigin)
+        WriteStringResponse(storage(), resource_id, kMockScriptBody);
+      else
+        WriteStringResponse(storage(), resource_id, "mock_different_script");
+      version->script_cache_map()->NotifyFinishedCaching(script, true);
+    }
+    EmbeddedWorkerTestHelper::OnStartWorker(
+        embedded_worker_id, version_id, scope, script, pause_after_download);
+  }
+
+  // ServiceWorkerRegistration::Listener overrides
+  virtual void OnVersionAttributesChanged(
+      ServiceWorkerRegistration* registration,
+      ChangedVersionAttributesMask changed_mask,
+      const ServiceWorkerRegistrationInfo& info) OVERRIDE {
+    AttributeChangeLogEntry entry;
+    entry.registration_id = registration->id();
+    entry.mask = changed_mask;
+    entry.info = info;
+    attribute_change_log_.push_back(entry);
+  }
+
+  virtual void OnRegistrationFailed(
+      ServiceWorkerRegistration* registration) OVERRIDE {
+    NOTREACHED();
+  }
+
+  // ServiceWorkerVersion::Listener overrides
+  virtual void OnVersionStateChanged(ServiceWorkerVersion* version) OVERRIDE {
+    StateChangeLogEntry entry;
+    entry.version_id = version->version_id();
+    entry.status = version->status();
+    state_change_log_.push_back(entry);
+  }
+
+  scoped_refptr<ServiceWorkerRegistration> registration_;
+
+  std::vector<AttributeChangeLogEntry> attribute_change_log_;
+  std::vector<StateChangeLogEntry> state_change_log_;
+};
+
+}  // namespace
+
+TEST_F(ServiceWorkerJobTest, Update_NoChange) {
+  UpdateJobTestHelper* update_helper =
+      new UpdateJobTestHelper(render_process_id_);
+  helper_.reset(update_helper);
+  scoped_refptr<ServiceWorkerRegistration> registration =
+      update_helper->SetupInitialRegistration(kNoChangeOrigin);
+  ASSERT_TRUE(registration);
+  ASSERT_EQ(4u, update_helper->state_change_log_.size());
+  EXPECT_EQ(ServiceWorkerVersion::INSTALLING,
+            update_helper->state_change_log_[0].status);
+  EXPECT_EQ(ServiceWorkerVersion::INSTALLED,
+            update_helper->state_change_log_[1].status);
+  EXPECT_EQ(ServiceWorkerVersion::ACTIVATING,
+            update_helper->state_change_log_[2].status);
+  EXPECT_EQ(ServiceWorkerVersion::ACTIVATED,
+            update_helper->state_change_log_[3].status);
+  update_helper->state_change_log_.clear();
+
+  // Run the update job.
+  registration->AddListener(update_helper);
+  scoped_refptr<ServiceWorkerVersion> first_version =
+      registration->active_version();
+  first_version->StartUpdate();
+  base::RunLoop().RunUntilIdle();
+
+  // Verify results.
+  ASSERT_TRUE(registration->active_version());
+  EXPECT_EQ(first_version.get(), registration->active_version());
+  EXPECT_FALSE(registration->installing_version());
+  EXPECT_FALSE(registration->waiting_version());
+  EXPECT_TRUE(update_helper->attribute_change_log_.empty());
+  ASSERT_EQ(1u, update_helper->state_change_log_.size());
+  EXPECT_NE(registration->active_version()->version_id(),
+            update_helper->state_change_log_[0].version_id);
+  EXPECT_EQ(ServiceWorkerVersion::REDUNDANT,
+            update_helper->state_change_log_[0].status);
+}
+
+TEST_F(ServiceWorkerJobTest, Update_NewVersion) {
+  UpdateJobTestHelper* update_helper =
+      new UpdateJobTestHelper(render_process_id_);
+  helper_.reset(update_helper);
+  scoped_refptr<ServiceWorkerRegistration> registration =
+      update_helper->SetupInitialRegistration(kNewVersionOrigin);
+  ASSERT_TRUE(registration);
+  update_helper->state_change_log_.clear();
+
+  // Run the update job.
+  registration->AddListener(update_helper);
+  scoped_refptr<ServiceWorkerVersion> first_version =
+      registration->active_version();
+  first_version->StartUpdate();
+  base::RunLoop().RunUntilIdle();
+
+  // Verify results.
+  ASSERT_TRUE(registration->active_version());
+  EXPECT_NE(first_version.get(), registration->active_version());
+  EXPECT_FALSE(registration->installing_version());
+  EXPECT_FALSE(registration->waiting_version());
+  ASSERT_EQ(3u, update_helper->attribute_change_log_.size());
+
+  UpdateJobTestHelper::AttributeChangeLogEntry entry;
+  entry = update_helper->attribute_change_log_[0];
+  EXPECT_TRUE(entry.mask.installing_changed());
+  EXPECT_FALSE(entry.mask.waiting_changed());
+  EXPECT_FALSE(entry.mask.active_changed());
+  EXPECT_FALSE(entry.info.installing_version.is_null);
+  EXPECT_TRUE(entry.info.waiting_version.is_null);
+  EXPECT_FALSE(entry.info.active_version.is_null);
+
+  entry = update_helper->attribute_change_log_[1];
+  EXPECT_TRUE(entry.mask.installing_changed());
+  EXPECT_TRUE(entry.mask.waiting_changed());
+  EXPECT_FALSE(entry.mask.active_changed());
+  EXPECT_TRUE(entry.info.installing_version.is_null);
+  EXPECT_FALSE(entry.info.waiting_version.is_null);
+  EXPECT_FALSE(entry.info.active_version.is_null);
+
+  entry = update_helper->attribute_change_log_[2];
+  EXPECT_FALSE(entry.mask.installing_changed());
+  EXPECT_TRUE(entry.mask.waiting_changed());
+  EXPECT_TRUE(entry.mask.active_changed());
+  EXPECT_TRUE(entry.info.installing_version.is_null);
+  EXPECT_TRUE(entry.info.waiting_version.is_null);
+  EXPECT_FALSE(entry.info.active_version.is_null);
+
+  // expected version state transitions:
+  // new.installing, new.installed,
+  // old.redundant,
+  // new.activating, new.activated
+  ASSERT_EQ(5u, update_helper->state_change_log_.size());
+
+  EXPECT_EQ(registration->active_version()->version_id(),
+            update_helper->state_change_log_[0].version_id);
+  EXPECT_EQ(ServiceWorkerVersion::INSTALLING,
+            update_helper->state_change_log_[0].status);
+
+  EXPECT_EQ(registration->active_version()->version_id(),
+            update_helper->state_change_log_[1].version_id);
+  EXPECT_EQ(ServiceWorkerVersion::INSTALLED,
+            update_helper->state_change_log_[1].status);
+
+  EXPECT_EQ(first_version->version_id(),
+            update_helper->state_change_log_[2].version_id);
+  EXPECT_EQ(ServiceWorkerVersion::REDUNDANT,
+            update_helper->state_change_log_[2].status);
+
+  EXPECT_EQ(registration->active_version()->version_id(),
+            update_helper->state_change_log_[3].version_id);
+  EXPECT_EQ(ServiceWorkerVersion::ACTIVATING,
+            update_helper->state_change_log_[3].status);
+
+  EXPECT_EQ(registration->active_version()->version_id(),
+            update_helper->state_change_log_[4].version_id);
+  EXPECT_EQ(ServiceWorkerVersion::ACTIVATED,
+            update_helper->state_change_log_[4].status);
+}
+
 }  // namespace content