blob: c12ff2b655cb5570b8e7a1dd263e391a7ede43e0 [file] [edit]
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// Text-format conformance tests for the separators of the syntax: the
// optional "," / ";" after a field, and the "," between the values of a list.
// This replaces the "Separators" block of the legacy
// TextFormatConformanceTestSuiteImpl<M>::RunAllTests(), which ran for the
// proto3-style message types only; the test names and the requests sent to
// the testee are identical to the legacy ones.
#include <ostream>
#include <string>
#include <tuple>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "conformance/binary_test_util.h"
#include "conformance/matchers.h"
#include "conformance/message_type_fixtures.h"
#include "conformance/test_fixture.h"
#include "conformance/testee.h"
#include "google/protobuf/descriptor.h"
namespace google {
namespace protobuf {
namespace conformance {
namespace {
using ::testing::Combine;
using ::testing::Values;
using ::testing::ValuesIn;
// A field type the separator tests run over: the legacy suite ran every
// separator test once per entry, with "_<type>" appended to the test name and
// `value` as the literal of the optional_<type> / repeated_<type> field.
struct SeparatorCase {
absl::string_view type;
absl::string_view value;
// Whether two adjacent values of the type are one value to the parser
// (string literals concatenate) rather than a missing list separator.
bool adjacent_values_concatenate;
};
constexpr SeparatorCase kSeparatorCases[] = {
{"string", "\"abc\"", true}, {"bytes", "\"abc\"", true},
{"int32", "123", false}, {"bool", "true", false},
{"double", "1.23", false}, {"fixed32", "0x123", false},
};
std::string ParamName(const SeparatorCase& test_case) {
// Qualified: the overloads in this namespace hide the outer ones.
return conformance::ParamName(test_case.type);
}
// For gtest's failure messages (the parameter is otherwise printed as bytes).
void PrintTo(const SeparatorCase& test_case, std::ostream* os) {
*os << test_case.type;
}
// Whether the field separator tests use the optional_<type> field with a
// bare value ("Single") or the repeated_<type> field with a one-element list
// ("Repeated"); the legacy test names carry the same two words.
enum class FieldCardinality { kSingle, kRepeated };
std::string ParamName(FieldCardinality cardinality) {
return cardinality == FieldCardinality::kSingle ? "Single" : "Repeated";
}
void PrintTo(FieldCardinality cardinality, std::ostream* os) {
*os << ParamName(cardinality);
}
// The separators after a top-level field, parameterized over (test message
// type, field type, single/repeated). Reporting the message type through
// MessageUnderTest() makes the base SetUp() skip the editions instance when
// --maximum_edition doesn't cover it. TestName() and Input() only build
// values; every expectation is in the test bodies.
class TextFieldSeparatorTest
: public ConformanceTest,
public testing::WithParamInterface<
std::tuple<const Descriptor*, SeparatorCase, FieldCardinality>> {
protected:
const Descriptor* message() const { return std::get<0>(GetParam()); }
const SeparatorCase& test_case() const { return std::get<1>(GetParam()); }
FieldCardinality cardinality() const { return std::get<2>(GetParam()); }
// The legacy test name, e.g. "FieldSeparatorCommaTopLevelSingle_int32".
std::string TestName(absl::string_view base) const {
return absl::StrCat(base, ParamName(cardinality()), "_", test_case().type);
}
// The field set to its value, followed by `separator`, e.g.
// "optional_int32: 123," or "repeated_int32: [123],".
std::string Input(absl::string_view separator) const {
const bool single = cardinality() == FieldCardinality::kSingle;
return absl::StrCat(single ? "optional_" : "repeated_", test_case().type,
": ", single ? "" : "[", test_case().value,
single ? "" : "]", separator);
}
private:
const Descriptor* MessageUnderTest() const override { return message(); }
};
TEST_P(TextFieldSeparatorTest, Comma) {
const std::string input = Input(",");
EXPECT_THAT(Testee(TestName("FieldSeparatorCommaTopLevel"))
.ParseText(message(), input)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(input))));
EXPECT_THAT(Testee(TestName("FieldSeparatorCommaTopLevel"))
.ParseText(message(), input)
.SerializeText(),
Yields(WhenParsed(EqualsTextProto(input))));
}
// Note: the legacy name has "Single" in it even for the repeated variant.
TEST_P(TextFieldSeparatorTest, Semi) {
const std::string input = Input(";");
EXPECT_THAT(Testee(TestName("FieldSeparatorSemiTopLevelSingle"))
.ParseText(message(), input)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(input))));
EXPECT_THAT(Testee(TestName("FieldSeparatorSemiTopLevelSingle"))
.ParseText(message(), input)
.SerializeText(),
Yields(WhenParsed(EqualsTextProto(input))));
}
TEST_P(TextFieldSeparatorTest, CommaDuplicatesFails) {
EXPECT_THAT(Testee(TestName("FieldSeparatorCommaTopLevelDuplicatesFails"))
.ParseText(message(), Input(",,"))
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(TextFieldSeparatorTest, SemiDuplicatesFails) {
EXPECT_THAT(Testee(TestName("FieldSeparatorSemiTopLevelDuplicateFails"))
.ParseText(message(), Input(";;"))
.ParseOnly(),
Yields(IsParseError()));
}
INSTANTIATE_TEST_SUITE_P(
All, TextFieldSeparatorTest,
Combine(ValuesIn(Proto3TestMessageTypes()), ValuesIn(kSeparatorCases),
Values(FieldCardinality::kSingle, FieldCardinality::kRepeated)),
TupleParamName<TextFieldSeparatorTest::ParamType>);
// The required "," between the values of a list, parameterized over (test
// message type, field type).
class TextListSeparatorTest
: public ConformanceTest,
public testing::WithParamInterface<
std::tuple<const Descriptor*, SeparatorCase>> {
protected:
const Descriptor* message() const { return std::get<0>(GetParam()); }
const SeparatorCase& test_case() const { return std::get<1>(GetParam()); }
// The legacy test name, e.g. "ListSeparator_int32".
std::string TestName(absl::string_view base) const {
return absl::StrCat(base, "_", test_case().type);
}
// The repeated field set to a list whose body is `list_body`, e.g.
// "repeated_int32: [123,123]".
std::string Input(absl::string_view list_body) const {
return absl::StrCat("repeated_", test_case().type, ": [", list_body, "]");
}
absl::string_view value() const { return test_case().value; }
private:
const Descriptor* MessageUnderTest() const override { return message(); }
};
TEST_P(TextListSeparatorTest, Comma) {
const std::string input = Input(absl::StrCat(value(), ",", value()));
EXPECT_THAT(Testee(TestName("ListSeparator"))
.ParseText(message(), input)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(input))));
EXPECT_THAT(Testee(TestName("ListSeparator"))
.ParseText(message(), input)
.SerializeText(),
Yields(WhenParsed(EqualsTextProto(input))));
}
TEST_P(TextListSeparatorTest, SemiFails) {
EXPECT_THAT(
Testee(TestName("ListSeparatorSemiFails"))
.ParseText(message(), Input(absl::StrCat(value(), ";", value())))
.ParseOnly(),
Yields(IsParseError()));
}
// For string and bytes, if we skip the separator, the parser will treat the
// two values as a single value.
TEST_P(TextListSeparatorTest, Missing) {
const std::string input = Input(absl::StrCat(value(), " ", value()));
if (test_case().adjacent_values_concatenate) {
EXPECT_THAT(Testee(TestName("ListSeparatorMissingIsOneValue"))
.ParseText(message(), input)
.SerializeBinary(),
Yields(WhenParsed(EqualsTextProto(input))));
EXPECT_THAT(Testee(TestName("ListSeparatorMissingIsOneValue"))
.ParseText(message(), input)
.SerializeText(),
Yields(WhenParsed(EqualsTextProto(input))));
} else {
EXPECT_THAT(Testee(TestName("ListSeparatorMissingFails"))
.ParseText(message(), input)
.ParseOnly(),
Yields(IsParseError()));
}
}
TEST_P(TextListSeparatorTest, DuplicateFails) {
EXPECT_THAT(
Testee(TestName("ListSeparatorDuplicateFails"))
.ParseText(message(), Input(absl::StrCat(value(), ",,", value())))
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(TextListSeparatorTest, SingleTrailingFails) {
EXPECT_THAT(Testee(TestName("ListSeparatorSingleTrailingFails"))
.ParseText(message(), Input(absl::StrCat(value(), ",")))
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(TextListSeparatorTest, TwoValuesTrailingFails) {
EXPECT_THAT(
Testee(TestName("ListSeparatorTwoValuesTrailingFails"))
.ParseText(message(), Input(absl::StrCat(value(), ",", value(), ",")))
.ParseOnly(),
Yields(IsParseError()));
}
INSTANTIATE_TEST_SUITE_P(All, TextListSeparatorTest,
Combine(ValuesIn(Proto3TestMessageTypes()),
ValuesIn(kSeparatorCases)),
TupleParamName<TextListSeparatorTest::ParamType>);
// The test messages don't have every type nested, so just check one data
// type for the nested field separator support.
using TextNestedSeparatorTest = MessageTypeConformanceTest;
TEST_P(TextNestedSeparatorTest, Comma) {
EXPECT_THAT(Testee("FieldSeparatorCommaNested")
.ParseText(message(), "optional_nested_message: { a: 123, }")
.SerializeBinary(),
Yields(WhenParsed(
EqualsTextProto("optional_nested_message: { a: 123, }"))));
EXPECT_THAT(Testee("FieldSeparatorCommaNested")
.ParseText(message(), "optional_nested_message: { a: 123, }")
.SerializeText(),
Yields(WhenParsed(
EqualsTextProto("optional_nested_message: { a: 123, }"))));
}
TEST_P(TextNestedSeparatorTest, Semi) {
EXPECT_THAT(Testee("FieldSeparatorSemiNested")
.ParseText(message(), "optional_nested_message: { a: 123; }")
.SerializeBinary(),
Yields(WhenParsed(
EqualsTextProto("optional_nested_message: { a: 123; }"))));
EXPECT_THAT(Testee("FieldSeparatorSemiNested")
.ParseText(message(), "optional_nested_message: { a: 123; }")
.SerializeText(),
Yields(WhenParsed(
EqualsTextProto("optional_nested_message: { a: 123; }"))));
}
TEST_P(TextNestedSeparatorTest, CommaDuplicates) {
EXPECT_THAT(Testee("FieldSeparatorCommaNestedDuplicates")
.ParseText(message(), "optional_nested_message: { a: 123,, }")
.ParseOnly(),
Yields(IsParseError()));
}
TEST_P(TextNestedSeparatorTest, SemiDuplicates) {
EXPECT_THAT(Testee("FieldSeparatorSemiNestedDuplicates")
.ParseText(message(), "optional_nested_message: { a: 123;; }")
.ParseOnly(),
Yields(IsParseError()));
}
INSTANTIATE_TEST_SUITE_P(All, TextNestedSeparatorTest,
ValuesIn(Proto3TestMessageTypes()),
MessageTypeParamName);
} // namespace
} // namespace conformance
} // namespace protobuf
} // namespace google