devtools 0.1.0 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.circleci/config.yml +41 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1 -2
- data/Gemfile +9 -2
- data/README.md +3 -3
- data/config/devtools.yml +2 -0
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/mutant.yml +6 -2
- data/config/reek.yml +105 -106
- data/config/rubocop.yml +37 -3
- data/devtools.gemspec +27 -25
- data/lib/devtools/config.rb +74 -66
- data/lib/devtools/flay.rb +98 -0
- data/lib/devtools/project/initializer/rake.rb +11 -7
- data/lib/devtools/project/initializer/rspec.rb +30 -55
- data/lib/devtools/project/initializer.rb +2 -12
- data/lib/devtools/project.rb +27 -105
- data/lib/devtools/rake/flay.rb +155 -0
- data/lib/devtools/spec_helper.rb +1 -3
- data/lib/devtools.rb +42 -89
- data/shared/spec/shared/abstract_type_behavior.rb +0 -2
- data/shared/spec/shared/command_method_behavior.rb +0 -2
- data/shared/spec/shared/each_method_behaviour.rb +0 -2
- data/shared/spec/shared/idempotent_method_behavior.rb +2 -3
- data/shared/spec/support/ice_nine_config.rb +0 -2
- data/spec/integration/devtools/rake/flay/verify_spec.rb +196 -0
- data/spec/spec_helper.rb +4 -11
- data/spec/unit/devtools/config/yardstick_spec.rb +17 -0
- data/spec/unit/devtools/config_spec.rb +78 -0
- data/spec/unit/devtools/flay/file_list/call_spec.rb +19 -0
- data/spec/unit/devtools/flay/scale/flay_report_spec.rb +17 -0
- data/spec/unit/devtools/flay/scale/measure_spec.rb +43 -0
- data/spec/unit/devtools/project/initializer/rake_spec.rb +21 -0
- data/spec/unit/devtools/project/initializer/rspec_spec.rb +52 -0
- data/spec/unit/devtools/project_spec.rb +34 -0
- data/spec/unit/devtools_spec.rb +14 -0
- data/tasks/metrics/ci.rake +3 -7
- data/tasks/metrics/coverage.rake +2 -2
- data/tasks/metrics/flay.rake +6 -33
- data/tasks/metrics/flog.rake +10 -7
- data/tasks/metrics/reek.rake +5 -4
- data/tasks/metrics/rubocop.rake +1 -3
- data/tasks/metrics/yardstick.rake +0 -14
- data/tasks/spec.rake +1 -3
- data/tasks/yard.rake +1 -3
- metadata +124 -65
- data/.ruby-gemset +0 -1
- data/.travis.yml +0 -15
- data/TODO +0 -0
- data/bin/devtools +0 -18
- data/circle.yml +0 -7
- data/default/config/devtools.yml +0 -2
- data/default/config/flay.yml +0 -3
- data/default/config/flog.yml +0 -2
- data/default/config/mutant.yml +0 -3
- data/default/config/reek.yml +0 -103
- data/default/config/rubocop.yml +0 -91
- data/default/config/yardstick.yml +0 -2
- data/lib/devtools/platform.rb +0 -118
- data/lib/devtools/site/initializer.rb +0 -57
- data/lib/devtools/site.rb +0 -41
- data/shared/spec/shared/hash_method_behavior.rb +0 -9
- data/tasks/metrics/mutant.rake +0 -50
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devtools
|
|
4
|
+
module Rake
|
|
5
|
+
# Flay metric runner
|
|
6
|
+
class Flay
|
|
7
|
+
include Anima.new(:threshold, :total_score, :lib_dirs, :excludes),
|
|
8
|
+
Procto.call(:verify),
|
|
9
|
+
Adamantium
|
|
10
|
+
|
|
11
|
+
BELOW_THRESHOLD = 'Adjust flay threshold down to %<mass>d'
|
|
12
|
+
TOTAL_MISMATCH = 'Flay total is now %<mass>d, but expected %<expected>d'
|
|
13
|
+
ABOVE_THRESHOLD = '%<mass>d chunks have a duplicate mass > %<threshold>d'
|
|
14
|
+
|
|
15
|
+
# Verify code specified by `files` does not violate flay expectations
|
|
16
|
+
#
|
|
17
|
+
# @raise [SystemExit] if a violation is found
|
|
18
|
+
# @return [undefined] otherwise
|
|
19
|
+
#
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
def verify
|
|
23
|
+
# Run flay first to ensure the max mass matches the threshold
|
|
24
|
+
below_threshold_message if below_threshold?
|
|
25
|
+
|
|
26
|
+
total_mismatch_message if total_mismatch?
|
|
27
|
+
|
|
28
|
+
# Run flay a second time with the threshold set
|
|
29
|
+
return unless above_threshold?
|
|
30
|
+
|
|
31
|
+
restricted_flay_scale.flay_report
|
|
32
|
+
above_threshold_message
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# List of files flay will analyze
|
|
38
|
+
#
|
|
39
|
+
# @return [Set<Pathname>]
|
|
40
|
+
#
|
|
41
|
+
# @api private
|
|
42
|
+
def files
|
|
43
|
+
Devtools::Flay::FileList.call(lib_dirs, excludes)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Is there mass duplication which exceeds specified `threshold`
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
#
|
|
50
|
+
# @api private
|
|
51
|
+
def above_threshold?
|
|
52
|
+
restricted_mass_size.nonzero?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Is the specified `threshold` greater than the largest flay mass
|
|
56
|
+
#
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
#
|
|
59
|
+
# @api private
|
|
60
|
+
def below_threshold?
|
|
61
|
+
threshold > largest_mass
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Is the expected mass total different from the actual mass total
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
#
|
|
68
|
+
# @api private
|
|
69
|
+
def total_mismatch?
|
|
70
|
+
!total_mass.equal?(total_score)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Above threshold message
|
|
74
|
+
#
|
|
75
|
+
# @return [String]
|
|
76
|
+
#
|
|
77
|
+
# @api private
|
|
78
|
+
def above_threshold_message
|
|
79
|
+
format_values = { mass: restricted_mass_size, threshold: threshold }
|
|
80
|
+
Devtools.notify_metric_violation(
|
|
81
|
+
format(ABOVE_THRESHOLD, format_values)
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Below threshold message
|
|
86
|
+
#
|
|
87
|
+
# @return [String]
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
def below_threshold_message
|
|
91
|
+
Devtools.notify_metric_violation(
|
|
92
|
+
format(BELOW_THRESHOLD, mass: largest_mass)
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Total mismatch message
|
|
97
|
+
#
|
|
98
|
+
# @return [String]
|
|
99
|
+
#
|
|
100
|
+
# @api private
|
|
101
|
+
def total_mismatch_message
|
|
102
|
+
Devtools.notify_metric_violation(
|
|
103
|
+
format(TOTAL_MISMATCH, mass: total_mass, expected: total_score)
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Size of mass measured by `Flay::Scale` and filtered by `threshold`
|
|
108
|
+
#
|
|
109
|
+
# @return [Integer]
|
|
110
|
+
#
|
|
111
|
+
# @api private
|
|
112
|
+
def restricted_mass_size
|
|
113
|
+
restricted_flay_scale.measure.size
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Sum of all flay mass
|
|
117
|
+
#
|
|
118
|
+
# @return [Integer]
|
|
119
|
+
#
|
|
120
|
+
# @api private
|
|
121
|
+
def total_mass
|
|
122
|
+
flay_masses.reduce(:+).to_i
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Largest flay mass found
|
|
126
|
+
#
|
|
127
|
+
# @return [Integer]
|
|
128
|
+
#
|
|
129
|
+
# @api private
|
|
130
|
+
def largest_mass
|
|
131
|
+
flay_masses.max.to_i
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Flay scale which only measures mass above `threshold`
|
|
135
|
+
#
|
|
136
|
+
# @return [Flay::Scale]
|
|
137
|
+
#
|
|
138
|
+
# @api private
|
|
139
|
+
def restricted_flay_scale
|
|
140
|
+
Devtools::Flay::Scale.new(minimum_mass: threshold.succ, files: files)
|
|
141
|
+
end
|
|
142
|
+
memoize :restricted_flay_scale
|
|
143
|
+
|
|
144
|
+
# All flay masses found in `files`
|
|
145
|
+
#
|
|
146
|
+
# @return [Array<Rational>]
|
|
147
|
+
#
|
|
148
|
+
# @api private
|
|
149
|
+
def flay_masses
|
|
150
|
+
Devtools::Flay::Scale.call(minimum_mass: 0, files: files)
|
|
151
|
+
end
|
|
152
|
+
memoize :flay_masses
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
data/lib/devtools/spec_helper.rb
CHANGED
data/lib/devtools.rb
CHANGED
|
@@ -1,44 +1,41 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Stdlib infrastructure
|
|
3
4
|
require 'pathname'
|
|
4
5
|
require 'rake'
|
|
5
6
|
require 'timeout'
|
|
6
7
|
require 'yaml'
|
|
7
8
|
require 'fileutils'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
require '
|
|
11
|
-
require '
|
|
12
|
-
require '
|
|
13
|
-
require '
|
|
14
|
-
require '
|
|
15
|
-
require 'devtools/project/initializer/rake'
|
|
16
|
-
require 'devtools/project/initializer/rspec'
|
|
17
|
-
|
|
18
|
-
# Provides access to metric tools
|
|
19
|
-
module Devtools
|
|
10
|
+
# Non stdlib infrastructure
|
|
11
|
+
require 'abstract_type'
|
|
12
|
+
require 'procto'
|
|
13
|
+
require 'anima'
|
|
14
|
+
require 'concord'
|
|
15
|
+
require 'adamantium'
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
# Wrapped tools
|
|
18
|
+
require 'flay'
|
|
19
|
+
require 'rspec'
|
|
20
|
+
require 'rspec/its'
|
|
21
|
+
require 'simplecov'
|
|
22
22
|
|
|
23
|
+
# Main devtools namespace population
|
|
24
|
+
module Devtools
|
|
23
25
|
ROOT = Pathname.new(__FILE__).parent.parent.freeze
|
|
24
26
|
PROJECT_ROOT = Pathname.pwd.freeze
|
|
25
27
|
SHARED_PATH = ROOT.join('shared').freeze
|
|
26
28
|
SHARED_SPEC_PATH = SHARED_PATH.join('spec').freeze
|
|
27
29
|
DEFAULT_CONFIG_PATH = ROOT.join('default/config').freeze
|
|
28
30
|
RAKE_FILES_GLOB = ROOT.join('tasks/**/*.rake').to_s.freeze
|
|
29
|
-
LIB_DIRECTORY_NAME = 'lib'
|
|
30
|
-
SPEC_DIRECTORY_NAME = 'spec'
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
SHARED_SPEC_PATTERN = '{shared,support}/**/*.rb'.freeze
|
|
36
|
-
UNIT_TEST_PATH_REGEXP = %r{\bspec/unit/}.freeze
|
|
37
|
-
DEFAULT_CONFIG_DIR_NAME = 'config'.freeze
|
|
38
|
-
ANNOTATION_WRAPPER = "\n# Added by devtools\n%s\n".freeze
|
|
31
|
+
LIB_DIRECTORY_NAME = 'lib'
|
|
32
|
+
SPEC_DIRECTORY_NAME = 'spec'
|
|
33
|
+
RAKE_FILE_NAME = 'Rakefile'
|
|
34
|
+
SHARED_SPEC_PATTERN = '{shared,support}/**/*.rb'
|
|
35
|
+
UNIT_TEST_PATH_REGEXP = %r{\bspec/unit/}
|
|
36
|
+
DEFAULT_CONFIG_DIR_NAME = 'config'
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
SITE = Site.new(Project.new(PROJECT_ROOT))
|
|
38
|
+
private_constant(*constants(false))
|
|
42
39
|
|
|
43
40
|
# React to metric violation
|
|
44
41
|
#
|
|
@@ -63,26 +60,13 @@ module Devtools
|
|
|
63
60
|
self
|
|
64
61
|
end
|
|
65
62
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# Expects to be called from $application_root/spec/spec_helper.rb
|
|
63
|
+
# Return devtools root path
|
|
69
64
|
#
|
|
70
|
-
# @return [
|
|
65
|
+
# @return [Pathname]
|
|
71
66
|
#
|
|
72
|
-
# @api
|
|
73
|
-
def self.
|
|
74
|
-
|
|
75
|
-
self
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Initialize devtools using default config
|
|
79
|
-
#
|
|
80
|
-
# @return [undefined]
|
|
81
|
-
#
|
|
82
|
-
# @api public
|
|
83
|
-
def self.init
|
|
84
|
-
SITE.init
|
|
85
|
-
self
|
|
67
|
+
# @api private
|
|
68
|
+
def self.root
|
|
69
|
+
ROOT
|
|
86
70
|
end
|
|
87
71
|
|
|
88
72
|
# Return project
|
|
@@ -91,53 +75,22 @@ module Devtools
|
|
|
91
75
|
#
|
|
92
76
|
# @api private
|
|
93
77
|
def self.project
|
|
94
|
-
|
|
78
|
+
PROJECT
|
|
95
79
|
end
|
|
96
80
|
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
# @param [Pathname] dir
|
|
100
|
-
# the directory containing the files to require
|
|
101
|
-
#
|
|
102
|
-
# @param [String] pattern
|
|
103
|
-
# the file pattern to match inside directory
|
|
104
|
-
#
|
|
105
|
-
# @return [self]
|
|
106
|
-
#
|
|
107
|
-
# @api private
|
|
108
|
-
def self.require_files(dir, pattern)
|
|
109
|
-
Dir[dir.join(pattern)].each { |file| require file }
|
|
110
|
-
self
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Detect ci
|
|
114
|
-
#
|
|
115
|
-
# @return [Boolean]
|
|
116
|
-
#
|
|
117
|
-
# @api private
|
|
118
|
-
#
|
|
119
|
-
def self.ci?
|
|
120
|
-
ENV.key?('CI')
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Detect circle ci
|
|
124
|
-
#
|
|
125
|
-
# @return [Boolean]
|
|
126
|
-
#
|
|
127
|
-
# @api private
|
|
128
|
-
#
|
|
129
|
-
def self.circle_ci?
|
|
130
|
-
ENV.key?('CIRCLECI')
|
|
131
|
-
end
|
|
81
|
+
end # module Devtools
|
|
132
82
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
end
|
|
83
|
+
# Devtools implementation
|
|
84
|
+
require 'devtools/config'
|
|
85
|
+
require 'devtools/project'
|
|
86
|
+
require 'devtools/project/initializer'
|
|
87
|
+
require 'devtools/project/initializer/rake'
|
|
88
|
+
require 'devtools/project/initializer/rspec'
|
|
89
|
+
require 'devtools/flay'
|
|
90
|
+
require 'devtools/rake/flay'
|
|
142
91
|
|
|
143
|
-
|
|
92
|
+
# Devtools self initialization
|
|
93
|
+
module Devtools
|
|
94
|
+
# The project devtools is active for
|
|
95
|
+
PROJECT = Project.new(PROJECT_ROOT)
|
|
96
|
+
end
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
# encoding: utf-8
|
|
2
|
-
|
|
3
1
|
shared_examples_for 'an idempotent method' do
|
|
4
2
|
it 'is idempotent' do
|
|
5
3
|
first = subject
|
|
6
|
-
|
|
4
|
+
error = 'RSpec not configured for threadsafety'
|
|
5
|
+
fail error unless RSpec.configuration.threadsafe?
|
|
7
6
|
mutex = __memoized.instance_variable_get(:@mutex)
|
|
8
7
|
memoized = __memoized.instance_variable_get(:@memoized)
|
|
9
8
|
mutex.synchronize { memoized.delete(:subject) }
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe Devtools::Rake::Flay, '#verify' do
|
|
4
|
+
let(:tempfile) { Tempfile.new(%w[file .rb], Dir.mktmpdir) }
|
|
5
|
+
let(:file) { Pathname(tempfile.path) }
|
|
6
|
+
let(:directories) { [file.dirname.to_s] }
|
|
7
|
+
|
|
8
|
+
let(:ruby) do
|
|
9
|
+
<<-ERUBY
|
|
10
|
+
def foo; end
|
|
11
|
+
def bar; end
|
|
12
|
+
ERUBY
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
around(:each) do |example|
|
|
16
|
+
begin
|
|
17
|
+
# silence other flay output
|
|
18
|
+
$stdout = $stderr = StringIO.new
|
|
19
|
+
|
|
20
|
+
tempfile.write(ruby)
|
|
21
|
+
tempfile.close
|
|
22
|
+
|
|
23
|
+
example.run
|
|
24
|
+
ensure
|
|
25
|
+
$stdout = STDOUT
|
|
26
|
+
$stderr = STDERR
|
|
27
|
+
|
|
28
|
+
file.unlink
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'reporting' do
|
|
33
|
+
let(:options) do
|
|
34
|
+
{ threshold: 3, total_score: 3, lib_dirs: directories, excludes: [] }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
let(:instance) { described_class.new(options) }
|
|
38
|
+
|
|
39
|
+
it 'measures total mass' do
|
|
40
|
+
allow(::Flay).to receive(:new).and_call_original
|
|
41
|
+
|
|
42
|
+
instance.verify
|
|
43
|
+
|
|
44
|
+
expect(::Flay).to have_received(:new).with(hash_including(mass: 0))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'does not report the files it is processing' do
|
|
48
|
+
expect { instance.verify }.to_not output(/Processing #{file}/).to_stderr
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context 'when theshold is too low' do
|
|
53
|
+
let(:instance) do
|
|
54
|
+
described_class.new(
|
|
55
|
+
threshold: 0,
|
|
56
|
+
total_score: 0,
|
|
57
|
+
lib_dirs: directories,
|
|
58
|
+
excludes: []
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
specify do
|
|
63
|
+
expect { instance.verify }
|
|
64
|
+
.to raise_error(SystemExit)
|
|
65
|
+
.with_message('Flay total is now 3, but expected 0')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'when threshold is too high' do
|
|
70
|
+
let(:instance) do
|
|
71
|
+
described_class.new(
|
|
72
|
+
threshold: 1000,
|
|
73
|
+
total_score: 0,
|
|
74
|
+
lib_dirs: directories,
|
|
75
|
+
excludes: []
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
specify do
|
|
80
|
+
expect { instance.verify }
|
|
81
|
+
.to raise_error(SystemExit)
|
|
82
|
+
.with_message('Adjust flay threshold down to 3')
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
context 'when total is too high' do
|
|
87
|
+
let(:instance) do
|
|
88
|
+
described_class.new(
|
|
89
|
+
threshold: 3,
|
|
90
|
+
total_score: 50,
|
|
91
|
+
lib_dirs: directories,
|
|
92
|
+
excludes: []
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
specify do
|
|
97
|
+
expect { instance.verify }
|
|
98
|
+
.to raise_error(SystemExit)
|
|
99
|
+
.with_message('Flay total is now 3, but expected 50')
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context 'when duplicate mass is greater than 0' do
|
|
104
|
+
let(:ruby) do
|
|
105
|
+
<<-ERUBY
|
|
106
|
+
def foo
|
|
107
|
+
:hi if baz?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def bar
|
|
111
|
+
:hi if baz?
|
|
112
|
+
end
|
|
113
|
+
ERUBY
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
let(:report) do
|
|
117
|
+
<<~REPORT
|
|
118
|
+
Total score (lower is better) = 10
|
|
119
|
+
|
|
120
|
+
1) Similar code found in :defn (mass = 10)
|
|
121
|
+
#{file}:1
|
|
122
|
+
#{file}:5
|
|
123
|
+
REPORT
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
let(:instance) do
|
|
127
|
+
described_class.new(
|
|
128
|
+
threshold: 3,
|
|
129
|
+
total_score: 5,
|
|
130
|
+
lib_dirs: directories,
|
|
131
|
+
excludes: []
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
specify do
|
|
136
|
+
expect { instance.verify }
|
|
137
|
+
.to raise_error(SystemExit)
|
|
138
|
+
.with_message('1 chunks have a duplicate mass > 3')
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
specify do
|
|
142
|
+
expect { instance.verify }
|
|
143
|
+
.to raise_error(SystemExit)
|
|
144
|
+
.and output(report).to_stdout
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
context 'when multiple duplicate masses' do
|
|
149
|
+
let(:ruby) do
|
|
150
|
+
<<-ERUBY
|
|
151
|
+
def foo; end
|
|
152
|
+
def bar; end
|
|
153
|
+
|
|
154
|
+
class Foo
|
|
155
|
+
def initialize
|
|
156
|
+
@a = 1
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
class Bar
|
|
160
|
+
def initialize
|
|
161
|
+
@a = 1
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
ERUBY
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
let(:instance) do
|
|
168
|
+
described_class.new(
|
|
169
|
+
threshold: 5,
|
|
170
|
+
total_score: 8,
|
|
171
|
+
lib_dirs: directories,
|
|
172
|
+
excludes: []
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'sums masses for total' do
|
|
177
|
+
expect { instance.verify }.to_not raise_error
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
context 'when no duplication masses' do
|
|
182
|
+
let(:ruby) { '' }
|
|
183
|
+
let(:instance) do
|
|
184
|
+
described_class.new(
|
|
185
|
+
threshold: 0,
|
|
186
|
+
total_score: 0,
|
|
187
|
+
lib_dirs: directories,
|
|
188
|
+
excludes: []
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
specify do
|
|
193
|
+
expect { instance.verify }.to_not raise_error
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
# encoding: utf-8
|
|
2
|
-
|
|
3
1
|
require 'devtools/spec_helper'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
require 'tmpdir'
|
|
4
4
|
|
|
5
5
|
if ENV['COVERAGE'] == 'true'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
|
10
|
-
SimpleCov::Formatter::HTMLFormatter,
|
|
11
|
-
Coveralls::SimpleCov::Formatter
|
|
12
|
-
]
|
|
6
|
+
formatter = [SimpleCov::Formatter::HTMLFormatter]
|
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(formatter)
|
|
13
8
|
|
|
14
9
|
SimpleCov.start do
|
|
15
10
|
command_name 'spec:unit'
|
|
@@ -22,8 +17,6 @@ if ENV['COVERAGE'] == 'true'
|
|
|
22
17
|
end
|
|
23
18
|
end
|
|
24
19
|
|
|
25
|
-
require 'devtools'
|
|
26
|
-
|
|
27
20
|
RSpec.configure do |config|
|
|
28
21
|
config.expect_with :rspec do |expect_with|
|
|
29
22
|
expect_with.syntax = :expect
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
RSpec.describe Devtools::Config::Yardstick do
|
|
2
|
+
let(:object) { described_class.new(Devtools.root.join('config')) }
|
|
3
|
+
|
|
4
|
+
describe '#options' do
|
|
5
|
+
subject { object.options }
|
|
6
|
+
|
|
7
|
+
specify do
|
|
8
|
+
should eql(
|
|
9
|
+
'threshold' => 100,
|
|
10
|
+
'rules' => nil,
|
|
11
|
+
'verbose' => nil,
|
|
12
|
+
'path' => nil,
|
|
13
|
+
'require_exact_threshold' => nil
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
RSpec.describe Devtools::Config do
|
|
2
|
+
|
|
3
|
+
describe '.attribute' do
|
|
4
|
+
let(:raw) do
|
|
5
|
+
{
|
|
6
|
+
'a' => 'bar',
|
|
7
|
+
'c' => []
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
let(:config_path) { instance_double(Pathname) }
|
|
13
|
+
|
|
14
|
+
let(:class_under_test) do
|
|
15
|
+
expect(config_path).to receive(:file?)
|
|
16
|
+
.and_return(file?)
|
|
17
|
+
expect(config_path).to receive(:frozen?)
|
|
18
|
+
.and_return(true)
|
|
19
|
+
expect(config_path).to receive(:join)
|
|
20
|
+
.with('bar.yml')
|
|
21
|
+
.and_return(config_path)
|
|
22
|
+
|
|
23
|
+
Class.new(described_class) do
|
|
24
|
+
attribute :a, [String]
|
|
25
|
+
attribute :b, [Array], default: []
|
|
26
|
+
attribute :c, [TrueClass, FalseClass]
|
|
27
|
+
|
|
28
|
+
const_set(:FILE, 'bar.yml')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
subject do
|
|
33
|
+
class_under_test.new(config_path)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'on present config' do
|
|
37
|
+
let(:class_under_test) do
|
|
38
|
+
# Setup message expectation in a lasy way, not in a before
|
|
39
|
+
# block to make sure the around hook setting timeouts from the
|
|
40
|
+
# code under test is not affected.
|
|
41
|
+
expect(YAML).to receive(:load_file)
|
|
42
|
+
.with(config_path)
|
|
43
|
+
.and_return(raw)
|
|
44
|
+
|
|
45
|
+
expect(IceNine).to receive(:deep_freeze)
|
|
46
|
+
.with(raw)
|
|
47
|
+
.and_return(raw)
|
|
48
|
+
|
|
49
|
+
super()
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
let(:file?) { true }
|
|
53
|
+
|
|
54
|
+
it 'allows to receive existing keys' do
|
|
55
|
+
expect(subject.a).to eql('bar')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'allows to receive absent keys with defaults' do
|
|
59
|
+
expect(subject.b).to eql([])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'executes checks when configured' do
|
|
63
|
+
expect { subject.c }.to raise_error(
|
|
64
|
+
Devtools::Config::TypeError,
|
|
65
|
+
'c: Got instance of Array expected TrueClass,FalseClass'
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context 'on absent config' do
|
|
71
|
+
let(:file?) { false }
|
|
72
|
+
|
|
73
|
+
it 'defaults to absent keys' do
|
|
74
|
+
expect(subject.b).to eql([])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|