| // 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. | |
| // This tests the VerilogLinter class and its associated functions, end-to-end. | |
| // | |
| // Tests for individual lint rules can be found in | |
| // verilog/analysis/checkers/. | |
| #include "verilog/analysis/verilog_linter.h" | |
| #include <algorithm> | |
| #include <iterator> | |
| #include <memory> | |
| #include <sstream> // IWYU pragma: keep // for ostringstream | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "absl/memory/memory.h" | |
| #include "absl/status/status.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/util/file_util.h" | |
| #include "common/util/logging.h" | |
| #include "verilog/analysis/default_rules.h" | |
| #include "verilog/analysis/verilog_analyzer.h" | |
| #include "verilog/analysis/verilog_linter_configuration.h" | |
| namespace verilog { | |
| namespace { | |
| using ::testing::EndsWith; | |
| using ::testing::StartsWith; | |
| using verible::file::testing::ScopedTestFile; | |
| class DefaultLinterConfigTestFixture { | |
| public: | |
| // Test code using the default rule set. | |
| DefaultLinterConfigTestFixture() : config_() { | |
| config_.UseRuleSet(RuleSet::kDefault); | |
| } | |
| protected: | |
| LinterConfiguration config_; | |
| }; | |
| class LintOneFileTest : public DefaultLinterConfigTestFixture, | |
| public testing::Test { | |
| public: | |
| LintOneFileTest() = default; | |
| }; | |
| // Tests that nonexistent file is handled as a fatal error. | |
| TEST_F(LintOneFileTest, FileNotFound) { | |
| std::ostringstream output; | |
| const int exit_code = | |
| LintOneFile(&output, "FileNotFound.sv", config_, false, false); | |
| EXPECT_EQ(exit_code, 2); | |
| } | |
| // Tests that clean code exits 0 (success). | |
| TEST_F(LintOneFileTest, LintCleanFiles) { | |
| const absl::string_view kTestCases[] = { | |
| "", // empty file | |
| "\n", | |
| "class foo;\n" | |
| "endclass : foo\n", | |
| }; | |
| for (const auto test_code : kTestCases) { | |
| const ScopedTestFile temp_file(testing::TempDir(), test_code); | |
| std::ostringstream output; | |
| const int exit_code = | |
| LintOneFile(&output, temp_file.filename(), config_, false, false); | |
| EXPECT_EQ(exit_code, 0); | |
| } | |
| } | |
| // Tests that invalid code is handled according to 'parse_fatal' parameter. | |
| TEST_F(LintOneFileTest, SyntaxError) { | |
| const absl::string_view kTestCases[] = { | |
| "class foo;\n", // no endclass | |
| "endclass : foo\n", // no begin class | |
| }; | |
| for (const auto test_code : kTestCases) { | |
| const ScopedTestFile temp_file(testing::TempDir(), test_code); | |
| std::ostringstream output; | |
| { // continue even with syntax error | |
| const int exit_code = | |
| LintOneFile(&output, temp_file.filename(), config_, false, false); | |
| EXPECT_EQ(exit_code, 0); | |
| } | |
| { // abort on syntax error | |
| const int exit_code = | |
| LintOneFile(&output, temp_file.filename(), config_, true, false); | |
| EXPECT_EQ(exit_code, 1); | |
| } | |
| } | |
| } | |
| TEST_F(LintOneFileTest, LintError) { | |
| const absl::string_view kTestCases[] = { | |
| "task automatic foo;\n" | |
| " $psprintf(\"blah\");\n" // forbidden function | |
| "endtask\n", | |
| }; | |
| for (const auto test_code : kTestCases) { | |
| const ScopedTestFile temp_file(testing::TempDir(), test_code); | |
| std::ostringstream output; | |
| { // continue even with lint error | |
| const int exit_code = | |
| LintOneFile(&output, temp_file.filename(), config_, false, false); | |
| EXPECT_EQ(exit_code, 0) << "output:\n" << output.str(); | |
| } | |
| { // abort on lint error | |
| const int exit_code = | |
| LintOneFile(&output, temp_file.filename(), config_, false, true); | |
| EXPECT_EQ(exit_code, 1) << "output:\n" << output.str(); | |
| } | |
| } | |
| } | |
| class VerilogLinterTest : public DefaultLinterConfigTestFixture, | |
| public testing::Test { | |
| public: | |
| // Test code using the default rule set. | |
| VerilogLinterTest() = default; | |
| protected: | |
| // Returns diagnostic text from analyzing source code. | |
| std::pair<absl::Status, std::string> LintAnalyzeText( | |
| const std::string& filename, const std::string& content) const { | |
| // Run the analyzer to produce a syntax tree from source code. | |
| const auto analyzer = absl::make_unique<VerilogAnalyzer>(content, filename); | |
| const absl::Status status = ABSL_DIE_IF_NULL(analyzer)->Analyze(); | |
| std::ostringstream diagnostics; | |
| if (!status.ok()) { | |
| const std::vector<std::string> syntax_error_messages( | |
| analyzer->LinterTokenErrorMessages()); | |
| for (const auto& message : syntax_error_messages) { | |
| diagnostics << message << std::endl; | |
| } | |
| } | |
| // For testing purposes we want the status returned to reflect | |
| // lint success, so as long as we have a syntax tree (even if there | |
| // are errors), run the lint checks. | |
| const absl::Status lint_status = VerilogLintTextStructure( | |
| &diagnostics, filename, content, config_, analyzer->Data()); | |
| return {lint_status, diagnostics.str()}; | |
| } | |
| }; | |
| // This test verifies that VerilogLintTextStructure runs on an empty tree. | |
| TEST_F(VerilogLinterTest, AnonymousEmptyTree) { | |
| const auto diagnostics = LintAnalyzeText("", ""); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_EQ(diagnostics.second, ""); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // that is lint-clean. | |
| TEST_F(VerilogLinterTest, NoLintViolation) { | |
| const auto diagnostics = LintAnalyzeText("good.sv", | |
| "task automatic foo;\n" | |
| " $display(\"blah\");\n" | |
| "endtask\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_EQ(diagnostics.second, ""); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // with one syntax tree lint finding. | |
| TEST_F(VerilogLinterTest, KnownTreeLintViolation) { | |
| const auto diagnostics = LintAnalyzeText("bad.sv", | |
| "task automatic foo;\n" | |
| " $psprintf(\"blah\");\n" | |
| "endtask\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| const auto expected = | |
| "bad.sv:2:3: $psprintf is a forbidden system function " | |
| "or task, please use $sformatf instead"; | |
| EXPECT_THAT(diagnostics.second, StartsWith(expected)); | |
| EXPECT_THAT(diagnostics.second, EndsWith("[invalid-system-task-function]\n")); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // with one syntax tree lint finding that is waived (next-line). | |
| TEST_F(VerilogLinterTest, KnownTreeLintViolationWaivedNextLine) { | |
| const auto diagnostics = | |
| LintAnalyzeText("bad.sv", | |
| "task automatic foo;\n" | |
| " // verilog_lint: waive invalid-system-task-function\n" | |
| " $psprintf(\"blah\");\n" | |
| "endtask\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_EQ(diagnostics.second, ""); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // with one syntax tree lint finding that is waived (same-line). | |
| TEST_F(VerilogLinterTest, KnownTreeLintViolationWaivedSameLine) { | |
| const auto diagnostics = | |
| LintAnalyzeText("bad.sv", | |
| "task automatic foo;\n" | |
| " $psprintf(\"blah\"); // verilog_lint: waive " | |
| "invalid-system-task-function\n" | |
| "endtask\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_EQ(diagnostics.second, ""); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // with one syntax tree lint finding that is waived (line-range). | |
| TEST_F(VerilogLinterTest, KnownTreeLintViolationWaivedLineRange) { | |
| const auto diagnostics = LintAnalyzeText( | |
| "bad.sv", | |
| "task automatic foo;\n" | |
| " // verilog_lint: waive-start invalid-system-task-function\n" | |
| " $psprintf(\"blah\");\n" | |
| " // verilog_lint: waive-end invalid-system-task-function\n" | |
| "endtask\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_EQ(diagnostics.second, ""); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // with one token stream lint finding. | |
| TEST_F(VerilogLinterTest, KnownTokenStreamLintViolation) { | |
| using analysis::kDefaultRuleSet; | |
| // TODO(fangism): Remove this conditional check or choose a different | |
| // token-stream based lint rule that is enabled by default. | |
| // (Just so happens that we may not want endif-comment enabled by default.) | |
| if (std::find(std::begin(kDefaultRuleSet), std::end(kDefaultRuleSet), | |
| "endif-comment") != std::end(kDefaultRuleSet)) { | |
| const auto diagnostics = LintAnalyzeText("endif.sv", | |
| "`ifdef SIM\n" | |
| "module foo;\n" | |
| "endmodule\n" | |
| "`endif\n"); | |
| static const auto expect_message = | |
| "endif.sv:4:1: `endif should be followed on the same line by a " | |
| "comment that matches the opening `ifdef/`ifndef. (SIM) "; | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_THAT(diagnostics.second, StartsWith(expect_message)); | |
| EXPECT_THAT(diagnostics.second, EndsWith("[endif-comment]\n")); | |
| } | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // with one line-lint-rule finding. | |
| TEST_F(VerilogLinterTest, KnownLineLintViolation) { | |
| const auto diagnostics = LintAnalyzeText("tab.sv", | |
| "`include \"blah.svh\";\n" | |
| "\n" | |
| "module\ttab;\n" // bad tab here | |
| "endmodule\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_THAT(diagnostics.second, | |
| StartsWith("tab.sv:3:7: Use spaces, not tabs.")); | |
| EXPECT_THAT(diagnostics.second, EndsWith("[no-tabs]\n")); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // with one text structure lint rule finding (line-length). | |
| TEST_F(VerilogLinterTest, KnownTextStructureLintViolation) { | |
| const auto diagnostics = LintAnalyzeText( | |
| "long.sv", | |
| "module long;\n" | |
| "initial xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = " | |
| "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy[777777777777];\n" | |
| "endmodule\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_THAT(diagnostics.second, | |
| StartsWith("long.sv:2:101: Line length exceeds " | |
| "max: 100; is: 114")); | |
| EXPECT_THAT(diagnostics.second, EndsWith("[line-length]\n")); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on complete source, | |
| // with one waived lint rule finding (line-length). | |
| TEST_F(VerilogLinterTest, KnownTextStructureLintViolationWaived) { | |
| const auto diagnostics = LintAnalyzeText( | |
| "long.sv", | |
| "module long;\n" | |
| "initial xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = " | |
| "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy[777777777777]; " | |
| "// verilog_lint: waive line-length\n" | |
| "endmodule\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_EQ(diagnostics.second, ""); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on a module-body | |
| // source, with one lint rule finding (line-length). | |
| TEST_F(VerilogLinterTest, ModuleBodyLineLength) { | |
| const auto diagnostics = LintAnalyzeText( | |
| "module-body.sv", | |
| "// verilog_syntax: parse-as-module-body\n" | |
| "\n" | |
| "initial xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = " | |
| "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy[777777777777];\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_THAT(diagnostics.second, | |
| StartsWith("module-body.sv:3:101: Line length exceeds max: ")); | |
| EXPECT_THAT(diagnostics.second, EndsWith("[line-length]\n")); | |
| } | |
| // This test verifies that VerilogLintTextStructure runs on a module-body | |
| // source, with one waived lint rule finding (line-length). | |
| TEST_F(VerilogLinterTest, ModuleBodyLineLengthWaived) { | |
| const auto diagnostics = LintAnalyzeText( | |
| "module-body.sv", | |
| "// verilog_syntax: parse-as-module-body\n" | |
| "\n" | |
| "initial xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = " | |
| "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy[777777777777]; " | |
| "// verilog_lint: waive line-length\n"); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_EQ(diagnostics.second, ""); | |
| } | |
| TEST_F(VerilogLinterTest, MultiByteUTF8CharactersAreOnlyCountedOnce) { | |
| // Typical comment that might be found in verilog: some ASCII-art diagram | |
| // except that the 'ˉ'-'overscore' is actually a two-byte UTF8 character. | |
| constexpr char comment_with_utf8[] = | |
| "module utf8_short;\n" | |
| R"(initial a = 42; // __/ˉˉˉˉˉˉˉˉˉ\___/ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ\___/ˉˉˉˉˉ)" | |
| "\nendmodule\n"; | |
| const auto diagnostics = LintAnalyzeText("utf8_short.sv", comment_with_utf8); | |
| EXPECT_TRUE(diagnostics.first.ok()); | |
| EXPECT_EQ(diagnostics.second, ""); | |
| } | |
| TEST(VerilogLinterDocumentationTest, AllRulesHelpDescriptions) { | |
| std::ostringstream stream; | |
| verilog::GetLintRuleDescriptionsHelpFlag(&stream, "all"); | |
| // Spot-check a few patterns, must mostly make sure generation | |
| // works without any fatal errors. | |
| EXPECT_TRUE(absl::StrContains(stream.str(), "line-length")); | |
| EXPECT_TRUE(absl::StrContains(stream.str(), "posix-eof")); | |
| EXPECT_TRUE(absl::StrContains(stream.str(), "Enabled by default:")); | |
| } | |
| TEST(VerilogLinterDocumentationTest, AllRulesMarkdown) { | |
| std::ostringstream stream; | |
| verilog::GetLintRuleDescriptionsMarkdown(&stream); | |
| // Spot-check a few patterns, must mostly make sure generation | |
| // works without any fatal errors. | |
| EXPECT_TRUE(absl::StrContains(stream.str(), "line-length")); | |
| EXPECT_TRUE(absl::StrContains(stream.str(), "posix-eof")); | |
| EXPECT_TRUE(absl::StrContains(stream.str(), "Enabled by default:")); | |
| } | |
| } // namespace | |
| } // namespace verilog |