Remove ContentVerifyJob::TestDelegate.

The delegate used to create separate code paths in ContentVerifyJob,
which makes the code harder to read and also creates a parallel
reality in tests. Remove it entirely since tests using the delegate
can work with the TestObserver in ContentVerifyJob.

Remove two tests: ContentVerifierTest:FailOnRead/FailOnDone, that
heavily used the TestDelegate. These tests were *artificially*
(different from how production code does) creating error conditions
from ContentVerifyJob::BytesRead and ContentVerifyJob::DoneReading.

Extract test utilities related to content verification to a
separate file (e/b/content_verifier/test_utils.h)

Bug: 796395
Change-Id: Ib59e9f0c7a4aac9b6a883d7f42647cecc603fa79
Reviewed-on: https://chromium-review.googlesource.com/888327
Commit-Queue: Istiaque Ahmed <[email protected]>
Reviewed-by: Devlin <[email protected]>
Cr-Commit-Position: refs/heads/master@{#533050}
diff --git a/chrome/browser/extensions/extension_protocols_unittest.cc b/chrome/browser/extensions/extension_protocols_unittest.cc
index b247cad..f171bc42 100644
--- a/chrome/browser/extensions/extension_protocols_unittest.cc
+++ b/chrome/browser/extensions/extension_protocols_unittest.cc
@@ -29,11 +29,13 @@
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/content_verifier.h"
+#include "extensions/browser/content_verifier/test_utils.h"
 #include "extensions/browser/extension_protocols.h"
 #include "extensions/browser/info_map.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_paths.h"
 #include "extensions/common/file_util.h"
 #include "extensions/test/test_extension_dir.h"
 #include "net/base/request_priority.h"
@@ -55,14 +57,11 @@
   return path.AppendASCII("extensions").AppendASCII(name);
 }
 
-// Helper function that creates a file at |relative_path| within |directory|
-// and fills it with |content|.
-bool AddFileToDirectory(const base::FilePath& directory,
-                        const base::FilePath& relative_path,
-                        const std::string& content) {
-  base::FilePath full_path = directory.Append(relative_path);
-  int result = base::WriteFile(full_path, content.data(), content.size());
-  return static_cast<size_t>(result) == content.size();
+base::FilePath GetContentVerifierTestPath() {
+  base::FilePath path;
+  EXPECT_TRUE(PathService::Get(extensions::DIR_TEST_DATA, &path));
+  return path.AppendASCII("content_hash_fetcher")
+      .AppendASCII("different_sized_files");
 }
 
 scoped_refptr<Extension> CreateTestExtension(const std::string& name,
@@ -120,60 +119,6 @@
   return extension;
 }
 
-// A ContentVerifyJob::TestDelegate that observes DoneReading().
-class JobDelegate : public ContentVerifyJob::TestDelegate {
- public:
-  explicit JobDelegate(const std::string& expected_contents)
-      : expected_contents_(expected_contents), run_loop_(new base::RunLoop()) {
-    ContentVerifyJob::SetDelegateForTests(this);
-  }
-  ~JobDelegate() override { ContentVerifyJob::SetDelegateForTests(nullptr); }
-
-  ContentVerifyJob::FailureReason BytesRead(const ExtensionId& id,
-                                            int count,
-                                            const char* data) override {
-    read_contents_.append(data, count);
-    return ContentVerifyJob::NONE;
-  }
-
-  ContentVerifyJob::FailureReason DoneReading(const ExtensionId& id) override {
-    seen_done_reading_extension_ids_.insert(id);
-    if (waiting_for_extension_id_ == id)
-      run_loop_->Quit();
-
-    if (!base::StartsWith(expected_contents_, read_contents_,
-                          base::CompareCase::SENSITIVE)) {
-      ADD_FAILURE() << "Unexpected read, expected: " << expected_contents_
-                    << ", but found: " << read_contents_;
-    }
-    return ContentVerifyJob::NONE;
-  }
-
-  void WaitForDoneReading(const ExtensionId& id) {
-    ASSERT_FALSE(waiting_for_extension_id_);
-    if (seen_done_reading_extension_ids_.count(id))
-      return;
-    waiting_for_extension_id_ = id;
-    run_loop_->Run();
-  }
-
-  void Reset() {
-    read_contents_.clear();
-    waiting_for_extension_id_.reset();
-    seen_done_reading_extension_ids_.clear();
-    run_loop_ = std::make_unique<base::RunLoop>();
-  }
-
- private:
-  std::string expected_contents_;
-  std::string read_contents_;
-  std::set<ExtensionId> seen_done_reading_extension_ids_;
-  base::Optional<ExtensionId> waiting_for_extension_id_;
-  std::unique_ptr<base::RunLoop> run_loop_;
-
-  DISALLOW_COPY_AND_ASSIGN(JobDelegate);
-};
-
 }  // namespace
 
 // This test lives in src/chrome instead of src/extensions because it tests
