Move dependency "devtools" to the repo

This commit is contained in:
Alex Kotov 2021-01-17 04:07:52 +05:00
parent 51f2ff7efc
commit 443fdc93f1
54 changed files with 2025 additions and 39 deletions

View file

@ -4,4 +4,6 @@ source 'https://rubygems.org'
gemspec name: 'mutant'
gem 'devtools', path: 'devtools'
eval_gemfile File.expand_path('Gemfile.shared', __dir__)

View file

@ -20,6 +20,29 @@ PATH
mutant (~> 0.8.24)
rspec-core (>= 3.4.0, < 4.0.0)
PATH
remote: devtools
specs:
devtools (0.1.22)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
anima (~> 0.3.0)
concord (~> 0.1.5)
flay (~> 2.12.0)
flog (~> 4.6.2)
mutant (~> 0.8.19)
mutant-rspec (~> 0.8.19)
procto (~> 0.0.3)
rake (~> 12.3.0)
reek (~> 5.2.0)
rspec (~> 3.8.0)
rspec-core (~> 3.8.0)
rspec-its (~> 1.2.0)
rubocop (~> 0.60.0)
simplecov (~> 0.16.1)
yard (~> 0.9.16)
yardstick (~> 0.9.9)
GEM
remote: https://rubygems.org/
specs:
@ -45,41 +68,22 @@ GEM
equalizer (~> 0.0.9)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devtools (0.1.22)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
anima (~> 0.3.0)
concord (~> 0.1.5)
flay (~> 2.12.0)
flog (~> 4.6.2)
mutant (~> 0.8.19)
mutant-rspec (~> 0.8.19)
procto (~> 0.0.3)
rake (~> 12.3.0)
reek (~> 5.2.0)
rspec (~> 3.8.0)
rspec-core (~> 3.8.0)
rspec-its (~> 1.2.0)
rubocop (~> 0.60.0)
simplecov (~> 0.16.1)
yard (~> 0.9.16)
yardstick (~> 0.9.9)
diff-lcs (1.3)
docile (1.3.1)
docile (1.3.5)
equalizer (0.0.11)
erubis (2.7.0)
flay (2.12.0)
flay (2.12.1)
erubis (~> 2.7.0)
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
flog (4.6.2)
flog (4.6.4)
path_expander (~> 1.0)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.8)
ice_nine (0.11.2)
jaro_winkler (1.5.1)
json (2.1.0)
jaro_winkler (1.5.4)
json (2.5.1)
kwalify (0.7.2)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
@ -95,11 +99,11 @@ GEM
parallel (1.12.1)
parser (2.5.3.0)
ast (~> 2.4.0)
path_expander (1.0.3)
powerpack (0.1.2)
path_expander (1.1.0)
powerpack (0.1.3)
procto (0.0.3)
rainbow (3.0.0)
rake (12.3.1)
rake (12.3.3)
reek (5.2.0)
codeclimate-engine-rb (~> 0.4.0)
kwalify (~> 0.7.0)
@ -110,18 +114,18 @@ GEM
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.0)
rspec-core (3.8.2)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.2)
rspec-expectations (3.8.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-its (1.2.0)
rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0)
rspec-mocks (3.8.0)
rspec-mocks (3.8.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.0)
rspec-support (3.8.3)
rubocop (0.60.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
@ -130,17 +134,17 @@ GEM
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.4.0)
ruby-progressbar (1.10.0)
ruby_parser (3.11.0)
ruby-progressbar (1.11.0)
ruby_parser (3.15.1)
sexp_processor (~> 4.9)
sexp_processor (4.11.0)
sexp_processor (4.15.2)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
thread_safe (0.3.6)
unicode-display_width (1.4.0)
unicode-display_width (1.4.1)
unparser (0.4.2)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
@ -154,7 +158,7 @@ GEM
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
yard (0.9.16)
yard (0.9.26)
yardstick (0.9.9)
yard (~> 0.8, >= 0.8.7.2)
@ -162,9 +166,9 @@ PLATFORMS
ruby
DEPENDENCIES
devtools (~> 0.1.22)
devtools!
mutant!
parallel (~> 1.3)
BUNDLED WITH
1.17.1
1.17.3

View file

@ -0,0 +1,41 @@
defaults: &defaults
working_directory: ~/mutant
docker:
- image: circleci/ruby:2.5.3
version: 2
jobs:
unit_specs:
<<: *defaults
steps:
- checkout
- run: bundle install
- run: bundle exec rspec spec/unit
integration_specs:
<<: *defaults
steps:
- checkout
- run: bundle install
- run: bundle exec rspec spec/integration
metrics:
<<: *defaults
steps:
- checkout
- run: bundle install
- run: bundle exec rake metrics:rubocop
- run: bundle exec rake metrics:reek
- run: bundle exec rake metrics:flay
- run: bundle exec rake metrics:flog
mutant:
<<: *defaults
steps:
- checkout
- run: bundle install
- run: bundle exec rake metrics:mutant
workflows:
version: 2
test:
jobs:
- unit_specs
- integration_specs
- metrics
- mutant

37
devtools/.gitignore vendored Normal file
View file

@ -0,0 +1,37 @@
## MAC OS
.DS_Store
## TEXTMATE
*.tmproj
tmtags
## EMACS
*~
\#*
.\#*
## VIM
*.sw[op]
## Rubinius
*.rbc
.rbx
## PROJECT::GENERAL
*.gem
coverage
profiling
turbulence
rdoc
pkg
tmp
doc
log
.yardoc
measurements
## BUNDLER
.bundle
Gemfile.lock
## PROJECT::SPECIFIC

6
devtools/.rspec Normal file
View file

@ -0,0 +1,6 @@
--color
--format progress
--profile
--warnings
--order random
--require spec_helper

4
devtools/.rubocop.yml Normal file
View file

@ -0,0 +1,4 @@
AllCops:
DisplayCopNames: true
Include:
- 'Gemfile'

3
devtools/Gemfile Normal file
View file

@ -0,0 +1,3 @@
source 'https://rubygems.org'
gemspec

20
devtools/LICENSE Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2014 Markus Schirp
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

47
devtools/README.md Normal file
View file

