tryouts 3.5.0 → 3.5.2
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 +5 -8
- data/exe/try +6 -0
- data/lib/tryouts/cli/formatters/agent.rb +206 -34
- data/lib/tryouts/cli/opts.rb +15 -15
- data/lib/tryouts/expectation_evaluators/result_type.rb +15 -0
- data/lib/tryouts/file_processor.rb +2 -2
- data/lib/tryouts/parser_warning.rb +10 -0
- data/lib/tryouts/parsers/CLAUDE.md +178 -0
- data/lib/tryouts/parsers/base_parser.rb +101 -1
- data/lib/tryouts/parsers/enhanced_parser.rb +177 -25
- data/lib/tryouts/parsers/legacy_parser.rb +254 -0
- data/lib/tryouts/parsers/shared_methods.rb +5 -1
- data/lib/tryouts/test_batch.rb +1 -1
- data/lib/tryouts/test_executor.rb +12 -4
- data/lib/tryouts/test_result_aggregator.rb +15 -11
- data/lib/tryouts/test_runner.rb +18 -15
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +1 -1
- metadata +3 -2
- data/lib/tryouts/parsers/prism_parser.rb +0 -122
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 958254585920c850e17a18e13699040fad97ec8610f3c2141b32899016dc4194
|
4
|
+
data.tar.gz: 3666350beb13d17cf24bf184350fd88746cebbf7b7efc109c3e5af7f8d5c6c3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a1091a28256b97c210f8a0a970db3a78d715e1b195e95a5a844e8f32b7d65f7e6d5cb49209baaf2c061b02500ad6bf2d71968cbcb76c16206108eabfa8039f3
|
7
|
+
data.tar.gz: 9613b25213146c5da6dbd5153fa101e8139e205f06fb5bcddb8fa172a82fc76dffff56f79d69609c4764623ac9bf0682a0ba8ef4e76fd49517920a9b3e1141af
|
data/README.md
CHANGED
@@ -132,18 +132,15 @@ try --agent --agent-limit 1000 # limit output to 1000 tokens
|
|
132
132
|
|
133
133
|
#### Why Not Pipe Test Output Directly to AI?
|
134
134
|
|
135
|
-
|
135
|
+
I mean, you could. If that already works well, you could probably still benefit from an agent that is able to focus on the critical information for the task. And the extra context window space.
|
136
136
|
|
137
|
-
|
138
|
-
- **Signal vs noise**: Important failures get buried in passing test details and framework boilerplate
|
139
|
-
- **Inconsistent parsing**: AI struggles with varying output formats across different test runs
|
140
|
-
- **Context overflow**: Large test suites exceed AI token limits, truncating critical information
|
137
|
+
Raw test output creates problems when working with AI assistants: high token usage with inconsistent parsing across different runs, where the same logical failure might be interpreted differently, making it difficult to reliably produce and analyze results consistently.
|
141
138
|
|
142
|
-
####
|
139
|
+
#### TOPAZ: A Better Approach
|
143
140
|
|
144
|
-
Tryouts' `--agent` mode inspired the development of **
|
141
|
+
Tryouts' `--agent` mode inspired the development of **TOPAZ (Test Output Protocol for AI Zealots)** - a standardized format optimized for AI analysis. The [tpane](https://github.com/delano/tpane) tool implements this protocol, transforming any test framework's output into structured, token-efficient formats.
|
145
142
|
|
146
|
-
Instead of overwhelming AI with raw output,
|
143
|
+
Instead of overwhelming AI with raw output, TOPAZ provides clean semantic data focusing on what actually needs attention - failures, errors, and actionable context.
|
147
144
|
|
148
145
|
### Exit Codes
|
149
146
|
|
data/exe/try
CHANGED
@@ -38,10 +38,16 @@ require_relative '../lib/tryouts'
|
|
38
38
|
lib_glob = File.join(Dir.pwd, '{lib,../lib,.}')
|
39
39
|
Tryouts.update_load_path(lib_glob) if Tryouts.respond_to?(:update_load_path)
|
40
40
|
|
41
|
+
# Capture original command for agent mode before ARGV gets modified
|
42
|
+
original_command = [$0] + ARGV
|
43
|
+
|
41
44
|
# Parse args and run CLI
|
42
45
|
begin
|
43
46
|
files, options = Tryouts::CLI.parse_args(ARGV)
|
44
47
|
|
48
|
+
# Add original command to options for agent formatter
|
49
|
+
options[:original_command] = original_command
|
50
|
+
|
45
51
|
# Expand files if directories are given, preserving line specs
|
46
52
|
expanded_files = []
|
47
53
|
files.each do |file_or_dir|
|
@@ -4,13 +4,42 @@ require_relative 'token_budget'
|
|
4
4
|
|
5
5
|
class Tryouts
|
6
6
|
class CLI
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# -
|
10
|
-
#
|
11
|
-
# -
|
12
|
-
#
|
13
|
-
#
|
7
|
+
# TOPAZ (Test Output Protocol for AI Zealots) Formatter
|
8
|
+
#
|
9
|
+
# Language-agnostic test output format designed for LLM context management.
|
10
|
+
# This formatter implements the TOPAZ v1.0 specification for structured,
|
11
|
+
# token-efficient test result communication.
|
12
|
+
#
|
13
|
+
# TOPAZ Features:
|
14
|
+
# - Language-agnostic field naming (snake_case, hierarchical)
|
15
|
+
# - Standardized execution context (runtime, environment, VCS)
|
16
|
+
# - Token budget awareness with smart truncation
|
17
|
+
# - Cross-platform compatibility (CI/CD, package managers)
|
18
|
+
# - Structured failure reporting with diffs
|
19
|
+
# - Protocol versioning for forward compatibility
|
20
|
+
#
|
21
|
+
# Field Specifications:
|
22
|
+
# - command: Exact command executed
|
23
|
+
# - process_id: System process identifier
|
24
|
+
# - runtime: Language, version, platform info
|
25
|
+
# - package_manager: Dependency management system
|
26
|
+
# - version_control: VCS branch/commit info
|
27
|
+
# - environment: Normalized env vars (ci_system, app_env, etc.)
|
28
|
+
# - test_framework: Framework name, isolation mode, parser
|
29
|
+
# - execution_flags: Runtime flags in normalized form
|
30
|
+
# - protocol: TOPAZ version and configuration
|
31
|
+
# - project: Auto-detected project type
|
32
|
+
# - test_discovery: File pattern matching rules
|
33
|
+
#
|
34
|
+
# Compatible with: Ruby/RSpec/Minitest, Python/pytest/unittest,
|
35
|
+
# JavaScript/Jest/Mocha, Java/JUnit, Go, C#/NUnit, and more.
|
36
|
+
#
|
37
|
+
# Language Adaptation Examples:
|
38
|
+
# Python: runtime.language=python, package_manager.name=pip/poetry/conda
|
39
|
+
# Node.js: runtime.language=javascript, package_manager.name=npm/yarn/pnpm
|
40
|
+
# Java: runtime.language=java, package_manager.name=maven/gradle
|
41
|
+
# Go: runtime.language=go, package_manager.name=go_modules
|
42
|
+
# C#: runtime.language=csharp, package_manager.name=nuget/dotnet
|
14
43
|
class AgentFormatter
|
15
44
|
include FormatterInterface
|
16
45
|
|
@@ -20,7 +49,7 @@ class Tryouts
|
|
20
49
|
@focus_mode = options[:agent_focus] || :failures
|
21
50
|
@collected_files = []
|
22
51
|
@current_file_data = nil
|
23
|
-
@total_stats = { files: 0, tests: 0, failures: 0, errors: 0,
|
52
|
+
@total_stats = { files: 0, tests: 0, failures: 0, errors: 0, elapsed_time: 0 }
|
24
53
|
@output_rendered = false
|
25
54
|
@options = options # Store all options for execution context display
|
26
55
|
@all_warnings = [] # Store warnings globally for execution details
|
@@ -91,7 +120,7 @@ class Tryouts
|
|
91
120
|
# Always update global totals
|
92
121
|
@total_stats[:failures] += failed_count
|
93
122
|
@total_stats[:errors] += error_count
|
94
|
-
@total_stats[:
|
123
|
+
@total_stats[:elapsed_time] += elapsed_time if elapsed_time
|
95
124
|
|
96
125
|
# Update per-file data - file_result is called AFTER file_end, so data is in @collected_files
|
97
126
|
relative_file_path = relative_path(file_path)
|
@@ -146,7 +175,7 @@ class Tryouts
|
|
146
175
|
error_count: @collected_files.sum { |f| f[:errors].size },
|
147
176
|
successful_files: @collected_files.size - @collected_files.count { |f| f[:failures].any? || f[:errors].any? },
|
148
177
|
total_files: @collected_files.size,
|
149
|
-
elapsed_time: @total_stats[:
|
178
|
+
elapsed_time: @total_stats[:elapsed_time]
|
150
179
|
) unless @output_rendered
|
151
180
|
end
|
152
181
|
|
@@ -159,7 +188,7 @@ class Tryouts
|
|
159
188
|
errors: error_count,
|
160
189
|
successful_files: successful_files,
|
161
190
|
total_files: total_files,
|
162
|
-
|
191
|
+
elapsed_time: elapsed_time,
|
163
192
|
)
|
164
193
|
|
165
194
|
# Now render all collected data
|
@@ -249,6 +278,12 @@ class Tryouts
|
|
249
278
|
def render_summary_only
|
250
279
|
output = []
|
251
280
|
|
281
|
+
time_str = if @total_stats[:elapsed_time] < 2.0
|
282
|
+
" (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
|
283
|
+
else
|
284
|
+
" (#{@total_stats[:elapsed_time].round(2)}s)"
|
285
|
+
end
|
286
|
+
|
252
287
|
# Add execution context header for agent clarity
|
253
288
|
output << render_execution_context
|
254
289
|
output << ""
|
@@ -264,13 +299,13 @@ class Tryouts
|
|
264
299
|
details = []
|
265
300
|
details << "#{failed_count} failed" if failed_count > 0
|
266
301
|
details << "#{error_count} errors" if error_count > 0
|
267
|
-
status_parts << "FAIL: #{issues_count}/#{@total_stats[:tests]} tests (#{details.join(', ')}, #{passed_count} passed)"
|
302
|
+
status_parts << "FAIL: #{issues_count}/#{@total_stats[:tests]} tests (#{details.join(', ')}, #{passed_count} passed#{time_str})"
|
268
303
|
else
|
269
304
|
# Agent doesn't need output in the positive case (i.e. for passing
|
270
305
|
# tests). It just fills out the context window.
|
271
306
|
end
|
272
307
|
|
273
|
-
status_parts << "(#{format_time(@total_stats[:
|
308
|
+
status_parts << "(#{format_time(@total_stats[:elapsed_time])})" if @total_stats[:elapsed_time]
|
274
309
|
|
275
310
|
output << status_parts.join(" ")
|
276
311
|
|
@@ -302,6 +337,12 @@ class Tryouts
|
|
302
337
|
# Only show errors (exceptions), skip assertion failures
|
303
338
|
critical_files = @collected_files.select { |f| f[:errors].any? }
|
304
339
|
|
340
|
+
time_str = if @total_stats[:elapsed_time] < 2.0
|
341
|
+
" (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
|
342
|
+
else
|
343
|
+
" (#{@total_stats[:elapsed_time].round(2)}s)"
|
344
|
+
end
|
345
|
+
|
305
346
|
output = []
|
306
347
|
|
307
348
|
# Add execution context header for agent clarity
|
@@ -314,7 +355,7 @@ class Tryouts
|
|
314
355
|
return
|
315
356
|
end
|
316
357
|
|
317
|
-
output << "CRITICAL: #{critical_files.size} file#{'s' if critical_files.size != 1} with errors"
|
358
|
+
output << "CRITICAL: #{critical_files.size} file#{'s' if critical_files.size != 1} with errors#{time_str}"
|
318
359
|
output << ""
|
319
360
|
|
320
361
|
critical_files.each do |file_data|
|
@@ -344,6 +385,12 @@ class Tryouts
|
|
344
385
|
def render_full_structured
|
345
386
|
output = []
|
346
387
|
|
388
|
+
time_str = if @total_stats[:elapsed_time] < 2.0
|
389
|
+
" (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
|
390
|
+
else
|
391
|
+
" (#{@total_stats[:elapsed_time].round(2)}s)"
|
392
|
+
end
|
393
|
+
|
347
394
|
# Add execution context header for agent clarity
|
348
395
|
output << render_execution_context
|
349
396
|
output << ""
|
@@ -379,7 +426,7 @@ class Tryouts
|
|
379
426
|
summary = "Summary: \n"
|
380
427
|
summary += "#{passed_count} testcases passed, #{failed_count} failed"
|
381
428
|
summary += ", #{error_count} errors" if error_count > 0
|
382
|
-
summary += " in #{@total_stats[:files]} files"
|
429
|
+
summary += " in #{@total_stats[:files]} files#{time_str}"
|
383
430
|
|
384
431
|
output << summary
|
385
432
|
|
@@ -506,9 +553,41 @@ class Tryouts
|
|
506
553
|
|
507
554
|
def render_execution_context
|
508
555
|
context_lines = []
|
509
|
-
context_lines << "
|
556
|
+
context_lines << "EXECUTION_CONTEXT:"
|
557
|
+
|
558
|
+
# Command that was executed
|
559
|
+
if @options[:original_command]
|
560
|
+
command_str = @options[:original_command].join(' ')
|
561
|
+
context_lines << " command: #{command_str}"
|
562
|
+
end
|
510
563
|
|
511
|
-
#
|
564
|
+
# Compact system info on one line when possible
|
565
|
+
context_lines << " pid: #{Process.pid} | pwd: #{Dir.pwd}"
|
566
|
+
|
567
|
+
# Runtime - compact format
|
568
|
+
platform = RUBY_PLATFORM.gsub(/darwin\d+/, 'darwin') # Simplify darwin25 -> darwin
|
569
|
+
context_lines << " runtime: ruby #{RUBY_VERSION} (#{platform}); tryouts #{Tryouts::VERSION}"
|
570
|
+
|
571
|
+
# Package manager - only if present, compact format
|
572
|
+
if defined?(Bundler)
|
573
|
+
context_lines << " package_manager: bundler #{Bundler::VERSION}"
|
574
|
+
end
|
575
|
+
|
576
|
+
# Version control - compact single line with timeout protection
|
577
|
+
git_info = safe_git_info
|
578
|
+
if git_info[:branch] && git_info[:commit] && !git_info[:branch].empty? && !git_info[:commit].empty?
|
579
|
+
context_lines << " vcs: git #{git_info[:branch]}@#{git_info[:commit]}"
|
580
|
+
end
|
581
|
+
|
582
|
+
# Environment - only non-defaults
|
583
|
+
env_vars = build_environment_context
|
584
|
+
if env_vars.any?
|
585
|
+
# Compact key=value format
|
586
|
+
env_str = env_vars.map { |k, v| "#{k}=#{v}" }.join(', ')
|
587
|
+
context_lines << " environment: #{env_str}"
|
588
|
+
end
|
589
|
+
|
590
|
+
# Test framework - compact critical info only
|
512
591
|
framework = @options[:framework] || :direct
|
513
592
|
shared_context = if @options.key?(:shared_context)
|
514
593
|
@options[:shared_context]
|
@@ -522,26 +601,24 @@ class Tryouts
|
|
522
601
|
end
|
523
602
|
end
|
524
603
|
|
525
|
-
|
526
|
-
context_lines << "
|
527
|
-
|
528
|
-
# Parser type
|
529
|
-
parser = @options[:parser] || :enhanced
|
530
|
-
context_lines << " Parser: #{parser}"
|
604
|
+
isolation = shared_context ? 'shared' : 'isolated'
|
605
|
+
context_lines << " test_framework: #{framework} (#{isolation})"
|
531
606
|
|
532
|
-
#
|
533
|
-
flags =
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
flags << "stack-traces" if @options[:stack_traces]
|
538
|
-
flags << "parallel(#{@options[:parallel_threads] || 'auto'})" if @options[:parallel]
|
539
|
-
flags << "line-spec" if @options[:line_spec]
|
607
|
+
# Execution flags - only if non-standard
|
608
|
+
flags = build_execution_flags
|
609
|
+
if flags.any?
|
610
|
+
context_lines << " flags: #{flags.join(', ')}"
|
611
|
+
end
|
540
612
|
|
541
|
-
|
613
|
+
# TOPAZ protocol - compact
|
614
|
+
context_lines << " protocol: TOPAZ v0.3 | focus: #{@focus_mode} | limit: #{@budget.limit}"
|
542
615
|
|
543
|
-
#
|
544
|
-
|
616
|
+
# File count being tested
|
617
|
+
if @collected_files && @collected_files.any?
|
618
|
+
context_lines << " files_under_test: #{@collected_files.size}"
|
619
|
+
elsif @total_stats[:files] && @total_stats[:files] > 0
|
620
|
+
context_lines << " files_under_test: #{@total_stats[:files]}"
|
621
|
+
end
|
545
622
|
|
546
623
|
# Add syntax errors if any (these prevent test execution)
|
547
624
|
if @syntax_errors.any?
|
@@ -571,6 +648,101 @@ class Tryouts
|
|
571
648
|
|
572
649
|
context_lines.join("\n")
|
573
650
|
end
|
651
|
+
|
652
|
+
# Build environment context with language-agnostic keys
|
653
|
+
def build_environment_context
|
654
|
+
env_vars = {}
|
655
|
+
|
656
|
+
# CI/CD detection - prioritize most specific
|
657
|
+
if ENV['GITHUB_ACTIONS']
|
658
|
+
env_vars['CI'] = 'github'
|
659
|
+
elsif ENV['GITLAB_CI']
|
660
|
+
env_vars['CI'] = 'gitlab'
|
661
|
+
elsif ENV['JENKINS_URL']
|
662
|
+
env_vars['CI'] = 'jenkins'
|
663
|
+
elsif ENV['CI']
|
664
|
+
env_vars['CI'] = 'true'
|
665
|
+
end
|
666
|
+
|
667
|
+
# Runtime environment - only if not default
|
668
|
+
if ENV['RAILS_ENV'] && ENV['RAILS_ENV'] != 'development'
|
669
|
+
env_vars['ENV'] = ENV['RAILS_ENV']
|
670
|
+
elsif ENV['RACK_ENV'] && ENV['RACK_ENV'] != 'development'
|
671
|
+
env_vars['ENV'] = ENV['RACK_ENV']
|
672
|
+
elsif ENV['NODE_ENV'] && ENV['NODE_ENV'] != 'development'
|
673
|
+
env_vars['ENV'] = ENV['NODE_ENV']
|
674
|
+
end
|
675
|
+
|
676
|
+
# Coverage - simplified
|
677
|
+
env_vars['COV'] = '1' if ENV['COVERAGE'] || ENV['SIMPLECOV']
|
678
|
+
|
679
|
+
# Test seed for reproducibility
|
680
|
+
env_vars['SEED'] = ENV['SEED'] if ENV['SEED']
|
681
|
+
|
682
|
+
env_vars
|
683
|
+
end
|
684
|
+
|
685
|
+
# Build execution flags in language-agnostic format
|
686
|
+
def build_execution_flags
|
687
|
+
flags = []
|
688
|
+
flags << "verbose" if @options[:verbose]
|
689
|
+
flags << "fails-only" if @options[:fails_only]
|
690
|
+
flags << "debug" if @options[:debug]
|
691
|
+
flags << "traces" if @options[:stack_traces] && !@options[:debug] # debug implies traces
|
692
|
+
flags << "parallel" if @options[:parallel]
|
693
|
+
flags << "line-spec" if @options[:line_spec]
|
694
|
+
flags << "strict" if @options[:strict]
|
695
|
+
flags << "quiet" if @options[:quiet]
|
696
|
+
flags
|
697
|
+
end
|
698
|
+
|
699
|
+
# Get test discovery patterns in language-agnostic format
|
700
|
+
def get_test_discovery_patterns
|
701
|
+
patterns = []
|
702
|
+
|
703
|
+
# Ruby/Tryouts patterns
|
704
|
+
patterns.concat([
|
705
|
+
"**/*_try.rb",
|
706
|
+
"**/*.try.rb",
|
707
|
+
"try/**/*.rb",
|
708
|
+
"tryouts/**/*.rb"
|
709
|
+
])
|
710
|
+
|
711
|
+
# TOPA-compatible patterns for other languages:
|
712
|
+
# Python: ["**/*_test.py", "**/test_*.py", "tests/**/*.py"]
|
713
|
+
# JavaScript: ["**/*.test.js", "**/*.spec.js", "__tests__/**/*.js"]
|
714
|
+
# Java: ["**/*Test.java", "**/Test*.java", "src/test/**/*.java"]
|
715
|
+
# Go: ["**/*_test.go"]
|
716
|
+
# C#: ["**/*Test.cs", "**/*Tests.cs"]
|
717
|
+
# PHP: ["**/*Test.php", "tests/**/*.php"]
|
718
|
+
# Rust: ["**/*_test.rs", "tests/**/*.rs"]
|
719
|
+
|
720
|
+
patterns
|
721
|
+
end
|
722
|
+
|
723
|
+
private
|
724
|
+
|
725
|
+
# Safely get git information with timeout protection
|
726
|
+
def safe_git_info
|
727
|
+
# Check if we're in a git repository
|
728
|
+
return {} unless File.directory?('.git') || system('git rev-parse --git-dir >/dev/null 2>&1')
|
729
|
+
|
730
|
+
require 'timeout'
|
731
|
+
|
732
|
+
Timeout.timeout(2) do
|
733
|
+
branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
|
734
|
+
commit = `git rev-parse --short HEAD 2>/dev/null`.strip
|
735
|
+
|
736
|
+
# Validate output to prevent injection
|
737
|
+
branch = nil unless branch =~ /\A[\w\-\/\.]+\z/
|
738
|
+
commit = nil unless commit =~ /\A[a-f0-9]+\z/i
|
739
|
+
|
740
|
+
{ branch: branch, commit: commit }
|
741
|
+
end
|
742
|
+
rescue Timeout::Error, StandardError
|
743
|
+
# Return empty hash on any error (timeout, permission, etc.)
|
744
|
+
{}
|
745
|
+
end
|
574
746
|
end
|
575
747
|
end
|
576
748
|
end
|
data/lib/tryouts/cli/opts.rb
CHANGED
@@ -10,20 +10,20 @@ class Tryouts
|
|
10
10
|
Minitest: Fresh context (each test isolated)
|
11
11
|
|
12
12
|
Examples:
|
13
|
-
try test_try.rb
|
14
|
-
try --rspec test_try.rb
|
15
|
-
try --direct --shared-context test_try.rb
|
16
|
-
try --generate-rspec test_try.rb
|
17
|
-
try --inspect test_try.rb
|
18
|
-
try --agent test_try.rb
|
19
|
-
try --agent --agent-limit 10000 tests/
|
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
|
+
try --agent test_try.rb # Agent-optimized structured output
|
19
|
+
try --agent --agent-limit 10000 tests/ # Agent mode with 10K token limit
|
20
20
|
|
21
21
|
Agent Output Modes:
|
22
|
-
--agent
|
23
|
-
--agent-focus summary
|
24
|
-
--agent-focus first-failure
|
25
|
-
--agent-focus critical
|
26
|
-
--agent-limit 1000
|
22
|
+
--agent # Structured, token-efficient output
|
23
|
+
--agent-focus summary # Show counts and problem files only
|
24
|
+
--agent-focus first-failure # Show first failure per file
|
25
|
+
--agent-focus critical # Show errors/exceptions only
|
26
|
+
--agent-limit 1000 # Limit output to 1000 tokens
|
27
27
|
|
28
28
|
File Naming & Organization:
|
29
29
|
Files must end with '_try.rb' or '.try.rb' (e.g., auth_service_try.rb, user_model.try.rb)
|
@@ -37,7 +37,7 @@ class Tryouts
|
|
37
37
|
#=> true # this is the expected result
|
38
38
|
|
39
39
|
File Structure (3 sections):
|
40
|
-
# Setup section (optional) - runs once before all tests
|
40
|
+
# Setup section (optional) - code before first testcase runs once before all tests
|
41
41
|
@shared_var = "available to all test cases"
|
42
42
|
|
43
43
|
## TEST: Feature description
|
@@ -45,9 +45,9 @@ class Tryouts
|
|
45
45
|
result = some_operation()
|
46
46
|
#=> expected_value
|
47
47
|
|
48
|
-
# Teardown section (optional) - runs once after all tests
|
48
|
+
# Teardown section (optional) - code after last testcase runs once after all tests
|
49
49
|
|
50
|
-
Context
|
50
|
+
Execution Context:
|
51
51
|
Shared Context (default): Instance variables persist across test cases
|
52
52
|
- Use for: Integration testing, stateful scenarios, realistic workflows
|
53
53
|
- Caution: Test order matters, state accumulates
|
@@ -40,6 +40,21 @@ class Tryouts
|
|
40
40
|
# Try to evaluate in test context first, then fallback to global context for constants
|
41
41
|
begin
|
42
42
|
expected_class = eval_expectation_content(@expectation.content, expectation_result)
|
43
|
+
|
44
|
+
# If the evaluated result is not a class/module (e.g., shadowed constant),
|
45
|
+
# fall back to global context resolution
|
46
|
+
unless expected_class.is_a?(Class) || expected_class.is_a?(Module)
|
47
|
+
# Attempt to get the constant from global context
|
48
|
+
# This handles cases where a local variable/method shadows a class constant
|
49
|
+
constant_name = @expectation.content.strip
|
50
|
+
# Ensure the constant name is valid before attempting const_get
|
51
|
+
if constant_name =~ /\A[A-Z][\w:]*\z/
|
52
|
+
expected_class = Object.const_get(constant_name)
|
53
|
+
else
|
54
|
+
# If it's not a valid constant name, the evaluation failed
|
55
|
+
raise TypeError, "Expected a Class or Module, got #{expected_class.class}"
|
56
|
+
end
|
57
|
+
end
|
43
58
|
rescue NameError => e
|
44
59
|
# If we can't find the constant in test context, try global context
|
45
60
|
# This is common for exception classes like ArgumentError, StandardError, etc.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# lib/tryouts/file_processor.rb
|
2
2
|
|
3
|
-
require_relative 'parsers/
|
3
|
+
require_relative 'parsers/legacy_parser'
|
4
4
|
require_relative 'parsers/enhanced_parser'
|
5
5
|
require_relative 'test_executor'
|
6
6
|
require_relative 'cli/modes/inspect'
|
@@ -84,7 +84,7 @@ class Tryouts
|
|
84
84
|
when :enhanced
|
85
85
|
EnhancedParser.new(file, options)
|
86
86
|
when :prism
|
87
|
-
|
87
|
+
LegacyParser.new(file, options)
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
@@ -22,5 +22,15 @@ class Tryouts
|
|
22
22
|
suggestion: "Use explicit '## Description' to clarify test structure"
|
23
23
|
)
|
24
24
|
end
|
25
|
+
|
26
|
+
def self.malformed_expectation(line_number:, syntax:, context:)
|
27
|
+
new(
|
28
|
+
type: :malformed_expectation,
|
29
|
+
message: "Malformed expectation syntax '#=#{syntax}>' at line #{line_number}",
|
30
|
+
line_number: line_number,
|
31
|
+
context: context,
|
32
|
+
suggestion: "Use valid expectation syntax like #=>, #==>, #=:>, #=!>, etc."
|
33
|
+
)
|
34
|
+
end
|
25
35
|
end
|
26
36
|
end
|