tryouts 3.5.0 → 3.5.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.
- checksums.yaml +4 -4
- data/exe/try +6 -0
- data/lib/tryouts/cli/formatters/agent.rb +180 -26
- data/lib/tryouts/expectation_evaluators/result_type.rb +15 -0
- data/lib/tryouts/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab0eb9c341b10dd5e505a3f62cc33a19a393cb294aecd756809b19c2484784f1
|
4
|
+
data.tar.gz: cddfd1a827b0ef37a9e69224124f2f696bbd8c2f098edbfce79b563b3017fbcd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 066b7697b04b3501f01a0994e9d10a66c5a94d593bdfa5cb1b618bb1264f228425bd7d8b4d98cf9a555a5e23bbe002f137c943d5195ad4ce972de30f03dfac9c
|
7
|
+
data.tar.gz: 82390327e8da8e35d0c9eaa4557800b09bba76e065704f9d704acab45ab24dd62e97edc7aa54d8104ee50cab31a9dab321c75449386f70243674c8f2d838716a
|
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
|
+
# TOPA (Test Output Protocol for AI) Formatter
|
8
|
+
#
|
9
|
+
# Language-agnostic test output format designed for LLM context management.
|
10
|
+
# This formatter implements the TOPA v1.0 specification for structured,
|
11
|
+
# token-efficient test result communication.
|
12
|
+
#
|
13
|
+
# TOPA 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: TOPA 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
|
|
@@ -506,9 +535,41 @@ class Tryouts
|
|
506
535
|
|
507
536
|
def render_execution_context
|
508
537
|
context_lines = []
|
509
|
-
context_lines << "
|
538
|
+
context_lines << "EXECUTION_CONTEXT:"
|
510
539
|
|
511
|
-
#
|
540
|
+
# Command that was executed
|
541
|
+
if @options[:original_command]
|
542
|
+
command_str = @options[:original_command].join(' ')
|
543
|
+
context_lines << " command: #{command_str}"
|
544
|
+
end
|
545
|
+
|
546
|
+
# Compact system info on one line when possible
|
547
|
+
context_lines << " pid: #{Process.pid} | pwd: #{Dir.pwd}"
|
548
|
+
|
549
|
+
# Runtime - compact format
|
550
|
+
platform = RUBY_PLATFORM.gsub(/darwin\d+/, 'darwin') # Simplify darwin25 -> darwin
|
551
|
+
context_lines << " runtime: ruby #{RUBY_VERSION} (#{platform})"
|
552
|
+
|
553
|
+
# Package manager - only if present, compact format
|
554
|
+
if defined?(Bundler)
|
555
|
+
context_lines << " package_manager: bundler #{Bundler::VERSION}"
|
556
|
+
end
|
557
|
+
|
558
|
+
# Version control - compact single line with timeout protection
|
559
|
+
git_info = safe_git_info
|
560
|
+
if git_info[:branch] && git_info[:commit] && !git_info[:branch].empty? && !git_info[:commit].empty?
|
561
|
+
context_lines << " vcs: git #{git_info[:branch]}@#{git_info[:commit]}"
|
562
|
+
end
|
563
|
+
|
564
|
+
# Environment - only non-defaults
|
565
|
+
env_vars = build_environment_context
|
566
|
+
if env_vars.any?
|
567
|
+
# Compact key=value format
|
568
|
+
env_str = env_vars.map { |k, v| "#{k}=#{v}" }.join(', ')
|
569
|
+
context_lines << " environment: #{env_str}"
|
570
|
+
end
|
571
|
+
|
572
|
+
# Test framework - compact critical info only
|
512
573
|
framework = @options[:framework] || :direct
|
513
574
|
shared_context = if @options.key?(:shared_context)
|
514
575
|
@options[:shared_context]
|
@@ -522,26 +583,24 @@ class Tryouts
|
|
522
583
|
end
|
523
584
|
end
|
524
585
|
|
525
|
-
|
526
|
-
context_lines << "
|
527
|
-
|
528
|
-
# Parser type
|
529
|
-
parser = @options[:parser] || :enhanced
|
530
|
-
context_lines << " Parser: #{parser}"
|
586
|
+
isolation = shared_context ? 'shared' : 'isolated'
|
587
|
+
context_lines << " test_framework: #{framework} (#{isolation})"
|
531
588
|
|
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]
|
589
|
+
# Execution flags - only if non-standard
|
590
|
+
flags = build_execution_flags
|
591
|
+
if flags.any?
|
592
|
+
context_lines << " flags: #{flags.join(', ')}"
|
593
|
+
end
|
540
594
|
|
541
|
-
|
595
|
+
# TOPA protocol - compact
|
596
|
+
context_lines << " protocol: TOPA v1.0 | focus: #{@focus_mode} | limit: #{@budget.limit}"
|
542
597
|
|
543
|
-
#
|
544
|
-
|
598
|
+
# File count being tested
|
599
|
+
if @collected_files && @collected_files.any?
|
600
|
+
context_lines << " files_under_test: #{@collected_files.size}"
|
601
|
+
elsif @total_stats[:files] && @total_stats[:files] > 0
|
602
|
+
context_lines << " files_under_test: #{@total_stats[:files]}"
|
603
|
+
end
|
545
604
|
|
546
605
|
# Add syntax errors if any (these prevent test execution)
|
547
606
|
if @syntax_errors.any?
|
@@ -571,6 +630,101 @@ class Tryouts
|
|
571
630
|
|
572
631
|
context_lines.join("\n")
|
573
632
|
end
|
633
|
+
|
634
|
+
# Build environment context with language-agnostic keys
|
635
|
+
def build_environment_context
|
636
|
+
env_vars = {}
|
637
|
+
|
638
|
+
# CI/CD detection - prioritize most specific
|
639
|
+
if ENV['GITHUB_ACTIONS']
|
640
|
+
env_vars['CI'] = 'github'
|
641
|
+
elsif ENV['GITLAB_CI']
|
642
|
+
env_vars['CI'] = 'gitlab'
|
643
|
+
elsif ENV['JENKINS_URL']
|
644
|
+
env_vars['CI'] = 'jenkins'
|
645
|
+
elsif ENV['CI']
|
646
|
+
env_vars['CI'] = 'true'
|
647
|
+
end
|
648
|
+
|
649
|
+
# Runtime environment - only if not default
|
650
|
+
if ENV['RAILS_ENV'] && ENV['RAILS_ENV'] != 'development'
|
651
|
+
env_vars['ENV'] = ENV['RAILS_ENV']
|
652
|
+
elsif ENV['RACK_ENV'] && ENV['RACK_ENV'] != 'development'
|
653
|
+
env_vars['ENV'] = ENV['RACK_ENV']
|
654
|
+
elsif ENV['NODE_ENV'] && ENV['NODE_ENV'] != 'development'
|
655
|
+
env_vars['ENV'] = ENV['NODE_ENV']
|
656
|
+
end
|
657
|
+
|
658
|
+
# Coverage - simplified
|
659
|
+
env_vars['COV'] = '1' if ENV['COVERAGE'] || ENV['SIMPLECOV']
|
660
|
+
|
661
|
+
# Test seed for reproducibility
|
662
|
+
env_vars['SEED'] = ENV['SEED'] if ENV['SEED']
|
663
|
+
|
664
|
+
env_vars
|
665
|
+
end
|
666
|
+
|
667
|
+
# Build execution flags in language-agnostic format
|
668
|
+
def build_execution_flags
|
669
|
+
flags = []
|
670
|
+
flags << "verbose" if @options[:verbose]
|
671
|
+
flags << "fails-only" if @options[:fails_only]
|
672
|
+
flags << "debug" if @options[:debug]
|
673
|
+
flags << "traces" if @options[:stack_traces] && !@options[:debug] # debug implies traces
|
674
|
+
flags << "parallel" if @options[:parallel]
|
675
|
+
flags << "line-spec" if @options[:line_spec]
|
676
|
+
flags << "strict" if @options[:strict]
|
677
|
+
flags << "quiet" if @options[:quiet]
|
678
|
+
flags
|
679
|
+
end
|
680
|
+
|
681
|
+
# Get test discovery patterns in language-agnostic format
|
682
|
+
def get_test_discovery_patterns
|
683
|
+
patterns = []
|
684
|
+
|
685
|
+
# Ruby/Tryouts patterns
|
686
|
+
patterns.concat([
|
687
|
+
"**/*_try.rb",
|
688
|
+
"**/*.try.rb",
|
689
|
+
"try/**/*.rb",
|
690
|
+
"tryouts/**/*.rb"
|
691
|
+
])
|
692
|
+
|
693
|
+
# TOPA-compatible patterns for other languages:
|
694
|
+
# Python: ["**/*_test.py", "**/test_*.py", "tests/**/*.py"]
|
695
|
+
# JavaScript: ["**/*.test.js", "**/*.spec.js", "__tests__/**/*.js"]
|
696
|
+
# Java: ["**/*Test.java", "**/Test*.java", "src/test/**/*.java"]
|
697
|
+
# Go: ["**/*_test.go"]
|
698
|
+
# C#: ["**/*Test.cs", "**/*Tests.cs"]
|
699
|
+
# PHP: ["**/*Test.php", "tests/**/*.php"]
|
700
|
+
# Rust: ["**/*_test.rs", "tests/**/*.rs"]
|
701
|
+
|
702
|
+
patterns
|
703
|
+
end
|
704
|
+
|
705
|
+
private
|
706
|
+
|
707
|
+
# Safely get git information with timeout protection
|
708
|
+
def safe_git_info
|
709
|
+
# Check if we're in a git repository
|
710
|
+
return {} unless File.directory?('.git') || system('git rev-parse --git-dir >/dev/null 2>&1')
|
711
|
+
|
712
|
+
require 'timeout'
|
713
|
+
|
714
|
+
Timeout.timeout(2) do
|
715
|
+
branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
|
716
|
+
commit = `git rev-parse --short HEAD 2>/dev/null`.strip
|
717
|
+
|
718
|
+
# Validate output to prevent injection
|
719
|
+
branch = nil unless branch =~ /\A[\w\-\/\.]+\z/
|
720
|
+
commit = nil unless commit =~ /\A[a-f0-9]+\z/i
|
721
|
+
|
722
|
+
{ branch: branch, commit: commit }
|
723
|
+
end
|
724
|
+
rescue Timeout::Error, StandardError
|
725
|
+
# Return empty hash on any error (timeout, permission, etc.)
|
726
|
+
{}
|
727
|
+
end
|
574
728
|
end
|
575
729
|
end
|
576
730
|
end
|
@@ -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.
|
data/lib/tryouts/version.rb
CHANGED