Rename `GetInputFileComponents` and extract it to a common library.

The new name for the function is `ParseCrashingInputFilename`. It is extracted
so that it can be reused in other parts of the code base (in particular,
googletest_adapter) which are supported in the CMake builds.

PiperOrigin-RevId: 828504089
diff --git a/centipede/BUILD b/centipede/BUILD
index 8f725b9..df9016c 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -895,6 +895,7 @@
         "@abseil-cpp//absl/status:statusor",
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/time",
+        "@com_google_fuzztest//common:crashing_input_filename",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:hash",
         "@com_google_fuzztest//common:logging",
@@ -1864,7 +1865,6 @@
         ":util",
         ":workdir",
         "@abseil-cpp//absl/container:flat_hash_map",
-        "@abseil-cpp//absl/status:status_matchers",
         "@abseil-cpp//absl/strings:str_format",
         "@abseil-cpp//absl/time",
         "@com_google_fuzztest//common:defs",
diff --git a/centipede/crash_deduplication.cc b/centipede/crash_deduplication.cc
index 315c4cf..417a0b6 100644
--- a/centipede/crash_deduplication.cc
+++ b/centipede/crash_deduplication.cc
@@ -26,8 +26,6 @@
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/str_cat.h"
-#include "absl/strings/str_join.h"
-#include "absl/strings/str_split.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
 #include "./centipede/centipede_callbacks.h"
@@ -35,6 +33,7 @@
 #include "./centipede/environment.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/workdir.h"
+#include "./common/crashing_input_filename.h"
 #include "./common/defs.h"
 #include "./common/hash.h"
 #include "./common/logging.h"
@@ -102,33 +101,6 @@
   return crashes;
 }
 
-absl::StatusOr<InputFileComponents> GetInputFileComponents(
-    std::string_view input_file_path) {
-  const std::string file_name =
-      std::filesystem::path(std::string(input_file_path)).filename();
-  std::vector<std::string> parts = absl::StrSplit(file_name, '-');
-  if (parts.size() == 1) {
-    // Old format where the input file name is both the bug ID and the input
-    // signature.
-    return InputFileComponents{
-        /*bug_id=*/parts[0],
-        /*crash_signature=*/"",
-        /*input_signature=*/parts[0],
-    };
-  }
-  if (parts.size() < 3) {
-    return absl::InvalidArgumentError(
-        absl::StrCat("Input file name not in the format of "
-                     "<bug_id>-<crash_signature>-<input_signature>: ",
-                     file_name));
-  }
-  return InputFileComponents{
-      /*bug_id=*/absl::StrJoin(parts.begin(), parts.end() - 2, "-"),
-      /*crash_signature=*/std::move(parts[parts.size() - 2]),
-      /*input_signature=*/std::move(parts[parts.size() - 1]),
-  };
-}
-
 void OrganizeCrashingInputs(
     const std::filesystem::path& regression_dir,
     const std::filesystem::path& crashing_dir, const Environment& env,
@@ -154,7 +126,7 @@
     const bool is_reproducible = !scoped_callbacks.callbacks()->Execute(
                                      env.binary, {old_input}, batch_result) &&
                                  batch_result.IsInputFailure();
-    auto input_file_components = GetInputFileComponents(old_input_file);
+    auto input_file_components = ParseCrashingInputFilename(old_input_file);
     FUZZTEST_LOG_IF(WARNING, !input_file_components.ok())
         << "Failed to get input file components for " << old_input_file
         << ". Status: " << input_file_components.status();
diff --git a/centipede/crash_deduplication.h b/centipede/crash_deduplication.h
index 3300bd8..f5859eb 100644
--- a/centipede/crash_deduplication.h
+++ b/centipede/crash_deduplication.h
@@ -18,10 +18,8 @@
 #include <cstddef>
 #include <filesystem>  // NOLINT
 #include <string>
-#include <string_view>
 
 #include "absl/container/flat_hash_map.h"
-#include "absl/status/statusor.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/crash_summary.h"
 #include "./centipede/environment.h"
@@ -40,24 +38,6 @@
 absl::flat_hash_map<std::string, CrashDetails> GetCrashesFromWorkdir(
     const WorkDir& workdir, size_t total_shards);
 
-struct InputFileComponents {
-  // The identifier that is used to keep track of the crash over time even if
-  // the crash signature or the crashing input changes.
-  std::string bug_id;
-  // The hash of the crash metadata used to deduplicate crashes.
-  std::string crash_signature;
-  // The hash of the input.
-  std::string input_signature;
-};
-
-// Returns the components of an input file extracted from the file name.
-// The file name is expected to be in the format
-// `<bug_id>-<crash_signature>-<input_signature>` or `<input_signature>` for
-// backwards compatibility, where `<crash_signature>` and `<input_signature>`
-// don't contain dashes.
-absl::StatusOr<InputFileComponents> GetInputFileComponents(
-    std::string_view input_file_path);
-
 // Organizes crashing inputs from `crashing_dir` by attempting to reproduce
 // them, and stores new crashes from `new_crashes_by_signature` that are not
 // duplicates of existing ones.
diff --git a/centipede/crash_deduplication_test.cc b/centipede/crash_deduplication_test.cc
index b6008bf..e73e7c5 100644
--- a/centipede/crash_deduplication_test.cc
+++ b/centipede/crash_deduplication_test.cc
@@ -24,7 +24,6 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/container/flat_hash_map.h"
-#include "absl/status/status_matchers.h"
 #include "absl/strings/str_format.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
@@ -41,15 +40,12 @@
 namespace fuzztest::internal {
 namespace {
 
-using ::absl_testing::IsOk;
-using ::absl_testing::IsOkAndHolds;
 using ::testing::AllOf;
 using ::testing::AnyOf;
 using ::testing::EndsWith;
 using ::testing::FieldsAre;
 using ::testing::HasSubstr;
 using ::testing::IsEmpty;
-using ::testing::Not;
 using ::testing::Pair;
 using ::testing::UnorderedElementsAre;
 
@@ -104,25 +100,6 @@
           Pair("csig2", FieldsAre("isig2", "desc2", input2_path))));
 }
 
