diff --git a/common/util/iterator_range.h b/common/util/iterator_range.h index e5f29ac02..f6c6e46d6 100644 --- a/common/util/iterator_range.h +++ b/common/util/iterator_range.h @@ -64,6 +64,12 @@ iterator_range make_range(std::pair p) { return iterator_range(std::move(p.first), std::move(p.second)); } +// Test if range is empty +template +bool empty_range(Iter begin, Iter end) { + return begin == end; +} + } // namespace verible #endif // VERIBLE_COMMON_UTIL_ITERATOR_RANGE_H_ diff --git a/verilog/formatting/BUILD b/verilog/formatting/BUILD index afaa77368..60f94a3f4 100644 --- a/verilog/formatting/BUILD +++ b/verilog/formatting/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) package( default_visibility = [ "//verilog/tools/formatter:__pkg__", + "//verilog/tools/style_tester:__pkg__", ], ) @@ -237,6 +238,68 @@ cc_library( ], ) +cc_library( + name = "lowrisc_format_style", + hdrs = ["lowrisc_format_style.h"], + deps = [ + ":format_style" + ], +) + +cc_library( + name = "formatter_lowrisc_style_test_cases", + srcs = ["formatter_lowrisc_style_test_cases.cc"], + hdrs = ["formatter_lowrisc_style_test_cases.h"], + deps = [ + ":lowrisc_format_style", + ], +) + +cc_test( + name = "formatter_lowrisc_style_test", + srcs = ["formatter_lowrisc_style_test.cc"], + deps = [ + ":format_style", + ":lowrisc_format_style", + ":formatter", + ":style_compliance_report", + ":formatter_lowrisc_style_test_cases", + "//common/formatting:align", + "//common/strings:position", + "//common/text:text_structure", + "//common/util:logging", + "//verilog/analysis:verilog_analyzer", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "style_compliance_report", + srcs = ["style_compliance_report.cc"], + hdrs = ["style_compliance_report.h"], + deps = [ + "//common/util:logging", + "//common/formatting:basic_format_style", + "//common/util:spacer", + "//verilog/formatting:formatter", + "//verilog/formatting:lowrisc_format_style", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "style_compliance_report_test", + srcs = ["style_compliance_report_test.cc"], + deps = [ + ":style_compliance_report", + "//common/util:logging", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "token_annotator", srcs = ["token_annotator.cc"], diff --git a/verilog/formatting/format_style.h b/verilog/formatting/format_style.h index 495caa6d2..216c72fe0 100644 --- a/verilog/formatting/format_style.h +++ b/verilog/formatting/format_style.h @@ -144,6 +144,13 @@ struct FormatStyle : public verible::BasicFormatStyle { enum_assignment_statement_alignment = policy; distribution_items_alignment = policy; } + + absl::string_view StyleName() const { + return style_name_; + } + + protected: + std::string style_name_ = "default"; }; } // namespace formatter diff --git a/verilog/formatting/formatter_lowrisc_style_test.cc b/verilog/formatting/formatter_lowrisc_style_test.cc new file mode 100644 index 000000000..7d71d97dc --- /dev/null +++ b/verilog/formatting/formatter_lowrisc_style_test.cc @@ -0,0 +1,74 @@ +// Copyright 2017-2021 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/formatting/formatter.h" + +#include +#include +#include +#include + +#include + +#include "absl/status/status.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "common/formatting/align.h" +#include "common/strings/position.h" +#include "common/text/text_structure.h" +#include "common/util/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "verilog/analysis/verilog_analyzer.h" +#include "verilog/formatting/format_style.h" +#include "verilog/formatting/formatter_lowrisc_style_test_cases.h" + +#undef EXPECT_OK +#define EXPECT_OK(value) EXPECT_TRUE((value).ok()) + +#undef ASSERT_OK +#define ASSERT_OK(value) ASSERT_TRUE((value).ok()) + +namespace verilog { +namespace formatter { + +namespace { + +using absl::StatusCode; +using verible::AlignmentPolicy; +using verible::IndentationStyle; +using verible::LineNumberSet; + +TEST(FormatterLowRISCStyleTest, ComplianceTest) { + std::pair kComplianceTestCases = + verilog::formatter::tests::GetLowRISCComplianceTestCases(); + + const auto* test_cases = ABSL_DIE_IF_NULL(std::get<0>(kComplianceTestCases)); + size_t n = std::get<1>(kComplianceTestCases); + + for (size_t i = 0 ; i < n ; ++i) { + std::ostringstream stream; + + VLOG(1) << "code-to-format:\n" << test_cases[i].input << ""; + const auto status = + FormatVerilog(test_cases[i].input, "", test_cases[i].style, stream); + EXPECT_OK(status) << status.message(); + EXPECT_EQ(stream.str(), test_cases[i].expected) << "code:\n" << test_cases[i].input; + } +} + +} // namespace +} // namespace formatter +} // namespace verilog diff --git a/verilog/formatting/formatter_lowrisc_style_test_cases.cc b/verilog/formatting/formatter_lowrisc_style_test_cases.cc new file mode 100644 index 000000000..046016c37 --- /dev/null +++ b/verilog/formatting/formatter_lowrisc_style_test_cases.cc @@ -0,0 +1,921 @@ +// Copyright 2017-2021 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. + +// Test cases in this file should check LowRISC style compliance +// Other test cases should be placed in formatter_test.cc and +// formatter_tuning_test.cc + +#include "verilog/formatting/formatter_lowrisc_style_test_cases.h" + +namespace verilog { +namespace formatter { +namespace tests { + +static const ComplianceTestCase kComplianceTestCases[] = { + {"Constraint blocks\n" + "\n" + "tags: constraint block formatter\n" + "\n" + "lrm: IEEE Std 1800-2017 18.5 \"Constraint blocks\"\n" + "\n" + "Related:\n" + "https://github.com/google/verible/issues/445\n" + "https://github.com/google/verible/issues/445#issuecomment-806232188\n"}, + + { + "Expand expression containing brackets (if-statement)", + + LowRISCFormatStyle(), + + "constraint c_iv {" + " if (fixed_iv_en) {" + " aes_iv == fixed_iv" + " };" + "}", + + // Expected + "constraint c_iv {\n" + " if (fixed_iv_en)\n" + " {aes_iv == fixed_iv};\n" + "}\n", + + // Compliant + "constraint c_iv {\n" + " if (fixed_iv_en) {\n" + " aes_iv == fixed_iv\n" + " };\n" + "}\n", + }, + { + "Expand expression containing brackets", + + LowRISCFormatStyle(), + + "constraint data_size_c {\n" + " data.size() inside {[1:65536]};\n" + " }\n", + + "constraint data_size_c {data.size() inside {[1 : 65536]};}\n", + + "constraint data_size_c {\n" + " data.size() inside {[1:65536]};\n" + "}\n", + }, + + { + "Expand blocks with two or more expressions (two statements)", + + LowRISCFormatStyle(), + + "constraint param_c {\n" + " a_param == 0;\n" + " d_param == 0;\n" + "}\n", + + "constraint param_c {\n" + " a_param == 0;\n" + " d_param == 0;\n" + "}\n", + }, + + { + "Compact constraint blocks with one expression", + + LowRISCFormatStyle(), + + "constraint only_vec_instr_c {soft only_vec_instr == 0;}", + "constraint only_vec_instr_c {soft only_vec_instr == 0;}\n", + }, + { + "Compact blocks with one expression (column limited to 40)", + + LowRISCFormatStyle(40), + + "constraint only_vec_instr_c {soft only_vec_instr == 0;}", + + "constraint only_vec_instr_c {\n" + " soft only_vec_instr == 0;\n" + "}\n", + }, + { + "Compact blocks with one expression (function call)", + + LowRISCFormatStyle(), + + "constraint mask_contiguous_c {\n" + " $countones(a_mask ^ {a_mask[MaskWidth-2:0], 1'b0}) <= 2;\n" + "}\n", + + "constraint mask_contiguous_c {\n" + " $countones(\n" + " a_mask ^ {a_mask[MaskWidth-2:0], 1'b0}\n" + " ) <= 2;\n" + "}\n", + + "constraint mask_contiguous_c {\n" + " $countones(a_mask ^ {a_mask[MaskWidth-2:0], 1'b0}) <= 2;\n" + "}\n", + }, + { + "Compact blocks with one expression", + + LowRISCFormatStyle(), + + "constraint d_opcode_c {\n" + " d_opcode inside {AccessAckData, AccessAck};\n" + "}\n", + + "constraint d_opcode_c {d_opcode inside {AccessAckData, AccessAck};}\n", + + "constraint d_opcode_c {\n" + " d_opcode inside {AccessAckData, AccessAck};\n" + "}\n", + }, + + + { + "Functional coverage\n" + "\n" + "LRM: IEEE Std 1800-2017 19.3 \"Defining the coverage model: covergroup\"\n" + }, + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/hw/dv/sv/" + "cip_lib/cip_base_env_cov.sv", + + LowRISCFormatStyle(), + + "covergroup intr_cg (uint num_interrupts) with function sample(uint intr,\n" + " bit intr_en,\n" + " bit intr_state);\n" + "endgroup\n", + + "covergroup intr_cg(\n" + " uint num_interrupts\n" + ") with function sample (\n" + " uint intr, bit intr_en, bit intr_state\n" + ");\n" + "endgroup\n", + + "covergroup intr_cg (uint num_interrupts) with function sample(uint intr,\n" + " bit intr_en,\n" + " bit intr_state);\n" + "endgroup\n", + }, + // FIXME(ldk): column_limit 80, 50, etc... + + + {"Import declarations\n" + "\n" + "tags: declaration dpi import\n" + "\n" + "LRM:\n" + "IEEE Std 1800-2017 35.5.4 \"Import declarations\"\n"}, + + { + "Import declarations", + + LowRISCFormatStyle(), + + "import \"DPI-C\" function chandle spidpi_create(input string name, input int mode,\n" + " input int loglevel);\n", + + "import \"DPI-C\" function chandle spidpi_create(input string name, input int mode,\n" + " input int loglevel);\n", + + "import \"DPI-C\"\n" + "function chandle spidpi_create(input string name, input int mode,\n" + " input int loglevel);\n", + }, + { + "", + + LowRISCFormatStyle(), + + "import \"DPI-C\"" + "function void dmidpi_tick(input chandle ctx, output bit dmi_req_valid," + " input bit dmi_req_ready, output bit [6:0] dmi_req_addr," + " output bit [1:0] dmi_req_op, output bit [31:0] dmi_req_data," + " input bit dmi_rsp_valid, output bit dmi_rsp_ready," + " input bit [31:0] dmi_rsp_data, input bit [1:0] dmi_rsp_resp," + " output bit dmi_rst_n);", + + "import \"DPI-C\" function void dmidpi_tick(\n" + " input chandle ctx, output bit dmi_req_valid, input bit dmi_req_ready,\n" + " output bit [6:0] dmi_req_addr, output bit [1:0] dmi_req_op, output bit [31:0] dmi_req_data,\n" + " input bit dmi_rsp_valid, output bit dmi_rsp_ready, input bit [31:0] dmi_rsp_data,\n" + " input bit [1:0] dmi_rsp_resp, output bit dmi_rst_n);\n", + + "import \"DPI-C\"\n" + "function void dmidpi_tick(input chandle ctx, output bit dmi_req_valid,\n" + " input bit dmi_req_ready, output bit [6:0] dmi_req_addr,\n" + " output bit [1:0] dmi_req_op, output bit [31:0] dmi_req_data,\n" + " input bit dmi_rsp_valid, output bit dmi_rsp_ready,\n" + " input bit [31:0] dmi_rsp_data, input bit [1:0] dmi_rsp_resp,\n" + " output bit dmi_rst_n);\n", + }, + + {"Continuous assignments"}, + + { + "Continuous assignment should be in one line (if fits)", + + LowRISCFormatStyle(), + + "assign d2p = {spi_device_sdo_i, spi_device_sdo_en_i};\n", + + "assign d2p = {spi_device_sdo_i, spi_device_sdo_en_i};\n", + }, + { + "Continuous assignment (column limited to 40)", + + LowRISCFormatStyle(40), + + "assign d2p = {spi_device_sdo_i, spi_device_sdo_en_i};\n", + + "assign d2p = {\n" + " spi_device_sdo_i, spi_device_sdo_en_i\n" + "};\n", + + // Desired output + "assign d2p = {\n" + " spi_device_sdo_i,\n" + " spi_device_sdo_en_i\n" + "};\n", + }, + { + "", + + LowRISCFormatStyle(20), + + "assign d2p = {spi_device_sdo_i, spi_device_sdo_en_i};\n", + + "assign d2p = {\n" + " spi_device_sdo_i,\n" + " spi_device_sdo_en_i\n" + "};\n", + }, + + + {"Module declaration"}, + + { + "Module parameters", + + LowRISCFormatStyle(), + + "module spidpi\n" + " #(\n" + " parameter string NAME = \"spi0\",\n" + " parameter MODE = 0,\n" + " parameter LOG_LEVEL = 9\n" + ");\n" + "endmodule", + + "module spidpi #(\n" + " parameter string NAME = \"spi0\",\n" + " parameter MODE = 0,\n" + " parameter LOG_LEVEL = 9\n" + ");\n" + "endmodule\n", + }, + + { + "Module port list", + + LowRISCFormatStyle(), + + "module spidpi (" + "input logic clk_i," + "input logic rst_ni," + "output logic spi_device_sck_o," + "output logic spi_device_csb_o," + "output logic spi_device_sdi_o," + "input logic spi_device_sdo_i," + "input logic spi_device_sdo_en_i);" + "endmodule", + + "module spidpi (\n" + " input logic clk_i,\n" + " input logic rst_ni,\n" + " output logic spi_device_sck_o,\n" + " output logic spi_device_csb_o,\n" + " output logic spi_device_sdi_o,\n" + " input logic spi_device_sdo_i,\n" + " input logic spi_device_sdo_en_i\n" + ");\n" + "endmodule\n", + }, + + { + "Module with ports and parameters", + + LowRISCFormatStyle(), + + "module spidpi" + " #(" + " parameter string NAME = \"spi0\"," + " parameter MODE = 0," + " parameter LOG_LEVEL = 9" + " )(" + " input logic clk_i," + " input logic rst_ni," + " output logic spi_device_sck_o," + " output logic spi_device_csb_o," + " output logic spi_device_sdi_o," + " input logic spi_device_sdo_i," + " input logic spi_device_sdo_en_i" + ");endmodule", + + "module spidpi #(\n" + " parameter string NAME = \"spi0\",\n" + " parameter MODE = 0,\n" + " parameter LOG_LEVEL = 9\n" + ") (\n" + " input logic clk_i,\n" + " input logic rst_ni,\n" + " output logic spi_device_sck_o,\n" + " output logic spi_device_csb_o,\n" + " output logic spi_device_sdi_o,\n" + " input logic spi_device_sdo_i,\n" + " input logic spi_device_sdo_en_i\n" + ");\n" + "endmodule\n", + + "module spidpi #(\n" + " parameter string NAME = \"spi0\",\n" + " parameter MODE = 0,\n" + " parameter LOG_LEVEL = 9\n" + ") (\n" + " input logic clk_i,\n" + " input logic rst_ni,\n" + " output logic spi_device_sck_o,\n" + " output logic spi_device_csb_o,\n" + " output logic spi_device_sdi_o,\n" + " input logic spi_device_sdo_i,\n" + " input logic spi_device_sdo_en_i\n" + ");\n" + "endmodule\n", + }, + + {"Binary operators"}, + + { + "Binary operators", + + LowRISCFormatStyle(100), + + "parameter int KMAC_REQ_DATA_WIDTH = keymgr_pkg::KmacDataIfWidth\n" + " + keymgr_pkg::KmacDataIfWidth / 8\n" + " + 1;\n", + + "parameter\n" + " int KMAC_REQ_DATA_WIDTH = keymgr_pkg::KmacDataIfWidth + keymgr_pkg::KmacDataIfWidth / 8 + 1;\n" + }, + { + "", + + LowRISCFormatStyle(80), + + "parameter int KMAC_REQ_DATA_WIDTH = keymgr_pkg::KmacDataIfWidth\n" + " + keymgr_pkg::KmacDataIfWidth / 8\n" + " + 1;\n", + + "parameter int KMAC_REQ_DATA_WIDTH =\n" + " keymgr_pkg::KmacDataIfWidth + keymgr_pkg::KmacDataIfWidth / 8 + 1;\n" + }, + + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/" + "hw/dv/sv/csr_utils/csr_seq_lib.sv", + + LowRISCFormatStyle(), + + "class csr_aliasing_seq extends csr_base_seq;\n" + " virtual task body();\n" + " foreach (all_csrs[j]) begin\n" + " if (is_excl(all_csrs[j], CsrExclInitCheck, CsrAliasingTest) ||\n" + " is_excl(all_csrs[j], CsrExclWriteCheck, CsrAliasingTest)) begin\n" + " end\n" + " end\n" + " endtask\n" + "endclass\n", + + "class csr_aliasing_seq extends csr_base_seq;\n" + " virtual task body();\n" + " foreach (all_csrs[j]) begin\n" + " if (is_excl(\n" + " all_csrs[j], CsrExclInitCheck, CsrAliasingTest\n" + " ) || is_excl(\n" + " all_csrs[j], CsrExclWriteCheck, CsrAliasingTest\n" + " )) begin\n" + " end\n" + " end\n" + " endtask\n" + "endclass\n", + + "class csr_aliasing_seq extends csr_base_seq;\n" + " virtual task body();\n" + " foreach (all_csrs[j]) begin\n" + " if (is_excl(all_csrs[j], CsrExclInitCheck, CsrAliasingTest) ||\n" + " is_excl(all_csrs[j], CsrExclWriteCheck, CsrAliasingTest)) begin\n" + " end\n" + " end\n" + " endtask\n" + "endclass\n", + }, + + { + "Ternary operators" + }, + + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/" + "hw/dv/sv/alert_esc_agent/esc_receiver_driver.sv", + + LowRISCFormatStyle(), + + "class esc_receiver_driver extends alert_esc_base_driver;\n" + " virtual task drive_esc_resp(alert_esc_seq_item req);\n" + " int toggle_cycle = req.int_err ? cfg.ping_timeout_cycle / 2 : 1;\n" + " endtask\n" + "endclass", + + "class esc_receiver_driver extends alert_esc_base_driver;\n" + " virtual task drive_esc_resp(alert_esc_seq_item req);\n" + " int toggle_cycle = req.int_err ? cfg.ping_timeout_cycle / 2 : 1;\n" + " endtask\n" + "endclass\n" + }, + + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/" + "hw/dv/sv/alert_esc_agent/alert_receiver_driver.sv", + + LowRISCFormatStyle(), + + "class alert_receiver_driver extends alert_esc_base_driver;\n" + " virtual task drive_alert_ping(alert_esc_seq_item req);\n" + " int unsigned ping_delay = (cfg.use_seq_item_ping_delay) ? req.ping_delay :\n" + " $urandom_range(cfg.ping_delay_max, cfg.ping_delay_min);\n" + " endtask\n" + "endclass\n", + + "class alert_receiver_driver extends alert_esc_base_driver;\n" + " virtual task drive_alert_ping(alert_esc_seq_item req);\n" + " int unsigned ping_delay = (cfg.use_seq_item_ping_delay) ? req.ping_delay : $urandom_range(\n" + " cfg.ping_delay_max, cfg.ping_delay_min\n" + " );\n" + " endtask\n" + "endclass\n", + + "class alert_receiver_driver extends alert_esc_base_driver;\n" + " virtual task drive_alert_ping(alert_esc_seq_item req);\n" + " int unsigned ping_delay = (cfg.use_seq_item_ping_delay) ? req.ping_delay :\n" + " $urandom_range(cfg.ping_delay_max, cfg.ping_delay_min);\n" + " endtask\n" + "endclass\n", + }, + + {"Labels\n" + "\n" + "StyleGuide:\n" + "https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md#labels\n"}, + + { + "When labeling code blocks, add one space before and after the colon.\n" + "\n" + "ref: https://raw.githubusercontent.com/lowRISC/opentitan/" + "8933d96c28e0e1054ea488d56940093109451c68/hw/dv/sv/" + "push_pull_agent/push_pull_agent_pkg.sv", + + LowRISCFormatStyle(), + + "package push_pull_agent_pkg;\n" + "endpackage: push_pull_agent_pkg\n", + + "package push_pull_agent_pkg;\n" + "endpackage : push_pull_agent_pkg\n", + }, + + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/hw/dv/" + "sv/dv_lib/dv_base_monitor.sv", + + LowRISCFormatStyle(), + + "class dv_base_monitor;\n" + "virtual task watchdog_ok_to_end(uvm_phase run_phase);\n" + " fork\n" + " begin: isolation_fork\n" + " end: isolation_fork\n" + " join\n" + "endtask\n" + "endclass\n", + + "class dv_base_monitor;\n" + " virtual task watchdog_ok_to_end(uvm_phase run_phase);\n" + " fork\n" + " begin : isolation_fork\n" + " end : isolation_fork\n" + " join\n" + " endtask\n" + "endclass\n", + }, + + { + "Line wrapping\n" + "\n" + "guide: https://github.com/lowRISC/style-guides/blob/" + "master/VerilogCodingStyle.md#line-wrapping" + }, + + { + "Open syntax characters such as { or ( that end one line of" + " a multi-line expression should be terminated with close " + "characters (}, )) on their own line.\n" + "\n" + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/hw/dv/sv/" + "push_pull_agent/push_pull_item.sv", + + LowRISCFormatStyle(), + + "class push_pull_item;\n" + " virtual function string convert2string();\n" + " return {$sformatf(\"h_data = 0x%0x \", h_data),\n" + " $sformatf(\"d_data = 0x%0x \", d_data),\n" + " $sformatf(\"host_delay = 0x%0x \", host_delay),\n" + " $sformatf(\"device_delay = 0x%0x \", device_delay)};\n" + " endfunction\n" + "endclass\n", + + "class push_pull_item;\n" + " virtual function string convert2string();\n" + " return {\n" + " $sformatf(\"h_data = 0x%0x \", h_data),\n" + " $sformatf(\"d_data = 0x%0x \", d_data),\n" + " $sformatf(\"host_delay = 0x%0x \", host_delay),\n" + " $sformatf(\"device_delay = 0x%0x \", device_delay)\n" + " };\n" + " endfunction\n" + "endclass\n", + }, + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/hw/dv/sv/" + "uart_agent/uart_agent_cov.sv", + + LowRISCFormatStyle(), + + "class uart_agent_cov;\n" + "covergroup uart_reset_cg;\n" + " cp_dir: coverpoint dir;\n" + " cp_rst_pos: coverpoint bit_position {\n" + " bins values[] = {[0:NUM_UART_XFER_BITS_WO_PARITY]};\n" + " }\n" + " cross cp_dir, cp_rst_pos;\n" + "endgroup\n" + "endclass\n", + + "class uart_agent_cov;\n" + " covergroup uart_reset_cg;\n" + " cp_dir: coverpoint dir;\n" + " cp_rst_pos: coverpoint bit_position {bins values[] = {[0 : NUM_UART_XFER_BITS_WO_PARITY]};}\n" + " cross cp_dir, cp_rst_pos;\n" + " endgroup\n" + "endclass\n", + }, + + { + "", + + LowRISCFormatStyle(80), + + "class uart_agent_cov;\n" + "covergroup uart_reset_cg;\n" + " cp_dir: coverpoint dir;\n" + " cp_rst_pos: coverpoint bit_position {\n" + " bins values[] = {[0:NUM_UART_XFER_BITS_WO_PARITY]};\n" + " }\n" + " cross cp_dir, cp_rst_pos;\n" + "endgroup\n" + "endclass\n", + + "class uart_agent_cov;\n" + " covergroup uart_reset_cg;\n" + " cp_dir: coverpoint dir;\n" + " cp_rst_pos: coverpoint bit_position {\n" + " bins values[] = {[0 : NUM_UART_XFER_BITS_WO_PARITY]};\n" + " }\n" + " cross cp_dir, cp_rst_pos;\n" + " endgroup\n" + "endclass\n" + }, + + { + "", + + LowRISCFormatStyle(50), + + "class uart_agent_cov;\n" + "covergroup uart_reset_cg;\n" + " cp_dir: coverpoint dir;\n" + " cp_rst_pos: coverpoint bit_position {\n" + " bins values[] = {[0:NUM_UART_XFER_BITS_WO_PARITY]};\n" + " }\n" + " cross cp_dir, cp_rst_pos;\n" + "endgroup\n" + "endclass\n", + + "class uart_agent_cov;\n" + " covergroup uart_reset_cg;\n" + " cp_dir: coverpoint dir;\n" + " cp_rst_pos: coverpoint bit_position {\n" + " bins values[] = {\n" + " [0 : NUM_UART_XFER_BITS_WO_PARITY]\n" + " };\n" + " }\n" + " cross cp_dir, cp_rst_pos;\n" + " endgroup\n" + "endclass\n" + }, + + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/hw/dv/sv/" + "cip_lib/cip_base_env_cov.sv", + + LowRISCFormatStyle(), + + "covergroup intr_test_cg;" + " cross cp_intr, cp_intr_test, cp_intr_en, cp_intr_state {\n" + " illegal_bins test_1_state_0 = binsof(cp_intr_test) intersect {1} &&\n" + " binsof(cp_intr_state) intersect {0};\n" + " }\n" + "endgroup\n", + + "covergroup intr_test_cg;\n" + " cross cp_intr, cp_intr_test, cp_intr_en, cp_intr_state{\n" + " illegal_bins test_1_state_0 = binsof (cp_intr_test) intersect {\n" + " 1\n" + " } && binsof (cp_intr_state) intersect {\n" + " 0\n" + " };\n" + " }\n" + "endgroup\n", + + "covergroup intr_test_cg;" + " cross cp_intr, cp_intr_test, cp_intr_en, cp_intr_state {\n" + " illegal_bins test_1_state_0 = binsof(cp_intr_test) intersect {1} &&\n" + " binsof(cp_intr_state) intersect {0};\n" + " }\n" + "endgroup\n" + }, + + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/hw/dv/sv/" + "test_vectors/test_vectors_pkg.sv", + + LowRISCFormatStyle(), + + "string sha_file_list[] = {\"vectors/sha/sha256/SHA256ShortMsg.rsp\",\n" + " \"vectors/sha/sha256/SHA256LongMsg.rsp\"\n" + " };\n", + + "string sha_file_list[] = {\n" + " \"vectors/sha/sha256/SHA256ShortMsg.rsp\", \"vectors/sha/sha256/SHA256LongMsg.rsp\"\n" + "};\n" + }, + { + "", + + LowRISCFormatStyle(80), + + "string sha_file_list[] = {\"vectors/sha/sha256/SHA256ShortMsg.rsp\",\n" + " \"vectors/sha/sha256/SHA256LongMsg.rsp\"\n" + " };\n", + + "string sha_file_list[] = {\n" + " \"vectors/sha/sha256/SHA256ShortMsg.rsp\",\n" + " \"vectors/sha/sha256/SHA256LongMsg.rsp\"\n" + "};\n" + }, + + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/" + "hw/dv/sv/tl_agent/seq_lib/tl_host_seq.sv", + + LowRISCFormatStyle(), + + "class tl_host_seq;\n" + " virtual function void randomize_req(REQ req, int idx);\n" + " if (!(req.randomize() with {\n" + " a_valid_delay inside {[min_req_delay:max_req_delay]};})) begin\n" + " `uvm_fatal(`gfn, \"Cannot randomize req\")\n" + " end\n" + " endfunction\n" + "endclass\n", + + "class tl_host_seq;\n" + " virtual function void randomize_req(REQ req, int idx);\n" + " if (!(req.randomize() with {a_valid_delay inside {[min_req_delay : max_req_delay]};})) begin\n" + " `uvm_fatal(`gfn, \"Cannot randomize req\")\n" + " end\n" + " endfunction\n" + "endclass\n" + }, + + { + "", + + LowRISCFormatStyle(80), + + "class tl_host_seq;\n" + " virtual function void randomize_req(REQ req, int idx);\n" + " if (!(req.randomize() with {\n" + " a_valid_delay inside {[min_req_delay:max_req_delay]};})) begin\n" + " `uvm_fatal(`gfn, \"Cannot randomize req\")\n" + " end\n" + " endfunction\n" + "endclass\n", + + "class tl_host_seq;\n" + " virtual function void randomize_req(REQ req, int idx);\n" + " if (!(req.randomize() with {\n" + " a_valid_delay inside {[min_req_delay : max_req_delay]};\n" + " })) begin\n" + " `uvm_fatal(`gfn, \"Cannot randomize req\")\n" + " end\n" + " endfunction\n" + "endclass\n", + + "class tl_host_seq;\n" + " virtual function void randomize_req(REQ req, int idx);\n" + " if (!(req.randomize() with {\n" + " a_valid_delay inside {[min_req_delay : max_req_delay]};})) begin\n" + " `uvm_fatal(`gfn, \"Cannot randomize req\")\n" + " end\n" + " endfunction\n" + "endclass\n" + }, + + { + "Nested function calls", + }, + + { + "", + + LowRISCFormatStyle(), + + "`uvm_info(`gtn, $sformatf(\"Verifying reset value of register %0s\",\n" + " test_csrs[i].get_full_name()), UVM_MEDIUM)\n", + + "`uvm_info(`gtn, $sformatf(\"Verifying reset value of register %0s\", test_csrs[i].get_full_name()),\n" + " UVM_MEDIUM)\n" + }, + { + "", + + LowRISCFormatStyle(80), + + "`uvm_info(`gtn, $sformatf(\"Verifying reset value of register %0s\",\n" + " test_csrs[i].get_full_name()), UVM_MEDIUM)\n", + + "`uvm_info(`gtn, $sformatf(\n" + " \"Verifying reset value of register %0s\", test_csrs[i].get_full_name()\n" + " ), UVM_MEDIUM)\n", + + "`uvm_info(`gtn, $sformatf(\"Verifying reset value of register %0s\",\n" + " test_csrs[i].get_full_name()), UVM_MEDIUM)\n" + }, + + { + "Alignment" + }, + + { + "ref:https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/" + "hw/dv/sv/csr_utils/csr_seq_lib.sv", + + LowRISCFormatStyle(), + + "class csr_bit_bash_seq extends csr_base_seq;\n" + " task bash_kth_bit;\n" + " repeat (2) begin\n" + " csr_rd_check(.ptr (rg),\n" + " .blocking (0),\n" + " .compare (!external_checker),\n" + " .compare_vs_ral(1'b1),\n" + " .compare_mask (~mask),\n" + " .err_msg (err_msg));\n" + " end\n" + " endtask: bash_kth_bit\n" + "endclass\n", + + "class csr_bit_bash_seq extends csr_base_seq;\n" + " task bash_kth_bit;\n" + " repeat (2) begin\n" + " csr_rd_check(.ptr(rg), .blocking(0), .compare(!external_checker), .compare_vs_ral(1'b1),\n" + " .compare_mask(~mask), .err_msg(err_msg));\n" + " end\n" + " endtask : bash_kth_bit\n" + "endclass\n", + + "class csr_bit_bash_seq extends csr_base_seq;\n" + " task bash_kth_bit;\n" + " repeat (2) begin\n" + " csr_rd_check(.ptr (rg),\n" + " .blocking (0),\n" + " .compare (!external_checker),\n" + " .compare_vs_ral(1'b1),\n" + " .compare_mask (~mask),\n" + " .err_msg (err_msg));\n" + " end\n" + " endtask: bash_kth_bit\n" + "endclass\n", + }, + + { + "ref: https://github.com/lowRISC/opentitan/blob/" + "8933d96c28e0e1054ea488d56940093109451c68/" + "hw/dv/sv/kmac_app_agent/seq_lib/kmac_app_device_seq.sv", + + LowRISCFormatStyle(), + + "class kmac_app_device_seq extends kmac_app_base_seq;\n" + " virtual function void randomize_item(REQ item);\n" + " `DV_CHECK_RANDOMIZE_WITH_FATAL(item,\n" + " if (cfg.zero_delays) {\n" + " rsp_delay == 0;\n" + " } else {\n" + " rsp_delay inside {[cfg.rsp_delay_min : cfg.rsp_delay_max]};\n" + " }\n" + " is_kmac_rsp_err dist {1 :/ cfg.error_rsp_pct,\n" + " 0 :/ 100 - cfg.error_rsp_pct};\n" + " )\n" + " endfunction\n" + "endclass\n", + + "class kmac_app_device_seq extends kmac_app_base_seq;\n" + " virtual function void randomize_item(REQ item);\n" + " `DV_CHECK_RANDOMIZE_WITH_FATAL(item,\n" + " if (cfg.zero_delays) {\n" + " rsp_delay == 0;\n" + " } else {\n" + " rsp_delay inside {[cfg.rsp_delay_min : cfg.rsp_delay_max]};\n" + " }\n" + " is_kmac_rsp_err dist {1 :/ cfg.error_rsp_pct,\n" + " 0 :/ 100 - cfg.error_rsp_pct};)\n" + " endfunction\n" + "endclass\n", + + "class kmac_app_device_seq extends kmac_app_base_seq;\n" + " virtual function void randomize_item(REQ item);\n" + " `DV_CHECK_RANDOMIZE_WITH_FATAL(\n" + " item,\n" + " if (cfg.zero_delays) {\n" + " rsp_delay == 0;\n" + " } else {\n" + " rsp_delay inside {[cfg.rsp_delay_min : cfg.rsp_delay_max]};\n" + " }\n" + " is_kmac_rsp_err dist {1 :/ cfg.error_rsp_pct,\n" + " 0 :/ 100 - cfg.error_rsp_pct};)\n" + " endfunction\n" + "endclass\n", + }, +}; + +template +constexpr std::size_t arraySize(T (&)[N]) noexcept { + return N; +} + +std::pair +GetLowRISCComplianceTestCases() { + return std::make_pair( + kComplianceTestCases, arraySize(kComplianceTestCases)); +} + +} // namespace tests +} // namespace formatter +} // namespace verilog diff --git a/verilog/formatting/formatter_lowrisc_style_test_cases.h b/verilog/formatting/formatter_lowrisc_style_test_cases.h new file mode 100644 index 000000000..c2a4fdcf8 --- /dev/null +++ b/verilog/formatting/formatter_lowrisc_style_test_cases.h @@ -0,0 +1,44 @@ +// Copyright 2017-2021 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. + +#ifndef VERIBLE_VERILOG_FORMATTING_FORMATTER_LOWRISC_STYLE_TEST_CASES_H_ +#define VERIBLE_VERILOG_FORMATTING_FORMATTER_LOWRISC_STYLE_TEST_CASES_H_ + +#include + +#include "verilog/formatting/lowrisc_format_style.h" + +namespace verilog { +namespace formatter { +namespace tests { + +struct ComplianceTestCase { + absl::string_view description; + + LowRISCFormatStyle style; + + absl::string_view input; + absl::string_view expected; + + absl::string_view compliant; +}; + +std::pair +GetLowRISCComplianceTestCases(); + +} // namespace tests +} // namespace formatter +} // namespace verilog + +#endif // VERIBLE_VERILOG_FORMATTING_FORMATTER_LOWRISC_STYLE_TEST_CASES_H_ diff --git a/verilog/formatting/lowrisc_format_style.h b/verilog/formatting/lowrisc_format_style.h new file mode 100644 index 000000000..a7bdedb98 --- /dev/null +++ b/verilog/formatting/lowrisc_format_style.h @@ -0,0 +1,49 @@ +// Copyright 2017-2021 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. +// LowRISC format style. + +#ifndef VERIBLE_VERILOG_FORMATTING_LOWRISC_FORMAT_STYLE_H_ +#define VERIBLE_VERILOG_FORMATTING_LOWRISC_FORMAT_STYLE_H_ + +#include "verilog/formatting/format_style.h" + +namespace verilog { +namespace formatter { + +// Style parameters specific to LowRISC format style guide +struct LowRISCFormatStyle : public FormatStyle { + LowRISCFormatStyle() : FormatStyle() { + indentation_spaces = 2; + wrap_spaces = 4; + column_limit = 100; + + formal_parameters_indentation = verible::IndentationStyle::kIndent; + named_parameter_indentation = verible::IndentationStyle::kIndent; + named_port_indentation = verible::IndentationStyle::kIndent; + port_declarations_alignment = verible::AlignmentPolicy::kPreserve; + + style_name_ = "lowrisc"; + } + + LowRISCFormatStyle(unsigned int n) : LowRISCFormatStyle() { + column_limit = n; + } + + LowRISCFormatStyle(const LowRISCFormatStyle&) = default; +}; + +} // namespace formatter +} // namespace verilog + +#endif // VERIBLE_VERILOG_FORMATTING_LOWRISC_FORMAT_STYLE_H_ diff --git a/verilog/formatting/style_compliance_report.cc b/verilog/formatting/style_compliance_report.cc new file mode 100644 index 000000000..7d21033c4 --- /dev/null +++ b/verilog/formatting/style_compliance_report.cc @@ -0,0 +1,336 @@ +// Copyright 2017-2021 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. +// LowRISC format style. + +#include + +#include "absl/strings/str_split.h" +#include "absl/strings/str_cat.h" +#include "common/util/logging.h" +#include "verilog/formatting/formatter.h" +#include "verilog/formatting/format_style.h" +#include "verilog/formatting/style_compliance_report.h" +#include "verilog/formatting/lowrisc_format_style.h" + +namespace verible { + +std::string StyleComplianceReport::BuildConfiguration() const { + std::ostringstream configuration; + + configuration << "import sphinx_rtd_theme\n"; + + configuration << "\n"; + + configuration << "project = '"; + configuration << project_name_; + configuration << "'\n"; + + configuration << "copyright = '"; + configuration << copyrights_; + configuration << "'\n"; + + configuration << "author = '"; + configuration << authors_; + configuration << "'\n"; + + configuration << "\n"; + + configuration << "exclude_patterns = []\n"; + + configuration << "\n"; + + configuration << "extensions = [ \"sphinx_rtd_theme\", ]\n"; + configuration << "\n"; + + // Optional extra options + // configuration << "html_theme_options = {\n"; + // configuration << " 'collapse_navigation' : True,\n"; + // configuration << " 'navigation_depth' : 1,\n"; + // configuration << " 'titles_only' : True,\n"; + // configuration << "}\n"; + // configuration << "\n"; + + configuration << "html_theme = 'sphinx_rtd_theme'\n"; + + return configuration.str(); +} + +std::string StyleComplianceReport::BuildHeader() const { + std::ostringstream header; + + header << ".. |hr| raw:: html\n"; + header << "\n"; + header << "
\n\n"; + + header << project_name_ << std::endl; + header << verible::Spacer(project_name_.size(), '=') << std::endl; + + header << ".. toctree::\n"; + header << " :maxdepth: 1\n"; + header << " :caption: Contents:\n"; + header << "\n"; + + return header.str(); +} + +absl::string_view trim(absl::string_view s) { + if (s.size() == 0) { + return s; + } + + const auto pos = s.find_first_not_of(' '); + const auto n = s.find_last_not_of(' '); + + return s.substr(pos, pos - n); +} + +std::map StyleComplianceTestCase::GetDescription() const { + std::map ret; + const auto& description = description_; + + std::vector splitted_description = + absl::StrSplit(description, '\n'); + + const int tag_max_size = 20; + std::map::iterator itr = ret.end(); + + for (const auto& line : splitted_description) { + // end of description field + if (line.size() == 0) { + itr = ret.end(); + continue ; + } + + if (itr == ret.end()) { + const auto tag_pos = line.find(':'); + if (tag_pos == absl::string_view::npos || tag_pos > tag_max_size) { + if (ret.find("title") == ret.end()) { + // didn't find, defaulting to title + std::pair< + std::map::iterator, + bool> r = ret.insert(std::pair("title", line)); + itr = r.first; + } else if (ret.find("info") == ret.end() && ret.size() == 1) { + std::pair< + std::map::iterator, + bool> r = ret.insert(std::pair("info", line)); + itr = r.first; + //} else { + // std::pair< + // std::map::iterator, + // bool> r = ret.insert(std::pair("unknown", line)); + // itr = r.first; + } + } else { + absl::string_view tag_name = line.substr(0, tag_pos); + std::pair< + std::map::iterator, + bool> r = ret.insert(std::pair( + tag_name, + trim(line.substr(tag_pos+1)))); + itr = r.first; + } + } else { + if (itr->second.size() > 0) { + itr->second += "\n"; + } + itr->second += std::string{trim(line)}; + } + } + + return ret; +} + +StyleComplianceTestCase::StyleComplianceTestCase(std::string filename, + std::string description, + std::string code) { + filename_ = filename; + + description_ = description; + + input_ = code; + expected_ = code; + compliance_ = code; + + // Default format style (basic) + if (style_ == nullptr) { + style_ = std::make_unique(); + } + + const auto desc = GetDescription(); + + const auto style_itr = desc.find("style"); + if (style_itr != desc.end()) { + std::vector tags = absl::StrSplit(style_itr->second, " "); + const auto& style_name = tags[0]; + if (!style_name.compare("lowrisc")) { + style_ = std::make_unique(); + } + + for (const auto itr : tags) { + const std::vector vars = absl::StrSplit(itr, "="); + + if (!vars[0].compare("column_limit")) { + bool noerror = absl::SimpleAtoi( + vars[1], &ABSL_DIE_IF_NULL(style_)->column_limit); + CHECK_EQ(noerror, true); + } + } + } + + const auto should_fail_itr = desc.find("should_fail"); + if (should_fail_itr != desc.end()) { + bool noerror = absl::SimpleAtob(should_fail_itr->second, &should_fail_); + CHECK_EQ(noerror, true); + } +} + +StyleComplianceTestCase::StyleComplianceTestCase(std::string description, + std::string input, + std::string expected, + std::string compliance) { + filename_ = ""; + + description_ = description; + + input_ = input; + expected_ = expected; + compliance_ = compliance; +} + +StyleComplianceTestCase StyleComplianceReport::BuildTestCase( + absl::string_view contents, absl::string_view filename) { + // split contents + std::vector lines = absl::StrSplit(contents, '\n'); + std::string description, code; + + auto itr = lines.begin(); + for (; itr != lines.end() && absl::StartsWith(*itr, "//"); ++itr) { + const auto at = itr->find_first_not_of("/ \t"); + if (at != absl::string_view::npos) { + absl::StrAppend(&description, itr->substr(at), "\n"); + } else { + // empty line + absl::StrAppend(&description, "\n"); + } + } + + // skip empty lines + for (; itr != lines.end() && itr->empty() ; ++itr) ; + + for (; itr != lines.end(); ++itr) { + absl::StrAppend(&code, *itr, "\n"); + } + + VLOG(4) << "desc:\n" << description << "\ncode:\n" << code; + return StyleComplianceTestCase(std::string{filename}, description, code); +} + +StyleComplianceTestCase StyleComplianceReport::BuildTestCase( + absl::string_view description, + absl::string_view input, + absl::string_view expected, + absl::string_view compliance) { + return StyleComplianceTestCase( + std::string{description}, std::string{input}, + std::string{expected}, std::string{compliance}); +} + +bool StyleComplianceTestCase::Format() { + std::ostringstream stream; + + const auto status = + verilog::formatter::FormatVerilog( + input_, filename_, *ABSL_DIE_IF_NULL(style_), stream); + + formatted_output_ = stream.str(); + return status.ok(); +} + +std::string StyleComplianceTestCase::BuildReportEntry() const { + std::ostringstream out; + + std::map desc = GetDescription(); + + auto title = desc.find("title"); + if (title != desc.end()) { + out << title->second << "\n"; + if (input_.size() == 0) { + out << verible::Spacer(title->second.size(), '='); + } else { + out << verible::Spacer(title->second.size(), '-'); + } + out << "\n\n"; + } + + auto info = desc.find("info"); + if (info != desc.end()) { + for (const auto itr : absl::StrSplit(info->second, '\n')) { + out << itr << "\n"; + } + out << "\n"; + } + + auto gh_issue = desc.find("gh_issue"); + if (gh_issue != desc.end()) { + out << ".. note::\n\n"; + out << " " << "GitHub issue(s):\n"; + for (auto rel : absl::StrSplit(gh_issue->second, '\n')) { + const auto number = rel.rfind('/'); + if (number != absl::string_view::npos) { + out << " `#" << rel.substr(number+1) << + " <" << rel << ">`_\n"; + } else { + out << " `url: <" << rel << ">`_\n"; + } + } + out << "\n"; + } + + if (input_.size() == 0) { + return out.str(); + } + + if (compliance_.size() > 0) { + out << ".. code-block:: systemverilog\n\n"; + for (const auto line : absl::StrSplit(compliance_, '\n')) { + out << " " << line << "\n"; + } + out << "\n"; + + if (compliance_ != formatted_output_) { + out << ".. error::\n Formatter generated output:\n\n"; + + out << " .. code-block:: systemverilog\n\n"; + for (const auto line : absl::StrSplit(formatted_output_, '\n')) { + out << " " << line << "\n"; + } + + out << "\n"; + } + } else { + out << "\nExample code:\n\n"; + out << ".. code-block:: systemverilog\n\n"; + for (const auto line : absl::StrSplit(formatted_output_, '\n')) { + out << " " << line << "\n"; + } + out << "\n"; + } + + out << "\n\n|hr|\n\n"; + + return out.str(); +} + +} // namespace verible diff --git a/verilog/formatting/style_compliance_report.h b/verilog/formatting/style_compliance_report.h new file mode 100644 index 000000000..0c2356ba3 --- /dev/null +++ b/verilog/formatting/style_compliance_report.h @@ -0,0 +1,155 @@ +// Copyright 2017-2021 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. +// LowRISC format style. + +#ifndef VERIBLE_COMMON_UTIL_STYLE_COMPLIANCE_REPORT_H_ +#define VERIBLE_COMMON_UTIL_STYLE_COMPLIANCE_REPORT_H_ + +#include + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "common/formatting/basic_format_style.h" +#include "common/util/logging.h" +#include "common/util/spacer.h" +#include "verilog/formatting/format_style.h" + +namespace verible { + +class StyleComplianceTestCase { + public: + StyleComplianceTestCase(std::string filename, + std::string description, + std::string code); + + StyleComplianceTestCase(std::string description, + std::string input, + std::string expected, + std::string compliance); + + ~StyleComplianceTestCase() = default; + StyleComplianceTestCase(StyleComplianceTestCase&&) = default; + + StyleComplianceTestCase() = delete; + StyleComplianceTestCase(const StyleComplianceTestCase&) = delete; + StyleComplianceTestCase& operator=(const StyleComplianceTestCase&) = delete; + StyleComplianceTestCase& operator=(StyleComplianceTestCase&&) = delete; + + std::map GetDescription() const; + + std::string BuildReportEntry() const; + + const verilog::formatter::FormatStyle& GetStyle() const { + return *ABSL_DIE_IF_NULL(style_); + } + + bool ShouldFail() const { + return should_fail_; + } + + bool Format(); + + bool AsExpected() const { + return formatted_output_ == expected_; + } + + private: + std::string filename_; + + std::string description_; + + std::string input_; + std::string expected_; + std::string compliance_; + + std::unique_ptr style_; + bool should_fail_ = false; + + std::string formatted_output_; +}; + +class StyleComplianceReport { + public: + StyleComplianceReport() = default; + ~StyleComplianceReport() = default; + + StyleComplianceReport(const StyleComplianceReport&) = delete; + StyleComplianceReport(const StyleComplianceReport&&) = delete; + StyleComplianceReport& operator=(const StyleComplianceReport&) = delete; + + void SetProjectName(absl::string_view project_name) { + project_name_ = std::string{project_name}; + } + + void SetCopyrights(absl::string_view copyrights) { + copyrights_ = std::string{copyrights}; + } + + void SetAuthors(absl::string_view authors) { + authors_ = std::string{authors}; + } + + absl::string_view GetProjectName() const { + return project_name_; + } + + absl::string_view GetCopyrights() const { + return copyrights_; + } + + absl::string_view GetAuthors() const { + return authors_; + } + + // Generates Sphinx configuration, e.g. + // import sphinx_rtd_theme + // + // project = '$project_name_' + // copyright = $copyrights_' + // author = '$authors_' + // + // exclude_patterns = [] + // + // extensions = [ "sphinx_rtd_theme", ] + // + // html_theme = 'sphinx_rtd_theme' + std::string BuildConfiguration() const; + + std::string BuildHeader() const; + + // FIXME(ldk): Make this const + // std::string? + StyleComplianceTestCase BuildTestCase( + absl::string_view contents, absl::string_view filename); + + StyleComplianceTestCase BuildTestCase( + absl::string_view description, + absl::string_view input, + absl::string_view expected, + absl::string_view compliance); + + private: + // Report name + std::string project_name_ = "LowRISC style compliance report"; + + // Report copyrights + std::string copyrights_ = "2017-2021, The Verible Authors"; + + // Report authors + std::string authors_ = "The Verible Authors"; +}; + +} // namespace verible + +#endif // VERIBLE_COMMON_UTIL_STYLE_COMPLIANCE_REPORT_H_ diff --git a/verilog/formatting/style_compliance_report_test.cc b/verilog/formatting/style_compliance_report_test.cc new file mode 100644 index 000000000..06bb17ec3 --- /dev/null +++ b/verilog/formatting/style_compliance_report_test.cc @@ -0,0 +1,518 @@ +// Copyright 2017-2021 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. +// LowRISC format style. + +#include "common/util/logging.h" +#include "verilog/formatting/style_compliance_report.h" +#include "verilog/formatting/lowrisc_format_style.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace verible { +namespace { + +TEST(StyleComplianceReportTest, SphinxConfiguration) { + StyleComplianceReport report; + report.SetProjectName("project_name"); + report.SetCopyrights("copyrights"); + report.SetAuthors("authors"); + + const auto& conf_str = report.BuildConfiguration(); + + absl::string_view expected_conf = + "import sphinx_rtd_theme\n" + "\n" + "project = 'project_name'\n" + "copyright = 'copyrights'\n" + "author = 'authors'\n" + "\n" + "exclude_patterns = []\n" + "\n" + "extensions = [ \"sphinx_rtd_theme\", ]\n" + "\n" + "html_theme = 'sphinx_rtd_theme'\n"; + + EXPECT_EQ(conf_str, expected_conf); +} + +TEST(StyleComplianceReportTest, DefaultSphinxConfiguration) { + StyleComplianceReport report; + + const auto& conf_str = report.BuildConfiguration(); + + absl::string_view expected_conf = + "import sphinx_rtd_theme\n" + "\n" + "project = 'LowRISC style compliance report'\n" + "copyright = '2017-2021, The Verible Authors'\n" + "author = 'The Verible Authors'\n" + "\n" + "exclude_patterns = []\n" + "\n" + "extensions = [ \"sphinx_rtd_theme\", ]\n" + "\n" + "html_theme = 'sphinx_rtd_theme'\n"; + + EXPECT_EQ(conf_str, expected_conf); +} + +TEST(StyleComplianceReportTest, SphinxConfigurationChangeAuthors) { + StyleComplianceReport report; + report.SetProjectName("project_name"); + report.SetCopyrights("copyrights"); + report.SetAuthors("authors"); + + absl::string_view expected_conf = + "import sphinx_rtd_theme\n" + "\n" + "project = 'project_name'\n" + "copyright = 'copyrights'\n" + "author = 'authors'\n" + "\n" + "exclude_patterns = []\n" + "\n" + "extensions = [ \"sphinx_rtd_theme\", ]\n" + "\n" + "html_theme = 'sphinx_rtd_theme'\n"; + + EXPECT_EQ(report.BuildConfiguration(), expected_conf); + + report.SetAuthors("different_authors"); + absl::string_view expected_conf_with_other_authors = + "import sphinx_rtd_theme\n" + "\n" + "project = 'project_name'\n" + "copyright = 'copyrights'\n" + "author = 'different_authors'\n" + "\n" + "exclude_patterns = []\n" + "\n" + "extensions = [ \"sphinx_rtd_theme\", ]\n" + "\n" + "html_theme = 'sphinx_rtd_theme'\n"; + + EXPECT_EQ(report.BuildConfiguration(), + expected_conf_with_other_authors); +} + +TEST(StyleComplianceReportTest, SphinxConfigurationChangeCopyrights) { + StyleComplianceReport report; + report.SetProjectName("project_name"); + report.SetCopyrights("copyrights"); + report.SetAuthors("authors"); + + absl::string_view expected_conf = + "import sphinx_rtd_theme\n" + "\n" + "project = 'project_name'\n" + "copyright = 'copyrights'\n" + "author = 'authors'\n" + "\n" + "exclude_patterns = []\n" + "\n" + "extensions = [ \"sphinx_rtd_theme\", ]\n" + "\n" + "html_theme = 'sphinx_rtd_theme'\n"; + + EXPECT_EQ(report.BuildConfiguration(), expected_conf); + + report.SetCopyrights("different_copyrights"); + absl::string_view expected_conf_with_changed_copyrights = + "import sphinx_rtd_theme\n" + "\n" + "project = 'project_name'\n" + "copyright = 'different_copyrights'\n" + "author = 'authors'\n" + "\n" + "exclude_patterns = []\n" + "\n" + "extensions = [ \"sphinx_rtd_theme\", ]\n" + "\n" + "html_theme = 'sphinx_rtd_theme'\n"; + + EXPECT_EQ(report.BuildConfiguration(), + expected_conf_with_changed_copyrights); +} + +TEST(StyleComplianceReportTest, SphinxConfigurationChangeProjectName) { + StyleComplianceReport report; + report.SetProjectName("project_name"); + report.SetCopyrights("copyrights"); + report.SetAuthors("authors"); + + absl::string_view expected_conf = + "import sphinx_rtd_theme\n" + "\n" + "project = 'project_name'\n" + "copyright = 'copyrights'\n" + "author = 'authors'\n" + "\n" + "exclude_patterns = []\n" + "\n" + "extensions = [ \"sphinx_rtd_theme\", ]\n" + "\n" + "html_theme = 'sphinx_rtd_theme'\n"; + + EXPECT_EQ(report.BuildConfiguration(), expected_conf); + + report.SetProjectName("different_project_name"); + absl::string_view expected_conf_with_changed_project_name = + "import sphinx_rtd_theme\n" + "\n" + "project = 'different_project_name'\n" + "copyright = 'copyrights'\n" + "author = 'authors'\n" + "\n" + "exclude_patterns = []\n" + "\n" + "extensions = [ \"sphinx_rtd_theme\", ]\n" + "\n" + "html_theme = 'sphinx_rtd_theme'\n"; + + EXPECT_EQ(report.BuildConfiguration(), + expected_conf_with_changed_project_name); +} + +TEST(StyleComplianceReportTest, TestHeader) { + StyleComplianceReport report; + + //auto itr = report.begin(); + //EXPECT_EQ(itr, report.end()); + + absl::string_view expected_header = + ".. |hr| raw:: html\n" + "\n" + "
\n" + "\n" + "LowRISC style compliance report\n" + "===============================\n" + ".. toctree::\n" + " :maxdepth: 1\n" + " :caption: Contents:\n" + "\n"; + + // Just header + //EXPECT_EQ(*itr, expected_header); + + EXPECT_EQ(report.BuildHeader(), expected_header); +} + +//TEST(StyleComplianceReportTest, EmptyTestSuite) { +// StyleComplianceReport report; +// +// auto itr = report.begin(); +// EXPECT_EQ(itr, report.end()); +// +// absl::string_view expected_header = +// ".. |hr| raw:: html\n" +// "\n" +// "
\n" +// "\n" +// "LowRISC style compliance report\n" +// "===============================\n" +// ".. toctree::\n" +// " :maxdepth: 1\n" +// " :caption: Contents:\n" +// "\n"; +// +// // Just header +// EXPECT_EQ(*itr, expected_header); +//} + +// FIXME(ldk): Move to common/util/array_size.h +template +constexpr std::size_t ArraySize(T (&)[N]) noexcept { + return N; +} + +TEST(StyleComplianceReportTest, EmptyDescription) { + StyleComplianceTestCase compliance_test_case("", "", ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 0); +} + +TEST(StyleComplianceReportTest, DescriptionWithTitleOnly) { + StyleComplianceTestCase compliance_test_case("", "Test title", ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 1); + + const auto itr = desc_map.find("title"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Test title"); +} + +TEST(StyleComplianceReportTest, DescriptionWithTitleAndTagsSameLine) { + StyleComplianceTestCase compliance_test_case( + "", + "Test Title\n" + "\n" + "tags: test_tag1 test_tag2\n", + ""); + + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 2); + + auto itr = desc_map.find("title"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Test Title"); + + itr = desc_map.find("tags"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "test_tag1 test_tag2"); +} + +TEST(StyleComplianceReportTest, DescriptionWithTitleAndTagsNextLine) { + StyleComplianceTestCase compliance_test_case( + "", + "Test Title\n" + "\n" + "tags:\n" + "test_tag1 test_tag2\n", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 2); + + auto itr = desc_map.find("title"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Test Title"); + + itr = desc_map.find("tags"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "test_tag1 test_tag2"); +} + +TEST(StyleComplianceReportTest, DescriptionWithTitleAndMultiLineTag) { + StyleComplianceTestCase compliance_test_case( + "", + "Test Title\n" + "\n" + "tags: test_tag1\n" + " test_tag2\n" + " test_tag3\n", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 2); + + auto itr = desc_map.find("title"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Test Title"); + + itr = desc_map.find("tags"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "test_tag1\n" + "test_tag2\n" + "test_tag3"); +} + +TEST(StyleComplianceReportTest, DescriptionWithTitleAndMissingTag) { + StyleComplianceTestCase compliance_test_case( + "", + "Test Title\n" + "\n" + "tags: test_tag\n" + "\n" + "ignored_tag\n", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 2); + + auto itr = desc_map.find("title"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Test Title"); + + itr = desc_map.find("info"); + EXPECT_EQ(itr, desc_map.end()); + + itr = desc_map.find("unknown"); + EXPECT_EQ(itr, desc_map.end()); + + itr = desc_map.find("tags"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "test_tag"); +} + +TEST(StyleComplianceReportTest, DescriptionWithTitleAndInfo) { + StyleComplianceTestCase compliance_test_case( + "", + "Test Title\n" + "\n" + "Info text\n" + "\n" + "tags: test_tag\n", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 3); + + auto itr = desc_map.find("title"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Test Title"); + + itr = desc_map.find("info"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Info text"); + + itr = desc_map.find("unknown"); + EXPECT_EQ(itr, desc_map.end()); + + itr = desc_map.find("tags"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "test_tag"); +} + +TEST(StyleComplianceReportTest, DescriptionWithTitleAndInfoAndIgnored) { + StyleComplianceTestCase compliance_test_case( + "", + "Test Title\n" + "\n" + "Info text\n" + "\n" + "tags: test_tag\n" + "\n" + "ignored text\n", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 3); + + auto itr = desc_map.find("title"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Test Title"); + + itr = desc_map.find("info"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "Info text"); + + EXPECT_EQ(desc_map.find("unknown"), desc_map.end()); + + itr = desc_map.find("tags"); + EXPECT_NE(itr, desc_map.end()); + EXPECT_EQ(itr->second, "test_tag"); +} + +TEST(StyleComplianceReportTest, TestDefaultFormatStyleFromDescription) { + StyleComplianceTestCase compliance_test_case("", "Title\n", ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 1); // just test title + + const auto& style = compliance_test_case.GetStyle(); + EXPECT_EQ(style.StyleName(), "default"); +} + +TEST(StyleComplianceReportTest, TestFormatStyleFromDescription) { + StyleComplianceTestCase compliance_test_case( + "", + "Title\n" + "\n" + "style: lowrisc\n", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 2); // title + style + + const auto& style = compliance_test_case.GetStyle(); + EXPECT_EQ(style.StyleName(), "lowrisc"); + + // default column limit + EXPECT_EQ(style.column_limit, + verilog::formatter::LowRISCFormatStyle().column_limit); +} + +TEST(StyleComplianceReportTest, TestColumnLimitOverride) { + StyleComplianceTestCase compliance_test_case( + "", + "Title\n" + "\n" + "style: lowrisc column_limit=40\n", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 2); // title + style + + const auto& style = compliance_test_case.GetStyle(); + EXPECT_EQ(style.StyleName(), "lowrisc"); + EXPECT_EQ(style.column_limit, 40); +} + +TEST(StyleComplianceReportTest, TestShouldFailTag) { + StyleComplianceTestCase compliance_test_case( + "", + "test_title\n" + "\n" + "should_fail: true", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 2); + EXPECT_TRUE(compliance_test_case.ShouldFail()); +} + +TEST(StyleComplianceReportTest, TestShouldFailTagDefaultValue) { + StyleComplianceTestCase compliance_test_case( + "", + "test_title\n", + ""); + + const auto desc_map = compliance_test_case.GetDescription(); + EXPECT_EQ(desc_map.size(), 1); + EXPECT_FALSE(compliance_test_case.ShouldFail()); +} + +TEST(StyleComplianceReportTest, TestSimpleFormatting) { + StyleComplianceTestCase test_case( + "", + "test_title", + "module m;\n" + "endmodule\n"); + + EXPECT_TRUE(test_case.Format()); + EXPECT_FALSE(test_case.ShouldFail()); + EXPECT_TRUE(test_case.AsExpected()); +} + +TEST(StyleComplianceReportTest, TestSimpleFailingFormatting) { + StyleComplianceTestCase test_case( + "", + "test_title", + "module m;endmodule\n"); + + EXPECT_TRUE(test_case.Format()); + EXPECT_FALSE(test_case.ShouldFail()); + EXPECT_FALSE(test_case.AsExpected()); +} + +// delete this test case? +//TEST(StyleComplianceReportTest, TestIgnoredFailingFormatting) { +// StyleComplianceTestCase test_case( +// "", +// "test_title\n" +// "\n" +// "should_fail: true\n", +// "module m;endmodule\n"); +// +// EXPECT_TRUE(test_case.Format()); +// EXPECT_TRUE(test_case.ShouldFail()); +// EXPECT_FALSE(test_case.AsExpected()); +//} + +} // namespace +} // namespace verible diff --git a/verilog/tools/style_tester/BUILD b/verilog/tools/style_tester/BUILD new file mode 100644 index 000000000..ec19e0bf0 --- /dev/null +++ b/verilog/tools/style_tester/BUILD @@ -0,0 +1,24 @@ +# 'verilog_style_tester' is a program for checking internal style compliance state + +licenses(["notice"]) + +cc_binary( + name = "verible-verilog-style-tester", + srcs = ["verilog_style_tester.cc"], + visibility = ["//visibility:public"], # for verilog_style_lint.bzl + deps = [ + "//common/text:concrete_syntax_tree", + "//common/util:enum_flags", + "//common/util:file_util", + "//common/util:init_command_line", + "//common/util:logging", + "//verilog/formatting:style_compliance_report", + "//verilog/formatting:formatter", + "//verilog/formatting:lowrisc_format_style", + "//verilog/formatting:formatter_lowrisc_style_test_cases", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) diff --git a/verilog/tools/style_tester/constraints.sv b/verilog/tools/style_tester/constraints.sv new file mode 100644 index 000000000..359fc523a --- /dev/null +++ b/verilog/tools/style_tester/constraints.sv @@ -0,0 +1,5 @@ +// Expand blocks with two or more expressions +// +// gh_issue: https://github.com/google/verible/issues/445 + +constraint param_c {a_param == 0; d_param == 0;} diff --git a/verilog/tools/style_tester/verilog_style_tester.cc b/verilog/tools/style_tester/verilog_style_tester.cc new file mode 100644 index 000000000..a73c27f83 --- /dev/null +++ b/verilog/tools/style_tester/verilog_style_tester.cc @@ -0,0 +1,107 @@ +// Copyright 2017-2021 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 +#include +#include +#include +#include // IWYU pragma: keep // for ostringstream +#include // for string, allocator, etc +#include + +#include "absl/flags/flag.h" +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "common/formatting/basic_format_style.h" +#include "common/text/concrete_syntax_tree.h" +#include "common/util/enum_flags.h" +#include "common/util/file_util.h" +#include "common/util/init_command_line.h" +#include "common/util/logging.h" // for operator<<, LOG, LogMessage, etc +#include "verilog/formatting/lowrisc_format_style.h" +#include "verilog/formatting/formatter.h" +#include "verilog/formatting/style_compliance_report.h" +#include "verilog/formatting/formatter_lowrisc_style_test_cases.h" + + +ABSL_FLAG(bool, dump_header, false, "Print report header"); + + +ABSL_FLAG(bool, dump_configuration, false, "Print Sphinx configuration"); + + +ABSL_FLAG(bool, dump_internal, false, "Dump internal test suite"); + + +int main(int argc, char** argv) { + const auto usage = + absl::StrCat("usage: ", argv[0], " [options] [...]"); + const auto args = verible::InitCommandLine(usage, &argc, &argv); + verible::StyleComplianceReport report; + + if (absl::GetFlag(FLAGS_dump_configuration)) { + std::cout << report.BuildConfiguration(); + return 0; + } + + if (absl::GetFlag(FLAGS_dump_header)) { + std::cout << report.BuildHeader(); + } + + if (absl::GetFlag(FLAGS_dump_internal)) { + std::pair kComplianceTestCases = + verilog::formatter::tests::GetLowRISCComplianceTestCases(); + + const auto* test_cases = ABSL_DIE_IF_NULL(std::get<0>(kComplianceTestCases)); + size_t n = std::get<1>(kComplianceTestCases); + + for (size_t i = 0 ; i < n ; ++i) { + const auto& test_case = test_cases[i]; + + std::cout << report.BuildTestCase(test_case.description, + test_case.input, + test_case.expected, + test_case.compliant).BuildReportEntry(); + } + + if (!verible::empty_range(args.begin() + 1, args.end())) { + std::cout << report.BuildTestCase("External test suite", "", "", "").BuildReportEntry(); + } + } + + int exit_status = 0; + // All positional arguments are file names. Exclude program name. + for (const auto filename : + verible::make_range(args.begin() + 1, args.end())) { + std::string content; + if (!verible::file::GetContents(filename, &content).ok()) { + exit_status = 1; + continue; + } + + auto test_case = report.BuildTestCase(content, filename); + bool succeeded = test_case.Format(); + + if (!test_case.ShouldFail() && (!succeeded || !test_case.AsExpected())) { + // Break? + exit_status = 1; + } + + std::cout << test_case.BuildReportEntry(); + } + + return exit_status; +}