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.
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