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.
Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +41 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +1 -2
  5. data/Gemfile +9 -2
  6. data/README.md +3 -3
  7. data/config/devtools.yml +2 -0
  8. data/config/flay.yml +2 -2
  9. data/config/flog.yml +1 -1
  10. data/config/mutant.yml +6 -2
  11. data/config/reek.yml +105 -106
  12. data/config/rubocop.yml +37 -3
  13. data/devtools.gemspec +27 -25
  14. data/lib/devtools/config.rb +74 -66
  15. data/lib/devtools/flay.rb +98 -0
  16. data/lib/devtools/project/initializer/rake.rb +11 -7
  17. data/lib/devtools/project/initializer/rspec.rb +30 -55
  18. data/lib/devtools/project/initializer.rb +2 -12
  19. data/lib/devtools/project.rb +27 -105
  20. data/lib/devtools/rake/flay.rb +155 -0
  21. data/lib/devtools/spec_helper.rb +1 -3
  22. data/lib/devtools.rb +42 -89
  23. data/shared/spec/shared/abstract_type_behavior.rb +0 -2
  24. data/shared/spec/shared/command_method_behavior.rb +0 -2
  25. data/shared/spec/shared/each_method_behaviour.rb +0 -2
  26. data/shared/spec/shared/idempotent_method_behavior.rb +2 -3
  27. data/shared/spec/support/ice_nine_config.rb +0 -2
  28. data/spec/integration/devtools/rake/flay/verify_spec.rb +196 -0
  29. data/spec/spec_helper.rb +4 -11
  30. data/spec/unit/devtools/config/yardstick_spec.rb +17 -0
  31. data/spec/unit/devtools/config_spec.rb +78 -0
  32. data/spec/unit/devtools/flay/file_list/call_spec.rb +19 -0
  33. data/spec/unit/devtools/flay/scale/flay_report_spec.rb +17 -0
  34. data/spec/unit/devtools/flay/scale/measure_spec.rb +43 -0
  35. data/spec/unit/devtools/project/initializer/rake_spec.rb +21 -0
  36. data/spec/unit/devtools/project/initializer/rspec_spec.rb +52 -0
  37. data/spec/unit/devtools/project_spec.rb +34 -0
  38. data/spec/unit/devtools_spec.rb +14 -0
  39. data/tasks/metrics/ci.rake +3 -7
  40. data/tasks/metrics/coverage.rake +2 -2
  41. data/tasks/metrics/flay.rake +6 -33
  42. data/tasks/metrics/flog.rake +10 -7
  43. data/tasks/metrics/reek.rake +5 -4
  44. data/tasks/metrics/rubocop.rake +1 -3
  45. data/tasks/metrics/yardstick.rake +0 -14
  46. data/tasks/spec.rake +1 -3
  47. data/tasks/yard.rake +1 -3
  48. metadata +124 -65
  49. data/.ruby-gemset +0 -1
  50. data/.travis.yml +0 -15
  51. data/TODO +0 -0
  52. data/bin/devtools +0 -18
  53. data/circle.yml +0 -7
  54. data/default/config/devtools.yml +0 -2
  55. data/default/config/flay.yml +0 -3
  56. data/default/config/flog.yml +0 -2
  57. data/default/config/mutant.yml +0 -3
  58. data/default/config/reek.yml +0 -103
  59. data/default/config/rubocop.yml +0 -91
  60. data/default/config/yardstick.yml +0 -2
  61. data/lib/devtools/platform.rb +0 -118
  62. data/lib/devtools/site/initializer.rb +0 -57
  63. data/lib/devtools/site.rb +0 -41
  64. data/shared/spec/shared/hash_method_behavior.rb +0 -9
  65. 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
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'devtools'
4
2
 
5
- Devtools.init_spec_helper
3
+ Devtools::PROJECT.init_rspec
data/lib/devtools.rb CHANGED
@@ -1,44 +1,41 @@
1
- # encoding: utf-8
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
- require 'devtools/platform'
10
- require 'devtools/site'
11
- require 'devtools/site/initializer'
12
- require 'devtools/project'
13
- require 'devtools/config'
14
- require 'devtools/project/initializer'
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
- extend Platform
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'.freeze
30
- SPEC_DIRECTORY_NAME = 'spec'.freeze
31
- RB_FILE_PATTERN = '**/*.rb'.freeze
32
- RAKE_FILE_NAME = 'Rakefile'.freeze
33
- REQUIRE = "require 'devtools'".freeze
34
- INIT_RAKE_TASKS = 'Devtools.init_rake_tasks'.freeze
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
- # Provides devtools for a project
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
- # Initialize project and load shared specs
67
- #
68
- # Expects to be called from $application_root/spec/spec_helper.rb
63
+ # Return devtools root path
69
64
  #
70
- # @return [self]
65
+ # @return [Pathname]
71
66
  #
72
- # @api public
73
- def self.init_spec_helper
74
- SITE.init_spec_helper
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
- SITE.project
78
+ PROJECT
95
79
  end
96
80
 
97
- # Require shared examples
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
- # Detect travis ci
134
- #
135
- # @return [Boolean]
136
- #
137
- # @api private
138
- #
139
- def self.travis?
140
- ENV.key?('TRAVIS')
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
- end # module Devtools
92
+ # Devtools self initialization
93
+ module Devtools
94
+ # The project devtools is active for
95
+ PROJECT = Project.new(PROJECT_ROOT)
96
+ end
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  shared_examples_for 'an abstract type' do
4
2
  context 'called on a subclass' do
5
3
  let(:object) { Class.new(described_class) }
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  shared_examples_for 'a command method' do
4
2
  it 'returns self' do
5
3
  should equal(object)
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  shared_examples_for 'an #each method' do
4
2
  it_should_behave_like 'a command method'
5
3
 
@@ -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
- fail 'RSpec not configured for threadsafety' unless RSpec.configuration.threadsafe?
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) }
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  if defined?(IceNine)
4
2
  module IceNine
5
3
 
@@ -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
- require 'simplecov'
7
- require 'coveralls'
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