tryouts 2.4.1 → 3.0.0.pre2
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/LICENSE.txt +1 -1
- data/README.md +187 -73
- data/exe/try +53 -45
- data/exe/tryouts +2 -2
- data/lib/tryouts/cli/formatters/base.rb +108 -0
- data/lib/tryouts/cli/formatters/compact.rb +246 -0
- data/lib/tryouts/cli/formatters/factory.rb +52 -0
- data/lib/tryouts/cli/formatters/output_manager.rb +140 -0
- data/lib/tryouts/cli/formatters/quiet.rb +159 -0
- data/lib/tryouts/cli/formatters/verbose.rb +344 -0
- data/lib/tryouts/cli/formatters.rb +6 -0
- data/lib/tryouts/cli/modes/generate.rb +22 -0
- data/lib/tryouts/cli/modes/inspect.rb +42 -0
- data/lib/tryouts/cli/opts.rb +88 -0
- data/lib/tryouts/cli.rb +54 -0
- data/lib/tryouts/console.rb +55 -40
- data/lib/tryouts/file_processor.rb +66 -0
- data/lib/tryouts/prism_parser.rb +314 -0
- data/lib/tryouts/test_executor.rb +82 -0
- data/lib/tryouts/test_runner.rb +128 -0
- data/lib/tryouts/testbatch.rb +293 -53
- data/lib/tryouts/testcase.rb +32 -91
- data/lib/tryouts/translators/minitest_translator.rb +106 -0
- data/lib/tryouts/translators/rspec_translator.rb +88 -0
- data/lib/tryouts/version.rb +2 -12
- data/lib/tryouts.rb +16 -263
- metadata +60 -13
- data/VERSION.yml +0 -4
- data/lib/tryouts/section.rb +0 -27
data/lib/tryouts/testbatch.rb
CHANGED
@@ -1,74 +1,314 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# lib/tryouts/testbatch.rb
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
3
5
|
class Tryouts
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
# Modern TestBatch using Ruby 3.4+ patterns and formatter system
|
7
|
+
class TestBatch
|
8
|
+
attr_reader :testrun, :failed_count, :container, :status, :results, :formatter, :output_manager
|
9
|
+
|
10
|
+
def initialize(testrun, **options)
|
11
|
+
@testrun = testrun
|
12
|
+
@container = Object.new
|
13
|
+
@options = options
|
14
|
+
@formatter = Tryouts::CLI::FormatterFactory.create_formatter(options)
|
15
|
+
@output_manager = options[:output_manager]
|
16
|
+
@global_tally = options[:global_tally]
|
17
|
+
@failed_count = 0
|
18
|
+
@status = :pending
|
19
|
+
@results = []
|
20
|
+
@start_time = nil
|
9
21
|
end
|
10
|
-
|
11
|
-
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
@
|
16
|
-
@
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
setup
|
25
|
-
failed_tests = self.select do |tc|
|
26
|
-
before_test.call(tc) unless before_test.nil?
|
27
|
-
begin
|
28
|
-
testcase_score = tc.run # returns -1 for failed, 0 for skipped, 1 for passed
|
29
|
-
rescue StandardError => e
|
30
|
-
testcase_score = -1
|
31
|
-
warn Console.color(:red, "Error in test: #{tc.inspect}")
|
32
|
-
warn Console.color(:red, e.message)
|
33
|
-
warn e.backtrace.join($/), $/
|
34
|
-
end
|
35
|
-
after_test.call(tc) # runs the tallying code
|
36
|
-
testcase_score.negative? # select failed tests
|
22
|
+
|
23
|
+
# Main execution pipeline using functional composition
|
24
|
+
def run(before_test_hook = nil, &)
|
25
|
+
return false if empty?
|
26
|
+
|
27
|
+
@start_time = Time.now
|
28
|
+
@output_manager&.execution_phase(test_cases.size)
|
29
|
+
@output_manager&.info("Context: #{@options[:shared_context] ? 'shared' : 'fresh'}", 1)
|
30
|
+
@output_manager&.file_start(path, context: @options[:shared_context] ? :shared : :fresh)
|
31
|
+
|
32
|
+
if shared_context?
|
33
|
+
@output_manager&.info('Running global setup...', 2)
|
34
|
+
execute_global_setup
|
37
35
|
end
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
idx = 0
|
38
|
+
execution_results = test_cases.map do |test_case|
|
39
|
+
@output_manager&.trace("Test #{idx + 1}/#{test_cases.size}: #{test_case.description}", 2)
|
40
|
+
idx += 1
|
41
|
+
result = execute_single_test(test_case, before_test_hook, &) # runs the test code
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
execute_global_teardown
|
46
|
+
finalize_results(execution_results)
|
47
|
+
|
48
|
+
@status = :completed
|
43
49
|
!failed?
|
50
|
+
end
|
51
|
+
|
52
|
+
def empty?
|
53
|
+
@testrun.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def size
|
57
|
+
@testrun.total_tests
|
58
|
+
end
|
44
59
|
|
45
|
-
|
46
|
-
@
|
47
|
-
|
60
|
+
def test_cases
|
61
|
+
@testrun.test_cases
|
62
|
+
end
|
63
|
+
|
64
|
+
def path
|
65
|
+
@testrun.source_file
|
48
66
|
end
|
49
67
|
|
50
68
|
def failed?
|
51
|
-
|
69
|
+
@failed_count > 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def completed?
|
73
|
+
@status == :completed
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Pattern matching for execution strategy selection
|
79
|
+
def execute_single_test(test_case, before_test_hook = nil)
|
80
|
+
before_test_hook&.call(test_case)
|
81
|
+
|
82
|
+
# Capture output during test execution
|
83
|
+
result = nil
|
84
|
+
captured_output = capture_output do
|
85
|
+
result = case @options[:shared_context]
|
86
|
+
when true
|
87
|
+
execute_with_shared_context(test_case)
|
88
|
+
when false, nil
|
89
|
+
execute_with_fresh_context(test_case)
|
90
|
+
else
|
91
|
+
raise 'Invalid execution context configuration'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Add captured output to the result
|
96
|
+
result[:captured_output] = captured_output if captured_output && !captured_output.empty?
|
97
|
+
|
98
|
+
process_test_result(result)
|
99
|
+
yield(test_case) if block_given?
|
100
|
+
result
|
52
101
|
end
|
53
102
|
|
54
|
-
|
55
|
-
|
103
|
+
# Shared context execution - setup runs once, all tests share state
|
104
|
+
def execute_with_shared_context(test_case)
|
105
|
+
code = test_case.code
|
106
|
+
path = test_case.path
|
107
|
+
range = test_case.line_range
|
56
108
|
|
57
|
-
|
58
|
-
|
109
|
+
result_value = @container.instance_eval(code, path, range.first + 1)
|
110
|
+
expectations_result = evaluate_expectations(test_case, result_value, @container)
|
111
|
+
|
112
|
+
build_test_result(test_case, result_value, expectations_result)
|
113
|
+
rescue StandardError => ex
|
114
|
+
build_error_result(test_case, ex.message, ex)
|
59
115
|
end
|
60
116
|
|
61
|
-
|
62
|
-
|
117
|
+
# Fresh context execution - setup runs per test, isolated state
|
118
|
+
def execute_with_fresh_context(test_case)
|
119
|
+
fresh_container = Object.new
|
120
|
+
|
121
|
+
# Execute setup in fresh context if present
|
122
|
+
setup = @testrun.setup
|
123
|
+
if setup && !setup.code.empty?
|
124
|
+
fresh_container.instance_eval(setup.code, setup.path, 1)
|
125
|
+
end
|
63
126
|
|
64
|
-
|
65
|
-
|
127
|
+
# Execute test in same fresh context
|
128
|
+
code = test_case.code
|
129
|
+
path = test_case.path
|
130
|
+
range = test_case.line_range
|
66
131
|
|
67
|
-
|
132
|
+
result_value = fresh_container.instance_eval(code, path, range.first + 1)
|
133
|
+
expectations_result = evaluate_expectations(test_case, result_value, fresh_container)
|
134
|
+
|
135
|
+
build_test_result(test_case, result_value, expectations_result)
|
136
|
+
rescue StandardError => ex
|
137
|
+
build_error_result(test_case, ex.message, ex)
|
68
138
|
end
|
69
139
|
|
70
|
-
|
71
|
-
|
140
|
+
# Evaluate expectations using pattern matching for clean result handling
|
141
|
+
def evaluate_expectations(test_case, actual_result, context)
|
142
|
+
if test_case.expectations.empty?
|
143
|
+
{ passed: true, actual_results: [], expected_results: [] }
|
144
|
+
else
|
145
|
+
evaluation_results = test_case.expectations.map do |expectation|
|
146
|
+
evaluate_single_expectation(expectation, actual_result, context, test_case)
|
147
|
+
end
|
148
|
+
|
149
|
+
{
|
150
|
+
passed: evaluation_results.all? { |r| r[:passed] },
|
151
|
+
actual_results: evaluation_results.map { |r| r[:actual] },
|
152
|
+
expected_results: evaluation_results.map { |r| r[:expected] },
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def evaluate_single_expectation(expectation, actual_result, context, test_case)
|
158
|
+
path = test_case.path
|
159
|
+
range = test_case.line_range
|
160
|
+
|
161
|
+
expected_value = context.instance_eval(expectation, path, range.first + 1)
|
162
|
+
|
163
|
+
{
|
164
|
+
passed: actual_result == expected_value,
|
165
|
+
actual: actual_result,
|
166
|
+
expected: expected_value,
|
167
|
+
expectation: expectation,
|
168
|
+
}
|
169
|
+
rescue StandardError => ex
|
170
|
+
{
|
171
|
+
passed: false,
|
172
|
+
actual: actual_result,
|
173
|
+
expected: "EXPECTED: #{ex.message}",
|
174
|
+
expectation: expectation,
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
# Build structured test results using pattern matching
|
179
|
+
def build_test_result(test_case, result_value, expectations_result)
|
180
|
+
if expectations_result[:passed]
|
181
|
+
{
|
182
|
+
test_case: test_case,
|
183
|
+
status: :passed,
|
184
|
+
result_value: result_value,
|
185
|
+
actual_results: expectations_result[:actual_results],
|
186
|
+
error: nil,
|
187
|
+
}
|
188
|
+
else
|
189
|
+
{
|
190
|
+
test_case: test_case,
|
191
|
+
status: :failed,
|
192
|
+
result_value: result_value,
|
193
|
+
actual_results: expectations_result[:actual_results],
|
194
|
+
error: nil,
|
195
|
+
}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def build_error_result(test_case, message, exception = nil)
|
200
|
+
{
|
201
|
+
test_case: test_case,
|
202
|
+
status: :error,
|
203
|
+
result_value: nil,
|
204
|
+
actual_results: ["ACTUAL: #{message}"],
|
205
|
+
error: exception,
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
# Process and display test results using formatter
|
210
|
+
def process_test_result(result)
|
211
|
+
@results << result
|
212
|
+
|
213
|
+
if [:failed, :error].include?(result[:status])
|
214
|
+
@failed_count += 1
|
215
|
+
end
|
216
|
+
|
217
|
+
show_test_result(result)
|
218
|
+
|
219
|
+
# Show captured output if any exists
|
220
|
+
if result[:captured_output] && !result[:captured_output].empty?
|
221
|
+
@output_manager&.test_output(result[:test_case], result[:captured_output])
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Global setup execution for shared context mode
|
226
|
+
def execute_global_setup
|
227
|
+
setup = @testrun.setup
|
228
|
+
|
229
|
+
if setup && !setup.code.empty? && @options[:shared_context]
|
230
|
+
@output_manager&.setup_start(setup.line_range)
|
231
|
+
|
232
|
+
# Capture setup output instead of letting it print directly
|
233
|
+
captured_output = capture_output do
|
234
|
+
@container.instance_eval(setup.code, setup.path, setup.line_range.first + 1)
|
235
|
+
end
|
236
|
+
|
237
|
+
@output_manager&.setup_output(captured_output) if captured_output && !captured_output.empty?
|
238
|
+
end
|
239
|
+
rescue StandardError => ex
|
240
|
+
@global_tally[:total_errors] += 1 if @global_tally
|
241
|
+
raise "Global setup failed: #{ex.message}"
|
242
|
+
end
|
243
|
+
|
244
|
+
# Global teardown execution
|
245
|
+
def execute_global_teardown
|
246
|
+
teardown = @testrun.teardown
|
247
|
+
|
248
|
+
if teardown && !teardown.code.empty?
|
249
|
+
@output_manager&.teardown_start(teardown.line_range)
|
250
|
+
|
251
|
+
# Capture teardown output instead of letting it print directly
|
252
|
+
captured_output = capture_output do
|
253
|
+
@container.instance_eval(teardown.code, teardown.path, teardown.line_range.first + 1)
|
254
|
+
end
|
255
|
+
|
256
|
+
@output_manager&.teardown_output(captured_output) if captured_output && !captured_output.empty?
|
257
|
+
end
|
258
|
+
rescue StandardError => ex
|
259
|
+
@global_tally[:total_errors] += 1 if @global_tally
|
260
|
+
@output_manager&.error("Teardown failed: #{ex.message}")
|
261
|
+
end
|
262
|
+
|
263
|
+
# Result finalization and summary display
|
264
|
+
def finalize_results(_execution_results)
|
265
|
+
@status = :completed
|
266
|
+
elapsed_time = Time.now - @start_time
|
267
|
+
show_summary(elapsed_time)
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
def show_test_result(result)
|
272
|
+
test_case = result[:test_case]
|
273
|
+
status = result[:status]
|
274
|
+
actuals = result[:actual_results]
|
275
|
+
|
276
|
+
@output_manager&.test_result(test_case, status, actuals)
|
277
|
+
end
|
278
|
+
|
279
|
+
def show_summary(elapsed_time)
|
280
|
+
@output_manager&.batch_summary(size, @failed_count, elapsed_time)
|
281
|
+
end
|
282
|
+
|
283
|
+
# Helper methods using pattern matching
|
284
|
+
|
285
|
+
def shared_context?
|
286
|
+
@options[:shared_context] == true
|
287
|
+
end
|
288
|
+
|
289
|
+
def capture_output
|
290
|
+
old_stdout = $stdout
|
291
|
+
old_stderr = $stderr
|
292
|
+
$stdout = StringIO.new
|
293
|
+
$stderr = StringIO.new
|
294
|
+
|
295
|
+
yield
|
296
|
+
|
297
|
+
captured = $stdout.string + $stderr.string
|
298
|
+
captured.empty? ? nil : captured
|
299
|
+
ensure
|
300
|
+
$stdout = old_stdout
|
301
|
+
$stderr = old_stderr
|
302
|
+
end
|
303
|
+
|
304
|
+
def handle_batch_error(exception)
|
305
|
+
@status = :error
|
306
|
+
@failed_count = 1
|
307
|
+
|
308
|
+
error_message = "Batch execution failed: #{exception.message}"
|
309
|
+
backtrace = exception.respond_to?(:backtrace) ? exception.backtrace : nil
|
310
|
+
|
311
|
+
@output_manager&.error(error_message, backtrace)
|
72
312
|
end
|
73
313
|
end
|
74
314
|
end
|
data/lib/tryouts/testcase.rb
CHANGED
@@ -1,110 +1,51 @@
|
|
1
|
-
#
|
1
|
+
# lib/tryouts/testcase.rb
|
2
2
|
|
3
|
+
# Modern data structures using Ruby 3.2+ Data classes
|
3
4
|
class Tryouts
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@desc, @test, @exps, @path = d, t, e
|
9
|
-
@testrunner_output = []
|
10
|
-
@console_output = StringIO.new
|
5
|
+
# Core data structures
|
6
|
+
TestCase = Data.define(:description, :code, :expectations, :line_range, :path, :source_lines) do
|
7
|
+
def empty?
|
8
|
+
code.empty?
|
11
9
|
end
|
12
10
|
|
13
|
-
def
|
14
|
-
|
11
|
+
def expectations?
|
12
|
+
!expectations.empty?
|
15
13
|
end
|
14
|
+
end
|
16
15
|
|
17
|
-
|
18
|
-
|
16
|
+
Setup = Data.define(:code, :line_range, :path) do
|
17
|
+
def empty?
|
18
|
+
code.empty?
|
19
19
|
end
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
$stdout = @console_output
|
26
|
-
expectations = exps.collect do |exp, _idx|
|
27
|
-
exp =~ /\A\#?\s*=>\s*(.+)\Z/
|
28
|
-
::Regexp.last_match(1) # this will be nil if the expectation is commented out
|
29
|
-
end
|
30
|
-
|
31
|
-
Tryouts.info 'Capturing STDOUT for tryout'
|
32
|
-
Tryouts.info 'vvvvvvvvvvvvvvvvvvv'
|
33
|
-
# Evaluate test block only if there are valid expectations
|
34
|
-
unless expectations.compact.empty? # TODO: fast-fail if no expectations
|
35
|
-
test_value = Tryouts.eval @test.to_s, @test.path, @test.first
|
36
|
-
@has_run = true
|
37
|
-
end
|
38
|
-
Tryouts.info '^^^^^^^^^^^^^^^^^^^'
|
39
|
-
|
40
|
-
Tryouts.info "Capturing STDOUT for expectations"
|
41
|
-
Tryouts.info 'vvvvvvvvvvvvvvvvvvv'
|
42
|
-
expectations.each_with_index do |exp, idx|
|
43
|
-
if exp.nil?
|
44
|
-
@testrunner_output << ' [skipped]'
|
45
|
-
@test_result = 0
|
46
|
-
else
|
47
|
-
# Evaluate expectation
|
48
|
-
|
49
|
-
exp_value = Tryouts.eval(exp, @exps.path, @exps.first + idx)
|
50
|
-
|
51
|
-
test_passed = test_value.eql?(exp_value)
|
52
|
-
@test_result = test_passed ? 1 : -1
|
53
|
-
@testrunner_output << test_value.inspect
|
54
|
-
end
|
55
|
-
end
|
56
|
-
Tryouts.info '^^^^^^^^^^^^^^^^^^^'
|
57
|
-
$stdout = STDOUT # restore stdout
|
58
|
-
|
59
|
-
Tryouts.debug # extra newline
|
60
|
-
failed?
|
61
|
-
|
62
|
-
@test_result
|
63
|
-
rescue StandardError => e
|
64
|
-
Tryouts.debug "[testcaste.run] #{e.message}", e.backtrace.join($/), $/
|
65
|
-
# Continue raising the exception
|
66
|
-
raise e
|
67
|
-
ensure
|
68
|
-
$stdout = STDOUT # restore stdout
|
69
|
-
@test_result
|
22
|
+
Teardown = Data.define(:code, :line_range, :path) do
|
23
|
+
def empty?
|
24
|
+
code.empty?
|
70
25
|
end
|
26
|
+
end
|
71
27
|
|
72
|
-
|
73
|
-
|
28
|
+
Testrun = Data.define(:setup, :test_cases, :teardown, :source_file, :metadata) do
|
29
|
+
def total_tests
|
30
|
+
test_cases.size
|
74
31
|
end
|
75
32
|
|
76
|
-
def
|
77
|
-
|
33
|
+
def empty?
|
34
|
+
test_cases.empty?
|
78
35
|
end
|
36
|
+
end
|
79
37
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
def failed?
|
84
|
-
@test_result == -1
|
85
|
-
end
|
38
|
+
# Enhanced error with context
|
39
|
+
class TryoutSyntaxError < StandardError
|
40
|
+
attr_reader :line_number, :context, :source_file
|
86
41
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
when 0
|
92
|
-
:white
|
93
|
-
else
|
94
|
-
:red
|
95
|
-
end
|
96
|
-
end
|
42
|
+
def initialize(message, line_number:, context:, source_file: nil)
|
43
|
+
@line_number = line_number
|
44
|
+
@context = context
|
45
|
+
@source_file = source_file
|
97
46
|
|
98
|
-
|
99
|
-
|
100
|
-
when 1
|
101
|
-
'PASSED'
|
102
|
-
when 0
|
103
|
-
'SKIPPED'
|
104
|
-
else
|
105
|
-
'FAILED'
|
106
|
-
end
|
47
|
+
location = source_file ? "#{source_file}:#{line_number}" : "line #{line_number}"
|
48
|
+
super("#{message} at #{location}: #{context}")
|
107
49
|
end
|
108
|
-
|
109
50
|
end
|
110
51
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# lib/tryouts/translators/minitest_translator.rb
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
module Translators
|
5
|
+
class MinitestTranslator
|
6
|
+
def initialize
|
7
|
+
require 'minitest/test'
|
8
|
+
rescue LoadError
|
9
|
+
raise 'Minitest gem is required for Minitest translation'
|
10
|
+
end
|
11
|
+
|
12
|
+
def translate(testrun)
|
13
|
+
file_basename = File.basename(testrun.source_file, '.rb')
|
14
|
+
class_name = "Test#{file_basename.gsub(/[^A-Za-z0-9]/, '')}"
|
15
|
+
|
16
|
+
test_class = Class.new(Minitest::Test) do
|
17
|
+
# Setup method
|
18
|
+
if testrun.setup && !testrun.setup.empty?
|
19
|
+
define_method(:setup) do
|
20
|
+
instance_eval(testrun.setup.code)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Generate test methods
|
25
|
+
testrun.test_cases.each_with_index do |test_case, index|
|
26
|
+
next if test_case.empty? || !test_case.expectations?
|
27
|
+
|
28
|
+
method_name = "test_#{index.to_s.rjust(3, '0')}_#{parameterize(test_case.description)}"
|
29
|
+
define_method(method_name) do
|
30
|
+
result = instance_eval(test_case.code) unless test_case.code.strip.empty?
|
31
|
+
|
32
|
+
test_case.expectations.each do |expectation|
|
33
|
+
expected_value = instance_eval(expectation)
|
34
|
+
assert_equal expected_value, result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Teardown method
|
40
|
+
if testrun.teardown && !testrun.teardown.empty?
|
41
|
+
define_method(:teardown) do
|
42
|
+
instance_eval(testrun.teardown.code)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set the class name dynamically
|
48
|
+
Object.const_set(class_name, test_class) unless Object.const_defined?(class_name)
|
49
|
+
test_class
|
50
|
+
end
|
51
|
+
|
52
|
+
def generate_code(testrun)
|
53
|
+
file_basename = File.basename(testrun.source_file, '.rb')
|
54
|
+
class_name = "Test#{file_basename.gsub(/[^A-Za-z0-9]/, '')}"
|
55
|
+
lines = []
|
56
|
+
|
57
|
+
lines << ''
|
58
|
+
lines << "require 'minitest/test'"
|
59
|
+
lines << "require 'minitest/autorun'"
|
60
|
+
lines << ''
|
61
|
+
lines << "class #{class_name} < Minitest::Test"
|
62
|
+
|
63
|
+
if testrun.setup && !testrun.setup.empty?
|
64
|
+
lines << ' def setup'
|
65
|
+
testrun.setup.code.lines.each { |line| lines << " #{line.chomp}" }
|
66
|
+
lines << ' end'
|
67
|
+
lines << ''
|
68
|
+
end
|
69
|
+
|
70
|
+
testrun.test_cases.each_with_index do |test_case, index|
|
71
|
+
next if test_case.empty? || !test_case.expectations?
|
72
|
+
|
73
|
+
method_name = "test_#{index.to_s.rjust(3, '0')}_#{parameterize(test_case.description)}"
|
74
|
+
lines << " def #{method_name}"
|
75
|
+
unless test_case.code.strip.empty?
|
76
|
+
lines << ' result = begin'
|
77
|
+
test_case.code.lines.each { |line| lines << " #{line.chomp}" }
|
78
|
+
lines << ' end'
|
79
|
+
end
|
80
|
+
|
81
|
+
test_case.expectations.each do |expectation|
|
82
|
+
lines << " assert_equal #{expectation}, result"
|
83
|
+
end
|
84
|
+
lines << ' end'
|
85
|
+
lines << ''
|
86
|
+
end
|
87
|
+
|
88
|
+
if testrun.teardown && !testrun.teardown.empty?
|
89
|
+
lines << ' def teardown'
|
90
|
+
testrun.teardown.code.lines.each { |line| lines << " #{line.chomp}" }
|
91
|
+
lines << ' end'
|
92
|
+
end
|
93
|
+
|
94
|
+
lines << 'end'
|
95
|
+
lines.join("\n")
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Simple string parameterization for method names
|
101
|
+
def parameterize(string)
|
102
|
+
string.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/^_|_$/, '')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|