// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  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

// Author: kenton@google.com (Kenton Varda)
//  Based on original Protocol Buffers design by
//  Sanjay Ghemawat, Jeff Dean, and others.
//
// This file makes extensive use of RFC 3092.  :)

#include "google/protobuf/descriptor_database.h"

#include <algorithm>
#include <memory>

#include "google/protobuf/descriptor.pb.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "google/protobuf/descriptor.h"
#include "google/protobuf/test_textproto.h"
#include "google/protobuf/text_format.h"


namespace google {
namespace protobuf {
namespace {

static void AddToDatabase(SimpleDescriptorDatabase* database,
                          const char* file_text) {
  FileDescriptorProto file_proto;
  EXPECT_TRUE(TextFormat::ParseFromString(file_text, &file_proto));
  database->Add(file_proto);
}

static void ExpectContainsType(const FileDescriptorProto& proto,
                               const std::string& type_name) {
  for (int i = 0; i < proto.message_type_size(); i++) {
    if (proto.message_type(i).name() == type_name) return;
  }
  ADD_FAILURE() << "\"" << proto.name() << "\" did not contain expected type \""
                << type_name << "\".";
}

// ===================================================================

// SimpleDescriptorDatabase, EncodedDescriptorDatabase, and
// DescriptorPoolDatabase call for very similar tests.  Instead of writing
// three nearly-identical sets of tests, we use parameterized tests to apply
// the same code to all three.

// The parameterized test runs against a DescriptorDatabaseTestCase.  We have
// implementations for each of the three classes we want to test.
class DescriptorDatabaseTestCase {
 public:
  virtual ~DescriptorDatabaseTestCase() {}

  virtual DescriptorDatabase* GetDatabase() = 0;
  virtual bool AddToDatabase(const FileDescriptorProto& file) = 0;
};

// Factory function type.
typedef DescriptorDatabaseTestCase* DescriptorDatabaseTestCaseFactory();

// Specialization for SimpleDescriptorDatabase.
class SimpleDescriptorDatabaseTestCase : public DescriptorDatabaseTestCase {
 public:
  static DescriptorDatabaseTestCase* New() {
    return new SimpleDescriptorDatabaseTestCase;
  }

  virtual ~SimpleDescriptorDatabaseTestCase() {}

  virtual DescriptorDatabase* GetDatabase() { return &database_; }
  virtual bool AddToDatabase(const FileDescriptorProto& file) {
    return database_.Add(file);
  }

 private:
  SimpleDescriptorDatabase database_;
};

// Specialization for EncodedDescriptorDatabase.
class EncodedDescriptorDatabaseTestCase : public DescriptorDatabaseTestCase {
 public:
  static DescriptorDatabaseTestCase* New() {
    return new EncodedDescriptorDatabaseTestCase;
  }

  virtual ~EncodedDescriptorDatabaseTestCase() {}

  virtual DescriptorDatabase* GetDatabase() { return &database_; }
  virtual bool AddToDatabase(const FileDescriptorProto& file) {
    std::string data;
    file.SerializeToString(&data);
    return database_.AddCopy(data.data(), data.size());
  }

 private:
  EncodedDescriptorDatabase database_;
};

// Specialization for DescriptorPoolDatabase.
class DescriptorPoolDatabaseTestCase : public DescriptorDatabaseTestCase {
 public:
  static DescriptorDatabaseTestCase* New() {
    return new EncodedDescriptorDatabaseTestCase;
  }

  DescriptorPoolDatabaseTestCase() : database_(pool_) {}
  virtual ~DescriptorPoolDatabaseTestCase() {}

  virtual DescriptorDatabase* GetDatabase() { return &database_; }
  virtual bool AddToDatabase(const FileDescriptorProto& file) {
    return pool_.BuildFile(file);
  }

