aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/google/protobuf/compiler/importer_unittest.cc
diff options
context:
space:
mode:
authorGravatar temporal <temporal@630680e5-0e50-0410-840e-4b1c322b438d>2008-07-10 02:12:20 +0000
committerGravatar temporal <temporal@630680e5-0e50-0410-840e-4b1c322b438d>2008-07-10 02:12:20 +0000
commit40ee551715c3a784ea6132dbf604b0e665ca2def (patch)
tree6e3ea9674be5b0f59106f88f3afa1313854beebf /src/google/protobuf/compiler/importer_unittest.cc
Initial checkin.
Diffstat (limited to 'src/google/protobuf/compiler/importer_unittest.cc')
-rw-r--r--src/google/protobuf/compiler/importer_unittest.cc539
1 files changed, 539 insertions, 0 deletions
diff --git a/src/google/protobuf/compiler/importer_unittest.cc b/src/google/protobuf/compiler/importer_unittest.cc
new file mode 100644
index 00000000..5b5d2831
--- /dev/null
+++ b/src/google/protobuf/compiler/importer_unittest.cc
@@ -0,0 +1,539 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.
+// http://code.google.com/p/protobuf/
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Author: kenton@google.com (Kenton Varda)
+// Based on original Protocol Buffers design by
+// Sanjay Ghemawat, Jeff Dean, and others.
+
+#include <google/protobuf/stubs/hash.h>
+
+#include <google/protobuf/compiler/importer.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+
+#include <google/protobuf/stubs/map-util.h>
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/testing/file.h>
+#include <google/protobuf/stubs/strutil.h>
+#include <google/protobuf/stubs/substitute.h>
+#include <google/protobuf/testing/googletest.h>
+#include <gtest/gtest.h>
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+
+namespace {
+
+#define EXPECT_SUBSTRING(needle, haystack) \
+ EXPECT_PRED_FORMAT2(testing::IsSubstring, (needle), (haystack))
+
+class MockErrorCollector : public MultiFileErrorCollector {
+ public:
+ MockErrorCollector() {}
+ ~MockErrorCollector() {}
+
+ string text_;
+
+ // implements ErrorCollector ---------------------------------------
+ void AddError(const string& filename, int line, int column,
+ const string& message) {
+ strings::SubstituteAndAppend(&text_, "$0:$1:$2: $3\n",
+ filename, line, column, message);
+ }
+};
+
+// -------------------------------------------------------------------
+
+// A dummy implementation of SourceTree backed by a simple map.
+class MockSourceTree : public SourceTree {
+ public:
+ MockSourceTree() {}
+ ~MockSourceTree() {}
+
+ void AddFile(const string& name, const char* contents) {
+ files_[name] = contents;
+ }
+
+ // implements SourceTree -------------------------------------------
+ io::ZeroCopyInputStream* Open(const string& filename) {
+ const char* contents = FindPtrOrNull(files_, filename);
+ if (contents == NULL) {
+ return NULL;
+ } else {
+ return new io::ArrayInputStream(contents, strlen(contents));
+ }
+ }
+
+ private:
+ hash_map<string, const char*> files_;
+};
+
+// ===================================================================
+
+class ImporterTest : public testing::Test {
+ protected:
+ ImporterTest()
+ : importer_(&source_tree_, &error_collector_) {}
+
+ void AddFile(const string& filename, const char* text) {
+ source_tree_.AddFile(filename, text);
+ }
+
+ // Return the collected error text
+ string error() const { return error_collector_.text_; }
+
+ MockErrorCollector error_collector_;
+ MockSourceTree source_tree_;
+ Importer importer_;
+};
+
+TEST_F(ImporterTest, Import) {
+ // Test normal importing.
+ AddFile("foo.proto",
+ "syntax = \"proto2\";\n"
+ "message Foo {}\n");
+
+ const FileDescriptor* file = importer_.Import("foo.proto");
+ EXPECT_EQ("", error_collector_.text_);
+ ASSERT_TRUE(file != NULL);
+
+ ASSERT_EQ(1, file->message_type_count());
+ EXPECT_EQ("Foo", file->message_type(0)->name());
+
+ // Importing again should return same object.
+ EXPECT_EQ(file, importer_.Import("foo.proto"));
+}
+
+TEST_F(ImporterTest, ImportNested) {
+ // Test that importing a file which imports another file works.
+ AddFile("foo.proto",
+ "syntax = \"proto2\";\n"
+ "import \"bar.proto\";\n"
+ "message Foo {\n"
+ " optional Bar bar = 1;\n"
+ "}\n");
+ AddFile("bar.proto",
+ "syntax = \"proto2\";\n"
+ "message Bar {}\n");
+
+ // Note that both files are actually parsed by the first call to Import()
+ // here, since foo.proto imports bar.proto. The second call just returns
+ // the same ProtoFile for bar.proto which was constructed while importing
+ // foo.proto. We test that this is the case below by checking that bar
+ // is among foo's dependencies (by pointer).
+ const FileDescriptor* foo = importer_.Import("foo.proto");
+ const FileDescriptor* bar = importer_.Import("bar.proto");
+ EXPECT_EQ("", error_collector_.text_);
+ ASSERT_TRUE(foo != NULL);
+ ASSERT_TRUE(bar != NULL);
+
+ // Check that foo's dependency is the same object as bar.
+ ASSERT_EQ(1, foo->dependency_count());
+ EXPECT_EQ(bar, foo->dependency(0));
+
+ // Check that foo properly cross-links bar.
+ ASSERT_EQ(1, foo->message_type_count());
+ ASSERT_EQ(1, bar->message_type_count());
+ ASSERT_EQ(1, foo->message_type(0)->field_count());
+ ASSERT_EQ(FieldDescriptor::TYPE_MESSAGE,
+ foo->message_type(0)->field(0)->type());
+ EXPECT_EQ(bar->message_type(0),
+ foo->message_type(0)->field(0)->message_type());
+}
+
+TEST_F(ImporterTest, FileNotFound) {
+ // Error: Parsing a file that doesn't exist.
+ EXPECT_TRUE(importer_.Import("foo.proto") == NULL);
+ EXPECT_EQ(
+ "foo.proto:-1:0: File not found.\n",
+ error_collector_.text_);
+}
+
+TEST_F(ImporterTest, ImportNotFound) {
+ // Error: Importing a file that doesn't exist.
+ AddFile("foo.proto",
+ "syntax = \"proto2\";\n"
+ "import \"bar.proto\";\n");
+
+ EXPECT_TRUE(importer_.Import("foo.proto") == NULL);
+ EXPECT_EQ(
+ "bar.proto:-1:0: File not found.\n"
+ "foo.proto:-1:0: Import \"bar.proto\" was not found or had errors.\n",
+ error_collector_.text_);
+}
+
+TEST_F(ImporterTest, RecursiveImport) {
+ // Error: Recursive import.
+ AddFile("recursive1.proto",
+ "syntax = \"proto2\";\n"
+ "import \"recursive2.proto\";\n");
+ AddFile("recursive2.proto",
+ "syntax = \"proto2\";\n"
+ "import \"recursive1.proto\";\n");
+
+ EXPECT_TRUE(importer_.Import("recursive1.proto") == NULL);
+ EXPECT_EQ(
+ "recursive1.proto:-1:0: File recursively imports itself: recursive1.proto "
+ "-> recursive2.proto -> recursive1.proto\n"
+ "recursive2.proto:-1:0: Import \"recursive1.proto\" was not found "
+ "or had errors.\n"
+ "recursive1.proto:-1:0: Import \"recursive2.proto\" was not found "
+ "or had errors.\n",
+ error_collector_.text_);
+}
+
+// TODO(sanjay): The MapField tests below more properly belong in
+// descriptor_unittest, but are more convenient to test here.
+TEST_F(ImporterTest, MapFieldValid) {
+ AddFile(
+ "map.proto",
+ "syntax = \"proto2\";\n"
+ "message Item {\n"
+ " required string key = 1;\n"
+ "}\n"
+ "message Map {\n"
+ " repeated Item items = 1 [experimental_map_key = \"key\"];\n"
+ "}\n"
+ );
+ const FileDescriptor* file = importer_.Import("map.proto");
+ ASSERT_TRUE(file != NULL) << error_collector_.text_;
+ EXPECT_EQ("", error_collector_.text_);
+
+ // Check that Map::items points to Item::key
+ const Descriptor* item_type = file->FindMessageTypeByName("Item");
+ ASSERT_TRUE(item_type != NULL);
+ const Descriptor* map_type = file->FindMessageTypeByName("Map");
+ ASSERT_TRUE(map_type != NULL);
+ const FieldDescriptor* key_field = item_type->FindFieldByName("key");
+ ASSERT_TRUE(key_field != NULL);
+ const FieldDescriptor* items_field = map_type->FindFieldByName("items");
+ ASSERT_TRUE(items_field != NULL);
+ EXPECT_EQ(items_field->experimental_map_key(), key_field);
+}
+
+TEST_F(ImporterTest, MapFieldNotRepeated) {
+ AddFile(
+ "map.proto",
+ "syntax = \"proto2\";\n"
+ "message Item {\n"
+ " required string key = 1;\n"
+ "}\n"
+ "message Map {\n"
+ " required Item items = 1 [experimental_map_key = \"key\"];\n"
+ "}\n"
+ );
+ EXPECT_TRUE(importer_.Import("map.proto") == NULL);
+ EXPECT_SUBSTRING("only allowed for repeated fields", error());
+}
+
+TEST_F(ImporterTest, MapFieldNotMessageType) {
+ AddFile(
+ "map.proto",
+ "syntax = \"proto2\";\n"
+ "message Map {\n"
+ " repeated int32 items = 1 [experimental_map_key = \"key\"];\n"
+ "}\n"
+ );
+ EXPECT_TRUE(importer_.Import("map.proto") == NULL);
+ EXPECT_SUBSTRING("only allowed for fields with a message type", error());
+}
+
+TEST_F(ImporterTest, MapFieldTypeNotFound) {
+ AddFile(
+ "map.proto",
+ "syntax = \"proto2\";\n"
+ "message Map {\n"
+ " repeated Unknown items = 1 [experimental_map_key = \"key\"];\n"
+ "}\n"
+ );
+ EXPECT_TRUE(importer_.Import("map.proto") == NULL);
+ EXPECT_SUBSTRING("not defined", error());
+}
+
+TEST_F(ImporterTest, MapFieldKeyNotFound) {
+ AddFile(
+ "map.proto",
+ "syntax = \"proto2\";\n"
+ "message Item {\n"
+ " required string key = 1;\n"
+ "}\n"
+ "message Map {\n"
+ " repeated Item items = 1 [experimental_map_key = \"badkey\"];\n"
+ "}\n"
+ );
+ EXPECT_TRUE(importer_.Import("map.proto") == NULL);
+ EXPECT_SUBSTRING("Could not find field", error());
+}
+
+TEST_F(ImporterTest, MapFieldKeyRepeated) {
+ AddFile(
+ "map.proto",
+ "syntax = \"proto2\";\n"
+ "message Item {\n"
+ " repeated string key = 1;\n"
+ "}\n"
+ "message Map {\n"
+ " repeated Item items = 1 [experimental_map_key = \"key\"];\n"
+ "}\n"
+ );
+ EXPECT_TRUE(importer_.Import("map.proto") == NULL);
+ EXPECT_SUBSTRING("must not name a repeated field", error());
+}
+
+TEST_F(ImporterTest, MapFieldKeyNotScalar) {
+ AddFile(
+ "map.proto",
+ "syntax = \"proto2\";\n"
+ "message ItemKey { }\n"
+ "message Item {\n"
+ " required ItemKey key = 1;\n"
+ "}\n"
+ "message Map {\n"
+ " repeated Item items = 1 [experimental_map_key = \"key\"];\n"
+ "}\n"
+ );
+ EXPECT_TRUE(importer_.Import("map.proto") == NULL);
+ EXPECT_SUBSTRING("must name a scalar or string", error());
+}
+
+// ===================================================================
+
+class DiskSourceTreeTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ dirnames_.push_back(TestTempDir() + "/test_proto2_import_path_1");
+ dirnames_.push_back(TestTempDir() + "/test_proto2_import_path_2");
+
+ for (int i = 0; i < dirnames_.size(); i++) {
+ if (File::Exists(dirnames_[i])) {
+ File::DeleteRecursively(dirnames_[i], NULL, NULL);
+ }
+ GOOGLE_CHECK(File::CreateDir(dirnames_[i].c_str(), DEFAULT_FILE_MODE));
+ }
+ }
+
+ virtual void TearDown() {
+ for (int i = 0; i < dirnames_.size(); i++) {
+ File::DeleteRecursively(dirnames_[i], NULL, NULL);
+ }
+ }
+
+ void AddFile(const string& filename, const char* contents) {
+ File::WriteStringToFileOrDie(contents, filename);
+ }
+
+ void AddSubdir(const string& dirname) {
+ GOOGLE_CHECK(File::CreateDir(dirname.c_str(), DEFAULT_FILE_MODE));
+ }
+
+ void ExpectFileContents(const string& filename,
+ const char* expected_contents) {
+ scoped_ptr<io::ZeroCopyInputStream> input(source_tree_.Open(filename));
+
+ ASSERT_FALSE(input == NULL);
+
+ // Read all the data from the file.
+ string file_contents;
+ const void* data;
+ int size;
+ while (input->Next(&data, &size)) {
+ file_contents.append(reinterpret_cast<const char*>(data), size);
+ }
+
+ EXPECT_EQ(expected_contents, file_contents);
+ }
+
+ void ExpectFileNotFound(const string& filename) {
+ scoped_ptr<io::ZeroCopyInputStream> input(source_tree_.Open(filename));
+ EXPECT_TRUE(input == NULL);
+ }
+
+ DiskSourceTree source_tree_;
+
+ // Paths of two on-disk directories to use during the test.
+ vector<string> dirnames_;
+};
+
+TEST_F(DiskSourceTreeTest, MapRoot) {
+ // Test opening a file in a directory that is mapped to the root of the
+ // source tree.
+ AddFile(dirnames_[0] + "/foo", "Hello World!");
+ source_tree_.MapPath("", dirnames_[0]);
+
+ ExpectFileContents("foo", "Hello World!");
+ ExpectFileNotFound("bar");
+}
+
+TEST_F(DiskSourceTreeTest, MapDirectory) {
+ // Test opening a file in a directory that is mapped to somewhere other
+ // than the root of the source tree.
+
+ AddFile(dirnames_[0] + "/foo", "Hello World!");
+ source_tree_.MapPath("baz", dirnames_[0]);
+
+ ExpectFileContents("baz/foo", "Hello World!");
+ ExpectFileNotFound("baz/bar");
+ ExpectFileNotFound("foo");
+ ExpectFileNotFound("bar");
+
+ // Non-canonical file names should not work.
+ ExpectFileNotFound("baz//foo");
+ ExpectFileNotFound("baz/../baz/foo");
+ ExpectFileNotFound("baz/./foo");
+ ExpectFileNotFound("baz/foo/");
+}
+
+TEST_F(DiskSourceTreeTest, NoParent) {
+ // Test that we cannot open files in a parent of a mapped directory.
+
+ AddFile(dirnames_[0] + "/foo", "Hello World!");
+ AddSubdir(dirnames_[0] + "/bar");
+ AddFile(dirnames_[0] + "/bar/baz", "Blah.");
+ source_tree_.MapPath("", dirnames_[0] + "/bar");
+
+ ExpectFileContents("baz", "Blah.");
+ ExpectFileNotFound("../foo");
+ ExpectFileNotFound("../bar/baz");
+}
+
+TEST_F(DiskSourceTreeTest, MapFile) {
+ // Test opening a file that is mapped directly into the source tree.
+
+ AddFile(dirnames_[0] + "/foo", "Hello World!");
+ source_tree_.MapPath("foo", dirnames_[0] + "/foo");
+
+ ExpectFileContents("foo", "Hello World!");
+ ExpectFileNotFound("bar");
+}
+
+TEST_F(DiskSourceTreeTest, SearchMultipleDirectories) {
+ // Test mapping and searching multiple directories.
+
+ AddFile(dirnames_[0] + "/foo", "Hello World!");
+ AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
+ AddFile(dirnames_[1] + "/bar", "Goodbye World!");
+ source_tree_.MapPath("", dirnames_[0]);
+ source_tree_.MapPath("", dirnames_[1]);
+
+ ExpectFileContents("foo", "Hello World!");
+ ExpectFileContents("bar", "Goodbye World!");
+ ExpectFileNotFound("baz");
+}
+
+TEST_F(DiskSourceTreeTest, OrderingTrumpsSpecificity) {
+ // Test that directories are always searched in order, even when a latter
+ // directory is more-specific than a former one.
+
+ // Create the "bar" directory so we can put a file in it.
+ ASSERT_TRUE(File::CreateDir((dirnames_[0] + "/bar").c_str(),
+ DEFAULT_FILE_MODE));
+
+ // Add files and map paths.
+ AddFile(dirnames_[0] + "/bar/foo", "Hello World!");
+ AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
+ source_tree_.MapPath("", dirnames_[0]);
+ source_tree_.MapPath("bar", dirnames_[1]);
+
+ // Check.
+ ExpectFileContents("bar/foo", "Hello World!");
+}
+
+TEST_F(DiskSourceTreeTest, DiskFileToVirtualFile) {
+ // Test DiskFileToVirtualFile.
+
+ AddFile(dirnames_[0] + "/foo", "Hello World!");
+ AddFile(dirnames_[1] + "/foo", "This file should be hidden.");
+ source_tree_.MapPath("bar", dirnames_[0]);
+ source_tree_.MapPath("bar", dirnames_[1]);
+
+ string virtual_file;
+ string shadowing_disk_file;
+
+ EXPECT_EQ(DiskSourceTree::NO_MAPPING,
+ source_tree_.DiskFileToVirtualFile(
+ "/foo", &virtual_file, &shadowing_disk_file));
+
+ EXPECT_EQ(DiskSourceTree::SHADOWED,
+ source_tree_.DiskFileToVirtualFile(
+ dirnames_[1] + "/foo", &virtual_file, &shadowing_disk_file));
+ EXPECT_EQ("bar/foo", virtual_file);
+ EXPECT_EQ(dirnames_[0] + "/foo", shadowing_disk_file);
+
+ EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
+ source_tree_.DiskFileToVirtualFile(
+ dirnames_[1] + "/baz", &virtual_file, &shadowing_disk_file));
+ EXPECT_EQ("bar/baz", virtual_file);
+
+ EXPECT_EQ(DiskSourceTree::SUCCESS,
+ source_tree_.DiskFileToVirtualFile(
+ dirnames_[0] + "/foo", &virtual_file, &shadowing_disk_file));
+ EXPECT_EQ("bar/foo", virtual_file);
+}
+
+TEST_F(DiskSourceTreeTest, DiskFileToVirtualFileCanonicalization) {
+ // Test handling of "..", ".", etc. in DiskFileToVirtualFile().
+
+ source_tree_.MapPath("dir1", "..");
+ source_tree_.MapPath("dir2", "../../foo");
+ source_tree_.MapPath("dir3", "./foo/bar/.");
+ source_tree_.MapPath("dir4", ".");
+ source_tree_.MapPath("", "/qux");
+
+ string virtual_file;
+ string shadowing_disk_file;
+
+ // "../.." should not be considered to be under "..".
+ EXPECT_EQ(DiskSourceTree::NO_MAPPING,
+ source_tree_.DiskFileToVirtualFile(
+ "../../baz", &virtual_file, &shadowing_disk_file));
+
+ // But "../baz" should be.
+ EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
+ source_tree_.DiskFileToVirtualFile(
+ "../baz", &virtual_file, &shadowing_disk_file));
+ EXPECT_EQ("dir1/baz", virtual_file);
+
+ // "../../foo/baz" is under "../../foo".
+ EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
+ source_tree_.DiskFileToVirtualFile(
+ "../../foo/baz", &virtual_file, &shadowing_disk_file));
+ EXPECT_EQ("dir2/baz", virtual_file);
+
+ // "foo/./bar/baz" is under "./foo/bar/.".
+ EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
+ source_tree_.DiskFileToVirtualFile(
+ "foo/bar/baz", &virtual_file, &shadowing_disk_file));
+ EXPECT_EQ("dir3/baz", virtual_file);
+
+ // "bar" is under ".".
+ EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
+ source_tree_.DiskFileToVirtualFile(
+ "bar", &virtual_file, &shadowing_disk_file));
+ EXPECT_EQ("dir4/bar", virtual_file);
+
+ // "/qux/baz" is under "/qux".
+ EXPECT_EQ(DiskSourceTree::CANNOT_OPEN,
+ source_tree_.DiskFileToVirtualFile(
+ "/qux/baz", &virtual_file, &shadowing_disk_file));
+ EXPECT_EQ("baz", virtual_file);
+}
+
+} // namespace
+
+} // namespace compiler
+} // namespace protobuf
+} // namespace google