blob: ea2077241aa918b8b23d046b96cb09d61b787325 [file] [log] [blame]
// 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