blob: b98f8c3e74889f3b5099625c89fdf9c71cdb218e [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 <zircon/assert.h>
#include <cstring>
#include <type_traits>
#include <gtest/gtest.h>
#include "tools/fidl/fidlc/src/compiler.h"
#include "tools/fidl/fidlc/src/diagnostic_types.h"
#include "tools/fidl/fidlc/src/experimental_flags.h"
#include "tools/fidl/fidlc/src/findings.h"
#include "tools/fidl/fidlc/src/flat_ast.h"
#include "tools/fidl/fidlc/src/json_generator.h"
#include "tools/fidl/fidlc/src/source_file.h"
#include "tools/fidl/fidlc/src/versioning_types.h"
#include "tools/fidl/fidlc/src/virtual_source_file.h"
#define ASSERT_COMPILED(library) \
{ \
TestLibrary& library_ref = (library); \
if (!library_ref.Compile()) { \
EXPECT_EQ(library_ref.errors().size(), 0u); \
for (const auto& error : library_ref.errors()) { \
EXPECT_EQ(error->def.msg, ""); \
} \
FAIL() << "stopping test, compilation failed"; \
} \
EXPECT_EQ(library_ref.warnings().size(), 0u); \
for (const auto& warning : library_ref.warnings()) { \
EXPECT_EQ(warning->def.msg, ""); \
} \
}
#define ASSERT_COMPILER_DIAGNOSTICS(library) \
{ \
TestLibrary& library_ref = (library); \
if (!library_ref.CheckCompile()) { \
FAIL() << "Diagnostics mismatch"; \
} \
}
namespace fidlc {
struct LintArgs {
public:
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;
};
// Behavior that applies to SharedAmongstLibraries, but that is also provided on
// TestLibrary for convenience in single-library tests.
class SharedInterface {
public:
virtual Reporter* reporter() = 0;
virtual Libraries* all_libraries() = 0;
virtual VersionSelection* version_selection() = 0;
virtual ExperimentalFlagSet& experimental_flags() = 0;
virtual MethodHasher& method_hasher() = 0;
// Adds and compiles a library similar to //zircon/vsdo/zx, defining "Handle",
// "ObjType", and "Rights".
virtual void UseLibraryZx() = 0;
// Adds and compiles a library defining fdf.handle and fdf.obj_type.
virtual void UseLibraryFdf() = 0;
const std::vector<std::unique_ptr<Diagnostic>>& errors() { return reporter()->errors(); }
const std::vector<std::unique_ptr<Diagnostic>>& warnings() { return reporter()->warnings(); }
std::vector<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(std::string platform, std::string_view version) {
SelectVersion(std::move(platform), Version::Parse(version).value());
}
void SelectVersion(std::string platform, Version version) {
version_selection()->Insert(Platform::Parse(std::move(platform)).value(), version);
}
void EnableFlag(ExperimentalFlag flag) { experimental_flags().Enable(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_, &virtual_file_) {}
// Unsafe to copy/move because all_libraries_ stores a pointer to reporter_.
SharedAmongstLibraries(const SharedAmongstLibraries&) = delete;
SharedAmongstLibraries(SharedAmongstLibraries&&) = delete;
Reporter* reporter() override { return &reporter_; }
Libraries* all_libraries() override { return &all_libraries_; }
VersionSelection* version_selection() override { return &version_selection_; }
ExperimentalFlagSet& experimental_flags() override { return experimental_flags_; }
MethodHasher& method_hasher() override { return method_hasher_; }
void UseLibraryZx() override;
void UseLibraryFdf() override;
std::vector<std::unique_ptr<SourceFile>>& all_sources_of_all_libraries() {
return all_sources_of_all_libraries_;
}
private:
Reporter reporter_;
VirtualSourceFile virtual_file_{"generated"};
MethodHasher method_hasher_ = Sha256MethodHasher;
Libraries all_libraries_;
std::vector<std::unique_ptr<SourceFile>> all_sources_of_all_libraries_;
VersionSelection version_selection_;
ExperimentalFlagSet experimental_flags_;
};
// Helper template to allow passing either a string literal or `const Arg&`.
template <typename Arg>
struct StringOrArg {
// NOLINTNEXTLINE(google-explicit-constructor)
StringOrArg(const char* string) : string(string) {}
template <typename From, typename = std::enable_if_t<std::is_convertible_v<From, Arg>>>
// NOLINTNEXTLINE(google-explicit-constructor)
StringOrArg(const From& arg) : string(internal::Display(static_cast<const Arg&>(arg))) {}
std::string string;
};
// 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).
TestLibrary() {
owned_shared_.emplace();
shared_ = &owned_shared_.value();
}
// Constructor for a multi-library, single-file test.
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) {}
~TestLibrary();
Reporter* reporter() override { return shared_->reporter(); }
Libraries* all_libraries() override { return shared_->all_libraries(); }
VersionSelection* version_selection() override { return shared_->version_selection(); }
ExperimentalFlagSet& experimental_flags() override { return shared_->experimental_flags(); }
MethodHasher& method_hasher() override { return shared_->method_hasher(); }
void UseLibraryZx() override { shared_->UseLibraryZx(); }
void UseLibraryFdf() override { shared_->UseLibraryFdf(); }
void AddSource(const std::string& filename, const std::string& raw_source_code);
AttributeSchema& AddAttributeSchema(std::string name) {
return all_libraries()->AddAttributeSchema(std::move(name));
}
static std::string TestFilePath(const std::string& name);
// Read the source from an associated external file.
void AddFile(const std::string& name);
// Record that a particular error is expected during the compile.
// The args can either match the ErrorDef's argument types, or they can be string literals.
template <ErrorId Id, typename... Args>
void ExpectFail(const ErrorDef<Id, Args...>& def,
cpp20::type_identity_t<StringOrArg<Args>>... args) {
expected_diagnostics_.push_back(internal::FormatDiagnostic(def.msg, args.string...));
}
// Record that a particular warning is expected during the compile.
// The args can either match the WarningDef's argument types, or they can be string literals.
template <ErrorId Id, typename... Args>
void ExpectWarn(const WarningDef<Id, Args...>& def,
cpp20::type_identity_t<StringOrArg<Args>>... args) {
expected_diagnostics_.push_back(internal::FormatDiagnostic(def.msg, args.string...));
}
// Check that the diagnostics expected with ExpectFail and ExpectWarn were recorded, in that order
// by the compilation. This prints information about diagnostics mismatches and returns false if
// any were found.
bool CheckDiagnostics();
// TODO(https://fxbug.dev/42069446): remove (or rename this class to be more general), as this
// does not use a library.
bool Parse(std::unique_ptr<File>* out_ast_ptr);
// Compiles the library. Must have compiled all dependencies first, using the
// same SharedAmongstLibraries object for all of them.
bool Compile();
// Compiles the library and checks that the diagnostics asserted with
bool CheckCompile();
bool Lint(LintArgs args = {});
std::string GenerateJSON() {
auto json_generator = JSONGenerator(compilation_.get(), experimental_flags());
auto out = json_generator.Produce();
return out.str();
}
const Bits* LookupBits(std::string_view name);
const Const* LookupConstant(std::string_view name);
const Enum* LookupEnum(std::string_view name);
const Resource* LookupResource(std::string_view name);
const Service* LookupService(std::string_view name);
const Struct* LookupStruct(std::string_view name);
const NewType* LookupNewType(std::string_view name);
const Table* LookupTable(std::string_view name);
const Alias* LookupAlias(std::string_view name);
const Union* LookupUnion(std::string_view name);
const Overlay* LookupOverlay(std::string_view name);
const Protocol* LookupProtocol(std::string_view name);
bool HasBits(std::string_view name) { return LookupBits(name) != nullptr; }
bool HasConstant(std::string_view name) { return LookupConstant(name) != nullptr; }
bool HasEnum(std::string_view name) { return LookupEnum(name) != nullptr; }
bool HasResource(std::string_view name) { return LookupResource(name) != nullptr; }
bool HasService(std::string_view name) { return LookupService(name) != nullptr; }
bool HasStruct(std::string_view name) { return LookupStruct(name) != nullptr; }
bool HasNewType(std::string_view name) { return LookupNewType(name) != nullptr; }
bool HasTable(std::string_view name) { return LookupTable(name) != nullptr; }
bool HasAlias(std::string_view name) { return LookupAlias(name) != nullptr; }
bool HasUnion(std::string_view name) { return LookupUnion(name) != nullptr; }
bool HasOverlay(std::string_view name) { return LookupOverlay(name) != nullptr; }
bool HasProtocol(std::string_view name) { return LookupProtocol(name) != nullptr; }
const SourceFile& source_file() const {
ZX_ASSERT_MSG(all_sources_.size() == 1, "convenience method only possible with single source");
return *all_sources_.at(0);
}
std::vector<const SourceFile*> source_files() const;
SourceSpan source_span(size_t start, size_t size) const;
SourceSpan find_source_span(std::string_view span_text);
const Findings& findings() const { return findings_; }
const std::vector<std::string>& lints() const { return lints_; }
std::string_view name() const { return compilation_->library_name; }
const Platform& platform() const { return *compilation_->platform; }
const AttributeList* attributes() { return compilation_->library_attributes; }
const std::vector<const Struct*>& external_structs() const {
return compilation_->external_structs;
}
const std::vector<const Decl*>& declaration_order() const {
return compilation_->declaration_order;
}
const std::vector<Compilation::Dependency>& direct_and_composed_dependencies() const {
return compilation_->direct_and_composed_dependencies;
}
private:
std::optional<SharedAmongstLibraries> owned_shared_;
SharedAmongstLibraries* shared_;
Findings findings_;
std::vector<std::string> lints_;
std::vector<SourceFile*> all_sources_;
std::unique_ptr<Compilation> compilation_;
std::vector<std::string> expected_diagnostics_;
bool used_ = false;
};
} // namespace fidlc
#endif // TOOLS_FIDL_FIDLC_TESTS_TEST_LIBRARY_H_