sugarcane 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/HISTORY.md +126 -0
- data/LICENSE +14 -0
- data/README.md +235 -0
- data/bin/sugarcane +7 -0
- data/lib/cane.rb +4 -0
- data/lib/sugarcane/abc_check.rb +216 -0
- data/lib/sugarcane/cli/options.rb +30 -0
- data/lib/sugarcane/cli/parser.rb +191 -0
- data/lib/sugarcane/cli.rb +21 -0
- data/lib/sugarcane/default_checks.rb +17 -0
- data/lib/sugarcane/doc_check.rb +156 -0
- data/lib/sugarcane/encoding_aware_iterator.rb +30 -0
- data/lib/sugarcane/ext/array.rb +24 -0
- data/lib/sugarcane/file.rb +30 -0
- data/lib/sugarcane/json_formatter.rb +17 -0
- data/lib/sugarcane/menu.rb +228 -0
- data/lib/sugarcane/rake_task.rb +78 -0
- data/lib/sugarcane/runner.rb +59 -0
- data/lib/sugarcane/style_check.rb +91 -0
- data/lib/sugarcane/task_runner.rb +20 -0
- data/lib/sugarcane/threshold_check.rb +83 -0
- data/lib/sugarcane/version.rb +3 -0
- data/lib/sugarcane/violation_formatter.rb +75 -0
- data/spec/abc_check_spec.rb +164 -0
- data/spec/cane_spec.rb +136 -0
- data/spec/cli_spec.rb +23 -0
- data/spec/doc_check_spec.rb +163 -0
- data/spec/encoding_aware_iterator_spec.rb +32 -0
- data/spec/file_spec.rb +25 -0
- data/spec/json_formatter_spec.rb +10 -0
- data/spec/parser_spec.rb +161 -0
- data/spec/rake_task_spec.rb +68 -0
- data/spec/runner_spec.rb +23 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/style_check_spec.rb +57 -0
- data/spec/threshold_check_spec.rb +101 -0
- data/spec/violation_formatter_spec.rb +38 -0
- data/sugarcane.gemspec +41 -0
- metadata +196 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'sugarcane/abc_check'
|
4
|
+
|
5
|
+
describe SugarCane::AbcCheck do
|
6
|
+
def check(file_name, opts = {})
|
7
|
+
described_class.new(opts.merge(abc_glob: file_name))
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'does not create violations when no_abc flag is set' do
|
11
|
+
file_name = make_file(<<-RUBY)
|
12
|
+
class Harness
|
13
|
+
def complex_method(a)
|
14
|
+
b = a
|
15
|
+
return b if b > 3
|
16
|
+
end
|
17
|
+
end
|
18
|
+
RUBY
|
19
|
+
|
20
|
+
violations = check(file_name, abc_max: 1, no_abc: true).violations
|
21
|
+
violations.should be_empty
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'creates an AbcMaxViolation for each method above the threshold' do
|
25
|
+
file_name = make_file(<<-RUBY)
|
26
|
+
class Harness
|
27
|
+
def not_complex
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def complex_method(a)
|
32
|
+
b = a
|
33
|
+
return b if b > 3
|
34
|
+
end
|
35
|
+
end
|
36
|
+
RUBY
|
37
|
+
|
38
|
+
violations = check(file_name, abc_max: 1, no_abc: false).violations
|
39
|
+
violations.length.should == 1
|
40
|
+
violations[0].values_at(:file, :label, :value).should ==
|
41
|
+
[file_name, "Harness#complex_method", 2]
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'sorts violations by complexity' do
|
45
|
+
file_name = make_file(<<-RUBY)
|
46
|
+
class Harness
|
47
|
+
def not_complex
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def complex_method(a)
|
52
|
+
b = a
|
53
|
+
return b if b > 3
|
54
|
+
end
|
55
|
+
end
|
56
|
+
RUBY
|
57
|
+
|
58
|
+
violations = check(file_name, abc_max: 0).violations
|
59
|
+
violations.length.should == 2
|
60
|
+
complexities = violations.map {|x| x[:value] }
|
61
|
+
complexities.should == complexities.sort.reverse
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'creates a violation when code cannot be parsed' do
|
65
|
+
file_name = make_file(<<-RUBY)
|
66
|
+
class Harness
|
67
|
+
RUBY
|
68
|
+
|
69
|
+
violations = check(file_name).violations
|
70
|
+
violations.length.should == 1
|
71
|
+
violations[0][:file].should == file_name
|
72
|
+
violations[0][:description].should be_instance_of(String)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'skips declared exclusions' do
|
76
|
+
file_name = make_file(<<-RUBY)
|
77
|
+
class Harness
|
78
|
+
def instance_meth
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.class_meth
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
module Nested
|
87
|
+
def i_meth
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.c_meth
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def other_meth
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
RUBY
|
101
|
+
|
102
|
+
exclusions = %w[ Harness#instance_meth Harness.class_meth
|
103
|
+
Harness::Nested#i_meth Harness::Nested.c_meth ]
|
104
|
+
violations = check(file_name,
|
105
|
+
abc_max: 0,
|
106
|
+
abc_exclude: exclusions
|
107
|
+
).violations
|
108
|
+
violations.length.should == 1
|
109
|
+
violations[0].values_at(:file, :label, :value).should ==
|
110
|
+
[file_name, "Harness::Nested#other_meth", 1]
|
111
|
+
end
|
112
|
+
|
113
|
+
it "creates an AbcMaxViolation for method in assigned anonymous class" do
|
114
|
+
file_name = make_file(<<-RUBY)
|
115
|
+
MyClass = Struct.new(:foo) do
|
116
|
+
def test_method(a)
|
117
|
+
b = a
|
118
|
+
return b if b > 3
|
119
|
+
end
|
120
|
+
end
|
121
|
+
RUBY
|
122
|
+
|
123
|
+
violations = check(file_name, abc_max: 1).violations
|
124
|
+
violations[0][:label] == "MyClass#test_method"
|
125
|
+
end
|
126
|
+
|
127
|
+
it "creates an AbcMaxViolation for method in anonymous class" do
|
128
|
+
file_name = make_file(<<-RUBY)
|
129
|
+
Class.new do
|
130
|
+
def test_method(a)
|
131
|
+
b = a
|
132
|
+
return b if b > 3
|
133
|
+
end
|
134
|
+
end
|
135
|
+
RUBY
|
136
|
+
|
137
|
+
violations = check(file_name, abc_max: 1).violations
|
138
|
+
violations[0][:label].should == "(anon)#test_method"
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.it_should_extract_method_name(name, label=name, sep='#')
|
142
|
+
it "creates an AbcMaxViolation for #{name}" do
|
143
|
+
file_name = make_file(<<-RUBY)
|
144
|
+
class Harness
|
145
|
+
def #{name}(a)
|
146
|
+
b = a
|
147
|
+
return b if b > 3
|
148
|
+
end
|
149
|
+
end
|
150
|
+
RUBY
|
151
|
+
|
152
|
+
violations = check(file_name, abc_max: 1).violations
|
153
|
+
violations[0][:label].should == "Harness#{sep}#{label}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# These method names all create different ASTs. Which is weird.
|
158
|
+
it_should_extract_method_name 'a'
|
159
|
+
it_should_extract_method_name 'self.a', 'a', '.'
|
160
|
+
it_should_extract_method_name 'next'
|
161
|
+
it_should_extract_method_name 'GET'
|
162
|
+
it_should_extract_method_name '`'
|
163
|
+
it_should_extract_method_name '>='
|
164
|
+
end
|
data/spec/cane_spec.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "stringio"
|
3
|
+
require 'sugarcane/cli'
|
4
|
+
require 'sugarcane/menu'
|
5
|
+
|
6
|
+
require 'sugarcane/rake_task'
|
7
|
+
require 'sugarcane/task_runner'
|
8
|
+
|
9
|
+
# Acceptance tests
|
10
|
+
describe 'The sugarcane application' do
|
11
|
+
let(:class_name) { "C#{rand(10 ** 10)}" }
|
12
|
+
|
13
|
+
let(:fn) do
|
14
|
+
make_file(<<-RUBY + " ")
|
15
|
+
class Harness
|
16
|
+
def complex_method(a)
|
17
|
+
if a < 2
|
18
|
+
return "low"
|
19
|
+
else
|
20
|
+
return "high"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:check_file) do
|
28
|
+
make_file <<-RUBY
|
29
|
+
class #{class_name} < Struct.new(:opts)
|
30
|
+
def self.options
|
31
|
+
{
|
32
|
+
unhappy_file: ["File to check", default: [nil]]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def violations
|
37
|
+
[
|
38
|
+
description: "Files are unhappy",
|
39
|
+
file: opts.fetch(:unhappy_file),
|
40
|
+
label: ":("
|
41
|
+
]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
RUBY
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns a non-zero exit code and a details of checks that failed' do
|
48
|
+
output, exitstatus = run %(
|
49
|
+
--report
|
50
|
+
--style-glob #{fn}
|
51
|
+
--doc-glob #{fn}
|
52
|
+
--abc-glob #{fn}
|
53
|
+
--abc-max 1
|
54
|
+
-r #{check_file}
|
55
|
+
--check #{class_name}
|
56
|
+
--unhappy-file #{fn}
|
57
|
+
)
|
58
|
+
output.should include("Lines violated style requirements")
|
59
|
+
output.should include("Methods exceeded maximum allowed ABC complexity")
|
60
|
+
output.should include(
|
61
|
+
"Class and Module definitions require explanatory comments"
|
62
|
+
)
|
63
|
+
exitstatus.should == 1
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should not show methods within the expected ABC complexity' do
|
67
|
+
output, exitstatus = run %(
|
68
|
+
--report
|
69
|
+
--style-glob #{fn}
|
70
|
+
--doc-glob #{fn}
|
71
|
+
--abc-glob #{fn}
|
72
|
+
--abc-max 10
|
73
|
+
-r #{check_file}
|
74
|
+
--check #{class_name}
|
75
|
+
--unhappy-file #{fn}
|
76
|
+
)
|
77
|
+
output.should include("Lines violated style requirements")
|
78
|
+
output.should_not include("Methods exceeded maximum allowed ABC complexity")
|
79
|
+
output.should include(
|
80
|
+
"Class and Module definitions require explanatory comments"
|
81
|
+
)
|
82
|
+
exitstatus.should == 1
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'creates a menu' do
|
86
|
+
pending "Make a unit test or find a way to create artificial key presses"
|
87
|
+
output, exitstatus = run %(
|
88
|
+
--style-glob #{fn}
|
89
|
+
--doc-glob #{fn}
|
90
|
+
--abc-glob #{fn}
|
91
|
+
--abc-max 1
|
92
|
+
-r #{check_file}
|
93
|
+
--check #{class_name}
|
94
|
+
--unhappy-file #{fn}
|
95
|
+
)
|
96
|
+
|
97
|
+
exitstatus.should == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'handles invalid unicode input' do
|
101
|
+
fn = make_file("\xc3\x28")
|
102
|
+
|
103
|
+
_, exitstatus = run("--style-glob #{fn} --abc-glob #{fn} --doc-glob #{fn}")
|
104
|
+
|
105
|
+
exitstatus.should == 0
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'can run tasks in parallel' do
|
109
|
+
# This spec isn't great, but there is no good way to actually observe that
|
110
|
+
# tasks run in parallel and we want to verify the conditional is correct.
|
111
|
+
SugarCane.task_runner(parallel: true).should == Parallel
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'colorizes output' do
|
115
|
+
output, exitstatus = run("--report --color --abc-max 0")
|
116
|
+
|
117
|
+
output.should include("\e[31m")
|
118
|
+
end
|
119
|
+
|
120
|
+
after do
|
121
|
+
if Object.const_defined?(class_name)
|
122
|
+
Object.send(:remove_const, class_name)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def run(cli_args)
|
127
|
+
result = nil
|
128
|
+
output = capture_stdout do
|
129
|
+
result = SugarCane::CLI.run(
|
130
|
+
%w(--no-abc --no-style --no-doc) + cli_args.split(/\s+/m)
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
[output, result ? 0 : 1]
|
135
|
+
end
|
136
|
+
end
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sugarcane/cli'
|
3
|
+
|
4
|
+
describe SugarCane::CLI do
|
5
|
+
describe '.run' do
|
6
|
+
|
7
|
+
let!(:parser) { class_double("SugarCane::CLI::Parser").as_stubbed_const }
|
8
|
+
let!(:cane) { class_double("SugarCane").as_stubbed_const }
|
9
|
+
|
10
|
+
it 'runs SugarCane with the given arguments' do
|
11
|
+
parser.should_receive(:parse).with("--args").and_return(args: true)
|
12
|
+
cane.should_receive(:run).with(args: true).and_return("tracer")
|
13
|
+
|
14
|
+
described_class.run("--args").should == "tracer"
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not run SugarCane if parser was able to handle input' do
|
18
|
+
parser.should_receive(:parse).with("--args").and_return("tracer")
|
19
|
+
|
20
|
+
described_class.run("--args").should == "tracer"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sugarcane/doc_check'
|
3
|
+
|
4
|
+
describe SugarCane::DocCheck do
|
5
|
+
def check(file_name, opts = {})
|
6
|
+
described_class.new(opts.merge(doc_glob: file_name))
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'creates a DocViolation for each undocumented class with a method' do
|
10
|
+
file_name = make_file <<-RUBY
|
11
|
+
class Doc; end
|
12
|
+
class Empty; end # No doc is fine
|
13
|
+
class NoDoc; def with_method; end; end
|
14
|
+
classIgnore = nil
|
15
|
+
[:class]
|
16
|
+
# class Ignore
|
17
|
+
class Meta
|
18
|
+
class << self; end
|
19
|
+
end
|
20
|
+
module DontNeedDoc; end
|
21
|
+
# This module is documented
|
22
|
+
module HasDoc
|
23
|
+
def mixin; end
|
24
|
+
end
|
25
|
+
module AlsoNeedsDoc; def mixin; end; end
|
26
|
+
module NoDocIsFine
|
27
|
+
module ButThisNeedsDoc
|
28
|
+
def self.global
|
29
|
+
end
|
30
|
+
end
|
31
|
+
module AlsoNoDocIsFine; end
|
32
|
+
# We've got docs
|
33
|
+
module CauseWeNeedThem
|
34
|
+
def mixin
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
|
40
|
+
violations = check(file_name).violations
|
41
|
+
violations.length.should == 3
|
42
|
+
|
43
|
+
violations[0].values_at(:file, :line, :label).should == [
|
44
|
+
file_name, 3, "NoDoc"
|
45
|
+
]
|
46
|
+
|
47
|
+
violations[1].values_at(:file, :line, :label).should == [
|
48
|
+
file_name, 15, "AlsoNeedsDoc"
|
49
|
+
]
|
50
|
+
|
51
|
+
violations[2].values_at(:file, :line, :label).should == [
|
52
|
+
file_name, 17, "ButThisNeedsDoc"
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'does not create violations for single line classes without methods' do
|
57
|
+
file_name = make_file <<-RUBY
|
58
|
+
class NeedsDoc
|
59
|
+
class AlsoNeedsDoc < StandardError; def foo; end; end
|
60
|
+
class NoDocIsOk < StandardError; end
|
61
|
+
class NoDocIsAlsoOk < StandardError; end # No doc is fine on this too
|
62
|
+
|
63
|
+
def my_method
|
64
|
+
end
|
65
|
+
end
|
66
|
+
RUBY
|
67
|
+
|
68
|
+
violations = check(file_name).violations
|
69
|
+
violations.length.should == 2
|
70
|
+
|
71
|
+
violations[0].values_at(:file, :line, :label).should == [
|
72
|
+
file_name, 1, "NeedsDoc"
|
73
|
+
]
|
74
|
+
|
75
|
+
violations[1].values_at(:file, :line, :label).should == [
|
76
|
+
file_name, 2, "AlsoNeedsDoc"
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'ignores magic encoding comments' do
|
81
|
+
file_name = make_file <<-RUBY
|
82
|
+
# coding = utf-8
|
83
|
+
class NoDoc; def do_stuff; end; end
|
84
|
+
# -*- encoding : utf-8 -*-
|
85
|
+
class AlsoNoDoc; def do_more_stuff; end; end
|
86
|
+
# Parse a Transfer-Encoding: Chunked response
|
87
|
+
class Doc; end
|
88
|
+
RUBY
|
89
|
+
|
90
|
+
violations = check(file_name).violations
|
91
|
+
violations.length.should == 2
|
92
|
+
|
93
|
+
violations[0].values_at(:file, :line, :label).should == [
|
94
|
+
file_name, 2, "NoDoc"
|
95
|
+
]
|
96
|
+
violations[1].values_at(:file, :line, :label).should == [
|
97
|
+
file_name, 4, "AlsoNoDoc"
|
98
|
+
]
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'creates a violation for missing README' do
|
102
|
+
file = class_double("SugarCane::File").as_stubbed_const
|
103
|
+
stub_const("SugarCane::File", file)
|
104
|
+
file.should_receive(:case_insensitive_glob).with("README*").and_return([])
|
105
|
+
|
106
|
+
violations = check("").violations
|
107
|
+
violations.length.should == 1
|
108
|
+
|
109
|
+
violations[0].values_at(:description, :label).should == [
|
110
|
+
"Missing documentation", "No README found"
|
111
|
+
]
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'does not create a violation when readme exists' do
|
115
|
+
file = class_double("SugarCane::File").as_stubbed_const
|
116
|
+
stub_const("SugarCane::File", file)
|
117
|
+
file
|
118
|
+
.should_receive(:case_insensitive_glob)
|
119
|
+
.with("README*")
|
120
|
+
.and_return(%w(readme.md))
|
121
|
+
|
122
|
+
violations = check("").violations
|
123
|
+
violations.length.should == 0
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'skips declared exclusions' do
|
127
|
+
file_name = make_file <<-FILE.gsub /^\s{6}/, ''
|
128
|
+
class NeedsDocumentation
|
129
|
+
end
|
130
|
+
FILE
|
131
|
+
|
132
|
+
violations = check(file_name,
|
133
|
+
doc_exclude: [file_name]
|
134
|
+
).violations
|
135
|
+
|
136
|
+
violations.length.should == 0
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'skips declared glob-based exclusions' do
|
140
|
+
file_name = make_file <<-FILE.gsub /^\s{6}/, ''
|
141
|
+
class NeedsDocumentation
|
142
|
+
end
|
143
|
+
FILE
|
144
|
+
|
145
|
+
violations = check(file_name,
|
146
|
+
doc_exclude: ["#{File.dirname(file_name)}/*"]
|
147
|
+
).violations
|
148
|
+
|
149
|
+
violations.length.should == 0
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'skips class inside an array' do
|
153
|
+
file_name = make_file <<-RUBY
|
154
|
+
%w(
|
155
|
+
class
|
156
|
+
method
|
157
|
+
)
|
158
|
+
RUBY
|
159
|
+
|
160
|
+
violations = check(file_name).violations
|
161
|
+
violations.length.should == 0
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'sugarcane/encoding_aware_iterator'
|
4
|
+
|
5
|
+
# Example bad input from:
|
6
|
+
# http://stackoverflow.com/questions/1301402/example-invalid-utf8-string
|
7
|
+
describe SugarCane::EncodingAwareIterator do
|
8
|
+
it 'handles non-UTF8 input' do
|
9
|
+
lines = ["\xc3\x28"]
|
10
|
+
result = described_class.new(lines).map.with_index do |line, number|
|
11
|
+
line.should be_kind_of(String)
|
12
|
+
[line =~ /\s/, number]
|
13
|
+
end
|
14
|
+
result.should == [[nil, 0]]
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not enter an infinite loop on persistently bad input' do
|
18
|
+
->{
|
19
|
+
described_class.new([""]).map.with_index do |line, number|
|
20
|
+
"\xc3\x28" =~ /\s/
|
21
|
+
end
|
22
|
+
}.should raise_error(ArgumentError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'allows each with no block' do
|
26
|
+
called_with_line = nil
|
27
|
+
described_class.new([""]).each.with_index do |line, number|
|
28
|
+
called_with_line = line
|
29
|
+
end
|
30
|
+
called_with_line.should == ""
|
31
|
+
end
|
32
|
+
end
|
data/spec/file_spec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
require 'sugarcane/file'
|
5
|
+
|
6
|
+
describe SugarCane::File do
|
7
|
+
describe '.case_insensitive_glob' do
|
8
|
+
it 'matches all kinds of readmes' do
|
9
|
+
expected = %w(
|
10
|
+
README
|
11
|
+
readme.md
|
12
|
+
ReaDME.TEXTILE
|
13
|
+
)
|
14
|
+
|
15
|
+
Dir.mktmpdir do |dir|
|
16
|
+
Dir.chdir(dir) do
|
17
|
+
expected.each do |x|
|
18
|
+
FileUtils.touch(x)
|
19
|
+
end
|
20
|
+
SugarCane::File.case_insensitive_glob("README*").should =~ expected
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sugarcane/json_formatter'
|
3
|
+
|
4
|
+
describe SugarCane::JsonFormatter do
|
5
|
+
it 'outputs violations as JSON' do
|
6
|
+
violations = [{description: 'Fail', line: 3}]
|
7
|
+
JSON.parse(described_class.new(violations).to_s).should ==
|
8
|
+
[{'description' => 'Fail', 'line' => 3}]
|
9
|
+
end
|
10
|
+
end
|