@@ -62,6 +62,7 @@ using ::fuzztest::domain_implementor::PrintMode;
6262using ::testing::_;
6363using ::testing::AllOf;
6464using ::testing::AnyOf;
65+ using ::testing::Contains;
6566using ::testing::ContainsRegex;
6667using ::testing::Eq;
6768using ::testing::FieldsAre;
@@ -116,16 +117,11 @@ int CountTargetRuns(absl::string_view std_err) {
116117
117118class UnitTestModeTest : public ::testing::Test {
118119 protected:
119- RunResults Run (
120+ RunResults RunWithExactFuzzerFlags (
120121 absl::string_view test_filter,
121122 absl::string_view target_binary = kDefaultTargetBinary ,
122123 const absl::flat_hash_map<std::string, std::string>& env = {},
123124 absl::flat_hash_map<std::string, std::string> fuzzer_flags = {}) {
124- fuzzer_flags[" print_subprocess_log" ] = " true" ;
125- fuzzer_flags[" unguided" ] = " true" ;
126- if (!fuzzer_flags.contains (" fuzz_for" )) {
127- fuzzer_flags[" fuzz_for" ] = " 10s" ;
128- }
129125 RunOptions run_options;
130126 run_options.flags = {
131127 {GTEST_FLAG_PREFIX_ " filter" , std::string (test_filter)},
@@ -134,6 +130,20 @@ class UnitTestModeTest : public ::testing::Test {
134130 run_options.env = WithTestSanitizerOptions (env);
135131 return RunBinary (BinaryPath (target_binary), run_options);
136132 }
133+
134+ RunResults Run (
135+ absl::string_view test_filter,
136+ absl::string_view target_binary = kDefaultTargetBinary ,
137+ const absl::flat_hash_map<std::string, std::string>& env = {},
138+ absl::flat_hash_map<std::string, std::string> fuzzer_flags = {}) {
139+ fuzzer_flags[" print_subprocess_log" ] = " true" ;
140+ fuzzer_flags[" unguided" ] = " true" ;
141+ if (!fuzzer_flags.contains (" fuzz_for" )) {
142+ fuzzer_flags[" fuzz_for" ] = " 10s" ;
143+ }
144+ return RunWithExactFuzzerFlags (test_filter, target_binary, env,
145+ std::move (fuzzer_flags));
146+ }
137147};
138148
139149TEST_F (UnitTestModeTest, PassingTestPassesInUnitTestingMode) {
@@ -668,6 +678,52 @@ TEST_F(UnitTestModeTest, InputsAreSkippedWhenRequestedInTests) {
668678 EXPECT_THAT_LOG (std_err, HasSubstr (" Skipped input" ));
669679}
670680
681+ // Identifies fuzz tests as those with test suite name "FuzzTest".
682+ MATCHER (IsXmlWithExactlyFuzzTestsHavingFuzzTestProperty, " " ) {
683+ absl::string_view xml = arg;
684+ absl::string_view attrs;
685+ while (RE2::FindAndConsume (&xml, R"re( (?s)<testcase\s(.*?)>)re" , &attrs)) {
686+ const bool is_fuzz_test =
687+ RE2::PartialMatch (attrs, R"re( classname="FuzzTest")re" );
688+ if (!attrs.empty () && attrs.back () == ' /' ) {
689+ // Self-closing tag; no properties.
690+ if (is_fuzz_test) {
691+ *result_listener << " found a fuzz test without fuzz_test property" ;
692+ return false ;
693+ }
694+ continue ;
695+ }
696+ bool has_fuzz_test_property = false ;
697+ absl::string_view body;
698+ if (RE2::Consume (&xml, R"re( (?s)(.*?)</testcase>)re" , &body)) {
699+ has_fuzz_test_property =
700+ RE2::PartialMatch (body, R"re( <property name="fuzz_test")re" );
701+ }
702+ if (is_fuzz_test != has_fuzz_test_property) {
703+ *result_listener << " found a "
704+ << (is_fuzz_test ? " fuzz test " : " unit test " )
705+ << (has_fuzz_test_property ? " with " : " without " )
706+ << " fuzz_test property" ;
707+ return false ;
708+ }
709+ }
710+ return true ;
711+ }
712+
713+ TEST_F (UnitTestModeTest, FuzzTestsRecordFuzzTestProperty) {
714+ TempDir out_dir;
715+ const std::string xml_output_file = out_dir.path () / " output.xml" ;
716+ auto [status, std_out, std_err] =
717+ RunWithExactFuzzerFlags (" *" , " testdata/unit_test_and_fuzz_tests" ,
718+ {{" XML_OUTPUT_FILE" , xml_output_file}});
719+
720+ // Ensure that we've executed at least one unit test and one fuzz test.
721+ EXPECT_THAT_LOG (std_out, AllOf (HasSubstr (" UnitTest.AlwaysPasses" ),
722+ HasSubstr (" FuzzTest.AlwaysPasses" )));
723+ EXPECT_THAT (ReadFile (xml_output_file),
724+ Optional (IsXmlWithExactlyFuzzTestsHavingFuzzTestProperty ()));
725+ }
726+
671727// Tests for the FuzzTest command line interface.
672728class GenericCommandLineInterfaceTest : public ::testing::Test {
673729 protected:
@@ -1247,6 +1303,28 @@ TEST_F(FuzzingModeCommandLineInterfaceTest,
12471303 EXPECT_THAT (status, Eq (ExitCode (0 )));
12481304}
12491305
1306+ TEST_F (FuzzingModeCommandLineInterfaceTest,
1307+ FuzzTestsRecordFuzzTestPropertyWhenRunningWithCentipede) {
1308+ #ifndef FUZZTEST_USE_CENTIPEDE
1309+ GTEST_SKIP () << " Skipping Centipede-specific test" ;
1310+ #endif
1311+ TempDir temp_dir;
1312+ const std::string xml_output_file = temp_dir.path () / " output.xml" ;
1313+ auto [status, std_out, std_err] = RunWith (
1314+ {
1315+ {" fuzz_for" , " 1s" },
1316+ {" corpus_database" , temp_dir.path () / " corpus_database" },
1317+ {" internal_centipede_command" , ShellEscape (CentipedePath ())},
1318+ },
1319+ {{" XML_OUTPUT_FILE" , xml_output_file}},
1320+ /* timeout=*/ absl::Minutes (1 ), " testdata/unit_test_and_fuzz_tests" );
1321+
1322+ // Ensure that we've executed at least one fuzz test.
1323+ EXPECT_THAT_LOG (std_out, HasSubstr (" FuzzTest.AlwaysPasses" ));
1324+ EXPECT_THAT (ReadFile (xml_output_file),
1325+ Optional (IsXmlWithExactlyFuzzTestsHavingFuzzTestProperty ()));
1326+ }
1327+
12501328TEST_F (FuzzingModeCommandLineInterfaceTest,
12511329 FailsTestForSetupFailureWithoutCorpusDatabase) {
12521330#ifndef FUZZTEST_USE_CENTIPEDE
0 commit comments