blob: 421dfeb6bda668070f4da059a5d318f39a33f24c [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/fidlcat/lib/comparator.h"
#include <iostream>
#include <string>
#include <utility>
#include <vector>
namespace fidlcat {
void Comparator::CompareInput(std::string_view syscall_inputs, uint64_t actual_pid,
uint64_t actual_tid) {
// A difference has already been found, no need for anymore comparing.
if (found_difference_) {
return;
}
std::unique_ptr<Message> expected_inputs = GetNextExpectedMessage(actual_pid, actual_tid);
if (!expected_inputs) {
found_difference_ = true;
return;
}
// Now get process name, and remove the header from syscall_inputs.
std::string_view actual_process_name =
syscall_inputs.length() > 0 && syscall_inputs[0] == '\n'
? syscall_inputs.substr(1, syscall_inputs.find(' ') - 1)
: syscall_inputs.substr(0, syscall_inputs.find(' '));
if (actual_process_name.compare(expected_inputs->process_name) != 0) {
found_difference_ = true;
compare_results_ << "Different process names for actual pid:tid " << actual_pid << ":"
<< actual_tid << ", matched with expected pid:tid "
<< expected_pids_[actual_pid] << ":"
<< expected_pids_tids_[std::make_pair(actual_pid, actual_tid)].second
<< ", expected " << expected_inputs->process_name << " actual was "
<< actual_process_name;
return;
}
std::string actual_inputs = static_cast<std::string>(
syscall_inputs.substr(syscall_inputs.find(' ', syscall_inputs.find(":")) + 1));
// Now we replace handles ids, then compare.
static std::vector<std::string> handle_texts = {"handle: ", "handle = "};
for (size_t i = 0; i < handle_texts.size(); i++) {
if (!CouldReplaceHandles(&actual_inputs, expected_inputs->message, handle_texts[i])) {
found_difference_ = true;
return;
}
}
if (actual_inputs.compare(expected_inputs->message) != 0) {
compare_results_ << "Different messages. expected " << expected_inputs->message << " got "
<< actual_inputs;
found_difference_ = true;
return;
}
}
void Comparator::CompareOutput(std::string_view syscall_outputs, uint64_t actual_pid,
uint64_t actual_tid) {
// A difference has already been found, no need for anymore comparison.
if (found_difference_) {
return;
}
std::unique_ptr<Message> expected_outputs = GetNextExpectedMessage(actual_pid, actual_tid);
if (!expected_outputs) {
found_difference_ = true;
return;
}
// Does output begin with process name, pid and tid?
std::string actual_outputs;
//'process_name pid:tid ' with one char for process_name/pid/tid
constexpr size_t kMinNbCharHeader = 5;
if (syscall_outputs.find("->") > kMinNbCharHeader) {
// We check that processes names match.
std::string_view actual_process_name =
syscall_outputs.length() > 0 && syscall_outputs[0] == '\n'
? syscall_outputs.substr(1, syscall_outputs.find(' ') - 1)
: syscall_outputs.substr(0, syscall_outputs.find(' '));
if (actual_process_name.compare(expected_outputs->process_name) != 0) {
found_difference_ = true;
compare_results_ << "Different process names for actual pid:tid " << actual_pid << ":"
<< actual_tid << ", matched with expected pid:tid "
<< expected_pids_[actual_pid] << ":"
<< expected_pids_tids_[std::make_pair(actual_pid, actual_tid)].second
<< ", expected " << expected_outputs->process_name << " actual was "
<< actual_process_name;
return;
}
actual_outputs = static_cast<std::string>(
syscall_outputs.substr(syscall_outputs.find(' ', syscall_outputs.find(":")) + 1));
} else {
actual_outputs = syscall_outputs.length() > 0 && syscall_outputs[0] == '\n'
? static_cast<std::string>(syscall_outputs.substr(1))
: static_cast<std::string>(syscall_outputs);
}
// Now we replace handle ids, then compare.
static std::vector<std::string> handle_texts = {"handle: ", "handle = "};
for (size_t i = 0; i < handle_texts.size(); i++) {
if (!CouldReplaceHandles(&actual_outputs, expected_outputs->message, handle_texts[i])) {
found_difference_ = true;
return;
}
}
if (actual_outputs.compare(expected_outputs->message) != 0) {
compare_results_ << "Different messages, expected " << expected_outputs->message << " got "
<< actual_outputs;
found_difference_ = true;
return;
}
}
void Comparator::DecodingError(std::string error) {
found_difference_ = true;
compare_results_ << "Unexpected decoding error in the current execution:\n" << error;
}
bool Comparator::CouldReplaceHandles(std::string* actual, std::string_view expected,
std::string_view handle_text) {
size_t handle_position = actual->find(handle_text);
while (handle_position != std::string::npos) {
if (expected.find(handle_text, handle_position) != handle_position) {
compare_results_ << "Different messages, expected " << expected << " actual " << actual;
return false;
}
handle_position += handle_text.length();
uint32_t act_handle = ExtractHexUint32(std::string_view(*actual).substr(handle_position));
uint32_t exp_handle = ExtractHexUint32(expected.substr(handle_position));
// Actual handle matches to the expected
if (expected_handles_.find(act_handle) == expected_handles_.end()) {
expected_handles_[act_handle] = exp_handle;
} else if (expected_handles_[act_handle] != exp_handle) {
compare_results_ << std::hex;
compare_results_ << "Different handles, actual handle " << act_handle << " should be "
<< expected_handles_[act_handle] << " in golden file but was " << exp_handle;
compare_results_ << std::dec;
return false;
}
// and no other actual handle matches to the same expected.
if (actual_handles_.find(exp_handle) == actual_handles_.end()) {
actual_handles_[exp_handle] = act_handle;
} else if (actual_handles_[exp_handle] != act_handle) {
compare_results_ << std::hex;
compare_results_ << "Different handles, expected handle " << exp_handle << " should be "
<< actual_handles_[exp_handle] << " in this execution but was "
<< act_handle;
compare_results_ << std::dec;
return false;
}
size_t act_handle_end = actual->find_first_not_of("abcdef1234567890", handle_position);
size_t exp_handle_end = expected.find_first_not_of("abcdef1234567890", handle_position);
actual->replace(handle_position, act_handle_end - handle_position,
expected.substr(handle_position, exp_handle_end - handle_position));
handle_position = actual->find(handle_text, handle_position);
}
return true;
}
uint64_t Comparator::ExtractUint64(std::string_view str) {
uint64_t result = 0;
for (size_t i = 0; i < str.size(); ++i) {
char value = str[i];
if ((value < '0') || (value > '9')) {
break;
}
result = 10 * result + (value - '0');
}
return result;
}
uint32_t Comparator::ExtractHexUint32(std::string_view str) {
uint32_t result = 0;
for (size_t i = 0; i < str.size(); ++i) {
char value = str[i];
if ('0' <= value && value <= '9') {
result = 16 * result + (value - '0');
} else if ('a' <= value && value <= 'f') {
result = 16 * result + (value - 'a' + 10);
} else {
break;
}
}
return result;
}
std::unique_ptr<Message> Comparator::GetNextExpectedMessage(uint64_t actual_pid,
uint64_t actual_tid) {
uint64_t expected_pid = 0;
uint64_t expected_tid = 0;
// Have we already seen this pid?
if (expected_pids_.find(actual_pid) == expected_pids_.end()) {
if (pids_by_order_of_appearance_.empty()) {
compare_results_ << "More actual processes than expected.";
return nullptr;
}
// We assume the order of appearance of pids are the same in the golden file and in the current
// execution.
expected_pid = pids_by_order_of_appearance_.front();
pids_by_order_of_appearance_.pop_front();
expected_pids_[actual_pid] = expected_pid;
// As each expected pid appears only once in pids_by_order_of_appearance_, we know for sure that
// only this actual_pid can link to this expected_pid, no need for a reverse map.
} else {
expected_pid = expected_pids_[actual_pid];
}
// Have we already seen this tid?
if (expected_pids_tids_.find(std::make_pair(actual_pid, actual_tid)) ==
expected_pids_tids_.end()) {
expected_pid = expected_pids_[actual_pid];
if (tids_by_order_of_appearance_[expected_pid].empty()) {
compare_results_ << "More actual threads than expected for actual pid " << actual_pid
<< " matched with expected pid " << expected_pid;
return nullptr;
}
// We assume the order of appearance of tids per pid are the same in the golden file and in the
// current execution.
expected_tid = tids_by_order_of_appearance_[expected_pid].front();
tids_by_order_of_appearance_[expected_pid].pop_front();
expected_pids_tids_[std::make_pair(actual_pid, actual_tid)] =
std::make_pair(expected_pid, expected_tid);
// Same as above, no need for a reverse map.
} else {
expected_tid = expected_pids_tids_[std::make_pair(actual_pid, actual_tid)].second;
}
if (messages_[std::make_pair(expected_pid, expected_tid)].empty()) {
compare_results_ << "More actual messages than expected for actual pid:tid " << actual_pid
<< ":" << actual_tid << " matched with expected pid:tid " << expected_pid
<< ":" << expected_tid;
return nullptr;
}
std::unique_ptr<Message> expected_message =
std::move(messages_[std::make_pair(expected_pid, expected_tid)].front());
messages_[std::make_pair(expected_pid, expected_tid)].pop_front();
return expected_message;
}
void Comparator::ParseGolden(std::string_view golden_file_contents) {
size_t processed_char_count = 0;
std::string_view cur_msg = GetMessage(golden_file_contents, &processed_char_count);
uint64_t previous_pid = 0;
uint64_t previous_tid = 0;
std::string_view previous_process_name;
while (!cur_msg.empty()) {
uint64_t pid, tid;
std::string_view process_name;
//'process_name pid:tid ' with one char for process_name/pid/tid
constexpr size_t kMinNbCharHeader = 5;
// The message does not contain the process name and pid:tid.
if (cur_msg.find("->") <= kMinNbCharHeader) {
pid = previous_pid;
tid = previous_tid;
process_name = previous_process_name;
} else {
pid = ExtractUint64(cur_msg.substr(cur_msg.find(' ') + 1));
tid = ExtractUint64(cur_msg.substr(cur_msg.find(':') + 1));
process_name = cur_msg.substr(0, cur_msg.find(' '));
// We remove process name, pid and tid from the message, as those are now saved in the map
// explicitely
cur_msg = cur_msg.substr(cur_msg.find(' ', cur_msg.find(':')) + 1);
}
// Have we already seen this pid?
if (tids_by_order_of_appearance_.find(pid) == tids_by_order_of_appearance_.end()) {
std::deque<uint64_t> tids = {tid};
tids_by_order_of_appearance_[pid] = tids;
pids_by_order_of_appearance_.push_back(pid);
messages_[std::make_pair(pid, tid)] = std::deque<std::unique_ptr<Message>>();
}
// Have we already seen this tid?
else if (messages_.find(std::make_pair(pid, tid)) == messages_.end()) {
tids_by_order_of_appearance_[pid].push_back(tid);
messages_[std::make_pair(pid, tid)] = std::deque<std::unique_ptr<Message>>();
}
messages_[std::make_pair(pid, tid)].push_back(std::make_unique<Message>(process_name, cur_msg));
golden_file_contents = golden_file_contents.substr(processed_char_count);
cur_msg = GetMessage(golden_file_contents, &processed_char_count);
previous_pid = pid;
previous_tid = tid;
previous_process_name = process_name;
}
}
std::string_view Comparator::GetMessage(std::string_view messages, size_t* processed_char_count) {
// begin points to the beginning of the line, end to its end.
size_t begin = 0;
size_t end = messages.find('\n', begin);
// Ignore fidlcat startup lines or empty lines.
while (end != std::string::npos) {
if (!IgnoredLine(messages.substr(begin, end - begin))) {
break;
}
begin = end + 1;
end = messages.find('\n', begin);
}
// Now we get the message.
size_t bpos = begin;
while (end != std::string::npos) {
// New line indicates end of syscall input or output.
if (bpos < begin && end == begin && messages[begin] == '\n') {
break;
}
// Beginning of syscall output display.
if (bpos < begin && messages.substr(begin, end - begin).find(" ->") == 0) {
break;
}
begin = end + 1;
end = messages.find('\n', begin);
}
*processed_char_count = begin;
// Note that as expected_output_ is a string_view, substr() returns a string_view as well.
return messages.substr(bpos, begin - bpos);
}
bool Comparator::IgnoredLine(std::string_view line) {
if (line.length() == 0) {
return true;
}
if (line.length() == 1 && line[0] == '\n') {
return true;
}
static std::vector<std::string> to_be_ignored = {"Checking", "Debug", "Launched", "Monitoring",
"Stop"};
for (size_t i = 0; i < to_be_ignored.size(); i++) {
if (line.find(to_be_ignored[i]) == 0) {
return true;
}
}
return false;
}
} // namespace fidlcat