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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 975acb3a790344e059de10c8eafae0a58de04e407f89a42af031d7a76c082ca2
4
- data.tar.gz: a9d77fe3c5f15d2080999b93b7b42c3d16a465fbe31397338f806cd6d67bb253
3
+ metadata.gz: 07be17b9ebef2d51f24a192dab9c292cf9613cf6ae32b42a98f47d34dea70045
4
+ data.tar.gz: 618e98529240a7a6df56667a1fa64a9f758d76c40ace4bd815275061894c176f
5
5
  SHA512:
6
- metadata.gz: 4c70927fb871e78c28d44a1b996844e76b84eb944d57c9943f5603b5227f6f18d38bcdc836fa504b4e4b1220683c5d1b9c02a7af5636f884e393697927d8fc68
7
- data.tar.gz: 7ccb05deaf71afdb6ff239e312c6bba8c7ef371a8fe746b15a47cf84cc81652502a62bff704ea8d33e345fa9350bcaec90d3055860c71973f9e2d8ffe618412d
6
+ metadata.gz: 8d8e493922bfd6c734466ec1f69becf5668f6b571b37e8367395e0f26aec8d19d5831e04169952b9acf2b1f04b3370fe33a163d50d776d42dcdfecc3aea1a0c6
7
+ data.tar.gz: 4aaac8820547f393a95dfb82f621aedef8142264298ef437beffe853b83a2fb61ee2687d5bf1cf0c74f852bc7192b49b963d133fcda5354a0250dfe309ec435c
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2011-2024 Delano Mandelbaum
3
+ Copyright (c) 2011-2025 Delano Mandelbaum
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,109 +1,223 @@
1
- # Tryouts v2.4 (2024-07-20)
1
+ # Tryouts v3.0.0-pre
2
2
 
3
3
  **Ruby tests that read like documentation.**
4
4
 
5
- A simple test framework for Ruby code that uses introspection to allow defining checks in comments.
5
+ A modern test framework for Ruby that uses comments to define expectations. Tryouts are meant to double as documentation, so the Ruby code should be plain and reminiscent of real code.
6
+
7
+ > [!WARNING]
8
+ > Version 3.0 uses Ruby's Prism parser and pattern matching, requiring Ruby 3.4+
9
+
10
+ ## Key Features
11
+
12
+ - **Documentation-style tests** using comment-based expectations (`#=>`)
13
+ - **Framework integration** with RSpec and Minitest translators
14
+ - **Modern Ruby architecture** using Prism parser and pattern matching
15
+ - **Immutable data structures** with `Data.define` classes
16
+ - **Enhanced error reporting** with line numbers and context
6
17
 
7
18
  ## Installation
8
19
 
9
- One of:
10
- * In your Gemfile: `gem 'tryouts'`
11
- * As a gem: `gem install tryouts`
12
- * From source:
20
+ Add to your Gemfile:
21
+ ```ruby
22
+ gem 'tryouts', '~> 3.0.0-pre'
23
+ ```
24
+
25
+ Or install directly:
26
+ ```bash
27
+ gem install tryouts --pre
28
+ ```
13
29
 
30
+ From source:
14
31
  ```bash
15
- $ git clone git://github.com/tryouts/tryouts.git
32
+ git clone https://github.com/delano/tryouts.git
33
+ cd tryouts
34
+ bundle install
16
35
  ```
17
36
 
37
+ ## Requirements
38
+
39
+ - **Ruby >= 3.4.4** (for Prism parser and pattern matching)
40
+ - **RSpec** or **Minitest** (optional, for framework integration)
41
+
18
42
  ## Usage
19
43
 
