shard-ameba/spec/ameba/runner_spec.cr
2023-11-09 00:16:29 +01:00

252 lines
8.6 KiB
Crystal

require "../spec_helper"
module Ameba
private def runner(files = [__FILE__], formatter = DummyFormatter.new)
config = Config.load
config.formatter = formatter
config.globs = files
config.update_rule ErrorRule.rule_name, enabled: false
config.update_rule PerfRule.rule_name, enabled: false
config.update_rule AtoAA.rule_name, enabled: false
config.update_rule AtoB.rule_name, enabled: false
config.update_rule BtoA.rule_name, enabled: false
config.update_rule BtoC.rule_name, enabled: false
config.update_rule CtoA.rule_name, enabled: false
config.update_rule ClassToModule.rule_name, enabled: false
config.update_rule ModuleToClass.rule_name, enabled: false
Runner.new(config)
end
describe Runner do
formatter = DummyFormatter.new
default_severity = Severity::Convention
describe "#run" do
it "returns self" do
runner.run.should_not be_nil
end
it "calls started callback" do
runner(formatter: formatter).run
formatter.started_sources.should_not be_nil
end
it "calls finished callback" do
runner(formatter: formatter).run
formatter.finished_sources.should_not be_nil
end
it "calls source_started callback" do
runner(formatter: formatter).run
formatter.started_source.should_not be_nil
end
it "calls source_finished callback" do
runner(formatter: formatter).run
formatter.finished_source.should_not be_nil
end
it "skips rule check if source is excluded" do
path = "source.cr"
source = Source.new "", path
all_rules = ([] of Rule::Base).tap do |rules|
rule = ErrorRule.new
rule.excluded = [path]
rules << rule
end
Runner.new(all_rules, [source], formatter, default_severity).run.success?.should be_true
end
it "aborts because of an infinite loop" do
rules = [AtoAA.new] of Rule::Base
source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoAA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
context "exception in rule" do
it "raises an exception raised in fiber while running a rule" do
rule = RaiseRule.new
rule.should_raise = true
rules = [rule] of Rule::Base
source = Source.new "", "source.cr"
expect_raises(Exception, "something went wrong") do
Runner.new(rules, [source], formatter, default_severity).run
end
end
end
context "invalid syntax" do
it "reports a syntax error" do
rules = [Rule::Lint::Syntax.new] of Rule::Base
source = Source.new "def bad_syntax"
Runner.new(rules, [source], formatter, default_severity).run
source.should_not be_valid
source.issues.first.rule.name.should eq Rule::Lint::Syntax.rule_name
end
it "does not run other rules" do
rules = [Rule::Lint::Syntax.new, Rule::Naming::ConstantNames.new] of Rule::Base
source = Source.new <<-CRYSTAL
MyBadConstant = 1
when my_bad_syntax
CRYSTAL
Runner.new(rules, [source], formatter, default_severity).run
source.should_not be_valid
source.issues.size.should eq 1
end
end
context "unneeded disables" do
it "reports an issue if such disable exists" do
rules = [Rule::Lint::UnneededDisableDirective.new] of Rule::Base
source = Source.new <<-CRYSTAL
a = 1 # ameba:disable LineLength
CRYSTAL
Runner.new(rules, [source], formatter, default_severity).run
source.should_not be_valid
source.issues.first.rule.name.should eq Rule::Lint::UnneededDisableDirective.rule_name
end
end
end
describe "#explain" do
io = IO::Memory.new
it "writes nothing if sources are valid" do
io.clear
runner = runner(formatter: formatter).run
runner.explain({file: "source.cr", line: 1, column: 2}, io)
io.to_s.should be_empty
end
it "writes the explanation if sources are not valid and location found" do
io.clear
rules = [ErrorRule.new] of Rule::Base
source = Source.new "a = 1", "source.cr"
runner = Runner.new(rules, [source], formatter, default_severity).run
runner.explain({file: "source.cr", line: 1, column: 1}, io)
io.to_s.should_not be_empty
end
it "writes nothing if sources are not valid and location is not found" do
io.clear
rules = [ErrorRule.new] of Rule::Base
source = Source.new "a = 1", "source.cr"
runner = Runner.new(rules, [source], formatter, default_severity).run
runner.explain({file: "source.cr", line: 1, column: 2}, io)
io.to_s.should be_empty
end
end
describe "#success?" do
it "returns true if runner has not been run" do
runner.success?.should be_true
end
it "returns true if all sources are valid" do
runner.run.success?.should be_true
end
it "returns false if there are invalid sources" do
rules = Rule.rules.map &.new.as(Rule::Base)
source = Source.new "WrongConstant = 5"
Runner.new(rules, [source], formatter, default_severity).run.success?.should be_false
end
it "depends on the level of severity" do
rules = Rule.rules.map &.new.as(Rule::Base)
source = Source.new "WrongConstant = 5\n"
Runner.new(rules, [source], formatter, :error).run.success?.should be_true
Runner.new(rules, [source], formatter, :warning).run.success?.should be_true
Runner.new(rules, [source], formatter, :convention).run.success?.should be_false
end
it "returns false if issue is disabled" do
rules = [NamedRule.new] of Rule::Base
source = Source.new <<-CRYSTAL
def foo
bar = 1 # ameba:disable #{NamedRule.name}
end
CRYSTAL
source.add_issue NamedRule.new, location: {2, 1},
message: "Useless assignment"
Runner
.new(rules, [source], formatter, default_severity)
.run.success?.should be_true
end
end
describe "#run with rules autocorrecting each other" do
context "with two conflicting rules" do
context "if there is an offense in an inspected file" do
it "aborts because of an infinite loop" do
rules = [AtoB.new, BtoA.new]
source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
context "if there are multiple offenses in an inspected file" do
it "aborts because of an infinite loop" do
rules = [AtoB.new, BtoA.new]
source = Source.new <<-CRYSTAL, "source.cr"
class A; end
class A_A; end
CRYSTAL
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
end
context "with two pairs of conflicting rules" do
it "aborts because of an infinite loop" do
rules = [ClassToModule.new, ModuleToClass.new, AtoB.new, BtoA.new]
source = Source.new "class A_A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/ClassToModule, Ameba/AtoB -> Ameba/ModuleToClass, Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
context "with three rule cycle" do
it "aborts because of an infinite loop" do
rules = [AtoB.new, BtoC.new, CtoA.new]
source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoC -> Ameba/CtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
end
end
end