Added float test cases generator
This commit is contained in:
parent
d480321014
commit
cf5f51ccd8
|
|
@ -4,6 +4,7 @@ Generates comprehensive test cases for all integer types and bases.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .integer_tests import generate_integer_literal_tests
|
from .integer_tests import generate_integer_literal_tests
|
||||||
|
from .float_tests import generate_float_literal_tests
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# import json
|
# import json
|
||||||
|
|
@ -12,6 +13,7 @@ if __name__ == "__main__":
|
||||||
# Generate tests
|
# Generate tests
|
||||||
tests = []
|
tests = []
|
||||||
tests += generate_integer_literal_tests()
|
tests += generate_integer_literal_tests()
|
||||||
|
tests += generate_float_literal_tests()
|
||||||
|
|
||||||
# Print summary
|
# Print summary
|
||||||
print(f"Generated {len(tests)} test cases")
|
print(f"Generated {len(tests)} test cases")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
from .utils import Token, Operation, StackItem, RuntimeError, TestCase, to_dict
|
||||||
|
|
||||||
|
class FloatTestGenerator:
|
||||||
|
"""Generate test cases for floating point literals."""
|
||||||
|
|
||||||
|
# Special float values
|
||||||
|
SPECIAL_VALUES = {
|
||||||
|
'f32': {
|
||||||
|
'min': -3.4028235e38,
|
||||||
|
'max': 3.4028235e38,
|
||||||
|
'min_positive': 1.1754944e-38,
|
||||||
|
'epsilon': 1.1920929e-7,
|
||||||
|
},
|
||||||
|
'f64': {
|
||||||
|
'min': -1.7976931348623157e308,
|
||||||
|
'max': 1.7976931348623157e308,
|
||||||
|
'min_positive': 2.2250738585072014e-308,
|
||||||
|
'epsilon': 2.220446049250313e-16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tests = []
|
||||||
|
|
||||||
|
def add_test(self, name: str, code: str, tokens: List[Token],
|
||||||
|
operations: Optional[List[Operation]] = None,
|
||||||
|
stack_final: Optional[List[StackItem]] = None,
|
||||||
|
runtime_error: Optional[RuntimeError] = None):
|
||||||
|
"""Add a test case."""
|
||||||
|
test = TestCase(
|
||||||
|
name=name,
|
||||||
|
code=code,
|
||||||
|
tokens=[to_dict(t) for t in tokens],
|
||||||
|
operations=[to_dict(o) for o in operations] if operations else None,
|
||||||
|
stack_final=[to_dict(s) for s in stack_final] if stack_final else None,
|
||||||
|
runtime_error=to_dict(runtime_error) if runtime_error else None
|
||||||
|
)
|
||||||
|
self.tests.append(to_dict(test))
|
||||||
|
|
||||||
|
def make_push_op(self, type_name: str, value: Any) -> Operation:
|
||||||
|
"""Create a push operation."""
|
||||||
|
return Operation(function="push", type=type_name, value=value)
|
||||||
|
|
||||||
|
def make_stack_item(self, type_name: str, value: Any) -> StackItem:
|
||||||
|
"""Create a stack item."""
|
||||||
|
return StackItem(type=type_name, value=value)
|
||||||
|
|
||||||
|
def make_error_token(self, message: str) -> Token:
|
||||||
|
"""Create an error token."""
|
||||||
|
return Token(type="error", value=message)
|
||||||
|
|
||||||
|
def make_success_test(self, name: str, code: str, type_name: str, value: float):
|
||||||
|
"""Create a successful test case."""
|
||||||
|
token = Token(type=type_name, value=value)
|
||||||
|
op = self.make_push_op(type_name, value)
|
||||||
|
stack = self.make_stack_item(type_name, value)
|
||||||
|
self.add_test(name, code, [token], [op], [stack])
|
||||||
|
|
||||||
|
def make_error_test(self, name: str, code: str, error_msg: str):
|
||||||
|
"""Create an error test case."""
|
||||||
|
token = self.make_error_token(error_msg)
|
||||||
|
self.add_test(name, code, [token])
|
||||||
|
|
||||||
|
def generate_basic_tests(self):
|
||||||
|
"""Generate basic test cases."""
|
||||||
|
# Simple default floats (f64)
|
||||||
|
self.make_success_test("Float Default Simple", "3.14", "f64", 3.14)
|
||||||
|
self.make_success_test("Float Default Zero", "0.0", "f64", 0.0)
|
||||||
|
self.make_success_test("Float Default Negative", "-2.5", "f64", -2.5)
|
||||||
|
self.make_success_test("Float Default One", "1.0", "f64", 1.0)
|
||||||
|
|
||||||
|
# Simple with type annotation
|
||||||
|
self.make_success_test("Float f32 Simple", "3.14:f32", "f32", 3.14)
|
||||||
|
self.make_success_test("Float f64 Simple", "2.718:f64", "f64", 2.718)
|
||||||
|
|
||||||
|
def generate_format_tests(self):
|
||||||
|
"""Generate tests for different float formats."""
|
||||||
|
# Leading zeros
|
||||||
|
self.make_success_test("Float Default Leading Zeros", "00042.5", "f64", 42.5)
|
||||||
|
self.make_success_test("Float Default Leading Zero Decimal", "0.5", "f64", 0.5)
|
||||||
|
|
||||||
|
# Trailing zeros
|
||||||
|
self.make_success_test("Float Default Trailing Zeros", "3.1400", "f64", 3.14)
|
||||||
|
|
||||||
|
# No leading digit
|
||||||
|
self.make_success_test("Float Default No Leading Digit", ".5", "f64", 0.5)
|
||||||
|
self.make_success_test("Float Default No Leading Digit Negative", "-.25", "f64", -0.25)
|
||||||
|
|
||||||
|
# No trailing digits
|
||||||
|
self.make_success_test("Float Default No Trailing Digits", "42.", "f64", 42.0)
|
||||||
|
self.make_success_test("Float Default No Trailing Digits Negative", "-7.", "f64", -7.0)
|
||||||
|
|
||||||
|
# Scientific notation
|
||||||
|
self.make_success_test("Float Default Scientific Positive Exp", "1.5e10", "f64", 1.5e10)
|
||||||
|
self.make_success_test("Float Default Scientific Negative Exp", "2.5e-5", "f64", 2.5e-5)
|
||||||
|
self.make_success_test("Float Default Scientific Capital E", "3.14E8", "f64", 3.14e8)
|
||||||
|
self.make_success_test("Float Default Scientific Plus Sign", "1.0e+3", "f64", 1000.0)
|
||||||
|
|
||||||
|
# Very small numbers
|
||||||
|
self.make_success_test("Float Default Very Small", "0.000001", "f64", 0.000001)
|
||||||
|
self.make_success_test("Float Default Scientific Very Small", "1.0e-20", "f64", 1.0e-20)
|
||||||
|
|
||||||
|
# Very large numbers
|
||||||
|
self.make_success_test("Float Default Very Large", "1000000.0", "f64", 1000000.0)
|
||||||
|
self.make_success_test("Float Default Scientific Very Large", "1.0e20", "f64", 1.0e20)
|
||||||
|
|
||||||
|
def generate_underscore_tests(self):
|
||||||
|
"""Generate tests for underscores in floats."""
|
||||||
|
# Underscores in integer part
|
||||||
|
self.make_success_test("Float Default Underscore Integer Part",
|
||||||
|
"1_000_000.5", "f64", 1000000.5)
|
||||||
|
|
||||||
|
# Underscores in decimal part
|
||||||
|
self.make_success_test("Float Default Underscore Decimal Part",
|
||||||
|
"3.141_592_653", "f64", 3.141592653)
|
||||||
|
|
||||||
|
# Underscores in both parts
|
||||||
|
self.make_success_test("Float Default Underscore Both Parts",
|
||||||
|
"1_234.567_89", "f64", 1234.56789)
|
||||||
|
|
||||||
|
# Underscores in scientific notation
|
||||||
|
self.make_success_test("Float Default Underscore Scientific Mantissa",
|
||||||
|
"1_000.5e10", "f64", 1000.5e10)
|
||||||
|
self.make_success_test("Float Default Underscore Scientific Exponent",
|
||||||
|
"1.5e1_0", "f64", 1.5e10)
|
||||||
|
|
||||||
|
# Trailing underscore
|
||||||
|
self.make_success_test("Float Default Underscore Trailing", "42.5_", "f64", 42.5)
|
||||||
|
|
||||||
|
# Double underscore
|
||||||
|
self.make_success_test("Float Default Underscore Double", "4__2.5", "f64", 42.5)
|
||||||
|
|
||||||
|
# With type annotation
|
||||||
|
self.make_success_test("Float f32 With Underscores",
|
||||||
|
"1_234.567_89:f32", "f32", 1234.56789)
|
||||||
|
|
||||||
|
def generate_special_value_tests(self):
|
||||||
|
"""Generate tests for special float values."""
|
||||||
|
# Infinity
|
||||||
|
self.make_success_test("Float Default Positive Infinity", "inf", "f64", float('inf'))
|
||||||
|
self.make_success_test("Float Default Negative Infinity", "-inf", "f64", float('-inf'))
|
||||||
|
self.make_success_test("Float f32 Positive Infinity", "inf:f32", "f32", float('inf'))
|
||||||
|
self.make_success_test("Float f32 Negative Infinity", "-inf:f32", "f32", float('-inf'))
|
||||||
|
|
||||||
|
# NaN
|
||||||
|
self.make_success_test("Float Default NaN", "nan", "f64", float('nan'))
|
||||||
|
self.make_success_test("Float f32 NaN", "nan:f32", "f32", float('nan'))
|
||||||
|
|
||||||
|
# Note: NaN comparison is special - NaN != NaN, so these tests may need
|
||||||
|
# special handling in the test runner
|
||||||
|
|
||||||
|
def generate_edge_case_tests(self, type_name: str):
|
||||||
|
"""Generate edge case tests for a specific float type."""
|
||||||
|
values = self.SPECIAL_VALUES[type_name]
|
||||||
|
|
||||||
|
# Maximum value
|
||||||
|
self.make_success_test(f"Float {type_name} Max Value",
|
||||||
|
f"{values['max']}:{type_name}", type_name, values['max'])
|
||||||
|
|
||||||
|
# Minimum value (most negative)
|
||||||
|
self.make_success_test(f"Float {type_name} Min Value",
|
||||||
|
f"{values['min']}:{type_name}", type_name, values['min'])
|
||||||
|
|
||||||
|
# Smallest positive normalized value
|
||||||
|
self.make_success_test(f"Float {type_name} Min Positive",
|
||||||
|
f"{values['min_positive']}:{type_name}",
|
||||||
|
type_name, values['min_positive'])
|
||||||
|
|
||||||
|
# Machine epsilon
|
||||||
|
self.make_success_test(f"Float {type_name} Epsilon",
|
||||||
|
f"{values['epsilon']}:{type_name}",
|
||||||
|
type_name, values['epsilon'])
|
||||||
|
|
||||||
|
# Near zero
|
||||||
|
self.make_success_test(f"Float {type_name} Near Zero Positive",
|
||||||
|
f"1e-30:{type_name}", type_name, 1e-30)
|
||||||
|
self.make_success_test(f"Float {type_name} Near Zero Negative",
|
||||||
|
f"-1e-30:{type_name}", type_name, -1e-30)
|
||||||
|
|
||||||
|
# Subnormal numbers
|
||||||
|
if type_name == 'f64':
|
||||||
|
self.make_success_test("Float f64 Subnormal",
|
||||||
|
"1e-320:f64", "f64", 1e-320)
|
||||||
|
elif type_name == 'f32':
|
||||||
|
self.make_success_test("Float f32 Subnormal",
|
||||||
|
"1e-40:f32", "f32", 1e-40)
|
||||||
|
|
||||||
|
def generate_overflow_tests(self):
|
||||||
|
"""Generate overflow tests."""
|
||||||
|
# f32 overflow
|
||||||
|
self.make_error_test("Float f32 Overflow Positive",
|
||||||
|
"1e40:f32",
|
||||||
|
"Float overflow: value exceeds range for f32.")
|
||||||
|
self.make_error_test("Float f32 Overflow Negative",
|
||||||
|
"-1e40:f32",
|
||||||
|
"Float overflow: value exceeds range for f32.")
|
||||||
|
|
||||||
|
# f64 overflow (extremely large values)
|
||||||
|
self.make_error_test("Float f64 Overflow Positive",
|
||||||
|
"1e310:f64",
|
||||||
|
"Float overflow: value exceeds range for f64.")
|
||||||
|
self.make_error_test("Float f64 Overflow Negative",
|
||||||
|
"-1e310:f64",
|
||||||
|
"Float overflow: value exceeds range for f64.")
|
||||||
|
|
||||||
|
def generate_precision_tests(self):
|
||||||
|
"""Generate tests for precision limits."""
|
||||||
|
# f32 precision (~7 decimal digits)
|
||||||
|
self.make_success_test("Float f32 Precision Limit",
|
||||||
|
"1.2345678:f32", "f32", 1.2345678)
|
||||||
|
self.make_success_test("Float f32 High Precision",
|
||||||
|
"3.141592653589793:f32", "f32", 3.141592653589793)
|
||||||
|
|
||||||
|
# f64 precision (~15 decimal digits)
|
||||||
|
self.make_success_test("Float f64 Precision Limit",
|
||||||
|
"1.234567890123456:f64", "f64", 1.234567890123456)
|
||||||
|
self.make_success_test("Float f64 High Precision",
|
||||||
|
"3.141592653589793238:f64", "f64", 3.141592653589793238)
|
||||||
|
|
||||||
|
# Very close numbers
|
||||||
|
self.make_success_test("Float f64 Close Numbers 1",
|
||||||
|
"1.0000000000000001:f64", "f64", 1.0000000000000001)
|
||||||
|
self.make_success_test("Float f64 Close Numbers 2",
|
||||||
|
"1.0000000000000002:f64", "f64", 1.0000000000000002)
|
||||||
|
|
||||||
|
def generate_error_tests(self):
|
||||||
|
"""Generate error tests."""
|
||||||
|
# Invalid formats
|
||||||
|
self.make_error_test("Float Invalid No Decimal Point",
|
||||||
|
"42",
|
||||||
|
"Not a valid float literal: missing decimal point.")
|
||||||
|
self.make_error_test("Float Invalid Multiple Decimal Points",
|
||||||
|
"3.14.159",
|
||||||
|
"Invalid float literal: multiple decimal points.")
|
||||||
|
self.make_error_test("Float Invalid Only Decimal Point",
|
||||||
|
".",
|
||||||
|
"Invalid float literal: no digits before or after decimal point.")
|
||||||
|
self.make_error_test("Float Invalid Characters",
|
||||||
|
"3.1a4",
|
||||||
|
"Invalid float literal: unexpected 'a' in float.")
|
||||||
|
|
||||||
|
# Invalid scientific notation
|
||||||
|
self.make_error_test("Float Invalid Scientific No Exponent",
|
||||||
|
"3.14e",
|
||||||
|
"Invalid float literal: missing exponent value.")
|
||||||
|
self.make_error_test("Float Invalid Scientific Double E",
|
||||||
|
"3.14e10e5",
|
||||||
|
"Invalid float literal: multiple exponent markers.")
|
||||||
|
self.make_error_test("Float Invalid Scientific Invalid Exponent",
|
||||||
|
"3.14eX",
|
||||||
|
"Invalid float literal: invalid exponent 'X'.")
|
||||||
|
|
||||||
|
# Invalid underscores
|
||||||
|
self.make_error_test("Float Invalid Leading Underscore",
|
||||||
|
"_3.14",
|
||||||
|
"Invalid float literal: leading underscore.")
|
||||||
|
self.make_error_test("Float Invalid Underscore Before Decimal",
|
||||||
|
"3_.14",
|
||||||
|
"Invalid float literal: underscore before decimal point.")
|
||||||
|
self.make_error_test("Float Invalid Underscore After Decimal",
|
||||||
|
"3._14",
|
||||||
|
"Invalid float literal: underscore after decimal point.")
|
||||||
|
|
||||||
|
# Invalid type annotations
|
||||||
|
self.make_error_test("Float Invalid Type Annotation",
|
||||||
|
"3.14:i32",
|
||||||
|
"Type mismatch: float literal cannot be annotated as integer type.")
|
||||||
|
self.make_error_test("Float Invalid Type Name",
|
||||||
|
"3.14:f16",
|
||||||
|
"Invalid type annotation: unknown type 'f16'.")
|
||||||
|
|
||||||
|
# Comma separators not allowed
|
||||||
|
self.make_error_test("Float Invalid Comma Separator",
|
||||||
|
"1,234.56",
|
||||||
|
"Invalid float literal: unexpected ',' in float.")
|
||||||
|
|
||||||
|
def generate_whitespace_tests(self):
|
||||||
|
"""Generate tests with whitespace."""
|
||||||
|
self.make_success_test("Float Default Leading Whitespace",
|
||||||
|
" 3.14", "f64", 3.14)
|
||||||
|
self.make_success_test("Float Default Trailing Whitespace",
|
||||||
|
"3.14 ", "f64", 3.14)
|
||||||
|
self.make_success_test("Float Default Both Whitespace",
|
||||||
|
" 3.14 ", "f64", 3.14)
|
||||||
|
self.make_success_test("Float f32 With Whitespace",
|
||||||
|
" 2.718:f32 ", "f32", 2.718)
|
||||||
|
|
||||||
|
def generate_mathematical_constants_tests(self):
|
||||||
|
"""Generate tests for common mathematical constants."""
|
||||||
|
# Pi
|
||||||
|
self.make_success_test("Float Default Pi Approximate",
|
||||||
|
"3.141592653589793", "f64", 3.141592653589793)
|
||||||
|
self.make_success_test("Float f32 Pi Approximate",
|
||||||
|
"3.1415927:f32", "f32", 3.1415927)
|
||||||
|
|
||||||
|
# Euler's number
|
||||||
|
self.make_success_test("Float Default Euler Approximate",
|
||||||
|
"2.718281828459045", "f64", 2.718281828459045)
|
||||||
|
self.make_success_test("Float f32 Euler Approximate",
|
||||||
|
"2.7182817:f32", "f32", 2.7182817)
|
||||||
|
|
||||||
|
# Golden ratio
|
||||||
|
self.make_success_test("Float Default Golden Ratio",
|
||||||
|
"1.618033988749895", "f64", 1.618033988749895)
|
||||||
|
|
||||||
|
# Square root of 2
|
||||||
|
self.make_success_test("Float Default Sqrt2",
|
||||||
|
"1.4142135623730951", "f64", 1.4142135623730951)
|
||||||
|
|
||||||
|
def generate_signed_zero_tests(self):
|
||||||
|
"""Generate tests for signed zeros."""
|
||||||
|
self.make_success_test("Float Default Positive Zero", "0.0", "f64", 0.0)
|
||||||
|
self.make_success_test("Float Default Negative Zero", "-0.0", "f64", -0.0)
|
||||||
|
self.make_success_test("Float f32 Positive Zero", "0.0:f32", "f32", 0.0)
|
||||||
|
self.make_success_test("Float f32 Negative Zero", "-0.0:f32", "f32", -0.0)
|
||||||
|
|
||||||
|
# Note: In IEEE 754, +0.0 and -0.0 are distinct values but compare equal
|
||||||
|
|
||||||
|
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Generate all test cases."""
|
||||||
|
# Basic tests
|
||||||
|
self.generate_basic_tests()
|
||||||
|
|
||||||
|
# Format variations
|
||||||
|
self.generate_format_tests()
|
||||||
|
|
||||||
|
# Underscores
|
||||||
|
self.generate_underscore_tests()
|
||||||
|
|
||||||
|
# Special values (inf, nan)
|
||||||
|
self.generate_special_value_tests()
|
||||||
|
|
||||||
|
# Edge cases for each type
|
||||||
|
for type_name in ['f32', 'f64']:
|
||||||
|
self.generate_edge_case_tests(type_name)
|
||||||
|
|
||||||
|
# Overflow tests
|
||||||
|
self.generate_overflow_tests()
|
||||||
|
|
||||||
|
# Precision tests
|
||||||
|
self.generate_precision_tests()
|
||||||
|
|
||||||
|
# Error tests
|
||||||
|
self.generate_error_tests()
|
||||||
|
|
||||||
|
# Whitespace tests
|
||||||
|
self.generate_whitespace_tests()
|
||||||
|
|
||||||
|
# Mathematical constants
|
||||||
|
self.generate_mathematical_constants_tests()
|
||||||
|
|
||||||
|
# Signed zeros
|
||||||
|
self.generate_signed_zero_tests()
|
||||||
|
|
||||||
|
return self.tests
|
||||||
|
|
||||||
|
|
||||||
|
def generate_float_literal_tests() -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Main function to generate all floating point literal test cases.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of test case dictionaries ready for serialization
|
||||||
|
"""
|
||||||
|
generator = FloatTestGenerator()
|
||||||
|
return generator.generate_all_tests()
|
||||||
Loading…
Reference in New Issue