tryouts 3.1.2 → 3.2.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.
@@ -18,8 +18,16 @@ class Tryouts
18
18
 
19
19
  File Format:
20
20
  ## Test description # Test case marker
21
- code_to_test # Ruby code
22
- #=> expected_result # Expectation
21
+ code_to_test # Ruby code
22
+ #=> expected_result # Expectation (various types available)
23
+
24
+ Great Expectations System:
25
+ Multiple expectation types are supported for different testing needs.
26
+
27
+ #=> Value equality #==> Must be true #=/=> Must be false
28
+ #=|> True OR false #=!> Must raise error #=:> Type matching
29
+ #=~> Regex matching #=%> Time constraints #=1> STDOUT content
30
+ #=2> STDERR content #=<> Intentional failure
23
31
  HELP
24
32
 
25
33
  class << self
@@ -50,12 +58,13 @@ class Tryouts
50
58
  end
51
59
 
52
60
  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 }
61
+ opts.on('--shared-context', 'Override default context mode') { options[:shared_context] = true }
62
+ opts.on('--no-shared-context', 'Override default context mode') { options[:shared_context] = false }
63
+ opts.on('-v', '--verbose', 'Show detailed test output with line numbers') { options[:verbose] = true }
64
+ opts.on('-f', '--fails', 'Show only failing tests') { options[:fails_only] = true }
65
+ opts.on('-q', '--quiet', 'Minimal output (dots and summary only)') { options[:quiet] = true }
66
+ opts.on('-c', '--compact', 'Compact single-line output') { options[:compact] = true }
67
+ opts.on('-l', '--live', 'Live status display') { options[:live_status] = true }
59
68
 
60
69
  opts.separator "\nInspection Options:"
61
70
  opts.on('-i', '--inspect', 'Inspect file structure without running tests') { options[:inspect] = true }