 private:
  DescriptorPool pool_;
  DescriptorPoolDatabase database_;
};

// -------------------------------------------------------------------

class DescriptorDatabaseTest
    : public testing::TestWithParam<DescriptorDatabaseTestCaseFactory*> {
 protected:
  virtual void SetUp() {
    test_case_.reset(GetParam()());
    database_ = test_case_->GetDatabase();
  }

  void AddToDatabase(const char* file_descriptor_text) {
    FileDescriptorProto file_proto;
    EXPECT_TRUE(TextFormat::ParseFromString(file_descriptor_text, &file_proto));
    EXPECT_TRUE(test_case_->AddToDatabase(file_proto));
  }

  void AddToDatabaseWithError(const char* file_descriptor_text) {
    FileDescriptorProto file_proto;
    EXPECT_TRUE(TextFormat::ParseFromString(file_descriptor_text, &file_proto));
    EXPECT_FALSE(test_case_->AddToDatabase(file_proto));
  }

  std::unique_ptr<DescriptorDatabaseTestCase> test_case_;
  DescriptorDatabase* database_;
};

TEST_P(DescriptorDatabaseTest, FindFileByName) {
  AddToDatabase(
      "name: \"foo.proto\" "
      "message_type { name:\"Foo\" }");
  AddToDatabase(
      "name: \"bar.proto\" "
      "message_type { name:\"Bar\" }");

  {
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileByName("foo.proto", &file));
    EXPECT_EQ("foo.proto", file.name());
    ExpectContainsType(file, "Foo");
  }

  {
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileByName("bar.proto", &file));
    EXPECT_EQ("bar.proto", file.name());
    ExpectContainsType(file, "Bar");
  }

  {
    // Fails to find undefined files.
    FileDescriptorProto file;
    EXPECT_FALSE(database_->FindFileByName("baz.proto", &file));
  }
}

TEST_P(DescriptorDatabaseTest, FindFileContainingSymbol) {
  AddToDatabase(
      "name: \"foo.proto\" "
      "message_type { "
      "  name: \"Foo\" "
      "  field { name:\"qux\" }"
      "  nested_type { name: \"Grault\" } "
      "  enum_type { name: \"Garply\" } "
      "} "
      "enum_type { "
      "  name: \"Waldo\" "
      "  value { name:\"FRED\" } "
      "} "
      "extension { name: \"plugh\" } "
      "service { "
      "  name: \"Xyzzy\" "
      "  method { name: \"Thud\" } "
      "}");
  AddToDatabase(
      "name: \"bar.proto\" "
      "package: \"corge\" "
      "message_type { name: \"Bar\" }");

  {
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("Foo", &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    // Can find fields.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("Foo.qux", &file));
    EXPECT_EQ("foo.proto", file.name());
    // Non-existent field under a valid top level symbol can also be
    // found.
    EXPECT_TRUE(
        database_->FindFileContainingSymbol("Foo.none_field.none", &file));
  }

  {
    // Can find nested types.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("Foo.Grault", &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    // Can find nested enums.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("Foo.Garply", &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    // Can find enum types.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("Waldo", &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    // Can find enum values.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("Waldo.FRED", &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    // Can find extensions.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("plugh", &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    // Can find services.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("Xyzzy", &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    // Can find methods.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("Xyzzy.Thud", &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    // Can find things in packages.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingSymbol("corge.Bar", &file));
    EXPECT_EQ("bar.proto", file.name());
  }

  {
    // Fails to find undefined symbols.
    FileDescriptorProto file;
    EXPECT_FALSE(database_->FindFileContainingSymbol("Baz", &file));
  }

  {
    // Names must be fully-qualified.
    FileDescriptorProto file;
    EXPECT_FALSE(database_->FindFileContainingSymbol("Bar", &file));
  }
}

TEST_P(DescriptorDatabaseTest, FindFileContainingExtension) {
  AddToDatabase(
      "name: \"foo.proto\" "
      "message_type { "
      "  name: \"Foo\" "
      "  extension_range { start: 1 end: 1000 } "
      "  extension { name:\"qux\" label:LABEL_OPTIONAL type:TYPE_INT32 "
      "number:5 "
      "              extendee: \".Foo\" }"
      "}");
  AddToDatabase(
      "name: \"bar.proto\" "
      "package: \"corge\" "
      "dependency: \"foo.proto\" "
      "message_type { "
      "  name: \"Bar\" "
      "  extension_range { start: 1 end: 1000 } "
      "} "
      "extension { name:\"grault\" extendee: \".Foo\"       number:32 } "
      "extension { name:\"garply\" extendee: \".corge.Bar\" number:70 } "
      "extension { name:\"waldo\"  extendee: \"Bar\"        number:56 } ");

  {
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingExtension("Foo", 5, &file));
    EXPECT_EQ("foo.proto", file.name());
  }

  {
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingExtension("Foo", 32, &file));
    EXPECT_EQ("bar.proto", file.name());
  }

