devtools 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|