@@ -0,0 +1,92 @@
1
+ # lib/tryouts/cli/tty_detector.rb
2
+
3
+ require 'tty-screen'
4
+
5
+ class Tryouts
6
+ class CLI
7
+ # TTY detection utility for determining live formatter availability
8
+ module TTYDetector
9
+ STATUS_LINES = 4 # Lines needed for live formatter status area
10
+
11
+ # Check if TTY features are available for live formatting
12
+ # Returns: { available: boolean, reason: string }
13
+ def self.check_tty_support(debug: false)
14
+ result = { available: false, reason: nil }
15
+
16
+ # FORCE_LIVE override for testing
17
+ if ENV['FORCE_LIVE'] == '1'
18
+ debug_log('FORCE_LIVE=1 - forcing TTY support', debug)
19
+ result[:available] = true
20
+ result[:reason] = 'Forced via FORCE_LIVE=1'
21
+ return result
22
+ end
23
+
24
+ # Enhanced TTY detection to work with bundler and other execution contexts
25
+ debug_log('TTY Detection:', debug)
26
+ debug_log(" $stdout.tty? = #{$stdout.tty?}", debug)
27
+ debug_log(" $stderr.tty? = #{$stderr.tty?}", debug)
28
+ debug_log(" $stdin.tty? = #{$stdin.tty?}", debug)
29
+
30
+ # Check if any standard stream is a TTY or if we have a controlling terminal
31
+ has_tty = $stdout.tty? || $stderr.tty? || $stdin.tty?
32
+ debug_log(" Combined streams TTY: #{has_tty}", debug)
33
+
34
+ # Additional check: try to access controlling terminal directly
35
+ unless has_tty
36
+ begin
37
+ # On Unix systems, /dev/tty represents the controlling terminal
38
+ File.open('/dev/tty', 'r') { |f| has_tty = f.tty? }
39
+ debug_log(" /dev/tty accessible: #{has_tty}", debug)
40
+ rescue StandardError => ex
41
+ debug_log(" /dev/tty error: #{ex.class}: #{ex.message}", debug)
42
+ end
43
+ end
44
+
45
+ unless has_tty
46
+ debug_log(' Final result: No TTY detected', debug)
47
+ result[:reason] = 'No TTY detected (not running in terminal)'
48
+ return result
49
+ end
50
+
51
+ # Skip in CI or dumb terminals
52
+ if ENV['CI'] || ENV['TERM'] == 'dumb'
53
+ debug_log(" CI or dumb terminal detected (CI=#{ENV['CI']}, TERM=#{ENV.fetch('TERM', nil)})", debug)
54
+ result[:reason] = 'CI environment or dumb terminal detected'
55
+ return result
56
+ end
57
+
58
+ # Test TTY gem availability and basic functionality
59
+ begin
60
+ height = TTY::Screen.height
61
+ debug_log(" Screen height: #{height}, need minimum: #{STATUS_LINES + 5}", debug)
62
+
63
+ if height < STATUS_LINES + 5 # Need minimum screen space
64
+ debug_log(' Screen too small', debug)
65
+ result[:reason] = "Terminal too small (#{height} lines < #{STATUS_LINES + 5} needed)"
66
+ return result
67
+ end
68
+
69
+ # Test cursor control (basic check without actually saving)
70
+ require 'tty-cursor'
71
+ TTY::Cursor.save # Just test that it exists
72
+
73
+ debug_log(' TTY support enabled', debug)
74
+ result[:available] = true
75
+ result[:reason] = 'TTY support available'
76
+ rescue LoadError => ex
77
+ debug_log(" TTY gem loading failed: #{ex.message}", debug)
78
+ result[:reason] = "TTY gems not available: #{ex.message}"
79
+ rescue StandardError => ex
80
+ debug_log(" TTY setup failed: #{ex.class}: #{ex.message}", debug)
81
+ result[:reason] = "TTY setup failed: #{ex.message}"
82
+ end
83
+
84
+ result
85
+ end
86
+
87
+ def self.debug_log(message, debug_enabled)
88
+ $stderr.puts "DEBUG: #{message}" if debug_enabled
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,109 @@
1
+ # lib/tryouts/failure_collector.rb
2
+
3
+ require_relative 'console'
4
+
5
+ class Tryouts
6
+ # Collects and organizes failed test results across files for summary display
7
+ # Similar to RSpec's failure summary at the end of test runs
8
+ class FailureCollector
9
+ # Data structure for a single failure entry
10
+ FailureEntry = Data.define(:file_path, :test_case, :result_packet) do
11
+ def line_number
12
+ # Use last line of range (expectation line) for failure display
13
+ test_case.line_range&.last || test_case.first_expectation_line || 0
14
+ end
15
+
16
+ def description
17
+ desc = test_case.description.to_s.strip
18
+ desc.empty? ? 'unnamed test' : desc
19
+ end
20
+
21
+ def failure_reason
22
+ case result_packet.status
23
+ when :failed
24
+ if result_packet.actual_results.any? && result_packet.expected_results.any?
25
+ "expected #{result_packet.first_expected.inspect}, got #{result_packet.first_actual.inspect}"
26
+ else
27
+ 'test failed'
28
+ end
29
+ when :error
30
+ error_msg = result_packet.error&.message || 'unknown error'
31
+ "#{result_packet.error&.class&.name || 'Error'}: #{error_msg}"
32
+ else
33
+ 'test did not pass'
34
+ end
35
+ end
36
+
37
+ def source_context
38
+ return [] unless test_case.source_lines
39
+
40
+ # Show the test code (excluding setup/teardown)
41
+ test_case.source_lines.reject do |line|
42
+ line.strip.empty? || line.strip.start_with?('#')
43
+ end.first(3) # Limit to first 3 relevant lines
44
+ end
45
+ end
46
+
47
+ def initialize
48
+ @failures = []
49
+ @files_with_failures = Set.new
50
+ end
51
+
52
+ # Add a failed test result
53
+ def add_failure(file_path, result_packet)
54
+ return unless result_packet.failed? || result_packet.error?
55
+
56
+ entry = FailureEntry.new(
57
+ file_path: file_path,
58
+ test_case: result_packet.test_case,
59
+ result_packet: result_packet,
60
+ )
61
+
62
+ @failures << entry
63
+ @files_with_failures << file_path
64
+ end
65
+
66
+ # Check if any failures were collected
67
+ def any_failures?
68
+ !@failures.empty?
69
+ end
70
+
71
+ # Get count of total failures
72
+ def failure_count
73
+ @failures.count { |f| f.result_packet.failed? }
74
+ end
75
+
76
+ # Get count of total errors
77
+ def error_count
78
+ @failures.count { |f| f.result_packet.error? }
79
+ end
80
+
81
+ # Get total issues (failures + errors)
82
+ def total_issues
83
+ @failures.size
84
+ end
85
+
86
+ # Get count of files with failures
87
+ def files_with_failures_count
88
+ @files_with_failures.size
89
+ end
90
+
91
+ # Get failures grouped by file for summary display
92
+ def failures_by_file
93
+ @failures.group_by(&:file_path).transform_values do |file_failures|
94
+ file_failures.sort_by(&:line_number)
95
+ end
96
+ end
97
+
98
+ # Get all failure entries (for detailed processing)
99
+ def all_failures
100
+ @failures.dup
101
+ end
102
+
103
+ # Reset the collector (useful for testing)
104
+ def clear
105
+ @failures.clear
106
+ @files_with_failures.clear
107
+ end
108
+ end
109
+ end
@@ -110,7 +110,7 @@ class Tryouts
110
110
 
111
111
  result
112
112
  rescue StandardError => ex
113
- @output_manager&.test_end(test_case, idx, @test_case_count, status: :failed, error: ex)
113
+ @output_manager&.test_end(test_case, idx, @test_case_count)
114
114
  # Create error result packet to maintain consistent data flow
115
115
  error_result = build_error_result(test_case, ex)
116
116
  process_test_result(error_result)
@@ -367,13 +367,18 @@ class Tryouts
367
367
 
368
368
  if result.failed? || result.error?
369
369
  @failed_count += 1
370
+
371
+ # Collect failure details for end-of-run summary
372
+ if @global_tally && @global_tally[:failure_collector]
373
+ @global_tally[:failure_collector].add_failure(@testrun.source_file, result)
374
+ end
370
375
  end