  {
    // Can find extensions for qualified type names.
    FileDescriptorProto file;
    EXPECT_TRUE(database_->FindFileContainingExtension("corge.Bar", 70, &file));
    EXPECT_EQ("bar.proto", file.name());
  }

  {
    // Can't find extensions whose extendee was not fully-qualified in the
    // FileDescriptorProto.
    FileDescriptorProto file;
    EXPECT_FALSE(database_->FindFileContainingExtension("Bar", 56, &file));
    EXPECT_FALSE(
        database_->FindFileContainingExtension("corge.Bar", 56, &file));
  }

  {
    // Can't find non-existent extension numbers.
    FileDescriptorProto file;
    EXPECT_FALSE(database_->FindFileContainingExtension("Foo", 12, &file));
  }

  {
    // Can't find extensions for non-existent types.
    FileDescriptorProto file;
    EXPECT_FALSE(
        database_->FindFileContainingExtension("NoSuchType", 5, &file));
  }

  {
    // Can't find extensions for unqualified type names.
    FileDescriptorProto file;
    EXPECT_FALSE(database_->FindFileContainingExtension("Bar", 70, &file));
  }
}

TEST_P(DescriptorDatabaseTest, FindAllExtensionNumbers) {
  AddToDatabase(
      "name: \"foo.proto\" "
      "message_type { "
      "  name: \"Foo\" "
      "  extension_range { start: 1 end: 1000 } "
      "  extension { name:\"qux\" label:LABEL_OPTIONAL type:TYPE_INT32 "
      "number:5 "
      "              extendee: \".Foo\" }"
      "}");
  AddToDatabase(
      "name: \"bar.proto\" "
      "package: \"corge\" "
      "dependency: \"foo.proto\" "
      "message_type { "
      "  name: \"Bar\" "
      "  extension_range { start: 1 end: 1000 } "
      "} "
      "extension { name:\"grault\" extendee: \".Foo\"       number:32 } "
      "extension { name:\"garply\" extendee: \".corge.Bar\" number:70 } "
      "extension { name:\"waldo\"  extendee: \"Bar\"        number:56 } ");

  {
    std::vector<int> numbers;
    EXPECT_TRUE(database_->FindAllExtensionNumbers("Foo", &numbers));
    ASSERT_EQ(2, numbers.size());
    std::sort(numbers.begin(), numbers.end());
    EXPECT_EQ(5, numbers[0]);
    EXPECT_EQ(32, numbers[1]);
  }

  {
    std::vector<int> numbers;
    EXPECT_TRUE(database_->FindAllExtensionNumbers("corge.Bar", &numbers));
    // Note: won't find extension 56 due to the name not being fully qualified.
    ASSERT_EQ(1, numbers.size());
    EXPECT_EQ(70, numbers[0]);
  }

  {
    // Can't find extensions for non-existent types.
    std::vector<int> numbers;
    EXPECT_FALSE(database_->FindAllExtensionNumbers("NoSuchType", &numbers));
  }

  {
    // Can't find extensions for unqualified types.
    std::vector<int> numbers;
    EXPECT_FALSE(database_->FindAllExtensionNumbers("Bar", &numbers));
  }
}

TEST_P(DescriptorDatabaseTest, ConflictingFileError) {
  AddToDatabase(
      "name: \"foo.proto\" "
      "message_type { "
      "  name: \"Foo\" "
      "}");
  AddToDatabaseWithError(
      "name: \"foo.proto\" "
      "message_type { "
      "  name: \"Bar\" "
      "}");
}

TEST_P(DescriptorDatabaseTest, ConflictingTypeError) {
  AddToDatabase(
      "name: \"foo.proto\" "
      "message_type { "
      "  name: \"Foo\" "
      "}");
  AddToDatabaseWithError(
      "name: \"bar.proto\" "
      "message_type { "
      "  name: \"Foo\" "
      "}");
}

