2012-08-28 12:57:39 -04:00
|
|
|
module Mutant
|
2013-02-02 10:56:48 -05:00
|
|
|
|
2014-08-07 12:00:31 -04:00
|
|
|
# Commandline parser
|
2013-05-14 21:25:16 -04:00
|
|
|
class CLI
|
2014-07-03 17:16:12 -04:00
|
|
|
include Adamantium::Flat, Equalizer.new(:config), Procto.call(:config)
|
2012-08-28 12:57:39 -04:00
|
|
|
|
2014-08-07 12:00:31 -04:00
|
|
|
# Error failed when CLI argv is invalid
|
2013-05-14 21:25:16 -04:00
|
|
|
Error = Class.new(RuntimeError)
|
|
|
|
|
|
|
|
EXIT_FAILURE = 1
|
|
|
|
EXIT_SUCCESS = 0
|
|
|
|
|
2012-08-28 13:43:15 -04:00
|
|
|
# Run cli with arguments
|
|
|
|
#
|
|
|
|
# @param [Array<String>] arguments
|
|
|
|
#
|
|
|
|
# @return [Fixnum]
|
2013-09-11 14:48:44 -04:00
|
|
|
# the exit status
|
2012-08-28 13:43:15 -04:00
|
|
|
#
|
|
|
|
# @api private
|
2013-05-14 21:25:16 -04:00
|
|
|
def self.run(arguments)
|
2014-12-22 09:42:20 -05:00
|
|
|
Runner.call(Env::Bootstrap.call(call(arguments))).success? ? EXIT_SUCCESS : EXIT_FAILURE
|
2012-11-21 16:28:08 -05:00
|
|
|
rescue Error => exception
|
|
|
|
$stderr.puts(exception.message)
|
|
|
|
EXIT_FAILURE
|
|
|
|
end
|
|
|
|
|
2013-02-02 10:56:48 -05:00
|
|
|
# Initialize objecct
|
2012-11-21 16:28:08 -05:00
|
|
|
#
|
2013-02-02 10:56:48 -05:00
|
|
|
# @param [Array<String>]
|
2012-11-21 16:28:08 -05:00
|
|
|
#
|
2013-02-02 10:56:48 -05:00
|
|
|
# @return [undefined]
|
2012-11-21 16:28:08 -05:00
|
|
|
#
|
|
|
|
# @api private
|
2014-07-03 17:16:12 -04:00
|
|
|
def initialize(arguments)
|
|
|
|
@config = Config::DEFAULT
|
|
|
|
|
2013-05-14 21:25:16 -04:00
|
|
|
parse(arguments)
|
2013-02-24 14:40:23 -05:00
|
|
|
end
|
2014-06-15 15:30:17 -04:00
|
|
|
|
2015-07-03 11:21:39 -04:00
|
|
|
# Config parsed from CLI
|
2014-06-15 15:30:17 -04:00
|
|
|
#
|
|
|
|
# @return [Config]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
attr_reader :config
|
2013-02-24 14:40:23 -05:00
|
|
|
|
|
|
|
private
|
|
|
|
|
2013-06-15 10:37:43 -04:00
|
|
|
# Parse the command-line options
|
2013-05-14 21:25:16 -04:00
|
|
|
#
|
|
|
|
# @param [Array<String>] arguments
|
|
|
|
# Command-line options and arguments to be parsed.
|
|
|
|
#
|
2014-07-03 17:16:12 -04:00
|
|
|
# @fail [Error]
|
2013-05-14 21:25:16 -04:00
|
|
|
# An error occurred while parsing the options.
|
|
|
|
#
|
2013-06-15 10:37:43 -04:00
|
|
|
# @return [undefined]
|
|
|
|
#
|
2013-05-14 21:25:16 -04:00
|
|
|
# @api private
|
|
|
|
def parse(arguments)
|
2013-07-28 13:58:53 -04:00
|
|
|
opts = OptionParser.new do |builder|
|
2014-07-06 11:00:37 -04:00
|
|
|
builder.banner = 'usage: mutant [options] MATCH_EXPRESSION ...'
|
2014-07-03 17:16:12 -04:00
|
|
|
%w[add_environment_options add_mutation_options add_filter_options add_debug_options].each do |name|
|
2015-05-02 18:01:10 -04:00
|
|
|
__send__(name, builder)
|
2013-06-21 11:21:04 -04:00
|
|
|
end
|
2014-07-03 17:16:12 -04:00
|
|
|
end
|
2013-05-14 21:25:16 -04:00
|
|
|
|
2014-07-03 17:16:12 -04:00
|
|
|
parse_match_expressions(opts.parse!(arguments))
|
|
|
|
rescue OptionParser::ParseError => error
|
2015-05-02 18:01:10 -04:00
|
|
|
raise(Error, error)
|
2013-06-21 11:52:53 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Parse matchers
|
|
|
|
#
|
2014-08-07 12:47:28 -04:00
|
|
|
# @param [Array<String>] expressions
|
2013-06-21 11:52:53 -04:00
|
|
|
#
|
2013-06-21 21:47:23 -04:00
|
|
|
# @return [undefined]
|
|
|
|
#
|
2013-06-21 11:52:53 -04:00
|
|
|
# @api private
|
2014-07-03 17:16:12 -04:00
|
|
|
def parse_match_expressions(expressions)
|
|
|
|
fail Error, 'No expressions given' if expressions.empty?
|
|
|
|
|
2015-06-21 10:44:33 -04:00
|
|
|
expressions.each do |expression|
|
|
|
|
add_matcher(:match_expressions, config.expression_parser.(expression))
|
2013-05-14 21:25:16 -04:00
|
|
|
end
|
|
|
|
end
|
2013-06-14 14:54:02 -04:00
|
|
|
|
2013-08-04 17:03:39 -04:00
|
|
|
# Add environmental options
|
|
|
|
#
|
|
|
|
# @param [Object] opts
|
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
2014-12-21 20:28:30 -05:00
|
|
|
# rubocop:disable MethodLength
|
|
|
|
#
|
2015-07-01 23:35:54 -04:00
|
|
|
# @api private
|
2014-07-03 17:16:12 -04:00
|
|
|
def add_environment_options(opts)
|
2014-01-18 18:27:56 -05:00
|
|
|
opts.separator('Environment:')
|
2013-08-04 17:03:39 -04:00
|
|
|
opts.on('--zombie', 'Run mutant zombified') do
|
2014-07-03 17:16:12 -04:00
|
|
|
update(zombie: true)
|
|
|
|
end
|
|
|
|
opts.on('-I', '--include DIRECTORY', 'Add DIRECTORY to $LOAD_PATH') do |directory|
|
|
|
|
add(:includes, directory)
|
|
|
|
end
|
|
|
|
opts.on('-r', '--require NAME', 'Require file with NAME') do |name|
|
|
|
|
add(:requires, name)
|
2013-08-04 17:03:39 -04:00
|
|
|
end
|
2014-09-24 15:16:39 -04:00
|
|
|
opts.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
|
|
|
|
update(jobs: Integer(number))
|
2014-07-17 09:59:25 -04:00
|
|
|
end
|
2013-08-04 17:03:39 -04:00
|
|
|
end
|
|
|
|
|
2014-07-03 17:16:12 -04:00
|
|
|
# Use integration
|
2014-01-17 18:25:16 -05:00
|
|
|
#
|
|
|
|
# @param [String] name
|
2014-01-17 18:15:42 -05:00
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
|
|
|
# @api private
|
2014-07-03 17:16:12 -04:00
|
|
|
def setup_integration(name)
|
2014-08-17 12:22:20 -04:00
|
|
|
update(integration: Integration.setup(name))
|
2014-11-30 18:22:21 -05:00
|
|
|
rescue LoadError
|
2014-12-21 20:28:30 -05:00
|
|
|
raise Error, "Could not load integration #{name.inspect} (you may want to try installing the gem mutant-#{name})"
|
2014-01-17 18:15:42 -05:00
|
|
|
end
|
|
|
|
|
2015-03-05 23:46:03 -05:00
|
|
|
# Add mutation options
|
2013-06-21 11:21:04 -04:00
|
|
|
#
|
2014-01-18 18:27:56 -05:00
|
|
|
# @param [OptionParser] opts
|
2013-06-21 11:21:04 -04:00
|
|
|
#
|
2013-06-21 21:47:23 -04:00
|
|
|
# @return [undefined]
|
|
|
|
#
|
2013-06-21 11:21:04 -04:00
|
|
|
# @api private
|
2014-01-18 18:27:56 -05:00
|
|
|
def add_mutation_options(opts)
|
2015-05-02 18:01:10 -04:00
|
|
|
opts.separator(nil)
|
2014-01-18 18:27:56 -05:00
|
|
|
opts.separator('Options:')
|
2013-06-21 11:21:04 -04:00
|
|
|
|
2015-04-28 09:40:34 -04:00
|
|
|
opts.on(
|
|
|
|
'--expected-coverage COVERAGE',
|
|
|
|
'Fail unless COVERAGE is not reached exactly, parsed via Rational()'
|
|
|
|
) do |coverage|
|
|
|
|
update(expected_coverage: Rational(coverage))
|
|
|
|
end
|
2015-06-13 00:26:08 -04:00
|
|
|
opts.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations', &method(:setup_integration))
|
2014-02-02 16:48:08 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Add filter options
|
|
|
|
#
|
|
|
|
# @param [OptionParser] opts
|
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
def add_filter_options(opts)
|
2015-07-13 13:04:35 -04:00
|
|
|
opts.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
|
2015-07-07 15:41:12 -04:00
|
|
|
add_matcher(:ignore_expressions, config.expression_parser.(pattern))
|
2014-02-02 16:48:08 -05:00
|
|
|
end
|
2015-07-12 21:36:52 -04:00
|
|
|
opts.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
|
|
|
|
add_matcher(:subject_filters, Repository::SubjectFilter.new(Repository::Diff.from_head(revision)))
|
|
|
|
end
|
2014-01-18 18:27:56 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Add debug options
|
|
|
|
#
|
|
|
|
# @param [OptionParser] opts
|
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
def add_debug_options(opts)
|
|
|
|
opts.on('--fail-fast', 'Fail fast') do
|
2014-07-03 17:16:12 -04:00
|
|
|
update(fail_fast: true)
|
2014-12-21 20:28:30 -05:00
|
|
|
end
|
|
|
|
opts.on('--version', 'Print mutants version') do
|
2015-05-02 18:01:10 -04:00
|
|
|
puts("mutant-#{VERSION}")
|
2014-07-06 11:00:37 -04:00
|
|
|
Kernel.exit(EXIT_SUCCESS)
|
2014-12-21 20:28:30 -05:00
|
|
|
end
|
|
|
|
opts.on('-d', '--debug', 'Enable debugging output') do
|
2014-07-03 17:16:12 -04:00
|
|
|
update(debug: true)
|
2014-12-21 20:28:30 -05:00
|
|
|
end
|
|
|
|
opts.on_tail('-h', '--help', 'Show this message') do
|
2014-07-06 11:00:37 -04:00
|
|
|
puts(opts.to_s)
|
|
|
|
Kernel.exit(EXIT_SUCCESS)
|
2013-06-21 11:21:04 -04:00
|
|
|
end
|
|
|
|
end
|
2014-07-03 17:16:12 -04:00
|
|
|
|
|
|
|
# Update configuration
|
|
|
|
#
|
|
|
|
# @param [Hash<Symbol, Object>] attributes
|
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
def update(attributes)
|
|
|
|
@config = @config.update(attributes)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Add configuration
|
|
|
|
#
|
|
|
|
# @param [Symbol] attribute
|
|
|
|
# the attribute to add to
|
|
|
|
#
|
|
|
|
# @param [Object] value
|
|
|
|
# the value to add
|
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
def add(attribute, value)
|
|
|
|
update(attribute => config.public_send(attribute).dup << value)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Add matcher configuration
|
|
|
|
#
|
|
|
|
# @param [Symbol] attribute
|
|
|
|
# the attribute to add to
|
|
|
|
#
|
|
|
|
# @param [Object] value
|
|
|
|
# the value to add
|
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
def add_matcher(attribute, value)
|
2014-12-22 13:27:12 -05:00
|
|
|
update(matcher: config.matcher.add(attribute, value))
|
2014-07-03 17:16:12 -04:00
|
|
|
end
|
|
|
|
|
2013-06-14 14:54:02 -04:00
|
|
|
end # CLI
|
|
|
|
end # Mutant
|