Skip to content

Commit

Permalink
refactor(test): allow assert_diagnostics to check Diag_List
Browse files Browse the repository at this point in the history
assert_diagnostic only supports std::vector<Diag_Collector::Diag>. 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.
  • Loading branch information
strager committed Jan 4, 2024
1 parent 8516310 commit 2343088
Show file tree
Hide file tree
Showing 6 changed files with 435 additions and 98 deletions.
49 changes: 32 additions & 17 deletions test/quick-lint-js/diag-matcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ Variable_Kind Diag_Matcher_Arg::get_variable_kind(

template <class State, class Field>
class Diag_Fields_Matcher_Impl_Base
: public testing::MatcherInterface<const Diag_Collector::Diag &> {
: public testing::MatcherInterface<const Diag_Collector::Diag &>,
public testing::MatcherInterface<const Any_Diag_Pointer &> {
public:
explicit Diag_Fields_Matcher_Impl_Base(State s) : state_(std::move(s)) {}

Expand All @@ -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;
}
Expand All @@ -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_;
Expand Down Expand Up @@ -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<CLI_Source_Position::Offset_Type>(
span.begin() - this->state_.input->data());
auto span_end_offset = narrow_cast<CLI_Source_Position::Offset_Type>(
Expand Down Expand Up @@ -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<CLI_Source_Position::Offset_Type>(
span.begin() - this->state_.input.data());
auto span_end_offset = narrow_cast<CLI_Source_Position::Offset_Type>(
Expand All @@ -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<char>(character) << "') "
Expand All @@ -278,15 +289,15 @@ 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;
return matches;
}

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) << "\") "
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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 Diag_Collector::Diag &>()
const {
return testing::Matcher<const Diag_Collector::Diag &>(new Impl(this->state_));
}

Diag_Matcher_2::operator testing::Matcher<const Any_Diag_Pointer &>() const {
return testing::Matcher<const Any_Diag_Pointer &>(new Impl(this->state_));
}

Diag_Spans_Matcher::Diag_Spans_Matcher(Diag_Type type, Field field_0)
: state_{type, {field_0}} {}

Expand All @@ -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()) << "` @"
Expand Down
6 changes: 6 additions & 0 deletions test/quick-lint-js/diag-matcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -242,6 +247,7 @@ class Diag_Matcher_2 {
Diag_Matcher_2 &operator=(Diag_Matcher_2 &&) = default;

/*implicit*/ operator testing::Matcher<const Diag_Collector::Diag &>() const;
/*implicit*/ operator testing::Matcher<const Any_Diag_Pointer &>() const;

private:
class Impl;
Expand Down
116 changes: 116 additions & 0 deletions test/quick-lint-js/diagnostic-assertion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <quick-lint-js/container/hash-map.h>
#include <quick-lint-js/container/string-view.h>
#include <quick-lint-js/diag-matcher.h>
#include <quick-lint-js/diag/diag-list.h>
#include <quick-lint-js/diag/diagnostic-types.h>
#include <quick-lint-js/diag/diagnostic.h>
#include <quick-lint-js/diagnostic-assertion.h>
Expand Down Expand Up @@ -463,6 +464,20 @@ void assert_diagnostics(Padded_String_View code,
Span<const Diagnostic_Assertion>(assertions), caller);
}

void assert_diagnostics(Padded_String_View code, const Diag_List& diagnostics,
Span<const Diagnostic_Assertion> 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<Diagnostic_Assertion> assertions,
Source_Location caller) {
assert_diagnostics(code, diagnostics,
Span<const Diagnostic_Assertion>(assertions), caller);
}

// TODO(#1154): Delete in favor of diagnostics_matcher_2.
::testing::Matcher<const std::vector<Diag_Collector::Diag>&>
diagnostics_matcher(Padded_String_View code,
Span<const Diagnostic_Assertion> assertions) {
Expand Down Expand Up @@ -527,6 +542,107 @@ diagnostics_matcher(Padded_String_View code,
Span<const Diagnostic_Assertion>(assertions));
}

namespace {
class Diag_List_Matcher_Impl
: public testing::MatcherInterface<const Diag_List&> {
public:
explicit Diag_List_Matcher_Impl(std::vector<Diag_Matcher_2>&& 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<Any_Diag_Pointer> 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<const std::vector<Any_Diag_Pointer>&>;
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<Diag_Matcher_2> error_matchers_;
};
}

::testing::Matcher<const Diag_List&> diagnostics_matcher_2(
Padded_String_View code, Span<const Diagnostic_Assertion> assertions) {
std::vector<Diag_Matcher_2> error_matchers;
for (const Diagnostic_Assertion& diag : assertions) {
Diagnostic_Assertion adjusted_diag =
diag.adjusted_for_escaped_characters(code.string_view());

std::vector<Diag_Matcher_2::Field> 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<CLI_Source_Position::Offset_Type>(
member.span_begin_offset);
field.end_offset = narrow_cast<CLI_Source_Position::Offset_Type>(
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<const Diag_List&>(
new Diag_List_Matcher_Impl(std::move(error_matchers)));
}

::testing::Matcher<const Diag_List&> diagnostics_matcher_2(
Padded_String_View code,
std::initializer_list<Diagnostic_Assertion> assertions) {
return diagnostics_matcher_2(code,
Span<const Diagnostic_Assertion>(assertions));
}

namespace {
std::optional<Enum_Kind> try_parse_enum_kind(String8_View s) {
#define QLJS_CASE(kind) \
Expand Down
13 changes: 13 additions & 0 deletions test/quick-lint-js/diagnostic-assertion.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,25 @@ void assert_diagnostics(Padded_String_View code,
std::initializer_list<Diagnostic_Assertion> assertions,
Source_Location caller = Source_Location::current());

void assert_diagnostics(Padded_String_View code, const Diag_List& diagnostics,
Span<const Diagnostic_Assertion> assertions,
Source_Location caller);
void assert_diagnostics(Padded_String_View code, const Diag_List& diagnostics,
std::initializer_list<Diagnostic_Assertion> assertions,
Source_Location caller = Source_Location::current());

::testing::Matcher<const std::vector<Diag_Collector::Diag>&>
diagnostics_matcher(Padded_String_View code,
Span<const Diagnostic_Assertion> assertions);
::testing::Matcher<const std::vector<Diag_Collector::Diag>&>
diagnostics_matcher(Padded_String_View code,
std::initializer_list<Diagnostic_Assertion> assertions);

::testing::Matcher<const Diag_List&> diagnostics_matcher_2(
Padded_String_View code, Span<const Diagnostic_Assertion> assertions);
::testing::Matcher<const Diag_List&> diagnostics_matcher_2(
Padded_String_View code,
std::initializer_list<Diagnostic_Assertion> assertions);
}

// quick-lint-js finds bugs in JavaScript programs.
Expand Down
Loading

0 comments on commit 2343088

Please sign in to comment.