cane 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,10 @@ require 'spec_helper'
3
3
  require 'cane/abc_check'
4
4
 
5
5
  describe Cane::AbcCheck do
6
+ def check(file_name, opts = {})
7
+ described_class.new(opts.merge(abc_glob: file_name))
8
+ end
9
+
6
10
  it 'creates an AbcMaxViolation for each method above the threshold' do
7
11
  file_name = make_file(<<-RUBY)
8
12
  class Harness
@@ -17,10 +21,10 @@ describe Cane::AbcCheck do
17
21
  end
18
22
  RUBY
19
23
 
20
- violations = described_class.new(files: file_name, max: 1).violations
24
+ violations = check(file_name, abc_max: 1).violations
21
25
  violations.length.should == 1
22
- violations[0].should be_instance_of(Cane::AbcMaxViolation)
23
- violations[0].columns.should == [file_name, "Harness > complex_method", 2]
26
+ violations[0].values_at(:file, :label, :value).should ==
27
+ [file_name, "Harness#complex_method", 2]
24
28
  end
25
29
 
26
30
  it 'sorts violations by complexity' do
@@ -37,22 +41,21 @@ describe Cane::AbcCheck do
37
41
  end
38
42
  RUBY
39
43
 
40
- violations = described_class.new(files: file_name, max: 0).violations
44
+ violations = check(file_name, abc_max: 0).violations
41
45
  violations.length.should == 2
42
- complexities = violations.map(&:complexity)
46
+ complexities = violations.map {|x| x[:value] }
43
47
  complexities.should == complexities.sort.reverse
44
48
  end
45
49
 
46
- it 'creates a SyntaxViolation when code cannot be parsed' do
50
+ it 'creates a violation when code cannot be parsed' do
47
51
  file_name = make_file(<<-RUBY)
48
52
  class Harness
49
53
  RUBY
50
54
 
51
- violations = described_class.new(files: file_name).violations
55
+ violations = check(file_name).violations
52
56
  violations.length.should == 1
53
- violations[0].should be_instance_of(Cane::SyntaxViolation)
54
- violations[0].columns.should == [file_name]
55
- violations[0].description.should be_instance_of(String)
57
+ violations[0][:file].should == file_name
58
+ violations[0][:description].should be_instance_of(String)
56
59
  end
57
60
 
58
61
  it 'skips declared exclusions' do
@@ -84,33 +87,62 @@ describe Cane::AbcCheck do
84
87
 
85
88
  exclusions = %w[ Harness#instance_meth Harness.class_meth
86
89
  Harness::Nested#i_meth Harness::Nested.c_meth ]
87
- violations = described_class.new(files: file_name, max: 0,
88
- exclusions: exclusions).violations
90
+ violations = check(file_name,
91
+ abc_max: 0,
92
+ abc_exclude: exclusions
93
+ ).violations
89
94
  violations.length.should == 1
90
- violations[0].should be_instance_of(Cane::AbcMaxViolation)
91
- columns = violations[0].columns
92
- columns.should == [file_name, "Harness > Nested > other_meth", 1]
95
+ violations[0].values_at(:file, :label, :value).should ==
96
+ [file_name, "Harness::Nested#other_meth", 1]
97
+ end
98
+
99
+ it "creates an AbcMaxViolation for method in assigned anonymous class" do
100
+ file_name = make_file(<<-RUBY)
101
+ MyClass = Struct.new(:foo) do
102
+ def test_method(a)
103
+ b = a
104
+ return b if b > 3
105
+ end
106
+ end
107
+ RUBY
108
+
109
+ violations = check(file_name, abc_max: 1).violations
110
+ violations[0][:label] == "MyClass#test_method"
111
+ end
112
+
113
+ it "creates an AbcMaxViolation for method in anonymous class" do
114
+ file_name = make_file(<<-RUBY)
115
+ Class.new 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].should == "(anon)#test_method"
93
125
  end
94
126
 
95
- def self.it_should_extract_method_name(method_name, label=method_name)
96
- it "creates an AbcMaxViolation for #{method_name}" do
127
+ def self.it_should_extract_method_name(name, label=name, sep='#')
128
+ it "creates an AbcMaxViolation for #{name}" do
97
129
  file_name = make_file(<<-RUBY)
98
130
  class Harness
99
- def #{method_name}(a)
131
+ def #{name}(a)
100
132
  b = a
101
133
  return b if b > 3
102
134
  end
103
135
  end
104
136
  RUBY
105
137
 
