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.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rspec +5 -0
- data/.rubocop.yml +5 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +15 -0
- data/Gemfile +5 -0
- data/LICENSE +20 -0
- data/README.md +47 -0
- data/Rakefile +5 -0
- data/TODO +0 -0
- data/bin/devtools +18 -0
- data/circle.yml +7 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +2 -0
- data/config/reek.yml +107 -0
- data/config/rubocop.yml +117 -0
- data/config/yardstick.yml +2 -0
- data/default/config/devtools.yml +2 -0
- data/default/config/flay.yml +3 -0
- data/default/config/flog.yml +2 -0
- data/default/config/mutant.yml +3 -0
- data/default/config/reek.yml +103 -0
- data/default/config/rubocop.yml +91 -0
- data/default/config/yardstick.yml +2 -0
- data/devtools.gemspec +33 -0
- data/lib/devtools.rb +143 -0
- data/lib/devtools/config.rb +158 -0
- data/lib/devtools/platform.rb +118 -0
- data/lib/devtools/project.rb +157 -0
- data/lib/devtools/project/initializer.rb +21 -0
- data/lib/devtools/project/initializer/rake.rb +19 -0
- data/lib/devtools/project/initializer/rspec.rb +105 -0
- data/lib/devtools/site.rb +41 -0
- data/lib/devtools/site/initializer.rb +57 -0
- data/lib/devtools/spec_helper.rb +5 -0
- data/shared/spec/shared/abstract_type_behavior.rb +18 -0
- data/shared/spec/shared/command_method_behavior.rb +7 -0
- data/shared/spec/shared/each_method_behaviour.rb +15 -0
- data/shared/spec/shared/hash_method_behavior.rb +9 -0
- data/shared/spec/shared/idempotent_method_behavior.rb +12 -0
- data/shared/spec/support/ice_nine_config.rb +14 -0
- data/spec/spec_helper.rb +31 -0
- data/tasks/metrics/ci.rake +18 -0
- data/tasks/metrics/coverage.rake +13 -0
- data/tasks/metrics/flay.rake +48 -0
- data/tasks/metrics/flog.rake +40 -0
- data/tasks/metrics/mutant.rake +50 -0
- data/tasks/metrics/reek.rake +7 -0
- data/tasks/metrics/rubocop.rake +15 -0
- data/tasks/metrics/yardstick.rake +26 -0
- data/tasks/spec.rake +36 -0
- data/tasks/yard.rake +11 -0
- 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,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,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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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,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,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
|
data/tasks/spec.rake
ADDED
@@ -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
|