rcheck 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|