-TEST(GetInputFileComponentsTest, ParsesFileNameWithOnlyInputSignature) {
-  EXPECT_THAT(GetInputFileComponents("input_signature"),
-              IsOkAndHolds(FieldsAre(/*bug_id=*/"input_signature",
-                                     /*crash_signature=*/"",
-                                     /*input_signature=*/"input_signature")));
-}
-
-TEST(GetInputFileComponentsTest, FailsOnInvalidFileName) {
-  EXPECT_THAT(GetInputFileComponents("single-dash"), Not(IsOk()));
-}
-
-TEST(GetInputFileComponentsTest, ParsesFileNameWithAllComponents) {
-  EXPECT_THAT(
-      GetInputFileComponents("id-with-dash-crash_signature-input_signature"),
-      IsOkAndHolds(FieldsAre(/*bug_id=*/"id-with-dash",
-                             /*crash_signature=*/"crash_signature",
-                             /*input_signature=*/"input_signature")));
-}
-
 class FakeCentipedeCallbacks : public CentipedeCallbacks {
  public:
   struct Crash {
diff --git a/common/BUILD b/common/BUILD
index 2c17265..d64c480 100644
--- a/common/BUILD
+++ b/common/BUILD
@@ -81,6 +81,18 @@
 )
 
 cc_library(
+    name = "crashing_input_filename",
+    srcs = ["crashing_input_filename.cc"],
+    hdrs = ["crashing_input_filename.h"],
+    deps = [
+        "@abseil-cpp//absl/status",
+        "@abseil-cpp//absl/status:statusor",
+        "@abseil-cpp//absl/strings",
+        "@com_google_fuzztest//fuzztest/internal:io",
+    ],
+)
+
+cc_library(
     name = "defs",
     hdrs = ["defs.h"],
     deps = ["@abseil-cpp//absl/types:span"],
@@ -223,6 +235,15 @@
 )
 
 cc_test(
+    name = "crashing_input_filename_test",
+    srcs = ["crashing_input_filename_test.cc"],
+    deps = [
+        ":crashing_input_filename",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "hash_test",
     srcs = ["hash_test.cc"],
     deps = [
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 4e0f026..f451674 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -61,6 +61,20 @@
 
 fuzztest_cc_library(
   NAME
+    crashing_input_filename
+  HDRS
+    "crashing_input_filename.h"
+  SRCS
+    "crashing_input_filename.cc"
+  DEPS
+    absl::status
+    absl::statusor
+    absl::strings
+    fuzztest::io
+)
+
+fuzztest_cc_library(
+  NAME
     defs
   HDRS
     "defs.h"
@@ -180,6 +194,16 @@
 
 fuzztest_cc_test(
   NAME
+    crashing_input_filename_test
+  SRCS
+    "crashing_input_filename_test.cc"
+  DEPS
+    fuzztest::crashing_input_filename
+    GTest::gmock_main
+)
+
+fuzztest_cc_test(
+  NAME
     hash_test
   SRCS
     "hash_test.cc"
diff --git a/common/crashing_input_filename.cc b/common/crashing_input_filename.cc
new file mode 100644
index 0000000..36939c4
--- /dev/null
+++ b/common/crashing_input_filename.cc
@@ -0,0 +1,59 @@
+// Copyright 2025 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 "./common/crashing_input_filename.h"
+
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "./fuzztest/internal/io.h"
+
+namespace fuzztest::internal {
+
+absl::StatusOr<InputFileComponents> ParseCrashingInputFilename(
+    std::string_view input_file_path) {
+  absl::string_view file_name = Basename(
+      absl::string_view{input_file_path.data(), input_file_path.size()});
+  std::vector<std::string> parts = absl::StrSplit(file_name, '-');
+  if (parts.size() == 1) {
+    // Old format where the input file name is both the bug ID and the input
+    // signature.
+    return InputFileComponents{
+        /*bug_id=*/parts[0],
+        /*crash_signature=*/"",
+        /*input_signature=*/parts[0],
+    };
+  }
+  if (parts.size() < 3) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Input file name not in the format of "
+                     "<bug_id>-<crash_signature>-<input_signature>: ",
+                     file_name));
+  }
+  return InputFileComponents{
+      /*bug_id=*/absl::StrJoin(parts.begin(), parts.end() - 2, "-"),
+      /*crash_signature=*/std::move(parts[parts.size() - 2]),
+      /*input_signature=*/std::move(parts[parts.size() - 1]),
+  };
+}
+
+}  // namespace fuzztest::internal
diff --git a/common/crashing_input_filename.h b/common/crashing_input_filename.h
new file mode 100644
index 0000000..04af386
--- /dev/null
+++ b/common/crashing_input_filename.h
@@ -0,0 +1,45 @@
+// Copyright 2025 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.
+
+#ifndef FUZZTEST_COMMON_CRASHING_INPUT_FILENAME_H_
+#define FUZZTEST_COMMON_CRASHING_INPUT_FILENAME_H_
+
+#include <string>
+#include <string_view>
+
+#include "absl/status/statusor.h"
+
+namespace fuzztest::internal {
+
+struct InputFileComponents {
+  // The identifier that is used to keep track of the crash over time even if
+  // the crash signature or the crashing input changes.
+  std::string bug_id;
+  // The hash of the crash metadata used to deduplicate crashes.
+  std::string crash_signature;
+  // The hash of the input.
+  std::string input_signature;
+};
+
+// Returns the components of an input file extracted from the file name.
+// The file name is expected to be in the format
+// `<bug_id>-<crash_signature>-<input_signature>` or `<input_signature>` for
+// backwards compatibility, where `<crash_signature>` and `<input_signature>`
+// don't contain dashes.
+absl::StatusOr<InputFileComponents> ParseCrashingInputFilename(
+    std::string_view input_file_path);
+
+}  // namespace fuzztest::internal
+
+#endif  // FUZZTEST_COMMON_CRASHING_INPUT_FILENAME_H_
diff --git a/common/crashing_input_filename_test.cc b/common/crashing_input_filename_test.cc
new file mode 100644
index 0000000..827ba4e
--- /dev/null
+++ b/common/crashing_input_filename_test.cc
@@ -0,0 +1,47 @@
+// Copyright 2025 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 "./common/crashing_input_filename.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace fuzztest::internal {
+namespace {
+
+using ::testing::FieldsAre;
+
+TEST(ParseCrashingInputFilenameTest, ParsesFileNameWithOnlyInputSignature) {
+  auto components = ParseCrashingInputFilename("input_signature");
+  ASSERT_TRUE(components.ok());
+  EXPECT_THAT(*components, FieldsAre(/*bug_id=*/"input_signature",
+                                     /*crash_signature=*/"",
+                                     /*input_signature=*/"input_signature"));
+}
+
+TEST(ParseCrashingInputFilenameTest, FailsOnInvalidFileName) {
+  EXPECT_FALSE(ParseCrashingInputFilename("single-dash").ok());
+}
+
+TEST(ParseCrashingInputFilenameTest, ParsesFileNameWithAllComponents) {
+  auto components = ParseCrashingInputFilename(
+      "id-with-dash-crash_signature-input_signature");
+  ASSERT_TRUE(components.ok());
+  EXPECT_THAT(*components, FieldsAre(/*bug_id=*/"id-with-dash",
+                                     /*crash_signature=*/"crash_signature",
+                                     /*input_signature=*/"input_signature"));
+}
+
+}  // namespace
+}  // namespace fuzztest::internal