blob: 73a20ece0150d4110a6aeb990e9bb2d9d7abbfbc [file] [log] [blame] [edit]
// 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.
#ifndef TOOLS_FIDLCAT_LIB_COMPARATOR_H_
#define TOOLS_FIDLCAT_LIB_COMPARATOR_H_
#include <deque>
#include <fstream>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include "tools/fidlcat/lib/message_graph.h"
namespace fidlcat {
// To compare the messages stored in the golden file to the messages intercepted by fidlcat in the
// current execution, this class first builds a GoldenMessageGraph from the golden file when
// initialized. It also creates an empty ActualMessageGraph, actual_message_graph_, for the current
// execution. Then for each message passed to CompareInput or CompareOutput, it updates
// actual_message_graph_ by inserting the new message in it. If this message can be matched uniquely
// to a message from the golden_message_graph_, we record it, and try to propagate this matching
// along dependenies in the graphs. When there are no more messages to receive, that is to say when
// FinishComparison is called, we propagate (along dependencies) and reverse propagate
// (along reverse dependencies) matchings for all nodes.
class Comparator {
public:
Comparator(std::string_view compare_file_name, std::ostream& os) : compare_results_(os) {
std::string golden_file_contents;
std::ifstream compare_file(compare_file_name);
golden_file_contents.assign((std::istreambuf_iterator<char>(compare_file)),
(std::istreambuf_iterator<char>()));
ParseGolden(golden_file_contents);
}
// Creates a new node in actual_message_graph_ and tries to match.
void CompareInput(std::string_view syscall_inputs, std::string_view actual_process_name,
uint64_t actual_pid, uint64_t actual_tid);
void CompareOutput(std::string_view syscall_outputs, std::string_view actual_process_name,
uint64_t actual_pid, uint64_t actual_tid);
// As the golden file should not contain any error, any error in the actual execution results in
// an error message.
void DecodingError(std::string_view error);
// Tries, using all the information in golden_message_graph_ and actual_message_graph_, to match
// as many nodes as possible to one another, and outputs the result of the comparison.
void FinishComparison();
protected:
// For testing, expected_output_ should then be set using ParseGolden.
Comparator(std::ostream& os) : compare_results_(os) {}
// Given a message node for the current execution, see if it can be uniquely matched with a golden
// message node. If there is exactly one golden node that could match returns it, and returns
// nullptr otherwise. In case no golden node could match this message, outputs an error to
// compare_results_.
std::shared_ptr<GoldenMessageNode> UniqueMatchToGolden(
std::shared_ptr<ActualMessageNode> actual_message_node);
// Given an actual node and the golden node that should match it, checks that this match is
// possible (neither node is already matched, and they have the same number of dependencies), and
// recursively propagates this matching along all dependency links. Returns false if an
// inconsistency in the matching is found. If reverse_propagate is set to true, also runs
// ReversePropagate on actual_node.
bool PropagateMatch(std::shared_ptr<ActualNode> actual_node,
std::shared_ptr<GoldenNode> golden_node, bool reverse_propagate);
// Given an actual node with a matching golden node, propagates this matching
// along the reverse dependency links of actual_node and alls PropagateMatch(with
// reverse_propagate set to true) for any new node matched. Returns false if an inconsistency in
// the matching was found while propagating.
// Assumes that PropagateMatch was called successfully on actual_node before, in particular that
// actual_node->matching_golden_node() is not null, and that the actual_message_graph_ is
// complete, that is to say no more messages/nodes/links will be added to it.
// Note: this function relies on PropagateMatching to take care of recursion termination, and
// never calls itself directly (the code here could be inserted at the end of PropagateMatch with
// the same behavior).
bool ReversePropagateMatch(std::shared_ptr<ActualNode> actual_node);
// Creates the golden_message_graph_ from the contents of the file.
void ParseGolden(std::string_view golden_file_contents);
// Returns the first block of syscall input or output from messages, and stores the number of
// characters processed in processed_char_count (which may be different from the length of the
// message if some lines from messages were ignored).
static std::string_view GetMessage(std::string_view messages, size_t* processed_char_count);
// golden_message_graph contains all the information about the execution saved in the golden file,
// while acutal_message_graph is constructed progressively, every time fidlcat intercepts a
// message in the current execution.
GoldenMessageGraph golden_message_graph_;
ActualMessageGraph actual_message_graph_;
// We need this map to link output messages to their corresponding input messages.
std::map<uint64_t, std::shared_ptr<ActualMessageNode>> last_unmatched_input_from_tid_;
private:
std::ostream& compare_results_;
// Returns true if line is the final line of a multiline request/response (ie begins with the
// proper number of spaces as indentation, then a sequence of " }" or " ]") We rely here on
// fidl_codec printing: it indents all the contents of a message by at least (indentation of
// beginning of message + 1), and the last line of the message will be a line containing exactly
// the same indentation as the beginning of the message and one or more closing brackets.
static bool ClosingSequence(std::string_view line, size_t indentation);
// Returns true if line is not part of a message (ie a fidlcat startup indication or a newline).
static bool IgnoredLine(std::string_view line);
// Expects decimal numbers.
static uint64_t ExtractUint64(std::string_view str);
// Removes the header (process name pid:tid) from a message. If there is no header, returns the
// string unchanged. If pid, tid and process_name pointers are passed as arguments, updates them
// if a header was present, leaves them unchanged otherwise.
static std::string_view AnalyzesAndRemovesHeader(std::string_view message,
std::string* process_name = nullptr,
uint64_t* pid = nullptr,
uint64_t* tid = nullptr);
// Returns true if message is the input message of a syscall with no return value.
static bool HasReturn(std::string_view message);
};
} // namespace fidlcat
#endif // TOOLS_FIDLCAT_LIB_COMPARATOR_H_