// Copyright 2020 Google LLC // // 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 // // https://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. #include #include #include #include #include #include #include "gtest/gtest.h" #include "contrib/jsonnet/jsonnet_base_sandbox.h" #include "sandboxed_api/util/path.h" #include "sandboxed_api/util/status_matchers.h" namespace { class JsonnetTest : public ::testing::Test { protected: enum Evaluation { kBase, kMultipleFiles, kYamlStream }; void SetUp() override { // Get paths to where input and output is stored. char buffer[256]; int error = readlink("/proc/self/exe", buffer, 256); ASSERT_GE(error, 0); std::pair parts_of_path = sapi::file::SplitPath(buffer); absl::string_view binary_path = parts_of_path.first; std::string input_path = sapi::file::JoinPath(binary_path, "tests_input", "dummy_input"); std::string output_path = sapi::file::JoinPath(binary_path, "tests_output", "dummy_input"); // Set up sandbox and api. sandbox_ = std::make_unique(input_path, output_path); ASSERT_THAT(sandbox_->Init(), sapi::IsOk()); api_ = std::make_unique(sandbox_.get()); // Initialize library's main structure. SAPI_ASSERT_OK_AND_ASSIGN(JsonnetVm * vm_ptr, api_->c_jsonnet_make()); vm_ = std::make_unique(vm_ptr); } void TearDown() override { if (jsonnet_vm_was_used_) { SAPI_ASSERT_OK_AND_ASSIGN( char* result, api_->c_jsonnet_realloc(vm_.get(), output_.get(), 0)); } ASSERT_THAT(api_->c_jsonnet_destroy(vm_.get()), sapi::IsOk()); if (input_was_read_) { ASSERT_THAT(api_->c_free_input(input_.get()), sapi::IsOk()); } } // Reads input from file. void ReadInput(const char* filename); // Evaluates jsonnet code. void EvaluateJsonnetCode(Evaluation type, bool expected_correct); // Writes output to file. void WriteOutput(const char* filename_or_directory, Evaluation type); // Reads the output written to a file by library function / expected output. std::string ReadOutput(const char* filename); std::unique_ptr sandbox_; std::unique_ptr api_; std::unique_ptr input_; std::unique_ptr output_; std::unique_ptr vm_; std::string input_filename_in_sandboxee_; bool jsonnet_vm_was_used_ = false; bool input_was_read_ = false; }; void JsonnetTest::ReadInput(const char* filename) { std::string in_file_in_sandboxee(std::string("/input/") + basename(const_cast(&filename[0]))); input_filename_in_sandboxee_ = std::move(in_file_in_sandboxee); sapi::v::ConstCStr in_file_var(input_filename_in_sandboxee_.c_str()); SAPI_ASSERT_OK_AND_ASSIGN(char* input_ptr, api_->c_read_input(0, in_file_var.PtrBefore())); input_ = std::make_unique(input_ptr); input_was_read_ = true; } void JsonnetTest::EvaluateJsonnetCode(Evaluation type, bool expected_correct) { sapi::v::ConstCStr in_file_var(input_filename_in_sandboxee_.c_str()); sapi::v::Int error; char* output_ptr; switch (type) { case kBase: { SAPI_ASSERT_OK_AND_ASSIGN( output_ptr, api_->c_jsonnet_evaluate_snippet(vm_.get(), in_file_var.PtrBefore(), input_.get(), error.PtrAfter())); break; } case kMultipleFiles: { SAPI_ASSERT_OK_AND_ASSIGN( output_ptr, api_->c_jsonnet_evaluate_snippet_multi( vm_.get(), in_file_var.PtrBefore(), input_.get(), error.PtrAfter())); break; } case kYamlStream: { SAPI_ASSERT_OK_AND_ASSIGN( output_ptr, api_->c_jsonnet_evaluate_snippet_stream( vm_.get(), in_file_var.PtrBefore(), input_.get(), error.PtrAfter())); break; } } if (expected_correct) { ASSERT_THAT(error.GetValue(), testing::Eq(0)); } else { ASSERT_THAT(error.GetValue(), testing::Eq(1)); } output_ = std::make_unique(output_ptr); jsonnet_vm_was_used_ = true; } void JsonnetTest::WriteOutput(const char* filename_or_directory, Evaluation type) { bool success; switch (type) { case kBase: { std::string out_file_in_sandboxee( std::string("/output/") + basename(const_cast(&filename_or_directory[0]))); sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str()); SAPI_ASSERT_OK_AND_ASSIGN( success, api_->c_write_output_file(output_.get(), out_file_var.PtrBefore())); break; } case kMultipleFiles: { std::string out_file_in_sandboxee(std::string("/output/")); sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str()); SAPI_ASSERT_OK_AND_ASSIGN( success, api_->c_write_multi_output_files( output_.get(), out_file_var.PtrBefore(), false)); break; } case kYamlStream: { std::string out_file_in_sandboxee( std::string("/output/") + basename(const_cast(&filename_or_directory[0]))); sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str()); SAPI_ASSERT_OK_AND_ASSIGN( success, api_->c_write_output_stream(output_.get(), out_file_var.PtrBefore())); break; } } ASSERT_THAT(success, testing::Eq(true)); } std::string JsonnetTest::ReadOutput(const char* filename) { std::ifstream input_stream(filename); std::string contents((std::istreambuf_iterator(input_stream)), std::istreambuf_iterator()); return contents; } // One file evaluation to one file TEST_F(JsonnetTest, OneFileNoDependencies) { constexpr char kInputFile[] = "arith.jsonnet"; constexpr char kOutputFile[] = "arith_output"; constexpr char kOutputToRead[] = "tests_output/arith_output"; constexpr char kOutputToExpect[] = "tests_expected_output/arith.golden"; ReadInput(kInputFile); EvaluateJsonnetCode(kBase, true); WriteOutput(kOutputFile, kBase); std::string produced_output = ReadOutput(kOutputToRead); std::string expected_output = ReadOutput(kOutputToExpect); ASSERT_STREQ(produced_output.c_str(), expected_output.c_str()); } // One file evaluating to one file, dependent on some other files TEST_F(JsonnetTest, OneFileSomeDependencies) { constexpr char kInputFile[] = "negroni.jsonnet"; constexpr char kOutputFile[] = "negroni_output"; constexpr char kOutputToRead[] = "tests_output/negroni_output"; constexpr char kOutputToExpect[] = "tests_expected_output/negroni.golden"; ReadInput(kInputFile); EvaluateJsonnetCode(kBase, true); WriteOutput(kOutputFile, kBase); const std::string produced_output = ReadOutput(kOutputToRead); const std::string expected_output = ReadOutput(kOutputToExpect); ASSERT_STREQ(produced_output.c_str(), expected_output.c_str()); } // One file evaluating to two files TEST_F(JsonnetTest, MultipleFiles) { constexpr char kInputFile[] = "multiple_files_example.jsonnet"; constexpr char kOutputFile[] = ""; constexpr char kOutputToRead1[] = "tests_output/first_file.json"; constexpr char kOutputToRead2[] = "tests_output/second_file.json"; constexpr char kOutputToExpect1[] = "tests_expected_output/first_file.json"; constexpr char kOutputToExpect2[] = "tests_expected_output/second_file.json"; ReadInput(kInputFile); EvaluateJsonnetCode(kMultipleFiles, true); WriteOutput(kOutputFile, kMultipleFiles); const std::string produced_output_1 = ReadOutput(kOutputToRead1); const std::string produced_output_2 = ReadOutput(kOutputToRead2); const std::string expected_output_1 = ReadOutput(kOutputToExpect1); const std::string expected_output_2 = ReadOutput(kOutputToExpect2); ASSERT_STREQ(produced_output_1.c_str(), expected_output_1.c_str()); ASSERT_STREQ(produced_output_2.c_str(), expected_output_2.c_str()); } // One file evaluating to yaml stream format TEST_F(JsonnetTest, YamlStream) { constexpr char kInputFile[] = "yaml_stream_example.jsonnet"; constexpr char kOutputFile[] = "yaml_stream_example.yaml"; constexpr char kOutputToRead[] = "tests_output/yaml_stream_example.yaml"; constexpr char kOutputToExpect[] = "tests_expected_output/yaml_stream_example.yaml"; ReadInput(kInputFile); EvaluateJsonnetCode(kYamlStream, true); WriteOutput(kOutputFile, kYamlStream); const std::string produced_output = ReadOutput(kOutputToRead); const std::string expected_output = ReadOutput(kOutputToExpect); ASSERT_STREQ(produced_output.c_str(), expected_output.c_str()); } // One file depended on some other files not accessible by the sandbox TEST_F(JsonnetTest, BadEvaluation) { constexpr char kInputFile[] = "imports.jsonnet"; ReadInput(kInputFile); EvaluateJsonnetCode(kBase, false); } } // namespace