Skip to content

Commit f71009a

Browse files
committed
test/docs: automatically check doc snippets to verity their correctness
1 parent 8b8ca90 commit f71009a

16 files changed

Lines changed: 155 additions & 29 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
run: |
4040
python -m pip install --upgrade pip
4141
python -m pip install --upgrade wheel build
42-
python -m pip install pwntools pytest libdebug
42+
python -m pip install pwntools pytest libdebug mktestdocs
4343
4444
- name: Install library
4545
run: |

SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ Legacy syntax with `array_of()` is still supported:
197197
```python
198198
class packet_t(struct):
199199
length: c_int
200-
data: array_of(c_int, 8)
200+
data: array = array_of(c_int, 8)
201201
```
202202

203203
Access array elements:

docs/advanced/alignment.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ You can opt into natural alignment (matching standard C struct layout) by settin
77
## Enabling Alignment
88

99
```python
10-
from libdestruct import struct, c_char, c_int, c_long, size_of
10+
from libdestruct import struct, c_char, c_int, c_long, c_short, size_of, alignment_of
1111

1212
class packed_t(struct):
1313
a: c_char

docs/advanced/bitfields.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Bitfields let you pack multiple values into a single integer, just like C bitfie
77
Use `bitfield_of(backing_type, bit_width)` as a struct field descriptor:
88

99
```python
10-
from libdestruct import struct, c_uint, bitfield_of
10+
from libdestruct import struct, c_uint, c_long, bitfield_of
1111

1212
class flags_t(struct):
1313
read: c_uint = bitfield_of(c_uint, 1)

docs/advanced/freeze_diff.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ except ValueError:
3232
Use `diff()` to compare the frozen value with the current live value:
3333

3434
```python
35+
memory = bytearray(4)
36+
lib = inflater(memory)
37+
x = lib.inflate(c_int, 0)
38+
3539
x.value = 42
3640
x.freeze()
3741

@@ -96,22 +100,35 @@ except ValueError:
96100
A typical workflow for detecting changes:
97101

98102
```python
103+
class game_state_t(struct):
104+
health: c_int
105+
score: c_int
106+
level: c_int
107+
108+
memory = bytearray(12)
109+
lib = inflater(memory)
110+
99111
# 1. Inflate the struct
100-
state = lib.inflate(game_state_t, addr)
112+
state = lib.inflate(game_state_t, 0)
101113

102114
# 2. Freeze the current state
115+
state.health.value = 100
116+
state.score.value = 500
117+
state.level.value = 3
103118
state.freeze()
104119

105-
# 3. Let the program run (memory changes externally)
106-
# ...
120+
# 3. Something changes the underlying memory
121+
memory[4:8] = (9999).to_bytes(4, "little") # score changed
107122

108123
# 4. Check what changed
109124
for name in ["health", "score", "level"]:
110125
member = getattr(state, name)
111126
old, new = member.diff()
112127
if old != new:
113128
print(f"{name}: {old} -> {new}")
129+
# score: 500 -> 9999
114130

115131
# 5. Optionally reset to the frozen state
116132
state.reset()
133+
print(state.score.value) # 500 (restored)
117134
```

docs/advanced/offset.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,5 @@ class example_t(struct):
102102
items: Annotated[array[c_int, 4], offset(0x10)]
103103
```
104104

105-
With the legacy syntax, `offset()` can be combined with `Field` attributes using a tuple:
106-
107-
```python
108-
from libdestruct.common.field import Field
109-
110-
class example_t(struct):
111-
data: c_int = (Field(), offset(8))
112-
```
113-
114105
!!! note
115-
When using tuples of attributes, only one `Field` is allowed per annotation. Multiple `OffsetAttribute`s are also not typical — use a single `offset()` to set the position.
106+
The `Annotated` syntax is preferred for combining offsets with complex types. The legacy default-value syntax also supports offset combined with other field descriptors using a tuple (e.g., `data: c_int = (enum_of(Color), offset(8))`).

docs/advanced/tagged_unions.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ size_of(msg_t) # 12 (4 + max(4, 8))
153153
Use the `variant` property to get the active variant object directly:
154154

155155
```python
156+
data = pystruct.pack("<i", 0) + pystruct.pack("<i", 42) + b"\x00" * 4
156157
msg = message_t.from_bytes(data)
157158
variant_obj = msg.payload.variant # the raw c_int, c_float, etc.
158159
```
@@ -168,7 +169,10 @@ class msg_t(struct):
168169

169170
# type=99 has no matching variant
170171
memory = pystruct.pack("<i", 99) + b"\x00" * 4
171-
msg_t.from_bytes(memory) # raises ValueError
172+
try:
173+
msg_t.from_bytes(memory)
174+
except ValueError:
175+
print("ValueError: unknown discriminator")
172176
```
173177

174178
!!! info

docs/basics/arrays.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ The legacy `array_of()` syntax is also supported:
9494
from libdestruct import struct, c_int, array_of
9595

9696
class matrix_row_t(struct):
97-
values: array_of(c_int, 4)
97+
values: array = array_of(c_int, 4)
9898
```
9999

100100
```python

docs/basics/pointers.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,27 +153,27 @@ p2 = p_raw + 4 # advances by 4 bytes
153153
Pointer dereferencing is cached — repeated calls to `unwrap()` or `try_unwrap()` return the same object without re-inflating:
154154

155155
```python
156-
r1 = node.next.unwrap()
157-
r2 = node.next.unwrap()
156+
r1 = head.next.unwrap()
157+
r2 = head.next.unwrap()
158158
assert r1 is r2 # same object
159159
```
160160

161161
If the underlying memory changes, call `invalidate()` to clear the cache:
162162

163163
```python
164164
# Memory was modified externally
165-
node.next.invalidate()
166-
r3 = node.next.unwrap() # re-inflated from updated memory
165+
head.next.invalidate()
166+
r3 = head.next.unwrap() # re-inflated from updated memory
167167
```
168168

169169
Setting the pointer's value automatically invalidates the cache:
170170

171171
```python
172-
node.next.value = new_address # cache cleared automatically
172+
head.next.value = 24 # cache cleared automatically
173173
```
174174

175175
## Pointer String Representation
176176

177177
```python
178-
print(data.next.to_str()) # "c_int@0xc" (or "ptr@0xc" for untyped)
178+
print(data.next.to_str()) # "c_int@0xc"
179179
```

docs/basics/structs.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
Structs are the core building block for describing binary layouts. Define a struct by subclassing `struct` and using type annotations:
44

55
```python
6-
from libdestruct import struct, c_int, c_long
6+
from libdestruct import struct, c_int, c_uint, c_long
77

88
class header_t(struct):
9-
magic: c_int
9+
magic: c_uint
1010
version: c_int
1111
size: c_long
1212
```
@@ -40,6 +40,10 @@ print(f"size: {header.size.value}") # size: 4096
4040
Each struct field is an `obj` instance. Use `.value` to read or write the underlying value:
4141

4242
```python
43+
memory = bytearray(16)
44+
lib = inflater(memory)
45+
header = lib.inflate(header_t, 0)
46+
4347
header.magic.value = 0xcafebabe
4448
print(f"0x{header.magic.value:08x}") # 0xcafebabe
4549
```

0 commit comments

Comments
 (0)