rcheck 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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