shard-ameba/spec/ameba/runner_spec.cr

252 lines
8.6 KiB
Crystal
Raw Normal View History

require "../spec_helper"
module Ameba
private def runner(files = [__FILE__], formatter = DummyFormatter.new)
2017-12-01 17:00:11 +00:00
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
2017-11-30 21:50:07 +00:00
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
2017-11-30 21:50:07 +00:00
it "skips rule check if source is excluded" do
path = "source.cr"
source = Source.new "", path
2018-05-28 08:48:07 +00:00
all_rules = ([] of Rule::Base).tap do |rules|
2017-11-30 21:50:07 +00:00
rule = ErrorRule.new
rule.excluded = [path]
rules << rule
end
Runner.new(all_rules, [source], formatter, default_severity).run.success?.should be_true
2017-11-30 21:50:07 +00:00
end
2018-01-25 09:50:11 +00:00
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"
2022-12-19 17:03:11 +00:00
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
2018-01-25 09:50:11 +00:00
context "invalid syntax" do
it "reports a syntax error" do
rules = [Rule::Lint::Syntax.new] of Rule::Base
2018-01-25 09:50:11 +00:00
source = Source.new "def bad_syntax"
Runner.new(rules, [source], formatter, default_severity).run
2018-01-25 09:50:11 +00:00
source.should_not be_valid
source.issues.first.rule.name.should eq Rule::Lint::Syntax.rule_name
2018-01-25 09:50:11 +00:00
end
it "does not run other rules" do
rules = [Rule::Lint::Syntax.new, Rule::Style::ConstantNames.new] of Rule::Base
2022-12-19 17:03:11 +00:00
source = Source.new <<-CRYSTAL
MyBadConstant = 1
2018-01-25 09:50:11 +00:00
2022-12-19 17:03:11 +00:00
when my_bad_syntax
CRYSTAL
2018-01-25 09:50:11 +00:00
Runner.new(rules, [source], formatter, default_severity).run
2018-01-25 09:50:11 +00:00
source.should_not be_valid
2018-06-10 21:15:12 +00:00
source.issues.size.should eq 1
2018-01-25 09:50:11 +00:00
end
end
context "unneeded disables" do
2018-06-10 21:15:12 +00:00
it "reports an issue if such disable exists" do
rules = [Rule::Lint::UnneededDisableDirective.new] of Rule::Base
2022-12-19 17:03:11 +00:00
source = Source.new <<-CRYSTAL
a = 1 # ameba:disable LineLength
2022-12-19 17:03:11 +00:00
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
2022-12-19 17:03:11 +00:00
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
2022-12-19 17:03:11 +00:00
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)
2022-12-19 17:03:11 +00:00
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)
s = Source.new %q(WrongConstant = 5)
Runner.new(rules, [s], formatter, Severity::Error).run.success?.should be_true
Runner.new(rules, [s], formatter, Severity::Warning).run.success?.should be_true
Runner.new(rules, [s], formatter, Severity::Convention).run.success?.should be_false
end
it "returns false if issue is disabled" do
rules = [NamedRule.new] of Rule::Base
2022-12-19 17:03:11 +00:00
source = Source.new <<-CRYSTAL
def foo
bar = 1 # ameba:disable #{NamedRule.name}
end
2022-12-19 17:03:11 +00:00
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"
2022-12-19 17:03:11 +00:00
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]
2022-12-19 17:03:11 +00:00
source = Source.new <<-CRYSTAL, "source.cr"
class A; end
class A_A; end
2022-12-19 17:03:11 +00:00
CRYSTAL
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
2022-12-19 17:03:11 +00:00
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"
2022-12-19 17:03:11 +00:00
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"
2022-12-19 17:03:11 +00:00
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end
end
end
end
end
end