devtools 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/Gemfile +0 -2
  4. data/README.md +2 -2
  5. data/circle.yml +1 -1
  6. data/config/devtools.yml +2 -0
  7. data/config/flay.yml +2 -2
  8. data/config/flog.yml +1 -1
  9. data/config/mutant.yml +2 -0
  10. data/config/reek.yml +7 -12
  11. data/devtools.gemspec +20 -18
  12. data/lib/devtools.rb +34 -83
  13. data/lib/devtools/config.rb +76 -55
  14. data/lib/devtools/flay.rb +94 -0
  15. data/lib/devtools/project.rb +28 -105
  16. data/lib/devtools/project/initializer.rb +2 -12
  17. data/lib/devtools/project/initializer/rake.rb +9 -7
  18. data/lib/devtools/project/initializer/rspec.rb +28 -55
  19. data/lib/devtools/rake/flay.rb +126 -0
  20. data/lib/devtools/spec_helper.rb +1 -3
  21. data/shared/spec/shared/abstract_type_behavior.rb +0 -2
  22. data/shared/spec/shared/command_method_behavior.rb +0 -2
  23. data/shared/spec/shared/each_method_behaviour.rb +0 -2
  24. data/shared/spec/shared/hash_method_behavior.rb +0 -2
  25. data/shared/spec/shared/idempotent_method_behavior.rb +0 -2
  26. data/shared/spec/support/ice_nine_config.rb +0 -2
  27. data/spec/integration/devtools/rake/flay/verify_spec.rb +164 -0
  28. data/spec/spec_helper.rb +2 -8
  29. data/spec/unit/devtools/config/yardstick_spec.rb +17 -0
  30. data/spec/unit/devtools/config_spec.rb +78 -0
  31. data/spec/unit/devtools/flay/file_list/call_spec.rb +19 -0
  32. data/spec/unit/devtools/flay/scale/flay_report_spec.rb +17 -0
  33. data/spec/unit/devtools/flay/scale/measure_spec.rb +43 -0
  34. data/spec/unit/devtools/project/initializer/rake_spec.rb +21 -0
  35. data/spec/unit/devtools/project/initializer/rspec_spec.rb +52 -0
  36. data/spec/unit/devtools/project_spec.rb +34 -0
  37. data/spec/unit/devtools_spec.rb +14 -0
  38. data/tasks/metrics/ci.rake +1 -3
  39. data/tasks/metrics/coverage.rake +0 -2
  40. data/tasks/metrics/flay.rake +6 -33
  41. data/tasks/metrics/flog.rake +0 -2
  42. data/tasks/metrics/mutant.rake +31 -39
  43. data/tasks/metrics/reek.rake +0 -2
  44. data/tasks/metrics/rubocop.rake +0 -2
  45. data/tasks/metrics/yardstick.rake +0 -14
  46. data/tasks/spec.rake +0 -2
  47. data/tasks/yard.rake +0 -2
  48. metadata +96 -31
  49. data/.travis.yml +0 -15
  50. data/TODO +0 -0
  51. data/bin/devtools +0 -18
  52. data/default/config/devtools.yml +0 -2
  53. data/default/config/flay.yml +0 -3
  54. data/default/config/flog.yml +0 -2
  55. data/default/config/mutant.yml +0 -3
  56. data/default/config/reek.yml +0 -103
  57. data/default/config/rubocop.yml +0 -91
  58. data/default/config/yardstick.yml +0 -2
  59. data/lib/devtools/platform.rb +0 -118
  60. data/lib/devtools/site.rb +0 -41
  61. 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
@@ -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
- # The reek configuration
9
- #
10
- # @return [Config::Reek]
11
- #
12
- # @api private
13
- attr_reader :reek
14
-
15
- # The rubocop configuration
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
- # The flog configuration
23
- #
24
- # @return [Config::Flog]
25
- #
26
- # @api private
27
- attr_reader :flog
17
+ private_constant(*constants(false))
28
18
 
29
- # The yardstick configuration
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
- @root = root
37
+ super(root)
117
38
 
118
39
  initialize_environment
119
40
  initialize_configs
41
+ end
120
42
 
121
- @unit_test_timeout = @devtools.unit_test_timeout
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
- @default_config_path = @root.join(DEFAULT_CONFIG_DIR_NAME).freeze
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
- @reek = Config::Reek.new(self)
148
- @rubocop = Config::Rubocop.new(self)
149
- @flog = Config::Flog.new(self)
150
- @yardstick = Config::Yardstick.new(self)
151
- @flay = Config::Flay.new(self)
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
- attr_reader :project
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[RAKE_FILES_GLOB].each(&method(:import))
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
- def self.require_files(directory)
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 [Rspec]
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
- # The spec root
28
- #
29
- # @return [Pathname]
30
- #
31
- # @api private
32
- attr_reader :spec_root
33
- private :spec_root
22
+ private
34
23
 
35
- # The unit test timeout
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 unless Devtools.jit?
70
- self
31
+ enable_unit_test_timeout
71
32
  end
72
33
 
73
- private
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 # support the closure
88
- RSpec.configuration.around file_path: UNIT_TEST_PATH_REGEXP do |example|
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
- def require_files(directory)
99
- self.class.require_files(directory)
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