From 2343088ed6b85e4efa4bc5e2edd9cb2c984d0dc0 Mon Sep 17 00:00:00 2001 From: "Matthew \"strager\" Glazar" Date: Thu, 4 Jan 2024 01:23:24 -0500 Subject: [PATCH] refactor(test): allow assert_diagnostics to check Diag_List assert_diagnostic only supports std::vector. I want to drop Diag_Collector in favor of Diag_List. Add support for Diag_List in assert_diagnostic to help us migrate away from Diag_Collector. --- test/quick-lint-js/diag-matcher.cpp | 49 +++-- test/quick-lint-js/diag-matcher.h | 6 + test/quick-lint-js/diagnostic-assertion.cpp | 116 ++++++++++ test/quick-lint-js/diagnostic-assertion.h | 13 ++ test/test-configuration.cpp | 118 ++++------ test/test-diagnostic-assertion.cpp | 231 ++++++++++++++++++++ 6 files changed, 435 insertions(+), 98 deletions(-) diff --git a/test/quick-lint-js/diag-matcher.cpp b/test/quick-lint-js/diag-matcher.cpp index c636dde465..0ec4c911ce 100644 --- a/test/quick-lint-js/diag-matcher.cpp +++ b/test/quick-lint-js/diag-matcher.cpp @@ -138,7 +138,8 @@ Variable_Kind Diag_Matcher_Arg::get_variable_kind( template class Diag_Fields_Matcher_Impl_Base - : public testing::MatcherInterface { + : public testing::MatcherInterface, + public testing::MatcherInterface { public: explicit Diag_Fields_Matcher_Impl_Base(State s) : state_(std::move(s)) {} @@ -158,9 +159,19 @@ class Diag_Fields_Matcher_Impl_Base bool MatchAndExplain(const Diag_Collector::Diag &error, testing::MatchResultListener *listener) const final { - bool type_matches = error.type() == this->state_.type; + return this->MatchAndExplain( + Any_Diag_Pointer{ + .type = error.type(), + .data = error.data(), + }, + listener); + } + + bool MatchAndExplain(const Any_Diag_Pointer &error, + testing::MatchResultListener *listener) const final { + bool type_matches = error.type == this->state_.type; if (!type_matches) { - *listener << "whose type (" << error.type() << ") isn't " + *listener << "whose type (" << error.type << ") isn't " << this->state_.type; return false; } @@ -179,7 +190,7 @@ class Diag_Fields_Matcher_Impl_Base } protected: - virtual bool field_matches(const Diag_Collector::Diag &error, const Field &f, + virtual bool field_matches(const Any_Diag_Pointer &error, const Field &f, testing::MatchResultListener *listener) const = 0; State state_; @@ -209,10 +220,10 @@ class Diag_Matcher::Impl final using Base::Diag_Fields_Matcher_Impl_Base; protected: - bool field_matches(const Diag_Collector::Diag &error, const Field &f, + bool field_matches(const Any_Diag_Pointer &error, const Field &f, testing::MatchResultListener *listener) const override { QLJS_ASSERT(this->state_.input.has_value()); - Source_Code_Span span = f.arg.get_span(error.data()); + Source_Code_Span span = f.arg.get_span(error.data); auto span_begin_offset = narrow_cast( span.begin() - this->state_.input->data()); auto span_end_offset = narrow_cast( @@ -248,11 +259,11 @@ class Diag_Matcher_2::Impl final using Base::Diag_Fields_Matcher_Impl_Base; protected: - bool field_matches(const Diag_Collector::Diag &error, const Field &f, + bool field_matches(const Any_Diag_Pointer &error, const Field &f, testing::MatchResultListener *listener) const override { switch (f.arg.member_type) { case Diagnostic_Arg_Type::source_code_span: { - Source_Code_Span span = f.arg.get_span(error.data()); + Source_Code_Span span = f.arg.get_span(error.data); auto span_begin_offset = narrow_cast( span.begin() - this->state_.input.data()); auto span_end_offset = narrow_cast( @@ -268,7 +279,7 @@ class Diag_Matcher_2::Impl final } case Diagnostic_Arg_Type::char8: { - Char8 character = f.arg.get_char8(error.data()); + Char8 character = f.arg.get_char8(error.data); bool character_matches = character == f.character; *listener << "whose ." << f.arg.member_name << " ('" << static_cast(character) << "') " @@ -278,7 +289,7 @@ class Diag_Matcher_2::Impl final } case Diagnostic_Arg_Type::enum_kind: { - Enum_Kind enum_kind = f.arg.get_enum_kind(error.data()); + Enum_Kind enum_kind = f.arg.get_enum_kind(error.data); bool matches = enum_kind == f.enum_kind; *listener << "whose ." << f.arg.member_name << " (" << enum_kind << ") " << (matches ? "equals" : "doesn't equal") << " " << f.enum_kind; @@ -286,7 +297,7 @@ class Diag_Matcher_2::Impl final } case Diagnostic_Arg_Type::string8_view: { - String8_View string = f.arg.get_string8_view(error.data()); + String8_View string = f.arg.get_string8_view(error.data); bool character_matches = string == f.string; *listener << "whose ." << f.arg.member_name << " (\"" << to_string_view(string) << "\") " @@ -296,7 +307,7 @@ class Diag_Matcher_2::Impl final } case Diagnostic_Arg_Type::statement_kind: { - Statement_Kind statement_kind = f.arg.get_statement_kind(error.data()); + Statement_Kind statement_kind = f.arg.get_statement_kind(error.data); bool character_matches = statement_kind == f.statement_kind; *listener << "whose ." << f.arg.member_name << " (" << statement_kind << ") " << (character_matches ? "equals" : "doesn't equal") @@ -305,7 +316,7 @@ class Diag_Matcher_2::Impl final } case Diagnostic_Arg_Type::variable_kind: { - Variable_Kind variable_kind = f.arg.get_variable_kind(error.data()); + Variable_Kind variable_kind = f.arg.get_variable_kind(error.data); bool character_matches = variable_kind == f.variable_kind; *listener << "whose ." << f.arg.member_name << " (" << variable_kind << ") " << (character_matches ? "equals" : "doesn't equal") @@ -320,11 +331,15 @@ class Diag_Matcher_2::Impl final } }; -/*implicit*/ Diag_Matcher_2::operator testing::Matcher< - const Diag_Collector::Diag &>() const { +Diag_Matcher_2::operator testing::Matcher() + const { return testing::Matcher(new Impl(this->state_)); } +Diag_Matcher_2::operator testing::Matcher() const { + return testing::Matcher(new Impl(this->state_)); +} + Diag_Spans_Matcher::Diag_Spans_Matcher(Diag_Type type, Field field_0) : state_{type, {field_0}} {} @@ -342,9 +357,9 @@ class Diag_Spans_Matcher::Impl using Base::Diag_Fields_Matcher_Impl_Base; protected: - bool field_matches(const Diag_Collector::Diag &error, const Field &f, + bool field_matches(const Any_Diag_Pointer &error, const Field &f, testing::MatchResultListener *listener) const override { - Source_Code_Span span = f.arg.get_span(error.data()); + Source_Code_Span span = f.arg.get_span(error.data); bool span_matches = same_pointers(span, f.expected); *listener << "whose ." << f.arg.member_name << " (`" << out_string8(span.string_view()) << "` @" diff --git a/test/quick-lint-js/diag-matcher.h b/test/quick-lint-js/diag-matcher.h index cbfa038cef..ee39a7d473 100644 --- a/test/quick-lint-js/diag-matcher.h +++ b/test/quick-lint-js/diag-matcher.h @@ -206,6 +206,11 @@ class Diag_Matcher { State state_; }; +struct Any_Diag_Pointer { + Diag_Type type; + const void *data; +}; + // A mix of ::testing::VariantWith, ::testing::Field, and Offsets_Matcher. These // are combined into one matcher to significantly reduce compile times. class Diag_Matcher_2 { @@ -242,6 +247,7 @@ class Diag_Matcher_2 { Diag_Matcher_2 &operator=(Diag_Matcher_2 &&) = default; /*implicit*/ operator testing::Matcher() const; + /*implicit*/ operator testing::Matcher() const; private: class Impl; diff --git a/test/quick-lint-js/diagnostic-assertion.cpp b/test/quick-lint-js/diagnostic-assertion.cpp index dea4a4c0c2..0290157f91 100644 --- a/test/quick-lint-js/diagnostic-assertion.cpp +++ b/test/quick-lint-js/diagnostic-assertion.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -463,6 +464,20 @@ void assert_diagnostics(Padded_String_View code, Span(assertions), caller); } +void assert_diagnostics(Padded_String_View code, const Diag_List& diagnostics, + Span assertions, + Source_Location caller) { + EXPECT_THAT_AT_CALLER(diagnostics, diagnostics_matcher_2(code, assertions)); +} + +void assert_diagnostics(Padded_String_View code, const Diag_List& diagnostics, + std::initializer_list assertions, + Source_Location caller) { + assert_diagnostics(code, diagnostics, + Span(assertions), caller); +} + +// TODO(#1154): Delete in favor of diagnostics_matcher_2. ::testing::Matcher&> diagnostics_matcher(Padded_String_View code, Span assertions) { @@ -527,6 +542,107 @@ diagnostics_matcher(Padded_String_View code, Span(assertions)); } +namespace { +class Diag_List_Matcher_Impl + : public testing::MatcherInterface { + public: + explicit Diag_List_Matcher_Impl(std::vector&& error_matchers) + : error_matchers_(std::move(error_matchers)) {} + + void DescribeTo([[maybe_unused]] std::ostream* out) const override { + // FIXME(strager): Do we need to write anything here? + } + + void DescribeNegationTo([[maybe_unused]] std::ostream* out) const override { + // FIXME(strager): Do we need to write anything here? + } + + bool MatchAndExplain(const Diag_List& diags, + testing::MatchResultListener* listener) const override { + // TODO(strager): Write custom messages instead of delegating to Google + // Test's built-ins. + std::vector diag_pointers; + diags.for_each([&](Diag_Type type, const void* data) -> void { + diag_pointers.push_back(Any_Diag_Pointer{.type = type, .data = data}); + }); + + using Vector_Matcher = + ::testing::Matcher&>; + Vector_Matcher vector_matcher = + this->error_matchers_.size() <= 1 + ? + // ElementsAreArray produces better diagnostics than + // UnorderedElementsAreArray. + Vector_Matcher( + ::testing::ElementsAreArray(std::move(this->error_matchers_))) + : Vector_Matcher(::testing::UnorderedElementsAreArray( + std::move(this->error_matchers_))); + return vector_matcher.MatchAndExplain(diag_pointers, listener); + } + + private: + std::vector error_matchers_; +}; +} + +::testing::Matcher diagnostics_matcher_2( + Padded_String_View code, Span assertions) { + std::vector error_matchers; + for (const Diagnostic_Assertion& diag : assertions) { + Diagnostic_Assertion adjusted_diag = + diag.adjusted_for_escaped_characters(code.string_view()); + + std::vector fields; + for (const Diagnostic_Assertion::Member& member : adjusted_diag.members) { + Diag_Matcher_2::Field field; + field.arg = Diag_Matcher_Arg{ + .member_name = to_string_view(member.name), + .member_offset = member.offset, + .member_type = member.type, + }; + switch (member.type) { + case Diagnostic_Arg_Type::source_code_span: + field.begin_offset = narrow_cast( + member.span_begin_offset); + field.end_offset = narrow_cast( + member.span_end_offset); + break; + case Diagnostic_Arg_Type::char8: + field.character = member.character; + break; + case Diagnostic_Arg_Type::enum_kind: + field.enum_kind = member.enum_kind; + break; + case Diagnostic_Arg_Type::string8_view: + field.string = member.string; + break; + case Diagnostic_Arg_Type::statement_kind: + field.statement_kind = member.statement_kind; + break; + case Diagnostic_Arg_Type::variable_kind: + field.variable_kind = member.variable_kind; + break; + default: + QLJS_ASSERT(false); + break; + } + fields.push_back(field); + } + + error_matchers.push_back( + Diag_Matcher_2(code, adjusted_diag.type, std::move(fields))); + } + return ::testing::Matcher( + new Diag_List_Matcher_Impl(std::move(error_matchers))); +} + +::testing::Matcher diagnostics_matcher_2( + Padded_String_View code, + std::initializer_list assertions) { + return diagnostics_matcher_2(code, + Span(assertions)); +} + namespace { std::optional try_parse_enum_kind(String8_View s) { #define QLJS_CASE(kind) \ diff --git a/test/quick-lint-js/diagnostic-assertion.h b/test/quick-lint-js/diagnostic-assertion.h index d5f79b86dd..062fda56f7 100644 --- a/test/quick-lint-js/diagnostic-assertion.h +++ b/test/quick-lint-js/diagnostic-assertion.h @@ -148,12 +148,25 @@ void assert_diagnostics(Padded_String_View code, std::initializer_list assertions, Source_Location caller = Source_Location::current()); +void assert_diagnostics(Padded_String_View code, const Diag_List& diagnostics, + Span assertions, + Source_Location caller); +void assert_diagnostics(Padded_String_View code, const Diag_List& diagnostics, + std::initializer_list assertions, + Source_Location caller = Source_Location::current()); + ::testing::Matcher&> diagnostics_matcher(Padded_String_View code, Span assertions); ::testing::Matcher&> diagnostics_matcher(Padded_String_View code, std::initializer_list assertions); + +::testing::Matcher diagnostics_matcher_2( + Padded_String_View code, Span assertions); +::testing::Matcher diagnostics_matcher_2( + Padded_String_View code, + std::initializer_list assertions); } // quick-lint-js finds bugs in JavaScript programs. diff --git a/test/test-configuration.cpp b/test/test-configuration.cpp index f54ae79183..ce92e801bb 100644 --- a/test/test-configuration.cpp +++ b/test/test-configuration.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -21,7 +22,6 @@ (config).globals().find_runtime_or_type(u8"variableDoesNotExist"_sv)); \ } while (false) -using ::testing::ElementsAreArray; using namespace std::literals::string_view_literals; namespace quick_lint_js { @@ -623,12 +623,8 @@ TEST(Test_Configuration_JSON, invalid_json_reports_error) { Diag_List diags(&temp_memory); c.load_from_json(&json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); // TODO(strager): Check Diag_Config_Json_Syntax_Error::where. - EXPECT_THAT(errors.errors, - ElementsAreArray({DIAG_TYPE(Diag_Config_Json_Syntax_Error)})); + assert_diagnostics(&json, diags, {u8"Diag_Config_Json_Syntax_Error"_diag}); } } @@ -636,36 +632,27 @@ TEST(Test_Configuration_JSON, bad_schema_in_globals_reports_error) { Monotonic_Allocator temp_memory("test"); { - Padded_String json(u8R"({"globals":["myGlobalVariable"]})"_sv); + // clang-format off + Padded_String json(u8"{\"globals\":[\"myGlobalVariable\"]}"_sv); + auto error = /* */ u8" ^ Diag_Config_Globals_Type_Mismatch"_diag; + // clang-format on Configuration c; Diag_List diags(&temp_memory); c.load_from_json(&json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); - EXPECT_THAT(errors.errors, - ElementsAreArray({DIAG_TYPE_OFFSETS( - &json, Diag_Config_Globals_Type_Mismatch, // - value, u8R"({"globals":)"_sv.size(), u8"["_sv)})); + assert_diagnostics(&json, diags, {error}); EXPECT_FALSE(c.globals().find_runtime_or_type(u8"myGlobalVariable"_sv)) << "invalid global should be ignored"; } { - Padded_String json( - u8R"({"globals":{"testBefore":true,"testBad":"string","testAfter":true}})"_sv); + // clang-format off + Padded_String json(u8"{\"globals\":{\"testBefore\":true,\"testBad\":\"string\",\"testAfter\":true}}"_sv); + auto error = /* */ u8" ^^^^^^^^^^ Diag_Config_Globals_Descriptor_Type_Mismatch"_diag; + // clang-format on Configuration c; Diag_List diags(&temp_memory); c.load_from_json(&json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); - EXPECT_THAT(errors.errors, - ElementsAreArray({DIAG_TYPE_OFFSETS( - &json, Diag_Config_Globals_Descriptor_Type_Mismatch, // - descriptor, - u8R"({"globals":{"testBefore":true,"testBad":)"_sv.size(), - u8R"("string")"_sv)})); + assert_diagnostics(&json, diags, {error}); EXPECT_TRUE(c.globals().find_runtime_or_type(u8"testBefore"_sv)) << "valid globals before should work"; @@ -676,22 +663,14 @@ TEST(Test_Configuration_JSON, bad_schema_in_globals_reports_error) { } { - Padded_String json( - u8R"({"globals":{"testBefore":true,"testBad":{"writable":false,"shadowable":"string"},"testAfter":true}})"_sv); + // clang-format off + Padded_String json(u8"{\"globals\":{\"testBefore\":true,\"testBad\":{\"writable\":false,\"shadowable\":\"string\"},\"testAfter\":true}}"_sv); + auto error = /* */ u8" ^^^^^^^^^^ Diag_Config_Globals_Descriptor_Shadowable_Type_Mismatch"_diag; + // clang-format on Configuration c; Diag_List diags(&temp_memory); c.load_from_json(&json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); - EXPECT_THAT( - errors.errors, - ElementsAreArray({DIAG_TYPE_OFFSETS( - &json, Diag_Config_Globals_Descriptor_Shadowable_Type_Mismatch, // - value, - u8R"({"globals":{"testBefore":true,"testBad":{"writable":false,"shadowable":)"_sv - .size(), - u8R"("string")"_sv)})); + assert_diagnostics(&json, diags, {error}); EXPECT_TRUE(c.globals().find_runtime_or_type(u8"testBefore"_sv)) << "valid globals before should work"; @@ -707,22 +686,14 @@ TEST(Test_Configuration_JSON, bad_schema_in_globals_reports_error) { } { - Padded_String json( - u8R"({"globals":{"testBefore":true,"testBad":{"writable":"string","shadowable":false},"testAfter":true}})"_sv); + // clang-format off + Padded_String json(u8"{\"globals\":{\"testBefore\":true,\"testBad\":{\"writable\":\"string\",\"shadowable\":false},\"testAfter\":true}}"_sv); + auto error = /* */ u8" ^^^^^^^^^^ Diag_Config_Globals_Descriptor_Writable_Type_Mismatch"_diag; + // clang-format on Configuration c; Diag_List diags(&temp_memory); c.load_from_json(&json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); - EXPECT_THAT( - errors.errors, - ElementsAreArray({DIAG_TYPE_OFFSETS( - &json, Diag_Config_Globals_Descriptor_Writable_Type_Mismatch, // - value, - u8R"({"globals":{"testBefore":true,"testBad":{"writable":)"_sv - .size(), - u8R"("string")"_sv)})); + assert_diagnostics(&json, diags, {error}); EXPECT_TRUE(c.globals().find_runtime_or_type(u8"testBefore"_sv)) << "valid globals before should work"; @@ -742,35 +713,27 @@ TEST(Test_Configuration_JSON, bad_schema_in_global_groups_reports_error) { Monotonic_Allocator temp_memory("test"); { - Padded_String json(u8R"({"global-groups":{"browser":true}})"_sv); + // clang-format off + Padded_String json(u8"{\"global-groups\":{\"browser\":true}}"_sv); + auto error = /* */ u8" ^ Diag_Config_Global_Groups_Type_Mismatch"_diag; + // clang-format on Configuration c; Diag_List diags(&temp_memory); c.load_from_json(&json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); - EXPECT_THAT(errors.errors, - ElementsAreArray({DIAG_TYPE_OFFSETS( - &json, Diag_Config_Global_Groups_Type_Mismatch, // - value, u8R"({"global-groups":)"_sv.size(), u8"{"_sv)})); + assert_diagnostics(&json, diags, {error}); EXPECT_TRUE(c.globals().find_runtime_or_type(u8"Array"_sv)) << "invalid global-groups should be ignored"; } { - Padded_String json( - u8R"({"global-groups":["browser",false,"ecmascript"]})"_sv); + // clang-format off + Padded_String json(u8"{\"global-groups\":[\"browser\",false,\"ecmascript\"]}"_sv); + auto error = /* */ u8" ^^^^^ Diag_Config_Global_Groups_Group_Type_Mismatch"_diag; + // clang-format on Configuration c; Diag_List diags(&temp_memory); c.load_from_json(&json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); - EXPECT_THAT(errors.errors, - ElementsAreArray({DIAG_TYPE_OFFSETS( - &json, Diag_Config_Global_Groups_Group_Type_Mismatch, // - group, u8R"({"global-groups":["browser",)"_sv.size(), - u8"false"_sv)})); + assert_diagnostics(&json, diags, {error}); EXPECT_TRUE(c.globals().find_runtime_or_type(u8"Array"_sv)) << "valid group-groups entries should take effect\n" @@ -793,29 +756,22 @@ TEST(Test_Configuration_JSON, bad_global_error_excludes_trailing_whitespace) { // According to RFC 8259, whitespace characters are U+0009, U+000A, U+000D, // and U+0020. + // clang-format off Padded_String json(u8"{ \"globals\": { \"a\": \"b\" \n\t\r }}"_sv); + auto error = /* */ u8" ^^^^^ Diag_Config_Globals_Descriptor_Type_Mismatch"_diag; + // clang-format on Configuration c; Diag_List diags(&temp_memory); c.load_from_json(&json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); - EXPECT_THAT( - errors.errors, - ElementsAreArray({DIAG_TYPE_OFFSETS( - &json, Diag_Config_Globals_Descriptor_Type_Mismatch, // - descriptor, u8R"({ "globals": { "a": )"_sv.size(), u8R"("b")"_sv)})); + assert_diagnostics(&json, diags, {error}); } void load_from_json(Configuration& config, Padded_String_View json) { Monotonic_Allocator temp_memory("test"); Diag_List diags(&temp_memory); config.load_from_json(json, &diags); - // TODO(#1154): Remove Diag_Collector and use Diag_List directly. - Diag_Collector errors; - errors.report(diags); - EXPECT_THAT(errors.errors, ::testing::IsEmpty()); + assert_diagnostics(json, diags, {}); } void load_from_json(Configuration& config, String8_View json) { diff --git a/test/test-diagnostic-assertion.cpp b/test/test-diagnostic-assertion.cpp index 274529d5de..ec943bd35e 100644 --- a/test/test-diagnostic-assertion.cpp +++ b/test/test-diagnostic-assertion.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -747,6 +748,236 @@ TEST(Test_Diagnostic_Assertion, multiple_diagnostics_are_matched_in_any_order) { EXPECT_TRUE(continue_break_matcher.Matches({break_diag, continue_diag})); EXPECT_TRUE(continue_break_matcher.Matches({continue_diag, break_diag})); } + +class Test_Diagnostic_Assertion_2 : public ::testing::Test { + protected: + Monotonic_Allocator memory_{"Test_Diagnostic_Assertion_2"}; +}; + +TEST_F(Test_Diagnostic_Assertion_2, match_error_type_with_1_field) { + Padded_String code(u8"hello"_sv); + + ::testing::Matcher continue_matcher = + diagnostics_matcher_2(&code, {u8"^^^^^ Diag_Invalid_Continue"_diag}); + { + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Continue{ + .continue_statement = Source_Code_Span(&code[0], &code[5]), + }); + EXPECT_TRUE(continue_matcher.Matches(diags)); + } + { + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Break{ + .break_statement = Source_Code_Span(&code[0], &code[5]), + }); + EXPECT_FALSE(continue_matcher.Matches(diags)); + } + + ::testing::Matcher break_matcher = + diagnostics_matcher_2(&code, {u8"^^^^^ Diag_Invalid_Break"_diag}); + { + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Continue{ + .continue_statement = Source_Code_Span(&code[0], &code[5]), + }); + EXPECT_FALSE(break_matcher.Matches(diags)); + } + { + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Break{ + .break_statement = Source_Code_Span(&code[0], &code[5]), + }); + EXPECT_TRUE(break_matcher.Matches(diags)); + } +} + +TEST_F(Test_Diagnostic_Assertion_2, match_error_type_with_1_field_message) { + Padded_String code(u8"hello"_sv); + ::testing::Matcher matcher = + diagnostics_matcher_2(&code, {u8"^^^^^ Diag_Invalid_Continue"_diag}); + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Break{ + .break_statement = Source_Code_Span(&code[0], &code[5]), + }); + EXPECT_EQ(get_matcher_message(matcher, diags), + "whose element #0 doesn't match, whose type (Diag_Invalid_Break) " + "isn't Diag_Invalid_Continue"); +} + +TEST_F(Test_Diagnostic_Assertion_2, match_offsets_of_1_field_span) { + Padded_String code(u8"hello"_sv); + + ::testing::Matcher continue_matcher = + diagnostics_matcher_2(&code, {u8" ^^^^ Diag_Invalid_Continue"_diag}); + { + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Continue{ + .continue_statement = Source_Code_Span(&code[1], &code[5]), + }); + EXPECT_TRUE(continue_matcher.Matches(diags)); + } + { + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Continue{ + .continue_statement = Source_Code_Span(&code[0], &code[5]), + }); + EXPECT_FALSE(continue_matcher.Matches(diags)); + } + { + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Continue{ + .continue_statement = Source_Code_Span(&code[0], &code[4]), + }); + EXPECT_FALSE(continue_matcher.Matches(diags)); + } +} + +TEST_F(Test_Diagnostic_Assertion_2, match_offsets_of_1_field_message) { + Padded_String code(u8"hello"_sv); + + { + ::testing::Matcher matcher = + diagnostics_matcher_2(&code, {u8"^^^^^ Diag_Invalid_Continue"_diag}); + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Continue{ + .continue_statement = Source_Code_Span(&code[1], &code[4]), + }); + EXPECT_EQ(get_matcher_message(matcher, diags), + "whose element #0 doesn't match, whose .continue_statement (1-4) " + "doesn't equal 0-5"); + } + + { + ::testing::Matcher matcher = + diagnostics_matcher_2(&code, {u8"^^^^^ Diag_Invalid_Break"_diag}); + Diag_List diags(&this->memory_); + diags.add(Diag_Invalid_Break{ + .break_statement = Source_Code_Span(&code[1], &code[4]), + }); + EXPECT_EQ(get_matcher_message(matcher, diags), + "whose element #0 doesn't match, whose .break_statement (1-4) " + "doesn't equal 0-5"); + } +} + +TEST_F(Test_Diagnostic_Assertion_2, match_span_and_char8) { + Padded_String code(u8"(hello"_sv); + + ::testing::Matcher matcher = diagnostics_matcher_2( + &code, + {u8"^ Diag_Expected_Parenthesis_Around_Do_While_Condition.where{.token=)}"_diag}); + { + Diag_List diags(&this->memory_); + diags.add(Diag_Expected_Parenthesis_Around_Do_While_Condition{ + .where = Source_Code_Span(&code[0], &code[1]), + .token = u8')', + }); + EXPECT_TRUE(matcher.Matches(diags)); + } + { + Diag_List diags(&this->memory_); + diags.add(Diag_Expected_Parenthesis_Around_Do_While_Condition{ + .where = Source_Code_Span(&code[0], &code[1]), + .token = u8'(', + }); + EXPECT_FALSE(matcher.Matches(diags)); + } +} + +TEST_F(Test_Diagnostic_Assertion_2, char8_message) { + Padded_String code(u8"hello"_sv); + + ::testing::Matcher matcher = diagnostics_matcher_2( + &code, + {u8"^ Diag_Expected_Parenthesis_Around_Do_While_Condition.where{.token=)}"_diag}); + + Diag_List diags(&this->memory_); + diags.add(Diag_Expected_Parenthesis_Around_Do_While_Condition{ + .where = Source_Code_Span(&code[0], &code[1]), + .token = u8'(', + }); + EXPECT_EQ(get_matcher_message(matcher, diags), + "whose element #0 doesn't match, whose .where (0-1) equals 0-1 and " + "whose .token ('(') doesn't equal ')'"); +} + +TEST_F(Test_Diagnostic_Assertion_2, match_span_and_string8_view) { + Padded_String code(u8"hi"_sv); + + ::testing::Matcher matcher = diagnostics_matcher_2( + &code, + {u8"^ Diag_Integer_Literal_Will_Lose_Precision.characters{.rounded_val=hello}"_diag}); + { + Diag_List diags(&this->memory_); + diags.add(Diag_Integer_Literal_Will_Lose_Precision{ + .characters = Source_Code_Span(&code[0], &code[1]), + .rounded_val = u8"hello"_sv, + }); + EXPECT_TRUE(matcher.Matches(diags)); + } + { + Diag_List diags(&this->memory_); + diags.add(Diag_Integer_Literal_Will_Lose_Precision{ + .characters = Source_Code_Span(&code[0], &code[1]), + .rounded_val = u8"HELLO"_sv, + }); + EXPECT_FALSE(matcher.Matches(diags)); + } +} + +TEST_F(Test_Diagnostic_Assertion_2, string8_view_message) { + Padded_String code(u8"hi"_sv); + + ::testing::Matcher matcher = diagnostics_matcher_2( + &code, + {u8"^ Diag_Integer_Literal_Will_Lose_Precision.characters{.rounded_val=hello}"_diag}); + + Diag_List diags(&this->memory_); + diags.add(Diag_Integer_Literal_Will_Lose_Precision{ + .characters = Source_Code_Span(&code[0], &code[1]), + .rounded_val = u8"HELLO"_sv, + }); + EXPECT_EQ( + get_matcher_message(matcher, diags), + "whose element #0 doesn't match, whose .characters (0-1) equals 0-1 and " + "whose .rounded_val (\"HELLO\") doesn't equal \"hello\""); +} + +TEST_F(Test_Diagnostic_Assertion_2, + multiple_diagnostics_are_matched_in_any_order) { + Padded_String code(u8"hello"_sv); + + ::testing::Matcher continue_break_matcher = + diagnostics_matcher_2(&code, {u8"^^^^^ Diag_Invalid_Continue"_diag, + u8"^^^^^ Diag_Invalid_Break"_diag}); + ::testing::Matcher break_continue_matcher = + diagnostics_matcher_2(&code, {u8"^^^^^ Diag_Invalid_Break"_diag, + u8"^^^^^ Diag_Invalid_Continue"_diag}); + + Diag_Invalid_Continue continue_diag{ + .continue_statement = Source_Code_Span(&code[0], &code[5]), + }; + Diag_Invalid_Break break_diag{ + .break_statement = Source_Code_Span(&code[0], &code[5]), + }; + + { + Diag_List diags(&this->memory_); + diags.add(break_diag); + diags.add(continue_diag); + EXPECT_TRUE(break_continue_matcher.Matches(diags)); + EXPECT_TRUE(continue_break_matcher.Matches(diags)); + } + + { + Diag_List diags(&this->memory_); + diags.add(continue_diag); + diags.add(break_diag); + EXPECT_TRUE(break_continue_matcher.Matches(diags)); + EXPECT_TRUE(continue_break_matcher.Matches(diags)); + } +} } }