Custom `centipede` for targets that read inputs from file & write execution results to file

This change implements a custom `centipede` binary to exercise target binaries that read a batch of inputs from a file and store their execution results one by one into an output file, as opposed to the regular `centipede`'s data exchange protocol via shared memory and pipes.

PiperOrigin-RevId: 547673267
diff --git a/centipede/batch_fuzz_example/BUILD b/centipede/batch_fuzz_example/BUILD
index 774a6e9..b5f77b1 100644
--- a/centipede/batch_fuzz_example/BUILD
+++ b/centipede/batch_fuzz_example/BUILD
@@ -27,3 +27,26 @@
         "@com_google_fuzztest//centipede:centipede_runner_no_main",  # build-cleaner:keep
     ],
 )
+
+cc_binary(
+    name = "customized_centipede",
+    srcs = ["customized_centipede.cc"],
+    deps = [
+        "@com_google_absl//absl/base:log_severity",
+        "@com_google_absl//absl/flags:parse",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/log:globals",
+        "@com_google_absl//absl/log:initialize",
+        "@com_google_absl//absl/strings",
+        "@com_google_fuzztest//centipede:centipede_callbacks",
+        "@com_google_fuzztest//centipede:centipede_interface",
+        "@com_google_fuzztest//centipede:command",
+        "@com_google_fuzztest//centipede:config_file",
+        "@com_google_fuzztest//centipede:defs",
+        "@com_google_fuzztest//centipede:environment",
+        "@com_google_fuzztest//centipede:execution_result",
+        "@com_google_fuzztest//centipede:shared_memory_blob_sequence",
+        "@com_google_fuzztest//centipede:util",
+    ],
+)
diff --git a/centipede/batch_fuzz_example/customized_centipede.cc b/centipede/batch_fuzz_example/customized_centipede.cc
new file mode 100644
index 0000000..a686396
--- /dev/null
+++ b/centipede/batch_fuzz_example/customized_centipede.cc
@@ -0,0 +1,141 @@
+// Copyright 2023 The Centipede Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include "absl/base/log_severity.h"
+#include "absl/flags/parse.h"
+#include "absl/log/check.h"
+#include "absl/log/globals.h"
+#include "absl/log/initialize.h"
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "./centipede/centipede_callbacks.h"
+#include "./centipede/centipede_interface.h"
+#include "./centipede/command.h"
+#include "./centipede/config_file.h"
+#include "./centipede/defs.h"
+#include "./centipede/environment.h"
+#include "./centipede/execution_result.h"
+#include "./centipede/shared_memory_blob_sequence.h"
+#include "./centipede/util.h"
+
+namespace centipede {
+namespace {
+
+bool UpdateBatchResult(absl::string_view output_file,
+                       BatchResult& batch_result) {
+  ByteArray content;
+  ReadFromLocalFile(output_file, content);
+  if (content.empty()) {
+    LOG(WARNING) << "Skip updating batch result with an emtpy output file: "
+                 << output_file;
+    return true;
+  }
+
+  BlobSequence blob_seq(content.data(), content.size());
+  if (batch_result.Read(blob_seq)) return true;
+
+  LOG(ERROR) << "Failed to read blob sequence from file: " << output_file;
+  return false;
+}
+
+// This class implements the `Execute()` method of the `CentipedeCallbacks`
+// class. It saves a collection of inputs into files and passes them to a target
+// binary. The binary should exercise them in a batch and store the execution
+// result of each input into an output file. Those execution results will be
+// loaded from the output file and packed as the given `batch_result`.
+class CustomizedCallbacks : public CentipedeCallbacks {
+ public:
+  explicit CustomizedCallbacks(const Environment& env)
+      : CentipedeCallbacks(env) {}
+
+  bool Execute(std::string_view binary, const std::vector<ByteArray>& inputs,
+               BatchResult& batch_result) override {
+    const std::string temp_dir = TemporaryLocalDirPath();
+    CHECK(!temp_dir.empty());
+    std::filesystem::create_directory(temp_dir);
+
+    std::string input_file_list;
+    int index = 0;
+    for (const auto& input : inputs) {
+      const std::string temp_file_path = std::filesystem::path(temp_dir).append(
+          absl::StrCat("input-", index++));
+      WriteToLocalFile(temp_file_path, input);
+      absl::StrAppend(&input_file_list, temp_file_path);
+      absl::StrAppend(&input_file_list, "\n");
+    }
+    const std::string input_list_filepath =
+        std::filesystem::path(temp_dir).append("input_file_list");
+    WriteToLocalFile(input_list_filepath, input_file_list);
+
+    const std::string tmp_output_filepath =
+        std::filesystem::path(temp_dir).append("output_execution_results");
+    const std::string tmp_log_filepath =
+        std::filesystem::path(temp_dir).append("tmp_log");
+
+    // Execute.
+    Command cmd{env_.binary,
+                {input_list_filepath, tmp_output_filepath},
+                // TODO: pass additional runner flags, such as use_cmp_features,
+                // based on `env`. Will require a small refactoring.
+                /*env=*/{},
+                tmp_log_filepath,
+                tmp_log_filepath};
+    const int retval = cmd.Execute();
+
+    std::string tmp_log;
+    ReadFromLocalFile(tmp_log_filepath, tmp_log);
+    LOG(INFO) << tmp_log;
+
+    batch_result.ClearAndResize(inputs.size());
+    CHECK(UpdateBatchResult(tmp_output_filepath, batch_result));
+    return retval == 0;
+  }
+};
+}  // namespace
+}  // namespace centipede
+
+int main(int argc, char** argv) {
+  // TODO: Refactoring the following code into a utility function that takes
+  // callbacks_factory as an argument.
+  const centipede::config::MainRuntimeInit runtime_init =
+      [](int argc, char** argv) -> std::vector<std::string> {
+    // NB: The invocation order is important here.
+    // By default, log everything to stderr. Explicit --stderrthreshold=N on the
+    // command line takes precedence.
+    absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
+    // Perform the initial command line parsing.
+    std::vector<std::string> leftover_argv =
+        centipede::config::CastArgv(absl::ParseCommandLine(argc, argv));
+    // Initialize the logging subsystem.
+    absl::InitializeLog();
+    return leftover_argv;
+  };
+
+  // Resolve any possible config-related flags in the command line and reparse
+  // it if any augmentations had to be made.
+  const auto leftover_argv =
+      centipede::config::InitCentipede(argc, argv, runtime_init);
+
+  // Reads flags; must happen after ParseCommandLine().
+  centipede::Environment env{leftover_argv};
+  centipede::DefaultCallbacksFactory<centipede::CustomizedCallbacks>
+      callbacks_factory;
+  return CentipedeMain(env, callbacks_factory);
+}