tryouts 2.3.0 → 2.3.1
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 +4 -4
- data/README.md +1 -1
- data/VERSION.yml +4 -0
- data/exe/try +37 -23
- data/lib/tryouts/console.rb +122 -0
- data/lib/tryouts/section.rb +27 -0
- data/lib/tryouts/testbatch.rb +59 -0
- data/lib/tryouts/testcase.rb +97 -0
- data/lib/tryouts/version.rb +6 -4
- data/lib/tryouts.rb +167 -353
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c9881b556aa0492a4e0cba92343efa2c9761664a9507ac3d123fee8143301b8
|
4
|
+
data.tar.gz: 245f405149281445c9bb902695919f6fbd4c5ec461303f16c7078ed6ddcbe111
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ced6968dc159ab82ffcc8902b9835977bfdac9fbf1f8290ce20c216a09fb07fe7731235ac464827094cdebb422048ae963cf396fcb604c37d8945d73d148a619
|
7
|
+
data.tar.gz: b57ed46b023c084209153176fcbb05c4ec710cf1da0a77e61db1ac14e50b87f4bd1c5d35c73e2b9a950d855415e4728d5f229138bb6d8d51edab1bb3df8961f3
|
data/README.md
CHANGED
data/VERSION.yml
ADDED
data/exe/try
CHANGED
@@ -3,35 +3,49 @@
|
|
3
3
|
require 'optparse'
|
4
4
|
require_relative '../lib/tryouts'
|
5
5
|
|
6
|
-
#
|
7
|
-
|
6
|
+
# Adding local lib directories allows the test files
|
7
|
+
# to require the ruby code they are testing without
|
8
|
+
# needing even if the files use regular requires. It
|
9
|
+
# skips the test files from having to muck with the
|
10
|
+
# load path or use `require_relative` which is easy
|
11
|
+
# to forget. This is a convenience feature.
|
12
|
+
#
|
13
|
+
# Here we're looking for directories relative to the
|
14
|
+
# current working directory (i.e. where this script
|
15
|
+
# is being run from.
|
16
|
+
lib_glob = File.join(Dir.pwd, '{lib,../lib,.}')
|
17
|
+
Tryouts.update_load_path(lib_glob)
|
8
18
|
|
9
19
|
# Parse command-line arguments
|
10
|
-
options = { quiet: false, noisy: false, fails: false }
|
11
20
|
OptionParser.new do |opts|
|
12
|
-
opts.on('-V', '--version', 'Display the version')
|
13
|
-
|
14
|
-
|
15
|
-
|
21
|
+
opts.on('-V', '--version', 'Display the version') do
|
22
|
+
puts "Tryouts: #{Tryouts::VERSION}"
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
opts.on('-q', '--quiet', 'Run in quiet mode') { Tryouts.quiet = true }
|
26
|
+
opts.on('-v', '--verbose', 'Run in verbose mode') { Tryouts.noisy = true }
|
27
|
+
opts.on('-f', '--fails', 'Show only failing tryouts') { Tryouts.fails = true }
|
16
28
|
opts.on('-D', '--debug', 'Run in debug mode') { Tryouts.debug = true }
|
17
|
-
opts.on('-h', '--help', 'Display this help')
|
29
|
+
opts.on('-h', '--help', 'Display this help') do
|
30
|
+
puts opts
|
31
|
+
exit
|
32
|
+
end
|
18
33
|
end.parse!
|
19
34
|
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
35
|
+
# Find tryouts path with a path glob unless thin
|
36
|
+
# script was called with arguments in which case
|
37
|
+
# we consume those as a list of paths.
|
38
|
+
paths = if ARGV.empty?
|
39
|
+
Dir.glob(
|
40
|
+
['./{try,tryouts/,.}/*_{try,tryouts}.rb'],
|
41
|
+
base: Dir.pwd
|
42
|
+
).sort # deterministic order
|
24
43
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
['./{try,tryouts/,.}/*_{try,tryouts}.rb'],
|
29
|
-
base: Dir.pwd,
|
30
|
-
sort: true # deterministic order
|
31
|
-
)
|
32
|
-
|
33
|
-
else
|
34
|
-
paths = ARGV
|
35
|
-
end
|
44
|
+
else
|
45
|
+
ARGV
|
46
|
+
end
|
36
47
|
|
48
|
+
# Running the tryouts returns the number of
|
49
|
+
# test failures so here we pass that value
|
50
|
+
# through as the exit code for the script.
|
37
51
|
exit Tryouts.run_all(*paths)
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
module Console
|
5
|
+
# ANSI escape sequence numbers for text attributes
|
6
|
+
unless defined? ATTRIBUTES
|
7
|
+
ATTRIBUTES = {
|
8
|
+
normal: 0,
|
9
|
+
bright: 1,
|
10
|
+
dim: 2,
|
11
|
+
underline: 4,
|
12
|
+
blink: 5,
|
13
|
+
reverse: 7,
|
14
|
+
hidden: 8,
|
15
|
+
default: 0
|
16
|
+
}.freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
# ANSI escape sequence numbers for text colours
|
20
|
+
unless defined? COLOURS
|
21
|
+
COLOURS = {
|
22
|
+
black: 30,
|
23
|
+
red: 31,
|
24
|
+
green: 32,
|
25
|
+
yellow: 33,
|
26
|
+
blue: 34,
|
27
|
+
magenta: 35,
|
28
|
+
cyan: 36,
|
29
|
+
white: 37,
|
30
|
+
default: 39,
|
31
|
+
random: 30 + rand(10).to_i
|
32
|
+
}.freeze
|
33
|
+
end
|
34
|
+
|
35
|
+
# ANSI escape sequence numbers for background colours
|
36
|
+
unless defined? BGCOLOURS
|
37
|
+
BGCOLOURS = {
|
38
|
+
black: 40,
|
39
|
+
red: 41,
|
40
|
+
green: 42,
|
41
|
+
yellow: 43,
|
42
|
+
blue: 44,
|
43
|
+
magenta: 45,
|
44
|
+
cyan: 46,
|
45
|
+
white: 47,
|
46
|
+
default: 49,
|
47
|
+
random: 40 + rand(10).to_i
|
48
|
+
}.freeze
|
49
|
+
end
|
50
|
+
|
51
|
+
module InstanceMethods
|
52
|
+
def bright
|
53
|
+
Console.bright(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def underline
|
57
|
+
Console.underline(self)
|
58
|
+
end
|
59
|
+
|
60
|
+
def reverse
|
61
|
+
Console.reverse(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def color(col)
|
65
|
+
Console.color(col, self)
|
66
|
+
end
|
67
|
+
|
68
|
+
def att(col)
|
69
|
+
Console.att(col, self)
|
70
|
+
end
|
71
|
+
|
72
|
+
def bgcolor(col)
|
73
|
+
Console.bgcolor(col, self)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.bright(str)
|
78
|
+
str = [style(ATTRIBUTES[:bright]), str, default_style].join
|
79
|
+
str.extend Console::InstanceMethods
|
80
|
+
str
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.underline(str)
|
84
|
+
str = [style(ATTRIBUTES[:underline]), str, default_style].join
|
85
|
+
str.extend Console::InstanceMethods
|
86
|
+
str
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.reverse(str)
|
90
|
+
str = [style(ATTRIBUTES[:reverse]), str, default_style].join
|
91
|
+
str.extend Console::InstanceMethods
|
92
|
+
str
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.color(col, str)
|
96
|
+
str = [style(COLOURS[col]), str, default_style].join
|
97
|
+
str.extend Console::InstanceMethods
|
98
|
+
str
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.att(name, str)
|
102
|
+
str = [style(ATTRIBUTES[name]), str, default_style].join
|
103
|
+
str.extend Console::InstanceMethods
|
104
|
+
str
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.bgcolor(col, str)
|
108
|
+
str = [style(ATTRIBUTES[col]), str, default_style].join
|
109
|
+
str.extend Console::InstanceMethods
|
110
|
+
str
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.style(*att)
|
114
|
+
# => \e[8;34;42m
|
115
|
+
"\e[%sm" % att.join(';')
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.default_style
|
119
|
+
style(ATTRIBUTES[:default], ATTRIBUTES[:COLOURS], ATTRIBUTES[:BGCOLOURS])
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
class Tryouts
|
4
|
+
class Section < Array
|
5
|
+
attr_accessor :path, :first, :last
|
6
|
+
|
7
|
+
def initialize(path, start = 0)
|
8
|
+
@path = path
|
9
|
+
@first = start
|
10
|
+
@last = start
|
11
|
+
end
|
12
|
+
|
13
|
+
def range
|
14
|
+
@first..@last
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
range.to_a.zip(self).collect do |line|
|
19
|
+
"%-4d %s\n" % line
|
20
|
+
end.join
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
join($/)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
class Tryouts
|
4
|
+
class TestBatch < Array
|
5
|
+
class Container
|
6
|
+
def metaclass
|
7
|
+
class << self; end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
attr_reader :path, :failed, :lines
|
11
|
+
|
12
|
+
def initialize(p, l)
|
13
|
+
@path = p
|
14
|
+
@lines = l
|
15
|
+
@container = Container.new.metaclass
|
16
|
+
@run = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(before_test, &after_test)
|
20
|
+
return if empty?
|
21
|
+
|
22
|
+
setup
|
23
|
+
ret = self.select do |tc|
|
24
|
+
before_test.call(tc) unless before_test.nil?
|
25
|
+
ret = !tc.run
|
26
|
+
after_test.call(tc)
|
27
|
+
ret # select failed tests
|
28
|
+
end
|
29
|
+
@failed = ret.size
|
30
|
+
@run = true
|
31
|
+
clean
|
32
|
+
!failed?
|
33
|
+
end
|
34
|
+
|
35
|
+
def failed?
|
36
|
+
!@failed.nil? && @failed > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup
|
40
|
+
return if empty?
|
41
|
+
|
42
|
+
start = first.desc.nil? ? first.test.first : first.desc.first - 1
|
43
|
+
Tryouts.eval lines[0..start - 1].join, path, 0 if start > 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def clean
|
47
|
+
return if empty?
|
48
|
+
|
49
|
+
last_line = last.exps.last + 1
|
50
|
+
return unless last_line < lines.size
|
51
|
+
|
52
|
+
Tryouts.eval lines[last_line..-1].join, path, last_line
|
53
|
+
end
|
54
|
+
|
55
|
+
def run?
|
56
|
+
@run
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
class TestCase
|
5
|
+
attr_reader :desc, :test, :exps, :path, :outlines, :test_result
|
6
|
+
|
7
|
+
def initialize(d, t, e)
|
8
|
+
@desc, @test, @exps, @path = d, t, e
|
9
|
+
@outlines = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
[@desc.inspect, @test.inspect, @exps.inspect].join
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
[@desc.to_s, @test.to_s, @exps.to_s].join
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
Tryouts.debug format('%s:%d', @test.path, @test.first)
|
22
|
+
Tryouts.debug inspect, $/
|
23
|
+
$stdout = StringIO.new
|
24
|
+
expectations = exps.collect do |exp, _idx|
|
25
|
+
exp =~ /\A\#?\s*=>\s*(.+)\Z/
|
26
|
+
::Regexp.last_match(1) # this will be nil if the expectation is commented out
|
27
|
+
end
|
28
|
+
|
29
|
+
# Evaluate test block only if there are valid expectations
|
30
|
+
unless expectations.compact.empty? # TODO: fast-fail if no expectations
|
31
|
+
test_value = Tryouts.eval @test.to_s, @test.path, @test.first
|
32
|
+
@has_run = true
|
33
|
+
end
|
34
|
+
$stdout = STDOUT # restore stdout
|
35
|
+
|
36
|
+
|
37
|
+
expectations.each_with_index do |exp, idx|
|
38
|
+
if exp.nil?
|
39
|
+
@outlines << ' [skipped]'
|
40
|
+
@test_result = 0
|
41
|
+
else
|
42
|
+
# Evaluate expectation
|
43
|
+
|
44
|
+
exp_value = Tryouts.eval(exp, @exps.path, @exps.first + idx)
|
45
|
+
|
46
|
+
test_passed = test_value.eql?(exp_value)
|
47
|
+
@test_result = test_passed ? 1 : -1
|
48
|
+
@outlines << test_value.inspect
|
49
|
+
end
|
50
|
+
end
|
51
|
+
Tryouts.debug # extra newline
|
52
|
+
failed?
|
53
|
+
|
54
|
+
rescue Exception => e
|
55
|
+
Tryouts.debug e.message, e.backtrace.join($/), $/
|
56
|
+
$stdout = STDOUT # restore stdout
|
57
|
+
end
|
58
|
+
|
59
|
+
def run?
|
60
|
+
@has_run.eql?(true)
|
61
|
+
end
|
62
|
+
|
63
|
+
def skipped?
|
64
|
+
@test_result == 0
|
65
|
+
end
|
66
|
+
|
67
|
+
def passed?
|
68
|
+
@test_result == 1
|
69
|
+
end
|
70
|
+
def failed?
|
71
|
+
@test_result == -1
|
72
|
+
end
|
73
|
+
|
74
|
+
def color
|
75
|
+
case @test_result
|
76
|
+
when 1
|
77
|
+
:green
|
78
|
+
when 0
|
79
|
+
:white
|
80
|
+
else
|
81
|
+
:red
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def adjective
|
86
|
+
case @test_result
|
87
|
+
when 1
|
88
|
+
'PASSED'
|
89
|
+
when 0
|
90
|
+
'SKIPPED'
|
91
|
+
else
|
92
|
+
'FAILED'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
data/lib/tryouts/version.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Tryouts
|
2
4
|
module VERSION
|
3
5
|
def self.to_s
|
4
|
-
|
5
|
-
[@
|
6
|
+
version_file
|
7
|
+
[@version_file[:MAJOR], @version_file[:MINOR], @version_file[:PATCH]].join('.')
|
6
8
|
end
|
7
9
|
alias_method :inspect, :to_s
|
8
|
-
def self.
|
10
|
+
def self.version_file
|
9
11
|
require 'yaml'
|
10
|
-
@
|
12
|
+
@version_file ||= YAML.load_file(File.join(TRYOUTS_LIB_HOME, '..', 'VERSION.yml'))
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/tryouts.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
TRYOUTS_LIB_HOME = __dir__ unless defined?(TRYOUTS_LIB_HOME)
|
6
6
|
|
7
|
+
require_relative 'tryouts/console'
|
8
|
+
require_relative 'tryouts/section'
|
9
|
+
require_relative 'tryouts/testbatch'
|
10
|
+
require_relative 'tryouts/testcase'
|
7
11
|
require_relative 'tryouts/version'
|
8
12
|
|
9
13
|
class Tryouts
|
@@ -14,9 +18,12 @@ class Tryouts
|
|
14
18
|
@container = Class.new
|
15
19
|
@cases = []
|
16
20
|
@sysinfo = nil
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
@testcase_io = StringIO.new
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
attr_accessor :container, :quiet, :noisy, :fails
|
25
|
+
attr_writer :debug
|
26
|
+
attr_reader :cases, :testcase_io
|
20
27
|
|
21
28
|
def sysinfo
|
22
29
|
require 'sysinfo'
|
@@ -24,17 +31,26 @@ class Tryouts
|
|
24
31
|
@sysinfo
|
25
32
|
end
|
26
33
|
|
27
|
-
def debug?
|
34
|
+
def debug?
|
35
|
+
@debug == true
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_load_path(lib_glob)
|
39
|
+
Dir.glob(lib_glob).each { |dir| $LOAD_PATH.unshift(dir) }
|
40
|
+
end
|
28
41
|
|
29
42
|
def run_all *paths
|
30
43
|
batches = paths.collect do |path|
|
31
44
|
parse path
|
32
45
|
end
|
33
46
|
|
34
|
-
all
|
35
|
-
|
47
|
+
all = 0
|
48
|
+
skipped_tests = 0
|
49
|
+
failed_tests = 0
|
50
|
+
skipped_batches = 0
|
51
|
+
failed_batches = 0
|
36
52
|
|
37
|
-
msg 'Ruby %s @ %-
|
53
|
+
msg format('Ruby %s @ %-60s', RUBY_VERSION, Time.now), $/
|
38
54
|
|
39
55
|
if Tryouts.debug?
|
40
56
|
Tryouts.debug "Found #{paths.size} files:"
|
@@ -43,415 +59,213 @@ class Tryouts
|
|
43
59
|
end
|
44
60
|
|
45
61
|
batches.each do |batch|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
62
|
+
path = batch.path.gsub(%r{#{Dir.pwd}/?}, '')
|
63
|
+
divider = '-' * 70
|
64
|
+
path_pretty = format('>>>>> %-20s %s', path, '').ljust(70, '<')
|
65
|
+
|
66
|
+
msg $/
|
67
|
+
vmsg Console.reverse(divider)
|
68
|
+
vmsg Console.reverse(path_pretty)
|
69
|
+
vmsg Console.reverse(divider)
|
70
|
+
vmsg $/
|
71
|
+
|
72
|
+
before_handler = proc do |tc|
|
73
|
+
if Tryouts.noisy
|
74
|
+
tc_title = tc.desc.to_s
|
75
|
+
vmsg Console.underline(format('%-58s ', tc_title))
|
76
|
+
vmsg tc.test.inspect, tc.exps.inspect
|
55
77
|
end
|
56
78
|
end
|
57
79
|
|
58
|
-
batch.run(before_handler) do |
|
59
|
-
if t.failed?
|
60
|
-
failed_tests += 1
|
61
|
-
if Tryouts.noisy && Tryouts.fails
|
62
|
-
vmsg Console.color(:red, t.failed.join($/)), $/
|
63
|
-
else
|
64
|
-
msg ' %s (%s:%s)' % [Console.color(:red, "FAIL"), path, t.exps.first]
|
65
|
-
end
|
66
|
-
elsif (t.skipped? || !t.run?) && !Tryouts.fails
|
67
|
-
skipped_tests += 1
|
68
|
-
if Tryouts.noisy
|
69
|
-
vmsg Console.bright(t.skipped.join($/)), $/
|
70
|
-
else
|
71
|
-
msg ' SKIP (%s:%s)' % [path, t.exps.first]
|
72
|
-
end
|
73
|
-
elsif !Tryouts.fails
|
74
|
-
if Tryouts.noisy
|
75
|
-
vmsg Console.color(:green, t.passed.join($/)), $/
|
76
|
-
else
|
77
|
-
msg ' %s' % [Console.color(:green, 'PASS')]
|
78
|
-
end
|
79
|
-
end
|
80
|
+
batch.run(before_handler) do |tc|
|
80
81
|
all += 1
|
82
|
+
failed_tests += 1 if tc.failed?
|
83
|
+
skipped_tests += 1 if tc.skipped?
|
84
|
+
codelines = tc.outlines.join($/)
|
85
|
+
first_exp_line = tc.exps.first
|
86
|
+
result_adjective = tc.failed? ? 'FAILED' : 'PASSED'
|
87
|
+
|
88
|
+
first_exp_line = tc.exps.first
|
89
|
+
location = format('%s:%d', tc.exps.path, first_exp_line)
|
90
|
+
|
91
|
+
expectation = Console.color(tc.color, codelines)
|
92
|
+
summary = Console.color(tc.color, "%s @ %s" % [tc.adjective, location])
|
93
|
+
vmsg ' %s' % expectation
|
94
|
+
if tc.failed?
|
95
|
+
msg Console.reverse(summary)
|
96
|
+
else
|
97
|
+
msg summary
|
98
|
+
end
|
99
|
+
vmsg
|
100
|
+
|
101
|
+
# Output buffered testcase_io to stdout
|
102
|
+
# and reset it for the next test case.
|
103
|
+
unless Tryouts.fails && !tc.failed?
|
104
|
+
$stdout.puts testcase_io.string unless Tryouts.quiet
|
105
|
+
end
|
106
|
+
|
107
|
+
# Reset the testcase IO buffer
|
108
|
+
testcase_io.truncate(0)
|
81
109
|
end
|
82
110
|
end
|
83
111
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
suffix = "batches passed"
|
93
|
-
suffix << " (and #{skipped_batches} skipped)" if skipped_batches > 0
|
94
|
-
msg cformat(batches.size-skipped_batches-failed_batches, batches.size-skipped_batches, suffix)
|
112
|
+
# Create a line of separation before the result summary
|
113
|
+
msg $INPUT_RECORD_SEPARATOR # newline
|
114
|
+
|
115
|
+
if all
|
116
|
+
suffix = "tests passed (#{skipped_tests} skipped)" if skipped_tests > 0
|
117
|
+
actual_test_size = all - skipped_tests
|
118
|
+
if actual_test_size > 0
|
119
|
+
msg cformat(all - failed_tests - skipped_tests, all - skipped_tests, suffix)
|
95
120
|
end
|
96
121
|
end
|
97
122
|
|
98
|
-
|
123
|
+
actual_batch_size = (batches.size - skipped_batches)
|
124
|
+
if batches.size > 1 && actual_batch_size > 0
|
125
|
+
suffix = 'batches passed'
|
126
|
+
suffix << " (#{skipped_batches} skipped)" if skipped_batches > 0
|
127
|
+
msg cformat(batches.size - skipped_batches - failed_batches, batches.size - skipped_batches, suffix)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Print out the buffered result summary
|
131
|
+
$stdout.puts testcase_io.string
|
132
|
+
|
133
|
+
failed_tests # returns the number of failed tests (0 if all passed)
|
99
134
|
end
|
100
135
|
|
101
136
|
def cformat(*args)
|
102
137
|
Console.bright '%d of %d %s' % args
|
103
138
|
end
|
104
139
|
|
105
|
-
def run
|
140
|
+
def run(path)
|
106
141
|
batch = parse path
|
107
142
|
batch.run
|
108
143
|
batch
|
109
144
|
end
|
110
145
|
|
111
|
-
def parse
|
112
|
-
#debug "Loading #{path}"
|
146
|
+
def parse(path)
|
147
|
+
# debug "Loading #{path}"
|
113
148
|
lines = File.readlines path
|
114
149
|
skip_ahead = 0
|
115
150
|
batch = TestBatch.new path, lines
|
116
151
|
lines.size.times do |idx|
|
117
152
|
skip_ahead -= 1 and next if skip_ahead > 0
|
153
|
+
|
118
154
|
line = lines[idx].chomp
|
119
|
-
#debug('%-4d %s' % [idx, line])
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
155
|
+
# debug('%-4d %s' % [idx, line])
|
156
|
+
next unless expectation? line
|
157
|
+
|
158
|
+
offset = 0
|
159
|
+
exps = Section.new(path, idx + 1)
|
160
|
+
exps << line.chomp
|
161
|
+
while idx + offset < lines.size
|
162
|
+
offset += 1
|
163
|
+
this_line = lines[idx + offset]
|
164
|
+
break if ignore?(this_line)
|
165
|
+
|
166
|
+
if expectation?(this_line)
|
167
|
+
exps << this_line.chomp
|
168
|
+
skip_ahead += 1
|
133
169
|
end
|
170
|
+
exps.last += 1
|
171
|
+
end
|
134
172
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
offset += 1
|
153
|
-
buffer.unshift lines[idx-offset].chomp
|
154
|
-
end
|
155
|
-
end
|
156
|
-
if test_begin?(this_line) || idx-offset == 0 || expectation?(this_line)
|
157
|
-
adjust = expectation?(this_line) ? 2 : 1
|
158
|
-
test.first = idx-offset+buffer.size+adjust
|
159
|
-
desc.unshift *buffer
|
160
|
-
desc.last = test.first-1
|
161
|
-
desc.first = desc.last-desc.size+1
|
162
|
-
# remove empty lines between the description
|
163
|
-
# and the previous expectation
|
164
|
-
while !desc.empty? && desc[0].empty?
|
165
|
-
desc.shift
|
166
|
-
desc.first += 1
|
167
|
-
end
|
168
|
-
break
|
173
|
+
offset = 0
|
174
|
+
buffer = Section.new(path)
|
175
|
+
desc = Section.new(path)
|
176
|
+
test = Section.new(path, idx) # test start the line before the exp.
|
177
|
+
while idx - offset >= 0
|
178
|
+
offset += 1
|
179
|
+
this_line = lines[idx - offset].chomp
|
180
|
+
buffer.unshift this_line if ignore?(this_line)
|
181
|
+
buffer.unshift this_line if comment?(this_line)
|
182
|
+
if test?(this_line)
|
183
|
+
test.unshift(*buffer) && buffer.clear
|
184
|
+
test.unshift this_line
|
185
|
+
end
|
186
|
+
if test_begin?(this_line)
|
187
|
+
while test_begin?(lines[idx - (offset + 1)].chomp)
|
188
|
+
offset += 1
|
189
|
+
buffer.unshift lines[idx - offset].chomp
|
169
190
|
end
|
170
191
|
end
|
171
|
-
|
172
|
-
|
192
|
+
next unless test_begin?(this_line) || idx - offset == 0 || expectation?(this_line)
|
193
|
+
|
194
|
+
adjust = expectation?(this_line) ? 2 : 1
|
195
|
+
test.first = idx - offset + buffer.size + adjust
|
196
|
+
desc.unshift(*buffer)
|
197
|
+
desc.last = test.first - 1
|
198
|
+
desc.first = desc.last - desc.size + 1
|
199
|
+
# remove empty lines between the description
|
200
|
+
# and the previous expectation
|
201
|
+
while !desc.empty? && desc[0].empty?
|
202
|
+
desc.shift
|
203
|
+
desc.first += 1
|
204
|
+
end
|
205
|
+
break
|
173
206
|
end
|
207
|
+
|
208
|
+
batch << TestCase.new(desc, test, exps)
|
174
209
|
end
|
175
210
|
|
176
211
|
batch
|
177
212
|
end
|
178
|
-
|
179
|
-
def print str
|
213
|
+
def print(str)
|
180
214
|
return if Tryouts.quiet
|
181
|
-
|
182
|
-
|
215
|
+
|
216
|
+
$stdout.print str
|
217
|
+
$stdout.flush
|
183
218
|
end
|
184
219
|
|
185
|
-
def vmsg *
|
186
|
-
|
220
|
+
def vmsg *msgs
|
221
|
+
msg(*msgs) if Tryouts.noisy
|
187
222
|
end
|
188
223
|
|
189
|
-
def msg *
|
190
|
-
|
224
|
+
def msg *msgs
|
225
|
+
testcase_io.puts(*msgs) unless Tryouts.quiet
|
191
226
|
end
|
192
227
|
|
193
|
-
def err *
|
228
|
+
def err *msgs
|
194
229
|
msg.each do |line|
|
195
|
-
|
230
|
+
$stderr.puts Console.color :red, line
|
196
231
|
end
|
197
232
|
end
|
198
233
|
|
199
|
-
def debug *
|
200
|
-
|
234
|
+
def debug *msgs
|
235
|
+
$stderr.puts(*msgs) if debug?
|
201
236
|
end
|
202
237
|
|
203
238
|
def eval(str, path, line)
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
nil
|
210
|
-
end
|
239
|
+
Kernel.eval str, @container.send(:binding), path, line
|
240
|
+
rescue SyntaxError, LoadError => e
|
241
|
+
Tryouts.err Console.color(:red, e.message),
|
242
|
+
Console.color(:red, e.backtrace.first)
|
243
|
+
nil
|
211
244
|
end
|
212
245
|
|
213
246
|
private
|
214
247
|
|
215
|
-
def expectation?
|
248
|
+
def expectation?(str)
|
216
249
|
!ignore?(str) && str.strip.match(/\A\#+\s*=>/)
|
217
250
|
end
|
218
251
|
|
219
|
-
def comment?
|
252
|
+
def comment?(str)
|
220
253
|
!str.strip.match(/^\#+/).nil? && !expectation?(str)
|
221
254
|
end
|
222
255
|
|
223
|
-
def test?
|
256
|
+
def test?(str)
|
224
257
|
!ignore?(str) && !expectation?(str) && !comment?(str)
|
225
258
|
end
|
226
259
|
|
227
|
-
def ignore?
|
260
|
+
def ignore?(str)
|
228
261
|
str.to_s.strip.chomp.empty?
|
229
262
|
end
|
230
263
|
|
231
|
-
def test_begin?
|
232
|
-
|
233
|
-
|
234
|
-
ret
|
235
|
-
end
|
236
|
-
|
237
|
-
|
238
|
-
end
|
239
|
-
|
240
|
-
class TestBatch < Array
|
241
|
-
class Container
|
242
|
-
def metaclass
|
243
|
-
class << self; end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
attr_reader :path
|
247
|
-
attr_reader :failed
|
248
|
-
attr_reader :lines
|
249
|
-
def initialize(p,l)
|
250
|
-
@path, @lines = p, l
|
251
|
-
@container = Container.new.metaclass
|
252
|
-
@run = false
|
253
|
-
end
|
254
|
-
def run(before_test, &after_test)
|
255
|
-
return if empty?
|
256
|
-
setup
|
257
|
-
ret = self.select { |tc|
|
258
|
-
before_test.call(tc) unless before_test.nil?
|
259
|
-
ret = !tc.run
|
260
|
-
after_test.call(tc)
|
261
|
-
ret # select failed tests
|
262
|
-
}
|
263
|
-
@failed = ret.size
|
264
|
-
@run = true
|
265
|
-
clean
|
266
|
-
!failed?
|
267
|
-
end
|
268
|
-
def failed?
|
269
|
-
!@failed.nil? && @failed > 0
|
270
|
-
end
|
271
|
-
def setup
|
272
|
-
return if empty?
|
273
|
-
start = first.desc.nil? ? first.test.first : first.desc.first-1
|
274
|
-
Tryouts.eval lines[0..start-1].join, path, 0 if start > 0
|
275
|
-
end
|
276
|
-
def clean
|
277
|
-
return if empty?
|
278
|
-
last_line = last.exps.last+1
|
279
|
-
if last_line < lines.size
|
280
|
-
Tryouts.eval lines[last_line..-1].join, path, last_line
|
281
|
-
end
|
282
|
-
end
|
283
|
-
def run?
|
284
|
-
@run
|
285
|
-
end
|
286
|
-
end
|
287
|
-
class TestCase
|
288
|
-
attr_reader :desc, :test, :exps, :failed, :passed, :skipped
|
289
|
-
def initialize(d,t,e)
|
290
|
-
@desc, @test, @exps, @path = d,t,e
|
291
|
-
end
|
292
|
-
def inspect
|
293
|
-
[@desc.inspect, @test.inspect, @exps.inspect].join
|
294
|
-
end
|
295
|
-
def to_s
|
296
|
-
[@desc.to_s, @test.to_s, @exps.to_s].join
|
297
|
-
end
|
298
|
-
def run
|
299
|
-
Tryouts.debug '%s:%d' % [@test.path, @test.first]
|
300
|
-
Tryouts.debug inspect, $/
|
301
|
-
expectations = exps.collect { |exp,idx|
|
302
|
-
exp =~ /\A\#?\s*=>\s*(.+)\Z/
|
303
|
-
$1 # this will be nil if the expectation is commented out
|
304
|
-
}
|
305
|
-
|
306
|
-
# Evaluate test block only if there are valid expectations
|
307
|
-
unless expectations.compact.empty?
|
308
|
-
test_value = Tryouts.eval @test.to_s, @test.path, @test.first
|
309
|
-
@has_run = true
|
310
|
-
end
|
311
|
-
|
312
|
-
@passed, @failed, @skipped = [], [], []
|
313
|
-
expectations.each_with_index { |exp,idx|
|
314
|
-
if exp.nil?
|
315
|
-
@skipped << ' [skipped]'
|
316
|
-
else
|
317
|
-
exp_value = Tryouts.eval(exp, @exps.path, @exps.first+idx)
|
318
|
-
if test_value == exp_value
|
319
|
-
@passed << ' == %s' % [test_value.inspect]
|
320
|
-
else
|
321
|
-
@failed << ' != %s' % [test_value.inspect]
|
322
|
-
end
|
323
|
-
end
|
324
|
-
}
|
325
|
-
Tryouts.debug
|
326
|
-
@failed.empty?
|
327
|
-
end
|
328
|
-
def skipped?
|
329
|
-
!@skipped.nil? && !@skipped.empty?
|
330
|
-
end
|
331
|
-
def run?
|
332
|
-
@has_run == true
|
333
|
-
end
|
334
|
-
def failed?
|
335
|
-
!@failed.nil? && !@failed.empty?
|
336
|
-
end
|
337
|
-
private
|
338
|
-
def create_proc str, path, line
|
339
|
-
eval("Proc.new {\n #{str}\n}", binding, path, line)
|
340
|
-
end
|
341
|
-
end
|
342
|
-
class Section < Array
|
343
|
-
attr_accessor :path, :first, :last
|
344
|
-
def initialize path, start=0
|
345
|
-
@path = path
|
346
|
-
@first, @last = start, start
|
347
|
-
end
|
348
|
-
def range
|
349
|
-
@first..@last
|
350
|
-
end
|
351
|
-
def inspect
|
352
|
-
range.to_a.zip(self).collect do |line|
|
353
|
-
"%-4d %s\n" % line
|
354
|
-
end.join
|
355
|
-
end
|
356
|
-
def to_s
|
357
|
-
self.join($/)
|
358
|
-
end
|
359
|
-
end
|
360
|
-
|
361
|
-
|
362
|
-
module Console
|
363
|
-
|
364
|
-
# ANSI escape sequence numbers for text attributes
|
365
|
-
ATTRIBUTES = {
|
366
|
-
:normal => 0,
|
367
|
-
:bright => 1,
|
368
|
-
:dim => 2,
|
369
|
-
:underline => 4,
|
370
|
-
:blink => 5,
|
371
|
-
:reverse => 7,
|
372
|
-
:hidden => 8,
|
373
|
-
:default => 0,
|
374
|
-
}.freeze unless defined? ATTRIBUTES
|
375
|
-
|
376
|
-
# ANSI escape sequence numbers for text colours
|
377
|
-
COLOURS = {
|
378
|
-
:black => 30,
|
379
|
-
:red => 31,
|
380
|
-
:green => 32,
|
381
|
-
:yellow => 33,
|
382
|
-
:blue => 34,
|
383
|
-
:magenta => 35,
|
384
|
-
:cyan => 36,
|
385
|
-
:white => 37,
|
386
|
-
:default => 39,
|
387
|
-
:random => 30 + rand(10).to_i
|
388
|
-
}.freeze unless defined? COLOURS
|
389
|
-
|
390
|
-
# ANSI escape sequence numbers for background colours
|
391
|
-
BGCOLOURS = {
|
392
|
-
:black => 40,
|
393
|
-
:red => 41,
|
394
|
-
:green => 42,
|
395
|
-
:yellow => 43,
|
396
|
-
:blue => 44,
|
397
|
-
:magenta => 45,
|
398
|
-
:cyan => 46,
|
399
|
-
:white => 47,
|
400
|
-
:default => 49,
|
401
|
-
:random => 40 + rand(10).to_i
|
402
|
-
}.freeze unless defined? BGCOLOURS
|
403
|
-
|
404
|
-
module InstanceMethods
|
405
|
-
def bright
|
406
|
-
Console.bright(self)
|
407
|
-
end
|
408
|
-
def reverse
|
409
|
-
Console.reverse(self)
|
410
|
-
end
|
411
|
-
def color(col)
|
412
|
-
Console.color(col, self)
|
413
|
-
end
|
414
|
-
def att(col)
|
415
|
-
Console.att(col, self)
|
416
|
-
end
|
417
|
-
def bgcolor(col)
|
418
|
-
Console.bgcolor(col, self)
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
def self.bright(str)
|
423
|
-
str = [style(ATTRIBUTES[:bright]), str, default_style].join
|
424
|
-
str.extend Console::InstanceMethods
|
425
|
-
str
|
426
|
-
end
|
427
|
-
def self.reverse(str)
|
428
|
-
str = [style(ATTRIBUTES[:reverse]), str, default_style].join
|
429
|
-
str.extend Console::InstanceMethods
|
430
|
-
str
|
431
|
-
end
|
432
|
-
def self.color(col, str)
|
433
|
-
str = [style(COLOURS[col]), str, default_style].join
|
434
|
-
str.extend Console::InstanceMethods
|
435
|
-
str
|
436
|
-
end
|
437
|
-
def self.att(name, str)
|
438
|
-
str = [style(ATTRIBUTES[name]), str, default_style].join
|
439
|
-
str.extend Console::InstanceMethods
|
440
|
-
str
|
441
|
-
end
|
442
|
-
def self.bgcolor(col, str)
|
443
|
-
str = [style(ATTRIBUTES[col]), str, default_style].join
|
444
|
-
str.extend Console::InstanceMethods
|
445
|
-
str
|
446
|
-
end
|
447
|
-
private
|
448
|
-
def self.style(*att)
|
449
|
-
# => \e[8;34;42m
|
450
|
-
"\e[%sm" % att.join(';')
|
451
|
-
end
|
452
|
-
def self.default_style
|
453
|
-
style(ATTRIBUTES[:default], ATTRIBUTES[:COLOURS], ATTRIBUTES[:BGCOLOURS])
|
264
|
+
def test_begin?(str)
|
265
|
+
!str.strip.match(/\#+\s*TEST/i).nil? ||
|
266
|
+
!str.strip.match(/\A\#\#+[\s\w]+/i).nil?
|
454
267
|
end
|
455
268
|
end
|
456
269
|
|
270
|
+
extend ClassMethods
|
457
271
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tryouts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.3.
|
4
|
+
version: 2.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-06-
|
11
|
+
date: 2024-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A simple test framework for Ruby code that uses introspection to allow
|
14
14
|
defining checks in comments.
|
@@ -21,9 +21,14 @@ extra_rdoc_files: []
|
|
21
21
|
files:
|
22
22
|
- LICENSE.txt
|
23
23
|
- README.md
|
24
|
+
- VERSION.yml
|
24
25
|
- exe/try
|
25
26
|
- exe/tryouts
|
26
27
|
- lib/tryouts.rb
|
28
|
+
- lib/tryouts/console.rb
|
29
|
+
- lib/tryouts/section.rb
|
30
|
+
- lib/tryouts/testbatch.rb
|
31
|
+
- lib/tryouts/testcase.rb
|
27
32
|
- lib/tryouts/version.rb
|
28
33
|
homepage: https://github.com/delano/tryouts
|
29
34
|
licenses:
|
@@ -44,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
49
|
- !ruby/object:Gem::Version
|
45
50
|
version: '0'
|
46
51
|
requirements: []
|
47
|
-
rubygems_version: 3.
|
52
|
+
rubygems_version: 3.4.19
|
48
53
|
signing_key:
|
49
54
|
specification_version: 4
|
50
55
|
summary: Ruby tests that read like documentation.
|