106
- violations = described_class.new(files: file_name, max: 1).violations
107
- violations[0].detail.should == "Harness > #{label}"
138
+ violations = check(file_name, abc_max: 1).violations
139
+ violations[0][:label].should == "Harness#{sep}#{label}"
108
140
  end
109
141
  end
110
142
 
111
143
  # These method names all create different ASTs. Which is weird.
112
144
  it_should_extract_method_name 'a'
113
- it_should_extract_method_name 'self.a', 'a'
145
+ it_should_extract_method_name 'self.a', 'a', '.'
114
146
  it_should_extract_method_name 'next'
115
147
  it_should_extract_method_name 'GET'
116
148
  it_should_extract_method_name '`'
@@ -14,7 +14,7 @@ describe 'Cane' do
14
14
  def run(cli_args)
15
15
  result = nil
16
16
  output = capture_stdout do
17
- result = Cane::CLI.run(cli_args.split(' '))
17
+ result = Cane::CLI.run(['--no-abc'] + cli_args.split(' '))
18
18
  end
19
19
 
20
20
  [output, result ? 0 : 1]
@@ -42,8 +42,8 @@ describe 'Cane' do
42
42
  file_name = make_file("whitespace ")
43
43
 
44
44
  output, exitstatus = run("--style-glob #{file_name}")
45
- exitstatus.should == 1
46
45
  output.should include("Lines violated style requirements")
46
+ exitstatus.should == 1
47
47
  end
48
48
 
49
49
  it 'allows measure to be configured' do
@@ -59,7 +59,7 @@ describe 'Cane' do
59
59
 
60
60
  output, exitstatus = run("--style-glob #{file_name} --style-measure 80")
61
61
  exitstatus.should == 0
62
- output.should be_empty
62
+ output.should == ""
63
63
  end
64
64
 
65
65
  it 'allows upper bound of failed checks' do
@@ -74,8 +74,8 @@ describe 'Cane' do
74
74
  file_name = make_file("89")
75
75
 
76
76
  output, exitstatus = run("--gte #{file_name},90")
77
- exitstatus.should == 1
78
77
  output.should include("Quality threshold crossed")
78
+ exitstatus.should == 1
79
79
  end
80
80
 
81
81
  it 'allows checking of class documentation' do
@@ -143,21 +143,30 @@ describe 'Cane' do
143
143
  end
144
144
  RUBY
145
145
 
146
- exclusions_file = make_file(<<-YAML)
147
- abc:
148
- - Harness#complex_method
149
- style:
150
- - #{file_name}
151
- YAML
152
-
153
146
  options = [
154
147
  "--abc-glob", file_name,
148
+ "--abc-exclude", "Harness#complex_method",
155
149
  "--abc-max", 1,
156
150
  "--style-glob", file_name,
157
- "--exclusions-file", exclusions_file
151
+ "--style-exclude", file_name
158
152
  ].join(' ')
159
153
 
160
154
  _, exitstatus = run(options)
161
155
  exitstatus.should == 0
162
156
  end
157
+
158
+ it 'handles invalid unicode input' do
159
+ fn = make_file("\xc3\x28")
160
+
161
+ _, exitstatus = run("--style-glob #{fn} --abc-glob #{fn} --doc-glob #{fn}")
162
+
163
+ exitstatus.should == 0
164
+ end
165
+
166
+ it 'handles invalid options by showing help' do
167
+ out, exitstatus = run("--bogus")
168
+
169
+ out.should include("Usage:")
170
+ exitstatus.should == 1
171
+ end
163
172
  end
@@ -3,6 +3,10 @@ require 'spec_helper'
3
3
  require 'cane/doc_check'
4
4
 
5
5
  describe Cane::DocCheck do
6
+ def check(file_name, opts = {})
7
+ described_class.new(opts.merge(doc_glob: file_name))
8
+ end
9
+
6
10
  it 'creates a DocViolation for each undocumented class' do
7
11
  file_name = make_file <<-RUBY
8
12
  # This class is documented
@@ -16,17 +20,34 @@ class Meta
16
20
  end
17
21
  RUBY
18
22
 
19
- violations = described_class.new(files: file_name).violations
23
+ violations = check(file_name).violations
20
24
  violations.length.should == 2
21
25
 
