devtools 0.1.0

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +5 -0
  4. data/.rubocop.yml +5 -0
  5. data/.ruby-gemset +1 -0
  6. data/.travis.yml +15 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +20 -0
  9. data/README.md +47 -0
  10. data/Rakefile +5 -0
  11. data/TODO +0 -0
  12. data/bin/devtools +18 -0
  13. data/circle.yml +7 -0
  14. data/config/flay.yml +3 -0
  15. data/config/flog.yml +2 -0
  16. data/config/mutant.yml +2 -0
  17. data/config/reek.yml +107 -0
  18. data/config/rubocop.yml +117 -0
  19. data/config/yardstick.yml +2 -0
  20. data/default/config/devtools.yml +2 -0
  21. data/default/config/flay.yml +3 -0
  22. data/default/config/flog.yml +2 -0
  23. data/default/config/mutant.yml +3 -0
  24. data/default/config/reek.yml +103 -0
  25. data/default/config/rubocop.yml +91 -0
  26. data/default/config/yardstick.yml +2 -0
  27. data/devtools.gemspec +33 -0
  28. data/lib/devtools.rb +143 -0
  29. data/lib/devtools/config.rb +158 -0
  30. data/lib/devtools/platform.rb +118 -0
  31. data/lib/devtools/project.rb +157 -0
  32. data/lib/devtools/project/initializer.rb +21 -0
  33. data/lib/devtools/project/initializer/rake.rb +19 -0
  34. data/lib/devtools/project/initializer/rspec.rb +105 -0
  35. data/lib/devtools/site.rb +41 -0
  36. data/lib/devtools/site/initializer.rb +57 -0
  37. data/lib/devtools/spec_helper.rb +5 -0
  38. data/shared/spec/shared/abstract_type_behavior.rb +18 -0
  39. data/shared/spec/shared/command_method_behavior.rb +7 -0
  40. data/shared/spec/shared/each_method_behaviour.rb +15 -0
  41. data/shared/spec/shared/hash_method_behavior.rb +9 -0
  42. data/shared/spec/shared/idempotent_method_behavior.rb +12 -0
  43. data/shared/spec/support/ice_nine_config.rb +14 -0
  44. data/spec/spec_helper.rb +31 -0
  45. data/tasks/metrics/ci.rake +18 -0
  46. data/tasks/metrics/coverage.rake +13 -0
  47. data/tasks/metrics/flay.rake +48 -0
  48. data/tasks/metrics/flog.rake +40 -0
  49. data/tasks/metrics/mutant.rake +50 -0
  50. data/tasks/metrics/reek.rake +7 -0
  51. data/tasks/metrics/rubocop.rake +15 -0
  52. data/tasks/metrics/yardstick.rake +26 -0
  53. data/tasks/spec.rake +36 -0
  54. data/tasks/yard.rake +11 -0
  55. metadata +284 -0
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ module Devtools
4
+ class Site
5
+
6
+ # Supports initializing new projects with a Rakefile
7
+ class Initializer
8
+
9
+ def self.call(root)
10
+ new(root).call
11
+ end
12
+
13
+ attr_reader :site
14
+
15
+ attr_reader :root
16
+
17
+ attr_reader :config_dir
18
+
19
+ def initialize(site)
20
+ @site = site
21
+ @root = site.root
22
+ config_dir = @root.join(DEFAULT_CONFIG_DIR_NAME).tap(&:mkpath)
23
+ @config_dir = config_dir.parent
24
+ end
25
+
26
+ # Init devtools using default config
27
+ #
28
+ # @return [undefined]
29
+ #
30
+ # @api public
31
+ def call
32
+ FileUtils.cp_r(DEFAULT_CONFIG_PATH, config_dir)
33
+
34
+ site.sync
35
+ init_rakefile
36
+
37
+ self
38
+ end
39
+
40
+ private
41
+
42
+ # Initialize the Rakefile
43
+ #
44
+ # @return [undefined]
45
+ #
46
+ # @api private
47
+ def init_rakefile
48
+ rakefile = root.join(RAKE_FILE_NAME)
49
+ return if rakefile.file? && rakefile.read.include?(INIT_RAKE_TASKS)
50
+ rakefile.open('a') do |file|
51
+ file << ANNOTATION_WRAPPER % [REQUIRE, INIT_RAKE_TASKS].join("\n")
52
+ end
53
+ end
54
+
55
+ end # class Initializer
56
+ end # class Site
57
+ end # module Devtools
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ require 'devtools'
4
+
5
+ Devtools.init_spec_helper
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an abstract type' do
4
+ context 'called on a subclass' do
5
+ let(:object) { Class.new(described_class) }
6
+
7
+ it { should be_instance_of(object) }
8
+ end
9
+
10
+ context 'called on the class' do
11
+ let(:object) { described_class }
12
+
13
+ specify do
14
+ expect { subject }
15
+ .to raise_error(NotImplementedError, "#{object} is an abstract type")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a command method' do
4
+ it 'returns self' do
5
+ should equal(object)
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an #each method' do
4
+ it_should_behave_like 'a command method'
5
+
6
+ context 'with no block' do
7
+ subject { object.each }
8
+
9
+ it { should be_instance_of(to_enum.class) }
10
+
11
+ it 'yields the expected values' do
12
+ expect(subject.to_a).to eql(object.to_a)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a hash method' do
4
+ it_should_behave_like 'an idempotent method'
5
+
6
+ it 'is a Fixnum' do
7
+ should be_instance_of(Fixnum)
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an idempotent method' do
4
+ it 'is idempotent' do
5
+ first = subject
6
+ fail 'RSpec not configured for threadsafety' unless RSpec.configuration.threadsafe?
7
+ mutex = __memoized.instance_variable_get(:@mutex)
8
+ memoized = __memoized.instance_variable_get(:@memoized)
9
+ mutex.synchronize { memoized.delete(:subject) }
10
+ should equal(first)
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ if defined?(IceNine)
4
+ module IceNine
5
+
6
+ # Freezer namespace
7
+ class Freezer
8
+
9
+ # Rspec freezer
10
+ class RSpec < NoFreeze; end
11
+
12
+ end # Freezer
13
+ end # IceNine
14
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'devtools/spec_helper'
4
+
5
+ if ENV['COVERAGE'] == 'true'
6
+ require 'simplecov'
7
+ require 'coveralls'
8
+
9
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
10
+ SimpleCov::Formatter::HTMLFormatter,
11
+ Coveralls::SimpleCov::Formatter
12
+ ]
13
+
14
+ SimpleCov.start do
15
+ command_name 'spec:unit'
16
+
17
+ add_filter 'config'
18
+ add_filter 'spec'
19
+ add_filter 'vendor'
20
+
21
+ minimum_coverage 100
22
+ end
23
+ end
24
+
25
+ require 'devtools'
26
+
27
+ RSpec.configure do |config|
28
+ config.expect_with :rspec do |expect_with|
29
+ expect_with.syntax = :expect
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ desc 'Run all specs, metrics and mutant'
4
+ task ci: %w[spec ci:metrics metrics:mutant]
5
+
6
+ namespace :ci do
7
+ tasks = %w[
8
+ metrics:coverage
9
+ metrics:yardstick:verify
10
+ metrics:rubocop
11
+ metrics:flog
12
+ metrics:flay
13
+ metrics:reek
14
+ ]
15
+
16
+ desc 'Run metrics (except mutant)'
17
+ task metrics: tasks
18
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :metrics do
4
+ desc 'Measure code coverage'
5
+ task :coverage do
6
+ begin
7
+ original, ENV['COVERAGE'] = ENV['COVERAGE'], 'true'
8
+ Rake::Task['spec:unit'].execute
9
+ ensure
10
+ ENV['COVERAGE'] = original
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :metrics do
4
+ require 'flay'
5
+
6
+ project = Devtools.project
7
+ config = project.flay
8
+
9
+ # Original code by Marty Andrews:
10
+ # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
11
+ desc 'Measure code duplication'
12
+ task :flay do
13
+ threshold = config.threshold
14
+ total_score = config.total_score
15
+ files = Flay.expand_dirs_to_files(config.lib_dirs).sort
16
+
17
+ # Run flay first to ensure the max mass matches the threshold
18
+ flay = Flay.new(fuzzy: false, verbose: false, mass: 0)
19
+ flay.process(*files)
20
+ flay.analyze
21
+
22
+ masses = flay.masses.map do |hash, mass|
23
+ Rational(mass, flay.hashes[hash].size)
24
+ end
25
+
26
+ max = (masses.max || 0).to_i
27
+ unless max >= threshold
28
+ Devtools.notify_metric_violation "Adjust flay threshold down to #{max}"
29
+ end
30
+
31
+ total = masses.inject(:+).to_i
32
+ unless total == total_score
33
+ Devtools.notify_metric_violation "Flay total is now #{total}, but expected #{total_score}"
34
+ end
35
+
36
+ # Run flay a second time with the threshold set
37
+ flay = Flay.new(fuzzy: false, verbose: false, mass: threshold.succ)
38
+ flay.process(*files)
39
+ flay.analyze
40
+
41
+ mass_size = flay.masses.size
42
+
43
+ if mass_size.nonzero?
44
+ flay.report
45
+ Devtools.notify_metric_violation "#{mass_size} chunks have a duplicate mass > #{threshold}"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :metrics do
4
+ require 'flog'
5
+ require 'flog_cli'
6
+
7
+ project = Devtools.project
8
+ config = project.flog
9
+
10
+ # Original code by Marty Andrews:
11
+ # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
12
+ desc 'Measure code complexity'
13
+ task :flog do
14
+ threshold = config.threshold.to_f.round(1)
15
+ flog = Flog.new
16
+ flog.flog(*FlogCLI.expand_dirs_to_files(config.lib_dirs))
17
+
18
+ totals = flog.totals.select { |name, score| name[-5, 5] != '#none' }
19
+ .map { |name, score| [name, score.round(1)] }
20
+ .sort_by { |name, score| score }
21
+
22
+ if totals.any?
23
+ max = totals.last[1]
24
+ unless max >= threshold
25
+ Devtools.notify_metric_violation "Adjust flog score down to #{max}"
26
+ end
27
+ end
28
+
29
+ bad_methods = totals.select { |name, score| score > threshold }
30
+ if bad_methods.any?
31
+ bad_methods.reverse_each do |name, score|
32
+ printf "%8.1f: %s\n", score, name
33
+ end
34
+
35
+ Devtools.notify_metric_violation(
36
+ "#{bad_methods.size} methods have a flog complexity > #{threshold}"
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :metrics do
4
+ config = Devtools.project.mutant
5
+
6
+ if !ENV['DEVTOOLS_SELF']
7
+ desc 'Measure mutation coverage'
8
+ task mutant: :coverage do
9
+ require 'mutant'
10
+
11
+ namespace =
12
+ if config.zombify
13
+ Mutant.zombify
14
+ Zombie::Mutant
15
+ else
16
+ Mutant
17
+ end
18
+
19
+ namespaces = Array(config.namespace).map { |n| "#{n}*" }
20
+
21
+ ignore_subjects = config.ignore_subjects.flat_map do |matcher|
22
+ %W[--ignore #{matcher}]
23
+ end
24
+
25
+ since =
26
+ if config.since
27
+ %W[--since #{config.since}]
28
+ else
29
+ []
30
+ end
31
+
32
+ arguments = %W[
33
+ --include lib
34
+ --require #{config.name}
35
+ --expect-coverage #{config.expect_coverage}
36
+ --use #{config.strategy}
37
+ ].concat(ignore_subjects).concat(namespaces).concat(since)
38
+
39
+ status = namespace::CLI.run(arguments)
40
+ if status.nonzero?
41
+ Devtools.notify_metric_violation 'Mutant task is not successful'
42
+ end
43
+ end
44
+ else
45
+ desc 'Measure mutation coverage'
46
+ task mutant: :coverage do
47
+ $stderr.puts 'Mutant is disabled'
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :metrics do
4
+ task :reek do
5
+ $stderr.puts 'Reek is disabled due defunct AST processing'
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :metrics do
4
+ desc 'Check with code style guide'
5
+ task :rubocop do
6
+ require 'rubocop'
7
+ config = Devtools.project.rubocop
8
+ begin
9
+ exit_status = RuboCop::CLI.new.run(%W[--config #{config.config_file.to_s}])
10
+ fail 'Rubocop not successful' unless exit_status.zero?
11
+ rescue Encoding::CompatibilityError => exception
12
+ Devtools.notify_metric_violation exception.message
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :metrics do
4
+ namespace :yardstick do
5
+ require 'yardstick/rake/measurement'
6
+ require 'yardstick/rake/verify'
7
+
8
+ # Enable the legacy parser for JRuby until ripper is fully supported
9
+ if Devtools.jruby?
10
+ # Remove when https://github.com/lsegal/yard/issues/681 is resolved
11
+ # This code first requires ripper, then removes the constant so
12
+ # that it does not trigger a bug in YARD where if it checks if Ripper
13
+ # is available and assumes other constants are defined, when JRuby's
14
+ # implementation does not yet.
15
+ require 'ripper'
16
+ Object.send(:remove_const, :Ripper)
17
+ YARD::Parser::SourceParser.parser_type = :ruby18
18
+ end
19
+
20
+ options = Devtools.project.yardstick.options
21
+
22
+ Yardstick::Rake::Measurement.new(:measure, options)
23
+
24
+ Yardstick::Rake::Verify.new(:verify, options)
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ # Remove existing same-named tasks
7
+ %w[spec spec:unit spec:integration].each do |task|
8
+ klass = Rake::Task
9
+ klass[task].clear if klass.task_defined?(task)
10
+ end
11
+
12
+ desc 'Run all specs'
13
+ RSpec::Core::RakeTask.new(:spec) do |task|
14
+ task.pattern = 'spec/{unit,integration}/**/*_spec.rb'
15
+ end
16
+
17
+ namespace :spec do
18
+ desc 'Run unit specs'
19
+ RSpec::Core::RakeTask.new(:unit) do |task|
20
+ task.pattern = 'spec/unit/**/*_spec.rb'
21
+ end
22
+
23
+ desc 'Run integration specs'
24
+ RSpec::Core::RakeTask.new(:integration) do |task|
25
+ task.pattern = 'spec/integration/**/*_spec.rb'
26
+ end
27
+ end
28
+ rescue LoadError
29
+ %w[spec spec:unit spec:integration].each do |name|
30
+ task name do
31
+ $stderr.puts "In order to run #{name}, do: gem install rspec"
32
+ end
33
+ end
34
+ end
35
+
36
+ task test: :spec