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
@@ -1,48 +1,67 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Devtools
4
-
5
4
  # Abstract base class of tool configuration
6
5
  class Config
6
+ include Adamantium::Flat, AbstractType, Concord.new(:config_dir)
7
+
7
8
  # Represent no configuration
8
9
  DEFAULT_CONFIG = {}.freeze
9
10
 
11
+ # Simple named type check representation
12
+ class TypeCheck
13
+ # Type check against expected class
14
+ include Concord.new(:name, :allowed_classes)
15
+
16
+ ERROR_FORMAT = '%<name>s: Got instance of %<got>s expected %<allowed>s'
17
+ CLASS_DELIM = ','
18
+
19
+ # Check value for instance of expected class
20
+ #
21
+ # @param [Object] value
22
+ #
23
+ # @return [Object]
24
+ def call(value)
25
+ klass = value.class
26
+ format_values = {
27
+ name: name,
28
+ got: klass,
29
+ allowed: allowed_classes.join(CLASS_DELIM)
30
+ }
31
+
32
+ unless allowed_classes.any?(&klass.method(:equal?))
33
+ fail TypeError, format(ERROR_FORMAT, format_values)
34
+ end
35
+
36
+ value
37
+ end
38
+ end # TypeCheck
39
+
40
+ private_constant(*constants(false))
41
+
42
+ # Error raised on type errors
43
+ TypeError = Class.new(RuntimeError)
44
+
10
45
  # Declare an attribute
11
46
  #
12
47
  # @param [Symbol] name
13
- #
14
- # @yieldreturn [Object]
15
- # the default value to use
48
+ # @param [Array<Class>] classes
16
49
  #
17
50
  # @api private
18
51
  #
19
52
  # @return [self]
20
53
  #
21
- def self.attribute(name, *default)
54
+ def self.attribute(name, classes, **options)
55
+ default = [options.fetch(:default)] if options.key?(:default)
56
+ type_check = TypeCheck.new(name, classes)
57
+ key = name.to_s
58
+
22
59
  define_method(name) do
23
- raw.fetch(name.to_s, *default)
60
+ type_check.call(raw.fetch(key, *default))
24
61
  end
25
62
  end
26
63
  private_class_method :attribute
27
64
 
28
- # Return project
29
- #
30
- # @return [Project]
31
- #
32
- # @api private
33
- #
34
- attr_reader :project
35
-
36
- # Initialize object
37
- #
38
- # @return [Project]
39
- #
40
- # @api private
41
- #
42
- def initialize(project)
43
- @project = project
44
- end
45
-
46
65
  # Return config path
47
66
  #
48
67
  # @return [String]
@@ -50,8 +69,9 @@ module Devtools
50
69
  # @api private
51
70
  #
52
71
  def config_file
53
- @config_file ||= project.config_dir.join(self.class::FILE).freeze
72
+ config_dir.join(self.class::FILE)
54
73
  end
74
+ memoize :config_file
55
75
 
56
76
  private
57
77
 
@@ -62,8 +82,9 @@ module Devtools
62
82
  # @api private
63
83
  #
64
84
  def raw
65
- @raw ||= yaml_config || DEFAULT_CONFIG
85
+ yaml_config || DEFAULT_CONFIG
66
86
  end
87
+ memoize :raw
67
88
 
68
89
  # Return the raw config data from a yaml file
69
90
  #
@@ -75,33 +96,35 @@ module Devtools
75
96
  # @api private
76
97
  #
77
98
  def yaml_config
78
- config_file = self.config_file
79
- YAML.load_file(config_file).freeze if config_file.file?
99
+ IceNine.deep_freeze(YAML.load_file(config_file)) if config_file.file?
80
100
  end
81
101
 
82
102
  # Rubocop configuration
83
103
  class Rubocop < self
84
- FILE = 'rubocop.yml'.freeze
85
- end
104
+ FILE = 'rubocop.yml'
105
+ end # Rubocop
86
106
 
87
107
  # Reek configuration
88
108
  class Reek < self
89
- FILE = 'reek.yml'.freeze
90
- end
109
+ FILE = 'reek.yml'
110
+ end # Reek
91
111
 
92
112
  # Flay configuration
113
+ #
93
114
  class Flay < self
94
- FILE = 'flay.yml'.freeze
115
+ FILE = 'flay.yml'
95
116
  DEFAULT_LIB_DIRS = %w[lib].freeze
