devtools 0.1.0

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