@@ -516,101 +461,121 @@
 // Tests that unreadable files and deleted files correctly go through
 // ContentVerifyJob.
 TEST_F(ExtensionProtocolsTest, VerificationSeenForFileAccessErrors) {
-  const char kFooJsContents[] = "hello world.";
-  JobDelegate test_job_delegate(kFooJsContents);
   SetProtocolHandler(false);
 
-  const std::string kFooJs("foo.js");
-  // Create a temporary directory that a fake extension will live in and fill
-  // it with some test files.
+  // Unzip extension containing verification hashes to a temporary directory.
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-  base::FilePath foo_js(FILE_PATH_LITERAL("foo.js"));
-  ASSERT_TRUE(AddFileToDirectory(temp_dir.GetPath(), foo_js, kFooJsContents))
-      << "Failed to write " << temp_dir.GetPath().value() << "/"
-      << foo_js.value();
-
-  ExtensionBuilder builder;
-  builder
-      .SetManifest(DictionaryBuilder()
-                       .Set("name", "Foo")
-                       .Set("version", "1.0")
-                       .Set("manifest_version", 2)
-                       .Set("update_url",
-                            "https://clients2.google.com/service/update2/crx")
-                       .Build())
-      .SetID(crx_file::id_util::GenerateId("whatever"))
-      .SetPath(temp_dir.GetPath())
-      .SetLocation(Manifest::INTERNAL);
-  scoped_refptr<Extension> extension(builder.Build());
-
+  base::FilePath unzipped_path = temp_dir.GetPath();
+  scoped_refptr<Extension> extension =
+      content_verifier_test_utils::UnzipToDirAndLoadExtension(
+          GetContentVerifierTestPath().AppendASCII("source.zip"),
+          unzipped_path);
   ASSERT_TRUE(extension.get());
-  content_verifier_->OnExtensionLoaded(testing_profile_.get(), extension.get());
-  // Wait for PostTask to ContentVerifierIOData::AddData() to finish.
-  content::RunAllPendingInMessageLoop();
+  ExtensionId extension_id = extension->id();
 
-  // Valid and readable foo.js.
-  EXPECT_EQ(net::OK, DoRequest(*extension, kFooJs));
-  test_job_delegate.WaitForDoneReading(extension->id());
+  const std::string kJs("1024.js");
+  base::FilePath kRelativePath(FILE_PATH_LITERAL("1024.js"));
 
-  // chmod -r foo.js.
-  base::FilePath foo_path = temp_dir.GetPath().AppendASCII(kFooJs);
-  ASSERT_TRUE(base::MakeFileUnreadable(foo_path));
-  test_job_delegate.Reset();
-  EXPECT_EQ(net::ERR_ACCESS_DENIED, DoRequest(*extension, kFooJs));
-  test_job_delegate.WaitForDoneReading(extension->id());
+  // Valid and readable 1024.js.
+  {
+    TestContentVerifyJobObserver observer(extension_id, kRelativePath);
 
-  // Delete foo.js.
-  ASSERT_TRUE(base::DieFileDie(foo_path, false));
-  test_job_delegate.Reset();
-  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, DoRequest(*extension, kFooJs));
-  test_job_delegate.WaitForDoneReading(extension->id());
+    content_verifier_->OnExtensionLoaded(testing_profile_.get(),
+                                         extension.get());
+    // Wait for PostTask to ContentVerifierIOData::AddData() to finish.
+    content::RunAllPendingInMessageLoop();
+
+    EXPECT_EQ(net::OK, DoRequest(*extension, kJs));
+    EXPECT_EQ(ContentVerifyJob::NONE, observer.WaitAndGetFailureReason());
+  }
+
+  // chmod -r 1024.js.
+  {
+    TestContentVerifyJobObserver observer(extension->id(), kRelativePath);
+    base::FilePath file_path = unzipped_path.AppendASCII(kJs);
+    ASSERT_TRUE(base::MakeFileUnreadable(file_path));
+    EXPECT_EQ(net::ERR_ACCESS_DENIED, DoRequest(*extension, kJs));
+    EXPECT_EQ(ContentVerifyJob::HASH_MISMATCH,
+              observer.WaitAndGetFailureReason());
+    // NOTE: In production, hash mismatch would have disabled |extension|, but
+    // since UnzipToDirAndLoadExtension() doesn't add the extension to
+    // ExtensionRegistry, ChromeContentVerifierDelegate won't disable it.
+    // TODO(lazyboy): We may want to update this to more closely reflect the
+    // real flow.
+  }
+
+  // Delete 1024.js.
+  {
+    TestContentVerifyJobObserver observer(extension_id, kRelativePath);
+    base::FilePath file_path = unzipped_path.AppendASCII(kJs);
+    ASSERT_TRUE(base::DieFileDie(file_path, false));
+    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, DoRequest(*extension, kJs));
+    EXPECT_EQ(ContentVerifyJob::HASH_MISMATCH,
+              observer.WaitAndGetFailureReason());
+  }
 }
 
 // Tests that zero byte files correctly go through ContentVerifyJob.
 TEST_F(ExtensionProtocolsTest, VerificationSeenForZeroByteFile) {
-  const char kFooJsContents[] = "";  // Empty.
-  JobDelegate test_job_delegate(kFooJsContents);
   SetProtocolHandler(false);
 
-  const std::string kFooJs("foo.js");
-  // Create a temporary directory that a fake extension will live in and fill
-  // it with some test files.
+  const std::string kEmptyJs("empty.js");
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-  base::FilePath foo_js(FILE_PATH_LITERAL("foo.js"));
-  ASSERT_TRUE(AddFileToDirectory(temp_dir.GetPath(), foo_js, kFooJsContents))
-      << "Failed to write " << temp_dir.GetPath().value() << "/"
-      << foo_js.value();
+  base::FilePath unzipped_path = temp_dir.GetPath();
 
-  // Sanity check foo.js.
-  base::FilePath foo_path = temp_dir.GetPath().AppendASCII(kFooJs);
+  scoped_refptr<Extension> extension =
+      content_verifier_test_utils::UnzipToDirAndLoadExtension(
+          GetContentVerifierTestPath().AppendASCII("source.zip"),
+          unzipped_path);
+  ASSERT_TRUE(extension.get());
+
+  base::FilePath kRelativePath(FILE_PATH_LITERAL("empty.js"));
+  ExtensionId extension_id = extension->id();
+
+  // Sanity check empty.js.
+  base::FilePath file_path = unzipped_path.AppendASCII(kEmptyJs);
   int64_t foo_file_size = -1;
-  ASSERT_TRUE(base::GetFileSize(foo_path, &foo_file_size));
+  ASSERT_TRUE(base::GetFileSize(file_path, &foo_file_size));
   ASSERT_EQ(0, foo_file_size);
 
-  ExtensionBuilder builder;
-  builder
-      .SetManifest(DictionaryBuilder()
-                       .Set("name", "Foo")
-                       .Set("version", "1.0")
-                       .Set("manifest_version", 2)
-                       .Set("update_url",
-                            "https://clients2.google.com/service/update2/crx")
-                       .Build())
-      .SetID(crx_file::id_util::GenerateId("whatever"))
-      .SetPath(temp_dir.GetPath())
-      .SetLocation(Manifest::INTERNAL);
-  scoped_refptr<Extension> extension(builder.Build());
+  // Request empty.js.
+  {
+    TestContentVerifyJobObserver observer(extension_id, kRelativePath);
 
-  ASSERT_TRUE(extension.get());
-  content_verifier_->OnExtensionLoaded(testing_profile_.get(), extension.get());
-  // Wait for PostTask to ContentVerifierIOData::AddData() to finish.
-  content::RunAllPendingInMessageLoop();
+    content_verifier_->OnExtensionLoaded(testing_profile_.get(),
+                                         extension.get());
+    // Wait for PostTask to ContentVerifierIOData::AddData() to finish.
+    content::RunAllPendingInMessageLoop();
 
-  // Request foo.js.
-  EXPECT_EQ(net::OK, DoRequest(*extension, kFooJs));
-  test_job_delegate.WaitForDoneReading(extension->id());
+    EXPECT_EQ(net::OK, DoRequest(*extension, kEmptyJs));
+    EXPECT_EQ(ContentVerifyJob::NONE, observer.WaitAndGetFailureReason());
+  }
+
+  // chmod -r empty.js.
+  // Unreadable empty file doesn't generate hash mismatch. Note that this is the
+  // current behavior of ContentVerifyJob.
+  // TODO(lazyboy): The behavior is probably incorrect.
+  {
+    TestContentVerifyJobObserver observer(extension->id(), kRelativePath);
+    base::FilePath file_path = unzipped_path.AppendASCII(kEmptyJs);
+    ASSERT_TRUE(base::MakeFileUnreadable(file_path));
+    EXPECT_EQ(net::ERR_ACCESS_DENIED, DoRequest(*extension, kEmptyJs));
+    EXPECT_EQ(ContentVerifyJob::NONE, observer.WaitAndGetFailureReason());
+  }
+
+  // rm empty.js.
+  // Deleted empty file doesn't generate hash mismatch. Note that this is the
+  // current behavior of ContentVerifyJob.
+  // TODO(lazyboy): The behavior is probably incorrect.
+  {
+    TestContentVerifyJobObserver observer(extension_id, kRelativePath);
+    base::FilePath file_path = unzipped_path.AppendASCII(kEmptyJs);
+    ASSERT_TRUE(base::DieFileDie(file_path, false));
+    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, DoRequest(*extension, kEmptyJs));
+    EXPECT_EQ(ContentVerifyJob::NONE, observer.WaitAndGetFailureReason());
+  }
 }
 
 // Tests that mime types are properly set for returned extension resources.