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