| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "verilog/analysis/dependencies.h" | |
| #include "absl/status/status.h" | |
| #include "common/util/file_util.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| namespace verilog { | |
| namespace { | |
| using testing::ElementsAre; | |
| using verible::file::Basename; | |
| using verible::file::CreateDir; | |
| using verible::file::JoinPath; | |
| using verible::file::testing::ScopedTestFile; | |
| TEST(FileDependenciesTest, EmptyData) { | |
| const auto tempdir = ::testing::TempDir(); | |
| const std::string sources_dir = JoinPath(tempdir, __FUNCTION__); | |
| ASSERT_TRUE(CreateDir(sources_dir).ok()); | |
| VerilogProject project(sources_dir, {/* no include paths */}); | |
| // no files | |
| SymbolTable symbol_table(&project); | |
| { // pre-build should be empty | |
| FileDependencies file_deps(symbol_table); | |
| EXPECT_TRUE(file_deps.Empty()) << file_deps; | |
| } | |
| std::vector<absl::Status> build_diagnostics; | |
| symbol_table.Build(&build_diagnostics); | |
| EXPECT_TRUE(build_diagnostics.empty()); | |
| { // post-build should be empty | |
| FileDependencies file_deps(symbol_table); | |
| EXPECT_TRUE(file_deps.Empty()) << file_deps; | |
| } | |
| } | |
| TEST(FileDependenciesTest, OneFileNoDeps) { | |
| const auto tempdir = ::testing::TempDir(); | |
| const std::string sources_dir = JoinPath(tempdir, __FUNCTION__); | |
| ASSERT_TRUE(CreateDir(sources_dir).ok()); | |
| // None of these test cases will yield any inter-file deps. | |
| constexpr absl::string_view kTestCases[] = { | |
| "", | |
| // one module | |
| "module mmm;\n" | |
| "endmodule\n", | |
| // two modules, with dependency | |
| "module mmm;\n" | |
| "endmodule\n" | |
| "module ppp;\n" | |
| " mmm mmm_inst();\n" | |
| "endmodule\n", | |
| // parameters with dependency | |
| "localparam int foo = 0;\n" | |
| "localparam int bar = foo + 1;\n", | |
| }; | |
| for (const auto& code : kTestCases) { | |
| VLOG(1) << "code: " << code; | |
| VerilogProject project(sources_dir, {/* no include paths */}); | |
| ScopedTestFile tf(sources_dir, code); | |
| { | |
| const auto status_or_file = | |
| project.OpenTranslationUnit(Basename(tf.filename())); | |
| ASSERT_TRUE(status_or_file.ok()) << status_or_file.status().message(); | |
| } | |
| SymbolTable symbol_table(&project); | |
| std::vector<absl::Status> build_diagnostics; | |
| symbol_table.Build(&build_diagnostics); | |
| EXPECT_TRUE(build_diagnostics.empty()); | |
| // post-build should be empty | |
| FileDependencies file_deps(symbol_table); | |
| EXPECT_TRUE(file_deps.Empty()) << file_deps; | |
| } | |
| } | |
| TEST(FileDependenciesTest, TwoFilesNoDeps) { | |
| const auto tempdir = ::testing::TempDir(); | |
| const std::string sources_dir = JoinPath(tempdir, __FUNCTION__); | |
| ASSERT_TRUE(CreateDir(sources_dir).ok()); | |
| VerilogProject project(sources_dir, {/* no include paths */}); | |
| ScopedTestFile tf1(sources_dir, "localparam int foo = 0;"); | |
| const auto status_or_file1 = | |
| project.OpenTranslationUnit(Basename(tf1.filename())); | |
| ScopedTestFile tf2(sources_dir, "localparam int bar = 2;"); | |
| const auto status_or_file2 = | |
| project.OpenTranslationUnit(Basename(tf2.filename())); | |
| SymbolTable symbol_table(&project); | |
| std::vector<absl::Status> build_diagnostics; | |
| symbol_table.Build(&build_diagnostics); | |
| EXPECT_TRUE(build_diagnostics.empty()); | |
| // post-build should be empty | |
| FileDependencies file_deps(symbol_table); | |
| EXPECT_TRUE(file_deps.Empty()) << file_deps; | |
| } | |
| struct SymbolTableDebug { | |
| const SymbolTable& symbol_table; | |
| }; | |
| static std::ostream& operator<<(std::ostream& stream, | |
| const SymbolTableDebug& p) { | |
| stream << "Definitions:" << std::endl; | |
| p.symbol_table.PrintSymbolDefinitions(stream) << std::endl; | |
| stream << "References:" << std::endl; | |
| return p.symbol_table.PrintSymbolReferences(stream) << std::endl; | |
| } | |
| TEST(FileDependenciesTest, TwoFilesWithParamDepAtRootScope) { | |
| const auto tempdir = ::testing::TempDir(); | |
| const std::string sources_dir = JoinPath(tempdir, __FUNCTION__); | |
| ASSERT_TRUE(CreateDir(sources_dir).ok()); | |
| VerilogProject project(sources_dir, {/* no include paths */}); | |
| ScopedTestFile tf1(sources_dir, "localparam int zzz = 0;\n"); | |
| const auto status_or_file1 = | |
| project.OpenTranslationUnit(Basename(tf1.filename())); | |
| const VerilogSourceFile* file1 = *status_or_file1; | |
| ScopedTestFile tf2(sources_dir, "localparam int yyy = zzz * 2;\n"); | |
| const auto status_or_file2 = | |
| project.OpenTranslationUnit(Basename(tf2.filename())); | |
| const VerilogSourceFile* file2 = *status_or_file2; | |
| SymbolTable symbol_table(&project); | |
| std::vector<absl::Status> build_diagnostics; | |
| symbol_table.Build(&build_diagnostics); | |
| EXPECT_TRUE(build_diagnostics.empty()); | |
| FileDependencies file_deps(symbol_table); | |
| // "zzz" is defined in the same $root scope, but different files, | |
| // so we expect a dependency edge. | |
| EXPECT_FALSE(file_deps.Empty()) << file_deps; | |
| EXPECT_EQ(file_deps.file_deps.size(), 1) << SymbolTableDebug{symbol_table}; | |
| const auto found_ref = file_deps.file_deps.find(file2); | |
| ASSERT_NE(found_ref, file_deps.file_deps.end()) | |
| << file2->GetTextStructure()->Contents(); | |
| const auto found_def = found_ref->second.find(file1); | |
| ASSERT_NE(found_def, found_ref->second.end()) | |
| << file1->GetTextStructure()->Contents(); | |
| EXPECT_THAT(found_def->second, ElementsAre("zzz")); | |
| } | |
| TEST(FileDependenciesTest, TwoFilesWithParamDep) { | |
| const auto tempdir = ::testing::TempDir(); | |
| const std::string sources_dir = JoinPath(tempdir, __FUNCTION__); | |
| ASSERT_TRUE(CreateDir(sources_dir).ok()); | |
| VerilogProject project(sources_dir, {/* no include paths */}); | |
| ScopedTestFile tf1(sources_dir, | |
| "localparam int foo = 0;\n" | |
| "package p_pkg;\n" | |
| " localparam int goo = 1;\n" | |
| "endpackage\n"); | |
| const auto status_or_file1 = | |
| project.OpenTranslationUnit(Basename(tf1.filename())); | |
| const VerilogSourceFile* file1 = *status_or_file1; | |
| ScopedTestFile tf2(sources_dir, | |
| "localparam int bar = foo - 2;\n" | |
| "localparam int baz = p_pkg::goo;\n"); | |
| const auto status_or_file2 = | |
| project.OpenTranslationUnit(Basename(tf2.filename())); | |
| const VerilogSourceFile* file2 = *status_or_file2; | |
| SymbolTable symbol_table(&project); | |
| std::vector<absl::Status> build_diagnostics; | |
| symbol_table.Build(&build_diagnostics); | |
| EXPECT_TRUE(build_diagnostics.empty()); | |
| FileDependencies file_deps(symbol_table); | |
| EXPECT_FALSE(file_deps.Empty()) << file_deps; | |
| EXPECT_EQ(file_deps.file_deps.size(), 1) << SymbolTableDebug{symbol_table}; | |
| const auto found_ref = file_deps.file_deps.find(file2); | |
| ASSERT_NE(found_ref, file_deps.file_deps.end()) | |
| << file2->GetTextStructure()->Contents(); | |
| const auto found_def = found_ref->second.find(file1); | |
| ASSERT_NE(found_def, found_ref->second.end()) | |
| << file1->GetTextStructure()->Contents(); | |
| EXPECT_THAT(found_def->second, ElementsAre("foo", "p_pkg")); | |
| } | |
| TEST(FileDependenciesTest, TwoFilesWithCyclicDep) { | |
| const auto tempdir = ::testing::TempDir(); | |
| const std::string sources_dir = JoinPath(tempdir, __FUNCTION__); | |
| ASSERT_TRUE(CreateDir(sources_dir).ok()); | |
| VerilogProject project(sources_dir, {/* no include paths */}); | |
| ScopedTestFile tf1(sources_dir, | |
| "localparam int foo = 0;\n" | |
| "package p_pkg;\n" | |
| " localparam int goo = bar;\n" | |
| "endpackage\n"); | |
| const auto status_or_file1 = | |
| project.OpenTranslationUnit(Basename(tf1.filename())); | |
| const VerilogSourceFile* file1 = *status_or_file1; | |
| ScopedTestFile tf2(sources_dir, | |
| "localparam int bar = foo - 2;\n" | |
| "localparam int baz = p_pkg::goo;\n"); | |
| const auto status_or_file2 = | |
| project.OpenTranslationUnit(Basename(tf2.filename())); | |
| const VerilogSourceFile* file2 = *status_or_file2; | |
| SymbolTable symbol_table(&project); | |
| std::vector<absl::Status> build_diagnostics; | |
| symbol_table.Build(&build_diagnostics); | |
| EXPECT_TRUE(build_diagnostics.empty()); | |
| FileDependencies file_deps(symbol_table); | |
| EXPECT_FALSE(file_deps.Empty()) << file_deps; | |
| { // dependencies in one direction | |
| const auto found_ref = file_deps.file_deps.find(file2); | |
| ASSERT_NE(found_ref, file_deps.file_deps.end()) | |
| << file2->GetTextStructure()->Contents(); | |
| const auto found_def = found_ref->second.find(file1); | |
| ASSERT_NE(found_def, found_ref->second.end()) | |
| << file1->GetTextStructure()->Contents(); | |
| EXPECT_THAT(found_def->second, ElementsAre("foo", "p_pkg")); | |
| } | |
| { // dependencies in reverse direction | |
| const auto found_ref = file_deps.file_deps.find(file1); | |
| ASSERT_NE(found_ref, file_deps.file_deps.end()) | |
| << file1->GetTextStructure()->Contents(); | |
| const auto found_def = found_ref->second.find(file2); | |
| ASSERT_NE(found_def, found_ref->second.end()) | |
| << file2->GetTextStructure()->Contents(); | |
| EXPECT_THAT(found_def->second, ElementsAre("bar")); | |
| } | |
| } | |
| TEST(FileDependenciesTest, ModuleDiamondDependencies) { | |
| const auto tempdir = ::testing::TempDir(); | |
| const std::string sources_dir = JoinPath(tempdir, __FUNCTION__); | |
| ASSERT_TRUE(CreateDir(sources_dir).ok()); | |
| VerilogProject project(sources_dir, {/* no include paths */}); | |
| // 4 files: with a diamond dependency relationship | |
| ScopedTestFile mmm(sources_dir, | |
| "module mmm;\n" | |
| " ppp ppp_i();\n" | |
| " qqq qqq_i();\n" | |
| "endmodule\n"); | |
| const auto mmm_status_or_file = | |
| project.OpenTranslationUnit(Basename(mmm.filename())); | |
| const VerilogSourceFile* mmm_file = *mmm_status_or_file; | |
| ScopedTestFile ppp(sources_dir, | |
| "module ppp;\n" | |
| " rrr rrr_i();\n" | |
| "endmodule\n"); | |
| const auto ppp_status_or_file = | |
| project.OpenTranslationUnit(Basename(ppp.filename())); | |
| const VerilogSourceFile* ppp_file = *ppp_status_or_file; | |
| ScopedTestFile qqq(sources_dir, | |
| "module qqq;\n" | |
| " rrr rrr_i();\n" | |
| "endmodule\n"); | |
| const auto qqq_status_or_file = | |
| project.OpenTranslationUnit(Basename(qqq.filename())); | |
| const VerilogSourceFile* qqq_file = *qqq_status_or_file; | |
| ScopedTestFile rrr(sources_dir, | |
| "module rrr;\n" | |
| " wire w;\n" | |
| "endmodule\n"); | |
| const auto rrr_status_or_file = | |
| project.OpenTranslationUnit(Basename(rrr.filename())); | |
| const VerilogSourceFile* rrr_file = *rrr_status_or_file; | |
| SymbolTable symbol_table(&project); | |
| std::vector<absl::Status> build_diagnostics; | |
| symbol_table.Build(&build_diagnostics); | |
| EXPECT_TRUE(build_diagnostics.empty()); | |
| FileDependencies file_deps(symbol_table); | |
| EXPECT_FALSE(file_deps.Empty()) << file_deps; | |
| // Verify 4 edges in graph. | |
| { | |
| const auto found_ref = file_deps.file_deps.find(mmm_file); | |
| ASSERT_NE(found_ref, file_deps.file_deps.end()) | |
| << mmm_file->GetTextStructure()->Contents(); | |
| { | |
| // mmm -> ppp | |
| const auto found_def = found_ref->second.find(ppp_file); | |
| ASSERT_NE(found_def, found_ref->second.end()) | |
| << ppp_file->GetTextStructure()->Contents(); | |
| EXPECT_THAT(found_def->second, ElementsAre("ppp")); | |
| } | |
| { | |
| // mmm -> qqq | |
| const auto found_def = found_ref->second.find(qqq_file); | |
| ASSERT_NE(found_def, found_ref->second.end()) | |
| << qqq_file->GetTextStructure()->Contents(); | |
| EXPECT_THAT(found_def->second, ElementsAre("qqq")); | |
| } | |
| } | |
| { // ppp -> rrr | |
| const auto found_ref = file_deps.file_deps.find(ppp_file); | |
| ASSERT_NE(found_ref, file_deps.file_deps.end()) | |
| << ppp_file->GetTextStructure()->Contents(); | |
| const auto found_def = found_ref->second.find(rrr_file); | |
| ASSERT_NE(found_def, found_ref->second.end()) | |
| << rrr_file->GetTextStructure()->Contents(); | |
| EXPECT_THAT(found_def->second, ElementsAre("rrr")); | |
| } | |
| { | |
| // qqq -> rrr | |
| const auto found_ref = file_deps.file_deps.find(qqq_file); | |
| ASSERT_NE(found_ref, file_deps.file_deps.end()) | |
| << qqq_file->GetTextStructure()->Contents(); | |
| const auto found_def = found_ref->second.find(rrr_file); | |
| ASSERT_NE(found_def, found_ref->second.end()) | |
| << rrr_file->GetTextStructure()->Contents(); | |
| EXPECT_THAT(found_def->second, ElementsAre("rrr")); | |
| } | |
| } | |
| } // namespace | |
| } // namespace verilog |