blob: 38362864e933f4f4d0ec3f45bf5932a05ab7f838 [file] [log] [blame]
// Copyright 2018 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 <unittest/unittest.h>
#include <banjo/flat_ast.h>
#include <banjo/lexer.h>
#include <banjo/parser.h>
#include <banjo/source_file.h>
#include <banjo/tree_visitor.h>
#include "examples.h"
#include "test_library.h"
namespace {
// A TreeVisitor that reads in a file and spits back out the same file
class NoopTreeVisitor : public banjo::raw::DeclarationOrderTreeVisitor {
public:
NoopTreeVisitor()
: last_source_location_(nullptr) {}
virtual void OnSourceElementStart(const banjo::raw::SourceElement& element) override {
OnSourceElementShared(element.start_);
}
virtual void OnSourceElementEnd(const banjo::raw::SourceElement& element) override {
OnSourceElementShared(element.end_);
}
void OnSourceElementShared(const banjo::Token& current_token) {
const char* ws_location = current_token.previous_end().data().data();
// Printed code must increase in monotonic order, for two reasons.
// First of all, we don't reorder anything. Second of all, the start
// token for an identifier list (for example) is the same as the start
// token for the first identifier, so we need to make sure we don't
// print that token twice.
if (ws_location > last_source_location_) {
int size = (int)(current_token.data().data() - current_token.previous_end().data().data());
std::string gap(ws_location, size);
std::string content(current_token.data().data(), current_token.data().size());
output_ += gap + content;
last_source_location_ = ws_location;
}
}
std::string& output() { return output_; }
private:
std::string output_;
const char* last_source_location_;
};
// Provides more useful context for string diff than EXPECT_STR_EQ, which shows
// a limited prefix. When the string is long, and the difference is buried
// past the limited prefix, the limited prefix doesn't give useful information.
std::string targeted_diff(const char* expected, const char* actual, int size) {
// We want two lines of useful context:
int i = 0;
int last_nl = 0;
int last_last_nl = 0;
while (i <= size && expected[i] == actual[i]) {
if (expected[i] == '\n') {
last_last_nl = last_nl;
last_nl = i;
}
i++;
}
int start = last_last_nl;
int expected_end = (i + 10 < strlen(expected)) ? i + 10 : strlen(expected) - 1;
int actual_end = (i + 10 < strlen(actual)) ? i + 10 : strlen(actual) - 1;
std::string s("Expected contains \"");
s.append(std::string(expected + start, expected_end - start));
s.append("\" and actual contains \"");
s.append(std::string(actual + start, actual_end - start));
s.append("\"");
return s;
}
// Test that the AST visitor works: ensure that if you visit a file, you can
// reconstruct its original contents.
bool read_and_write_direct_test() {
BEGIN_TEST;
for (auto element : Examples::map()) {
TestLibrary library(element.first, element.second);
std::unique_ptr<banjo::raw::File> ast;
ASSERT_TRUE(library.Parse(ast));
NoopTreeVisitor visitor;
visitor.OnFile(ast);
std::string expected(library.source_file().data());
std::string output = visitor.output();
const char* actual = output.c_str();
std::string d = targeted_diff(expected.c_str(), actual, output.size());
d = element.first + ": " + d;
EXPECT_STR_EQ(expected.c_str(), actual, d.c_str());
}
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(visitor_tests);
RUN_TEST(read_and_write_direct_test);
END_TEST_CASE(visitor_tests);