TEST_P(DescriptorDatabaseTest, ConflictingExtensionError) {
  AddToDatabase(
      "name: \"foo.proto\" "
      "extension { name:\"foo\" label:LABEL_OPTIONAL type:TYPE_INT32 number:5 "
      "            extendee: \".Foo\" }");
  AddToDatabaseWithError(
      "name: \"bar.proto\" "
      "extension { name:\"bar\" label:LABEL_OPTIONAL type:TYPE_INT32 number:5 "
      "            extendee: \".Foo\" }");
}

INSTANTIATE_TEST_SUITE_P(
    Simple, DescriptorDatabaseTest,
    testing::Values(&SimpleDescriptorDatabaseTestCase::New));
INSTANTIATE_TEST_SUITE_P(
    MemoryConserving, DescriptorDatabaseTest,
    testing::Values(&EncodedDescriptorDatabaseTestCase::New));
INSTANTIATE_TEST_SUITE_P(Pool, DescriptorDatabaseTest,
                         testing::Values(&DescriptorPoolDatabaseTestCase::New));

TEST(EncodedDescriptorDatabaseExtraTest, FindNameOfFileContainingSymbol) {
  // Create two files, one of which is in two parts.
  FileDescriptorProto file1, file2a, file2b;
  file1.set_name("foo.proto");
  file1.set_package("foo");
  file1.add_message_type()->set_name("Foo");
  file2a.set_name("bar.proto");
  file2b.set_package("bar");
  file2b.add_message_type()->set_name("Bar");

  // Normal serialization allows our optimization to kick in.
  std::string data1 = file1.SerializeAsString();

  // Force out-of-order serialization to test slow path.
  std::string data2 = file2b.SerializeAsString() + file2a.SerializeAsString();

  // Create EncodedDescriptorDatabase containing both files.
  EncodedDescriptorDatabase db;
  db.Add(data1.data(), data1.size());
  db.Add(data2.data(), data2.size());

  // Test!
  std::string filename;
  EXPECT_TRUE(db.FindNameOfFileContainingSymbol("foo.Foo", &filename));
  EXPECT_EQ("foo.proto", filename);
  EXPECT_TRUE(db.FindNameOfFileContainingSymbol("foo.Foo.Blah", &filename));
  EXPECT_EQ("foo.proto", filename);
  EXPECT_TRUE(db.FindNameOfFileContainingSymbol("bar.Bar", &filename));
  EXPECT_EQ("bar.proto", filename);
  EXPECT_FALSE(db.FindNameOfFileContainingSymbol("foo", &filename));
  EXPECT_FALSE(db.FindNameOfFileContainingSymbol("bar", &filename));
  EXPECT_FALSE(db.FindNameOfFileContainingSymbol("baz.Baz", &filename));
}

TEST(SimpleDescriptorDatabaseExtraTest, FindAllFileNames) {
  FileDescriptorProto f;
  f.set_name("foo.proto");
  f.set_package("foo");
  f.add_message_type()->set_name("Foo");

  SimpleDescriptorDatabase db;
  db.Add(f);

  // Test!
  std::vector<std::string> all_files;
  db.FindAllFileNames(&all_files);
  EXPECT_THAT(all_files, testing::ElementsAre("foo.proto"));
}

TEST(SimpleDescriptorDatabaseExtraTest, FindAllPackageNames) {
  FileDescriptorProto f;
  f.set_name("foo.proto");
  f.set_package("foo");
  f.add_message_type()->set_name("Foo");

  FileDescriptorProto b;
  b.set_name("bar.proto");
  b.set_package("");
  b.add_message_type()->set_name("Bar");

  SimpleDescriptorDatabase db;
  db.Add(f);
  db.Add(b);

  std::vector<std::string> packages;
  EXPECT_TRUE(db.FindAllPackageNames(&packages));
  EXPECT_THAT(packages, ::testing::UnorderedElementsAre("foo", ""));
}

