1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <unistd.h>
16
17 #include <fstream>
18 #include <iostream>
19 #include <memory>
20 #include <streambuf>
21 #include <string>
22
23 #include "gtest/gtest.h"
24 #include "contrib/jsonnet/jsonnet_base_sandbox.h"
25 #include "sandboxed_api/util/path.h"
26 #include "sandboxed_api/util/status_matchers.h"
27
28 namespace {
29
30 class JsonnetTest : public ::testing::Test {
31 protected:
32 enum Evaluation { kBase, kMultipleFiles, kYamlStream };
33
SetUp()34 void SetUp() override {
35 // Get paths to where input and output is stored.
36 char buffer[256];
37 int error = readlink("/proc/self/exe", buffer, 256);
38 ASSERT_GE(error, 0);
39
40 std::pair<absl::string_view, absl::string_view> parts_of_path =
41 sapi::file::SplitPath(buffer);
42 absl::string_view binary_path = parts_of_path.first;
43
44 std::string input_path =
45 sapi::file::JoinPath(binary_path, "tests_input", "dummy_input");
46 std::string output_path =
47 sapi::file::JoinPath(binary_path, "tests_output", "dummy_input");
48
49 // Set up sandbox and api.
50 sandbox_ = std::make_unique<JsonnetBaseSandbox>(input_path, output_path);
51 ASSERT_THAT(sandbox_->Init(), sapi::IsOk());
52 api_ = std::make_unique<JsonnetApi>(sandbox_.get());
53
54 // Initialize library's main structure.
55 SAPI_ASSERT_OK_AND_ASSIGN(JsonnetVm * vm_ptr, api_->c_jsonnet_make());
56 vm_ = std::make_unique<sapi::v::RemotePtr>(vm_ptr);
57 }
58
TearDown()59 void TearDown() override {
60 if (jsonnet_vm_was_used_) {
61 SAPI_ASSERT_OK_AND_ASSIGN(
62 char* result, api_->c_jsonnet_realloc(vm_.get(), output_.get(), 0));
63 }
64 ASSERT_THAT(api_->c_jsonnet_destroy(vm_.get()), sapi::IsOk());
65 if (input_was_read_) {
66 ASSERT_THAT(api_->c_free_input(input_.get()), sapi::IsOk());
67 }
68 }
69
70 // Reads input from file.
71 void ReadInput(const char* filename);
72
73 // Evaluates jsonnet code.
74 void EvaluateJsonnetCode(Evaluation type, bool expected_correct);
75
76 // Writes output to file.
77 void WriteOutput(const char* filename_or_directory, Evaluation type);
78
79 // Reads the output written to a file by library function / expected output.
80 std::string ReadOutput(const char* filename);
81
82 std::unique_ptr<JsonnetBaseSandbox> sandbox_;
83 std::unique_ptr<JsonnetApi> api_;
84 std::unique_ptr<sapi::v::RemotePtr> input_;
85 std::unique_ptr<sapi::v::RemotePtr> output_;
86 std::unique_ptr<sapi::v::RemotePtr> vm_;
87
88 std::string input_filename_in_sandboxee_;
89 bool jsonnet_vm_was_used_ = false;
90 bool input_was_read_ = false;
91 };
92
ReadInput(const char * filename)93 void JsonnetTest::ReadInput(const char* filename) {
94 std::string in_file_in_sandboxee(std::string("/input/") +
95 basename(const_cast<char*>(&filename[0])));
96 input_filename_in_sandboxee_ = std::move(in_file_in_sandboxee);
97 sapi::v::ConstCStr in_file_var(input_filename_in_sandboxee_.c_str());
98
99 SAPI_ASSERT_OK_AND_ASSIGN(char* input_ptr,
100 api_->c_read_input(0, in_file_var.PtrBefore()));
101 input_ = std::make_unique<sapi::v::RemotePtr>(input_ptr);
102
103 input_was_read_ = true;
104 }
105
EvaluateJsonnetCode(Evaluation type,bool expected_correct)106 void JsonnetTest::EvaluateJsonnetCode(Evaluation type, bool expected_correct) {
107 sapi::v::ConstCStr in_file_var(input_filename_in_sandboxee_.c_str());
108 sapi::v::Int error;
109 char* output_ptr;
110
111 switch (type) {
112 case kBase: {
113 SAPI_ASSERT_OK_AND_ASSIGN(
114 output_ptr,
115 api_->c_jsonnet_evaluate_snippet(vm_.get(), in_file_var.PtrBefore(),
116 input_.get(), error.PtrAfter()));
117 break;
118 }
119
120 case kMultipleFiles: {
121 SAPI_ASSERT_OK_AND_ASSIGN(
122 output_ptr, api_->c_jsonnet_evaluate_snippet_multi(
123 vm_.get(), in_file_var.PtrBefore(), input_.get(),
124 error.PtrAfter()));
125 break;
126 }
127
128 case kYamlStream: {
129 SAPI_ASSERT_OK_AND_ASSIGN(
130 output_ptr, api_->c_jsonnet_evaluate_snippet_stream(
131 vm_.get(), in_file_var.PtrBefore(), input_.get(),
132 error.PtrAfter()));
133 break;
134 }
135 }
136
137 if (expected_correct) {
138 ASSERT_THAT(error.GetValue(), testing::Eq(0));
139 } else {
140 ASSERT_THAT(error.GetValue(), testing::Eq(1));
141 }
142
143 output_ = std::make_unique<sapi::v::RemotePtr>(output_ptr);
144
145 jsonnet_vm_was_used_ = true;
146 }
147
WriteOutput(const char * filename_or_directory,Evaluation type)148 void JsonnetTest::WriteOutput(const char* filename_or_directory,
149 Evaluation type) {
150 bool success;
151
152 switch (type) {
153 case kBase: {
154 std::string out_file_in_sandboxee(
155 std::string("/output/") +
156 basename(const_cast<char*>(&filename_or_directory[0])));
157 sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
158
159 SAPI_ASSERT_OK_AND_ASSIGN(
160 success,
161 api_->c_write_output_file(output_.get(), out_file_var.PtrBefore()));
162 break;
163 }
164 case kMultipleFiles: {
165 std::string out_file_in_sandboxee(std::string("/output/"));
166 sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
167 SAPI_ASSERT_OK_AND_ASSIGN(
168 success, api_->c_write_multi_output_files(
169 output_.get(), out_file_var.PtrBefore(), false));
170 break;
171 }
172
173 case kYamlStream: {
174 std::string out_file_in_sandboxee(
175 std::string("/output/") +
176 basename(const_cast<char*>(&filename_or_directory[0])));
177 sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
178 SAPI_ASSERT_OK_AND_ASSIGN(
179 success,
180 api_->c_write_output_stream(output_.get(), out_file_var.PtrBefore()));
181 break;
182 }
183 }
184
185 ASSERT_THAT(success, testing::Eq(true));
186 }
187
ReadOutput(const char * filename)188 std::string JsonnetTest::ReadOutput(const char* filename) {
189 std::ifstream input_stream(filename);
190 std::string contents((std::istreambuf_iterator<char>(input_stream)),
191 std::istreambuf_iterator<char>());
192 return contents;
193 }
194
195 // One file evaluation to one file
TEST_F(JsonnetTest,OneFileNoDependencies)196 TEST_F(JsonnetTest, OneFileNoDependencies) {
197 constexpr char kInputFile[] = "arith.jsonnet";
198 constexpr char kOutputFile[] = "arith_output";
199 constexpr char kOutputToRead[] = "tests_output/arith_output";
200 constexpr char kOutputToExpect[] = "tests_expected_output/arith.golden";
201
202 ReadInput(kInputFile);
203 EvaluateJsonnetCode(kBase, true);
204 WriteOutput(kOutputFile, kBase);
205
206 std::string produced_output = ReadOutput(kOutputToRead);
207 std::string expected_output = ReadOutput(kOutputToExpect);
208
209 ASSERT_STREQ(produced_output.c_str(), expected_output.c_str());
210 }
211
212 // One file evaluating to one file, dependent on some other files
TEST_F(JsonnetTest,OneFileSomeDependencies)213 TEST_F(JsonnetTest, OneFileSomeDependencies) {
214 constexpr char kInputFile[] = "negroni.jsonnet";
215 constexpr char kOutputFile[] = "negroni_output";
216 constexpr char kOutputToRead[] = "tests_output/negroni_output";
217 constexpr char kOutputToExpect[] = "tests_expected_output/negroni.golden";
218
219 ReadInput(kInputFile);
220 EvaluateJsonnetCode(kBase, true);
221 WriteOutput(kOutputFile, kBase);
222
223 const std::string produced_output = ReadOutput(kOutputToRead);
224 const std::string expected_output = ReadOutput(kOutputToExpect);
225
226 ASSERT_STREQ(produced_output.c_str(), expected_output.c_str());
227 }
228
229 // One file evaluating to two files
TEST_F(JsonnetTest,MultipleFiles)230 TEST_F(JsonnetTest, MultipleFiles) {
231 constexpr char kInputFile[] = "multiple_files_example.jsonnet";
232 constexpr char kOutputFile[] = "";
233 constexpr char kOutputToRead1[] = "tests_output/first_file.json";
234 constexpr char kOutputToRead2[] = "tests_output/second_file.json";
235 constexpr char kOutputToExpect1[] = "tests_expected_output/first_file.json";
236 constexpr char kOutputToExpect2[] = "tests_expected_output/second_file.json";
237
238 ReadInput(kInputFile);
239 EvaluateJsonnetCode(kMultipleFiles, true);
240 WriteOutput(kOutputFile, kMultipleFiles);
241
242 const std::string produced_output_1 = ReadOutput(kOutputToRead1);
243 const std::string produced_output_2 = ReadOutput(kOutputToRead2);
244 const std::string expected_output_1 = ReadOutput(kOutputToExpect1);
245 const std::string expected_output_2 = ReadOutput(kOutputToExpect2);
246
247 ASSERT_STREQ(produced_output_1.c_str(), expected_output_1.c_str());
248 ASSERT_STREQ(produced_output_2.c_str(), expected_output_2.c_str());
249 }
250
251 // One file evaluating to yaml stream format
TEST_F(JsonnetTest,YamlStream)252 TEST_F(JsonnetTest, YamlStream) {
253 constexpr char kInputFile[] = "yaml_stream_example.jsonnet";
254 constexpr char kOutputFile[] = "yaml_stream_example.yaml";
255 constexpr char kOutputToRead[] = "tests_output/yaml_stream_example.yaml";
256 constexpr char kOutputToExpect[] =
257 "tests_expected_output/yaml_stream_example.yaml";
258
259 ReadInput(kInputFile);
260 EvaluateJsonnetCode(kYamlStream, true);
261 WriteOutput(kOutputFile, kYamlStream);
262
263 const std::string produced_output = ReadOutput(kOutputToRead);
264 const std::string expected_output = ReadOutput(kOutputToExpect);
265
266 ASSERT_STREQ(produced_output.c_str(), expected_output.c_str());
267 }
268
269 // One file depended on some other files not accessible by the sandbox
TEST_F(JsonnetTest,BadEvaluation)270 TEST_F(JsonnetTest, BadEvaluation) {
271 constexpr char kInputFile[] = "imports.jsonnet";
272
273 ReadInput(kInputFile);
274 EvaluateJsonnetCode(kBase, false);
275 }
276
277 } // namespace
278