117
+ DEFAULT_EXCLUDES = %w[].freeze
96
118
 
97
- attribute :total_score
98
- attribute :threshold
99
- attribute :lib_dirs, DEFAULT_LIB_DIRS
100
- end
119
+ attribute :total_score, [0.class]
120
+ attribute :threshold, [0.class]
121
+ attribute :lib_dirs, [Array], default: DEFAULT_LIB_DIRS
122
+ attribute :excludes, [Array], default: DEFAULT_EXCLUDES
123
+ end # Flay
101
124
 
102
125
  # Yardstick configuration
103
126
  class Yardstick < self
104
- FILE = 'yardstick.yml'.freeze
127
+ FILE = 'yardstick.yml'
105
128
  OPTIONS = %w[
106
129
  threshold
107
130
  rules
@@ -120,39 +143,24 @@ module Devtools
120
143
  hash[name] = raw.fetch(name, nil)
121
144
  end
122
145
  end
123
- end
146
+ end # Yardstick
124
147
 
125
148
  # Flog configuration
126
149
  class Flog < self
127
- FILE = 'flog.yml'.freeze
128
- DEFAULT_LIB_DIRS = %w[lib].freeze
129
-
130
- attribute :total_score
131
- attribute :threshold
132
- attribute :lib_dirs, DEFAULT_LIB_DIRS
133
- end
150
+ FILE = 'flog.yml'
151
+ DEFAULT_LIB_DIRS = %w[lib].freeze
134
152
 
135
- # Mutant configuration
136
- class Mutant < self
137
- FILE = 'mutant.yml'.freeze
138
- DEFAULT_NAME = ''.freeze
139
- DEFAULT_STRATEGY = 'rspec'.freeze
140
-
141
- attribute :name, DEFAULT_NAME
142
- attribute :strategy, DEFAULT_STRATEGY
143
- attribute :zombify, false
144
- attribute :since, nil
145
- attribute :ignore_subjects, []
146
- attribute :expect_coverage, 1
147
- attribute :namespace
148
- end
153
+ attribute :total_score, [Float]
154
+ attribute :threshold, [Float]
155
+ attribute :lib_dirs, [Array], default: DEFAULT_LIB_DIRS
156
+ end # Flog
149
157
 
150
158
  # Devtools configuration
151
159
  class Devtools < self
152
- FILE = 'devtools.yml'.freeze
160
+ FILE = 'devtools.yml'
153
161
  DEFAULT_UNIT_TEST_TIMEOUT = 0.1 # 100ms
154
162
 
155
- attribute :unit_test_timeout, DEFAULT_UNIT_TEST_TIMEOUT
156
- end
157
- end
158
- end
163
+ attribute :unit_test_timeout, [Float], default: DEFAULT_UNIT_TEST_TIMEOUT
164
+ end # Devtools
165
+ end # Config
166
+ end # Devtools
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devtools
4
+ module Flay
5
+ # Measure flay mass relative to size of duplicated sexps
6
+ class Scale
7
+ include Adamantium
8
+ include Anima.new(:minimum_mass, :files)
9
+ include Procto.call(:measure)
10
+
11
+ # Measure duplication mass
12
+ #
13
+ # @return [Array<Rational>]
14
+ #
15
+ # @api private
16
+ def measure
17
+ flay.masses.map do |hash, mass|
18
+ Rational(mass, flay.hashes.fetch(hash).size)
19
+ end
20
+ end
21
+
22
+ # Report flay output
23
+ #
24
+ # @return [undefined]
25
+ #
26
+ # @api private
27
+ def flay_report
28
+ flay.report
29
+ end
30
+
31
+ private
32
+
33
+ # Memoized flay instance
34
+ #
35
+ # @return [Flay]
36
+ #
37
+ # @api private
38
+ def flay
39
+ ::Flay.new(mass: minimum_mass).tap do |flay|
40
+ flay.process(*files)
41
+ flay.analyze
42
+ end
43
+ end
44
+ memoize :flay, freezer: :noop
45
+ end
46
+
47
+ # Expand include and exclude file settings for flay
48
+ class FileList
49
+ include Procto.call, Concord.new(:includes, :excludes)
50
+
51
+ GLOB = '**/*.{rb,erb}'
52
+
53
+ # Expand includes and filter by excludes
54
+ #
55
+ # @return [Set<Pathname>]
56
+ #
57
+ # @api private
58
+ def call
59
+ include_set - exclude_set
60
+ end
61
+
62
+ private
63
+
64
+ # Set of excluded files
65
+ #
66
+ # @return [Set<Pathname>]
67
+ #
68
+ # @api private
69
+ def exclude_set
70
+ excludes.flat_map(&Pathname.method(:glob))
71
+ end
72
+
73
+ # Set of included files
74
+ #
75
+ # Expanded using flay's file expander which takes into
76
+ # account flay's plugin support
77
+ #
78
+ # @return [Set<Pathname>]
79
+ #
80
+ # @api private
81
+ def include_set
82
+ Set.new(flay_includes.map(&method(:Pathname)))
83
+ end
84
+
85
+ # Expand includes using flay
86
+ #
87
+ # Expanded using flay's file expander which takes into
88
+ # account flay's plugin support
89
+ #
90
+ # @return [Array<String>]
91
+ #
92
+ # @api private
93
+ def flay_includes
94
+ PathExpander.new(includes.dup, GLOB).process
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,16 +1,20 @@
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
15
+ .glob(RAKE_FILES_GLOB)
16
+ .each(&::Rake.application.method(:add_import))
17
+ self
14
18
  end
15
19
 
16
20
  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,35 @@ 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
48
+ .configuration
49
+ .around(file_path: UNIT_TEST_PATH_REGEXP) do |example|
89
50
  Timeout.timeout(timeout, &example)
90
51
  end
91
52
  end
92
53
 
54
+ # Trigger the require of shared spec files
55
+ #
56
+ # @return [undefined]
57
+ #
58
+ # @api private
59
+ #
93
60
  def require_shared_spec_files
94
61
  require_files(SHARED_SPEC_PATH)
95
- require_files(spec_root)
62
+ require_files(project.spec_root)
96
63
  end
97
64
 
98
- def require_files(directory)
99
- self.class.require_files(directory)
65
+ # Require files with pattern
66
+ #
67
+ # @param [Pathname] dir
68
+ # the directory containing the files to require
69
+ #
70
+ # @return [self]
71
+ #
72
+ # @api private
73
+ def require_files(dir)
74
+ Dir.glob(dir.join(SHARED_SPEC_PATTERN)).each(&Kernel.method(:require))
100
75
  end
101
76
 
102
77
  end # class Rspec
@@ -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,87 +1,21 @@
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
+ rubocop: Config::Rubocop,
13
+ yardstick: Config::Yardstick
14
+ }.freeze
21
15
 