TEST(SimpleDescriptorDatabaseExtraTest, FindAllMessageNames) {
  FileDescriptorProto f;
  f.set_name("foo.proto");
  f.set_package("foo");
  f.add_message_type()->set_name("Foo");

  FileDescriptorProto b;
  b.set_name("bar.proto");
  b.set_package("");
  b.add_message_type()->set_name("Bar");

  SimpleDescriptorDatabase db;
  db.Add(f);
  db.Add(b);

  std::vector<std::string> messages;
  EXPECT_TRUE(db.FindAllMessageNames(&messages));
  EXPECT_THAT(messages, ::testing::UnorderedElementsAre("foo.Foo", "Bar"));
}

TEST(SimpleDescriptorDatabaseExtraTest, AddUnowned) {
  FileDescriptorProto f;
  f.set_name("foo.proto");
  f.set_package("foo");
  f.add_message_type()->set_name("Foo");

  FileDescriptorProto b;
  b.set_name("bar.proto");
  b.set_package("");
  b.add_message_type()->set_name("Bar");

  SimpleDescriptorDatabase db;
  db.AddUnowned(&f);
  db.AddUnowned(&b);

  std::vector<std::string> packages;
  EXPECT_TRUE(db.FindAllPackageNames(&packages));
  EXPECT_THAT(packages, ::testing::UnorderedElementsAre("foo", ""));
  std::vector<std::string> messages;
  EXPECT_TRUE(db.FindAllMessageNames(&messages));
  EXPECT_THAT(messages, ::testing::UnorderedElementsAre("foo.Foo", "Bar"));
}

TEST(DescriptorPoolDatabaseTest, PreserveSourceCodeInfo) {
  SimpleDescriptorDatabase original_db;
  AddToDatabase(&original_db, R"pb(
    name: "foo.proto"
    package: "foo"
    message_type {
      name: "Foo"
      extension_range { start: 1 end: 100 }
    }
    extension {
      name: "foo_ext"
      extendee: ".foo.Foo"
      number: 3
      label: LABEL_OPTIONAL
      type: TYPE_INT32
    }
    source_code_info { location { leading_detached_comments: "comment" } }
  )pb");
  DescriptorPool pool(&original_db);
  DescriptorPoolDatabase db(
      pool, DescriptorPoolDatabaseOptions{/*preserve_source_code_info=*/true});

  FileDescriptorProto file;
  ASSERT_TRUE(db.FindFileByName("foo.proto", &file));
  EXPECT_THAT(
      file.source_code_info(),
      EqualsProto(R"pb(location { leading_detached_comments: "comment" })pb"));

  ASSERT_TRUE(db.FindFileContainingExtension("foo.Foo", 3, &file));
  EXPECT_THAT(
      file.source_code_info(),
      EqualsProto(R"pb(location { leading_detached_comments: "comment" })pb"));

  ASSERT_TRUE(db.FindFileContainingSymbol("foo.Foo", &file));
  EXPECT_THAT(
      file.source_code_info(),
      EqualsProto(R"pb(location { leading_detached_comments: "comment" })pb"));
}

TEST(DescriptorPoolDatabaseTest, StripSourceCodeInfo) {
  SimpleDescriptorDatabase original_db;
  AddToDatabase(&original_db, R"pb(
    name: "foo.proto"
    package: "foo"
    message_type {
      name: "Foo"
      extension_range { start: 1 end: 100 }
    }
    extension {
      name: "foo_ext"
      extendee: ".foo.Foo"
      number: 3
      label: LABEL_OPTIONAL
      type: TYPE_INT32
    }
    source_code_info { location { leading_detached_comments: "comment" } }
  )pb");
  DescriptorPool pool(&original_db);
  DescriptorPoolDatabase db(pool);

  FileDescriptorProto file;
  ASSERT_TRUE(db.FindFileByName("foo.proto", &file));
  EXPECT_FALSE(file.has_source_code_info());

  ASSERT_TRUE(db.FindFileContainingExtension("foo.Foo", 3, &file));
  EXPECT_FALSE(file.has_source_code_info());

  ASSERT_TRUE(db.FindFileContainingSymbol("foo.Foo", &file));
  EXPECT_FALSE(file.has_source_code_info());
}

// ===================================================================

class MergedDescriptorDatabaseTest : public testing::Test {
 protected:
  MergedDescriptorDatabaseTest()
      : forward_merged_(&database1_, &database2_),
        reverse_merged_(&database2_, &database1_) {}

