Skip to content

Commit 8ad7a4e

Browse files
authored
test(ssd1327): Improve mock scenarios beyond smoke tests. (#301)
* test(ssd1327): Improve mock scenarios beyond smoke tests. * test(ssd1327): Fix Copilot review comments on mock tests.
1 parent c2630d1 commit 8ad7a4e

3 files changed

Lines changed: 211 additions & 11 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ CLAUDE.md
1010
.build/
1111
.venv/
1212
.claude/
13+
.codex

tests/fake_machine/framebuf_stub.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,54 @@ def fill(self, col):
1818
self.buffer[i] = val
1919

2020
def pixel(self, x, y, col=None):
21-
pass
21+
# GS4_HMSB: 2 pixels per byte, high nibble first
22+
idx = (y * self.width + x) // 2
23+
if col is None:
24+
byte = self.buffer[idx]
25+
return (byte >> 4) & 0x0F if x % 2 == 0 else byte & 0x0F
26+
byte = self.buffer[idx]
27+
if x % 2 == 0:
28+
self.buffer[idx] = ((col & 0x0F) << 4) | (byte & 0x0F)
29+
else:
30+
self.buffer[idx] = (byte & 0xF0) | (col & 0x0F)
2231

2332
def text(self, string, x, y, col=15):
2433
pass
2534

2635
def line(self, x1, y1, x2, y2, col):
27-
pass
36+
# Bresenham's line algorithm (simplified)
37+
dx = abs(x2 - x1)
38+
dy = abs(y2 - y1)
39+
sx = 1 if x1 < x2 else -1
40+
sy = 1 if y1 < y2 else -1
41+
err = dx - dy
42+
while True:
43+
self.pixel(x1, y1, col)
44+
if x1 == x2 and y1 == y2:
45+
break
46+
e2 = 2 * err
47+
if e2 > -dy:
48+
err -= dy
49+
x1 += sx
50+
if e2 < dx:
51+
err += dx
52+
y1 += sy
2853

2954
def scroll(self, dx, dy):
30-
pass
55+
# Shift buffer content by (dx, dy) pixels
56+
buf_size = self.width * self.height // 2
57+
new_buf = bytearray(buf_size)
58+
for y in range(self.height):
59+
for x in range(self.width):
60+
src_x = x - dx
61+
src_y = y - dy
62+
if 0 <= src_x < self.width and 0 <= src_y < self.height:
63+
col = self.pixel(src_x, src_y)
64+
# Write to new buffer directly
65+
idx = (y * self.width + x) // 2
66+
byte = new_buf[idx]
67+
if x % 2 == 0:
68+
new_buf[idx] = (col << 4) | (byte & 0x0F)
69+
else:
70+
new_buf[idx] = (byte & 0xF0) | (col & 0x0F)
71+
self.buffer[:] = new_buf

tests/scenarios/ssd1327.yaml

Lines changed: 166 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ hardware_init: |
1818
dev = WS_OLED_128X128_SPI(spi, dc, res, cs)
1919
2020
tests:
21+
# ----- Mock: Smoke tests -----
22+
2123
- name: "Fill black does not crash"
2224
action: call
2325
method: fill
@@ -36,28 +38,184 @@ tests:
3638
args: ["Test", 0, 0, 15]
3739
mode: [mock]
3840

39-
- name: "Power off does not crash"
41+
- name: "Show does not crash"
42+
action: call
43+
method: show
44+
mode: [mock]
45+
46+
# ----- Mock: Verify power_off sends correct commands -----
47+
48+
- name: "Power off sends FN_SELECT_A disable and DISP off"
4049
action: script
4150
script: |
51+
i2c.clear_write_log()
4252
dev.power_off()
43-
result = True
53+
log = i2c.get_write_log()
54+
cmds = [data[1] for reg, data in log if reg is None and len(data) == 2 and data[0] == 0x80]
55+
# Expect: SET_FN_SELECT_A (0xAB), 0x00 (disable VDD), SET_DISP (0xAE)
56+
result = cmds == [0xAB, 0x00, 0xAE]
4457
expect_true: true
4558
mode: [mock]
4659

47-
- name: "Power on does not crash"
60+
# ----- Mock: Verify power_on sends correct commands -----
61+
62+
- name: "Power on sends FN_SELECT_A enable and DISP on"
4863
action: script
4964
script: |
50-
dev.power_off()
65+
i2c.clear_write_log()
5166
dev.power_on()
52-
result = True
67+
log = i2c.get_write_log()
68+
cmds = [data[1] for reg, data in log if reg is None and len(data) == 2 and data[0] == 0x80]
69+
# Expect: SET_FN_SELECT_A (0xAB), 0x01 (enable VDD), SET_DISP|0x01 (0xAF)
70+
result = cmds == [0xAB, 0x01, 0xAF]
5371
expect_true: true
5472
mode: [mock]
5573

56-
- name: "Show does not crash"
57-
action: call
58-
method: show
74+
# ----- Mock: Verify contrast command -----
75+
76+
- name: "Contrast sends SET_CONTRAST with value"
77+
action: script
78+
script: |
79+
i2c.clear_write_log()
80+
dev.contrast(200)
81+
log = i2c.get_write_log()
82+
cmds = [data[1] for reg, data in log if reg is None and len(data) == 2 and data[0] == 0x80]
83+
result = cmds == [0x81, 200]
84+
expect_true: true
85+
mode: [mock]
86+
87+
# ----- Mock: Verify invert commands -----
88+
89+
- name: "Invert on sends SET_DISP_MODE inverted"
90+
action: script
91+
script: |
92+
i2c.clear_write_log()
93+
dev.invert(True)
94+
log = i2c.get_write_log()
95+
cmds = [data[1] for reg, data in log if reg is None and len(data) == 2 and data[0] == 0x80]
96+
# SET_DISP_MODE (0xA4) | 0x03 = 0xA7 (inverted)
97+
result = cmds == [0xA7]
98+
expect_true: true
99+
mode: [mock]
100+
101+
- name: "Invert off sends SET_DISP_MODE normal"
102+
action: script
103+
script: |
104+
i2c.clear_write_log()
105+
dev.invert(False)
106+
log = i2c.get_write_log()
107+
cmds = [data[1] for reg, data in log if reg is None and len(data) == 2 and data[0] == 0x80]
108+
# SET_DISP_MODE (0xA4) = normal
109+
result = cmds == [0xA4]
110+
expect_true: true
59111
mode: [mock]
60112

113+
# ----- Mock: Verify rotate commands -----
114+
115+
- name: "Rotate sends offset and remap commands"
116+
action: script
117+
script: |
118+
i2c.clear_write_log()
119+
dev.rotate(True)
120+
log = i2c.get_write_log()
121+
cmds = [data[1] for reg, data in log if reg is None and len(data) == 2 and data[0] == 0x80]
122+
# power_off + SET_DISP_OFFSET, 128, SET_SEG_REMAP, 0x42 + power_on
123+
expected = [0xAB, 0x00, 0xAE, 0xA2, 128, 0xA0, 0x42, 0xAB, 0x01, 0xAF]
124+
result = cmds == expected
125+
expect_true: true
126+
mode: [mock]
127+
128+
# ----- Mock: Verify show sends column/row addr + data -----
129+
130+
- name: "Show sends column and row address then data"
131+
action: script
132+
script: |
133+
i2c.clear_write_log()
134+
dev.show()
135+
log = i2c.get_write_log()
136+
cmds = [data[1] for reg, data in log if reg is None and len(data) == 2 and data[0] == 0x80]
137+
# SET_COL_ADDR (0x15), col_start, col_end, SET_ROW_ADDR (0x75), row_start, row_end
138+
expected_prefix = [0x15, 0x00, 0x3F, 0x75, 0x00, 0x7F]
139+
# Check data was sent (writevto with 0x40 prefix) with correct payload size
140+
data_writes = [data for reg, data in log if reg is None and len(data) > 2 and data[0] == 0x40]
141+
expected_payload = (dev.width * dev.height) // 2
142+
result = (
143+
cmds[:6] == expected_prefix
144+
and len(data_writes) > 0
145+
and len(data_writes[0]) - 1 == expected_payload
146+
)
147+
expect_true: true
148+
mode: [mock]
149+
150+
# ----- Mock: Verify pixel buffer -----
151+
152+
- name: "Pixel modifies framebuffer"
153+
action: script
154+
script: |
155+
dev.fill(0)
156+
dev.pixel(10, 20, 15)
157+
result = dev.framebuf.pixel(10, 20) == 15
158+
expect_true: true
159+
mode: [mock]
160+
161+
- name: "Fill sets all pixels"
162+
action: script
163+
script: |
164+
dev.fill(7)
165+
result = (
166+
dev.framebuf.pixel(0, 0) == 7
167+
and dev.framebuf.pixel(63, 63) == 7
168+
and dev.framebuf.pixel(127, 0) == 7
169+
and dev.framebuf.pixel(0, 127) == 7
170+
and dev.framebuf.pixel(127, 127) == 7
171+
)
172+
expect_true: true
173+
mode: [mock]
174+
175+
# ----- Mock: Verify init_display sends expected command sequence -----
176+
177+
- name: "Init display sends unlock then display off then display on"
178+
action: script
179+
script: |
180+
i2c.clear_write_log()
181+
dev.init_display()
182+
log = i2c.get_write_log()
183+
cmds = [data[1] for reg, data in log if reg is None and len(data) == 2 and data[0] == 0x80]
184+
# First command must be SET_COMMAND_LOCK (0xFD), then unlock (0x12)
185+
# Then SET_DISP (0xAE = off), and last command must be SET_DISP|0x01 (0xAF = on)
186+
result = cmds[0] == 0xFD and cmds[1] == 0x12 and cmds[2] == 0xAE and cmds[-1] == 0xAF
187+
expect_true: true
188+
mode: [mock]
189+
190+
# ----- Mock: Verify line draws on framebuffer -----
191+
192+
- name: "Line modifies framebuffer"
193+
action: script
194+
script: |
195+
dev.fill(0)
196+
dev.line(0, 0, 10, 0, 15)
197+
# At least the start pixel should be set
198+
result = dev.framebuf.pixel(0, 0) == 15
199+
expect_true: true
200+
mode: [mock]
201+
202+
# ----- Mock: Verify scroll moves framebuffer content -----
203+
204+
- name: "Scroll shifts pixel position"
205+
action: script
206+
script: |
207+
dev.fill(0)
208+
dev.pixel(5, 0, 15)
209+
dev.scroll(1, 0)
210+
# Pixel should have moved from (5,0) to (6,0)
211+
moved = dev.framebuf.pixel(6, 0) == 15
212+
cleared = dev.framebuf.pixel(5, 0) == 0
213+
result = moved and cleared
214+
expect_true: true
215+
mode: [mock]
216+
217+
# ----- Hardware -----
218+
61219
- name: "Display white screen"
62220
action: hardware_script
63221
script: |

0 commit comments

Comments
 (0)