tryouts 2.3.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 964b15141b2d2a8658bb5beee61ef9618ad478b8c29a4c91c5595dfb7ac92323
4
- data.tar.gz: b33744a232df6dd0020deef5ea682217481ba6fc17d6d1336ccb9dc314080052
3
+ metadata.gz: 8c9881b556aa0492a4e0cba92343efa2c9761664a9507ac3d123fee8143301b8
4
+ data.tar.gz: 245f405149281445c9bb902695919f6fbd4c5ec461303f16c7078ed6ddcbe111
5
5
  SHA512:
6
- metadata.gz: 4116e1c5918e226ec2232a55cd4dfdc8ff034948ceafa03ca3cdaecee6df4aa2f7d1dc5598eed66074f0c2e22c71492b7fab83c83b8869165897be69cabf4e9c
7
- data.tar.gz: 826761b6d51762fb214380d05d438ab70b029bab4c7a138b59909ac096bcdc5bc79d7aed6c6f2509d728b6ea59f8ea03201fb42e6e77002fef1cd47a7635ec7d
6
+ metadata.gz: ced6968dc159ab82ffcc8902b9835977bfdac9fbf1f8290ce20c216a09fb07fe7731235ac464827094cdebb422048ae963cf396fcb604c37d8945d73d148a619
7
+ data.tar.gz: b57ed46b023c084209153176fcbb05c4ec710cf1da0a77e61db1ac14e50b87f4bd1c5d35c73e2b9a950d855415e4728d5f229138bb6d8d51edab1bb3df8961f3
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Tryouts v2.3.0 (2024-04-04)
1
+ # Tryouts v2.3.1 (2024-06-18)
2
2
 
3
3
  **Ruby tests that read like documentation.**
4
4
 
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :MAJOR: 2
3
+ :MINOR: 3
4
+ :PATCH: 1
data/exe/try CHANGED
@@ -3,35 +3,49 @@
3
3
  require 'optparse'
4
4
  require_relative '../lib/tryouts'
5
5
 
6
- # Add local lib directories to the load path
7
- Dir.glob(File.join(Dir.pwd, '{lib,..,lib}')).each { |dir| $LOAD_PATH.unshift(dir) }
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') { puts "Tryouts: #{Tryouts::VERSION}"; exit }
13
- opts.on('-q', '--quiet', 'Run in quiet mode') { options[:quiet] = true }
14
- opts.on('-v', '--verbose', 'Run in verbose mode') { options[:noisy] = true }
15
- opts.on('-f', '--fails', 'Show only failing tryouts') { options[:fails] = true }
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') { puts opts; exit }
29
+ opts.on('-h', '--help', 'Display this help') do
30
+ puts opts
31
+ exit
32
+ end
18
33
  end.parse!
19
34
 