  void SetUp() override {
    AddToDatabase(
        &database1_,
        "name: \"foo.proto\" "
        "message_type { name:\"Foo\" extension_range { start: 1 end: 100 } } "
        "extension { name:\"foo_ext\" extendee: \".Foo\" number:3 "
        "            label:LABEL_OPTIONAL type:TYPE_INT32 } ");
    AddToDatabase(
        &database2_,
        "name: \"bar.proto\" "
        "message_type { name:\"Bar\" extension_range { start: 1 end: 100 } } "
        "extension { name:\"bar_ext\" extendee: \".Bar\" number:5 "
        "            label:LABEL_OPTIONAL type:TYPE_INT32 } ");

    // baz.proto exists in both pools, with different definitions.
    AddToDatabase(
        &database1_,
        "name: \"baz.proto\" "
        "message_type { name:\"Baz\" extension_range { start: 1 end: 100 } } "
        "message_type { name:\"FromPool1\" } "
        "extension { name:\"baz_ext\" extendee: \".Baz\" number:12 "
        "            label:LABEL_OPTIONAL type:TYPE_INT32 } "
        "extension { name:\"database1_only_ext\" extendee: \".Baz\" number:13 "
        "            label:LABEL_OPTIONAL type:TYPE_INT32 } ");
    AddToDatabase(
        &database2_,
        "name: \"baz.proto\" "
        "message_type { name:\"Baz\" extension_range { start: 1 end: 100 } } "
        "message_type { name:\"FromPool2\" } "
        "extension { name:\"baz_ext\" extendee: \".Baz\" number:12 "
        "            label:LABEL_OPTIONAL type:TYPE_INT32 } ");
  }

  SimpleDescriptorDatabase database1_;
  SimpleDescriptorDatabase database2_;

  MergedDescriptorDatabase forward_merged_;
  MergedDescriptorDatabase reverse_merged_;
};

TEST_F(MergedDescriptorDatabaseTest, FindFileByName) {
  {
    // Can find file that is only in database1_.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileByName("foo.proto", &file));
    EXPECT_EQ("foo.proto", file.name());
    ExpectContainsType(file, "Foo");
  }

  {
    // Can find file that is only in database2_.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileByName("bar.proto", &file));
    EXPECT_EQ("bar.proto", file.name());
    ExpectContainsType(file, "Bar");
  }

  {
    // In forward_merged_, database1_'s baz.proto takes precedence.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileByName("baz.proto", &file));
    EXPECT_EQ("baz.proto", file.name());
    ExpectContainsType(file, "FromPool1");
  }

  {
    // In reverse_merged_, database2_'s baz.proto takes precedence.
    FileDescriptorProto file;
    EXPECT_TRUE(reverse_merged_.FindFileByName("baz.proto", &file));
    EXPECT_EQ("baz.proto", file.name());
    ExpectContainsType(file, "FromPool2");
  }

  {
    // Can't find non-existent file.
    FileDescriptorProto file;
    EXPECT_FALSE(forward_merged_.FindFileByName("no_such.proto", &file));
  }
}

TEST_F(MergedDescriptorDatabaseTest, FindFileContainingSymbol) {
  {
    // Can find file that is only in database1_.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileContainingSymbol("Foo", &file));
    EXPECT_EQ("foo.proto", file.name());
    ExpectContainsType(file, "Foo");
  }

  {
    // Can find file that is only in database2_.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileContainingSymbol("Bar", &file));
    EXPECT_EQ("bar.proto", file.name());
    ExpectContainsType(file, "Bar");
  }

  {
    // In forward_merged_, database1_'s baz.proto takes precedence.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileContainingSymbol("Baz", &file));
    EXPECT_EQ("baz.proto", file.name());
    ExpectContainsType(file, "FromPool1");
  }

  {
    // In reverse_merged_, database2_'s baz.proto takes precedence.
    FileDescriptorProto file;
    EXPECT_TRUE(reverse_merged_.FindFileContainingSymbol("Baz", &file));
    EXPECT_EQ("baz.proto", file.name());
    ExpectContainsType(file, "FromPool2");
  }

  {
    // FromPool1 only shows up in forward_merged_ because it is masked by
    // database2_'s baz.proto in reverse_merged_.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileContainingSymbol("FromPool1", &file));
    EXPECT_FALSE(reverse_merged_.FindFileContainingSymbol("FromPool1", &file));
  }

  {
    // Can't find non-existent symbol.
    FileDescriptorProto file;
    EXPECT_FALSE(forward_merged_.FindFileContainingSymbol("NoSuchType", &file));
  }
}

