-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_eeprom.py
304 lines (262 loc) · 10 KB
/
test_eeprom.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
"""EEPROM test module.
This module provides both unit tests for the EEPROM class and a main script
for testing EEPROM hardware. The test script performs the following:
1. Detects EEPROM presence on I2C bus
2. Reads and validates existing content
3. Handles various EEPROM states:
- Blank EEPROM: Performs full refresh with new serial
- Valid UUID: Updates or preserves existing content
- Invalid/corrupt content: Offers refresh option
4. Provides detailed logging of operations
Usage:
1. Run unit tests (no hardware required):
```
# Run all tests
pytest test_eeprom.py -v
# Run specific test
pytest test_eeprom.py -v -k "test_generate_serial_number"
# Run with coverage report
pytest test_eeprom.py -v --cov=eeprom
```
2. Run hardware test (requires physical EEPROM):
```
# Direct execution
python3 test_eeprom.py
# With verbose output
python3 test_eeprom.py --verbose
```
Requirements:
- pytest
- pytest-cov (optional, for coverage reports)
- Physical EEPROM device (for hardware tests only)
- Proper I2C configuration
- Root access for EEPROM operations
"""
import os
import sys
import json
import pytest
from pathlib import Path
from unittest.mock import patch
from eeprom import EEPROM, EEPROMConfig, EEPROMError, MockDigitalOutputDevice
# Add parent directory to path for imports
PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(PARENT_DIR)
# Test Fixtures
@pytest.fixture
def temp_dir(tmp_path):
"""Create temporary directory structure for tests."""
(tmp_path / "tools").mkdir()
(tmp_path / "files").mkdir()
(tmp_path / "json").mkdir()
return tmp_path
@pytest.fixture
def config(temp_dir):
"""Create test configuration."""
return EEPROMConfig(
tools_path=temp_dir / "tools",
files_path=temp_dir / "files",
json_path=temp_dir / "json",
i2c_bus=9,
i2c_address="0x50"
)
@pytest.fixture
def mock_gpio():
"""Mock GPIO device using our MockDigitalOutputDevice."""
# No need to mock since we're using our own mock implementation
return MockDigitalOutputDevice
@pytest.fixture
def eeprom(config):
"""Create EEPROM instance with mocked I2C check."""
with patch('eeprom.EEPROM._check_i2c'):
return EEPROM(config)
# Unit Tests
def test_init(eeprom):
"""Test EEPROM initialization."""
assert eeprom.config.model == "24c32"
assert eeprom.config.size_kbytes == 4
assert eeprom.serial_number is None
assert isinstance(eeprom.image_binary, Path)
assert eeprom.config.i2c_bus == 9
assert eeprom.config.i2c_address == "0x50"
def test_generate_serial_number(eeprom):
"""Test serial number generation."""
serial = eeprom.generate_serial_number()
assert len(serial) == 12
assert all(c in "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789" for c in serial)
@patch('subprocess.run')
def test_read_raw_eeprom_no_device(mock_run, eeprom):
"""Test reading EEPROM when no device is present."""
eeprom.test_result = False
eeprom._read_raw_eeprom()
mock_run.assert_not_called()
@patch('subprocess.run')
def test_read_raw_eeprom_with_device(mock_run, eeprom):
"""Test reading EEPROM when device is present."""
eeprom.test_result = True
mock_run.return_value.returncode = 0
eeprom._read_raw_eeprom()
assert mock_run.call_count == 2
def test_generate_summary(eeprom):
"""Test summary generation."""
summary = eeprom.generate_summary()
assert "eeprom" in summary
assert "serial_number" in summary
assert summary["eeprom"]["model"] == "24c32"
assert summary["eeprom"]["i2c_bus"] == 9
assert summary["eeprom"]["i2c_address"] == "0x50"
@patch('builtins.open', create=True)
def test_parse_eeprom_text_empty(mock_open, eeprom):
"""Test parsing empty EEPROM."""
mock_open.side_effect = OSError
info = eeprom._parse_eeprom_text()
assert info == {}
@patch('builtins.open')
def test_parse_eeprom_text_valid(mock_file_handler, eeprom):
"""Test parsing valid EEPROM content."""
mock_file = mock_file_handler.return_value.__enter__.return_value
# Set up the mock to return lines when iterated over
mock_file.__iter__.return_value = [
"product_uuid 12345678-1234-5678-1234-567812345678\n",
"product_id 0x1234\n",
"product_ver 0x1\n",
'vendor "Test Vendor"\n',
'product "Test Product"\n',
'dt_blob "test_blob"\n',
# First section - JSON format
"custom_data \"\n",
'{"serial_number": "ABC123"}\n',
"\\\"\n",
# Second section - JSON format
"custom_data \"\n",
'{"key": "value"}\n',
"\\\"\n",
# Third section - YAML format (with YAML-specific syntax)
"custom_data \"\n",
'serial_number: XYZ789\nmetadata: {key1: val1, key2: val2}\nlist: [1, 2, 3]\n',
"\\\"\n",
]
# Also set up readline for when it's explicitly called
mock_file.readline.side_effect = [
'{"serial_number": "ABC123"}\n',
"\\\"\n",
'{"key": "value"}\n',
"\\\"\n",
'serial_number: XYZ789\nmetadata: {key1: val1, key2: val2}\nlist: [1, 2, 3]\n',
"\\\"\n",
]
info = eeprom._parse_eeprom_text()
# Verify basic EEPROM fields
assert info["product_uuid"] == "12345678-1234-5678-1234-567812345678"
assert info["product_id"] == "0x1234"
assert info["product_ver"] == "0x1"
assert info["vendor"] == "Test Vendor"
assert info["product"] == "Test Product"
assert info["dt_blob"] == "test_blob"
# Verify custom data sections
assert len(info["custom_data_all"]) == 3
assert isinstance(info["custom_data"], dict) # First section should be parsed as JSON
assert info["custom_data"]["serial_number"] == "ABC123" # First section serial number
assert info["custom_data_all"][0]["serial_number"] == "ABC123" # First section
assert info["custom_data_all"][1]["key"] == "value" # Second section
# Verify YAML-specific content
assert info["custom_data_all"][2]["serial_number"] == "XYZ789"
assert info["custom_data_all"][2]["metadata"] == {"key1": "val1", "key2": "val2"}
assert info["custom_data_all"][2]["list"] == [1, 2, 3]
@patch('builtins.open')
def test_parse_eeprom_text_yaml_fallback(mock_file_handler, eeprom):
"""Test that parsing falls back to YAML when JSON fails."""
mock_file = mock_file_handler.return_value.__enter__.return_value
mock_file.__iter__.return_value = [
"product_uuid 12345678-1234-5678-1234-567812345678\n",
# YAML section with invalid JSON syntax
"custom_data \"\n",
'key1: value1\nkey2: {nested: value2}\n', # This is valid YAML but invalid JSON
"\\\"\n",
]
mock_file.readline.side_effect = [
'key1: value1\nkey2: {nested: value2}\n',
"\\\"\n",
]
info = eeprom._parse_eeprom_text()
assert len(info["custom_data_all"]) == 1
assert isinstance(info["custom_data"], dict)
assert info["custom_data"]["key1"] == "value1"
assert info["custom_data"]["key2"] == {"nested": "value2"}
def test_yaml_import():
"""Test that YAML module is actually available and working."""
try:
import yaml
test_data = "key: value\nlist: [1, 2, 3]"
parsed = yaml.safe_load(test_data)
assert parsed == {"key": "value", "list": [1, 2, 3]}
except ImportError:
pytest.fail("PyYAML is required but not installed")
def test_handle_existing_content_blank(eeprom):
"""Test handling blank EEPROM."""
with patch.object(eeprom, 'refresh') as mock_refresh:
mock_refresh.return_value = True
result = eeprom.handle_existing_content({})
assert result is True
mock_refresh.assert_called_once()
def test_handle_existing_content_zero_uuid(eeprom):
"""Test handling EEPROM with zero UUID."""
with patch.object(eeprom, 'refresh') as mock_refresh:
mock_refresh.return_value = True
result = eeprom.handle_existing_content({
"product_uuid": "00000000-0000-0000-0000-000000000000"
})
assert result is True
mock_refresh.assert_called_once()
@patch('subprocess.run')
def test_make_eeprom_multiple_json(mock_run, eeprom):
"""Test creating EEPROM binary with multiple JSON files."""
json_files = ["config1.json", "config2.json"]
eeprom._make_eeprom(f_json=json_files)
# Check that command was constructed correctly with single -c flag
cmd = mock_run.call_args[0][0]
assert cmd[0].endswith("eepmake")
c_index = cmd.index("-c")
assert len(cmd[c_index:]) == 3 # -c and two json files
assert all(f.endswith(".json") for f in cmd[c_index+1:])
def test_mock_gpio_operations(eeprom):
"""Test mock GPIO operations."""
eeprom.write_protect.off()
assert eeprom.write_protect._state is False
eeprom.write_protect.on()
assert eeprom.write_protect._state is True
# Main Test Script
def run_eeprom_test() -> int:
"""Run EEPROM hardware test.
Returns:
int: 0 for success, 1 for failure
"""
print("Starting EEPROM test...")
try:
eeprom = EEPROM()
if not eeprom.test_result:
print("No EEPROM device detected!")
return 1
print("Reading EEPROM content...")
info = eeprom.read_eeprom_content()
if not info:
print("Failed to read EEPROM content!")
return 1
print(f"EEPROM info: {json.dumps(info, indent=2)}")
if eeprom.handle_existing_content(info):
print("EEPROM test passed!")
return 0
else:
print("EEPROM test failed - could not handle content!")
return 1
except EEPROMError as e:
print(f"EEPROM Error: {e}")
return 1
except Exception as e:
print(f"Unexpected error: {e}")
return 1
except KeyboardInterrupt:
print("\nTest interrupted by user")
return 130
if __name__ == '__main__':
sys.exit(run_eeprom_test())