tryouts 2.4.1 → 3.0.0.pre
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 +20 -44
- data/exe/tryouts +2 -2
- data/lib/tryouts/cli/formatters/base.rb +108 -0
- data/lib/tryouts/cli/formatters/compact.rb +218 -0
- data/lib/tryouts/cli/formatters/factory.rb +44 -0
- data/lib/tryouts/cli/formatters/output_manager.rb +140 -0
- data/lib/tryouts/cli/formatters/quiet.rb +143 -0
- data/lib/tryouts/cli/formatters/verbose.rb +340 -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 +20 -263
- metadata +60 -13
- data/VERSION.yml +0 -4
- data/lib/tryouts/section.rb +0 -27
@@ -0,0 +1,340 @@
|
|
1
|
+
# lib/tryouts/cli/formatters/verbose.rb
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
class CLI
|
5
|
+
# Detailed formatter with comprehensive output and clear visual hierarchy
|
6
|
+
class VerboseFormatter
|
7
|
+
include FormatterInterface
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@line_width = options.fetch(:line_width, 70)
|
11
|
+
@show_passed = options.fetch(:show_passed, true)
|
12
|
+
@show_debug = options.fetch(:debug, false)
|
13
|
+
@show_trace = options.fetch(:trace, true)
|
14
|
+
@current_indent = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
# Phase-level output
|
18
|
+
def phase_header(message, _file_count = nil, level = 0)
|
19
|
+
separators = [
|
20
|
+
{ char: '=', width: @line_width }, # Major phases
|
21
|
+
{ char: '-', width: @line_width - 10 }, # Sub-phases
|
22
|
+
{ char: '.', width: @line_width - 20 }, # Details
|
23
|
+
{ char: '~', width: @line_width - 30 }, # Minor items
|
24
|
+
]
|
25
|
+
|
26
|
+
config = separators[level] || separators.last
|
27
|
+
|
28
|
+
separator_line = config[:char] * config[:width]
|
29
|
+
header_line = message.center(config[:width])
|
30
|
+
|
31
|
+
output = case level
|
32
|
+
when 0, 1
|
33
|
+
['', separator_line, header_line, separator_line]
|
34
|
+
else
|
35
|
+
['', header_line, separator_line]
|
36
|
+
end
|
37
|
+
|
38
|
+
with_indent(level) do
|
39
|
+
puts output.join("\n")
|
40
|
+
end
|
41
|
+
puts
|
42
|
+
end
|
43
|
+
|
44
|
+
# File-level operations
|
45
|
+
def file_start(file_path, _context_info = {})
|
46
|
+
# framework = context_info[:framework] || :direct
|
47
|
+
# context = context_info[:context] || :fresh
|
48
|
+
|
49
|
+
# with_indent(1) do
|
50
|
+
# puts "Framework: #{framework}"
|
51
|
+
# puts "Context: #{context}"
|
52
|
+
# end
|
53
|
+
|
54
|
+
puts file_header_visual(file_path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def file_parsed(file_path, test_count, setup_present: false, teardown_present: false)
|
58
|
+
pretty_path = Console.pretty_path(file_path)
|
59
|
+
message = "Parsed #{test_count} test cases from #{pretty_path}"
|
60
|
+
|
61
|
+
extras = []
|
62
|
+
extras << 'setup' if setup_present
|
63
|
+
extras << 'teardown' if teardown_present
|
64
|
+
message += " (#{extras.join(', ')})" unless extras.empty?
|
65
|
+
|
66
|
+
puts indent_text(message, 2)
|
67
|
+
end
|
68
|
+
|
69
|
+
def file_execution_start(_file_path, test_count, context_mode)
|
70
|
+
message = "Running #{test_count} tests with #{context_mode} context"
|
71
|
+
puts indent_text(message, 1)
|
72
|
+
end
|
73
|
+
|
74
|
+
def file_result(_file_path, total_tests, failed_count, error_count, elapsed_time)
|
75
|
+
issues_count = failed_count + error_count
|
76
|
+
details = []
|
77
|
+
|
78
|
+
status = if issues_count > 0
|
79
|
+
details << "#{failed_count} failed" if failed_count > 0
|
80
|
+
details << "#{error_count} errors" if error_count > 0
|
81
|
+
details_str = details.join(', ')
|
82
|
+
Console.color(:red, "✗ #{issues_count}/#{total_tests} tests had issues (#{details_str})")
|
83
|
+
else
|
84
|
+
Console.color(:green, "✓ #{total_tests} tests passed")
|
85
|
+
end
|
86
|
+
|
87
|
+
puts indent_text(status, 2)
|
88
|
+
|
89
|
+
if elapsed_time
|
90
|
+
time_msg = "Completed in #{elapsed_time.round(3)}s"
|
91
|
+
puts indent_text(Console.color(:dim, time_msg), 2)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Test-level operations
|
96
|
+
def test_start(test_case, index, total)
|
97
|
+
desc = test_case.description.to_s
|
98
|
+
desc = 'Unnamed test' if desc.empty?
|
99
|
+
message = "Test #{index}/#{total}: #{desc}"
|
100
|
+
puts indent_text(Console.color(:dim, message), 2)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_result(test_case, result_status, actual_results = [], _elapsed_time = nil)
|
104
|
+
should_show = @show_passed || result_status != :passed
|
105
|
+
|
106
|
+
return unless should_show
|
107
|
+
|
108
|
+
status_line = case result_status
|
109
|
+
when :passed
|
110
|
+
Console.color(:green, 'PASSED')
|
111
|
+
when :failed
|
112
|
+
Console.color(:red, 'FAILED')
|
113
|
+
when :error
|
114
|
+
Console.color(:red, 'ERROR')
|
115
|
+
when :skipped
|
116
|
+
Console.color(:yellow, 'SKIPPED')
|
117
|
+
else
|
118
|
+
'UNKNOWN'
|
119
|
+
end
|
120
|
+
|
121
|
+
location = "#{Console.pretty_path(test_case.path)}:#{test_case.line_range.first + 1}"
|
122
|
+
puts indent_text("#{status_line} #{test_case.description} @ #{location}", 2)
|
123
|
+
|
124
|
+
# Show source code for verbose mode
|
125
|
+
show_test_source_code(test_case)
|
126
|
+
|
127
|
+
# Show failure details for failed tests
|
128
|
+
if [:failed, :error].include?(result_status)
|
129
|
+
show_failure_details(test_case, actual_results)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_output(test_case, output_text)
|
134
|
+
return if output_text.nil? || output_text.strip.empty?
|
135
|
+
|
136
|
+
puts indent_text('Test Output:', 3)
|
137
|
+
puts indent_text(Console.color(:dim, '--- BEGIN OUTPUT ---'), 3)
|
138
|
+
|
139
|
+
output_text.lines.each do |line|
|
140
|
+
puts indent_text(line.chomp, 4)
|
141
|
+
end
|
142
|
+
|
143
|
+
puts indent_text(Console.color(:dim, '--- END OUTPUT ---'), 3)
|
144
|
+
puts
|
145
|
+
end
|
146
|
+
|
147
|
+
# Setup/teardown operations
|
148
|
+
def setup_start(line_range)
|
149
|
+
message = "Executing global setup (lines #{line_range.first}..#{line_range.last})"
|
150
|
+
puts indent_text(Console.color(:cyan, message), 2)
|
151
|
+
end
|
152
|
+
|
153
|
+
def setup_output(output_text)
|
154
|
+
return if output_text.strip.empty?
|
155
|
+
|
156
|
+
output_text.lines.each do |line|
|
157
|
+
puts indent_text(line.chomp, 0)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def teardown_start(line_range)
|
162
|
+
message = "Executing teardown (lines #{line_range.first}..#{line_range.last})"
|
163
|
+
puts indent_text(Console.color(:cyan, message), 2)
|
164
|
+
end
|
165
|
+
|
166
|
+
def teardown_output(output_text)
|
167
|
+
return if output_text.strip.empty?
|
168
|
+
|
169
|
+
output_text.lines.each do |line|
|
170
|
+
puts indent_text(line.chomp, 0)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Summary operations
|
175
|
+
def batch_summary(total_tests, failed_count, elapsed_time)
|
176
|
+
if failed_count > 0
|
177
|
+
passed = total_tests - failed_count
|
178
|
+
message = "#{failed_count} failed, #{passed} passed"
|
179
|
+
color = :red
|
180
|
+
else
|
181
|
+
message = "#{total_tests} tests passed"
|
182
|
+
color = :green
|
183
|
+
end
|
184
|
+
|
185
|
+
time_str = elapsed_time ? " (#{elapsed_time.round(2)}s)" : ''
|
186
|
+
summary = Console.color(color, "#{message}#{time_str}")
|
187
|
+
puts summary
|
188
|
+
end
|
189
|
+
|
190
|
+
def grand_total(total_tests, failed_count, error_count, successful_files, total_files, elapsed_time)
|
191
|
+
puts
|
192
|
+
puts '=' * @line_width
|
193
|
+
puts 'Grand Total:'
|
194
|
+
|
195
|
+
issues_count = failed_count + error_count
|
196
|
+
if issues_count > 0
|
197
|
+
passed = total_tests - issues_count
|
198
|
+
details = []
|
199
|
+
details << "#{failed_count} failed" if failed_count > 0
|
200
|
+
details << "#{error_count} errors" if error_count > 0
|
201
|
+
puts "#{details.join(', ')}, #{passed} passed (#{elapsed_time.round(2)}s)"
|
202
|
+
else
|
203
|
+
puts "#{total_tests} tests passed (#{elapsed_time.round(2)}s)"
|
204
|
+
end
|
205
|
+
|
206
|
+
puts "Files processed: #{successful_files} of #{total_files} successful"
|
207
|
+
puts '=' * @line_width
|
208
|
+
end
|
209
|
+
|
210
|
+
# Debug and diagnostic output
|
211
|
+
def debug_info(message, level = 0)
|
212
|
+
return unless @show_debug
|
213
|
+
|
214
|
+
prefix = Console.color(:cyan, 'INFO ')
|
215
|
+
puts indent_text("#{prefix} #{message}", level + 1)
|
216
|
+
end
|
217
|
+
|
218
|
+
def trace_info(message, level = 0)
|
219
|
+
return unless @show_trace
|
220
|
+
|
221
|
+
prefix = Console.color(:dim, 'TRACE')
|
222
|
+
puts indent_text("#{prefix} #{message}", level + 1)
|
223
|
+
end
|
224
|
+
|
225
|
+
def error_message(message, backtrace = nil)
|
226
|
+
error_msg = Console.color(:red, "ERROR: #{message}")
|
227
|
+
puts indent_text(error_msg, 1)
|
228
|
+
|
229
|
+
return unless backtrace && @show_debug
|
230
|
+
|
231
|
+
puts indent_text('Details:', 2)
|
232
|
+
# Show first 10 lines of backtrace to avoid overwhelming output
|
233
|
+
backtrace.first(10).each do |line|
|
234
|
+
puts indent_text(line, 3)
|
235
|
+
end
|
236
|
+
puts indent_text("... (#{backtrace.length - 10} more lines)", 3) if backtrace.length > 10
|
237
|
+
end
|
238
|
+
|
239
|
+
# Utility methods
|
240
|
+
def raw_output(text)
|
241
|
+
puts text
|
242
|
+
end
|
243
|
+
|
244
|
+
def separator(style = :light)
|
245
|
+
case style
|
246
|
+
when :heavy
|
247
|
+
puts '=' * @line_width
|
248
|
+
when :light
|
249
|
+
puts '-' * @line_width
|
250
|
+
when :dotted
|
251
|
+
puts '.' * @line_width
|
252
|
+
else # rubocop:disable Lint/DuplicateBranch
|
253
|
+
puts '-' * @line_width
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def show_test_source_code(test_case)
|
260
|
+
puts indent_text('Source code:', 3)
|
261
|
+
|
262
|
+
# Use pre-captured source lines from parsing
|
263
|
+
start_line = test_case.line_range.first
|
264
|
+
|
265
|
+
test_case.source_lines.each_with_index do |line_content, index|
|
266
|
+
line_num = start_line + index
|
267
|
+
line_display = format('%3d: %s', line_num + 1, line_content)
|
268
|
+
|
269
|
+
# Highlight expectation lines by checking if this line
|
270
|
+
# contains the expectation syntax
|
271
|
+
if line_content.match?(/^\s*#\s*=>\s*/)
|
272
|
+
line_display = Console.color(:yellow, line_display)
|
273
|
+
end
|
274
|
+
|
275
|
+
puts indent_text(line_display, 4)
|
276
|
+
end
|
277
|
+
puts
|
278
|
+
end
|
279
|
+
|
280
|
+
def show_failure_details(test_case, actual_results)
|
281
|
+
return if actual_results.empty?
|
282
|
+
|
283
|
+
puts indent_text('Expected vs Actual:', 3)
|
284
|
+
|
285
|
+
actual_results.each_with_index do |actual, idx|
|
286
|
+
expected_line = test_case.expectations[idx] if test_case.expectations
|
287
|
+
|
288
|
+
if expected_line
|
289
|
+
puts indent_text("Expected: #{Console.color(:green, expected_line)}", 4)
|
290
|
+
puts indent_text("Actual: #{Console.color(:red, actual.inspect)}", 4)
|
291
|
+
else
|
292
|
+
puts indent_text("Actual: #{Console.color(:red, actual.inspect)}", 4)
|
293
|
+
end
|
294
|
+
|
295
|
+
# Show difference if both are strings
|
296
|
+
if expected_line && actual.is_a?(String) && expected_line.is_a?(String)
|
297
|
+
show_string_diff(expected_line, actual)
|
298
|
+
end
|
299
|
+
|
300
|
+
puts
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def show_string_diff(expected, actual)
|
305
|
+
return if expected == actual
|
306
|
+
|
307
|
+
puts indent_text('Difference:', 4)
|
308
|
+
puts indent_text("- #{Console.color(:red, actual)}", 5)
|
309
|
+
puts indent_text("+ #{Console.color(:green, expected)}", 5)
|
310
|
+
end
|
311
|
+
|
312
|
+
def file_header_visual(file_path)
|
313
|
+
pretty_path = Console.pretty_path(file_path)
|
314
|
+
header_content = ">>>>> #{pretty_path} "
|
315
|
+
padding_length = [@line_width - header_content.length, 0].max
|
316
|
+
padding = '<' * padding_length
|
317
|
+
|
318
|
+
[
|
319
|
+
'-' * @line_width,
|
320
|
+
header_content + padding,
|
321
|
+
'-' * @line_width,
|
322
|
+
].join("\n")
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Verbose formatter that only shows failures and errors
|
327
|
+
class VerboseFailsFormatter < VerboseFormatter
|
328
|
+
def initialize(options = {})
|
329
|
+
super(options.merge(show_passed: false))
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_result(test_case, result_status, actual_results = [], elapsed_time = nil)
|
333
|
+
# Only show failed/error tests, but with full source code
|
334
|
+
return if result_status == :passed
|
335
|
+
|
336
|
+
super
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# lib/tryouts/cli/modes/generate.rb
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
class CLI
|
5
|
+
class GenerateMode
|
6
|
+
def initialize(file, testrun, options, output_manager, translator)
|
7
|
+
@file = file
|
8
|
+
@testrun = testrun
|
9
|
+
@options = options
|
10
|
+
@output_manager = output_manager
|
11
|
+
@translator = translator
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle
|
15
|
+
@output_manager.raw("# Generated #{@options[:framework]} code for #{@file}")
|
16
|
+
@output_manager.raw("# Updated: #{Time.now}")
|
17
|
+
@output_manager.raw(@translator.generate_code(@testrun))
|
18
|
+
@output_manager.raw('')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# lib/tryouts/cli/modes/inspect.rb
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
class CLI
|
5
|
+
class InspectMode
|
6
|
+
def initialize(file, testrun, options, output_manager, translator)
|
7
|
+
@file = file
|
8
|
+
@testrun = testrun
|
9
|
+
@options = options
|
10
|
+
@output_manager = output_manager
|
11
|
+
@translator = translator
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle
|
15
|
+
@output_manager.raw("Inspecting: #{@file}")
|
16
|
+
@output_manager.separator(:heavy)
|
17
|
+
@output_manager.raw("Found #{@testrun.total_tests} test cases")
|
18
|
+
@output_manager.raw("Setup code: #{@testrun.setup.empty? ? 'None' : 'Present'}")
|
19
|
+
@output_manager.raw("Teardown code: #{@testrun.teardown.empty? ? 'None' : 'Present'}")
|
20
|
+
@output_manager.raw('')
|
21
|
+
|
22
|
+
@testrun.test_cases.each_with_index do |tc, i|
|
23
|
+
@output_manager.raw("Test #{i + 1}: #{tc.description}")
|
24
|
+
@output_manager.raw(" Code lines: #{tc.code.lines.count}")
|
25
|
+
@output_manager.raw(" Expectations: #{tc.expectations.size}")
|
26
|
+
@output_manager.raw(" Range: #{tc.line_range}")
|
27
|
+
@output_manager.raw('')
|
28
|
+
end
|
29
|
+
|
30
|
+
return unless @options[:framework] != :direct
|
31
|
+
|
32
|
+
@output_manager.raw("Testing #{@options[:framework]} translation...")
|
33
|
+
framework_klass = TestRunner::FRAMEWORKS[@options[:framework]]
|
34
|
+
inspect_translator = framework_klass.new
|
35
|
+
|
36
|
+
translated_code = inspect_translator.generate_code(@testrun)
|
37
|
+
@output_manager.raw("#{@options[:framework].to_s.capitalize} code generated (#{translated_code.lines.count} lines)")
|
38
|
+
@output_manager.raw('')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# lib/tryouts/cli/opts.rb
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
class CLI
|
5
|
+
HELP = <<~HELP
|
6
|
+
|
7
|
+
Framework Defaults:
|
8
|
+
Tryouts: Shared context (state persists across tests)
|
9
|
+
RSpec: Fresh context (each test isolated)
|
10
|
+
Minitest: Fresh context (each test isolated)
|
11
|
+
|
12
|
+
Examples:
|
13
|
+
try test_try.rb # Tryouts test runner with shared context
|
14
|
+
try --rspec test_try.rb # RSpec with fresh context
|
15
|
+
try --direct --shared-context test_try.rb # Explicit shared context
|
16
|
+
try --generate-rspec test_try.rb # Output RSpec code only
|
17
|
+
try --inspect test_try.rb # Inspect file structure and validation
|
18
|
+
|
19
|
+
File Format:
|
20
|
+
## Test description # Test case marker
|
21
|
+
code_to_test # Ruby code
|
22
|
+
#=> expected_result # Expectation
|
23
|
+
HELP
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def parse_args(args)
|
27
|
+
Tryouts.trace "Parsing arguments: #{args.inspect}"
|
28
|
+
options = {}
|
29
|
+
|
30
|
+
parser = OptionParser.new do |opts|
|
31
|
+
opts.banner = "Usage: try [OPTIONS] FILE...\n\nModern Tryouts test runner with framework translation"
|
32
|
+
|
33
|
+
opts.separator "\nFramework Options:"
|
34
|
+
opts.on('--direct', 'Direct execution with TestBatch (default)') { options[:framework] = :direct }
|
35
|
+
opts.on('--rspec', 'Use RSpec framework') { options[:framework] = :rspec }
|
36
|
+
opts.on('--minitest', 'Use Minitest framework') { options[:framework] = :minitest }
|
37
|
+
|
38
|
+
opts.separator "\nGeneration Options:"
|
39
|
+
opts.on('--generate-rspec', 'Generate RSpec code only') do
|
40
|
+
options[:framework] = :rspec
|
41
|
+
options[:generate_only] = true
|
42
|
+
end
|
43
|
+
opts.on('--generate-minitest', 'Generate Minitest code only') do
|
44
|
+
options[:framework] = :minitest
|
45
|
+
options[:generate_only] = true
|
46
|
+
end
|
47
|
+
opts.on('--generate', 'Generate code only (use with --rspec/--minitest)') do
|
48
|
+
options[:generate_only] = true
|
49
|
+
options[:framework] ||= :rspec
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.separator "\nExecution Options:"
|
53
|
+
opts.on('--shared-context', 'Override default context mode') { options[:shared_context] = true }
|
54
|
+
opts.on('--no-shared-context', 'Override default context mode') { options[:shared_context] = false }
|
55
|
+
opts.on('-v', '--verbose', 'Show detailed test output with line numbers') { options[:verbose] = true }
|
56
|
+
opts.on('-f', '--fails', 'Show only failing tests (with --verbose)') { options[:fails_only] = true }
|
57
|
+
opts.on('-q', '--quiet', 'Minimal output (dots and summary only)') { options[:quiet] = true }
|
58
|
+
opts.on('-c', '--compact', 'Compact single-line output') { options[:compact] = true }
|
59
|
+
|
60
|
+
opts.separator "\nInspection Options:"
|
61
|
+
opts.on('-i', '--inspect', 'Inspect file structure without running tests') { options[:inspect] = true }
|
62
|
+
|
63
|
+
opts.separator "\nGeneral Options:"
|
64
|
+
opts.on('-V', '--version', 'Show version') { options[:version] = true }
|
65
|
+
opts.on('-D', '--debug', 'Enable debug mode') do
|
66
|
+
options[:debug] = true
|
67
|
+
Tryouts.debug = true
|
68
|
+
end
|
69
|
+
opts.on('-h', '--help', 'Show this help') do
|
70
|
+
puts opts
|
71
|
+
exit 0
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.separator HELP.freeze
|
75
|
+
end
|
76
|
+
|
77
|
+
files = parser.parse(args)
|
78
|
+
Tryouts.trace "Parsed files: #{files.inspect}, options: #{options.inspect}"
|
79
|
+
[files, options]
|
80
|
+
rescue OptionParser::InvalidOption => ex
|
81
|
+
Tryouts.info Console.color(:red, "Invalid option error: #{ex.message}")
|
82
|
+
warn "Error: #{ex.message}"
|
83
|
+
warn "Try 'try --help' for more information."
|
84
|
+
exit 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/tryouts/cli.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# lib/tryouts/cli.rb
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
require_relative 'cli/opts'
|
6
|
+
require_relative 'cli/formatters'
|
7
|
+
require_relative 'test_runner'
|
8
|
+
|
9
|
+
class Tryouts
|
10
|
+
class CLI
|
11
|
+
def initialize
|
12
|
+
@options = {
|
13
|
+
framework: :direct,
|
14
|
+
verbose: false,
|
15
|
+
inspect: false,
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(files, **options)
|
20
|
+
@options.merge!(options)
|
21
|
+
|
22
|
+
output_manager = FormatterFactory.create_output_manager(@options)
|
23
|
+
|
24
|
+
handle_version_flag(@options, output_manager)
|
25
|
+
validate_files_exist(files, output_manager)
|
26
|
+
|
27
|
+
runner = TestRunner.new(
|
28
|
+
files: files,
|
29
|
+
options: @options,
|
30
|
+
output_manager: output_manager,
|
31
|
+
)
|
32
|
+
|
33
|
+
runner.run
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def handle_version_flag(options, output_manager)
|
39
|
+
return unless options[:version]
|
40
|
+
|
41
|
+
output_manager.raw("Tryouts version #{Tryouts::VERSION}")
|
42
|
+
exit 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_files_exist(files, output_manager)
|
46
|
+
missing_files = files.reject { |file| File.exist?(file) }
|
47
|
+
|
48
|
+
unless missing_files.empty?
|
49
|
+
missing_files.each { |file| output_manager.error("File not found: #{file}") }
|
50
|
+
exit 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/tryouts/console.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# lib/tryouts/console.rb
|
2
|
+
|
3
|
+
require 'pathname'
|
2
4
|
|
3
5
|
class Tryouts
|
4
6
|
module Console
|
@@ -12,7 +14,7 @@ class Tryouts
|
|
12
14
|
blink: 5,
|
13
15
|
reverse: 7,
|
14
16
|
hidden: 8,
|
15
|
-
default: 0
|
17
|
+
default: 0,
|
16
18
|
}.freeze
|
17
19
|
end
|
18
20
|
|
@@ -28,7 +30,7 @@ class Tryouts
|
|
28
30
|
cyan: 36,
|
29
31
|
white: 37,
|
30
32
|
default: 39,
|
31
|
-
random: 30 + rand(10).to_i
|
33
|
+
random: 30 + rand(10).to_i,
|
32
34
|
}.freeze
|
33
35
|
end
|
34
36
|
|
@@ -44,7 +46,7 @@ class Tryouts
|
|
44
46
|
cyan: 46,
|
45
47
|
white: 47,
|
46
48
|
default: 49,
|
47
|
-
random: 40 + rand(10).to_i
|
49
|
+
random: 40 + rand(10).to_i,
|
48
50
|
}.freeze
|
49
51
|
end
|
50
52
|
|
@@ -73,50 +75,63 @@ class Tryouts
|
|
73
75
|
Console.bgcolor(col, self)
|
74
76
|
end
|
75
77
|
end
|
78
|
+
class << self
|
79
|
+
def bright(str)
|
80
|
+
str = [style(ATTRIBUTES[:bright]), str, default_style].join
|
81
|
+
str.extend Console::InstanceMethods
|
82
|
+
str
|
83
|
+
end
|
76
84
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
85
|
+
def underline(str)
|
86
|
+
str = [style(ATTRIBUTES[:underline]), str, default_style].join
|
87
|
+
str.extend Console::InstanceMethods
|
88
|
+
str
|
89
|
+
end
|
82
90
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
91
|
+
def reverse(str)
|
92
|
+
str = [style(ATTRIBUTES[:reverse]), str, default_style].join
|
93
|
+
str.extend Console::InstanceMethods
|
94
|
+
str
|
95
|
+
end
|
88
96
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
97
|
+
def color(col, str)
|
98
|
+
str = [style(COLOURS[col]), str, default_style].join
|
99
|
+
str.extend Console::InstanceMethods
|
100
|
+
str
|
101
|
+
end
|
94
102
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
103
|
+
def att(name, str)
|
104
|
+
str = [style(ATTRIBUTES[name]), str, default_style].join
|
105
|
+
str.extend Console::InstanceMethods
|
106
|
+
str
|
107
|
+
end
|
100
108
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
109
|
+
def bgcolor(col, str)
|
110
|
+
str = [style(ATTRIBUTES[col]), str, default_style].join
|
111
|
+
str.extend Console::InstanceMethods
|
112
|
+
str
|
113
|
+
end
|
106
114
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
115
|
+
def style(*att)
|
116
|
+
# => \e[8;34;42m
|
117
|
+
"\e[%sm" % att.join(';')
|
118
|
+
end
|
112
119
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
120
|
+
def default_style
|
121
|
+
style(ATTRIBUTES[:default], COLOURS[:default], BGCOLOURS[:default])
|
122
|
+
end
|
117
123
|
|
118
|
-
|
119
|
-
|
124
|
+
# Converts an absolute file path to a path relative to the application's
|
125
|
+
# base directory. This simplifies logging and error reporting by showing
|
126
|
+
# only the relevant parts of file paths instead of lengthy absolute paths.
|
127
|
+
#
|
128
|
+
def pretty_path(file)
|
129
|
+
return nil if file.nil?
|
130
|
+
|
131
|
+
file = File.expand_path(file) # be absolutely sure
|
132
|
+
basepath = File.expand_path('..', TRYOUTS_LIB_HOME)
|
133
|
+
Pathname.new(file).relative_path_from(basepath).to_s
|
134
|
+
end
|
120
135
|
end
|
121
136
|
end
|
122
137
|
end
|