From 8c1fa8dace0c2e4b7d2aefc763e94eba6059f682 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Mon, 23 Jul 2012 16:37:44 +0200 Subject: [PATCH] Add project infrastructure * This infrastructure, especially the rake tasks should be gemified at some point in the future. I copied exactly the same bytes many times in the last month. --- Gemfile | 42 +++++++ Gemfile.lock | 150 +++++++++++++++++++++++++ Guardfile | 18 +++ TODO | 9 ++ config/flay.yml | 3 + config/flog.yml | 2 + config/roodi.yml | 26 +++++ config/site.reek | 91 +++++++++++++++ config/yardstick.yml | 2 + lib/mutant/version.rb | 3 + mutant.gemspec | 20 ++++ spec/spec.opts | 3 + spec/spec_helper.rb | 12 ++ tasks/metrics/ci.rake | 7 ++ tasks/metrics/flay.rake | 47 ++++++++ tasks/metrics/flog.rake | 43 ++++++++ tasks/metrics/heckle.rake | 208 +++++++++++++++++++++++++++++++++++ tasks/metrics/metric_fu.rake | 29 +++++ tasks/metrics/reek.rake | 15 +++ tasks/metrics/roodi.rake | 15 +++ tasks/metrics/yardstick.rake | 23 ++++ tasks/spec.rake | 45 ++++++++ tasks/yard.rake | 9 ++ 23 files changed, 822 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Guardfile create mode 100644 TODO create mode 100644 config/flay.yml create mode 100644 config/flog.yml create mode 100644 config/roodi.yml create mode 100644 config/site.reek create mode 100644 config/yardstick.yml create mode 100644 lib/mutant/version.rb create mode 100644 mutant.gemspec create mode 100644 spec/spec.opts create mode 100644 spec/spec_helper.rb create mode 100644 tasks/metrics/ci.rake create mode 100644 tasks/metrics/flay.rake create mode 100644 tasks/metrics/flog.rake create mode 100644 tasks/metrics/heckle.rake create mode 100644 tasks/metrics/metric_fu.rake create mode 100644 tasks/metrics/reek.rake create mode 100644 tasks/metrics/roodi.rake create mode 100644 tasks/metrics/yardstick.rake create mode 100644 tasks/spec.rake create mode 100644 tasks/yard.rake diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..33a76e1a --- /dev/null +++ b/Gemfile @@ -0,0 +1,42 @@ +# encoding: utf-8 + +source 'https://rubygems.org' + +gemspec + +group :development do + gem 'rake', '~> 0.9.2' + gem 'rspec', '~> 1.3.2' + gem 'yard', '~> 0.8.1' +end + +group :guard do + gem 'guard', '~> 1.1.1' + gem 'guard-bundler', '~> 0.1.3' + gem 'guard-rspec', '~> 0.7.3' +end + +group :metrics do + gem 'flay', '~> 1.4.2' + gem 'flog', '~> 2.5.1' + gem 'reek', '~> 1.2.8', :github => 'dkubb/reek' + gem 'roodi', '~> 2.1.0' + gem 'yardstick', '~> 0.5.0' + gem 'yard-spellcheck', '~> 0.1.5' + + platforms :mri_18 do + gem 'arrayfields', '~> 4.7.4' # for metric_fu + gem 'fattr', '~> 2.2.0' # for metric_fu + gem 'heckle', '~> 1.4.3' + gem 'json', '~> 1.7.3' # for metric_fu rake task + gem 'map', '~> 6.0.1' # for metric_fu + gem 'metric_fu', '~> 2.1.1' + gem 'mspec', '~> 1.5.17' + gem 'rcov', '~> 1.0.0' + gem 'ruby2ruby', '= 1.2.2' + end + + platforms :rbx do + gem 'pelusa', '~> 0.2.1' + end +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..184dc5fb --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,150 @@ +GIT + remote: git://github.com/dkubb/reek.git + revision: b85adad65e992e194023f6346f3e72c3b46b4c56 + specs: + reek (1.2.8) + ruby2ruby (~> 1.2) + ruby_parser (~> 2.0) + sexp_processor (~> 3.0) + +PATH + remote: . + specs: + mutant (0.0.1) + backports (~> 2.6.1) + +GEM + remote: https://rubygems.org/ + specs: + ParseTree (3.0.8) + RubyInline (>= 3.7.0) + sexp_processor (>= 3.0.0) + RubyInline (3.11.3) + ZenTest (~> 4.3) + Saikuro (1.1.0) + ZenTest (4.8.1) + activesupport (3.2.6) + i18n (~> 0.6) + multi_json (~> 1.0) + arrayfields (4.7.4) + awesome_print (1.0.2) + backports (2.6.2) + chronic (0.3.0) + churn (0.0.13) + chronic (>= 0.2.3) + hirb + json_pure + main + ruby_parser (~> 2.0.4) + sexp_processor (~> 3.0.3) + colored (1.2) + env (0.3.0) + erubis (2.7.0) + fattr (2.2.1) + ffi (1.1.0) + ffi-hunspell (0.2.5) + env (~> 0.2) + ffi (~> 1.0) + flay (1.4.3) + ruby_parser (~> 2.0) + sexp_processor (~> 3.0) + flog (2.5.3) + ruby_parser (~> 2.0) + sexp_processor (~> 3.0) + guard (1.1.1) + listen (>= 0.4.2) + thor (>= 0.14.6) + guard-bundler (0.1.3) + bundler (>= 1.0.0) + guard (>= 0.2.2) + guard-rspec (0.7.3) + guard (>= 0.10.0) + heckle (1.4.3) + ParseTree (>= 2.0.0) + ZenTest (>= 3.5.2) + ruby2ruby (>= 1.1.6) + hirb (0.7.0) + i18n (0.6.0) + json (1.7.3) + json_pure (1.7.3) + listen (0.4.7) + rb-fchange (~> 0.0.5) + rb-fsevent (~> 0.9.1) + rb-inotify (~> 0.8.8) + main (4.7.1) + map (6.0.1) + metric_fu (2.1.1) + Saikuro (>= 1.1.0) + activesupport (>= 2.0.0) + chronic (~> 0.3.0) + churn (>= 0.0.7) + flay (>= 1.2.1) + flog (>= 2.3.0) + rails_best_practices (>= 0.6.4) + rcov (>= 0.8.3.3) + reek (>= 1.2.6) + roodi (>= 2.1.0) + syntax + mspec (1.5.17) + multi_json (1.3.6) + pelusa (0.2.1) + progressbar (0.11.0) + rails_best_practices (1.10.1) + activesupport + awesome_print + colored + erubis + i18n + progressbar + sexp_processor + rake (0.9.2.2) + rb-fchange (0.0.5) + ffi + rb-fsevent (0.9.1) + rb-inotify (0.8.8) + ffi (>= 0.5.0) + rcov (1.0.0) + roodi (2.1.0) + ruby_parser + rspec (1.3.2) + ruby2ruby (1.2.2) + ParseTree (~> 3.0) + ruby_parser (2.0.6) + sexp_processor (~> 3.0) + sexp_processor (3.0.10) + syntax (1.0.0) + thor (0.15.4) + yard (0.8.2.1) + yard-spellcheck (0.1.5) + ffi-hunspell (~> 0.2) + yard (~> 0.6) + yardstick (0.5.0) + yard (~> 0.8.1) + +PLATFORMS + ruby + +DEPENDENCIES + arrayfields (~> 4.7.4) + fattr (~> 2.2.0) + flay (~> 1.4.2) + flog (~> 2.5.1) + guard (~> 1.1.1) + guard-bundler (~> 0.1.3) + guard-rspec (~> 0.7.3) + heckle (~> 1.4.3) + json (~> 1.7.3) + map (~> 6.0.1) + metric_fu (~> 2.1.1) + mspec (~> 1.5.17) + mutant! + pelusa (~> 0.2.1) + rake (~> 0.9.2) + rcov (~> 1.0.0) + reek (~> 1.2.8)! + roodi (~> 2.1.0) + rspec (~> 1.3.2) + ruby2ruby (= 1.2.2) + yard (~> 0.8.1) + yard-spellcheck (~> 0.1.5) + yardstick (~> 0.5.0) diff --git a/Guardfile b/Guardfile new file mode 100644 index 00000000..fa1eb9c3 --- /dev/null +++ b/Guardfile @@ -0,0 +1,18 @@ +# encoding: utf-8 + +guard :bundler do + watch('Gemfile') +end + +guard :rspec do + # run all specs if the spec_helper or supporting files files are modified + watch('spec/spec_helper.rb') { 'spec' } + watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec' } + + # run unit specs if associated lib code is modified + watch(%r{\Alib/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}"] } + watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { 'spec' } + + # run a spec if it is modified + watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z}) +end diff --git a/TODO b/TODO new file mode 100644 index 00000000..353d9b16 --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +* Get a rid of heckle and test mutant with mutant. + This is interesting IMHO mutant should have another entry point + that does not create the ::Mutant namespace. + This will be easy when there is a gem release. + This ::Zombie namespace can be created with dynamically creating a + new library under a differend load path. Also a gem release + could provide this feature. +* Get a rid of rspec-1 (can be done once we do not use heckle anymore) +* Add an infrastructure to whitelist components to heckle. diff --git a/config/flay.yml b/config/flay.yml new file mode 100644 index 00000000..8f0e9d27 --- /dev/null +++ b/config/flay.yml @@ -0,0 +1,3 @@ +--- +threshold: 9 +total_score: 41 diff --git a/config/flog.yml b/config/flog.yml new file mode 100644 index 00000000..46abd913 --- /dev/null +++ b/config/flog.yml @@ -0,0 +1,2 @@ +--- +threshold: 12.4 diff --git a/config/roodi.yml b/config/roodi.yml new file mode 100644 index 00000000..3f1b551a --- /dev/null +++ b/config/roodi.yml @@ -0,0 +1,26 @@ +--- +AbcMetricMethodCheck: + score: 12.1 +AssignmentInConditionalCheck: {} +CaseMissingElseCheck: {} +ClassLineCountCheck: + line_count: 200 +ClassNameCheck: + pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ +ClassVariableCheck: {} +CyclomaticComplexityBlockCheck: + complexity: 2 +CyclomaticComplexityMethodCheck: + complexity: 2 +EmptyRescueBodyCheck: {} +ForLoopCheck: {} +MethodLineCountCheck: + line_count: 9 +MethodNameCheck: + pattern: !ruby/regexp /\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|<<|[+*&|-])\z/ +ModuleLineCountCheck: + line_count: 202 +ModuleNameCheck: + pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ +ParameterNumberCheck: + parameter_count: 2 diff --git a/config/site.reek b/config/site.reek new file mode 100644 index 00000000..595d0cbf --- /dev/null +++ b/config/site.reek @@ -0,0 +1,91 @@ +--- +UncommunicativeParameterName: + accept: [] + exclude: [] + enabled: true + reject: + - !ruby/regexp /^.$/ + - !ruby/regexp /[0-9]$/ + - !ruby/regexp /[A-Z]/ +LargeClass: + max_methods: 10 + exclude: [] + enabled: true + max_instance_variables: 3 +UncommunicativeMethodName: + accept: [] + exclude: [] + enabled: true + reject: + - !ruby/regexp /^[a-z]$/ + - !ruby/regexp /[0-9]$/ + - !ruby/regexp /[A-Z]/ +LongParameterList: + max_params: 2 + exclude: [] + enabled: true + overrides: {} +FeatureEnvy: + exclude: [] + enabled: true +ClassVariable: + exclude: [] + enabled: true +BooleanParameter: + exclude: [] + enabled: true +IrresponsibleModule: + exclude: [] + enabled: true +UncommunicativeModuleName: + accept: [] + exclude: [] + enabled: true + reject: + - !ruby/regexp /^.$/ + - !ruby/regexp /[0-9]$/ +NestedIterators: + ignore_iterators: [] + exclude: [] + enabled: true + max_allowed_nesting: 1 +LongMethod: + max_statements: 6 + exclude: [] + enabled: true +Duplication: + allow_calls: [] + exclude: [] + enabled: true + max_calls: 1 +UtilityFunction: + max_helper_calls: 0 + exclude: [] + enabled: true +Attribute: + exclude: [] + enabled: false +UncommunicativeVariableName: + accept: [] + exclude: [] + enabled: true + reject: + - !ruby/regexp /^.$/ + - !ruby/regexp /[0-9]$/ + - !ruby/regexp /[A-Z]/ +SimulatedPolymorphism: + exclude: [] + enabled: true + max_ifs: 1 +DataClump: + exclude: [] + enabled: true + max_copies: 2 + min_clump_size: 2 +ControlCouple: + exclude: [] + enabled: true +LongYieldList: + max_params: 1 + exclude: [] + enabled: true diff --git a/config/yardstick.yml b/config/yardstick.yml new file mode 100644 index 00000000..a6b63e85 --- /dev/null +++ b/config/yardstick.yml @@ -0,0 +1,2 @@ +--- +threshold: 100 diff --git a/lib/mutant/version.rb b/lib/mutant/version.rb new file mode 100644 index 00000000..2151b434 --- /dev/null +++ b/lib/mutant/version.rb @@ -0,0 +1,3 @@ +module Mutant + VERSION = '0.0.1'.freeze +end diff --git a/mutant.gemspec b/mutant.gemspec new file mode 100644 index 00000000..a5869117 --- /dev/null +++ b/mutant.gemspec @@ -0,0 +1,20 @@ +# -*- encoding: utf-8 -*- + +require File.expand_path('../lib/mutant/version.rb', __FILE__) + +Gem::Specification.new do |gem| + gem.name = 'mutant' + gem.version = Mutant::VERSION.dup + gem.authors = [ 'Markus Schirp' ] + gem.email = [ 'mbj@seonic.net' ] + gem.description = 'Mutation testing for ruby under rubinius' + gem.summary = gem.description + gem.homepage = 'https://github.com/mbj/mutant' + + gem.require_paths = [ 'lib' ] + gem.files = `git ls-files`.split("\n") + gem.test_files = `git ls-files -- spec`.split("\n") + gem.extra_rdoc_files = %w[TODO] + + gem.add_runtime_dependency('backports', '~> 2.6.1') +end diff --git a/spec/spec.opts b/spec/spec.opts new file mode 100644 index 00000000..80d5b322 --- /dev/null +++ b/spec/spec.opts @@ -0,0 +1,3 @@ +--color +--loadby random +--format profile diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..addf90b3 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,12 @@ +# encoding: utf-8 + +require 'mutant' +require 'spec' +require 'spec/autorun' + +# require spec support files and shared behavior +Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each { |f| require f } + +Spec::Runner.configure do |config| + config.extend Spec::ExampleGroupMethods +end diff --git a/tasks/metrics/ci.rake b/tasks/metrics/ci.rake new file mode 100644 index 00000000..e28b262d --- /dev/null +++ b/tasks/metrics/ci.rake @@ -0,0 +1,7 @@ +desc 'Run metrics with Heckle' +task :ci => %w[ ci:metrics heckle ] + +namespace :ci do + desc 'Run metrics' + task :metrics => %w[ verify_measurements flog flay reek roodi metrics:all ] +end diff --git a/tasks/metrics/flay.rake b/tasks/metrics/flay.rake new file mode 100644 index 00000000..24339da2 --- /dev/null +++ b/tasks/metrics/flay.rake @@ -0,0 +1,47 @@ +begin + if RUBY_VERSION == '1.8.7' and !defined?(RUBY_ENGINE) + require 'flay' + require 'yaml' + + config = YAML.load_file(File.expand_path('../../../config/flay.yml', __FILE__)).freeze + threshold = config.fetch('threshold').to_i + total_score = config.fetch('total_score').to_f + files = Flay.expand_dirs_to_files(config.fetch('path', 'lib')) + + # original code by Marty Andrews: + # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html + desc 'Analyze for code duplication' + task :flay do + # run flay once without a threshold to ensure the max mass matches the threshold + flay = Flay.new(:fuzzy => false, :verbose => false, :mass => 0) + flay.process(*files) + + max = flay.masses.map { |hash, mass| mass.to_f / flay.hashes[hash].size }.max + unless max >= threshold + raise "Adjust flay threshold down to #{max}" + end + + total = flay.masses.reduce(0.0) { |total, (hash, mass)| total + (mass.to_f / flay.hashes[hash].size) } + unless total == total_score + raise "Flay total is now #{total}, but expected #{total_score}" + end + + # run flay a second time with the threshold set + flay = Flay.new(:fuzzy => false, :verbose => false, :mass => threshold.succ) + flay.process(*files) + + if flay.masses.any? + flay.report + raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}" + end + end + else + task :flay do + $stderr.puts 'Flay has inconsistend results accros ruby implementations. It is only enabled on 1.8.7, fix and remove guard' + end + end +rescue LoadError + task :flay do + abort 'Flay is not available. In order to run flay, you must: gem install flay' + end +end diff --git a/tasks/metrics/flog.rake b/tasks/metrics/flog.rake new file mode 100644 index 00000000..4f1b69e8 --- /dev/null +++ b/tasks/metrics/flog.rake @@ -0,0 +1,43 @@ +begin + require 'flog' + require 'yaml' + + class Float + def round_to(n) + (self * 10**n).round.to_f * 10**-n + end + end + + config = YAML.load_file(File.expand_path('../../../config/flog.yml', __FILE__)).freeze + threshold = config.fetch('threshold').to_f.round_to(1) + + # original code by Marty Andrews: + # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html + desc 'Analyze for code complexity' + task :flog do + flog = Flog.new + flog.flog Array(config.fetch('path', 'lib')) + + totals = flog.totals.select { |name, score| name[-5, 5] != '#none' }. + map { |name, score| [ name, score.round_to(1) ] }. + sort_by { |name, score| score } + + max = totals.last[1] + unless max >= threshold + raise "Adjust flog score down to #{max}" + end + + bad_methods = totals.select { |name, score| score > threshold } + if bad_methods.any? + bad_methods.reverse_each do |name, score| + puts '%8.1f: %s' % [ score, name ] + end + + raise "#{bad_methods.size} methods have a flog complexity > #{threshold}" + end + end +rescue LoadError + task :flog do + abort 'Flog is not available. In order to run flog, you must: gem install flog' + end +end diff --git a/tasks/metrics/heckle.rake b/tasks/metrics/heckle.rake new file mode 100644 index 00000000..d4875638 --- /dev/null +++ b/tasks/metrics/heckle.rake @@ -0,0 +1,208 @@ +$LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__)) + +# original code by Ashley Moran: +# http://aviewfromafar.net/2007/11/1/rake-task-for-heckling-your-specs + +begin + require 'pathname' + require 'backports' + require 'active_support/inflector' + require 'heckle' + require 'mspec' + require 'mspec/utils/name_map' + + SKIP_METHODS = %w[ blank_slate_method_added ].freeze + + class NameMap + def file_name(method, constant) + map = MAP[method] + name = if map + map[constant] || map[:default] + else + method. + gsub('?','_ques'). + gsub('!','_bang'). + gsub('=','_assign') + end + "#{name}_spec.rb" + end + end + + desc 'Heckle each module and class' + task :heckle => :rcov do + unless Ruby2Ruby::VERSION == '1.2.2' + raise "ruby2ruby version #{Ruby2Ruby::VERSION} may not work properly, 1.2.2 *only* is recommended for use with heckle" + end + + require 'veritas-mongo-adapter' + + root_module_regexp = Regexp.union('Mutant') + + spec_dir = Pathname('spec/unit') + + NameMap::MAP.each do |op, method| + next if method.kind_of?(Hash) + NameMap::MAP[op] = { :default => method } + end + + aliases = Hash.new { |h,mod| h[mod] = Hash.new { |h,method| h[method] = method } } + map = NameMap.new + + heckle_caught_modules = Hash.new { |hash, key| hash[key] = [] } + unhandled_mutations = 0 + + ObjectSpace.each_object(Module) do |mod| + next unless mod.name =~ /\A#{root_module_regexp}(?::|\z)/ + + spec_prefix = spec_dir.join(mod.name.underscore) + + specs = [] + + # get the public class methods + metaclass = class << mod; self end + ancestors = metaclass.ancestors + + spec_class_methods = mod.singleton_methods(false) + + spec_class_methods.reject! do |method| + %w[ yaml_new yaml_tag_subclasses? included nesting constants ].include?(method.to_s) + end + + if mod.ancestors.include?(Singleton) + spec_class_methods.reject! { |method| method.to_s == 'instance' } + end + + # get the protected and private class methods + other_class_methods = metaclass.protected_instance_methods(false) | + metaclass.private_instance_methods(false) + + ancestors.each do |ancestor| + other_class_methods -= ancestor.protected_instance_methods(false) | + ancestor.private_instance_methods(false) + end + + other_class_methods.reject! do |method| + method.to_s == 'allocate' || SKIP_METHODS.include?(method.to_s) + end + + other_class_methods.reject! do |method| + next unless spec_class_methods.any? { |specced| specced.to_s == $1 } + + spec_class_methods << method + end + + # get the instances methods + spec_methods = mod.public_instance_methods(false) + + other_methods = mod.protected_instance_methods(false) | + mod.private_instance_methods(false) + + other_methods.reject! do |method| + next unless spec_methods.any? { |specced| specced.to_s == $1 } + + spec_methods << method + end + + # map the class methods to spec files + spec_class_methods.each do |method| + method = aliases[mod.name][method] + next if SKIP_METHODS.include?(method.to_s) + + spec_file = spec_prefix.join('class_methods').join(map.file_name(method, mod.name)) + + unless spec_file.file? + raise "No spec file #{spec_file} for #{mod}.#{method}" + next + end + + specs << [ ".#{method}", [ spec_file ] ] + end + + # map the instance methods to spec files + spec_methods.each do |method| + method = aliases[mod.name][method] + next if SKIP_METHODS.include?(method.to_s) + + spec_file = spec_prefix.join(map.file_name(method, mod.name)) + + unless spec_file.file? + raise "No spec file #{spec_file} for #{mod}##{method}" + next + end + + specs << [ "##{method}", [ spec_file ] ] + end + + # non-public methods are considered covered if they can be mutated + # and any spec fails for the current or descendant modules + other_methods.each do |method| + descedant_specs = [] + + ObjectSpace.each_object(Module) do |descedant| + next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant + descedant_spec_prefix = spec_dir.join(descedant.name.underscore) + descedant_specs << descedant_spec_prefix + + if method.to_s == 'initialize' + descedant_specs.concat(Pathname.glob(descedant_spec_prefix.join('class_methods/new_spec.rb'))) + end + end + + specs << [ "##{method}", descedant_specs ] + end + + other_class_methods.each do |method| + descedant_specs = [] + + ObjectSpace.each_object(Module) do |descedant| + next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant + descedant_specs << spec_dir.join(descedant.name.underscore).join('class_methods') + end + + specs << [ ".#{method}", descedant_specs ] + end + + specs.sort.each do |(method, spec_files)| + puts "Heckling #{mod}#{method}" + IO.popen("spec #{spec_files.join(' ')} --heckle '#{mod}#{method}'") do |pipe| + while line = pipe.gets + case line = line.chomp + when "The following mutations didn't cause test failures:" + heckle_caught_modules[mod.name] << method + when '+++ mutation' + unhandled_mutations += 1 + end + end + end + end + end + + if unhandled_mutations > 0 + error_message_lines = [ "*************\n" ] + + error_message_lines << "Heckle found #{unhandled_mutations} " \ + "mutation#{"s" unless unhandled_mutations == 1} " \ + "that didn't cause spec violations\n" + + heckle_caught_modules.each do |mod, methods| + error_message_lines << "#{mod} contains the following " \ + 'poorly-specified methods:' + methods.each do |method| + error_message_lines << " - #{method}" + end + error_message_lines << '' + end + + error_message_lines << 'Get your act together and come back ' \ + 'when your specs are doing their job!' + + raise error_message_lines.join("\n") + else + puts 'Well done! Your code withstood a heckling.' + end + end +rescue LoadError + task :heckle => :spec do + $stderr.puts 'Heckle or mspec is not available. In order to run heckle, you must: gem install heckle mspec' + end +end diff --git a/tasks/metrics/metric_fu.rake b/tasks/metrics/metric_fu.rake new file mode 100644 index 00000000..f1e6c6a4 --- /dev/null +++ b/tasks/metrics/metric_fu.rake @@ -0,0 +1,29 @@ +begin + require 'metric_fu' + require 'json' + + # XXX: temporary hack until metric_fu is fixed + MetricFu::Saikuro.class_eval { include FileUtils } + + MetricFu::Configuration.run do |config| + config.rcov = { + :environment => 'test', + :test_files => %w[ spec/**/*_spec.rb ], + :rcov_opts => %w[ + --sort coverage + --no-html + --text-coverage + --no-color + --profile + --exclude spec/,^/ + --include lib:spec + ], + } + end +rescue LoadError + namespace :metrics do + task :all do + $stderr.puts 'metric_fu is not available. In order to run metrics:all, you must: gem install metric_fu' + end + end +end diff --git a/tasks/metrics/reek.rake b/tasks/metrics/reek.rake new file mode 100644 index 00000000..b9a1bad3 --- /dev/null +++ b/tasks/metrics/reek.rake @@ -0,0 +1,15 @@ +begin + require 'reek/rake/task' + + if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'rbx' + task :reek do + $stderr.puts 'Reek fails under rubinius, fix rubinius and remove guard' + end + else + Reek::Rake::Task.new + end +rescue LoadError + task :reek do + $stderr.puts 'Reek is not available. In order to run reek, you must: gem install reek' + end +end diff --git a/tasks/metrics/roodi.rake b/tasks/metrics/roodi.rake new file mode 100644 index 00000000..29b4616e --- /dev/null +++ b/tasks/metrics/roodi.rake @@ -0,0 +1,15 @@ +begin + require 'roodi' + require 'rake/tasklib' + require 'roodi_task' + + RoodiTask.new do |t| + t.verbose = false + t.config = File.expand_path('../../../config/roodi.yml', __FILE__) + t.patterns = %w[ lib/**/*.rb ] + end +rescue LoadError + task :roodi do + abort 'Roodi is not available. In order to run roodi, you must: gem install roodi' + end +end diff --git a/tasks/metrics/yardstick.rake b/tasks/metrics/yardstick.rake new file mode 100644 index 00000000..358cbf36 --- /dev/null +++ b/tasks/metrics/yardstick.rake @@ -0,0 +1,23 @@ +begin + require 'pathname' + require 'yardstick' + require 'yardstick/rake/measurement' + require 'yardstick/rake/verify' + require 'yaml' + + config = YAML.load_file(File.expand_path('../../../config/yardstick.yml', __FILE__)) + + # yardstick_measure task + Yardstick::Rake::Measurement.new + + # verify_measurements task + Yardstick::Rake::Verify.new do |verify| + verify.threshold = config.fetch('threshold') + end +rescue LoadError + %w[ yardstick_measure verify_measurements ].each do |name| + task name.to_s do + abort "Yardstick is not available. In order to run #{name}, you must: gem install yardstick" + end + end +end diff --git a/tasks/spec.rake b/tasks/spec.rake new file mode 100644 index 00000000..4ce92c06 --- /dev/null +++ b/tasks/spec.rake @@ -0,0 +1,45 @@ +begin + + begin + require 'rspec/core/rake_task' + rescue LoadError + require 'spec/rake/spectask' + + module RSpec + module Core + RakeTask = Spec::Rake::SpecTask + end + end + end + + desc 'run all specs' + task :spec => %w[ spec:unit spec:integration ] + + namespace :spec do + RSpec::Core::RakeTask.new(:integration) do |t| + t.pattern = 'spec/integration/**/*_spec.rb' + end + + RSpec::Core::RakeTask.new(:unit) do |t| + t.pattern = 'spec/unit/**/*_spec.rb' + end + end +rescue LoadError + task :spec do + abort 'rspec is not available. In order to run spec, you must: gem install rspec' + end +end + +begin + desc "Generate code coverage" + RSpec::Core::RakeTask.new(:rcov) do |t| + t.rcov = true + t.rcov_opts = File.read('spec/rcov.opts').split(/\s+/) + end +rescue LoadError + task :rcov do + abort 'rcov is not available. In order to run rcov, you must: gem install rcov' + end +end + +task :test => 'spec' diff --git a/tasks/yard.rake b/tasks/yard.rake new file mode 100644 index 00000000..a2768706 --- /dev/null +++ b/tasks/yard.rake @@ -0,0 +1,9 @@ +begin + require 'yard' + + YARD::Rake::YardocTask.new +rescue LoadError + task :yard do + abort 'YARD is not available. In order to run yard, you must: gem install yard' + end +end