tryouts 3.0.0.pre2 → 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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -115
  3. data/exe/try +25 -4
  4. data/lib/tryouts/cli/formatters/base.rb +33 -21
  5. data/lib/tryouts/cli/formatters/compact.rb +122 -84
  6. data/lib/tryouts/cli/formatters/factory.rb +1 -1
  7. data/lib/tryouts/cli/formatters/output_manager.rb +13 -2
  8. data/lib/tryouts/cli/formatters/quiet.rb +22 -16
  9. data/lib/tryouts/cli/formatters/verbose.rb +101 -60
  10. data/lib/tryouts/console.rb +53 -17
  11. data/lib/tryouts/expectation_evaluators/base.rb +101 -0
  12. data/lib/tryouts/expectation_evaluators/boolean.rb +60 -0
  13. data/lib/tryouts/expectation_evaluators/exception.rb +61 -0
  14. data/lib/tryouts/expectation_evaluators/expectation_result.rb +67 -0
  15. data/lib/tryouts/expectation_evaluators/false.rb +60 -0
  16. data/lib/tryouts/expectation_evaluators/intentional_failure.rb +74 -0
  17. data/lib/tryouts/expectation_evaluators/output.rb +101 -0
  18. data/lib/tryouts/expectation_evaluators/performance_time.rb +81 -0
  19. data/lib/tryouts/expectation_evaluators/regex_match.rb +57 -0
  20. data/lib/tryouts/expectation_evaluators/registry.rb +66 -0
  21. data/lib/tryouts/expectation_evaluators/regular.rb +67 -0
  22. data/lib/tryouts/expectation_evaluators/result_type.rb +51 -0
  23. data/lib/tryouts/expectation_evaluators/true.rb +58 -0
  24. data/lib/tryouts/prism_parser.rb +112 -15
  25. data/lib/tryouts/test_executor.rb +6 -4
  26. data/lib/tryouts/test_runner.rb +1 -1
  27. data/lib/tryouts/testbatch.rb +288 -98
  28. data/lib/tryouts/testcase.rb +141 -0
  29. data/lib/tryouts/translators/minitest_translator.rb +40 -11
  30. data/lib/tryouts/translators/rspec_translator.rb +47 -12
  31. data/lib/tryouts/version.rb +1 -1
  32. data/lib/tryouts.rb +42 -0
  33. 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