// Copyright 2016 Google Inc. All Rights Reserved. // // 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. #ifndef BLOATY_TESTS_TEST_H_ #define BLOATY_TESTS_TEST_H_ #include #include #include #include #include #include #include "absl/strings/numbers.h" #include "absl/strings/str_split.h" #include "gmock/gmock.h" #include "google/protobuf/text_format.h" #include "gtest/gtest.h" #include "strarr.h" #include "bloaty.h" #include "bloaty.pb.h" #if defined(_MSC_VER) #define PATH_MAX 4096 #endif inline bool GetFileSize(const std::string& filename, uint64_t* size) { FILE* file = fopen(filename.c_str(), "rb"); if (!file) { std::cerr << "Couldn't get file size for: " << filename << "\n"; return false; } fseek(file, 0L, SEEK_END); *size = ftell(file); fclose(file); return true; } inline std::string GetTestDirectory() { char pathbuf[PATH_MAX]; if (!getcwd(pathbuf, sizeof(pathbuf))) { return ""; } std::string path(pathbuf); size_t pos = path.rfind('/'); return path.substr(pos + 1); } inline std::string DebugString(const google::protobuf::Message& message) { std::string ret; google::protobuf::TextFormat::PrintToString(message, &ret); return ret; } #define NONE_STRING "[None]" // Testing Bloaty requires a delicate balance. Bloaty's output is by its // nature very compiler and platform dependent. So we want to verify correct // operation without overspecifying how the platform should behave. class BloatyTest : public ::testing::Test { protected: void CheckConsistencyForRow(const bloaty::RollupRow& row, bool is_toplevel, bool diff_mode, int* count) { // If any children exist, they should sum up to this row's values. // Also none of the children should have the same name. std::unordered_set names; if (row.sorted_children.size() > 0) { uint64_t vmtotal = 0; uint64_t filetotal = 0; for (const auto& child : row.sorted_children) { vmtotal += child.vmsize; filetotal += child.filesize; CheckConsistencyForRow(child, false, diff_mode, count); ASSERT_TRUE(names.insert(child.name).second); ASSERT_FALSE(child.vmsize == 0 && child.filesize == 0); } if (!diff_mode) { ASSERT_EQ(vmtotal, row.vmsize); ASSERT_EQ(filetotal, row.filesize); } } else { // Count leaf rows. *count += 1; } if (!is_toplevel && row.sorted_children.size() == 1) { ASSERT_NE(NONE_STRING, row.sorted_children[0].name); } } void CheckCSVConsistency(int row_count) { std::ostringstream stream; bloaty::OutputOptions options; options.output_format = bloaty::OutputFormat::kCSV; output_->Print(options, &stream); std::string csv_output = stream.str(); std::vector rows = absl::StrSplit(csv_output, '\n'); // Output ends with a final '\n', trim this. ASSERT_EQ("", rows[rows.size() - 1]); rows.pop_back(); ASSERT_GT(rows.size(), 0); // There should be a header row. ASSERT_EQ(rows.size() - 1, row_count); bool first = true; for (const auto& row : rows) { std::vector cols = absl::StrSplit(row, ','); if (first) { // header row should be: header1,header2,...,vmsize,filesize std::vector expected_headers(output_->source_names()); expected_headers.push_back("vmsize"); expected_headers.push_back("filesize"); ASSERT_EQ(cols, expected_headers); first = false; } else { // Final two columns should parse as integer. int out; ASSERT_EQ(output_->source_names().size() + 2, cols.size()); ASSERT_TRUE(absl::SimpleAtoi(cols[cols.size() - 1], &out)); ASSERT_TRUE(absl::SimpleAtoi(cols[cols.size() - 2], &out)); } } } void CheckConsistency(const bloaty::Options& options) { ASSERT_EQ(options.base_filename_size() > 0, output_->diff_mode()); if (!output_->diff_mode()) { size_t total_input_size = 0; for (const auto& filename : options.filename()) { uint64_t size; ASSERT_TRUE(GetFileSize(filename, &size)); total_input_size += size; } ASSERT_EQ(top_row_->filesize, total_input_size); } int rows = 0; CheckConsistencyForRow(*top_row_, true, output_->diff_mode(), &rows); CheckCSVConsistency(rows); ASSERT_EQ("TOTAL", top_row_->name); } std::string JoinStrings(const std::vector& strings) { std::string ret = strings[0]; for (size_t i = 1; i < strings.size(); i++) { ret += " " + strings[i]; } return ret; } bool TryRunBloatyWithOptions(const bloaty::Options& options, const bloaty::OutputOptions& output_options) { output_.reset(new bloaty::RollupOutput); top_row_ = &output_->toplevel_row(); std::string error; bloaty::MmapInputFileFactory factory; if (bloaty::BloatyMain(options, factory, output_.get(), &error)) { CheckConsistency(options); output_->Print(output_options, &std::cerr); return true; } else { std::cerr << "Bloaty returned error:" << error << "\n"; return false; } } bool TryRunBloaty(const std::vector& strings) { bloaty::Options options; bloaty::OutputOptions output_options; std::string error; StrArr str_arr(strings); int argc = strings.size(); char** argv = str_arr.get(); bool ok = bloaty::ParseOptions(false, &argc, &argv, &options, &output_options, &error); if (!ok) { std::cerr << "Error parsing options: " << error; return false; } return TryRunBloatyWithOptions(options, output_options); } void RunBloaty(const std::vector& strings) { std::cerr << "Running bloaty: " << JoinStrings(strings) << "\n"; ASSERT_TRUE(TryRunBloaty(strings)); } void RunBloatyWithOptions(const bloaty::Options& options, const bloaty::OutputOptions& output_options) { std::cerr << "Running bloaty, options: " << DebugString(options) << "\n"; ASSERT_TRUE(TryRunBloatyWithOptions(options, output_options)); } void AssertBloatyFails(const std::vector& strings, const std::string& /*msg_regex*/) { // TODO(haberman): verify msg_regex by making all errors logged to a // standard place. ASSERT_FALSE(TryRunBloaty(strings)); } // Special constants for asserting of children. static constexpr int kUnknown = -1; static constexpr int kSameAsVM = -2; // Only for file size. void AssertChildren( const bloaty::RollupRow& row, const std::vector>& children) { size_t i = 0; for (const auto& child : row.sorted_children) { std::string expected_name; int expected_vm, expected_file; std::tie(expected_name, expected_vm, expected_file) = children[i]; // Excluding leading '_' is kind of a hack to exclude symbols // automatically inserted by the compiler, like __x86.get_pc_thunk.bx // for 32-bit x86 builds or _IO_stdin_used in binaries. // // Excluding leading '[' is for things like this: // // [None] // [ELF Headers] // [AR Headers] // etc. if (child.name[0] == '[' || child.name[0] == '_') { continue; } EXPECT_EQ(expected_name, child.name); // <0 indicates that we don't know what the exact size should be (for // example for functions). if (expected_vm == kUnknown) { // Always pass. } else if (expected_vm > 0) { EXPECT_GE(child.vmsize, expected_vm); // Allow some overhead. EXPECT_LE(child.vmsize, (expected_vm * 1.1) + 100); } else { ASSERT_TRUE(false); } if (expected_file == kSameAsVM) { expected_file = child.vmsize; } if (expected_file != kUnknown) { EXPECT_GE(child.filesize, expected_file); // Allow some overhead. EXPECT_LE(child.filesize, (expected_file * 1.2) + 180); } if (++i == children.size()) { // We allow the actual data to have excess elements. break; } } // All expected elements must be present. ASSERT_EQ(i, children.size()); } const bloaty::RollupRow* FindRow(const std::string& name) { for (const auto& child : top_row_->sorted_children) { if (child.name == name) { return &child; } } EXPECT_TRUE(false) << name; return nullptr; } std::unique_ptr output_; const bloaty::RollupRow* top_row_; }; constexpr int BloatyTest::kUnknown; constexpr int BloatyTest::kSameAsVM; #endif // BLOATY_TESTS_TEST_H_