Wildcard support for Protobuf conformance failure lists by using a Trie made up of nodes that contain a section of a test name (divided by '.').
If there's an unexpected failure message or an unexpected succeeding test from a wildcard expansion users will be made to remove the wildcarded equivalent. Once removed, they must rerun the conformance test to add the failures contained within the removed wildcarded equivalent. PiperOrigin-RevId: 663040062pull/17738/head
parent
a60097abfc
commit
3f908cfff7
11 changed files with 481 additions and 188 deletions
@ -0,0 +1,87 @@ |
||||
#include "failure_list_trie_node.h" |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/match.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_format.h" |
||||
#include "absl/strings/str_split.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
namespace google { |
||||
namespace protobuf { |
||||
|
||||
absl::Status FailureListTrieNode::Insert(absl::string_view test_name) { |
||||
if (auto result = WalkDownMatch(test_name); result.has_value()) { |
||||
return absl::AlreadyExistsError( |
||||
absl::StrFormat("Test name %s already exists in the trie FROM %s", |
||||
test_name, result.value())); |
||||
} |
||||
|
||||
auto sections = absl::StrSplit(test_name, '.'); |
||||
for (auto section : sections) { |
||||
if (absl::StrContains(section, '*') && section.length() > 1) { |
||||
return absl::InvalidArgumentError(absl::StrFormat( |
||||
"Test name %s contains invalid wildcard(s) (wildcards " |
||||
"must span the whole of a section)", |
||||
test_name)); |
||||
} |
||||
} |
||||
InsertImpl(test_name); |
||||
return absl::OkStatus(); |
||||
} |
||||
|
||||
void FailureListTrieNode::InsertImpl(absl::string_view test_name) { |
||||
absl::string_view section = test_name.substr(0, test_name.find('.')); |
||||
|
||||
// Extracted last section -> no more '.' -> test_name_copy will be equal to
|
||||
// section
|
||||
if (test_name == section) { |
||||
children_.push_back(std::make_unique<FailureListTrieNode>(section)); |
||||
return; |
||||
} |
||||
test_name = test_name.substr(section.length() + 1); |
||||
for (auto& child : children_) { |
||||
if (child->data_ == section) { |
||||
return child->InsertImpl(test_name); |
||||
} |
||||
} |
||||
// No match
|
||||
children_.push_back(std::make_unique<FailureListTrieNode>(section)); |
||||
children_.back()->InsertImpl(test_name); |
||||
} |
||||
|
||||
absl::optional<std::string> FailureListTrieNode::WalkDownMatch( |
||||
absl::string_view test_name) { |
||||
absl::string_view section = test_name.substr(0, test_name.find('.')); |
||||
// test_name cannot be overridden
|
||||
absl::string_view to_match; |
||||
if (section != test_name) { |
||||
to_match = test_name.substr(section.length() + 1); |
||||
} |
||||
|
||||
for (auto& child : children_) { |
||||
if (child->data_ == section || child->data_ == "*" || section == "*") { |
||||
absl::string_view appended = child->data_; |
||||
// Extracted last section -> no more '.' -> test_name will be
|
||||
// equal to section
|
||||
if (test_name == section) { |
||||
// Must match all the way to the bottom of the tree
|
||||
if (child->children_.empty()) { |
||||
return std::string(appended); |
||||
} |
||||
} else { |
||||
if (auto result = child->WalkDownMatch(to_match); result.has_value()) { |
||||
return absl::StrCat(appended, ".", result.value()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// No match
|
||||
return absl::nullopt; |
||||
} |
||||
} // namespace protobuf
|
||||
} // namespace google
|
@ -0,0 +1,58 @@ |
||||
#ifndef GOOGLE_PROTOBUF_CONFORMANCE_FAILURE_LIST_TRIE_NODE_H__ |
||||
#define GOOGLE_PROTOBUF_CONFORMANCE_FAILURE_LIST_TRIE_NODE_H__ |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
namespace google { |
||||
namespace protobuf { |
||||
|
||||
// Each node represents a section of a test name (divided by '.'). One can
|
||||
// imagine them as prefixes to search for a match. Once we hit a prefix that
|
||||
// doesn't match, we can stop searching. Wildcards matching to any set of
|
||||
// characters are supported.
|
||||
//
|
||||
// This is not a general trie implementation
|
||||
// as pointed out by its name. It is only meant to only be used for conformance
|
||||
// failure lists.
|
||||
//
|
||||
// Example of what the trie might look like in practice:
|
||||
//
|
||||
// (root)
|
||||
// / \
|
||||
// "Recommended" "Required"
|
||||
// / \
|
||||
// "Proto2" "*"
|
||||
// / \ \
|
||||
// "JsonInput" "ProtobufInput" "JsonInput"
|
||||
//
|
||||
//
|
||||
class FailureListTrieNode { |
||||
public: |
||||
FailureListTrieNode() : data_("") {} |
||||
explicit FailureListTrieNode(absl::string_view data) : data_(data) {} |
||||
|
||||
// Will attempt to insert a test name into the trie returning
|
||||
// absl::StatusCode::kAlreadyExists if the test name already exists or
|
||||
// absl::StatusCode::kInvalidArgument if the test name contains invalid
|
||||
// wildcards; otherwise, insertion is successful.
|
||||
absl::Status Insert(absl::string_view test_name); |
||||
|
||||
// Returns what it matched to if it matched anything, otherwise returns
|
||||
// absl::nullopt
|
||||
absl::optional<std::string> WalkDownMatch(absl::string_view test_name); |
||||
|
||||
private: |
||||
absl::string_view data_; |
||||
std::vector<std::unique_ptr<FailureListTrieNode>> children_; |
||||
void InsertImpl(absl::string_view test_name); |
||||
}; |
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
#endif // GOOGLE_PROTOBUF_CONFORMANCE_FAILURE_LIST_TRIE_NODE_H__
|
@ -0,0 +1,125 @@ |
||||
#include "failure_list_trie_node.h" |
||||
|
||||
#include <memory> |
||||
|
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/types/optional.h" |
||||
|
||||
using ::testing::Eq; |
||||
using ::testing::HasSubstr; |
||||
using ::testing::Optional; |
||||
|
||||
absl::Status GetStatus(const absl::Status& s) { return s; } |
||||
template <typename T> |
||||
absl::Status GetStatus(const absl::StatusOr<T>& s) { |
||||
return s.status(); |
||||
} |
||||
MATCHER_P2(StatusIs, status, message, |
||||
absl::StrCat(".status() is ", testing::PrintToString(status))) { |
||||
return GetStatus(arg).code() == status && |
||||
testing::ExplainMatchResult(message, GetStatus(arg).message(), |
||||
result_listener); |
||||
} |
||||
#define EXPECT_OK(x) EXPECT_THAT(x, StatusIs(absl::StatusCode::kOk, testing::_)) |
||||
#define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk, testing::_)) |
||||
|
||||
namespace google { |
||||
namespace protobuf { |
||||
|
||||
TEST(FailureListTrieTest, WalkDownMatchWithoutWildcard) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
ASSERT_OK(root_->Insert("Recommended.Proto2.ProtobufInput.World")); |
||||
|
||||
EXPECT_THAT(root_->WalkDownMatch("Recommended.Proto2.ProtobufInput.World"), |
||||
Optional(Eq("Recommended.Proto2.ProtobufInput.World"))); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, WalkDownMatchWithoutWildcardNoMatch) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
|
||||
ASSERT_OK(root_->Insert("Recommended.Proto2.JsonInput.World")); |
||||
|
||||
EXPECT_EQ(root_->WalkDownMatch("Recommended.Proto2.TextFormatInput"), |
||||
absl::nullopt); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, WalkDownMatchWithWildcard) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
ASSERT_OK(root_->Insert("Recommended.*.ProtobufInput.World")); |
||||
|
||||
EXPECT_THAT(root_->WalkDownMatch("Recommended.Proto2.ProtobufInput.World"), |
||||
Optional(Eq("Recommended.*.ProtobufInput.World"))); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, WalkDownMatchWithWildcardNoMatch) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
ASSERT_OK(root_->Insert("Recommended.*.ProtobufInput.World")); |
||||
|
||||
EXPECT_EQ(root_->WalkDownMatch("Recommended.Proto2.JsonInput.World"), |
||||
absl::nullopt); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, WalkDownMatchTestLessNumberofSectionsNoMatch) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
ASSERT_OK(root_->Insert("Recommended.*.*.*")); |
||||
|
||||
EXPECT_EQ(root_->WalkDownMatch("Recommended.Proto2.JsonInput"), |
||||
absl::nullopt); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, WalkDownMatchTestMoreNumberOfSectionsNoMatch) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
ASSERT_OK(root_->Insert("*")); |
||||
|
||||
EXPECT_EQ(root_->WalkDownMatch("Recommended.Proto2.JsonInput.World"), |
||||
absl::nullopt); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, WalkDownMatchTakeMoreThanOneBranch) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
ASSERT_OK(root_->Insert( |
||||
"Recommended.*.JsonInput.TrailingCommaInAnObjectWithSpaceCommaSpace")); |
||||
ASSERT_OK(root_->Insert( |
||||
"Recommended.Proto3.*.RepeatedFieldTrailingCommaWithSpaceCommaSpace")); |
||||
|
||||
EXPECT_THAT( |
||||
root_->WalkDownMatch("Recommended.Proto3.JsonInput." |
||||
"RepeatedFieldTrailingCommaWithSpaceCommaSpace"), |
||||
Optional(Eq("Recommended.Proto3.*." |
||||
"RepeatedFieldTrailingCommaWithSpaceCommaSpace"))); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, InsertWilcardedAmbiguousMatchFails) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
ASSERT_OK(root_->Insert( |
||||
"Recommended.*.JsonInput.TrailingCommaInAnObjectWithSpaceCommaSpace")); |
||||
|
||||
// Essentially a duplicated test name if inserted.
|
||||
EXPECT_THAT( |
||||
root_->Insert( |
||||
"Recommended.Proto3.*.TrailingCommaInAnObjectWithSpaceCommaSpace"), |
||||
StatusIs(absl::StatusCode::kAlreadyExists, HasSubstr("already exists"))); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, InsertWilcardedAmbiguousMatchMutlipleWildcardsFails) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
ASSERT_OK(root_->Insert("Recommended.*.JsonInput.FieldMaskInvalidCharacter")); |
||||
|
||||
// Essentially a duplicated test name if inserted.
|
||||
EXPECT_THAT( |
||||
root_->Insert("Recommended.*.*.*"), |
||||
StatusIs(absl::StatusCode::kAlreadyExists, HasSubstr("already exists"))); |
||||
} |
||||
|
||||
TEST(FailureListTrieTest, InsertInvalidWildcardFails) { |
||||
auto root_ = std::make_unique<google::protobuf::FailureListTrieNode>("dummy"); |
||||
EXPECT_THAT(root_->Insert("This*Is.Not.A.Valid.Wildcard"), |
||||
StatusIs(absl::StatusCode::kInvalidArgument, |
||||
HasSubstr("invalid wildcard"))); |
||||
} |
||||
} // namespace protobuf
|
||||
} // namespace google
|
@ -1,42 +1,22 @@ |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes # Should have failed to parse, but didn't. |
||||
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes # Should have failed to parse, but didn't. |
||||
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString # Should have failed to parse, but didn't. |
||||
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Hex # Should have failed to parse, but didn't. |
||||
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Octal # Should have failed to parse, but didn't. |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeBytes # Should have failed to parse, but didn't. |
||||
Required.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeString # Should have failed to parse, but didn't. |
||||
Required.Proto3.TextFormatInput.StringFieldBadUTF8Hex # Should have failed to parse, but didn't. |
||||
Required.Proto3.TextFormatInput.StringFieldBadUTF8Octal # Should have failed to parse, but didn't. |
||||
Required.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeBytes # Should have failed to parse, but didn't. |
||||
Required.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeString # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes # Should have failed to parse, but didn't. |
||||
Recommended.*.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString # Should have failed to parse, but didn't. |
||||
Required.*.TextFormatInput.StringFieldBadUTF8Hex # Should have failed to parse, but didn't. |
||||
Required.*.TextFormatInput.StringFieldBadUTF8Octal # Should have failed to parse, but didn't. |
||||
Required.*.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeBytes # Should have failed to parse, but didn't. |
||||
Required.*.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeString # Should have failed to parse, but didn't. |
||||
|
||||
# End up setting the high bit as a sign instead of failing to parse. |
||||
|
Loading…
Reference in new issue