371
376
 
372
377
  show_test_result(result)
373
378
 
374
379
  # Show captured output if any exists
375
380
  if result.has_output?
376
- @output_manager&.test_output(result.test_case, result.captured_output)
381
+ @output_manager&.test_output(result.test_case, result.captured_output, result)
377
382
  end
378
383
  end
379
384
 
@@ -494,9 +499,8 @@ class Tryouts
494
499
  end
495
500
 
496
501
  def show_summary(elapsed_time)
497
- # Use actual executed test count, not total tests in file
498
- executed_count = @results.size
499
- @output_manager&.batch_summary(executed_count, @failed_count, elapsed_time)
502
+ # Summary is now handled by TestRunner with failure details
503
+ # This method kept for compatibility but no longer calls batch_summary
500
504
  end
501
505
 
502
506
  # Helper methods using pattern matching
@@ -5,6 +5,7 @@ require_relative 'test_batch'
5
5
  require_relative 'translators/rspec_translator'
6
6
  require_relative 'translators/minitest_translator'
7
7
  require_relative 'file_processor'
8
+ require_relative 'failure_collector'
8
9
 
9
10
  class Tryouts
10
11
  class TestRunner
@@ -32,6 +33,7 @@ class Tryouts
32
33
  validate_framework
33
34
 
34
35
  result = process_files
36
+ show_failure_summary
35
37
  show_grand_total if @global_tally[:file_count] > 1
36
38
  result
37
39
  end
@@ -73,6 +75,7 @@ class Tryouts
73
75
  file_count: 0,
74
76
  start_time: Time.now,
75
77
  successful_files: 0,
78
+ failure_collector: FailureCollector.new,
76
79
  }
77
80
  end
78
81
 
@@ -104,8 +107,16 @@ class Tryouts
104
107
  1
105
108
  end
106
109
 
110
+ def show_failure_summary
111
+ # Show failure summary if any failures exist
112
+ if @global_tally[:failure_collector].any_failures?
113
+ @output_manager.batch_summary(@global_tally[:failure_collector])
114
+ end
115
+ end
116
+
107
117
  def show_grand_total
108
118
  elapsed_time = Time.now - @global_tally[:start_time]
119
+
109
120
  @output_manager.grand_total(
110
121
  @global_tally[:total_tests],
111
122
  @global_tally[:total_failed],
@@ -1,5 +1,5 @@
1
1
  # lib/tryouts/version.rb
2
2
 
3
3
  class Tryouts
4
- VERSION = '3.1.2'
4
+ VERSION = '3.2.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tryouts
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -9,6 +9,34 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: irb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: prism
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
12
40
  - !ruby/object:Gem::Dependency
13
41
  name: minitest
14
42
  requirement: !ruby/object:Gem::Requirement
@@ -37,8 +65,50 @@ dependencies:
37
65
  - - "~>"
38
66
  - !ruby/object:Gem::Version
39
67
  version: '3.0'
40
- description: A simple test framework for Ruby code that uses introspection to allow
41
- defining checks in comments.
68
+ - !ruby/object:Gem::Dependency
69
+ name: pastel
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.8'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.8'
82
+ - !ruby/object:Gem::Dependency
83
+ name: tty-cursor
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.7'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.7'
96
+ - !ruby/object:Gem::Dependency
97
+ name: tty-screen
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.8'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.8'
110
+ description: A simple test framework for Ruby code where the test descriptions and
111
+ expectations are written as comments.
42
112
  email: gems@solutious.com
43
113
  executables:
44
114
  - try
@@ -56,12 +126,16 @@ files:
56
126
  - lib/tryouts/cli/formatters/base.rb
57
127
  - lib/tryouts/cli/formatters/compact.rb
58
128
  - lib/tryouts/cli/formatters/factory.rb
129
+ - lib/tryouts/cli/formatters/live_status_manager.rb
59
130
  - lib/tryouts/cli/formatters/output_manager.rb
60
131
  - lib/tryouts/cli/formatters/quiet.rb
132
+ - lib/tryouts/cli/formatters/test_run_state.rb
133
+ - lib/tryouts/cli/formatters/tty_status_display.rb
61
134
  - lib/tryouts/cli/formatters/verbose.rb
62
135
  - lib/tryouts/cli/modes/generate.rb
63
136
  - lib/tryouts/cli/modes/inspect.rb
64
137
  - lib/tryouts/cli/opts.rb
138
+ - lib/tryouts/cli/tty_detector.rb
65
139
  - lib/tryouts/console.rb
66
140
  - lib/tryouts/expectation_evaluators/base.rb
67
141
  - lib/tryouts/expectation_evaluators/boolean.rb
@@ -76,6 +150,7 @@ files:
76
150
  - lib/tryouts/expectation_evaluators/regular.rb
77
151
  - lib/tryouts/expectation_evaluators/result_type.rb
78
152
  - lib/tryouts/expectation_evaluators/true.rb
153
+ - lib/tryouts/failure_collector.rb
79
154
  - lib/tryouts/file_processor.rb
80
155
  - lib/tryouts/prism_parser.rb
81
156
  - lib/tryouts/test_batch.rb