22
- # The flog configuration
23
- #
24
- # @return [Config::Flog]
25
- #
26
- # @api private
27
- attr_reader :flog
16
+ private_constant(*constants(false))
28
17
 
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
18
+ attr_reader(*CONFIGS.keys)
85
19
 
86
20
  # The spec root
87
21
  #
@@ -90,20 +24,6 @@ module Devtools
90
24
  # @api private
91
25
  attr_reader :spec_root
92
26
 
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
27
  # Initialize object
108
28
  #
109
29
  # @param [Pathname] root
@@ -113,12 +33,20 @@ module Devtools
113
33
  # @api private
114
34
  #
115
35
  def initialize(root)
116
- @root = root
36
+ super(root)
117
37
 
118
38
  initialize_environment
119
39
  initialize_configs
40
+ end
120
41
 
121
- @unit_test_timeout = @devtools.unit_test_timeout
42
+ # Init rspec
43
+ #
44
+ # @return [self]
45
+ #
46
+ # @api private
47
+ def init_rspec
48
+ Initializer::Rspec.call(self)
49
+ self
122
50
  end
123
51
 
124
52
  private
@@ -130,11 +58,7 @@ module Devtools
130
58
  # @api private
131
59
  #
132
60
  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
61
+ @spec_root = root.join(SPEC_DIRECTORY_NAME)
138
62
  end
139
63
 
140
64
  # Initialize configs
@@ -144,13 +68,11 @@ module Devtools
144
68
  # @api private
145
69
  #
146
70
  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)
71
+ config_dir = root.join(DEFAULT_CONFIG_DIR_NAME)
72
+
73
+ CONFIGS.each do |name, klass|
74
+ instance_variable_set(:"@#{name}", klass.new(config_dir))
75
+ end
154
76
  end
155
77
 
156
78
  end # class Project