Added base test generator class
This commit is contained in:
parent
cf5f51ccd8
commit
375c3f2422
|
|
@ -3,35 +3,22 @@ Test case generator for integer literals in the Stack Language.
|
|||
Generates comprehensive test cases for all integer types and bases.
|
||||
"""
|
||||
|
||||
from .integer_tests import generate_integer_literal_tests
|
||||
from .float_tests import generate_float_literal_tests
|
||||
from .general_tests import GeneralTestGenerator
|
||||
from .integer_tests import IntegerTestGenerator
|
||||
from .float_tests import FloatTestGenerator
|
||||
|
||||
if __name__ == "__main__":
|
||||
# import json
|
||||
import yaml
|
||||
|
||||
# Generate tests
|
||||
tests = []
|
||||
tests += generate_integer_literal_tests()
|
||||
tests += generate_float_literal_tests()
|
||||
tests += GeneralTestGenerator.generate_tests()
|
||||
tests += IntegerTestGenerator.generate_tests()
|
||||
tests += FloatTestGenerator.generate_tests()
|
||||
|
||||
# Print summary
|
||||
print(f"Generated {len(tests)} test cases")
|
||||
|
||||
# # Save as JSON
|
||||
# with open("integer_literal_tests.json", "w") as f:
|
||||
# json.dump(tests, f, indent=2)
|
||||
# print("Saved to integer_literal_tests.json")
|
||||
|
||||
# # Save as YAML
|
||||
# with open("integer_literal_tests.yaml", "w") as f:
|
||||
# yaml.dump(tests, f, default_flow_style=False, sort_keys=False)
|
||||
# print("Saved to integer_literal_tests.yaml")
|
||||
|
||||
# Save as YAML
|
||||
with open("cases.yaml", "w") as f:
|
||||
yaml.dump(tests, f, default_flow_style=False, sort_keys=False)
|
||||
|
||||
# Print first few tests as example
|
||||
# print("\nFirst 3 test cases:")
|
||||
# print(yaml.dump(tests[:3], default_flow_style=False, sort_keys=False))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,352 @@
|
|||
from typing import List, Dict, Any, Optional
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
@dataclass
|
||||
class Token:
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Operation:
|
||||
function: str
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class StackItem:
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuntimeError:
|
||||
message: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestCase:
|
||||
name: str
|
||||
code: str
|
||||
tokens: List[Dict[str, Any]]
|
||||
operations: Optional[List[Dict[str, Any]]] = None
|
||||
stack_final: Optional[List[Dict[str, Any]]] = None
|
||||
runtime_error: Optional[Dict[str, str]] = None
|
||||
|
||||
|
||||
def to_dict(obj) -> Dict[str, Any]:
|
||||
"""Convert dataclass to dict, removing None values."""
|
||||
if obj is None:
|
||||
raise ValueError("Obj cannot be None")
|
||||
d = asdict(obj) if hasattr(obj, '__dataclass_fields__') else obj
|
||||
return {k: v for k, v in d.items() if v is not None}
|
||||
|
||||
|
||||
class BaseTestGenerator(ABC):
|
||||
"""
|
||||
Abstract base class for test case generators.
|
||||
|
||||
Provides common functionality for generating test cases including
|
||||
test creation, operation building, and error handling.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the test generator with an empty test list."""
|
||||
self.tests: List[Dict[str, Any]] = []
|
||||
|
||||
# =========================================================================
|
||||
# Test Case Management
|
||||
# =========================================================================
|
||||
|
||||
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 to the test suite.
|
||||
|
||||
Args:
|
||||
name: Descriptive name for the test
|
||||
code: Source code being tested
|
||||
tokens: List of expected tokens from lexing
|
||||
operations: Optional list of operations for evaluation phase
|
||||
stack_final: Optional final stack state after execution
|
||||
runtime_error: Optional runtime error if test should fail
|
||||
"""
|
||||
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))
|
||||
|
||||
# =========================================================================
|
||||
# Factory Methods
|
||||
# =========================================================================
|
||||
|
||||
def make_push_op(self, type_name: str, value: Any) -> Operation:
|
||||
"""
|
||||
Create a push operation.
|
||||
|
||||
Args:
|
||||
type_name: Type of the value being pushed
|
||||
value: The value to push
|
||||
|
||||
Returns:
|
||||
Operation object representing a push
|
||||
"""
|
||||
return Operation(function="push", type=type_name, value=value)
|
||||
|
||||
def make_stack_item(self, type_name: str, value: Any) -> StackItem:
|
||||
"""
|
||||
Create a stack item.
|
||||
|
||||
Args:
|
||||
type_name: Type of the stack item
|
||||
value: The value of the stack item
|
||||
|
||||
Returns:
|
||||
StackItem object
|
||||
"""
|
||||
return StackItem(type=type_name, value=value)
|
||||
|
||||
def make_error_token(self, message: str) -> Token:
|
||||
"""
|
||||
Create an error token.
|
||||
|
||||
Args:
|
||||
message: Error message
|
||||
|
||||
Returns:
|
||||
Token object with type "error"
|
||||
"""
|
||||
return Token(type="error", value=message)
|
||||
|
||||
def make_runtime_error(self, message: str) -> RuntimeError:
|
||||
"""
|
||||
Create a runtime error.
|
||||
|
||||
Args:
|
||||
message: Error message
|
||||
|
||||
Returns:
|
||||
RuntimeError object
|
||||
"""
|
||||
return RuntimeError(message=message)
|
||||
|
||||
# =========================================================================
|
||||
# Convenience Test Creators
|
||||
# =========================================================================
|
||||
|
||||
def make_success_test(self, name: str, code: str, type_name: str, value: Any):
|
||||
"""
|
||||
Create a successful test case with standard push operation.
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
type_name: Type of the value
|
||||
value: The value
|
||||
"""
|
||||
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 (lexing error).
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
error_msg: Expected error message
|
||||
"""
|
||||
token = self.make_error_token(error_msg)
|
||||
self.add_test(name, code, [token])
|
||||
|
||||
def make_runtime_error_test(self, name: str, code: str, tokens: List[Token],
|
||||
operations: List[Operation], error_msg: str):
|
||||
"""
|
||||
Create a runtime error test case.
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
tokens: Tokens that were successfully lexed
|
||||
operations: Operations that led to the error
|
||||
error_msg: Expected runtime error message
|
||||
"""
|
||||
runtime_error = self.make_runtime_error(error_msg)
|
||||
self.add_test(name, code, tokens, operations, None, runtime_error)
|
||||
|
||||
def make_empty_test(self, name: str, code: str):
|
||||
"""
|
||||
Create a test case with empty result (e.g., comments, whitespace).
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
"""
|
||||
self.add_test(name, code, [], [], [])
|
||||
|
||||
# =========================================================================
|
||||
# Multi-Value Test Helpers
|
||||
# =========================================================================
|
||||
|
||||
def make_multi_value_test(self, name: str, code: str,
|
||||
values: List[tuple[str, Any]]):
|
||||
"""
|
||||
Create a test with multiple values on the stack.
|
||||
|
||||
Args:
|
||||
name: Test name
|
||||
code: Source code
|
||||
values: List of (type_name, value) tuples in stack order
|
||||
"""
|
||||
tokens = [Token(type=t, value=v) for t, v in values]
|
||||
operations = [self.make_push_op(t, v) for t, v in values]
|
||||
stack = [self.make_stack_item(t, v) for t, v in values]
|
||||
self.add_test(name, code, tokens, operations, stack)
|
||||
|
||||
# =========================================================================
|
||||
# Test Suite Generation
|
||||
# =========================================================================
|
||||
|
||||
@abstractmethod
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Generate all test cases for this generator.
|
||||
|
||||
Must be implemented by subclasses to define their specific test suite.
|
||||
|
||||
Returns:
|
||||
List of test case dictionaries ready for serialization
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_tests(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get the current list of tests.
|
||||
|
||||
Returns:
|
||||
List of test case dictionaries
|
||||
"""
|
||||
return self.tests
|
||||
|
||||
def clear_tests(self):
|
||||
"""Clear all tests from the generator."""
|
||||
self.tests = []
|
||||
|
||||
def test_count(self) -> int:
|
||||
"""
|
||||
Get the number of tests generated.
|
||||
|
||||
Returns:
|
||||
Number of tests
|
||||
"""
|
||||
return len(self.tests)
|
||||
|
||||
@classmethod
|
||||
def generate_tests(cls) -> List[Dict[str, Any]]:
|
||||
gen = cls()
|
||||
tests = gen.generate_all_tests()
|
||||
gen.print_statistics()
|
||||
return tests
|
||||
|
||||
# =========================================================================
|
||||
# Test Organization Helpers
|
||||
# =========================================================================
|
||||
|
||||
def add_test_group_comment(self, comment: str):
|
||||
"""
|
||||
Add a comment to organize test groups (for documentation).
|
||||
|
||||
Note: This doesn't add an actual test, just tracks organization
|
||||
in subclass implementations.
|
||||
|
||||
Args:
|
||||
comment: Comment describing the test group
|
||||
"""
|
||||
# Subclasses can override to add metadata or logging
|
||||
pass
|
||||
|
||||
# =========================================================================
|
||||
# Validation Helpers
|
||||
# =========================================================================
|
||||
|
||||
def validate_test_names_unique(self) -> bool:
|
||||
"""
|
||||
Check if all test names are unique.
|
||||
|
||||
Returns:
|
||||
True if all test names are unique, False otherwise
|
||||
"""
|
||||
names = [test['name'] for test in self.tests]
|
||||
return len(names) == len(set(names))
|
||||
|
||||
def get_duplicate_test_names(self) -> List[str]:
|
||||
"""
|
||||
Get list of duplicate test names.
|
||||
|
||||
Returns:
|
||||
List of test names that appear more than once
|
||||
"""
|
||||
names = [test['name'] for test in self.tests]
|
||||
seen = set()
|
||||
duplicates = set()
|
||||
for name in names:
|
||||
if name in seen:
|
||||
duplicates.add(name)
|
||||
seen.add(name)
|
||||
return list(duplicates)
|
||||
|
||||
# =========================================================================
|
||||
# Statistics
|
||||
# =========================================================================
|
||||
|
||||
def get_test_statistics(self) -> Dict[str, int]:
|
||||
"""
|
||||
Get statistics about the generated tests.
|
||||
|
||||
Returns:
|
||||
Dictionary with test statistics
|
||||
"""
|
||||
stats = {
|
||||
'total': len(self.tests),
|
||||
'success': 0,
|
||||
'lex_error': 0,
|
||||
'runtime_error': 0,
|
||||
'empty': 0,
|
||||
}
|
||||
|
||||
for test in self.tests:
|
||||
if test.get('runtime_error'):
|
||||
stats['runtime_error'] += 1
|
||||
elif test.get('tokens') and test['tokens'][0].get('type') == 'error':
|
||||
stats['lex_error'] += 1
|
||||
elif not test.get('tokens'):
|
||||
stats['empty'] += 1
|
||||
else:
|
||||
stats['success'] += 1
|
||||
|
||||
return stats
|
||||
|
||||
def print_statistics(self):
|
||||
"""Print test statistics to console."""
|
||||
stats = self.get_test_statistics()
|
||||
print(f"Test Statistics for {self.__class__.__name__}:")
|
||||
print(f" Total tests: {stats['total']}")
|
||||
print(f" Success tests: {stats['success']}")
|
||||
print(f" Lex error tests: {stats['lex_error']}")
|
||||
print(f" Runtime error tests: {stats['runtime_error']}")
|
||||
print(f" Empty tests: {stats['empty']}")
|
||||
|
||||
if not self.validate_test_names_unique():
|
||||
duplicates = self.get_duplicate_test_names()
|
||||
print(f" WARNING: Duplicate test names found: {duplicates}")
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import List, Dict, Any, Optional
|
||||
from .utils import Token, Operation, StackItem, RuntimeError, TestCase, to_dict
|
||||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
class FloatTestGenerator:
|
||||
class FloatTestGenerator(BaseTestGenerator):
|
||||
"""Generate test cases for floating point literals."""
|
||||
|
||||
# Special float values
|
||||
|
|
@ -20,48 +20,6 @@ class FloatTestGenerator:
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
@ -354,14 +312,3 @@ class FloatTestGenerator:
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
class GeneralTestGenerator(BaseTestGenerator):
|
||||
|
||||
def generate_all_tests(self) -> List[Dict[str, Any]]:
|
||||
"""Generate all test cases."""
|
||||
|
||||
self.add_test("Empty_Statement", "", [], [], [])
|
||||
|
||||
return self.tests
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import List, Dict, Any, Optional
|
||||
from .utils import Token, Operation, StackItem, RuntimeError, TestCase, to_dict
|
||||
from typing import List, Dict, Any
|
||||
from .base_tests import BaseTestGenerator
|
||||
|
||||
class IntegerTestGenerator:
|
||||
class IntegerTestGenerator(BaseTestGenerator):
|
||||
"""Generate test cases for integer literals."""
|
||||
|
||||
# Type ranges
|
||||
|
|
@ -16,52 +16,8 @@ class IntegerTestGenerator:
|
|||
'u64': (0, 18446744073709551615),
|
||||
}
|
||||
|
||||
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: Any):
|
||||
"""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."""
|
||||
# Empty statement
|
||||
self.add_test("Empty_Statement", "", [], [], [])
|
||||
|
||||
# Simple default integers
|
||||
self.make_success_test("Integer Default Decimal 0", "0", "i64", 0)
|
||||
|
|
@ -265,14 +221,3 @@ class IntegerTestGenerator:
|
|||
self.generate_underscore_tests()
|
||||
|
||||
return self.tests
|
||||
|
||||
|
||||
def generate_integer_literal_tests() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Main function to generate all integer literal test cases.
|
||||
|
||||
Returns:
|
||||
List of test case dictionaries ready for serialization
|
||||
"""
|
||||
generator = IntegerTestGenerator()
|
||||
return generator.generate_all_tests()
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
|
||||
from typing import List, Dict, Any, Optional
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
|
||||
@dataclass
|
||||
class Token:
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Operation:
|
||||
function: str
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class StackItem:
|
||||
type: str
|
||||
value: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuntimeError:
|
||||
message: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestCase:
|
||||
name: str
|
||||
code: str
|
||||
tokens: List[Dict[str, Any]]
|
||||
operations: Optional[List[Dict[str, Any]]] = None
|
||||
stack_final: Optional[List[Dict[str, Any]]] = None
|
||||
runtime_error: Optional[Dict[str, str]] = None
|
||||
|
||||
|
||||
def to_dict(obj):
|
||||
"""Convert dataclass to dict, removing None values."""
|
||||
if obj is None:
|
||||
return None
|
||||
d = asdict(obj) if hasattr(obj, '__dataclass_fields__') else obj
|
||||
return {k: v for k, v in d.items() if v is not None}
|
||||
Loading…
Reference in New Issue