@ -0,0 +1,47 @@
# devtools
[![Build Status](https://img.shields.io/circleci/project/mbj/devtools.svg)](https://circleci.com/gh/mbj/devtools/tree/master)
[![Dependency Status](https://gemnasium.com/mbj/devtools.png)](https://gemnasium.com/mbj/devtools)
[![Code Climate](https://codeclimate.com/github/datamapper/devtools.png)](https://codeclimate.com/github/datamapper/devtools)
<!-- [![Code Climate](https://codeclimate.com/github/mbj/devtools.png)](https://codeclimate.com/github/mbj/devtools) -->
Metagem to assist development.
Used to centralize metric setup and development gem dependencies.
## Installation
Add the gem to your Gemfile's development section.
```ruby
group :development, :test do
gem 'devtools', '~> 0.1.x'
end
```
## RSpec support
If you're using RSpec and want to have access to our common setup just adjust
`spec/spec_helper.rb` to include
```ruby
require 'devtools/spec_helper'
```
## Credits
The whole [ROM](https://github.com/rom-rb) team that created and maintained all
these tasks before they were centralized here.
## Contributing
* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a
future version unintentionally.
* Commit, do not mess with Rakefile or version
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.
## License
See `LICENSE` file.

5
devtools/Rakefile Normal file
View file

@ -0,0 +1,5 @@
require 'devtools'
ENV['DEVTOOLS_SELF'] = '1'
Devtools.init_rake_tasks

View file

@ -0,0 +1,2 @@
---
unit_test_timeout: 0.2

3
devtools/config/flay.yml Normal file
View file

@ -0,0 +1,3 @@
---
threshold: 8
total_score: 122

2
devtools/config/flog.yml Normal file
View file

@ -0,0 +1,2 @@
---
threshold: 13.5

View file

@ -0,0 +1,4 @@
name: devtools
namespace: Devtools
ignore_subjects:
- "Devtools::Flay::Scale#flay"

106
devtools/config/reek.yml Normal file
View file

@ -0,0 +1,106 @@
---
detectors:
Attribute:
enabled: false
exclude: []
BooleanParameter:
enabled: true
exclude: []
ClassVariable:
enabled: true
exclude: []
ControlParameter:
enabled: true
exclude: []
DataClump:
enabled: true
exclude: []
max_copies: 0
min_clump_size: 2
DuplicateMethodCall:
enabled: true
exclude: []
max_calls: 1
allow_calls: []
FeatureEnvy:
enabled: true
exclude: []
IrresponsibleModule:
enabled: true
exclude: []
LongParameterList:
enabled: true
exclude:
- Devtools::Config#self.attribute
max_params: 2
overrides: {}
LongYieldList:
enabled: true
exclude: []
max_params: 0
NestedIterators:
enabled: true
exclude: []
max_allowed_nesting: 1
ignore_iterators: []
NilCheck:
enabled: true
exclude: []
RepeatedConditional:
enabled: true
exclude: []
max_ifs: 1
TooManyConstants:
enabled: true
exclude:
- Devtools
TooManyInstanceVariables:
enabled: true
exclude: []
max_instance_variables: 2
TooManyMethods:
enabled: true
exclude: []
max_methods: 15
TooManyStatements:
enabled: true
exclude: []
max_statements: 5
UncommunicativeMethodName:
enabled: true
exclude: []
reject:
- '/^[a-z]$/'
- '/[0-9]$/'
- '/[A-Z]/'
accept: []
UncommunicativeModuleName:
enabled: true
exclude: []
reject:
- '/^.$/'
- '/[0-9]$/'
accept: []
UncommunicativeParameterName:
enabled: true
exclude: []
reject:
- '/^.$/'
- '/[0-9]$/'
- '/[A-Z]/'
accept: []
UncommunicativeVariableName:
enabled: true
exclude: []
reject:
- '/^.$/'
- '/[0-9]$/'
- '/[A-Z]/'
accept: []
UnusedParameters:
enabled: true
exclude: []
UtilityFunction:
enabled: true
exclude:
- Devtools::Project::Initializer::Rspec#require_files # intentional for deduplication

151
devtools/config/rubocop.yml Normal file
View file

@ -0,0 +1,151 @@
inherit_from: ../.rubocop.yml
AllCops:
TargetRubyVersion: 2.5.0
Metrics/BlockLength:
Exclude:
# Ignore RSpec DSL
- spec/**/*
# Ignore gemspec DSL
- '*.gemspec'
Naming/FileName:
Exclude:
- Rakefile
# Avoid parameter lists longer than five parameters.
ParameterLists:
Max: 3
CountKeywordArgs: true
# Avoid more than `Max` levels of nesting.
BlockNesting:
Max: 3
# Align with the style guide.
CollectionMethods:
PreferredMethods:
collect: 'map'
inject: 'reduce'
find: 'detect'
find_all: 'select'
# Do not force public/protected/private keyword to be indented at the same
# level as the def keyword. My personal preference is to outdent these keywords
# because I think when scanning code it makes it easier to identify the
# sections of code and visually separate them. When the keyword is at the same
# level I think it sort of blends in with the def keywords and makes it harder
# to scan the code and see where the sections are.
AccessModifierIndentation:
Enabled: false
# Limit line length
LineLength:
Max: 106
# Disable documentation checking until a class needs to be documented once
Documentation:
Enabled: false
# Do not always use &&/|| instead of and/or.
AndOr:
Enabled: false
# Do not favor modifier if/unless usage when you have a single-line body
IfUnlessModifier:
Enabled: false
# Allow case equality operator (in limited use within the specs)
CaseEquality:
Enabled: false
# Constants do not always have to use SCREAMING_SNAKE_CASE
ConstantName:
Enabled: false
# Not all trivial readers/writers can be defined with attr_* methods
TrivialAccessors:
Enabled: false
# Allow empty lines around class body
EmptyLinesAroundClassBody:
Enabled: false
# Allow empty lines around module body
EmptyLinesAroundModuleBody:
Enabled: false
# Allow empty lines around block body
EmptyLinesAroundBlockBody:
Enabled: false
# Allow multiple line operations to not require indentation
MultilineOperationIndentation:
Enabled: false
# Prefer String#% over Kernel#sprintf
FormatString:
Enabled: false
# Use square brackets for literal Array objects
PercentLiteralDelimiters:
PreferredDelimiters:
'%': '{}'
'%i': '[]'
'%q': ()
'%Q': ()
'%r': '{}'
'%s': ()
'%w': '[]'
'%W': '[]'
'%x': ()
# Align if/else blocks with the variable assignment
EndAlignment:
EnforcedStyleAlignWith: variable
# Do not always align parameters when it is easier to read
AlignParameters:
Exclude:
- spec/**/*_spec.rb
# Prefer #kind_of? over #is_a?
ClassCheck:
EnforcedStyle: kind_of?
# Do not prefer double quotes to be used when %q or %Q is more appropriate
UnneededPercentQ:
Enabled: false
# Allow a maximum ABC score
Metrics/AbcSize:
Max: 20.1
# Do not prefer lambda.call(...) over lambda.(...)
LambdaCall:
Enabled: false
# Allow additional spaces
ExtraSpacing:
Enabled: false
# All objects can still be mutated if their eigenclass is patched
RedundantFreeze:
Enabled: false
# Prefer using `fail` when raising and `raise` when reraising
SignalException:
EnforcedStyle: semantic
Style/FrozenStringLiteralComment:
Enabled: false
Style/CommentedKeyword:
Enabled: false
Style/MixinGrouping:
Enabled: false
Layout/MultilineMethodCallIndentation:
EnforcedStyle: indented

View file

@ -0,0 +1,2 @@
---
threshold: 100

36
devtools/devtools.gemspec Normal file
View file

@ -0,0 +1,36 @@
Gem::Specification.new do |gem|
gem.name = 'devtools'
gem.version = '0.1.22'
gem.authors = ['Markus Schirp']
gem.email = ['mbj@schirp-dso.com']
gem.description = 'A metagem wrapping development tools'
gem.summary = gem.description
gem.homepage = 'https://github.com/rom-rb/devtools'
gem.license = 'MIT'
gem.require_paths = %w[lib]
gem.files = `git ls-files`.split("\n")
gem.executables = %w[]
gem.test_files = `git ls-files -- spec`.split("\n")
gem.extra_rdoc_files = %w[README.md]
gem.required_ruby_version = '>= 2.5'
gem.add_runtime_dependency 'abstract_type', '~> 0.0.7'
gem.add_runtime_dependency 'adamantium', '~> 0.2.0'
gem.add_runtime_dependency 'anima', '~> 0.3.0'
gem.add_runtime_dependency 'concord', '~> 0.1.5'
gem.add_runtime_dependency 'flay', '~> 2.12.0'
gem.add_runtime_dependency 'flog', '~> 4.6.2'
gem.add_runtime_dependency 'mutant', '~> 0.8.19'
gem.add_runtime_dependency 'mutant-rspec', '~> 0.8.19'
gem.add_runtime_dependency 'procto', '~> 0.0.3'
gem.add_runtime_dependency 'rake', '~> 12.3.0'
gem.add_runtime_dependency 'reek', '~> 5.2.0'
gem.add_runtime_dependency 'rspec', '~> 3.8.0'
gem.add_runtime_dependency 'rspec-core', '~> 3.8.0'
gem.add_runtime_dependency 'rspec-its', '~> 1.2.0'
gem.add_runtime_dependency 'rubocop', '~> 0.60.0'
gem.add_runtime_dependency 'simplecov', '~> 0.16.1'
gem.add_runtime_dependency 'yard', '~> 0.9.16'
gem.add_runtime_dependency 'yardstick', '~> 0.9.9'
end

96
devtools/lib/devtools.rb Normal file
View file

@ -0,0 +1,96 @@
# frozen_string_literal: true
# Stdlib infrastructure
require 'pathname'
require 'rake'
require 'timeout'
require 'yaml'
require 'fileutils'
# Non stdlib infrastructure
require 'abstract_type'
require 'procto'
require 'anima'
require 'concord'
require 'adamantium'
# Wrapped tools
require 'flay'
require 'rspec'
require 'rspec/its'
require 'simplecov'
# Main devtools namespace population
module Devtools
ROOT = Pathname.new(__FILE__).parent.parent.freeze
PROJECT_ROOT = Pathname.pwd.freeze
SHARED_PATH = ROOT.join('shared').freeze
SHARED_SPEC_PATH = SHARED_PATH.join('spec').freeze
DEFAULT_CONFIG_PATH = ROOT.join('default/config').freeze
RAKE_FILES_GLOB = ROOT.join('tasks/**/*.rake').to_s.freeze
LIB_DIRECTORY_NAME = 'lib'
SPEC_DIRECTORY_NAME = 'spec'
RAKE_FILE_NAME = 'Rakefile'
SHARED_SPEC_PATTERN = '{shared,support}/**/*.rb'
UNIT_TEST_PATH_REGEXP = %r{\bspec/unit/}
DEFAULT_CONFIG_DIR_NAME = 'config'
private_constant(*constants(false))
# React to metric violation
#
# @param [String] msg
#
# @return [undefined]
#
# @api private
def self.notify_metric_violation(msg)
abort(msg)
end
# Initialize project and load tasks
#
# Should *only* be called from your $application_root/Rakefile
#
# @return [self]
#
# @api public
def self.init_rake_tasks
Project::Initializer::Rake.call
self
end
# Return devtools root path
#
# @return [Pathname]
#
# @api private
def self.root
ROOT
end
# Return project
#
# @return [Project]
#
# @api private
def self.project
PROJECT
end
end # module Devtools
# Devtools implementation
require 'devtools/config'
require 'devtools/project'
require 'devtools/project/initializer'
require 'devtools/project/initializer/rake'
require 'devtools/project/initializer/rspec'
require 'devtools/flay'
require 'devtools/rake/flay'
# Devtools self initialization
module Devtools
# The project devtools is active for
PROJECT = Project.new(PROJECT_ROOT)
end

View file

@ -0,0 +1,180 @@
# frozen_string_literal: true
module Devtools
# Abstract base class of tool configuration
class Config
include Adamantium::Flat, AbstractType, Concord.new(:config_dir)
# Represent no configuration
DEFAULT_CONFIG = {}.freeze
# Simple named type check representation
class TypeCheck
# Type check against expected class
include Concord.new(:name, :allowed_classes)
ERROR_FORMAT = '%<name>s: Got instance of %<got>s expected %<allowed>s'
CLASS_DELIM = ','
# Check value for instance of expected class
#
# @param [Object] value
#
# @return [Object]
def call(value)
klass = value.class
format_values = {
name: name,
got: klass,
allowed: allowed_classes.join(CLASS_DELIM)
}
unless allowed_classes.any?(&klass.method(:equal?))
fail TypeError, format(ERROR_FORMAT, format_values)
end
value
end
end # TypeCheck
private_constant(*constants(false))
# Error raised on type errors
TypeError = Class.new(RuntimeError)
# Declare an attribute
#
# @param [Symbol] name
# @param [Array<Class>] classes
#
# @api private
#
# @return [self]
#
def self.attribute(name, classes, **options)
default = [options.fetch(:default)] if options.key?(:default)
type_check = TypeCheck.new(name, classes)
key = name.to_s
define_method(name) do
type_check.call(raw.fetch(key, *default))
end
end
private_class_method :attribute
# Return config path
#
# @return [String]
#
# @api private
#
def config_file
config_dir.join(self.class::FILE)
end
memoize :config_file
private
# Return raw data
#
# @return [Hash]
#
# @api private
#
def raw
yaml_config || DEFAULT_CONFIG
end
memoize :raw
# Return the raw config data from a yaml file
#
# @return [Hash]
# returned if the yaml file is found
# @return [nil]
# returned if the yaml file is not found
#
# @api private
#
def yaml_config
IceNine.deep_freeze(YAML.load_file(config_file)) if config_file.file?
end
# Rubocop configuration
class Rubocop < self
FILE = 'rubocop.yml'
end # Rubocop
# Reek configuration
class Reek < self
FILE = 'reek.yml'
end # Reek
# Flay configuration
#
class Flay < self
FILE = 'flay.yml'
DEFAULT_LIB_DIRS = %w[lib].freeze
DEFAULT_EXCLUDES = %w[].freeze
attribute :total_score, [0.class]
attribute :threshold, [0.class]
attribute :lib_dirs, [Array], default: DEFAULT_LIB_DIRS
attribute :excludes, [Array], default: DEFAULT_EXCLUDES
end # Flay
# Yardstick configuration
class Yardstick < self
FILE = 'yardstick.yml'
OPTIONS = %w[
threshold
rules
verbose
path
require_exact_threshold
].freeze
# Options hash that Yardstick understands
#
# @return [Hash]
#
# @api private
def options
OPTIONS.each_with_object({}) do |name, hash|
hash[name] = raw.fetch(name, nil)
end
end
end # Yardstick
# Flog configuration
class Flog < self
FILE = 'flog.yml'
DEFAULT_LIB_DIRS = %w[lib].freeze
attribute :total_score, [Float]
attribute :threshold, [Float]
attribute :lib_dirs, [Array], default: DEFAULT_LIB_DIRS
end # Flog
# Mutant configuration
class Mutant < self
FILE = 'mutant.yml'
DEFAULT_NAME = ''
DEFAULT_STRATEGY = 'rspec'
attribute :name, [String], default: DEFAULT_NAME
attribute :strategy, [String], default: DEFAULT_STRATEGY
attribute :zombify, [TrueClass, FalseClass], default: false
attribute :since, [String, NilClass], default: nil
attribute :ignore_subjects, [Array], default: []
attribute :namespace, [String]
end # Mutant
# Devtools configuration
class Devtools < self
FILE = 'devtools.yml'
DEFAULT_UNIT_TEST_TIMEOUT = 0.1 # 100ms
attribute :unit_test_timeout, [Float], default: DEFAULT_UNIT_TEST_TIMEOUT
end # Devtools
end # Config
end # Devtools

View file

@ -0,0 +1,98 @@
# frozen_string_literal: true
module Devtools
module Flay
# Measure flay mass relative to size of duplicated sexps
class Scale
include Adamantium
include Anima.new(:minimum_mass, :files)
include Procto.call(:measure)
# Measure duplication mass
#
# @return [Array<Rational>]
#
# @api private
def measure
flay.masses.map do |hash, mass|
Rational(mass, flay.hashes.fetch(hash).size)
end
end
# Report flay output
#
# @return [undefined]
#
# @api private
def flay_report
flay.report
end
private
# Memoized flay instance
#
# @return [Flay]
#
# @api private
def flay
::Flay.new(mass: minimum_mass).tap do |flay|
flay.process(*files)
flay.analyze
end
end
memoize :flay, freezer: :noop
end
# Expand include and exclude file settings for flay
class FileList
include Procto.call, Concord.new(:includes, :excludes)
GLOB = '**/*.{rb,erb}'
# Expand includes and filter by excludes
#
# @return [Set<Pathname>]
#
# @api private
def call
include_set - exclude_set
end
private
# Set of excluded files
#
# @return [Set<Pathname>]
#
# @api private
def exclude_set
excludes.flat_map(&Pathname.method(:glob))
end
# Set of included files
#
# Expanded using flay's file expander which takes into
# account flay's plugin support
#
# @return [Set<Pathname>]
#
# @api private
def include_set
Set.new(flay_includes.map(&method(:Pathname)))
end
# Expand includes using flay
#
# Expanded using flay's file expander which takes into
# account flay's plugin support
#
# @return [Array<String>]
#
# @api private
def flay_includes
PathExpander.new(includes.dup, GLOB).process
end
end
end
end

View file

@ -0,0 +1,80 @@
module Devtools
# The project devtools supports
class Project
include Concord.new(:root)
CONFIGS = {
devtools: Config::Devtools,
flay: Config::Flay,
flog: Config::Flog,
reek: Config::Reek,
mutant: Config::Mutant,
rubocop: Config::Rubocop,
yardstick: Config::Yardstick
}.freeze
private_constant(*constants(false))
attr_reader(*CONFIGS.keys)
# The spec root
#
# @return [Pathname]
#
# @api private
attr_reader :spec_root
# Initialize object
#
# @param [Pathname] root
#
# @return [undefined]
#
# @api private
#
def initialize(root)
super(root)
initialize_environment
initialize_configs
end
# Init rspec
#
# @return [self]
#
# @api private
def init_rspec
Initializer::Rspec.call(self)
self
end
private
# Initialize environment
#
# @return [undefined]
#
# @api private
#
def initialize_environment
@spec_root = root.join(SPEC_DIRECTORY_NAME)
end
# Initialize configs
#
# @return [undefined]
#
# @api private
#
def initialize_configs
config_dir = root.join(DEFAULT_CONFIG_DIR_NAME)
CONFIGS.each do |name, klass|
instance_variable_set(:"@#{name}", klass.new(config_dir))
end
end
end # class Project
end # module Devtools

View file

@ -0,0 +1,11 @@
module Devtools
class Project
# Base class for project initializers
class Initializer
include AbstractType
abstract_singleton_method :call
end # class Initializer
end # class Project
end # module Devtools

View file

@ -0,0 +1,23 @@
module Devtools
class Project
class Initializer
# Imports all devtools rake tasks into a project
class Rake < self
include AbstractType
# Initialize rake tasks
#
# @return [undefined]
#
# @api rpivate
def self.call
FileList
.glob(RAKE_FILES_GLOB)
.each(&::Rake.application.method(:add_import))
self
end
end # class Rake
end # class Initializer
end # class Project
end # module Devtools

View file

@ -0,0 +1,80 @@
module Devtools
class Project
class Initializer
# Requires all shared specs in a project's spec_helper
# Also installs a configurable unit test timeout
class Rspec < self
include Concord.new(:project)
# Call initializer for project
#
# @param [Project] project
#
# @return [self]
#
# @api private
def self.call(project)
new(project).__send__(:call)
self
end
private
# Setup RSpec for project
#
# @return [self]
#
# @api private
def call
require_shared_spec_files
enable_unit_test_timeout
end
# Timeout unit tests that take longer than configured amount of time
#
# @param [Numeric] timeout
#
# @return [undefined]
#
# @raise [Timeout::Error]
# raised when the times are outside the timeout
#
# @api private
#
def enable_unit_test_timeout
timeout = project.devtools.unit_test_timeout
RSpec
.configuration
.around(file_path: UNIT_TEST_PATH_REGEXP) do |example|
Timeout.timeout(timeout, &example)
end
end
# Trigger the require of shared spec files
#
# @return [undefined]
#
# @api private
#
def require_shared_spec_files
require_files(SHARED_SPEC_PATH)
require_files(project.spec_root)
end
# Require files with pattern
#
# @param [Pathname] dir
# the directory containing the files to require
#
# @return [self]
#
# @api private
def require_files(dir)
Dir.glob(dir.join(SHARED_SPEC_PATTERN)).each(&Kernel.method(:require))
end
end # class Rspec
end # class Initializer
end # class Project
end # module Devtools

View file

@ -0,0 +1,155 @@
# frozen_string_literal: true
module Devtools
module Rake
# Flay metric runner
class Flay
include Anima.new(:threshold, :total_score, :lib_dirs, :excludes),
Procto.call(:verify),
Adamantium
BELOW_THRESHOLD = 'Adjust flay threshold down to %<mass>d'
TOTAL_MISMATCH = 'Flay total is now %<mass>d, but expected %<expected>d'
ABOVE_THRESHOLD = '%<mass>d chunks have a duplicate mass > %<threshold>d'
# Verify code specified by `files` does not violate flay expectations
#
# @raise [SystemExit] if a violation is found
# @return [undefined] otherwise
#
#
# @api private
def verify
# Run flay first to ensure the max mass matches the threshold
below_threshold_message if below_threshold?
total_mismatch_message if total_mismatch?
# Run flay a second time with the threshold set
return unless above_threshold?
restricted_flay_scale.flay_report
above_threshold_message
end
private
# List of files flay will analyze
#
# @return [Set<Pathname>]
#
# @api private
def files
Devtools::Flay::FileList.call(lib_dirs, excludes)
end
# Is there mass duplication which exceeds specified `threshold`
#
# @return [Boolean]
#
# @api private
def above_threshold?
restricted_mass_size.nonzero?
end
# Is the specified `threshold` greater than the largest flay mass
#
# @return [Boolean]
#
# @api private
def below_threshold?
threshold > largest_mass
end
# Is the expected mass total different from the actual mass total
#
# @return [Boolean]
#
# @api private
def total_mismatch?
!total_mass.equal?(total_score)
end
# Above threshold message
#
# @return [String]
#
# @api private
def above_threshold_message
format_values = { mass: restricted_mass_size, threshold: threshold }
Devtools.notify_metric_violation(
format(ABOVE_THRESHOLD, format_values)
)
end
# Below threshold message
#
# @return [String]
#
# @api private
def below_threshold_message
Devtools.notify_metric_violation(
format(BELOW_THRESHOLD, mass: largest_mass)
)
end
# Total mismatch message
#
# @return [String]
#
# @api private
def total_mismatch_message
Devtools.notify_metric_violation(
format(TOTAL_MISMATCH, mass: total_mass, expected: total_score)
)
end
# Size of mass measured by `Flay::Scale` and filtered by `threshold`
#
# @return [Integer]
#
# @api private
def restricted_mass_size
restricted_flay_scale.measure.size
end
# Sum of all flay mass
#
# @return [Integer]
#
# @api private
def total_mass
flay_masses.reduce(:+).to_i
end
# Largest flay mass found
#
# @return [Integer]
#
# @api private
def largest_mass
flay_masses.max.to_i
end
# Flay scale which only measures mass above `threshold`
#
# @return [Flay::Scale]
#
# @api private
def restricted_flay_scale
Devtools::Flay::Scale.new(minimum_mass: threshold.succ, files: files)
end
memoize :restricted_flay_scale
# All flay masses found in `files`
#
# @return [Array<Rational>]
#
# @api private
def flay_masses
Devtools::Flay::Scale.call(minimum_mass: 0, files: files)
end
memoize :flay_masses
end
end
end

View file

@ -0,0 +1,3 @@
require 'devtools'
Devtools::PROJECT.init_rspec

View file

@ -0,0 +1,16 @@
shared_examples_for 'an abstract type' do
context 'called on a subclass' do
let(:object) { Class.new(described_class) }
it { should be_instance_of(object) }
end
context 'called on the class' do
let(:object) { described_class }
specify do
expect { subject }
.to raise_error(NotImplementedError, "#{object} is an abstract type")
end
end
end

View file

@ -0,0 +1,5 @@
shared_examples_for 'a command method' do
it 'returns self' do
should equal(object)
end
end

View file

@ -0,0 +1,13 @@
shared_examples_for 'an #each method' do
it_should_behave_like 'a command method'
context 'with no block' do
subject { object.each }
it { should be_instance_of(to_enum.class) }
it 'yields the expected values' do
expect(subject.to_a).to eql(object.to_a)
end
end
end

View file

@ -0,0 +1,11 @@
shared_examples_for 'an idempotent method' do
it 'is idempotent' do
first = subject
error = 'RSpec not configured for threadsafety'
fail error unless RSpec.configuration.threadsafe?
mutex = __memoized.instance_variable_get(:@mutex)
memoized = __memoized.instance_variable_get(:@memoized)
mutex.synchronize { memoized.delete(:subject) }
should equal(first)
end
end

View file

@ -0,0 +1,12 @@
if defined?(IceNine)
module IceNine
# Freezer namespace
class Freezer
# Rspec freezer
class RSpec < NoFreeze; end
end # Freezer
end # IceNine
end

View file

@ -0,0 +1,196 @@
# frozen_string_literal: true
describe Devtools::Rake::Flay, '#verify' do
let(:tempfile) { Tempfile.new(%w[file .rb], Dir.mktmpdir) }
let(:file) { Pathname(tempfile.path) }
let(:directories) { [file.dirname.to_s] }
let(:ruby) do
<<-ERUBY
def foo; end
def bar; end
ERUBY
end
around(:each) do |example|
begin
# silence other flay output
$stdout = $stderr = StringIO.new
tempfile.write(ruby)
tempfile.close
example.run
ensure
$stdout = STDOUT
$stderr = STDERR
file.unlink
end
end
context 'reporting' do
let(:options) do
{ threshold: 3, total_score: 3, lib_dirs: directories, excludes: [] }
end
let(:instance) { described_class.new(options) }
it 'measures total mass' do
allow(::Flay).to receive(:new).and_call_original
instance.verify
expect(::Flay).to have_received(:new).with(hash_including(mass: 0))
end
it 'does not report the files it is processing' do
expect { instance.verify }.to_not output(/Processing #{file}/).to_stderr
end
end
context 'when theshold is too low' do
let(:instance) do
described_class.new(
threshold: 0,
total_score: 0,
lib_dirs: directories,
excludes: []
)
end
specify do
expect { instance.verify }
.to raise_error(SystemExit)
.with_message('Flay total is now 3, but expected 0')
end
end
context 'when threshold is too high' do
let(:instance) do
described_class.new(
threshold: 1000,
total_score: 0,
lib_dirs: directories,
excludes: []
)
end
specify do
expect { instance.verify }
.to raise_error(SystemExit)
.with_message('Adjust flay threshold down to 3')
end
end
context 'when total is too high' do
let(:instance) do
described_class.new(
threshold: 3,
total_score: 50,
lib_dirs: directories,
excludes: []
)
end
specify do
expect { instance.verify }
.to raise_error(SystemExit)
.with_message('Flay total is now 3, but expected 50')
end
end
context 'when duplicate mass is greater than 0' do
let(:ruby) do
<<-ERUBY
def foo
:hi if baz?
end
def bar
:hi if baz?
end
ERUBY
end
let(:report) do
<<~REPORT
Total score (lower is better) = 10
1) Similar code found in :defn (mass = 10)
#{file}:1
#{file}:5
REPORT
end
let(:instance) do
described_class.new(
threshold: 3,
total_score: 5,
lib_dirs: directories,
excludes: []
)
end
specify do
expect { instance.verify }
.to raise_error(SystemExit)
.with_message('1 chunks have a duplicate mass > 3')
end
specify do
expect { instance.verify }
.to raise_error(SystemExit)
.and output(report).to_stdout
end
end
context 'when multiple duplicate masses' do
let(:ruby) do
<<-ERUBY
def foo; end
def bar; end
class Foo
def initialize
@a = 1
end
end
class Bar
def initialize
@a = 1
end
end
ERUBY
end
let(:instance) do
described_class.new(
threshold: 5,
total_score: 8,
lib_dirs: directories,
excludes: []
)
end
it 'sums masses for total' do
expect { instance.verify }.to_not raise_error
end
end
context 'when no duplication masses' do
let(:ruby) { '' }
let(:instance) do
described_class.new(
threshold: 0,
total_score: 0,
lib_dirs: directories,
excludes: []
)
end
specify do
expect { instance.verify }.to_not raise_error
end
end
end

View file

@ -0,0 +1,24 @@
require 'devtools/spec_helper'
require 'tempfile'
require 'tmpdir'
if ENV['COVERAGE'] == 'true'
formatter = [SimpleCov::Formatter::HTMLFormatter]
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(formatter)
SimpleCov.start do
command_name 'spec:unit'
add_filter 'config'
add_filter 'spec'
add_filter 'vendor'
minimum_coverage 100
end
end
RSpec.configure do |config|
config.expect_with :rspec do |expect_with|
expect_with.syntax = :expect
end
end

View file

@ -0,0 +1,17 @@
RSpec.describe Devtools::Config::Yardstick do
let(:object) { described_class.new(Devtools.root.join('config')) }
describe '#options' do
subject { object.options }
specify do
should eql(
'threshold' => 100,
'rules' => nil,
'verbose' => nil,
'path' => nil,
'require_exact_threshold' => nil
)
end
end
end

View file

@ -0,0 +1,78 @@
RSpec.describe Devtools::Config do
describe '.attribute' do
let(:raw) do
{
'a' => 'bar',
'c' => []
}
end
let(:config_path) { instance_double(Pathname) }
let(:class_under_test) do
expect(config_path).to receive(:file?)
.and_return(file?)
expect(config_path).to receive(:frozen?)
.and_return(true)
expect(config_path).to receive(:join)
.with('bar.yml')
.and_return(config_path)
Class.new(described_class) do
attribute :a, [String]
attribute :b, [Array], default: []
attribute :c, [TrueClass, FalseClass]
const_set(:FILE, 'bar.yml')
end
end
subject do
class_under_test.new(config_path)
end
context 'on present config' do
let(:class_under_test) do
# Setup message expectation in a lasy way, not in a before
# block to make sure the around hook setting timeouts from the
# code under test is not affected.
expect(YAML).to receive(:load_file)
.with(config_path)
.and_return(raw)
expect(IceNine).to receive(:deep_freeze)
.with(raw)
.and_return(raw)
super()
end
let(:file?) { true }
it 'allows to receive existing keys' do
expect(subject.a).to eql('bar')
end
it 'allows to receive absent keys with defaults' do
expect(subject.b).to eql([])
end
it 'executes checks when configured' do
expect { subject.c }.to raise_error(
Devtools::Config::TypeError,
'c: Got instance of Array expected TrueClass,FalseClass'
)
end
end
context 'on absent config' do
let(:file?) { false }
it 'defaults to absent keys' do
expect(subject.b).to eql([])
end
end
end
end

View file

@ -0,0 +1,19 @@
describe Devtools::Flay::FileList, '.call' do
subject(:output) { described_class.call([tmpdir.to_s].freeze, [exclude]) }
let(:tmpdir) { Dir.mktmpdir }
let(:one) { Pathname(tmpdir).join('1.rb') }
let(:two) { Pathname(tmpdir).join('2.erb') }
let(:three) { Pathname(tmpdir).join('3.rb') }
let(:exclude) { Pathname(tmpdir).join('3*').to_s }
around(:each) do |example|
[one, two, three].map(&FileUtils.method(:touch))
example.run
FileUtils.rm_rf(tmpdir)
end
it { should eql(Set.new([one, two])) }
end

View file

@ -0,0 +1,17 @@
describe Devtools::Flay::Scale, '#flay_report' do
subject(:instance) { described_class.new(minimum_mass: 0, files: []) }
let(:flay) do
instance_double(::Flay, process: nil, analyze: nil, masses: {})
end
before do
allow(::Flay).to receive(:new).with(mass: 0).and_return(flay)
end
specify do
allow(flay).to receive(:report)
instance.flay_report
expect(flay).to have_received(:report)
end
end

View file

@ -0,0 +1,43 @@
describe Devtools::Flay::Scale, '#measure' do
subject(:measure) { instance.measure }
let(:minimum_mass) { 0 }
let(:files) { [instance_double(File)] }
let(:flay_masses) { { 0 => 5, 1 => 10 } }
let(:instance) do
described_class.new(minimum_mass: minimum_mass, files: files)
end
let(:flay_hashes) do
{
0 => instance_double(Array, size: 3),
1 => instance_double(Array, size: 11)
}
end
let(:flay) do
instance_double(
::Flay,
analyze: nil,
masses: flay_masses,
hashes: flay_hashes
)
end
before do
allow(::Flay).to receive(:new).with(mass: minimum_mass).and_return(flay)
allow(flay).to receive(:process).with(*files)
end
it { should eql([Rational(5, 3), Rational(10, 11)]) }
context 'when minimum mass is not 0' do
let(:minimum_mass) { 1 }
specify do
measure
expect(::Flay).to have_received(:new).with(mass: 1)
end
end
end

View file

@ -0,0 +1,21 @@
describe Devtools::Project::Initializer::Rake do
describe '.call' do
subject do
described_class.call
end
it 'performs expected rake initialization' do
path_a = instance_double(Pathname)
path_b = instance_double(Pathname)
expect(FileList).to receive(:glob)
.with(Devtools.root.join('tasks/**/*.rake').to_s)
.and_return([path_a, path_b])
expect(Rake.application).to receive(:add_import).with(path_a)
expect(Rake.application).to receive(:add_import).with(path_b)
expect(subject).to be(described_class)
end
end
end

View file

@ -0,0 +1,52 @@
describe Devtools::Project::Initializer::Rspec do
let(:spec_root) { Devtools.root.join('spec') }
let(:unit_test_timeout) { instance_double(Float) }
let(:project) do
instance_double(
Devtools::Project,
spec_root: spec_root,
devtools: instance_double(
Devtools::Config::Devtools,
unit_test_timeout: unit_test_timeout
)
)
end
describe '.call' do
subject do
described_class.call(project)
end
it 'performs expected rspec initialization' do
called = false
example = -> { called = true }
expect(Dir).to receive(:glob)
.with(Devtools.root.join('shared/spec/{shared,support}/**/*.rb'))
.and_return(%w[shared-a shared-b])
expect(Kernel).to receive(:require).with('shared-a')
expect(Kernel).to receive(:require).with('shared-b')
expect(Dir).to receive(:glob)
.with(Devtools.root.join('spec/{shared,support}/**/*.rb'))
.and_return(%w[support-a support-b])
expect(Kernel).to receive(:require).with('support-a')
expect(Kernel).to receive(:require).with('support-b')
expect(Timeout).to receive(:timeout).with(unit_test_timeout) do |&block|
block.call
end
expect(RSpec.configuration).to receive(:around)
.with(file_path: %r{\bspec/unit/})
.and_yield(example)
expect(subject).to be(described_class)
expect(called).to be(true)
end
end
end

View file

@ -0,0 +1,35 @@
RSpec.describe Devtools::Project do
let(:object) { described_class.new(Devtools.root) }
describe '#init_rspec' do
subject { object.init_rspec }
it 'calls the rspec initializer' do
expect(Devtools::Project::Initializer::Rspec)
.to receive(:call).with(Devtools.project)
expect(subject).to be(object)
end
end
{
devtools: Devtools::Config::Devtools,
flay: Devtools::Config::Flay,
flog: Devtools::Config::Flog,
reek: Devtools::Config::Reek,
mutant: Devtools::Config::Mutant,
rubocop: Devtools::Config::Rubocop,
yardstick: Devtools::Config::Yardstick
}.each do |name, klass|
describe "##{name}" do
subject { object.send(name) }
specify { should eql(klass.new(Devtools.root.join('config'))) }
end
end
describe '#spec_root' do
subject { object.spec_root }
specify { should eql(Devtools.root.join('spec')) }
end
end

View file

@ -0,0 +1,14 @@
describe Devtools do
describe '.project' do
specify do
expect(Devtools.project).to equal(Devtools::PROJECT)
end
end
describe '.init_rake_tasks' do
specify do
expect(Devtools::Project::Initializer::Rake).to receive(:call)
expect(Devtools.init_rake_tasks).to be(Devtools)
end
end
end

View file

@ -0,0 +1,17 @@
desc 'Run all specs, metrics and mutant'
task ci: %w[ci:metrics metrics:mutant]
namespace :ci do
tasks = %w[
metrics:coverage
metrics:yardstick:verify
metrics:rubocop
metrics:flog
metrics:flay
metrics:reek
spec:integration
]
desc 'Run metrics (except mutant)'
task metrics: tasks
end

View file

@ -0,0 +1,13 @@
namespace :metrics do
desc 'Measure code coverage'
task :coverage do
begin
# rubocop:disable Style/ParallelAssignment
original, ENV['COVERAGE'] = ENV['COVERAGE'], 'true'
# rubocop:enable Style/ParallelAssignment
Rake::Task['spec:unit'].execute
ensure
ENV['COVERAGE'] = original
end
end
end

View file

@ -0,0 +1,21 @@
namespace :metrics do
require 'flay'
project = Devtools.project
config = project.flay
# Original code by Marty Andrews:
# http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
desc 'Measure code duplication'
task :flay do
threshold = config.threshold
total_score = config.total_score
Devtools::Rake::Flay.call(
threshold: threshold,
total_score: total_score,
lib_dirs: config.lib_dirs,
excludes: config.excludes
)
end
end

View file

@ -0,0 +1,43 @@
# rubocop:disable Metrics/BlockLength
namespace :metrics do
require 'flog'
require 'flog_cli'
project = Devtools.project
config = project.flog
# Original code by Marty Andrews:
# http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
desc 'Measure code complexity'
task :flog do
threshold = config.threshold.to_f.round(1)
flog = Flog.new
flog.flog(*PathExpander.new(config.lib_dirs.dup, '**/*.rb').process)
totals = flog
.totals
.reject { |name, _score| name.end_with?('#none') }
.map { |name, score| [name, score.round(1)] }
.sort_by { |_name, score| score }
if totals.any?
max = totals.last[1]
unless max >= threshold
Devtools.notify_metric_violation "Adjust flog score down to #{max}"
end
end
bad_methods = totals.select { |_name, score| score > threshold }
if bad_methods.any?
bad_methods.reverse_each do |name, score|
printf "%8.1f: %s\n", score, name
end
Devtools.notify_metric_violation(
"#{bad_methods.size} methods have a flog complexity > #{threshold}"
)
end
end
end
# rubocop:enable Metrics/BlockLength

View file

@ -0,0 +1,43 @@
# rubocop:disable Metrics/BlockLength
namespace :metrics do
config = Devtools.project.mutant
desc 'Measure mutation coverage'
task mutant: :coverage do
require 'mutant'
namespace =
if config.zombify
Mutant.zombify
Zombie::Mutant
else
Mutant
end
namespaces = Array(config.namespace).map { |n| "#{n}*" }
ignore_subjects = config.ignore_subjects.flat_map do |matcher|
%W[--ignore #{matcher}]
end
jobs = ENV.key?('CIRCLECI') ? %w[--jobs 4] : []
since =
if config.since
%W[--since #{config.since}]
else
[]
end
arguments = %W[
--include lib
--require #{config.name}
--use #{config.strategy}
].concat(ignore_subjects).concat(namespaces).concat(since).concat(jobs)
unless namespace::CLI.run(arguments)
Devtools.notify_metric_violation('Mutant task is not successful')
end
end
end
# rubocop:enable Metrics/BlockLength

View file

@ -0,0 +1,8 @@
namespace :metrics do
require 'reek/rake/task'
Reek::Rake::Task.new do |reek|
reek.source_files = '{app,lib}/**/*.rb'
reek.config_file = 'config/reek.yml'
end
end

View file

@ -0,0 +1,13 @@
namespace :metrics do
desc 'Check with code style guide'
task :rubocop do
require 'rubocop'
config = Devtools.project.rubocop
begin
exit_status = RuboCop::CLI.new.run(%W[--config #{config.config_file}])
fail 'Rubocop not successful' unless exit_status.zero?
rescue Encoding::CompatibilityError => exception
Devtools.notify_metric_violation exception.message
end
end
end

View file

@ -0,0 +1,12 @@
namespace :metrics do
namespace :yardstick do
require 'yardstick/rake/measurement'
require 'yardstick/rake/verify'
options = Devtools.project.yardstick.options
Yardstick::Rake::Measurement.new(:measure, options)
Yardstick::Rake::Verify.new(:verify, options)
end
end

34
devtools/tasks/spec.rake Normal file
View file

@ -0,0 +1,34 @@
begin
require 'rspec/core/rake_task'
# Remove existing same-named tasks
%w[spec spec:unit spec:integration].each do |task|
klass = Rake::Task
klass[task].clear if klass.task_defined?(task)
end
desc 'Run all specs'
RSpec::Core::RakeTask.new(:spec) do |task|
task.pattern = 'spec/{unit,integration}/**/*_spec.rb'
end
namespace :spec do
desc 'Run unit specs'
RSpec::Core::RakeTask.new(:unit) do |task|
task.pattern = 'spec/unit/**/*_spec.rb'
end
desc 'Run integration specs'
RSpec::Core::RakeTask.new(:integration) do |task|
task.pattern = 'spec/integration/**/*_spec.rb'
end
end
rescue LoadError
%w[spec spec:unit spec:integration].each do |name|
task name do
warn "In order to run #{name}, do: gem install rspec"
end
end
end
task test: :spec

9
devtools/tasks/yard.rake Normal file
View file

@ -0,0 +1,9 @@
begin
require 'yard'
YARD::Rake::YardocTask.new
rescue LoadError
task :yard do
warn 'In order to run yard, you must: gem install yard'
end
end

View file

@ -36,6 +36,5 @@ Gem::Specification.new do |gem|
gem.add_runtime_dependency('regexp_parser', '~> 1.2')
gem.add_runtime_dependency('unparser', '~> 0.4.2')
gem.add_development_dependency('devtools', '~> 0.1.22')
gem.add_development_dependency('parallel', '~> 1.3')
end