blob: dc864f0aace4f159a21ddaf71c79befda7a571f3 [file] [log] [blame]
// 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 <fstream>
#include <memory>
#include <string>
#include <unordered_set>
#include <tuple>
#include <vector>
#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<std::string> 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<std::string> 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<std::string> cols = absl::StrSplit(row, ',');
if (first) {
// header row should be: header1,header2,...,vmsize,filesize
std::vector<std::string> 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::tuple<std::string, int, int>>& 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<bloaty::RollupOutput> output_;
const bloaty::RollupRow* top_row_;
};
constexpr int BloatyTest::kUnknown;
constexpr int BloatyTest::kSameAsVM;
#endif // BLOATY_TESTS_TEST_H_