TEST_F(MergedDescriptorDatabaseTest, FindFileContainingExtension) {
  {
    // Can find file that is only in database1_.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileContainingExtension("Foo", 3, &file));
    EXPECT_EQ("foo.proto", file.name());
    ExpectContainsType(file, "Foo");
  }

  {
    // Can find file that is only in database2_.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileContainingExtension("Bar", 5, &file));
    EXPECT_EQ("bar.proto", file.name());
    ExpectContainsType(file, "Bar");
  }

  {
    // In forward_merged_, database1_'s baz.proto takes precedence.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileContainingExtension("Baz", 12, &file));
    EXPECT_EQ("baz.proto", file.name());
    ExpectContainsType(file, "FromPool1");
  }

  {
    // In reverse_merged_, database2_'s baz.proto takes precedence.
    FileDescriptorProto file;
    EXPECT_TRUE(reverse_merged_.FindFileContainingExtension("Baz", 12, &file));
    EXPECT_EQ("baz.proto", file.name());
    ExpectContainsType(file, "FromPool2");
  }

  {
    // Baz's extension 13 only shows up in forward_merged_ because it is
    // masked by database2_'s baz.proto in reverse_merged_.
    FileDescriptorProto file;
    EXPECT_TRUE(forward_merged_.FindFileContainingExtension("Baz", 13, &file));
    EXPECT_FALSE(reverse_merged_.FindFileContainingExtension("Baz", 13, &file));
  }

  {
    // Can't find non-existent extension.
    FileDescriptorProto file;
    EXPECT_FALSE(forward_merged_.FindFileContainingExtension("Foo", 6, &file));
  }
}

TEST_F(MergedDescriptorDatabaseTest, FindAllExtensionNumbers) {
  {
    // Message only has extension in database1_
    std::vector<int> numbers;
    EXPECT_TRUE(forward_merged_.FindAllExtensionNumbers("Foo", &numbers));
    ASSERT_EQ(1, numbers.size());
    EXPECT_EQ(3, numbers[0]);
  }

  {
    // Message only has extension in database2_
    std::vector<int> numbers;
    EXPECT_TRUE(forward_merged_.FindAllExtensionNumbers("Bar", &numbers));
    ASSERT_EQ(1, numbers.size());
    EXPECT_EQ(5, numbers[0]);
  }

  {
    // Merge results from the two databases.
    std::vector<int> numbers;
    EXPECT_TRUE(forward_merged_.FindAllExtensionNumbers("Baz", &numbers));
    ASSERT_EQ(2, numbers.size());
    std::sort(numbers.begin(), numbers.end());
    EXPECT_EQ(12, numbers[0]);
    EXPECT_EQ(13, numbers[1]);
  }

  {
    std::vector<int> numbers;
    EXPECT_TRUE(reverse_merged_.FindAllExtensionNumbers("Baz", &numbers));
    ASSERT_EQ(2, numbers.size());
    std::sort(numbers.begin(), numbers.end());
    EXPECT_EQ(12, numbers[0]);
    EXPECT_EQ(13, numbers[1]);
  }

  {
    // Can't find extensions for a non-existent message.
    std::vector<int> numbers;
    EXPECT_FALSE(reverse_merged_.FindAllExtensionNumbers("Blah", &numbers));
  }
}

TEST_F(MergedDescriptorDatabaseTest, FindAllFileNames) {
  std::vector<std::string> files;
  EXPECT_TRUE(forward_merged_.FindAllFileNames(&files));
  EXPECT_THAT(files, ::testing::UnorderedElementsAre("foo.proto", "bar.proto",
                                                     "baz.proto", "baz.proto"));
}


}  // anonymous namespace
}  // namespace protobuf
}  // namespace google