22
- violations[0].should be_instance_of(Cane::UndocumentedClassViolation)
23
- violations[0].file_name.should == file_name
24
- violations[0].number.should == 3
25
- violations[0].columns.last.should eq("NoDoc")
26
+ violations[0].values_at(:file, :line, :label).should == [
27
+ file_name, 3, "NoDoc"
28
+ ]
29
+
30
+ violations[1].values_at(:file, :line, :label).should == [
31
+ file_name, 4, "AlsoNoDoc"
32
+ ]
33
+ end
34
+
35
+ it 'ignores magic encoding comments' do
36
+ file_name = make_file <<-RUBY
37
+ # coding = utf-8
38
+ class NoDoc; end
39
+ # -*- encoding : utf-8 -*-
40
+ class AlsoNoDoc; end
41
+ RUBY
42
+
43
+ violations = check(file_name).violations
44
+ violations.length.should == 2
26
45
 
27
- violations[1].should be_instance_of(Cane::UndocumentedClassViolation)
28
- violations[1].file_name.should == file_name
29
- violations[1].number.should == 4
30
- violations[1].columns.last.should eq("AlsoNoDoc")
46
+ violations[0].values_at(:file, :line, :label).should == [
47
+ file_name, 2, "NoDoc"
48
+ ]
49
+ violations[1].values_at(:file, :line, :label).should == [
50
+ file_name, 4, "AlsoNoDoc"
51
+ ]
31
52
  end
32
53
  end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ require 'cane/encoding_aware_iterator'
5
+
6
+ # Example bad input from:
7
+ # http://stackoverflow.com/questions/1301402/example-invalid-utf8-string
8
+ describe Cane::EncodingAwareIterator do
9
+ it 'handles non-UTF8 input' do
10
+ lines = ["\xc3\x28"]
11
+ result = described_class.new(lines).map_with_index do |line, number|
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
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ require 'cane/rake_task'
4
+
5
+ describe Cane::RakeTask do
6
+ it 'adds a new threshold' do
7
+ task = described_class.new do |t|
8
+ t.add_threshold 'coverage', :>=, 99
9
+ end
10
+
11
+ task.options[:gte].should == [["coverage", 99]]
12
+ end
13
+ end
@@ -3,6 +3,10 @@ require 'spec_helper'
3
3
  require 'cane/style_check'
4
4
 
5
5
  describe Cane::StyleCheck do
6
+ def check(file_name, opts = {})
7
+ described_class.new(opts.merge(style_glob: file_name))
8
+ end
9
+
6
10
  let(:ruby_with_style_issue) do