20
- # Set Tryouts options
21
- Tryouts.quiet = options[:quiet]
22
- Tryouts.noisy = options[:noisy]
23
- Tryouts.fails = options[:fails]
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
- # Find tryouts path
26
- if ARGV.empty?
27
- paths = Dir.glob(
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
@@ -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
- load_config
5
- [@version[:MAJOR], @version[:MINOR], @version[:PATCH]].join('.')
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.load_config
10
+ def self.version_file
9
11
  require 'yaml'
10
- @version ||= YAML.load_file(File.join(TRYOUTS_LIB_HOME, '..', 'VERSION.yml'))
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
- require 'ostruct'
1
+ # frozen_string_literal: true
2
2
 
3
- unless defined?(TRYOUTS_LIB_HOME)
4
- TRYOUTS_LIB_HOME = File.expand_path File.dirname(__FILE__)
5
- end
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
- class << self
18
- attr_accessor :debug, :container, :quiet, :noisy, :fails
19
- attr_reader :cases
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?() @debug == true end
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, skipped_tests, failed_tests = 0, 0, 0
35
- skipped_batches, failed_batches = 0, 0
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 @ %-40s' % [RUBY_VERSION, Time.now], $/
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
- path = batch.path.gsub(/#{Dir.pwd}\/?/, '')
48
-
49
- vmsg '%-60s %s' % [path, '']
50
-
51
- before_handler = Proc.new do |t|
52
- if Tryouts.noisy && !Tryouts.fails
53
- vmsg Console.reverse(' %-58s ' % [t.desc.to_s])
54
- vmsg t.test.inspect, t.exps.inspect
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 |t|
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
- msg
85
- if all > 0
86
- suffix = 'tests passed'
87
- suffix << " (and #{skipped_tests} skipped)" if skipped_tests > 0
88
- msg cformat(all-failed_tests-skipped_tests, all-skipped_tests, suffix) if all-skipped_tests > 0
89
- end
90
- if batches.size > 1
91
- if batches.size-skipped_batches > 0
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
- failed_tests # 0 means success
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 path
140
+ def run(path)
106
141
  batch = parse path
107
142
  batch.run
108
143
  batch
109
144
  end
110
145
 
111
- def parse path
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
- if expectation? line
121
- offset = 0
122
- exps = Section.new(path, idx+1)
123
- exps << line.chomp
124
- while (idx+offset < lines.size)
125
- offset += 1
126
- this_line = lines[idx+offset]
127
- break if ignore?(this_line)
128
- if expectation?(this_line)
129
- exps << this_line.chomp
130
- skip_ahead += 1
131
- end
132
- exps.last += 1
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
- offset = 0
136
- buffer, desc = Section.new(path), Section.new(path)
137
- test = Section.new(path, idx) # test start the line before the exp.
138
- blank_buffer = Section.new(path)
139
- while (idx-offset >= 0)
140
- offset += 1
141
- this_line = lines[idx-offset].chomp
142
- buffer.unshift this_line if ignore?(this_line)
143
- if comment?(this_line)
144
- buffer.unshift this_line
145
- end
146
- if test?(this_line)
147
- test.unshift(*buffer) && buffer.clear
148
- test.unshift this_line
149
- end
150
- if test_begin?(this_line)
151
- while test_begin?(lines[idx-(offset+1)].chomp)
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
- batch << TestCase.new(desc, test, exps)
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
- STDOUT.print str
182
- STDOUT.flush
215
+
216
+ $stdout.print str
217
+ $stdout.flush
183
218
  end
184
219
 
185
- def vmsg *msg
186
- STDOUT.puts *msg if !Tryouts.quiet && Tryouts.noisy
220
+ def vmsg *msgs
221
+ msg(*msgs) if Tryouts.noisy
187
222
  end
188
223
 
189
- def msg *msg
190
- STDOUT.puts *msg unless Tryouts.quiet
224
+ def msg *msgs
225
+ testcase_io.puts(*msgs) unless Tryouts.quiet
191
226
  end
192
227
 
193
- def err *msg
228
+ def err *msgs
194
229
  msg.each do |line|
195
- STDERR.puts Console.color :red, line
230
+ $stderr.puts Console.color :red, line
196
231
  end
197
232
  end
198
233
 
199
- def debug *msg
200
- STDERR.puts *msg if @debug
234
+ def debug *msgs
235
+ $stderr.puts(*msgs) if debug?
201
236
  end
202
237
 
203
238
  def eval(str, path, line)
204
- begin
205
- Kernel.eval str, @container.send(:binding), path, line
206
- rescue SyntaxError, LoadError => ex
207
- Tryouts.err Console.color(:red, ex.message),
208
- Console.color(:red, ex.backtrace.first)
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? str
248
+ def expectation?(str)
216
249
  !ignore?(str) && str.strip.match(/\A\#+\s*=>/)
217
250
  end
218
251
 
219
- def comment? str
252
+ def comment?(str)
220
253
  !str.strip.match(/^\#+/).nil? && !expectation?(str)
221
254
  end
222
255
 
223
- def test? str
256
+ def test?(str)
224
257
  !ignore?(str) && !expectation?(str) && !comment?(str)
225
258
  end
226
259
 
227
- def ignore? str
260
+ def ignore?(str)
228
261
  str.to_s.strip.chomp.empty?
229
262
  end
230
263
 
231
- def test_begin? str
232
- ret = !str.strip.match(/\#+\s*TEST/i).nil? ||
233
- !str.strip.match(/\A\#\#+[\s\w]+/i).nil?
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.0
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-18 00:00:00.000000000 Z
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.5.7
52
+ rubygems_version: 3.4.19
48
53
  signing_key:
49
54
  specification_version: 4
50
55
  summary: Ruby tests that read like documentation.