blob: a6ddc805aaaf8e19bddd2db9f5a6bf6c9c947f7c [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2023 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 <string>
#include <vector>
#include "google/protobuf/testing/file.h"
#include "google/protobuf/testing/file.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/container/flat_hash_map.h"
#include "absl/log/absl_check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/compiler/annotation_test_util.h"
#include "google/protobuf/compiler/code_generator_lite.h"
#include "google/protobuf/compiler/command_line_interface.h"
#include "hpb_generator/generator.h"
#include "google/protobuf/descriptor.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace hpb {
namespace atu = annotation_test_util;
namespace {
using ::testing::IsEmpty;
using Annotation = GeneratedCodeInfo::Annotation;
class HpbMetadataTest : public ::testing::Test {
public:
// Tries to capture a FileDescriptorProto, GeneratedCodeInfo, and output
// code from the previously added file with name `filename`. Returns true on
// success.
bool CaptureMetadata(const std::string& filename, FileDescriptorProto& file,
std::string& hpb_h, GeneratedCodeInfo& hpb_h_info) {
CommandLineInterface cli;
hpb_generator::Generator hpb_generator;
cli.RegisterGenerator("--hpb_out", &hpb_generator, "");
std::string hpb_out =
absl::StrCat("--hpb_out=annotate_headers=true:", ::testing::TempDir());
const bool result = atu::RunProtoCompiler(filename, hpb_out, &cli, &file);
if (!result) {
return result;
}
std::string output_base =
absl::StrCat(::testing::TempDir(), "/", StripProto(filename));
ABSL_CHECK_OK(File::GetContents(absl::StrCat(output_base, ".hpb.h"), &hpb_h,
true));
if (!atu::DecodeMetadata(absl::StrCat(output_base, ".hpb.h.meta"),
&hpb_h_info)) {
return false;
}
return true;
}
// Helper function to get annotations by the provided path and verify that
// these annotations contain a subset of the expected annotations (by
// substring) + semantics.
void ExpectAnnotationsForPathContain(
const GeneratedCodeInfo& info, const std::string& filename,
const std::string& hpb_h, const std::vector<int>& path,
const absl::flat_hash_map<std::string, Annotation::Semantic>&
expected_annotations) {
absl::flat_hash_map<std::string, Annotation::Semantic>
expected_annotations_copy = expected_annotations;
std::vector<const GeneratedCodeInfo::Annotation*> annotations;
atu::FindAnnotationsOnPath(info, filename, path, &annotations);
EXPECT_TRUE(!annotations.empty());
for (const auto* annotation : annotations) {
auto substring = atu::GetAnnotationSubstring(hpb_h, *annotation);
ASSERT_TRUE(substring.has_value());
if (auto node = expected_annotations_copy.extract(*substring);
!node.empty()) {
EXPECT_EQ(node.mapped(), annotation->semantic())
<< "for substring " << *substring;
}
}
EXPECT_THAT(expected_annotations_copy, IsEmpty())
<< "substrings above were not found in the annotations.";
}
};
constexpr absl::string_view kSmallTestFile = R"schema(syntax = "proto2";
package foo;
message Message { }
)schema";
TEST_F(HpbMetadataTest, CapturesMessageNames) {
FileDescriptorProto file;
GeneratedCodeInfo info;
std::string hpb_h;
atu::AddFile("test.proto", kSmallTestFile);
EXPECT_TRUE(CaptureMetadata("test.proto", file, hpb_h, info));
EXPECT_EQ("Message", file.message_type(0).name());
std::vector<int> message_path;
message_path.push_back(FileDescriptorProto::kMessageTypeFieldNumber);
message_path.push_back(0);
const GeneratedCodeInfo::Annotation* message_annotation =
atu::FindAnnotationOnPath(info, "test.proto", message_path);
EXPECT_TRUE(nullptr != message_annotation);
EXPECT_TRUE(
atu::AnnotationMatchesSubstring(hpb_h, message_annotation, "Message"));
}
constexpr absl::string_view kStringFieldTestFile = R"schema(
syntax = "proto2";
package foo;
message Message {
optional string sfield = 1;
repeated string rsfield = 2;
}
)schema";
TEST_F(HpbMetadataTest, AnnotatesStringSemantics) {
FileDescriptorProto file;
GeneratedCodeInfo info;
std::string hpb_h;
atu::AddFile("test.proto", kStringFieldTestFile);
EXPECT_TRUE(CaptureMetadata("test.proto", file, hpb_h, info));
EXPECT_EQ("Message", file.message_type(0).name());
// Check annotations for `sfield`.
std::vector<int> field_path{
FileDescriptorProto::kMessageTypeFieldNumber,
0,
DescriptorProto::kFieldFieldNumber,
0,
};
ExpectAnnotationsForPathContain(info, "test.proto", hpb_h, field_path,
{
{"sfield", Annotation::NONE},
{"set_sfield", Annotation::SET},
{"has_sfield", Annotation::NONE},
{"clear_sfield", Annotation::NONE},
});
// Check annotations for `rsfield`.
field_path = {
FileDescriptorProto::kMessageTypeFieldNumber,
0,
DescriptorProto::kFieldFieldNumber,
1,
};
ExpectAnnotationsForPathContain(info, "test.proto", hpb_h, field_path,
{
{"rsfield", Annotation::NONE},
{"add_rsfield", Annotation::SET},
{"mutable_rsfield", Annotation::ALIAS},
{"rsfield_size", Annotation::NONE},
});
}
} // namespace
} // namespace hpb
} // namespace compiler
} // namespace protobuf
} // namespace google