blob: 18b85673c8fac2d966fa16dd93981e7713871bef [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2025 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
#include "upb/conformance/conformance_upb_harness.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "conformance/binary_wireformat.h"
#include "conformance/conformance.upb.h"
#include "upb/base/string_view.h"
#include "upb/mem/arena.h"
#include "upb/mem/arena.hpp"
namespace {
using ::google::protobuf::conformance::LengthPrefixedField;
using ::google::protobuf::conformance::Tag;
using ::google::protobuf::conformance::Varint;
using ::google::protobuf::conformance::VarintField;
using ::google::protobuf::conformance::Wire;
using ::google::protobuf::conformance::WireType;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
constexpr char kProto3[] = "protobuf_test_messages.proto3.TestAllTypesProto3";
std::string ToString(upb_StringView view) {
return std::string(view.data, view.size);
}
upb_StringView FromString(const std::string& s) {
return upb_StringView_FromDataAndSize(s.data(), s.size());
}
// `wire` copied into `arena`. The upb setters keep a view of what they
// are given, so payloads built inline in a setter call have to live
// somewhere that outlives the request.
upb_StringView InArena(upb::Arena& arena, const Wire& wire) {
char* copy = static_cast<char*>(upb_Arena_Malloc(arena.ptr(), wire.size()));
memcpy(copy, wire.data().data(), wire.size());
return upb_StringView_FromDataAndSize(copy, wire.size());
}
// The parameter is `rebuild_minitables`, so every case runs against both the
// generated-minitable and the dynamic-minitable testee.
class ConformanceUpbHarnessTest : public ::testing::TestWithParam<bool> {
protected:
ConformanceUpbHarnessTest()
: harness_(
upb_ConformanceHarness_New(/*rebuild_minitables=*/GetParam())) {}
~ConformanceUpbHarnessTest() override {
upb_ConformanceHarness_Free(harness_);
}
// A request for TestAllTypesProto3 with no payload; tests add one.
conformance_ConformanceRequest* NewRequest(upb::Arena& arena,
int output_format) {
conformance_ConformanceRequest* request =
conformance_ConformanceRequest_new(arena.ptr());
conformance_ConformanceRequest_set_message_type(
request, upb_StringView_FromString(kProto3));
conformance_ConformanceRequest_set_requested_output_format(request,
output_format);
return request;
}
// The harness's response to `request`, decoded into `arena`; null if the
// request doesn't serialize or the response bytes aren't a
// ConformanceResponse (each test asserts on it).
const conformance_ConformanceResponse* Run(
const conformance_ConformanceRequest* request, upb::Arena& arena) {
size_t size;
char* bytes =
conformance_ConformanceRequest_serialize(request, arena.ptr(), &size);
if (bytes == nullptr) return nullptr;
return RunBytes(std::string(bytes, size), arena);
}
// The same for raw request bytes, which need not be a ConformanceRequest.
const conformance_ConformanceResponse* RunBytes(
const std::string& serialized_request, upb::Arena& arena) {
// The request only has to live for the call: the response is parsed into
// the caller's arena from the harness's output bytes with options = 0,
// which copies strings rather than aliasing them.
upb::Arena run_arena;
upb_StringView bytes = upb_ConformanceHarness_Run(
harness_, FromString(serialized_request), run_arena.ptr());
return conformance_ConformanceResponse_parse(bytes.data, bytes.size,
arena.ptr());
}
upb_ConformanceHarness* harness_;
};
TEST_P(ConformanceUpbHarnessTest, ProtobufRoundTrip) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
conformance_ConformanceRequest_set_protobuf_payload(
request, InArena(arena, VarintField(1, 99)));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_protobuf_payload(response));
EXPECT_EQ(
ToString(conformance_ConformanceResponse_protobuf_payload(response)),
VarintField(1, 99).data());
}
TEST_P(ConformanceUpbHarnessTest, TextFormatOutputHidesUnknownFieldsByDefault) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_TEXT_FORMAT);
conformance_ConformanceRequest_set_protobuf_payload(
request, InArena(arena, Wire(VarintField(1, 99), VarintField(9999, 1))));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_text_payload(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_text_payload(response)),
"optional_int32: 99\n");
}
TEST_P(ConformanceUpbHarnessTest,
TextFormatOutputPrintsUnknownFieldsOnRequest) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_TEXT_FORMAT);
conformance_ConformanceRequest_set_protobuf_payload(
request, InArena(arena, Wire(VarintField(1, 99), VarintField(9999, 1))));
conformance_ConformanceRequest_set_print_unknown_fields(request, true);
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_text_payload(response));
EXPECT_THAT(ToString(conformance_ConformanceResponse_text_payload(response)),
HasSubstr("9999: 1"));
}
TEST_P(ConformanceUpbHarnessTest, JsonOutput) {
upb::Arena arena;
conformance_ConformanceRequest* request = NewRequest(arena, conformance_JSON);
conformance_ConformanceRequest_set_protobuf_payload(
request, InArena(arena, VarintField(1, 99)));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_json_payload(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_json_payload(response)),
R"({"optionalInt32":99})");
}
TEST_P(ConformanceUpbHarnessTest, JsonInputRoundTrip) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
conformance_ConformanceRequest_set_json_payload(
request, upb_StringView_FromString(R"({"optionalInt32":99})"));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_protobuf_payload(response));
EXPECT_EQ(
ToString(conformance_ConformanceResponse_protobuf_payload(response)),
VarintField(1, 99).data());
}
TEST_P(ConformanceUpbHarnessTest, JsonUnknownFieldIsParseErrorByDefault) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
conformance_ConformanceRequest_set_json_payload(
request, upb_StringView_FromString(R"({"noSuchField":1})"));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_parse_error(response));
EXPECT_THAT(ToString(conformance_ConformanceResponse_parse_error(response)),
HasSubstr("noSuchField"));
}
TEST_P(ConformanceUpbHarnessTest,
JsonUnknownFieldIsIgnoredInIgnoreUnknownTest) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
conformance_ConformanceRequest_set_json_payload(
request, upb_StringView_FromString(R"({"noSuchField":1})"));
conformance_ConformanceRequest_set_test_category(
request, conformance_JSON_IGNORE_UNKNOWN_PARSING_TEST);
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_protobuf_payload(response));
EXPECT_THAT(
ToString(conformance_ConformanceResponse_protobuf_payload(response)),
IsEmpty());
}
TEST_P(ConformanceUpbHarnessTest, MalformedProtobufIsParseError) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
// A varint tag with no value.
conformance_ConformanceRequest_set_protobuf_payload(
request, InArena(arena, Tag(1, WireType::kVarint)));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_parse_error(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_parse_error(response)),
"Parse error");
}
TEST_P(ConformanceUpbHarnessTest, UnknownMessageTypeIsSkipped) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
conformance_ConformanceRequest_set_message_type(
request, upb_StringView_FromString("no.such.Message"));
conformance_ConformanceRequest_set_protobuf_payload(
request, InArena(arena, VarintField(1, 99)));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_skipped(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_skipped(response)),
"Unknown message type.");
}
TEST_P(ConformanceUpbHarnessTest, TextInputIsSkipped) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
conformance_ConformanceRequest_set_text_payload(
request, upb_StringView_FromString("optional_int32: 99"));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_skipped(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_skipped(response)),
"Unsupported input format.");
}
TEST_P(ConformanceUpbHarnessTest, UnspecifiedOutputFormatIsRuntimeError) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_UNSPECIFIED);
conformance_ConformanceRequest_set_protobuf_payload(
request, InArena(arena, VarintField(1, 99)));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_runtime_error(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_runtime_error(response)),
"conformance_upb: Unspecified output format.");
}
TEST_P(ConformanceUpbHarnessTest, MissingPayloadIsRuntimeError) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_runtime_error(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_runtime_error(response)),
"conformance_upb: Request didn't have payload.");
}
TEST_P(ConformanceUpbHarnessTest, UnparseableRequestIsRuntimeError) {
upb::Arena arena;
// A length-delimited field claiming more bytes than there are.
const conformance_ConformanceResponse* response = RunBytes(
Wire(Tag(1, WireType::kLengthPrefixed), Varint(127)).str(), arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_runtime_error(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_runtime_error(response)),
"conformance_upb: parse of ConformanceRequest failed.");
}
TEST_P(ConformanceUpbHarnessTest, ProtobufOutputAtDepthLimitRoundTrips) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
// recursive_message (27) nested 100 deep: exactly upb's default depth limit.
// The decoder accepts it, so the encoder must be able to serialize it again.
Wire nested;
for (int i = 0; i < 100; ++i) {
nested = LengthPrefixedField(27, nested);
}
conformance_ConformanceRequest_set_protobuf_payload(request,
InArena(arena, nested));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_protobuf_payload(response));
EXPECT_EQ(
ToString(conformance_ConformanceResponse_protobuf_payload(response)),
nested.data());
}
TEST_P(ConformanceUpbHarnessTest, ProtobufOutputSerializeErrorIsReported) {
upb::Arena arena;
conformance_ConformanceRequest* request =
NewRequest(arena, conformance_PROTOBUF);
// optional_value with 60 nested JSON arrays. The JSON parser limits JSON
// nesting (64 levels), so this parses. But each nested array is a Value
// holding a ListValue, i.e. two levels of message nesting, so the message is
// 120 levels deep and exceeds the wire encoder's limit (100).
constexpr int kNestedArrays = 60;
std::string json = R"({"optionalValue":)" + std::string(kNestedArrays, '[') +
std::string(kNestedArrays, ']') + "}";
conformance_ConformanceRequest_set_json_payload(request, FromString(json));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_serialize_error(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_serialize_error(response)),
"Error serializing.");
}
TEST_P(ConformanceUpbHarnessTest, JsonOutputSerializeErrorIsReported) {
upb::Arena arena;
conformance_ConformanceRequest* request = NewRequest(arena, conformance_JSON);
// optional_timestamp (302) one second past 9999-12-31T23:59:59Z, which
// parses from the wire but has no JSON representation.
conformance_ConformanceRequest_set_protobuf_payload(
request,
InArena(arena, LengthPrefixedField(
302, VarintField(1, int64_t{253402300799} + 1))));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_serialize_error(response));
EXPECT_THAT(
ToString(conformance_ConformanceResponse_serialize_error(response)),
HasSubstr("maximum acceptable value is 9999-12-31T23:59:59Z"));
}
TEST_P(ConformanceUpbHarnessTest, UnknownOutputFormatIsSkipped) {
upb::Arena arena;
// An output format this harness doesn't know (a future WireFormat value).
conformance_ConformanceRequest* request = NewRequest(arena, 99);
conformance_ConformanceRequest_set_protobuf_payload(
request, InArena(arena, VarintField(1, 99)));
const conformance_ConformanceResponse* response = Run(request, arena);
ASSERT_NE(response, nullptr);
ASSERT_TRUE(conformance_ConformanceResponse_has_skipped(response));
EXPECT_EQ(ToString(conformance_ConformanceResponse_skipped(response)),
"Unsupported output format.");
}
INSTANTIATE_TEST_SUITE_P(GeneratedAndDynamicMinitables,
ConformanceUpbHarnessTest, ::testing::Bool(),
[](const ::testing::TestParamInfo<bool>& info) {
return info.param ? "DynamicMinitables"
: "GeneratedMinitables";
});
} // namespace