| // 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_ |