rcheck 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 +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +28 -0
- data/README.md +36 -0
- data/Rakefile +37 -0
- data/TODO +20 -0
- data/bin/rcheck +10 -0
- data/lib/rcheck.rb +40 -0
- data/lib/rcheck/assertions.rb +122 -0
- data/lib/rcheck/backtrace.rb +39 -0
- data/lib/rcheck/class_methods.rb +18 -0
- data/lib/rcheck/cli.rb +57 -0
- data/lib/rcheck/colors.rb +47 -0
- data/lib/rcheck/command.rb +158 -0
- data/lib/rcheck/conf.rb +11 -0
- data/lib/rcheck/debugging.rb +32 -0
- data/lib/rcheck/default_commands.rb +29 -0
- data/lib/rcheck/dsl.rb +25 -0
- data/lib/rcheck/errors.rb +36 -0
- data/lib/rcheck/filters.rb +21 -0
- data/lib/rcheck/formatting.rb +58 -0
- data/lib/rcheck/headers.rb +24 -0
- data/lib/rcheck/option_expander.rb +57 -0
- data/lib/rcheck/options.rb +60 -0
- data/lib/rcheck/progress_printers.rb +16 -0
- data/lib/rcheck/report_printers.rb +118 -0
- data/lib/rcheck/result.rb +38 -0
- data/lib/rcheck/suite.rb +144 -0
- data/lib/rcheck/trollop.rb +861 -0
- data/lib/rcheck/version.rb +3 -0
- data/rcheck.gemspec +26 -0
- data/tests/rcheck/class_methods.rb +50 -0
- data/tests/rcheck/version.rb +4 -0
- data/tests/sanity.rb +113 -0
- metadata +119 -0
@@ -0,0 +1,158 @@
|
|
1
|
+
module RCheck
|
2
|
+
class Command
|
3
|
+
# Command definition dictionary that stores named definitions
|
4
|
+
DEFINITIONS = {}
|
5
|
+
# Command definitions not included in listings
|
6
|
+
HIDDEN = []
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Return the currently running RCheck command.
|
10
|
+
# RCheck may only be invoked once during the life cycle
|
11
|
+
# of the running Ruby interpreter.
|
12
|
+
def active
|
13
|
+
@active || raise("tried to look up command outside command")
|
14
|
+
end
|
15
|
+
|
16
|
+
# Hide a named command definition from listings
|
17
|
+
def hide(*args) HIDDEN.push(*args.flatten) end
|
18
|
+
# Find and return a named command definition
|
19
|
+
def find(name) DEFINITIONS[name] end
|
20
|
+
# Returns true if a command has been invoked
|
21
|
+
def active?() !!@active end
|
22
|
+
|
23
|
+
# Sets the invoked command. Can only be done once.
|
24
|
+
def active=(inv)
|
25
|
+
@active && raise('already invoked')
|
26
|
+
@active = inv
|
27
|
+
end
|
28
|
+
|
29
|
+
# Defines a named command
|
30
|
+
def define(name, *args)
|
31
|
+
DEFINITIONS[name] = new(name, *args)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns an array of available commands
|
35
|
+
def available
|
36
|
+
DEFINITIONS.values.reject { |c| HIDDEN.include? c.name }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Invokes an RCheck command by expanding and using
|
40
|
+
# the given commands and options.
|
41
|
+
def invoke!(*args)
|
42
|
+
args << :_default if args.none? {|a| a.is_a? Symbol }
|
43
|
+
new(nil, '(main)', *(args.unshift(:_base))).invoke!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader(*%i(name desc implied_commands config))
|
48
|
+
|
49
|
+
def initialize(name, desc, *args)
|
50
|
+
@name = name # if this is a named command definition
|
51
|
+
@desc = desc # help text
|
52
|
+
@implied_commands = []
|
53
|
+
@config = {}
|
54
|
+
@cache = {}
|
55
|
+
expand args
|
56
|
+
end
|
57
|
+
|
58
|
+
def suite()
|
59
|
+
RCheck[self[:suite]] ||
|
60
|
+
raise(Errors::NoSuchSuite, "no suite called #{self[:suite]}")
|
61
|
+
end
|
62
|
+
|
63
|
+
def read(param)
|
64
|
+
@config[param].tap {|v| return v unless v.nil? }
|
65
|
+
@implied_commands.reverse.each do |name|
|
66
|
+
inv = self.class.find(name) ||
|
67
|
+
raise(Errors::InvocationName,
|
68
|
+
"unknown invocation #{name.inspect}")
|
69
|
+
inv.read(param).tap {|v| return v unless v.nil? }
|
70
|
+
end; nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def [](param)
|
74
|
+
@cache[param] ||= OptionExpander[param, read(param)]
|
75
|
+
if @cache[param].nil?
|
76
|
+
raise(Errors::ConfigName,
|
77
|
+
"no configuration called #{param.inspect}")
|
78
|
+
end; @cache[param] # TODO
|
79
|
+
end
|
80
|
+
|
81
|
+
def invoke!
|
82
|
+
self.class.active = self
|
83
|
+
require_initializers
|
84
|
+
dispatch
|
85
|
+
end
|
86
|
+
|
87
|
+
def dispatch
|
88
|
+
%i(pry how).each do |option|
|
89
|
+
return send option if Conf[option]
|
90
|
+
end
|
91
|
+
run_tests
|
92
|
+
end
|
93
|
+
|
94
|
+
def how
|
95
|
+
print 'Command chain: '
|
96
|
+
print implied_commands.map(&:inspect).join(', ')
|
97
|
+
puts ' ' + @config.inspect
|
98
|
+
puts
|
99
|
+
Options.names.each do |name|
|
100
|
+
puts " %15s: #{self[name]}" % [name.to_s]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def pry
|
105
|
+
require 'pry'
|
106
|
+
RCheck.binding.pry
|
107
|
+
end
|
108
|
+
|
109
|
+
def run_tests
|
110
|
+
Colors.cputs :quiet, Array(self[:headers]).map(&:call)
|
111
|
+
puts if self[:progress].any?
|
112
|
+
require_test_files
|
113
|
+
%i(progress report).each {|v| make_space v }
|
114
|
+
suite.report!
|
115
|
+
exit %i(pass pending).include?(suite.severity(:total)) ?
|
116
|
+
self[:success_code] : self[:fail_code]
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def make_space(for_what)
|
122
|
+
puts if self[for_what].any? && suite.total(:all).any?
|
123
|
+
end
|
124
|
+
|
125
|
+
def expand(args)
|
126
|
+
args.each do |arg|
|
127
|
+
case arg
|
128
|
+
when Hash then @config.merge! arg
|
129
|
+
when Symbol then @implied_commands << arg
|
130
|
+
else raise Errors::Argument, "unexpected #{arg.inspect}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def expand_globs(glob_name, randomizer=nil)
|
136
|
+
Array(self[glob_name]).map do |glob|
|
137
|
+
Dir[glob].map do |file|
|
138
|
+
File.expand_path File.join(
|
139
|
+
File.dirname(file), File.basename(file, '.*'))
|
140
|
+
end
|
141
|
+
end.flatten
|
142
|
+
end
|
143
|
+
|
144
|
+
def require_globs(glob_name, randomizer=nil)
|
145
|
+
files = expand_globs(glob_name)
|
146
|
+
files.shuffle!(random: randomizer) if randomizer
|
147
|
+
files.each {|path| require path }
|
148
|
+
end
|
149
|
+
|
150
|
+
def require_test_files
|
151
|
+
require_globs(:files, Random.new(self[:seed]))
|
152
|
+
end
|
153
|
+
|
154
|
+
def require_initializers
|
155
|
+
require_globs(:initializers)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
data/lib/rcheck/conf.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module RCheck
|
2
|
+
module Debugging
|
3
|
+
module SuiteMethods
|
4
|
+
def debug(*args)
|
5
|
+
verify_not_done!
|
6
|
+
unless @assertions.any?
|
7
|
+
raise Errors::NoAssertions, 'debugger without a preceding assertion'
|
8
|
+
end
|
9
|
+
@assertions.last.debuggers << Debugger.new(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Debugger
|
14
|
+
attr_reader(*%i(items backtrace))
|
15
|
+
|
16
|
+
def initialize(*items)
|
17
|
+
@items = items
|
18
|
+
@backtrace = Backtrace.parse caller(3)
|
19
|
+
end
|
20
|
+
|
21
|
+
def join
|
22
|
+
@items.map do |data|
|
23
|
+
data.is_a?(String) ? data : data.inspect
|
24
|
+
end.join(' ')
|
25
|
+
end
|
26
|
+
|
27
|
+
def location
|
28
|
+
@backtrace.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RCheck
|
2
|
+
Command.hide %i(_base _default)
|
3
|
+
|
4
|
+
define :_base, 'used as a base for all other commands', Options::BASE
|
5
|
+
define :_default, 'used when nothing else given', Options::DEFAULT
|
6
|
+
|
7
|
+
define :quiet, 'turn off all reporting and headers',
|
8
|
+
headers: [], progress: [], report: []
|
9
|
+
|
10
|
+
define :tree, 'show all suites in a tree',
|
11
|
+
:quiet, report: ReportPrinters::Tree.new(show: :all)
|
12
|
+
|
13
|
+
define :list, 'show list of problematic assertions',
|
14
|
+
:quiet, report: ReportPrinters::List.new(show: :problematic)
|
15
|
+
|
16
|
+
define :all, 'list all assertions',
|
17
|
+
:quiet, report: ReportPrinters::List.new(show: :all)
|
18
|
+
|
19
|
+
define :numbers, 'show assertion counts',
|
20
|
+
:quiet, report: 'numbers'
|
21
|
+
|
22
|
+
# define(:html,
|
23
|
+
# progress_printers: %i(),
|
24
|
+
# report_printers: %i(Html)
|
25
|
+
# )
|
26
|
+
|
27
|
+
define :itself, 'run RCheck\'s own test suite',
|
28
|
+
:_default, files: "#{RCheck.which}/tests/**/*.rb"
|
29
|
+
end
|
data/lib/rcheck/dsl.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
module RCheck
|
3
|
+
module DSL
|
4
|
+
class Scope
|
5
|
+
def initialize(suite)
|
6
|
+
@__suite__ = suite
|
7
|
+
end
|
8
|
+
extend Forwardable
|
9
|
+
def_delegators(:@__suite__,
|
10
|
+
*%i(suite assert refute assert_safe assert_raises debug pending))
|
11
|
+
|
12
|
+
def respond_to?(name)
|
13
|
+
super || @__suite__.parent && @__suite__.parent.scope.respond_to?(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(*args)
|
17
|
+
if @__suite__.parent
|
18
|
+
@__suite__.parent.scope.send(*args)
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module RCheck
|
3
|
+
module Errors
|
4
|
+
|
5
|
+
# abstract
|
6
|
+
class Base < RuntimeError; end
|
7
|
+
class State < Base; end
|
8
|
+
class Argument < Base; end
|
9
|
+
class Name < Argument; end
|
10
|
+
|
11
|
+
# invalid arguments for invocation
|
12
|
+
class InvocationName < Name; end
|
13
|
+
|
14
|
+
# invalid arguments for invocation
|
15
|
+
class NoSuchSuite < Name; end
|
16
|
+
|
17
|
+
# invalid configuration parameter name
|
18
|
+
class ConfigName < Name; end
|
19
|
+
|
20
|
+
# invalid configuration parameter name
|
21
|
+
class ConfigParam < Name; end
|
22
|
+
|
23
|
+
# Tried to do something that requires an invocation
|
24
|
+
class NoInvocation < State; end
|
25
|
+
|
26
|
+
# tried to redefine suite after printing
|
27
|
+
class SuiteRedefinition < State; end
|
28
|
+
|
29
|
+
# tried to require same test more than once
|
30
|
+
class ReRequire < State; end
|
31
|
+
|
32
|
+
# tried to do something that requires assertions to be defined
|
33
|
+
# before they were defined
|
34
|
+
class NoAssertions < State; end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RCheck
|
2
|
+
module Filters
|
3
|
+
def self.port_regex(str)
|
4
|
+
Regexp.new(str.gsub("/", File::SEPARATOR))
|
5
|
+
end
|
6
|
+
|
7
|
+
InstalledGems = port_regex('/lib\d*/ruby/')
|
8
|
+
Executables = port_regex('bin/')
|
9
|
+
Rcheck = port_regex('lib/rcheck')
|
10
|
+
Jruby = port_regex('org/jruby/')
|
11
|
+
|
12
|
+
def self.gem(name)
|
13
|
+
sep = File::SEPARATOR
|
14
|
+
/#{sep}#{name}(-[^#{sep}]+)?#{sep}/
|
15
|
+
end
|
16
|
+
|
17
|
+
module Anti
|
18
|
+
Cwd = Regexp.new(Dir.getwd)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RCheck
|
2
|
+
module Formatting
|
3
|
+
def self.truncate(*lines)
|
4
|
+
lines.map do |line|
|
5
|
+
if line.length < Conf[:max_cols]
|
6
|
+
line
|
7
|
+
else
|
8
|
+
msg = ".. [#{line.length} c]"
|
9
|
+
"#{line[0..(Conf[:max_cols] - msg.length)]}#{msg}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Formatter
|
16
|
+
|
17
|
+
def trunc_cols(*lines)
|
18
|
+
end
|
19
|
+
|
20
|
+
def trunc_rows(*lines)
|
21
|
+
if lines.count < max_rows
|
22
|
+
lines
|
23
|
+
else
|
24
|
+
first = lines[0..(max_rows/2)]
|
25
|
+
last = lines[(max_rows/2), -1]
|
26
|
+
removed = lines.count - (first + last).count
|
27
|
+
first + ["< ! #{removed} lines removed... >"] + last
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def trunc(*lines)
|
32
|
+
trunc_cols(*trunc_rows(*lines))
|
33
|
+
end
|
34
|
+
|
35
|
+
def indent_with_opts(margin, bullet, bullet_all, *lines)
|
36
|
+
pad = ' ' * margin
|
37
|
+
bullet_pad = (' ' * (margin - 3)) + " #{bullet} "
|
38
|
+
result = []
|
39
|
+
result << bullet_pad + lines.shift if lines.any?
|
40
|
+
lines.each do |line|
|
41
|
+
result << (bullet_all ? bullet_pad : pad) + line
|
42
|
+
end
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def format_with_bullet(margin, bullet, *lines)
|
47
|
+
trunc indent_with_opts(margin, bullet, false, *lines)
|
48
|
+
end
|
49
|
+
|
50
|
+
def format_with_bullets(margin, bullet, *lines)
|
51
|
+
trunc indent_with_opts(margin, bullet, true, *lines)
|
52
|
+
end
|
53
|
+
|
54
|
+
def format(margin, *lines)
|
55
|
+
trunc indent_with_opts(margin, ' ', false, *lines)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module RCheck
|
4
|
+
module Headers
|
5
|
+
class Rcheck
|
6
|
+
def call
|
7
|
+
[RCheck.version, "#{Date.today.to_s} seed: #{Conf[:seed]}"]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Platform
|
12
|
+
def call() "#{RUBY_PLATFORM}" end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Commit
|
16
|
+
def call() [cwd_commit_hash].compact end
|
17
|
+
|
18
|
+
def cwd_commit_hash
|
19
|
+
path = File.expand_path File.join(*%w(.git refs heads master))
|
20
|
+
File.read(path).chomp if File.file? path
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RCheck
|
2
|
+
module OptionExpander
|
3
|
+
def self.constantize(name)
|
4
|
+
name.to_s.split('_').map(&:capitalize).join.to_sym
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.[](name, value)
|
8
|
+
return [] if value == :none
|
9
|
+
case name
|
10
|
+
when :colors then parse_theme value
|
11
|
+
when :progress then to_instance(ProgressPrinters, *value)
|
12
|
+
when :report then to_instance(ReportPrinters, *value)
|
13
|
+
when :filters then to_instance(Filters, *value)
|
14
|
+
when :anti_filters then to_instance(Filters::Anti, *value)
|
15
|
+
when :headers then to_instance(Headers, *value)
|
16
|
+
when :files, :initializers then parse_string_array value
|
17
|
+
when :seed, :fail_code, :success_code
|
18
|
+
value.to_s.to_i
|
19
|
+
else value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.to_instance(scope, *list)
|
24
|
+
list.map do |item|
|
25
|
+
case item
|
26
|
+
when Class then item.new
|
27
|
+
when String, Symbol then safer_const_get(scope, item.to_sym)
|
28
|
+
else item
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.safer_const_get(scope, item)
|
34
|
+
if scope.const_defined?(constantize item)
|
35
|
+
val = scope.const_get(constantize item)
|
36
|
+
val.respond_to?(:new) ? val.new : val
|
37
|
+
else
|
38
|
+
raise Errors::ConfigParam, "#{item.inspect} not found in "\
|
39
|
+
"#{scope.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.parse_string_array(value)
|
44
|
+
Array(value).map(&:to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.parse_theme(theme)
|
48
|
+
return theme if theme.is_a?(Hash)
|
49
|
+
name = :"#{theme.upcase}"
|
50
|
+
if Colors::Themes.const_defined?(name)
|
51
|
+
Colors::Themes.const_get(name)
|
52
|
+
else
|
53
|
+
raise "Invalid color theme: #{theme.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|