blob: c0a2f67e3b441219fe41970e353eef82425716c3 [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.
#ifndef TOOLS_FIDL_FIDLC_TESTS_TEST_LIBRARY_H_
#define TOOLS_FIDL_FIDLC_TESTS_TEST_LIBRARY_H_
#include <fidl/flat/compiler.h>
#include <fidl/flat_ast.h>
#include <fidl/json_generator.h>
#include <fidl/lexer.h>
#include <fidl/linter.h>
#include <fidl/ordinals.h>
#include <fidl/parser.h>
#include <fidl/source_file.h>
#include <fidl/tables_generator.h>
#include <zircon/assert.h>
#include <fstream>
#include "fidl/experimental_flags.h"
#include "fidl/versioning_types.h"
// Behavior that applies to SharedAmongstLibraries, but that is also provided on
// TestLibrary for convenience in single-library tests.
class SharedInterface {
public:
virtual fidl::Reporter* reporter() = 0;
virtual fidl::flat::Libraries* all_libraries() = 0;
virtual fidl::VersionSelection* version_selection() = 0;
virtual fidl::ExperimentalFlags& experimental_flags() = 0;
const std::vector<std::unique_ptr<fidl::Diagnostic>>& errors() { return reporter()->errors(); }
const std::vector<std::unique_ptr<fidl::Diagnostic>>& warnings() {
return reporter()->warnings();
}
std::vector<fidl::Diagnostic*> Diagnostics() { return reporter()->Diagnostics(); }
void set_warnings_as_errors(bool value) { reporter()->set_warnings_as_errors(value); }
void PrintReports() { reporter()->PrintReports(/*enable_color=*/false); }
void SelectVersion(const std::string& platform, std::string_view version) {
version_selection()->Insert(fidl::Platform::Parse(platform).value(),
fidl::Version::Parse(version).value());
}
void EnableFlag(fidl::ExperimentalFlags::Flag flag) { experimental_flags().EnableFlag(flag); }
};
// Stores data structures that are shared amongst all libraries being compiled
// together (i.e. the dependencies and the final library).
class SharedAmongstLibraries final : public SharedInterface {
public:
SharedAmongstLibraries() : all_libraries_(&reporter_) {}
// Unsafe to copy/move because all_libraries_ stores a pointer to reporter_.
SharedAmongstLibraries(const SharedAmongstLibraries&) = delete;
SharedAmongstLibraries(SharedAmongstLibraries&&) = delete;
// Adds and compiles a library similar to //zircon/vsdo/zx, defining "handle",
// "obj_type", and "rights".
void AddLibraryZx();
// Adds and compiles a library defining fdf.handle and fdf.obj_type.
void AddLibraryFdf();
fidl::Reporter* reporter() override { return &reporter_; }
fidl::flat::Libraries* all_libraries() override { return &all_libraries_; }
fidl::VersionSelection* version_selection() override { return &version_selection_; }
fidl::ExperimentalFlags& experimental_flags() override { return experimental_flags_; }
std::vector<std::unique_ptr<fidl::SourceFile>>& all_sources_of_all_libraries() {
return all_sources_of_all_libraries_;
}
private:
fidl::Reporter reporter_;
fidl::flat::Libraries all_libraries_;
std::vector<std::unique_ptr<fidl::SourceFile>> all_sources_of_all_libraries_;
fidl::VersionSelection version_selection_;
fidl::ExperimentalFlags experimental_flags_;
};
namespace internal {
// See ordinals_test.cc
inline fidl::raw::Ordinal64 GetGeneratedOrdinal64ForTesting(
const std::vector<std::string_view>& library_name, const std::string_view& protocol_name,
const std::string_view& selector_name, const fidl::raw::SourceElement& source_element) {
static std::map<std::string, uint64_t> special_selectors = {
{"ThisOneHashesToZero", 0},
{"ClashOne", 456789},
{"ClashOneReplacement", 987654},
{"ClashTwo", 456789},
};
if (library_name.size() == 1 && library_name[0] == "methodhasher" &&
(protocol_name == "Special" || protocol_name == "SpecialComposed")) {
auto it = special_selectors.find(std::string(selector_name));
ZX_ASSERT_MSG(it != special_selectors.end(), "only special selectors allowed");
return fidl::raw::Ordinal64(source_element, it->second);
}
return fidl::ordinals::GetGeneratedOrdinal64(library_name, protocol_name, selector_name,
source_element);
}
} // namespace internal
// Test harness for a single library. To compile multiple libraries together,
// first default construct a SharedAmongstLibraries and then pass it to each
// TestLibrary, and compile them one at a time in dependency order.
class TestLibrary final : public SharedInterface {
public:
// Constructor for a single-library, single-file test.
explicit TestLibrary(const std::string& raw_source_code) : TestLibrary() {
AddSource("example.fidl", raw_source_code);
}
// Constructor for a single-library, multi-file test (call AddSource after).
explicit TestLibrary() {
owned_shared_.emplace();
shared_ = &owned_shared_.value();
}
// Constructor for a multi-library, single-file test.
explicit TestLibrary(SharedAmongstLibraries* shared, const std::string& filename,
const std::string& raw_source_code)
: TestLibrary(shared) {
AddSource(filename, raw_source_code);
}
// Constructor for a multi-library, multi-file test (call AddSource after).
explicit TestLibrary(SharedAmongstLibraries* shared) : shared_(shared) {}
// Helper for making a single test library depend on library zx, without
// requiring an explicit SharedAmongstLibraries.
void UseLibraryZx() {
ZX_ASSERT_MSG(!compilation_, "must call before compiling");
owned_shared_.value().AddLibraryZx();
}
// Helper for making a single test library depend on library fdf, without
// requiring an explicit SharedAmongstLibraries.
void UseLibraryFdf() {
ZX_ASSERT_MSG(!compilation_, "must call before compiling");
owned_shared_.value().AddLibraryFdf();
}
fidl::Reporter* reporter() override { return shared_->reporter(); }
fidl::flat::Libraries* all_libraries() override { return shared_->all_libraries(); }
fidl::VersionSelection* version_selection() override { return shared_->version_selection(); }
fidl::ExperimentalFlags& experimental_flags() override { return shared_->experimental_flags(); }
void AddSource(const std::string& filename, const std::string& raw_source_code) {
std::string source_code(raw_source_code);
// NUL terminate the string.
source_code.resize(source_code.size() + 1);
auto file = std::make_unique<fidl::SourceFile>(filename, source_code);
all_sources_.push_back(file.get());
shared_->all_sources_of_all_libraries().push_back(std::move(file));
}
fidl::flat::AttributeSchema& AddAttributeSchema(std::string name) {
return all_libraries()->AddAttributeSchema(std::move(name));
}
// TODO(pascallouis): remove, this does not use a library.
bool Parse(std::unique_ptr<fidl::raw::File>* out_ast_ptr) {
ZX_ASSERT_MSG(all_sources_.size() == 1, "parse can only be used with one source");
auto source_file = all_sources_.at(0);
fidl::Lexer lexer(*source_file, reporter());
fidl::Parser parser(&lexer, reporter(), experimental_flags());
out_ast_ptr->reset(parser.Parse().release());
return parser.Success();
}
// Compiles the library. Must have compiled all dependencies first, using the
// same SharedAmongstLibraries object for all of them.
bool Compile() {
fidl::flat::Compiler compiler(all_libraries(), version_selection(),
internal::GetGeneratedOrdinal64ForTesting, experimental_flags());
for (auto source_file : all_sources_) {
fidl::Lexer lexer(*source_file, reporter());
fidl::Parser parser(&lexer, reporter(), experimental_flags());
auto ast = parser.Parse();
if (!parser.Success())
return false;
if (!compiler.ConsumeFile(std::move(ast)))
return false;
}
if (!compiler.Compile())
return false;
compilation_ = all_libraries()->Filter(version_selection());
return true;
}
// TODO(pascallouis): remove, this does not use a library.
bool Lint(fidl::Findings* findings, const std::set<std::string>& included_check_ids = {},
const std::set<std::string>& excluded_check_ids = {}, bool exclude_by_default = false,
std::set<std::string>* excluded_checks_not_found = nullptr) {
ZX_ASSERT_MSG(all_sources_.size() == 1, "lint can only be used with one source");
auto source_file = all_sources_.at(0);
fidl::Lexer lexer(*source_file, reporter());
fidl::Parser parser(&lexer, reporter(), experimental_flags());
auto ast = parser.Parse();
if (!parser.Success()) {
std::string_view beginning(source_file->data().data(), 0);
fidl::SourceSpan span(beginning, *source_file);
const auto& error = errors().at(0);
auto error_msg = fidl::Reporter::Format("error", error->span, error->msg, /*color=*/false);
findings->emplace_back(span, "parser-error", error_msg + "\n");
return false;
}
fidl::linter::Linter linter;
if (!included_check_ids.empty()) {
linter.set_included_checks(included_check_ids);
}
if (!excluded_check_ids.empty()) {
linter.set_excluded_checks(excluded_check_ids);
}
linter.set_exclude_by_default(exclude_by_default);
return linter.Lint(ast, findings, excluded_checks_not_found);
}
bool Lint() {
fidl::Findings findings;
bool passed = Lint(&findings);
lints_ = fidl::utils::FormatFindings(findings, false);
return passed;
}
std::string GenerateJSON() {
auto json_generator = fidl::JSONGenerator(compilation_.get(), experimental_flags());
auto out = json_generator.Produce();
return out.str();
}
std::string GenerateTables() {
auto tables_generator = fidl::TablesGenerator(compilation_.get());
auto out = tables_generator.Produce();
return out.str();
}
// Note: We don't provide a convenient library() method because inspecting a
// Library is usually the wrong thing to do in tests. What usually matters is
// the Compilation, for which we provide compilation() and helpers like
// LookupStruct() etc. However, sometimes tests really need to get a Library*
// (e.g. to construct Name::Key), hence this method.
const fidl::flat::Library* LookupLibrary(std::string_view name) {
std::vector<std::string_view> parts;
size_t dot_idx = 0;
for (size_t i = 0; dot_idx != std::string::npos; i = dot_idx + 1) {
dot_idx = name.find('.', i);
parts.push_back(name.substr(i, dot_idx));
}
auto library = all_libraries()->Lookup(parts);
ZX_ASSERT_MSG(library, "library not found");
return library;
}
const fidl::flat::Bits* LookupBits(std::string_view name) {
for (const auto& bits_decl : compilation_->declarations.bits) {
if (bits_decl->GetName() == name) {
return bits_decl;
}
}
return nullptr;
}
const fidl::flat::Const* LookupConstant(std::string_view name) {
for (const auto& const_decl : compilation_->declarations.consts) {
if (const_decl->GetName() == name) {
return const_decl;
}
}
return nullptr;
}
const fidl::flat::Enum* LookupEnum(std::string_view name) {
for (const auto& enum_decl : compilation_->declarations.enums) {
if (enum_decl->GetName() == name) {
return enum_decl;
}
}
return nullptr;
}
const fidl::flat::Resource* LookupResource(std::string_view name) {
for (const auto& resource_decl : compilation_->declarations.resources) {
if (resource_decl->GetName() == name) {
return resource_decl;
}
}
return nullptr;
}
const fidl::flat::Service* LookupService(std::string_view name) {
for (const auto& service_decl : compilation_->declarations.services) {
if (service_decl->GetName() == name) {
return service_decl;
}
}
return nullptr;
}
const fidl::flat::Struct* LookupStruct(std::string_view name) {
for (const auto& struct_decl : compilation_->declarations.structs) {
if (struct_decl->GetName() == name) {
return struct_decl;
}
}
return nullptr;
}
const fidl::flat::NewType* LookupNewType(std::string_view name) {
for (const auto& new_type_decl : compilation_->declarations.new_types) {
if (new_type_decl->GetName() == name) {
return new_type_decl;
}
}
return nullptr;
}
const fidl::flat::Table* LookupTable(std::string_view name) {
for (const auto& table_decl : compilation_->declarations.tables) {
if (table_decl->GetName() == name) {
return table_decl;
}
}
return nullptr;
}
const fidl::flat::TypeAlias* LookupTypeAlias(std::string_view name) {
for (const auto& type_alias_decl : compilation_->declarations.type_aliases) {
if (type_alias_decl->GetName() == name) {
return type_alias_decl;
}
}
return nullptr;
}
const fidl::flat::Union* LookupUnion(std::string_view name) {
for (const auto& union_decl : compilation_->declarations.unions) {
if (union_decl->GetName() == name) {
return union_decl;
}
}
return nullptr;
}
const fidl::flat::Protocol* LookupProtocol(std::string_view name) {
for (const auto& protocol_decl : compilation_->declarations.protocols) {
if (protocol_decl->GetName() == name) {
return protocol_decl;
}
}
return nullptr;
}
const fidl::SourceFile& source_file() const {
ZX_ASSERT_MSG(all_sources_.size() == 1, "convenience method only possible with single source");
return *all_sources_.at(0);
}
fidl::SourceSpan source_span(size_t start, size_t size) const {
ZX_ASSERT_MSG(all_sources_.size() == 1, "convenience method only possible with single source");
std::string_view data = all_sources_.at(0)->data();
data.remove_prefix(start);
data.remove_suffix(data.size() - size);
return fidl::SourceSpan(data, *all_sources_.at(0));
}
const std::vector<std::string>& lints() const { return lints_; }
const fidl::flat::Compilation* compilation() const {
ZX_ASSERT_MSG(compilation_, "must compile successfully before accessing compilation");
return compilation_.get();
}
const fidl::flat::AttributeList* attributes() { return compilation_->library_attributes; }
const std::vector<const fidl::flat::Struct*>& external_structs() const {
return compilation_->external_structs;
}
const std::vector<const fidl::flat::Decl*>& declaration_order() const {
return compilation_->declaration_order;
}
const std::vector<const fidl::flat::Decl*>& all_libraries_declaration_order() const {
return compilation_->all_libraries_declaration_order;
}
const std::vector<fidl::flat::Compilation::Dependency>& direct_and_composed_dependencies() const {
return compilation_->direct_and_composed_dependencies;
}
private:
std::optional<SharedAmongstLibraries> owned_shared_;
SharedAmongstLibraries* shared_;
std::vector<std::string> lints_;
std::vector<fidl::SourceFile*> all_sources_;
std::unique_ptr<fidl::flat::Compilation> compilation_;
};
#endif // TOOLS_FIDL_FIDLC_TESTS_TEST_LIBRARY_H_