tryouts 3.0.0 → 3.1.0
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 +51 -115
- data/exe/try +25 -4
- data/lib/tryouts/cli/formatters/base.rb +33 -21
- data/lib/tryouts/cli/formatters/compact.rb +122 -84
- data/lib/tryouts/cli/formatters/factory.rb +1 -1
- data/lib/tryouts/cli/formatters/output_manager.rb +13 -2
- data/lib/tryouts/cli/formatters/quiet.rb +22 -16
- data/lib/tryouts/cli/formatters/verbose.rb +101 -60
- data/lib/tryouts/console.rb +53 -17
- data/lib/tryouts/expectation_evaluators/base.rb +101 -0
- data/lib/tryouts/expectation_evaluators/boolean.rb +60 -0
- data/lib/tryouts/expectation_evaluators/exception.rb +61 -0
- data/lib/tryouts/expectation_evaluators/expectation_result.rb +67 -0
- data/lib/tryouts/expectation_evaluators/false.rb +60 -0
- data/lib/tryouts/expectation_evaluators/intentional_failure.rb +74 -0
- data/lib/tryouts/expectation_evaluators/output.rb +101 -0
- data/lib/tryouts/expectation_evaluators/performance_time.rb +81 -0
- data/lib/tryouts/expectation_evaluators/regex_match.rb +57 -0
- data/lib/tryouts/expectation_evaluators/registry.rb +66 -0
- data/lib/tryouts/expectation_evaluators/regular.rb +67 -0
- data/lib/tryouts/expectation_evaluators/result_type.rb +51 -0
- data/lib/tryouts/expectation_evaluators/true.rb +58 -0
- data/lib/tryouts/prism_parser.rb +112 -15
- data/lib/tryouts/test_executor.rb +6 -4
- data/lib/tryouts/test_runner.rb +1 -1
- data/lib/tryouts/testbatch.rb +288 -98
- data/lib/tryouts/testcase.rb +141 -0
- data/lib/tryouts/translators/minitest_translator.rb +40 -11
- data/lib/tryouts/translators/rspec_translator.rb +47 -12
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +42 -0
- metadata +16 -3
@@ -0,0 +1,67 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/expectation_result.rb
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
module ExpectationEvaluators
|
5
|
+
# Extensible container for evaluation context data
|
6
|
+
#
|
7
|
+
# Provides immutable data structure for passing test results and timing data to evaluators.
|
8
|
+
# Uses Data.define for lightweight implementation. Stores timing in nanoseconds internally,
|
9
|
+
# converts to milliseconds for display. Enables adding future metrics (memory, CPU) without
|
10
|
+
# breaking evaluator method signatures.
|
11
|
+
#
|
12
|
+
# Usage:
|
13
|
+
# ExpectationResult.from_result(actual_result) # Regular expectations
|
14
|
+
# ExpectationResult.from_timing(actual_result, execution_time_ns) # Performance expectations
|
15
|
+
# ExpectationResult.from_execution_with_output(actual_result, stdout, stderr) # Output expectations
|
16
|
+
#
|
17
|
+
# Variables available in eval_expectation_content:
|
18
|
+
# result, _ : actual_result (regular) or execution_time_ms (performance)
|
19
|
+
ExpectationResult = Data.define(:actual_result, :execution_time_ns, :start_time_ns, :end_time_ns, :stdout_content, :stderr_content) do
|
20
|
+
# Convert nanoseconds to milliseconds for human-readable timing
|
21
|
+
# Used for display and as the value of `result`/`_` variables in performance expectations
|
22
|
+
def execution_time_ms
|
23
|
+
execution_time_ns ? (execution_time_ns / 1_000_000.0).round(2) : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Helper to create a basic packet with just actual result
|
27
|
+
# Used by: regular, true, false, boolean, result_type, regex_match, exception evaluators
|
28
|
+
def self.from_result(actual_result)
|
29
|
+
new(
|
30
|
+
actual_result: actual_result,
|
31
|
+
execution_time_ns: nil,
|
32
|
+
start_time_ns: nil,
|
33
|
+
end_time_ns: nil,
|
34
|
+
stdout_content: nil,
|
35
|
+
stderr_content: nil
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Helper to create a timing packet with execution data
|
40
|
+
# Used by: performance_time evaluator
|
41
|
+
# Future: Could accept additional timing or resource metrics
|
42
|
+
def self.from_timing(actual_result, execution_time_ns, start_time_ns = nil, end_time_ns = nil)
|
43
|
+
new(
|
44
|
+
actual_result: actual_result,
|
45
|
+
execution_time_ns: execution_time_ns,
|
46
|
+
start_time_ns: start_time_ns,
|
47
|
+
end_time_ns: end_time_ns,
|
48
|
+
stdout_content: nil,
|
49
|
+
stderr_content: nil
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Helper to create a packet with captured output data
|
54
|
+
# Used by: output evaluator for stdout/stderr expectations
|
55
|
+
def self.from_execution_with_output(actual_result, stdout_content, stderr_content, execution_time_ns = nil)
|
56
|
+
new(
|
57
|
+
actual_result: actual_result,
|
58
|
+
execution_time_ns: execution_time_ns,
|
59
|
+
start_time_ns: nil,
|
60
|
+
end_time_ns: nil,
|
61
|
+
stdout_content: stdout_content,
|
62
|
+
stderr_content: stderr_content
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/false.rb
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
class Tryouts
|
6
|
+
module ExpectationEvaluators
|
7
|
+
# Evaluator for boolean false expectations using syntax: #=/=> expression
|
8
|
+
#
|
9
|
+
# PURPOSE:
|
10
|
+
# - Validates that an expression evaluates to exactly false (not falsy)
|
11
|
+
# - Provides explicit boolean validation for documentation-style tests
|
12
|
+
# - Distinguishes between true/false and truthy/falsy values
|
13
|
+
#
|
14
|
+
# SYNTAX: #=/=> boolean_expression
|
15
|
+
# Examples:
|
16
|
+
# [1, 2, 3] #=/=> result.empty? # Pass: expression is false
|
17
|
+
# [1, 2, 3] #=/=> result.include?(5) # Pass: expression is false
|
18
|
+
# [] #=/=> result.include?(1) # Pass: expression is false
|
19
|
+
# [] #=/=> result.empty? # Fail: expression is true
|
20
|
+
# [1, 2, 3] #=/=> result.first # Fail: expression is 1 (truthy but not false)
|
21
|
+
# [] #=/=> result.first # Fail: expression is nil (falsy but not false)
|
22
|
+
#
|
23
|
+
# BOOLEAN STRICTNESS:
|
24
|
+
# - Only passes when expression evaluates to exactly false (not falsy)
|
25
|
+
# - Fails for true, nil, 0, "", [], {}, or any non-false value
|
26
|
+
# - Uses Ruby's === comparison for exact boolean matching
|
27
|
+
# - Encourages explicit boolean expressions in documentation
|
28
|
+
#
|
29
|
+
# IMPLEMENTATION DETAILS:
|
30
|
+
# - Expression has access to `result` and `_` variables (actual_result)
|
31
|
+
# - Expected display shows 'false (exactly)' for clarity
|
32
|
+
# - Actual display shows the evaluated expression result
|
33
|
+
# - Distinguishes from regular expectations through strict false matching
|
34
|
+
#
|
35
|
+
# DESIGN DECISIONS:
|
36
|
+
# - Strict false matching prevents accidental falsy value acceptance
|
37
|
+
# - Clear expected display explains the exact requirement
|
38
|
+
# - Expression evaluation provides flexible boolean logic testing
|
39
|
+
# - Part of unified #= prefix convention for all expectation types
|
40
|
+
# - Uses #=/=> syntax to visually distinguish from true expectations
|
41
|
+
class False < Base
|
42
|
+
def self.handles?(expectation_type)
|
43
|
+
expectation_type == :false # rubocop:disable Lint/BooleanSymbol
|
44
|
+
end
|
45
|
+
|
46
|
+
def evaluate(actual_result = nil)
|
47
|
+
expectation_result = ExpectationResult.from_result(actual_result)
|
48
|
+
expression_result = eval_expectation_content(@expectation.content, expectation_result)
|
49
|
+
|
50
|
+
build_result(
|
51
|
+
passed: expression_result == false,
|
52
|
+
actual: expression_result,
|
53
|
+
expected: false,
|
54
|
+
)
|
55
|
+
rescue StandardError => ex
|
56
|
+
handle_evaluation_error(ex, actual_result)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/intentional_failure.rb
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative 'regular'
|
5
|
+
require_relative '../testcase'
|
6
|
+
|
7
|
+
class Tryouts
|
8
|
+
module ExpectationEvaluators
|
9
|
+
# Evaluator for intentional failure expectations using syntax: #=<> expression
|
10
|
+
#
|
11
|
+
# PURPOSE:
|
12
|
+
# - Validates that an expectation intentionally fails (passes when the underlying expectation fails)
|
13
|
+
# - Provides ability to test negative cases and expected failures
|
14
|
+
# - Useful for testing error conditions and boundary cases
|
15
|
+
#
|
16
|
+
# SYNTAX: #=<> expression
|
17
|
+
# Examples:
|
18
|
+
# 1 + 1 #=<> 3 # Pass: 1+1 ≠ 3, so failure expected
|
19
|
+
# "hello" #=<> result.include?("x") # Pass: "hello" doesn't contain "x"
|
20
|
+
# [1, 2, 3] #=<> result.empty? # Pass: array is not empty
|
21
|
+
# "test" #=<> /\d+/ # Pass: "test" doesn't match digits
|
22
|
+
#
|
23
|
+
# FAILURE INVERSION:
|
24
|
+
# - Takes any valid expectation expression and inverts the result
|
25
|
+
# - If underlying expectation would pass, intentional failure fails
|
26
|
+
# - If underlying expectation would fail, intentional failure passes
|
27
|
+
# - Preserves all error details and content from underlying expectation
|
28
|
+
#
|
29
|
+
# IMPLEMENTATION DETAILS:
|
30
|
+
# - Uses delegation pattern to wrap the regular evaluator
|
31
|
+
# - Expression has access to `result` and `_` variables (actual_result)
|
32
|
+
# - Expected display shows the evaluated expression result for clarity
|
33
|
+
# - Actual display shows the test result value
|
34
|
+
# - Inverts only the final passed/failed status, preserving other metadata
|
35
|
+
#
|
36
|
+
# DESIGN DECISIONS:
|
37
|
+
# - Uses #=<> syntax (angle brackets suggest "opposite/inverted direction")
|
38
|
+
# - Delegates to regular evaluator to maintain consistency
|
39
|
+
# - Preserves original expectation details for debugging
|
40
|
+
# - Clear messaging indicates this is an intentional failure test
|
41
|
+
# - Part of unified #= prefix convention for all expectation types
|
42
|
+
#
|
43
|
+
# DELEGATION PATTERN:
|
44
|
+
# - Creates a temporary regular expectation with same content
|
45
|
+
# - Delegates evaluation to Regular evaluator
|
46
|
+
# - Inverts the passed result while preserving actual/expected values
|
47
|
+
# - Provides clear messaging about intentional failure semantics
|
48
|
+
class IntentionalFailure < Base
|
49
|
+
def self.handles?(expectation_type)
|
50
|
+
expectation_type == :intentional_failure
|
51
|
+
end
|
52
|
+
|
53
|
+
def evaluate(actual_result = nil)
|
54
|
+
# Create a temporary regular expectation for delegation
|
55
|
+
regular_expectation = Expectation.new(content: @expectation.content, type: :regular)
|
56
|
+
|
57
|
+
# Delegate to regular evaluator
|
58
|
+
regular_evaluator = Regular.new(regular_expectation, @test_case, @context)
|
59
|
+
regular_result = regular_evaluator.evaluate(actual_result)
|
60
|
+
|
61
|
+
# Invert the result while preserving metadata
|
62
|
+
build_result(
|
63
|
+
passed: !regular_result[:passed],
|
64
|
+
actual: regular_result[:actual],
|
65
|
+
expected: "NOT #{regular_result[:expected]} (intentional failure)",
|
66
|
+
expectation: @expectation.content
|
67
|
+
)
|
68
|
+
rescue StandardError => ex
|
69
|
+
# If evaluation itself fails (not the expectation), that's a real error
|
70
|
+
handle_evaluation_error(ex, actual_result)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/output.rb
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
class Tryouts
|
6
|
+
module ExpectationEvaluators
|
7
|
+
# Evaluator for output expectations using syntax: #=1> content, #=2> content
|
8
|
+
#
|
9
|
+
# PURPOSE:
|
10
|
+
# - Validates that stdout (pipe 1) or stderr (pipe 2) contains expected content
|
11
|
+
# - Provides ability to test console output, error messages, and logging
|
12
|
+
# - Supports both string contains and regex pattern matching
|
13
|
+
#
|
14
|
+
# SYNTAX:
|
15
|
+
# - #=1> expression - Test stdout content
|
16
|
+
# - #=2> expression - Test stderr content
|
17
|
+
#
|
18
|
+
# Examples:
|
19
|
+
# puts "Hello World" #=1> "Hello" # String contains check
|
20
|
+
# puts "Hello World" #=1> /Hello.*World/ # Regex pattern match
|
21
|
+
# $stderr.puts "Error!" #=2> "Error" # Stderr contains check
|
22
|
+
# $stderr.puts "Warning: 404" #=2> /Warning.*\d+/ # Stderr regex match
|
23
|
+
#
|
24
|
+
# MATCHING BEHAVIOR:
|
25
|
+
# - String expectations: Uses String#include? for substring matching
|
26
|
+
# - Regex expectations: Uses =~ operator for pattern matching
|
27
|
+
# - Auto-detects expectation type based on content (literal string vs regex)
|
28
|
+
# - Case-sensitive matching for both strings and regex patterns
|
29
|
+
#
|
30
|
+
# IMPLEMENTATION DETAILS:
|
31
|
+
# - Requires output capture during test execution
|
32
|
+
# - Expression has access to `result` and `_` variables (actual_result)
|
33
|
+
# - Expected display shows the evaluated expression result
|
34
|
+
# - Actual display shows captured output content for the specific pipe
|
35
|
+
# - Supports evaluation of dynamic expressions in expectation content
|
36
|
+
#
|
37
|
+
# DESIGN DECISIONS:
|
38
|
+
# - Uses POSIX pipe convention: 1=stdout, 2=stderr
|
39
|
+
# - Always captures output for debugging regardless of expectations
|
40
|
+
# - Supports both literal strings and regex patterns seamlessly
|
41
|
+
# - Part of unified #= prefix convention for all expectation types
|
42
|
+
# - Uses #=N> syntax where N is the pipe number (following shell convention)
|
43
|
+
#
|
44
|
+
# PIPE MAPPING:
|
45
|
+
# - Pipe 1: Standard output (stdout) - $stdout, puts, print, p
|
46
|
+
# - Pipe 2: Standard error (stderr) - $stderr, warn, STDERR.puts
|
47
|
+
# - Future pipes (3+) could support custom streams if needed
|
48
|
+
class Output < Base
|
49
|
+
def self.handles?(expectation_type)
|
50
|
+
expectation_type == :output
|
51
|
+
end
|
52
|
+
|
53
|
+
def evaluate(actual_result = nil, stdout_content = nil, stderr_content = nil)
|
54
|
+
# Determine which pipe we're testing based on expectation metadata
|
55
|
+
pipe_number = @expectation.respond_to?(:pipe) ? @expectation.pipe : 1
|
56
|
+
|
57
|
+
# Get the appropriate captured content
|
58
|
+
captured_content = case pipe_number
|
59
|
+
when 1 then stdout_content || ""
|
60
|
+
when 2 then stderr_content || ""
|
61
|
+
else ""
|
62
|
+
end
|
63
|
+
|
64
|
+
# Create result packet for expression evaluation
|
65
|
+
expectation_result = ExpectationResult.from_execution_with_output(actual_result, stdout_content, stderr_content)
|
66
|
+
|
67
|
+
# Evaluate the expectation expression (could be string literal or regex)
|
68
|
+
expected_pattern = eval_expectation_content(@expectation.content, expectation_result)
|
69
|
+
|
70
|
+
# Determine matching strategy based on expectation type
|
71
|
+
matched = case expected_pattern
|
72
|
+
when Regexp
|
73
|
+
# Regex pattern matching
|
74
|
+
!!(captured_content =~ expected_pattern)
|
75
|
+
when String
|
76
|
+
# String contains matching
|
77
|
+
captured_content.include?(expected_pattern)
|
78
|
+
else
|
79
|
+
# Convert to string and do contains check
|
80
|
+
captured_content.include?(expected_pattern.to_s)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Build result with appropriate pipe description
|
84
|
+
pipe_name = case pipe_number
|
85
|
+
when 1 then "stdout"
|
86
|
+
when 2 then "stderr"
|
87
|
+
else "pipe#{pipe_number}"
|
88
|
+
end
|
89
|
+
|
90
|
+
build_result(
|
91
|
+
passed: matched,
|
92
|
+
actual: "#{pipe_name}: #{captured_content.inspect}",
|
93
|
+
expected: expected_pattern.inspect,
|
94
|
+
expectation: @expectation.content
|
95
|
+
)
|
96
|
+
rescue StandardError => ex
|
97
|
+
handle_evaluation_error(ex, actual_result)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/performance_time.rb
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
class Tryouts
|
6
|
+
module ExpectationEvaluators
|
7
|
+
# Evaluator for performance time expectations using syntax: #=%> milliseconds
|
8
|
+
#
|
9
|
+
# PURPOSE:
|
10
|
+
# - Validates that test execution time meets performance thresholds
|
11
|
+
# - Supports both static thresholds and dynamic expressions using timing data
|
12
|
+
# - Provides performance regression testing for documentation-style tests
|
13
|
+
#
|
14
|
+
# SYNTAX: #=%> threshold_expression
|
15
|
+
# Examples:
|
16
|
+
# 1 + 1 #=%> 100 # Pass: simple addition under 100ms
|
17
|
+
# sleep(0.01) #=%> 15 # Pass: 10ms sleep under 15ms (+10% tolerance)
|
18
|
+
# Array.new(1000) #=%> 1 # Pass: array creation under 1ms
|
19
|
+
# sleep(0.005) #=%> result * 2 # Pass: 5ms execution, 10ms threshold
|
20
|
+
# sleep(0.001) #=%> 0.1 # Fail: 1ms execution exceeds 0.1ms + 10%
|
21
|
+
#
|
22
|
+
# TIMING DATA AVAILABILITY:
|
23
|
+
# In performance expectations, the timing data is available as:
|
24
|
+
# - `result`: execution time in milliseconds (e.g., 5.23)
|
25
|
+
# - `_`: alias for the same timing data
|
26
|
+
# This allows expressions like: #=%> result * 2, #=%> _ + 10
|
27
|
+
#
|
28
|
+
# TOLERANCE LOGIC:
|
29
|
+
# - Performance expectations use "less than or equal to + 10%" logic
|
30
|
+
# - Formula: actual_time_ms <= expected_limit_ms * 1.1
|
31
|
+
# - This differs from strict window matching - only cares about upper bound
|
32
|
+
# - Designed for performance regression testing, not precision timing
|
33
|
+
#
|
34
|
+
# IMPLEMENTATION DETAILS:
|
35
|
+
# - Timing captured in nanoseconds for precision, displayed in milliseconds
|
36
|
+
# - Uses Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) for accuracy
|
37
|
+
# - Expected display shows evaluated threshold (e.g., "100", "10.5")
|
38
|
+
# - Actual display shows formatted timing (e.g., "5.23ms")
|
39
|
+
# - Timing data passed via ExpectationResult for extensibility
|
40
|
+
#
|
41
|
+
# DESIGN DECISIONS:
|
42
|
+
# - Chosen "less than or equal to + 10%" over strict window for usability
|
43
|
+
# - Nanosecond capture → millisecond display for precision + readability
|
44
|
+
# - Expression evaluation with timing context for flexible thresholds
|
45
|
+
# - Separate evaluate method signature to receive timing data from testbatch
|
46
|
+
class PerformanceTime < Base
|
47
|
+
def self.handles?(expectation_type)
|
48
|
+
expectation_type == :performance_time
|
49
|
+
end
|
50
|
+
|
51
|
+
def evaluate(actual_result = nil, execution_time_ns = nil)
|
52
|
+
if execution_time_ns.nil?
|
53
|
+
return build_result(
|
54
|
+
passed: false,
|
55
|
+
actual: 'No timing data available',
|
56
|
+
expected: 'Performance measurement',
|
57
|
+
error: 'Performance expectations require execution timing data'
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create result packet with timing data available to expectation
|
62
|
+
expectation_result = ExpectationResult.from_timing(actual_result, execution_time_ns)
|
63
|
+
expected_limit_ms = eval_expectation_content(@expectation.content, expectation_result)
|
64
|
+
|
65
|
+
actual_time_ms = expectation_result.execution_time_ms
|
66
|
+
|
67
|
+
# Performance tolerance: actual <= expected + 10% (not strict window)
|
68
|
+
max_allowed_ms = expected_limit_ms * 1.1
|
69
|
+
within_tolerance = actual_time_ms <= max_allowed_ms
|
70
|
+
|
71
|
+
build_result(
|
72
|
+
passed: within_tolerance,
|
73
|
+
actual: "#{actual_time_ms}ms",
|
74
|
+
expected: expected_limit_ms,
|
75
|
+
)
|
76
|
+
rescue StandardError => ex
|
77
|
+
handle_evaluation_error(ex, actual_result)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/regex_match.rb
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
class Tryouts
|
6
|
+
module ExpectationEvaluators
|
7
|
+
# Evaluator for regex match expectations using syntax: #=~> /pattern/
|
8
|
+
#
|
9
|
+
# PURPOSE:
|
10
|
+
# - Validates that the test result matches a regular expression pattern
|
11
|
+
# - Supports all Ruby regex features (anchors, flags, groups, etc.)
|
12
|
+
# - Provides pattern matching for string validation in documentation-style tests
|
13
|
+
#
|
14
|
+
# SYNTAX: #=~> /pattern/flags
|
15
|
+
# Examples:
|
16
|
+
# "hello world" #=~> /hello/ # Pass: basic pattern match
|
17
|
+
# "user@example.com" #=~> /^[^@]+@[^@]+$/ # Pass: email validation pattern
|
18
|
+
# "Phone: 555-1234" #=~> /\d{3}-\d{4}/ # Pass: phone number pattern
|
19
|
+
# "HELLO WORLD" #=~> /hello/i # Pass: case insensitive match
|
20
|
+
# "hello world" #=~> /goodbye/ # Fail: pattern not found
|
21
|
+
#
|
22
|
+
# IMPLEMENTATION DETAILS:
|
23
|
+
# - Converts actual_result to string using to_s for regex matching
|
24
|
+
# - Uses Ruby's =~ operator for pattern matching (returns match position or nil)
|
25
|
+
# - Evaluates expectation content to resolve regex patterns with flags
|
26
|
+
# - Expected display shows pattern.inspect (e.g., "/hello/i", "/\d+/")
|
27
|
+
# - Actual display shows stringified result for pattern comparison
|
28
|
+
#
|
29
|
+
# DESIGN DECISIONS:
|
30
|
+
# - Always converts to string (allows regex matching on any object)
|
31
|
+
# - Uses =~ operator (Ruby standard) rather than match? for compatibility
|
32
|
+
# - Pattern evaluation supports dynamic regex construction
|
33
|
+
# - Displays pattern.inspect for clear regex representation
|
34
|
+
class RegexMatch < Base
|
35
|
+
def self.handles?(expectation_type)
|
36
|
+
expectation_type == :regex_match
|
37
|
+
end
|
38
|
+
|
39
|
+
def evaluate(actual_result = nil)
|
40
|
+
expectation_result = ExpectationResult.from_result(actual_result)
|
41
|
+
pattern = eval_expectation_content(@expectation.content, expectation_result)
|
42
|
+
|
43
|
+
# Convert actual_result to string for regex matching
|
44
|
+
string_result = actual_result.to_s
|
45
|
+
match_result = string_result =~ pattern
|
46
|
+
|
47
|
+
build_result(
|
48
|
+
passed: !match_result.nil?,
|
49
|
+
actual: string_result,
|
50
|
+
expected: pattern.inspect,
|
51
|
+
)
|
52
|
+
rescue StandardError => ex
|
53
|
+
handle_evaluation_error(ex, actual_result)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/registry.rb
|
2
|
+
|
3
|
+
require_relative 'expectation_result'
|
4
|
+
require_relative 'base'
|
5
|
+
require_relative 'regular'
|
6
|
+
require_relative 'exception'
|
7
|
+
require_relative 'boolean'
|
8
|
+
require_relative 'true'
|
9
|
+
require_relative 'false'
|
10
|
+
require_relative 'result_type'
|
11
|
+
require_relative 'regex_match'
|
12
|
+
require_relative 'performance_time'
|
13
|
+
require_relative 'intentional_failure'
|
14
|
+
require_relative 'output'
|
15
|
+
|
16
|
+
class Tryouts
|
17
|
+
module ExpectationEvaluators
|
18
|
+
class Registry
|
19
|
+
@evaluators = []
|
20
|
+
|
21
|
+
class << self
|
22
|
+
attr_reader :evaluators
|
23
|
+
|
24
|
+
def evaluator_for(expectation, test_case, context)
|
25
|
+
evaluator_class = find_evaluator_class(expectation.type)
|
26
|
+
|
27
|
+
unless evaluator_class
|
28
|
+
raise ArgumentError, "No evaluator found for expectation type: #{expectation.type}"
|
29
|
+
end
|
30
|
+
|
31
|
+
evaluator_class.new(expectation, test_case, context)
|
32
|
+
end
|
33
|
+
|
34
|
+
def register(evaluator_class)
|
35
|
+
unless evaluator_class < Base
|
36
|
+
raise ArgumentError, 'Evaluator must inherit from ExpectationEvaluators::Base'
|
37
|
+
end
|
38
|
+
|
39
|
+
@evaluators << evaluator_class unless @evaluators.include?(evaluator_class)
|
40
|
+
end
|
41
|
+
|
42
|
+
def registered_evaluators
|
43
|
+
@evaluators.dup
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def find_evaluator_class(expectation_type)
|
49
|
+
@evaluators.find { |evaluator_class| evaluator_class.handles?(expectation_type) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Auto-register built-in evaluators
|
54
|
+
register(Regular)
|
55
|
+
register(Exception)
|
56
|
+
register(Boolean)
|
57
|
+
register(True)
|
58
|
+
register(False)
|
59
|
+
register(ResultType)
|
60
|
+
register(RegexMatch)
|
61
|
+
register(PerformanceTime)
|
62
|
+
register(IntentionalFailure)
|
63
|
+
register(Output)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/regular.rb
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
class Tryouts
|
6
|
+
module ExpectationEvaluators
|
7
|
+
# Evaluator for standard equality expectations using syntax: #=> expression
|
8
|
+
#
|
9
|
+
# PURPOSE:
|
10
|
+
# - Validates that test result equals the evaluated expectation expression
|
11
|
+
# - Provides the fundamental equality-based testing mechanism
|
12
|
+
# - Serves as the default and most commonly used expectation type
|
13
|
+
#
|
14
|
+
# SYNTAX: #=> expression
|
15
|
+
# Examples:
|
16
|
+
# 1 + 1 #=> 2 # Pass: 2 == 2
|
17
|
+
# [1, 2, 3] #=> result.length # Pass: [1,2,3] == 3 (fails, different types)
|
18
|
+
# [1, 2, 3] #=> [1, 2, 3] # Pass: array equality
|
19
|
+
# "hello" #=> "hello" # Pass: string equality
|
20
|
+
# [1, 2, 3] #=> result.sort # Pass: [1,2,3] == [1,2,3]
|
21
|
+
# { a: 1 } #=> { a: 1 } # Pass: hash equality
|
22
|
+
# 1 + 1 #=> result # Pass: 2 == 2 (result variable access)
|
23
|
+
#
|
24
|
+
# EQUALITY SEMANTICS:
|
25
|
+
# - Uses Ruby's == operator for equality comparison
|
26
|
+
# - Supports all Ruby types: primitives, collections, objects
|
27
|
+
# - Type-sensitive: 2 != "2", [1] != 1, nil != false
|
28
|
+
# - Reference-independent: compares values not object identity
|
29
|
+
#
|
30
|
+
# IMPLEMENTATION DETAILS:
|
31
|
+
# - Expression has access to `result` and `_` variables (actual_result)
|
32
|
+
# - Expected display shows the evaluated expression result (not raw content)
|
33
|
+
# - Actual display shows the test result value
|
34
|
+
# - Most flexible evaluator supporting arbitrary Ruby expressions
|
35
|
+
#
|
36
|
+
# DESIGN DECISIONS:
|
37
|
+
# - Standard equality provides intuitive testing behavior
|
38
|
+
# - Expression evaluation enables dynamic expectations (result.length, etc.)
|
39
|
+
# - Uses #=> syntax as the canonical expectation notation
|
40
|
+
# - Evaluated expected display prevents confusion with literal text content
|
41
|
+
# - Part of unified #= prefix convention for all expectation types
|
42
|
+
# - Serves as fallback when no specialized evaluator applies
|
43
|
+
#
|
44
|
+
# VARIABLE ACCESS:
|
45
|
+
# - `result`: contains the actual test result value
|
46
|
+
# - `_`: shorthand alias for the same actual result value
|
47
|
+
# - Enables expectations like: #=> result.upcase, #=> _.first, #=> result * 2
|
48
|
+
class Regular < Base
|
49
|
+
def self.handles?(expectation_type)
|
50
|
+
expectation_type == :regular
|
51
|
+
end
|
52
|
+
|
53
|
+
def evaluate(actual_result = nil)
|
54
|
+
expectation_result = ExpectationResult.from_result(actual_result)
|
55
|
+
expected_value = eval_expectation_content(@expectation.content, expectation_result)
|
56
|
+
|
57
|
+
build_result(
|
58
|
+
passed: actual_result == expected_value,
|
59
|
+
actual: actual_result,
|
60
|
+
expected: expected_value,
|
61
|
+
)
|
62
|
+
rescue StandardError => ex
|
63
|
+
handle_evaluation_error(ex, actual_result)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/result_type.rb
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
class Tryouts
|
6
|
+
module ExpectationEvaluators
|
7
|
+
# Evaluator for result type expectations using syntax: #=:> ClassName
|
8
|
+
#
|
9
|
+
# PURPOSE:
|
10
|
+
# - Validates that the test result is an instance of the expected class or its ancestors
|
11
|
+
# - Supports both exact class matches and inheritance (String is_a? Object)
|
12
|
+
# - Provides clean type validation for documentation-style tests
|
13
|
+
#
|
14
|
+
# SYNTAX: #=:> ClassName
|
15
|
+
# Examples:
|
16
|
+
# "hello" #=:> String # Pass: String.is_a?(String)
|
17
|
+
# [1, 2, 3] #=:> Array # Pass: Array.is_a?(Array)
|
18
|
+
# 42 #=:> Integer # Pass: Integer.is_a?(Integer)
|
19
|
+
# "hello" #=:> Object # Pass: String.is_a?(Object) - inheritance
|
20
|
+
# "hello" #=:> Integer # Fail: String is not Integer
|
21
|
+
#
|
22
|
+
# IMPLEMENTATION DETAILS:
|
23
|
+
# - Uses Ruby's is_a? method for type checking (supports inheritance)
|
24
|
+
# - Evaluates expectation content to resolve class constants (String, Array, etc.)
|
25
|
+
# - Expected display shows the evaluated class name (e.g., "String", "Integer")
|
26
|
+
# - Actual display shows the actual result's class for easy comparison
|
27
|
+
#
|
28
|
+
# DESIGN DECISIONS:
|
29
|
+
# - Chose is_a? over class == for inheritance support
|
30
|
+
# - Class resolution through evaluation allows dynamic class references
|
31
|
+
# - Clean expected/actual display focuses on type comparison
|
32
|
+
class ResultType < Base
|
33
|
+
def self.handles?(expectation_type)
|
34
|
+
expectation_type == :result_type
|
35
|
+
end
|
36
|
+
|
37
|
+
def evaluate(actual_result = nil)
|
38
|
+
expectation_result = ExpectationResult.from_result(actual_result)
|
39
|
+
expected_class = eval_expectation_content(@expectation.content, expectation_result)
|
40
|
+
|
41
|
+
build_result(
|
42
|
+
passed: actual_result.is_a?(expected_class),
|
43
|
+
actual: actual_result.class,
|
44
|
+
expected: expected_class,
|
45
|
+
)
|
46
|
+
rescue StandardError => ex
|
47
|
+
handle_evaluation_error(ex, actual_result)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|