devtools 0.1.2 → 0.1.3
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 +4 -4
- data/.rspec +1 -0
- data/Gemfile +0 -2
- data/README.md +2 -2
- data/circle.yml +1 -1
- data/config/devtools.yml +2 -0
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/mutant.yml +2 -0
- data/config/reek.yml +7 -12
- data/devtools.gemspec +20 -18
- data/lib/devtools.rb +34 -83
- data/lib/devtools/config.rb +76 -55
- data/lib/devtools/flay.rb +94 -0
- data/lib/devtools/project.rb +28 -105
- data/lib/devtools/project/initializer.rb +2 -12
- data/lib/devtools/project/initializer/rake.rb +9 -7
- data/lib/devtools/project/initializer/rspec.rb +28 -55
- data/lib/devtools/rake/flay.rb +126 -0
- data/lib/devtools/spec_helper.rb +1 -3
- 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/hash_method_behavior.rb +0 -2
- data/shared/spec/shared/idempotent_method_behavior.rb +0 -2
- data/shared/spec/support/ice_nine_config.rb +0 -2
- data/spec/integration/devtools/rake/flay/verify_spec.rb +164 -0
- data/spec/spec_helper.rb +2 -8
- 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 +1 -3
- data/tasks/metrics/coverage.rake +0 -2
- data/tasks/metrics/flay.rake +6 -33
- data/tasks/metrics/flog.rake +0 -2
- data/tasks/metrics/mutant.rake +31 -39
- data/tasks/metrics/reek.rake +0 -2
- data/tasks/metrics/rubocop.rake +0 -2
- data/tasks/metrics/yardstick.rake +0 -14
- data/tasks/spec.rake +0 -2
- data/tasks/yard.rake +0 -2
- metadata +96 -31
- data/.travis.yml +0 -15
- data/TODO +0 -0
- data/bin/devtools +0 -18
- 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.rb +0 -41
- data/lib/devtools/site/initializer.rb +0 -57
@@ -0,0 +1,94 @@
|
|
1
|
+
module Devtools
|
2
|
+
module Flay
|
3
|
+
# Measure flay mass relative to size of duplicated sexps
|
4
|
+
class Scale
|
5
|
+
include Adamantium
|
6
|
+
include Anima.new(:minimum_mass, :files)
|
7
|
+
include Procto.call(:measure)
|
8
|
+
|
9
|
+
# Measure duplication mass
|
10
|
+
#
|
11
|
+
# @return [Array<Rational>]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
def measure
|
15
|
+
flay.masses.map do |hash, mass|
|
16
|
+
Rational(mass, flay.hashes.fetch(hash).size)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Report flay output
|
21
|
+
#
|
22
|
+
# @return [undefined]
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
def flay_report
|
26
|
+
flay.report
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Memoized flay instance
|
32
|
+
#
|
33
|
+
# @return [Flay]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def flay
|
37
|
+
::Flay.new(mass: minimum_mass).tap do |flay|
|
38
|
+
flay.process(*files)
|
39
|
+
flay.analyze
|
40
|
+
end
|
41
|
+
end
|
42
|
+
memoize :flay, freezer: :noop
|
43
|
+
end
|
44
|
+
|
45
|
+
# Expand include and exclude file settings for flay
|
46
|
+
class FileList
|
47
|
+
include Procto.call, Concord.new(:includes, :excludes)
|
48
|
+
|
49
|
+
# Expand includes and filter by excludes
|
50
|
+
#
|
51
|
+
# @return [Set<Pathname>]
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
def call
|
55
|
+
include_set - exclude_set
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Set of excluded files
|
61
|
+
#
|
62
|
+
# @return [Set<Pathname>]
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def exclude_set
|
66
|
+
excludes.flat_map(&Pathname.method(:glob))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Set of included files
|
70
|
+
#
|
71
|
+
# Expanded using flay's file expander which takes into
|
72
|
+
# account flay's plugin support
|
73
|
+
#
|
74
|
+
# @return [Set<Pathname>]
|
75
|
+
#
|
76
|
+
# @api private
|
77
|
+
def include_set
|
78
|
+
Set.new(flay_includes.map(&method(:Pathname)))
|
79
|
+
end
|
80
|
+
|
81
|
+
# Expand includes using flay
|
82
|
+
#
|
83
|
+
# Expanded using flay's file expander which takes into
|
84
|
+
# account flay's plugin support
|
85
|
+
#
|
86
|
+
# @return [Array<String>]
|
87
|
+
#
|
88
|
+
# @api private
|
89
|
+
def flay_includes
|
90
|
+
::Flay.expand_dirs_to_files(includes)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/devtools/project.rb
CHANGED
@@ -1,87 +1,22 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Devtools
|
4
2
|
|
5
3
|
# The project devtools supports
|
6
4
|
class Project
|
5
|
+
include Concord.new(:root)
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# @return [Config::Rubocop]
|
18
|
-
#
|
19
|
-
# @api private
|
20
|
-
attr_reader :rubocop
|
7
|
+
CONFIGS = {
|
8
|
+
devtools: Config::Devtools,
|
9
|
+
flay: Config::Flay,
|
10
|
+
flog: Config::Flog,
|
11
|
+
reek: Config::Reek,
|
12
|
+
mutant: Config::Mutant,
|
13
|
+
rubocop: Config::Rubocop,
|
14
|
+
yardstick: Config::Yardstick
|
15
|
+
}.freeze
|
21
16
|
|
22
|
-
|
23
|
-
#
|
24
|
-
# @return [Config::Flog]
|
25
|
-
#
|
26
|
-
# @api private
|
27
|
-
attr_reader :flog
|
17
|
+
private_constant(*constants(false))
|
28
18
|
|
29
|
-
|
30
|
-
#
|
31
|
-
# @return [Config::Yardstick]
|
32
|
-
#
|
33
|
-
# @api private
|
34
|
-
attr_reader :yardstick
|
35
|
-
|
36
|
-
# The flay configuration
|
37
|
-
#
|
38
|
-
# @return [Config::Flay]
|
39
|
-
#
|
40
|
-
# @api private
|
41
|
-
attr_reader :flay
|
42
|
-
|
43
|
-
# The mutant configuration
|
44
|
-
#
|
45
|
-
# @return [Config::Mutant]
|
46
|
-
#
|
47
|
-
# @api private
|
48
|
-
attr_reader :mutant
|
49
|
-
|
50
|
-
# The devtools configuration
|
51
|
-
#
|
52
|
-
# @return [Config::Devtools]
|
53
|
-
#
|
54
|
-
# @api private
|
55
|
-
attr_reader :devtools
|
56
|
-
|
57
|
-
# Return project root
|
58
|
-
#
|
59
|
-
# @return [Pathname]
|
60
|
-
#
|
61
|
-
# @api private
|
62
|
-
#
|
63
|
-
attr_reader :root
|
64
|
-
|
65
|
-
# The default config path
|
66
|
-
#
|
67
|
-
# @return [Pathname]
|
68
|
-
#
|
69
|
-
# @api private
|
70
|
-
attr_reader :default_config_path
|
71
|
-
|
72
|
-
# The lib directory
|
73
|
-
#
|
74
|
-
# @return [Pathname]
|
75
|
-
#
|
76
|
-
# @api private
|
77
|
-
attr_reader :lib_dir
|
78
|
-
|
79
|
-
# The Ruby file pattern
|
80
|
-
#
|
81
|
-
# @return [Pathname]
|
82
|
-
#
|
83
|
-
# @api private
|
84
|
-
attr_reader :file_pattern
|
19
|
+
attr_reader(*CONFIGS.keys)
|
85
20
|
|
86
21
|
# The spec root
|
87
22
|
#
|
@@ -90,20 +25,6 @@ module Devtools
|
|
90
25
|
# @api private
|
91
26
|
attr_reader :spec_root
|
92
27
|
|
93
|
-
# Return config directory
|
94
|
-
#
|
95
|
-
# @return [Pathname]
|
96
|
-
#
|
97
|
-
# @api private
|
98
|
-
attr_reader :config_dir
|
99
|
-
|
100
|
-
# The unit test timeout
|
101
|
-
#
|
102
|
-
# @return [Numeric]
|
103
|
-
#
|
104
|
-
# @api private
|
105
|
-
attr_reader :unit_test_timeout
|
106
|
-
|
107
28
|
# Initialize object
|
108
29
|
#
|
109
30
|
# @param [Pathname] root
|
@@ -113,12 +34,20 @@ module Devtools
|
|
113
34
|
# @api private
|
114
35
|
#
|
115
36
|
def initialize(root)
|
116
|
-
|
37
|
+
super(root)
|
117
38
|
|
118
39
|
initialize_environment
|
119
40
|
initialize_configs
|
41
|
+
end
|
120
42
|
|
121
|
-
|
43
|
+
# Init rspec
|
44
|
+
#
|
45
|
+
# @return [self]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
def init_rspec
|
49
|
+
Initializer::Rspec.call(self)
|
50
|
+
self
|
122
51
|
end
|
123
52
|
|
124
53
|
private
|
@@ -130,11 +59,7 @@ module Devtools
|
|
130
59
|
# @api private
|
131
60
|
#
|
132
61
|
def initialize_environment
|
133
|
-
@
|
134
|
-
@lib_dir = @root.join(LIB_DIRECTORY_NAME).freeze
|
135
|
-
@spec_root = @root.join(SPEC_DIRECTORY_NAME).freeze
|
136
|
-
@file_pattern = @lib_dir.join(RB_FILE_PATTERN).freeze
|
137
|
-
@config_dir = @default_config_path
|
62
|
+
@spec_root = root.join(SPEC_DIRECTORY_NAME)
|
138
63
|
end
|
139
64
|
|
140
65
|
# Initialize configs
|
@@ -144,13 +69,11 @@ module Devtools
|
|
144
69
|
# @api private
|
145
70
|
#
|
146
71
|
def initialize_configs
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
@mutant = Config::Mutant.new(self)
|
153
|
-
@devtools = Config::Devtools.new(self)
|
72
|
+
config_dir = root.join(DEFAULT_CONFIG_DIR_NAME)
|
73
|
+
|
74
|
+
CONFIGS.each do |name, klass|
|
75
|
+
instance_variable_set(:"@#{name}", klass.new(config_dir))
|
76
|
+
end
|
154
77
|
end
|
155
78
|
|
156
79
|
end # class Project
|
@@ -1,21 +1,11 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Devtools
|
4
2
|
class Project
|
5
3
|
|
6
4
|
# Base class for project initializers
|
7
5
|
class Initializer
|
6
|
+
include AbstractType
|
8
7
|
|
9
|
-
|
10
|
-
protected :project
|
11
|
-
|
12
|
-
def initialize(project)
|
13
|
-
@project = project
|
14
|
-
end
|
15
|
-
|
16
|
-
def call
|
17
|
-
fail NotImplementedError, "#{self.class}##{__method__} must be implemented"
|
18
|
-
end
|
8
|
+
abstract_singleton_method :call
|
19
9
|
end # class Initializer
|
20
10
|
end # class Project
|
21
11
|
end # module Devtools
|
@@ -1,16 +1,18 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Devtools
|
4
2
|
class Project
|
5
3
|
class Initializer
|
6
|
-
|
7
4
|
# Imports all devtools rake tasks into a project
|
8
|
-
class Rake
|
9
|
-
|
10
|
-
extend ::Rake::DSL
|
5
|
+
class Rake < self
|
6
|
+
include AbstractType
|
11
7
|
|
8
|
+
# Initialize rake tasks
|
9
|
+
#
|
10
|
+
# @return [undefined]
|
11
|
+
#
|
12
|
+
# @api rpivate
|
12
13
|
def self.call
|
13
|
-
FileList
|
14
|
+
FileList.glob(RAKE_FILES_GLOB).each(&::Rake.application.method(:add_import))
|
15
|
+
self
|
14
16
|
end
|
15
17
|
|
16
18
|
end # class Rake
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Devtools
|
4
2
|
class Project
|
5
3
|
class Initializer
|
@@ -7,72 +5,33 @@ module Devtools
|
|
7
5
|
# Requires all shared specs in a project's spec_helper
|
8
6
|
# Also installs a configurable unit test timeout
|
9
7
|
class Rspec < self
|
8
|
+
include Concord.new(:project)
|
10
9
|
|
11
|
-
|
12
|
-
Devtools.require_files(directory, SHARED_SPEC_PATTERN)
|
13
|
-
end
|
14
|
-
|
15
|
-
# Initialize RSpec for +project+
|
10
|
+
# Call initializer for project
|
16
11
|
#
|
17
12
|
# @param [Project] project
|
18
|
-
# the project to initialize
|
19
13
|
#
|
20
|
-
# @return [
|
14
|
+
# @return [self]
|
21
15
|
#
|
22
16
|
# @api private
|
23
17
|
def self.call(project)
|
24
|
-
new(project).call
|
18
|
+
new(project).__send__(:call)
|
19
|
+
self
|
25
20
|
end
|
26
21
|
|
27
|
-
|
28
|
-
#
|
29
|
-
# @return [Pathname]
|
30
|
-
#
|
31
|
-
# @api private
|
32
|
-
attr_reader :spec_root
|
33
|
-
private :spec_root
|
22
|
+
private
|
34
23
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# @return [Numeric]
|
38
|
-
#
|
39
|
-
# @api private
|
40
|
-
attr_reader :unit_test_timeout
|
41
|
-
private :unit_test_timeout
|
42
|
-
|
43
|
-
# Initialize a new instance
|
44
|
-
#
|
45
|
-
# @param [Project] project
|
46
|
-
# the project to initialize
|
47
|
-
#
|
48
|
-
# @param [Numeric] unit_test_timeout
|
49
|
-
# the maximum time a single unit test can take
|
50
|
-
#
|
51
|
-
# @return [undefined]
|
52
|
-
#
|
53
|
-
# @api private
|
54
|
-
def initialize(project)
|
55
|
-
super
|
56
|
-
@spec_root = project.spec_root
|
57
|
-
@unit_test_timeout = project.unit_test_timeout
|
58
|
-
end
|
59
|
-
|
60
|
-
# Setup RSpec for {#project}
|
24
|
+
# Setup RSpec for project
|
61
25
|
#
|
62
26
|
# @return [self]
|
63
27
|
#
|
64
28
|
# @api private
|
65
29
|
def call
|
66
|
-
require 'rspec'
|
67
|
-
require 'rspec/its'
|
68
30
|
require_shared_spec_files
|
69
|
-
enable_unit_test_timeout
|
70
|
-
self
|
31
|
+
enable_unit_test_timeout
|
71
32
|
end
|
72
33
|
|
73
|
-
|
74
|
-
|
75
|
-
# Timeout unit tests that take longer than 1/10th of a second
|
34
|
+
# Timeout unit tests that take longer than configured amount of time
|
76
35
|
#
|
77
36
|
# @param [Numeric] timeout
|
78
37
|
#
|
@@ -84,19 +43,33 @@ module Devtools
|
|
84
43
|
# @api private
|
85
44
|
#
|
86
45
|
def enable_unit_test_timeout
|
87
|
-
timeout = unit_test_timeout
|
88
|
-
RSpec.configuration.around
|
46
|
+
timeout = project.devtools.unit_test_timeout
|
47
|
+
RSpec.configuration.around(file_path: UNIT_TEST_PATH_REGEXP) do |example|
|
89
48
|
Timeout.timeout(timeout, &example)
|
90
49
|
end
|
91
50
|
end
|
92
51
|
|
52
|
+
# Trigger the require of shared spec files
|
53
|
+
#
|
54
|
+
# @return [undefined]
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
#
|
93
58
|
def require_shared_spec_files
|
94
59
|
require_files(SHARED_SPEC_PATH)
|
95
|
-
require_files(spec_root)
|
60
|
+
require_files(project.spec_root)
|
96
61
|
end
|
97
62
|
|
98
|
-
|
99
|
-
|
63
|
+
# Require files with pattern
|
64
|
+
#
|
65
|
+
# @param [Pathname] dir
|
66
|
+
# the directory containing the files to require
|
67
|
+
#
|
68
|
+
# @return [self]
|
69
|
+
#
|
70
|
+
# @api private
|
71
|
+
def require_files(dir)
|
72
|
+
Dir.glob(dir.join(SHARED_SPEC_PATTERN)).each(&Kernel.method(:require))
|
100
73
|
end
|
101
74
|
|
102
75
|
end # class Rspec
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Devtools
|
2
|
+
module Rake
|
3
|
+
# Flay metric runner
|
4
|
+
class Flay
|
5
|
+
include Anima.new(:threshold, :total_score, :lib_dirs, :excludes),
|
6
|
+
Procto.call(:verify),
|
7
|
+
Adamantium
|
8
|
+
|
9
|
+
BELOW_THRESHOLD = 'Adjust flay threshold down to %d'.freeze
|
10
|
+
TOTAL_MISMATCH = 'Flay total is now %d, but expected %d'.freeze
|
11
|
+
ABOVE_THRESHOLD = '%d chunks have a duplicate mass > %d'.freeze
|
12
|
+
|
13
|
+
# Verify code specified by `files` does not violate flay expectations
|
14
|
+
#
|
15
|
+
# @raise [SystemExit] if a violation is found
|
16
|
+
# @return [undefined] otherwise
|
17
|
+
#
|
18
|
+
# rubocop:disable MethodLength
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
def verify
|
22
|
+
# Run flay first to ensure the max mass matches the threshold
|
23
|
+
Devtools.notify_metric_violation(
|
24
|
+
BELOW_THRESHOLD % largest_mass
|
25
|
+
) if below_threshold?
|
26
|
+
|
27
|
+
Devtools.notify_metric_violation(
|
28
|
+
TOTAL_MISMATCH % [total_mass, total_score]
|
29
|
+
) if total_mismatch?
|
30
|
+
|
31
|
+
# Run flay a second time with the threshold set
|
32
|
+
return unless above_threshold?
|
33
|
+
|
34
|
+
restricted_flay_scale.flay_report
|
35
|
+
Devtools.notify_metric_violation(
|
36
|
+
ABOVE_THRESHOLD % [restricted_mass_size, threshold]
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# List of files flay will analyze
|
43
|
+
#
|
44
|
+
# @return [Set<Pathname>]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
def files
|
48
|
+
Devtools::Flay::FileList.call(lib_dirs, excludes)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Is there mass duplication which exceeds specified `threshold`
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
def above_threshold?
|
57
|
+
restricted_mass_size.nonzero?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Is the specified `threshold` greater than the largest flay mass
|
61
|
+
#
|
62
|
+
# @return [Boolean]
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def below_threshold?
|
66
|
+
threshold > largest_mass
|
67
|
+
end
|
68
|
+
|
69
|
+
# Is the expected mass total different from the actual mass total
|
70
|
+
#
|
71
|
+
# @return [Boolean]
|
72
|
+
#
|
73
|
+
# @api private
|
74
|
+
def total_mismatch?
|
75
|
+
!total_mass.equal?(total_score)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Size of mass measured by `Flay::Scale` and filtered by `threshold`
|
79
|
+
#
|
80
|
+
# @return [Integer]
|
81
|
+
#
|
82
|
+
# @api private
|
83
|
+
def restricted_mass_size
|
84
|
+
restricted_flay_scale.measure.size
|
85
|
+
end
|
86
|
+
|
87
|
+
# Sum of all flay mass
|
88
|
+
#
|
89
|
+
# @return [Integer]
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
def total_mass
|
93
|
+
flay_masses.reduce(:+).to_i
|
94
|
+
end
|
95
|
+
|
96
|
+
# Largest flay mass found
|
97
|
+
#
|
98
|
+
# @return [Integer]
|
99
|
+
#
|
100
|
+
# @api private
|
101
|
+
def largest_mass
|
102
|
+
flay_masses.max.to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
# Flay scale which only measures mass above `threshold`
|
106
|
+
#
|
107
|
+
# @return [Flay::Scale]
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
def restricted_flay_scale
|
111
|
+
Devtools::Flay::Scale.new(minimum_mass: threshold.succ, files: files)
|
112
|
+
end
|
113
|
+
memoize :restricted_flay_scale
|
114
|
+
|
115
|
+
# All flay masses found in `files`
|
116
|
+
#
|
117
|
+
# @return [Array<Rational>]
|
118
|
+
#
|
119
|
+
# @api private
|
120
|
+
def flay_masses
|
121
|
+
Devtools::Flay::Scale.call(minimum_mass: 0, files: files)
|
122
|
+
end
|
123
|
+
memoize :flay_masses
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|