381 lines
16 KiB
Python
381 lines
16 KiB
Python
"""
|
|
Unit Tests for EngineSpecParser
|
|
|
|
Tests the engine specification parsing functionality including:
|
|
- Standard engine format parsing (displacement, configuration, cylinders)
|
|
- CRITICAL: L→I normalization (L3 becomes I3)
|
|
- Hybrid and electric vehicle detection
|
|
- Fuel type and aspiration parsing
|
|
- Electric motor creation for empty engines arrays
|
|
- Error handling for unparseable engines
|
|
"""
|
|
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
# Import the class we're testing
|
|
from ..utils.engine_spec_parser import EngineSpecParser, EngineSpec
|
|
|
|
|
|
class TestEngineSpecParser(unittest.TestCase):
|
|
"""Test cases for EngineSpecParser utility"""
|
|
|
|
def setUp(self):
|
|
"""Set up test environment before each test"""
|
|
self.parser = EngineSpecParser()
|
|
|
|
def test_parse_standard_engines(self):
|
|
"""Test parsing of standard engine formats"""
|
|
test_cases = [
|
|
# Format: (input, expected_displacement, expected_config, expected_cylinders)
|
|
("2.0L I4", 2.0, "I", 4),
|
|
("3.5L V6", 3.5, "V", 6),
|
|
("5.6L V8", 5.6, "V", 8),
|
|
("1.6L I4", 1.6, "I", 4),
|
|
("6.2L V8", 6.2, "V", 8),
|
|
]
|
|
|
|
for engine_str, expected_disp, expected_config, expected_cyl in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
|
|
self.assertEqual(spec.displacement_l, expected_disp,
|
|
f"Displacement: expected {expected_disp}, got {spec.displacement_l}")
|
|
self.assertEqual(spec.configuration, expected_config,
|
|
f"Configuration: expected {expected_config}, got {spec.configuration}")
|
|
self.assertEqual(spec.cylinders, expected_cyl,
|
|
f"Cylinders: expected {expected_cyl}, got {spec.cylinders}")
|
|
self.assertEqual(spec.fuel_type, "Gasoline") # Default
|
|
self.assertEqual(spec.aspiration, "Natural") # Default
|
|
|
|
def test_l_to_i_normalization(self):
|
|
"""Test CRITICAL L→I configuration normalization"""
|
|
test_cases = [
|
|
# L-configuration should become I (Inline)
|
|
("1.5L L3", "I", 3),
|
|
("2.0L L4", "I", 4),
|
|
("1.2L L3", "I", 3),
|
|
]
|
|
|
|
for engine_str, expected_config, expected_cyl in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
|
|
self.assertEqual(spec.configuration, expected_config,
|
|
f"L→I normalization failed: '{engine_str}' should become I{expected_cyl}")
|
|
self.assertEqual(spec.cylinders, expected_cyl)
|
|
self.assertEqual(spec.raw_string, engine_str) # Original preserved
|
|
|
|
def test_subaru_boxer_engines(self):
|
|
"""Test Subaru Boxer (H-configuration) engines"""
|
|
test_cases = [
|
|
("2.4L H4", 2.4, "H", 4),
|
|
("2.0L H4", 2.0, "H", 4),
|
|
("2.5L H4", 2.5, "H", 4),
|
|
]
|
|
|
|
for engine_str, expected_disp, expected_config, expected_cyl in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
|
|
self.assertEqual(spec.displacement_l, expected_disp)
|
|
self.assertEqual(spec.configuration, expected_config) # Should remain H
|
|
self.assertEqual(spec.cylinders, expected_cyl)
|
|
|
|
def test_w_configuration_engines(self):
|
|
"""Test W-configuration engines (VW Group, Bentley)"""
|
|
test_cases = [
|
|
("6.0L W12", 6.0, "W", 12),
|
|
("4.0L W8", 4.0, "W", 8),
|
|
("8.0L W16", 8.0, "W", 16), # Theoretical case
|
|
]
|
|
|
|
for engine_str, expected_disp, expected_config, expected_cyl in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
|
|
self.assertEqual(spec.displacement_l, expected_disp)
|
|
self.assertEqual(spec.configuration, expected_config) # Should remain W
|
|
self.assertEqual(spec.cylinders, expected_cyl)
|
|
|
|
def test_hybrid_detection(self):
|
|
"""Test hybrid engine detection patterns"""
|
|
test_cases = [
|
|
# Format: (input, expected_fuel_type)
|
|
("2.5L I4 FULL HYBRID EV- (FHEV)", "Full Hybrid"),
|
|
("1.5L L3 PLUG-IN HYBRID EV- (PHEV)", "Plug-in Hybrid"),
|
|
("2.0L I4 FULL HYBRID EV- (FHEV)", "Full Hybrid"),
|
|
("1.8L I4 HYBRID", "Hybrid"), # Generic hybrid
|
|
]
|
|
|
|
for engine_str, expected_fuel_type in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
|
|
self.assertEqual(spec.fuel_type, expected_fuel_type,
|
|
f"Expected fuel type '{expected_fuel_type}' for '{engine_str}'")
|
|
|
|
def test_l_to_i_with_hybrid(self):
|
|
"""Test L→I normalization combined with hybrid detection"""
|
|
test_cases = [
|
|
("1.5L L3 PLUG-IN HYBRID EV- (PHEV)", "I", 3, "Plug-in Hybrid"),
|
|
("1.2L L3 FULL HYBRID EV- (FHEV)", "I", 3, "Full Hybrid"),
|
|
]
|
|
|
|
for engine_str, expected_config, expected_cyl, expected_fuel in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
|
|
# Test both L→I normalization AND hybrid detection
|
|
self.assertEqual(spec.configuration, expected_config,
|
|
f"L→I normalization failed for hybrid: '{engine_str}'")
|
|
self.assertEqual(spec.cylinders, expected_cyl)
|
|
self.assertEqual(spec.fuel_type, expected_fuel,
|
|
f"Hybrid detection failed: '{engine_str}'")
|
|
|
|
def test_flex_fuel_detection(self):
|
|
"""Test flex fuel detection"""
|
|
test_cases = [
|
|
("5.6L V8 FLEX", "Flex Fuel"),
|
|
("4.0L V6 FLEX", "Flex Fuel"),
|
|
]
|
|
|
|
for engine_str, expected_fuel_type in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
self.assertEqual(spec.fuel_type, expected_fuel_type)
|
|
|
|
def test_electric_detection(self):
|
|
"""Test electric engine detection"""
|
|
test_cases = [
|
|
("1.8L I4 ELECTRIC", "Electric"),
|
|
]
|
|
|
|
for engine_str, expected_fuel_type in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
self.assertEqual(spec.fuel_type, expected_fuel_type)
|
|
|
|
def test_aspiration_detection(self):
|
|
"""Test turbo/supercharged detection"""
|
|
# Note: These patterns are less common in current JSON data
|
|
test_cases = [
|
|
("2.0L I4 TURBO", "Turbocharged"),
|
|
("6.2L V8 SUPERCHARGED", "Supercharged"),
|
|
("3.0L V6 SC", "Supercharged"), # SC abbreviation
|
|
]
|
|
|
|
for engine_str, expected_aspiration in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
self.assertEqual(spec.aspiration, expected_aspiration)
|
|
|
|
def test_create_electric_motor(self):
|
|
"""Test electric motor creation for empty engines arrays"""
|
|
spec = self.parser.create_electric_motor()
|
|
|
|
self.assertIsNone(spec.displacement_l)
|
|
self.assertEqual(spec.configuration, "Electric")
|
|
self.assertIsNone(spec.cylinders)
|
|
self.assertEqual(spec.fuel_type, "Electric")
|
|
self.assertIsNone(spec.aspiration)
|
|
self.assertEqual(spec.raw_string, "Electric Motor")
|
|
|
|
def test_parse_multiple_engines_empty_array(self):
|
|
"""Test handling of empty engines array (electric vehicles)"""
|
|
# Empty array should create electric motor
|
|
specs = self.parser.parse_multiple_engines([])
|
|
|
|
self.assertEqual(len(specs), 1)
|
|
self.assertEqual(specs[0].raw_string, "Electric Motor")
|
|
self.assertEqual(specs[0].fuel_type, "Electric")
|
|
|
|
def test_parse_multiple_engines_normal(self):
|
|
"""Test parsing multiple engine specifications"""
|
|
engine_strings = [
|
|
"2.0L I4",
|
|
"3.5L V6",
|
|
"1.5L L3 PLUG-IN HYBRID EV- (PHEV)" # Includes L→I normalization
|
|
]
|
|
|
|
specs = self.parser.parse_multiple_engines(engine_strings)
|
|
|
|
self.assertEqual(len(specs), 3)
|
|
|
|
# Check first engine
|
|
self.assertEqual(specs[0].displacement_l, 2.0)
|
|
self.assertEqual(specs[0].configuration, "I")
|
|
|
|
# Check third engine (L→I normalization + hybrid)
|
|
self.assertEqual(specs[2].configuration, "I") # L normalized to I
|
|
self.assertEqual(specs[2].fuel_type, "Plug-in Hybrid")
|
|
|
|
def test_unparseable_engines(self):
|
|
"""Test handling of unparseable engine strings"""
|
|
unparseable_cases = [
|
|
"Custom Hybrid System",
|
|
"V12 Twin-Turbo Custom",
|
|
"V10 Plus",
|
|
"Unknown Engine Type",
|
|
"", # Empty string
|
|
]
|
|
|
|
for engine_str in unparseable_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
|
|
# Should create fallback engine
|
|
self.assertEqual(spec.configuration, "Unknown")
|
|
self.assertEqual(spec.fuel_type, "Unknown")
|
|
self.assertEqual(spec.aspiration, "Natural")
|
|
self.assertIsNone(spec.displacement_l)
|
|
self.assertIsNone(spec.cylinders)
|
|
self.assertEqual(spec.raw_string, engine_str or "Empty Engine String")
|
|
|
|
def test_case_insensitive_parsing(self):
|
|
"""Test that parsing is case insensitive"""
|
|
test_cases = [
|
|
("2.0l i4", 2.0, "I", 4), # Lowercase
|
|
("3.5L v6", 3.5, "V", 6), # Mixed case
|
|
("1.5L l3 plug-in hybrid ev- (phev)", "I", 3, "Plug-in Hybrid"), # Lowercase with hybrid
|
|
]
|
|
|
|
for engine_str, expected_disp, expected_config, expected_cyl in test_cases[:2]:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
|
|
self.assertEqual(spec.displacement_l, expected_disp)
|
|
self.assertEqual(spec.configuration, expected_config)
|
|
self.assertEqual(spec.cylinders, expected_cyl)
|
|
|
|
# Test hybrid case separately
|
|
spec = self.parser.parse_engine_string("1.5L l3 plug-in hybrid ev- (phev)")
|
|
self.assertEqual(spec.configuration, "I") # L→I normalization
|
|
self.assertEqual(spec.fuel_type, "Plug-in Hybrid")
|
|
|
|
def test_get_unique_engines(self):
|
|
"""Test deduplication of engine specifications"""
|
|
# Create list with duplicates
|
|
engine_specs = [
|
|
self.parser.parse_engine_string("2.0L I4"),
|
|
self.parser.parse_engine_string("2.0L I4"), # Duplicate
|
|
self.parser.parse_engine_string("3.5L V6"),
|
|
self.parser.parse_engine_string("2.0L I4"), # Another duplicate
|
|
]
|
|
|
|
unique_specs = self.parser.get_unique_engines(engine_specs)
|
|
|
|
# Should have only 2 unique engines
|
|
self.assertEqual(len(unique_specs), 2)
|
|
|
|
# Check that we have the expected unique engines
|
|
displacement_configs = [(spec.displacement_l, spec.configuration, spec.cylinders)
|
|
for spec in unique_specs]
|
|
|
|
self.assertIn((2.0, "I", 4), displacement_configs)
|
|
self.assertIn((3.5, "V", 6), displacement_configs)
|
|
|
|
def test_validate_engine_spec(self):
|
|
"""Test engine specification validation"""
|
|
# Valid engine
|
|
valid_spec = self.parser.parse_engine_string("2.0L I4")
|
|
warnings = self.parser.validate_engine_spec(valid_spec)
|
|
self.assertEqual(len(warnings), 0)
|
|
|
|
# Invalid displacement
|
|
invalid_spec = EngineSpec(
|
|
displacement_l=-1.0, # Invalid
|
|
configuration="I",
|
|
cylinders=4,
|
|
fuel_type="Gasoline",
|
|
aspiration="Natural",
|
|
raw_string="Invalid Engine"
|
|
)
|
|
warnings = self.parser.validate_engine_spec(invalid_spec)
|
|
self.assertGreater(len(warnings), 0)
|
|
self.assertTrue(any("displacement" in w for w in warnings))
|
|
|
|
# Electric motor should be valid
|
|
electric_spec = self.parser.create_electric_motor()
|
|
warnings = self.parser.validate_engine_spec(electric_spec)
|
|
self.assertEqual(len(warnings), 0)
|
|
|
|
def test_normalize_configuration_directly(self):
|
|
"""Test the normalize_configuration method directly"""
|
|
# Test L→I normalization
|
|
self.assertEqual(self.parser.normalize_configuration('L'), 'I')
|
|
self.assertEqual(self.parser.normalize_configuration('l'), 'I') # Case insensitive
|
|
|
|
# Test other configurations remain unchanged
|
|
self.assertEqual(self.parser.normalize_configuration('I'), 'I')
|
|
self.assertEqual(self.parser.normalize_configuration('V'), 'V')
|
|
self.assertEqual(self.parser.normalize_configuration('H'), 'H')
|
|
self.assertEqual(self.parser.normalize_configuration('i'), 'I') # Uppercase
|
|
|
|
def test_decimal_displacement(self):
|
|
"""Test engines with decimal displacements"""
|
|
test_cases = [
|
|
("1.5L I4", 1.5),
|
|
("2.3L I4", 2.3),
|
|
("3.7L V6", 3.7),
|
|
]
|
|
|
|
for engine_str, expected_disp in test_cases:
|
|
with self.subTest(engine_str=engine_str):
|
|
spec = self.parser.parse_engine_string(engine_str)
|
|
self.assertEqual(spec.displacement_l, expected_disp)
|
|
|
|
def test_edge_case_patterns(self):
|
|
"""Test edge cases and boundary conditions"""
|
|
# Large engines
|
|
spec = self.parser.parse_engine_string("8.4L V10")
|
|
self.assertEqual(spec.displacement_l, 8.4)
|
|
self.assertEqual(spec.cylinders, 10)
|
|
|
|
# Small engines
|
|
spec = self.parser.parse_engine_string("1.0L I3")
|
|
self.assertEqual(spec.displacement_l, 1.0)
|
|
self.assertEqual(spec.cylinders, 3)
|
|
|
|
# Very small displacement
|
|
spec = self.parser.parse_engine_string("0.7L I3")
|
|
self.assertEqual(spec.displacement_l, 0.7)
|
|
|
|
|
|
class TestEngineSpec(unittest.TestCase):
|
|
"""Test cases for EngineSpec dataclass"""
|
|
|
|
def test_engine_spec_creation(self):
|
|
"""Test EngineSpec creation and string representation"""
|
|
spec = EngineSpec(
|
|
displacement_l=2.0,
|
|
configuration="I",
|
|
cylinders=4,
|
|
fuel_type="Gasoline",
|
|
aspiration="Natural",
|
|
raw_string="2.0L I4"
|
|
)
|
|
|
|
self.assertEqual(spec.displacement_l, 2.0)
|
|
self.assertEqual(spec.configuration, "I")
|
|
self.assertEqual(spec.cylinders, 4)
|
|
self.assertEqual(spec.fuel_type, "Gasoline")
|
|
self.assertEqual(spec.aspiration, "Natural")
|
|
self.assertEqual(spec.raw_string, "2.0L I4")
|
|
|
|
# Test string representation
|
|
str_repr = str(spec)
|
|
self.assertIn("2.0L I4", str_repr)
|
|
self.assertIn("Gasoline", str_repr)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Run specific test for L→I normalization if needed
|
|
if len(unittest.sys.argv) > 1 and 'l_to_i' in unittest.sys.argv[1].lower():
|
|
suite = unittest.TestSuite()
|
|
suite.addTest(TestEngineSpecParser('test_l_to_i_normalization'))
|
|
suite.addTest(TestEngineSpecParser('test_l_to_i_with_hybrid'))
|
|
runner = unittest.TextTestRunner(verbosity=2)
|
|
runner.run(suite)
|
|
else:
|
|
unittest.main(verbosity=2) |