44
+ ### Basic Commands
45
+
20
46
  ```bash
21
- # Run all tests accessible from the current directory (e.g. ./try, ./tryouts))
22
- $ try
23
-
24
- # Run a single test file
25
- $ try try/10_utils_try.rb
26
-
27
- # Command arguments
28
- $ try -h
29
- Usage: try [options]
30
- -V, --version Display the version
31
- -q, --quiet Run in quiet mode
32
- -v, --verbose Run in verbose mode
33
- -f, --fails Show only failing tryouts
34
- -D, --debug Run in debug mode
35
- -h, --help Display this help
47
+ # Auto-discover and run all tests
48
+ try
49
+
50
+ # Run specific test file
51
+ try try/step1_try.rb
52
+
53
+ # Framework integration
54
+ try --rspec try/step1_try.rb # Run with RSpec
55
+ try --minitest try/step1_try.rb # Run with Minitest
56
+
57
+ # Code generation only
58
+ try --generate-rspec try/step1_try.rb
59
+ try --generate-minitest try/step1_try.rb
60
+
61
+ # Output options
62
+ try -v # verbose (includes source code and return values)
63
+ try -q # quiet mode
64
+ try -f # show failures only
65
+ try -D # debug mode
36
66
  ```
37
67
 
38
- ### Exit codes
68
+ ### Exit Codes
39
69
 
40
- When all tests pass, try exits with a 0. An exit code of 1 or more indicates the number of failing tests.
70
+ - `0`: All tests pass
71
+ - `1+`: Number of failing tests
41
72
 
73
+ ## Writing Tests
42
74
 
43
- ## Writing tests
75
+ Tryouts use a unique comment-based expectation syntax:
44
76
 
45
77
  ```ruby
46
- ## A very simple test
47
- 1 + 1
48
- #=> 2
49
-
50
- ## The test description can spread
51
- ## across multiple lines. The same
52
- ## is true for test definitions.
53
- a = 'foo'
54
- b = 'bar'
55
- a + b
56
- #=> 'foobar'
57
-
58
- ## A test will pass when its return
59
- ## value equals the expectation.
60
- 'foo'.class
61
- #=> String
62
-
63
- ## The expectations are evaluated as well.
64
- 81
65
- #=> 9 * 9
66
-
67
- ## Here's an example of testing errors
68
- begin
69
- raise RuntimeError
70
- rescue RuntimeError
71
- :success
72
- end
73
- #=> :success
78
+ # Setup code runs before all tests
79
+ puts 'Setup running'
80
+
81
+ ## Simple test with expectation
82
+ a = 1 + 1
83
+ #=> 2
84
+
85
+ ## Multi-line test with description
86
+ ## TEST: Addition works correctly
87
+ a = 1
88
+ b = 2
89
+ a + b
90
+ #=> 3
91
+
92
+ ## Testing object methods
93
+ 'hello'.upcase
94
+ #=> 'HELLO'
95
+
96
+ ## Expressions are evaluated
97
+ 81
98
+ #=> 9 * 9
99
+
100
+ ## Testing errors with rescue
101
+ begin
102
+ raise RuntimeError, "test error"
103
+ rescue RuntimeError
104
+ :caught
105
+ end
106
+ #=> :caught
107
+
108
+ # Teardown runs after all tests
109
+ puts 'Cleanup complete'
74
110
  ```
75
111
 
