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.
@@ -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
@@ -0,0 +1,11 @@
1
+ module RCheck
2
+ module Conf
3
+ def self.[](name)
4
+ Command.active[name]
5
+ end
6
+
7
+ def self.valid_name?(param)
8
+ !Command.find(:_base).read(param).nil?
9
+ end
10
+ end
11
+ end
@@ -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