1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc.  All rights reserved.
3// http://code.google.com/p/protobuf/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9//     * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11//     * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15//     * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31// Author: kenton@google.com (Kenton Varda)
32
33#include <google/protobuf/compiler/mock_code_generator.h>
34
35#include <google/protobuf/testing/file.h>
36#include <google/protobuf/descriptor.h>
37#include <google/protobuf/io/zero_copy_stream.h>
38#include <google/protobuf/io/printer.h>
39#include <google/protobuf/stubs/strutil.h>
40#include <google/protobuf/stubs/substitute.h>
41#include <gtest/gtest.h>
42#include <google/protobuf/stubs/stl_util-inl.h>
43
44namespace google {
45namespace protobuf {
46namespace compiler {
47
48static const char* kFirstInsertionPointName = "first_mock_insertion_point";
49static const char* kSecondInsertionPointName = "second_mock_insertion_point";
50static const char* kFirstInsertionPoint =
51    "# @@protoc_insertion_point(first_mock_insertion_point) is here\n";
52static const char* kSecondInsertionPoint =
53    "  # @@protoc_insertion_point(second_mock_insertion_point) is here\n";
54
55MockCodeGenerator::MockCodeGenerator(const string& name)
56    : name_(name) {}
57
58MockCodeGenerator::~MockCodeGenerator() {}
59
60void MockCodeGenerator::ExpectGenerated(
61    const string& name,
62    const string& parameter,
63    const string& insertions,
64    const string& file,
65    const string& first_message_name,
66    const string& output_directory) {
67  string content;
68  ASSERT_TRUE(File::ReadFileToString(
69      output_directory + "/" + GetOutputFileName(name, file), &content));
70
71  vector<string> lines;
72  SplitStringUsing(content, "\n", &lines);
73
74  while (!lines.empty() && lines.back().empty()) {
75    lines.pop_back();
76  }
77  for (int i = 0; i < lines.size(); i++) {
78    lines[i] += "\n";
79  }
80
81  vector<string> insertion_list;
82  if (!insertions.empty()) {
83    SplitStringUsing(insertions, ",", &insertion_list);
84  }
85
86  ASSERT_EQ(lines.size(), 3 + insertion_list.size() * 2);
87  EXPECT_EQ(GetOutputFileContent(name, parameter, file, first_message_name),
88            lines[0]);
89
90  EXPECT_EQ(kFirstInsertionPoint, lines[1 + insertion_list.size()]);
91  EXPECT_EQ(kSecondInsertionPoint, lines[2 + insertion_list.size() * 2]);
92
93  for (int i = 0; i < insertion_list.size(); i++) {
94    EXPECT_EQ(GetOutputFileContent(insertion_list[i], "first_insert",
95                                   file, first_message_name),
96              lines[1 + i]);
97    // Second insertion point is indented, so the inserted text should
98    // automatically be indented too.
99    EXPECT_EQ("  " + GetOutputFileContent(insertion_list[i], "second_insert",
100                                          file, first_message_name),
101              lines[2 + insertion_list.size() + i]);
102  }
103}
104
105bool MockCodeGenerator::Generate(
106    const FileDescriptor* file,
107    const string& parameter,
108    OutputDirectory* output_directory,
109    string* error) const {
110  for (int i = 0; i < file->message_type_count(); i++) {
111    if (HasPrefixString(file->message_type(i)->name(), "MockCodeGenerator_")) {
112      string command = StripPrefixString(file->message_type(i)->name(),
113                                         "MockCodeGenerator_");
114      if (command == "Error") {
115        *error = "Saw message type MockCodeGenerator_Error.";
116        return false;
117      } else if (command == "Exit") {
118        cerr << "Saw message type MockCodeGenerator_Exit." << endl;
119        exit(123);
120      } else if (command == "Abort") {
121        cerr << "Saw message type MockCodeGenerator_Abort." << endl;
122        abort();
123      } else {
124        GOOGLE_LOG(FATAL) << "Unknown MockCodeGenerator command: " << command;
125      }
126    }
127  }
128
129  if (HasPrefixString(parameter, "insert=")) {
130    vector<string> insert_into;
131    SplitStringUsing(StripPrefixString(parameter, "insert="),
132                     ",", &insert_into);
133
134    for (int i = 0; i < insert_into.size(); i++) {
135      {
136        scoped_ptr<io::ZeroCopyOutputStream> output(
137            output_directory->OpenForInsert(
138              GetOutputFileName(insert_into[i], file),
139              kFirstInsertionPointName));
140        io::Printer printer(output.get(), '$');
141        printer.PrintRaw(GetOutputFileContent(name_, "first_insert", file));
142        if (printer.failed()) {
143          *error = "MockCodeGenerator detected write error.";
144          return false;
145        }
146      }
147
148      {
149        scoped_ptr<io::ZeroCopyOutputStream> output(
150            output_directory->OpenForInsert(
151              GetOutputFileName(insert_into[i], file),
152              kSecondInsertionPointName));
153        io::Printer printer(output.get(), '$');
154        printer.PrintRaw(GetOutputFileContent(name_, "second_insert", file));
155        if (printer.failed()) {
156          *error = "MockCodeGenerator detected write error.";
157          return false;
158        }
159      }
160    }
161  } else {
162    scoped_ptr<io::ZeroCopyOutputStream> output(
163        output_directory->Open(GetOutputFileName(name_, file)));
164
165    io::Printer printer(output.get(), '$');
166    printer.PrintRaw(GetOutputFileContent(name_, parameter, file));
167    printer.PrintRaw(kFirstInsertionPoint);
168    printer.PrintRaw(kSecondInsertionPoint);
169
170    if (printer.failed()) {
171      *error = "MockCodeGenerator detected write error.";
172      return false;
173    }
174  }
175
176  return true;
177}
178
179string MockCodeGenerator::GetOutputFileName(const string& generator_name,
180                                            const FileDescriptor* file) {
181  return GetOutputFileName(generator_name, file->name());
182}
183
184string MockCodeGenerator::GetOutputFileName(const string& generator_name,
185                                            const string& file) {
186  return file + ".MockCodeGenerator." + generator_name;
187}
188
189string MockCodeGenerator::GetOutputFileContent(const string& generator_name,
190                                               const string& parameter,
191                                               const FileDescriptor* file) {
192  return GetOutputFileContent(
193      generator_name, parameter, file->name(),
194      file->message_type_count() > 0 ?
195          file->message_type(0)->name() : "(none)");
196}
197
198string MockCodeGenerator::GetOutputFileContent(
199    const string& generator_name,
200    const string& parameter,
201    const string& file,
202    const string& first_message_name) {
203  return strings::Substitute("$0: $1, $2, $3\n",
204      generator_name, parameter, file, first_message_name);
205}
206
207}  // namespace compiler
208}  // namespace protobuf
209}  // namespace google
210