mirror of
https://github.com/Genaker/LoraSA.git
synced 2026-05-06 05:22:39 +02:00
217 lines
7.8 KiB
Python
217 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Unit tests for serial_utils module.
|
|
Run with: python3 -m unittest test_serial_utils.py
|
|
"""
|
|
|
|
import unittest
|
|
import sys
|
|
import os
|
|
|
|
# Add parent directory to path for imports
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from serial_utils import (
|
|
crc16,
|
|
parse_scan_result,
|
|
parse_scan_result_regex,
|
|
validate_serial_config
|
|
)
|
|
|
|
|
|
class TestCRC16(unittest.TestCase):
|
|
"""Tests for CRC16 calculation."""
|
|
|
|
def test_crc16_basic(self):
|
|
"""Test basic CRC16 calculation."""
|
|
result = crc16("test", 0)
|
|
self.assertIsInstance(result, int)
|
|
self.assertGreaterEqual(result, 0)
|
|
self.assertLessEqual(result, 0xFFFF)
|
|
|
|
def test_crc16_empty(self):
|
|
"""Test CRC16 with empty string."""
|
|
result = crc16("", 0)
|
|
# Empty string with initial value 0 produces 0
|
|
self.assertEqual(result, 0)
|
|
|
|
def test_crc16_consistency(self):
|
|
"""Test that same input gives same output."""
|
|
input_str = "SCAN_RESULT 123"
|
|
result1 = crc16(input_str, 0)
|
|
result2 = crc16(input_str, 0)
|
|
self.assertEqual(result1, result2)
|
|
|
|
|
|
class TestParseScanResult(unittest.TestCase):
|
|
"""Tests for parse_scan_result function."""
|
|
|
|
def test_valid_input(self):
|
|
"""Test parsing valid SCAN_RESULT data."""
|
|
line = "SCAN_RESULT 2 [(850000, -100), (860000, -90)]"
|
|
count, data = parse_scan_result(line)
|
|
|
|
self.assertEqual(count, 2)
|
|
self.assertEqual(len(data), 2)
|
|
self.assertEqual(data[0], [850000, -100])
|
|
self.assertEqual(data[1], [860000, -90])
|
|
|
|
def test_valid_with_garbage(self):
|
|
"""Test parsing with garbage before SCAN_RESULT."""
|
|
line = "garbage data SCAN_RESULT 1 [(900000, -80)]"
|
|
count, data = parse_scan_result(line)
|
|
|
|
self.assertEqual(count, 1)
|
|
self.assertEqual(len(data), 1)
|
|
|
|
def test_missing_scan_result(self):
|
|
"""Test error handling for missing SCAN_RESULT."""
|
|
with self.assertRaises(ValueError) as cm:
|
|
parse_scan_result("no scan result here")
|
|
self.assertIn("SCAN_RESULT", str(cm.exception))
|
|
|
|
def test_invalid_format(self):
|
|
"""Test error handling for invalid format."""
|
|
with self.assertRaises(ValueError):
|
|
parse_scan_result("SCAN_RESULT") # Missing count and data
|
|
|
|
def test_count_mismatch(self):
|
|
"""Test error handling when count doesn't match data length."""
|
|
line = "SCAN_RESULT 5 [(850000, -100)]" # Says 5 but only 1 item
|
|
with self.assertRaises(ValueError) as cm:
|
|
parse_scan_result(line)
|
|
self.assertIn("does not match count", str(cm.exception))
|
|
|
|
def test_invalid_frequency_low(self):
|
|
"""Test error handling for frequency too low."""
|
|
line = "SCAN_RESULT 1 [(1000, -100)]" # 1 kHz - too low
|
|
with self.assertRaises(ValueError) as cm:
|
|
parse_scan_result(line)
|
|
self.assertIn("Invalid frequency", str(cm.exception))
|
|
|
|
def test_invalid_frequency_high(self):
|
|
"""Test error handling for frequency too high."""
|
|
line = "SCAN_RESULT 1 [(9000000, -100)]" # 9 GHz - too high
|
|
with self.assertRaises(ValueError) as cm:
|
|
parse_scan_result(line)
|
|
self.assertIn("Invalid frequency", str(cm.exception))
|
|
|
|
def test_invalid_rssi_positive(self):
|
|
"""Test error handling for positive RSSI."""
|
|
line = "SCAN_RESULT 1 [(850000, 50)]" # Positive RSSI - invalid
|
|
with self.assertRaises(ValueError) as cm:
|
|
parse_scan_result(line)
|
|
self.assertIn("Invalid RSSI", str(cm.exception))
|
|
|
|
def test_invalid_rssi_low(self):
|
|
"""Test error handling for RSSI too low."""
|
|
line = "SCAN_RESULT 1 [(850000, -300)]" # -300 dBm - too low
|
|
with self.assertRaises(ValueError) as cm:
|
|
parse_scan_result(line)
|
|
self.assertIn("Invalid RSSI", str(cm.exception))
|
|
|
|
def test_count_too_large(self):
|
|
"""Test error handling for unreasonably large count."""
|
|
# Create a line with a very large count value
|
|
line = "SCAN_RESULT 99999 []"
|
|
with self.assertRaises(ValueError) as cm:
|
|
parse_scan_result(line)
|
|
# Should fail either on count validation or count mismatch
|
|
|
|
|
|
class TestParseScanResultRegex(unittest.TestCase):
|
|
"""Tests for parse_scan_result_regex function."""
|
|
|
|
def test_valid_input(self):
|
|
"""Test parsing valid data with regex method."""
|
|
line = "SCAN_RESULT 2 [ (850000, -100), (860000, -90) ]"
|
|
data = parse_scan_result_regex(line)
|
|
|
|
self.assertEqual(len(data), 2)
|
|
self.assertEqual(data[0], (850000, -100))
|
|
self.assertEqual(data[1], (860000, -90))
|
|
|
|
def test_empty_result(self):
|
|
"""Test error handling for empty result."""
|
|
line = "SCAN_RESULT 0 []"
|
|
with self.assertRaises(ValueError) as cm:
|
|
parse_scan_result_regex(line)
|
|
self.assertIn("No valid frequency/RSSI pairs", str(cm.exception))
|
|
|
|
def test_missing_scan_result(self):
|
|
"""Test error handling when SCAN_RESULT is missing."""
|
|
with self.assertRaises(ValueError):
|
|
parse_scan_result_regex("just some data")
|
|
|
|
|
|
class TestValidateSerialConfig(unittest.TestCase):
|
|
"""Tests for validate_serial_config function."""
|
|
|
|
def test_valid_config(self):
|
|
"""Test validation with valid configuration."""
|
|
# Should not raise exception
|
|
validate_serial_config("/dev/ttyUSB0", 115200, 5)
|
|
|
|
def test_invalid_port_empty(self):
|
|
"""Test error handling for empty port."""
|
|
with self.assertRaises(ValueError) as cm:
|
|
validate_serial_config("", 115200, 5)
|
|
self.assertIn("Port", str(cm.exception))
|
|
|
|
def test_invalid_port_none(self):
|
|
"""Test error handling for None port."""
|
|
with self.assertRaises(ValueError):
|
|
validate_serial_config(None, 115200, 5)
|
|
|
|
def test_invalid_baudrate(self):
|
|
"""Test error handling for invalid baudrate."""
|
|
with self.assertRaises(ValueError) as cm:
|
|
validate_serial_config("/dev/ttyUSB0", 12345, 5)
|
|
self.assertIn("Baudrate", str(cm.exception))
|
|
|
|
def test_invalid_timeout_negative(self):
|
|
"""Test error handling for negative timeout."""
|
|
with self.assertRaises(ValueError) as cm:
|
|
validate_serial_config("/dev/ttyUSB0", 115200, -1)
|
|
self.assertIn("Timeout", str(cm.exception))
|
|
|
|
def test_invalid_timeout_too_high(self):
|
|
"""Test error handling for timeout too high."""
|
|
with self.assertRaises(ValueError) as cm:
|
|
validate_serial_config("/dev/ttyUSB0", 115200, 500)
|
|
self.assertIn("Timeout", str(cm.exception))
|
|
|
|
def test_valid_baudrates(self):
|
|
"""Test that all common baudrates are accepted."""
|
|
valid_baudrates = [9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600]
|
|
for baudrate in valid_baudrates:
|
|
validate_serial_config("/dev/ttyUSB0", baudrate, 5)
|
|
|
|
|
|
class TestIntegration(unittest.TestCase):
|
|
"""Integration tests combining multiple functions."""
|
|
|
|
def test_full_parsing_pipeline(self):
|
|
"""Test complete parsing pipeline."""
|
|
# Simulate real data from device
|
|
raw_data = "other data SCAN_RESULT 3 [(850000, -110), (851000, -105), (852000, -100)]"
|
|
|
|
# Parse the data
|
|
count, data = parse_scan_result(raw_data)
|
|
|
|
# Verify results
|
|
self.assertEqual(count, 3)
|
|
self.assertEqual(len(data), 3)
|
|
|
|
# Check individual values
|
|
for freq, rssi in data:
|
|
self.assertGreaterEqual(freq, 100000)
|
|
self.assertLessEqual(freq, 6000000)
|
|
self.assertGreaterEqual(rssi, -200)
|
|
self.assertLessEqual(rssi, 0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Run tests with verbose output
|
|
unittest.main(verbosity=2)
|