76
- For real world examples, see [Onetimesecret](https://github.com/onetimesecret/onetimesecret/) tryouts.
112
+ ### Test Structure
77
113
 
114
+ - **Setup Section**: Code before first test case (accessible via instance variables)
115
+ - **Test Cases**: Description lines (`##`), Ruby code, and expectations (`#=>`)
116
+ - **Teardown Section**: Code after last test case
78
117
 
79
- ### Test setup / cleanup
118
+ ## Framework Integration
80
119
 
81
- Put the setup code at the top of the file, and cleanup code at the bottom. Like this:
120
+ Version 3.0 introduces framework translators that convert tryouts into established test frameworks:
82
121
 
83
- ```ruby
84
- # This is called before all tests
85
- require 'gibbler'
86
- Gibbler.digest_type = Digest::SHA256
122
+ ### RSpec Integration
123
+
124
+ ```bash
125
+ try --rspec file_try.rb
126
+ ```
127
+
128
+ Generates RSpec `describe/it` blocks with full RSpec ecosystem support including:
129
+ - Mocking and stubbing
130
+ - Custom matchers
131
+ - Parallel execution
132
+ - IDE integration
133
+
134
+ ### Minitest Integration
87
135
 
136
+ ```bash
137
+ try --minitest file_try.rb
138
+ ```
88
139
 
89
- ## This is a single testcase
90
- :anything.gibbler
91
- #=> '8574309'
140
+ Creates Minitest classes with `test_*` methods for:
141
+ - Standard assertions
142
+ - Custom test cases
143
+ - CI/CD integration
144
+ - Debugging support
92
145
 
146
+ ### Direct Mode
93
147
 
94
- # This will be called after all tests
95
- Gibbler.digest_type = Digest::SHA1
148
+ ```bash
149
+ try file_try.rb # Original tryouts execution
96
150
  ```
97
151
 
98
- __
152
+ Uses shared execution context with all tests running in the same environment.
153
+
154
+ ## File Discovery
155
+
156
+ Auto-discovers test files matching these patterns:
157
+ - `./try/*_try.rb`
158
+ - `./tryouts/*_try.rb`
159
+ - `./*_try.rb`
160
+
161
+ Files are processed in lexical order.
162
+
163
+ ## Modern Architecture (v3.0)
164
+
165
+ ### Core Components
166
+
167
+ - **Prism Parser**: Native Ruby parsing with pattern matching for line classification
168
+ - **Data Structures**: Immutable `Data.define` classes for test representation
169
+ - **Framework Translators**: Convert tryouts to RSpec/Minitest format
170
+ - **CLI**: Modern command-line interface with framework selection
171
+
172
+ ### Ruby 3.4+ Features
173
+
174
+ - **Pattern matching** throughout parsing and classification logic
175
+ - **Prism native parser** (no external grammar compilation)
176
+ - **Data.define classes** for immutable data structures
177
+ - **Enhanced error context** with line numbers and suggestions
178
+
179
+ ## Development
180
+
181
+ ### Running Tests
182
+
183
+ ```bash
184
+ # Test framework using itself
185
+ try try/*_try.rb
186
+
187
+ # With coverage
188
+ COVERAGE=1 try try/*_try.rb
189
+ ```
190
+
191
+ ### Code Quality
192
+
193
+ ```bash
194
+ bundle exec rubocop # Check style
195
+ bundle exec rubocop -A # Auto-fix issues
196
+ ```
197
+
198
+ ## Migration from v2.x
199
+
200
+ Version 3.0 represents a complete modernization:
201
+
202
+ - **Parser**: Tree-sitter → Prism (native Ruby)
203
+ - **Execution**: Custom runner → Framework delegation
204
+ - **Data**: Traditional classes → `Data.define` immutable structures
205
+ - **Syntax**: Standard Ruby → Pattern matching throughout
206
+ - **Ruby**: 2.7+ → 3.4+ requirement
207
+
208
+ ## Examples
209
+
210
+ For real-world usage examples, see:
211
+ - [Onetimesecret tryouts](https://github.com/onetimesecret/onetimesecret/)
212
+ - Test files in this repository: `try/*_try.rb` and `doc/`
99
213
 
214
+ ## AI Development Assistance
100
215
 
101
- ## Thanks
216
+ This version of Tryouts was developed with assistance from AI tools. The following tools provided significant help with architecture design, code generation, and documentation:
102
217
 
103
- * [cloudhead](https://github.com/cloudhead)
104
- * [mynyml](https://github.com/mynyml)
105
- * [Syntenic](https://syntenic.com/) for the hackfest venue.
106
- * [AlexPeuchert](https://www.rubypulse.com/) for the screencast.
107
- * Christian Michon for suggesting a better default output format.
218
+ - **Claude Sonnet 4** - Architecture design, code generation, and documentation
219
+ - **Claude Desktop & Claude Code** - Interactive development sessions and debugging
220
+ - **GitHub Copilot** - Code completion and refactoring assistance
221
+ - **Qodo Merge Pro** - Code review and quality improvements
108
222
 
109
- *This collision was brought to you by Montreal.rb.*
223
+ I remain responsible for all design decisions and the final code. I believe in being transparent about development tools, especially as AI becomes more integrated into our workflows as developers.
data/exe/try CHANGED
@@ -1,51 +1,27 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env /Users/d/.rbenv/shims/ruby
2
2
 
3
- require 'optparse'
4
3
  require_relative '../lib/tryouts'
5
4
 
6
- # Adding local lib directories allows the test files
7
- # to require the ruby code they are testing without
8
- # needing even if the files use regular requires. It
9
- # skips the test files from having to muck with the
10
- # load path or use `require_relative` which is easy
11
- # to forget. This is a convenience feature.
12
- #
13
- # Here we're looking for directories relative to the
14
- # current working directory (i.e. where this script
15
- # is being run from.
5
+ # Add development library paths
16
6
  lib_glob = File.join(Dir.pwd, '{lib,../lib,.}')
17
- Tryouts.update_load_path(lib_glob)
7
+ Tryouts.update_load_path(lib_glob) if Tryouts.respond_to?(:update_load_path)
18
8
 
19
- # Parse command-line arguments
20
- OptionParser.new do |opts|
21
- opts.on('-V', '--version', 'Display the version') do
22
- puts "Tryouts: #{Tryouts::VERSION}"
23
- exit
24
- end
25
- opts.on('-q', '--quiet', 'Run in quiet mode') { Tryouts.quiet = true }
26
- opts.on('-v', '--verbose', 'Run in verbose mode') { Tryouts.noisy = true }
27
- opts.on('-f', '--fails', 'Show only failing tryouts') { Tryouts.fails = true }
28
- opts.on('-D', '--debug', 'Run in debug mode') { Tryouts.debug = true }
29
- opts.on('-h', '--help', 'Display this help') do
30
- puts opts
31
- exit
32
- end
33
- end.parse!
9
+ # Parse args and run CLI
10
+ begin
11
+ files, options = Tryouts::CLI.parse_args(ARGV)
34
12
 
35
- # Find tryouts path with a path glob unless thin
36
- # script was called with arguments in which case
37
- # we consume those as a list of paths.
38
- paths = if ARGV.empty?
39
- Dir.glob(
40
- ['./{try,tryouts/,.}/*_{try,tryouts}.rb'],
41
- base: Dir.pwd
42
- ).sort # deterministic order
43
-
44
- else
45
- ARGV
46
- end
13
+ # Default file discovery if no files specified
14
+ if files.empty?
15
+ files = Dir.glob(
16
+ ['./{try,tryouts,.}/*_try.rb'],
17
+ base: Dir.pwd,
18
+ ).sort
19
+ end
47
20
 
48
- # Running the tryouts returns the number of
49
- # test failures so here we pass that value
50
- # through as the exit code for the script.
51
- exit Tryouts.run_all(*paths)
21
+ cli = Tryouts::CLI.new
22
+ exit cli.run(files, **options)
23
+ rescue StandardError => ex
24
+ warn "Error: #{ex.message}"
25
+ warn ex.backtrace.join("\n") if options[:debug]
26
+ exit 1
27
+ end
data/exe/tryouts CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- # frozen_string_literal: true
2
+ # exe/tryouts
3
3
 
4
- load File.expand_path("../try", __FILE__)
4
+ load File.expand_path('try', __dir__)
@@ -0,0 +1,108 @@
1
+ # lib/tryouts/cli/formatters/base.rb
2
+
3
+ require_relative 'factory'
4
+ require_relative 'output_manager'
5
+
6
+ class Tryouts
7
+ class CLI
8
+ # Enhanced interface for all test output formatting
9
+ module FormatterInterface
10
+ # Phase-level output (major sections)
11
+ def phase_header(message, file_count = nil, level = 0)
12
+ raise NotImplementedError, "#{self.class} must implement #phase_header"
13
+ end
14
+
15
+ # File-level operations
16
+ def file_start(file_path, context_info = {})
17
+ raise NotImplementedError, "#{self.class} must implement #file_start"
18
+ end
19
+
20
+ def file_parsed(file_path, test_count, setup_present: false, teardown_present: false)
21
+ raise NotImplementedError, "#{self.class} must implement #file_parsed"
22
+ end
23
+
24
+ def file_execution_start(file_path, test_count, context_mode)
25
+ raise NotImplementedError, "#{self.class} must implement #file_execution_start"
26
+ end
27
+
28
+ def file_result(file_path, total_tests, failed_count, error_count, elapsed_time)
29
+ raise NotImplementedError, "#{self.class} must implement #file_result"
30
+ end
31
+
32
+ # Test-level operations
33
+ def test_start(test_case, index, total)
34
+ raise NotImplementedError, "#{self.class} must implement #test_start"
35
+ end
36
+
37
+ def test_result(test_case, result_status, actual_results = [], elapsed_time = nil)
38
+ raise NotImplementedError, "#{self.class} must implement #test_result"
39
+ end
40
+
41
+ def test_output(test_case, output_text)
42
+ raise NotImplementedError, "#{self.class} must implement #test_output"
43
+ end
44
+
45
+ # Setup/teardown operations
46
+ def setup_start(line_range)
47
+ raise NotImplementedError, "#{self.class} must implement #setup_start"
48
+ end
49
+
50
+ def setup_output(output_text)
51
+ raise NotImplementedError, "#{self.class} must implement #setup_output"
52
+ end
53
+
54
+ def teardown_start(line_range)
55
+ raise NotImplementedError, "#{self.class} must implement #teardown_start"
56
+ end
57
+
58
+ def teardown_output(output_text)
59
+ raise NotImplementedError, "#{self.class} must implement #teardown_output"
60
+ end
61
+
62
+ # Summary operations
63
+ def batch_summary(total_tests, failed_count, elapsed_time)
64
+ raise NotImplementedError, "#{self.class} must implement #batch_summary"
65
+ end
66
+
67
+ def grand_total(total_tests, failed_count, error_count, successful_files, total_files, elapsed_time)
68
+ raise NotImplementedError, "#{self.class} must implement #grand_total"
69
+ end
70
+
71
+ # Debug and diagnostic output
72
+ def debug_info(message, level = 0)
73
+ raise NotImplementedError, "#{self.class} must implement #debug_info"
74
+ end
75
+
76
+ def trace_info(message, level = 0)
77
+ raise NotImplementedError, "#{self.class} must implement #trace_info"
78
+ end
79
+
80
+ def error_message(message, details = nil)
81
+ raise NotImplementedError, "#{self.class} must implement #error_message"
82
+ end
83
+
84
+ # Utility methods
85
+ def raw_output(text)
86
+ raise NotImplementedError, "#{self.class} must implement #raw_output"
87
+ end
88
+
89
+ def separator(style = :light)
90
+ raise NotImplementedError, "#{self.class} must implement #separator"
91
+ end
92
+
93
+ def indent_text(text, level)
94
+ indent = ' ' * level
95
+ "#{indent}#{text}"
96
+ end
97
+
98
+ def with_indent(level)
99
+ old_indent = @current_indent
100
+ @current_indent = level
101
+ yield
102
+ ensure
103
+ @current_indent = old_indent
104
+ end
105
+ end
106
+
107
+ end
108
+ end