7
11
  [
8
12
  "def test ",
@@ -14,16 +18,18 @@ describe Cane::StyleCheck do
14
18
  it 'creates a StyleViolation for each method above the threshold' do
15
19
  file_name = make_file(ruby_with_style_issue)
16
20
 
17
- violations = Cane::StyleCheck.new(files: file_name, measure: 80).violations
21
+ violations = check(file_name, style_measure: 80).violations
18
22
  violations.length.should == 2
19
- violations[0].should be_instance_of(StyleViolation)
20
23
  end
21
24
 
22
25
  it 'skips declared exclusions' do
23
26
  file_name = make_file(ruby_with_style_issue)
24
27
 
25
- violations = Cane::StyleCheck.new(files: file_name, measure: 80,
26
- exclusions: [file_name]).violations
28
+ violations = check(file_name,
29
+ style_measure: 80,
30
+ style_exclude: [file_name]
31
+ ).violations
32
+
27
33
  violations.length.should == 0
28
34
  end
29
35
  end
@@ -2,11 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  require 'cane/threshold_check'
4
4
 
5
- describe ThresholdCheck do
5
+ describe Cane::ThresholdCheck do
6
6
  it 'returns a value of unavailable when file cannot be read' do
7
- check = ThresholdCheck.new([[:>=, 'bogus_file', 20]])
7
+ check = Cane::ThresholdCheck.new(gte: [['bogus_file', 20]])
8
8
  violations = check.violations
9
9
  violations.length.should == 1
10
- violations[0].to_s.should include("unavailable")
10
+ violations[0][:label].should ==
11
+ 'bogus_file is unavailable, should be >= 20'
11
12
  end
12
13
  end
@@ -4,10 +4,9 @@ require 'cane/violation_formatter'
4
4
 
5
5
  describe Cane::ViolationFormatter do
6
6
  def violation(description)
7
- stub("violation",
8
- description: description,
9
- columns: []
10
- )
7
+ {
8
+ description: description
9
+ }
11
10
  end
12
11
 
13
12
  it 'includes number of violations in the group header' do
@@ -15,7 +14,7 @@ describe Cane::ViolationFormatter do
15
14
  end
16
15
 
17
16
  it 'includes total number of violations' do
18
- violations = [violation("FAIL1"),violation("FAIL2")]
17
+ violations = [violation("FAIL1"), violation("FAIL2")]
19
18
  result = described_class.new(violations).to_s
20
19
  result.should include("Total Violations: 2")
21
20
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cane
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-02 00:00:00.000000000 Z
12
+ date: 2012-08-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -70,22 +70,21 @@ files:
70
70
  - spec/abc_check_spec.rb
71
71
  - spec/cane_spec.rb
72
72
  - spec/doc_check_spec.rb
73
+ - spec/encoding_aware_iterator_spec.rb
74
+ - spec/rake_task_spec.rb
73
75
  - spec/spec_helper.rb
74
76
  - spec/style_check_spec.rb
75
77
  - spec/threshold_check_spec.rb
76
78
  - spec/violation_formatter_spec.rb
77
79
  - lib/cane/abc_check.rb
78
- - lib/cane/abc_max_violation.rb
79
80
  - lib/cane/cli/spec.rb
80
- - lib/cane/cli/translator.rb
81
81
  - lib/cane/cli.rb
82
82
  - lib/cane/doc_check.rb
83
+ - lib/cane/encoding_aware_iterator.rb
84
+ - lib/cane/file.rb
83
85
  - lib/cane/rake_task.rb
84
86
  - lib/cane/style_check.rb
85
- - lib/cane/style_violation.rb
86
- - lib/cane/syntax_violation.rb
87
87
  - lib/cane/threshold_check.rb
88
- - lib/cane/threshold_violation.rb
89
88
  - lib/cane/version.rb
90
89
  - lib/cane/violation_formatter.rb
91
90
  - lib/cane.rb
@@ -123,6 +122,8 @@ test_files:
123
122
  - spec/abc_check_spec.rb
124
123
  - spec/cane_spec.rb
125
124
  - spec/doc_check_spec.rb
125
+ - spec/encoding_aware_iterator_spec.rb
126
+ - spec/rake_task_spec.rb
126
127
  - spec/spec_helper.rb
127
128
  - spec/style_check_spec.rb
128
129
  - spec/threshold_check_spec.rb
@@ -1,15 +0,0 @@
1
- module Cane
2
-
3
- # Value object used by AbcCheck for a method that is too complicated.
4
- class AbcMaxViolation < Struct.new(:file_name, :detail, :complexity)
5
- def columns
6
- [file_name, detail, complexity]
7
- end
8
-
9
- def description
10
- "Methods exceeded maximum allowed ABC complexity"
11
- end
12
-
13
- alias_method :sort_index, :complexity
14
- end
15
- end
@@ -1,71 +0,0 @@
1
- require 'yaml'
2
-
3
- module Cane
4
- module CLI
5
-
6
- # Translates CLI options with given defaults to a hash suitable to be
7
- # passed to `Cane.run`.
8
- class Translator < Struct.new(:options, :defaults)
9
- def to_hash
10
- result = {}
11
- translate_abc_options(result)
12
- translate_doc_options(result)
13
- translate_style_options(result)
14
-
15
- result[:threshold] = options.fetch(:threshold, [])
16
- result[:max_violations] = option_with_default(:max_violations).to_i
17
-
18
- result
19
- end
20
-
21
- def translate_abc_options(result)
22
- result[:abc] = {
23
- files: option_with_default(:abc_glob),
24
- max: option_with_default(:abc_max).to_i,
25
- exclusions: exclusions_for('abc')
26
- } unless check_disabled(:no_abc, [:abc_glob, :abc_max])
27
- end
28
-
29
- def translate_style_options(result)
30
- result[:style] = {
31
- files: option_with_default(:style_glob),
32
- measure: option_with_default(:style_measure).to_i,
33
- exclusions: exclusions_for('style')
34
- } unless check_disabled(:no_style, [:style_glob])
35
- end
36
-
37
- def translate_doc_options(result)
38
- result[:doc] = {
39
- files: option_with_default(:doc_glob),
40
- } unless check_disabled(:no_doc, [:doc_glob])
41
- end
42
-
43
- def check_disabled(check, params)
44
- relevant_options = options.keys & params + [check]
45
-
46
- check == relevant_options[-1]
47
- end
48
-
49
- def option_with_default(key)
50
- options.fetch(key, defaults.fetch(key))
51
- end
52
-
53
- private
54
-
55
- def exclusions_for(tool)
56
- Array(exclusions[tool])
57
- end
58
-
59
- def exclusions
60
- @exclusions ||= begin
61
- if file = options[:exclusions_file]
62
- YAML.load_file(file)
63
- else
64
- {}
65
- end
66
- end
67
- end
68
- end
69
-
70
- end
71
- end