Move the Browsing Topics Annotator fuzz test

Moves the fuzz test into the browsing_topics component.

Verified this is fully working locally.

Bug: b/278162907
Change-Id: I4c26d117436316939145cc3c136f7a51f1df727a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4495631
Reviewed-by: Yao Xiao <[email protected]>
Commit-Queue: Robert Ogden <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1139126}
diff --git a/components/browsing_topics/BUILD.gn b/components/browsing_topics/BUILD.gn
index 8ea72a76..78f94e31a 100644
--- a/components/browsing_topics/BUILD.gn
+++ b/components/browsing_topics/BUILD.gn
@@ -1,6 +1,7 @@
 # Copyright 2022 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
+import("//testing/libfuzzer/fuzzer_test.gni")
 
 source_set("browsing_topics") {
   sources = [
@@ -106,3 +107,16 @@
     "//third_party/zlib/google:compression_utils",
   ]
 }
+
+fuzzer_test("browsing_topics_annotator_fuzzer") {
+  sources = [ "annotator_fuzzer.cc" ]
+  deps = [
+    ":browsing_topics",
+    "//base:base",
+    "//base/test:test_support",
+    "//components/optimization_guide/core:core",
+    "//components/optimization_guide/core:features",
+    "//components/optimization_guide/core:test_support",
+    "//components/optimization_guide/proto:optimization_guide_proto",
+  ]
+}
diff --git a/components/browsing_topics/annotator_fuzzer.cc b/components/browsing_topics/annotator_fuzzer.cc
new file mode 100644
index 0000000..45e13e9
--- /dev/null
+++ b/components/browsing_topics/annotator_fuzzer.cc
@@ -0,0 +1,146 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "components/browsing_topics/annotator_impl.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
+#include "components/optimization_guide/core/test_model_info_builder.h"
+#include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/optimization_guide/proto/page_topics_model_metadata.pb.h"
+
+// Sends a fully working model and configuration to the calling observer.
+class ModelProvider
+    : public optimization_guide::TestOptimizationGuideModelProvider {
+ public:
+  ModelProvider() = default;
+  ~ModelProvider() override = default;
+
+  // optimization_guide::TestOptimizationGuideModelProvider:
+  void AddObserverForOptimizationTargetModel(
+      optimization_guide::proto::OptimizationTarget optimization_target,
+      const absl::optional<optimization_guide::proto::Any>& model_metadata,
+      optimization_guide::OptimizationTargetModelObserver* observer) override {
+    optimization_guide::proto::Any any_metadata;
+    any_metadata.set_type_url(
+        "type.googleapis.com/com.foo.PageTopicsModelMetadata");
+    optimization_guide::proto::PageTopicsModelMetadata
+        page_topics_model_metadata;
+    page_topics_model_metadata.set_version(123);
+    page_topics_model_metadata.add_supported_output(
+        optimization_guide::proto::PAGE_TOPICS_SUPPORTED_OUTPUT_CATEGORIES);
+    auto* output_params =
+        page_topics_model_metadata.mutable_output_postprocessing_params();
+    auto* category_params = output_params->mutable_category_params();
+    category_params->set_max_categories(5);
+    category_params->set_min_none_weight(0.8);
+    category_params->set_min_category_weight(0.1);
+    category_params->set_min_normalized_weight_within_top_n(0.1);
+    page_topics_model_metadata.SerializeToString(any_metadata.mutable_value());
+
+    base::FilePath source_root_dir;
+    base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir);
+    base::FilePath model_file_path =
+        source_root_dir.AppendASCII("components")
+            .AppendASCII("test")
+            .AppendASCII("data")
+            .AppendASCII("browsing_topics")
+            .AppendASCII("golden_data_model.tflite");
+    std::unique_ptr<optimization_guide::ModelInfo> model_info =
+        optimization_guide::TestModelInfoBuilder()
+            .SetModelFilePath(model_file_path)
+            .SetModelMetadata(any_metadata)
+            .Build();
+
+    observer->OnModelUpdated(
+        optimization_guide::proto::OPTIMIZATION_TARGET_PAGE_TOPICS_V2,
+        *model_info);
+  }
+};
+
+// An AnnotatorImpl that never unloads the model, thus keeping the executions
+// per second high.
+class TestAnnotator : public browsing_topics::AnnotatorImpl {
+ public:
+  TestAnnotator(
+      optimization_guide::OptimizationGuideModelProvider* model_provider,
+      scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+      const absl::optional<optimization_guide::proto::Any>& model_metadata)
+      : browsing_topics::AnnotatorImpl(model_provider,
+                                       std::move(background_task_runner),
+                                       model_metadata) {}
+
+ protected:
+  // AnnotatorImpl:
+  void UnloadModel() override {
+    // Do nothing so that the model stays loaded.
+  }
+};
+
+// Static test fixture to maintain the state needed to run repeated fuzz tests.
+class AnnotatorFuzzerTest {
+ public:
+  explicit AnnotatorFuzzerTest()
+      : annotator_(std::make_unique<TestAnnotator>(
+            &model_provider_,
+            task_environment_.GetMainThreadTaskRunner(),
+            absl::nullopt)) {
+    scoped_feature_list_.InitAndDisableFeature(
+        optimization_guide::features::kPreventLongRunningPredictionModels);
+  }
+  ~AnnotatorFuzzerTest() { task_environment_.RunUntilIdle(); }
+
+  browsing_topics::Annotator* annotator() { return annotator_.get(); }
+
+  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  ModelProvider model_provider_;
+  std::unique_ptr<browsing_topics::AnnotatorImpl> annotator_;
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  static AnnotatorFuzzerTest test;
+  static bool had_successful_run = false;
+
+  std::string input(reinterpret_cast<const char*>(data), size);
+
+  base::RunLoop run_loop;
+  test.annotator()->BatchAnnotate(
+      base::BindOnce(
+          [](base::RunLoop* run_loop,
+             const std::vector<browsing_topics::Annotation>& annotations) {
+            if (!annotations[0].topics.empty() && !had_successful_run) {
+              had_successful_run = true;
+              // Print a single debug message so that its obvious things are
+              // working (or able to at least once) when running locally.
+              LOG(INFO) << "Congrats! Got a successful model execution. This "
+                           "message will not be printed again.";
+            }
+
+            run_loop->Quit();
+          },
+          &run_loop),
+      {input});
+  run_loop.Run();
+
+  // The model executor does some PostTask'ing to manage its state. While these
+  // tasks are not important for fuzzing, we don't want to queue up a ton of
+  // them.
+  test.RunUntilIdle();
+
+  return 0;
+}
diff --git a/components/browsing_topics/annotator_impl.h b/components/browsing_topics/annotator_impl.h
index d958528..87d63b4 100644
--- a/components/browsing_topics/annotator_impl.h
+++ b/components/browsing_topics/annotator_impl.h
@@ -77,6 +77,10 @@
   absl::optional<std::vector<int32_t>> ExtractCategoriesFromModelOutput(
       const std::vector<tflite::task::core::Category>& model_output) const;
 
+ protected:
+  // optimization_guide::BertModelHandler:
+  void UnloadModel() override;
+
  private:
   // Sets the |override_list_| after it was loaded on a background thread and
   // calls |StartBatchAnnotate|.
@@ -104,9 +108,6 @@
       BatchAnnotationCallback callback,
       std::unique_ptr<std::vector<Annotation>> annotations_ptr);
 
-  // optimization_guide::BertModelHandler:
-  void UnloadModel() override;
-
   // Sets |annotation.topics| from the output of the model, calling
   // |ExtractCategoriesFromModelOutput| in the process.
   void PostprocessCategoriesToBatchAnnotationResult(