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