Skip to content

Commit 948ea2e

Browse files
committed
docs: add optee-utee-writing-unit-tests-with-mocks
1 parent 2f14d6d commit 948ea2e

2 files changed

Lines changed: 275 additions & 0 deletions

File tree

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ permalink: /trustzone-sdk-docs
1212
* [TA Development Modes](ta-development-modes.md)
1313
* [Overview of OP-TEE Rust Examples](overview-of-optee-rust-examples.md)
1414
* [Writing Rust TAs using optee-utee-build](writing-rust-tas-using-optee-utee-build.md)
15+
* [OPTEE-UTEE: Writing Unit Tests with Mocks](optee-utee-writing-unit-tests-with-mocks.md)
1516
* [Building Rust CA as Android ELF](building-rust-ca-as-android-elf.md)
1617

1718
## Advanced Topics
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
---
2+
permalink: /trustzone-sdk-docs/optee-utee-writing-unit-tests-with-mocks
3+
---
4+
5+
# Writing Unit Tests with Mocks for OP-TEE Modules
6+
7+
This guide explains how to write unit tests in `optee-utee` crate using the
8+
built-in `mock` feature of `optee-utee-sys` crate.
9+
10+
The `mock` feature is built on the [`mockall`](https://crates.io/crates/mockall)
11+
crate. It automatically generates mock implementations of the OP-TEE Internal
12+
Core API.
13+
14+
## Running Mock-Based Tests
15+
16+
Mock tests run on the host machine using standard `cargo test`:
17+
18+
```bash
19+
cd crates
20+
cargo test -p optee-utee --features no_panic_handler -vv
21+
```
22+
23+
The `no_panic_handler` feature prevents the custom panic handler from
24+
interfering with test execution.
25+
26+
## Writing Your First Mock Test
27+
28+
### Basic Test Structure
29+
30+
All mock tests follow a consistent pattern:
31+
32+
```rust
33+
#[cfg(test)]
34+
mod tests {
35+
// Required: import std into no_std crate for testing
36+
extern crate std;
37+
38+
use optee_utee_sys::{
39+
mock_api,
40+
mock_utils::SERIAL_TEST_LOCK,
41+
};
42+
use super::*;
43+
44+
#[test]
45+
fn test_my_function() {
46+
// 1. Acquire the serial test lock
47+
let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock");
48+
49+
// 2. Get mock context for the API you want to mock
50+
let ctx = mock_api::TEE_SomeFunction_context();
51+
52+
// 3. Set up expectations
53+
ctx.expect().return_once_st(|params| {
54+
// Return success or specific error code
55+
raw::TEE_SUCCESS
56+
});
57+
58+
// 4. Execute code under test
59+
let result = my_function();
60+
61+
// 5. Assert results
62+
assert!(result.is_ok());
63+
}
64+
}
65+
```
66+
67+
### Key Imports
68+
69+
```rust
70+
use optee_utee_sys::{
71+
mock_api, // Mock API contexts
72+
mock_utils::SERIAL_TEST_LOCK, // Global test lock
73+
mock_utils::object::MockHandle, // Mock object handles
74+
};
75+
use optee_utee_sys as raw; // For TEE_SUCCESS, etc.
76+
```
77+
78+
## Mock Expectation Methods
79+
80+
The `mockall` crate provides several methods for setting up mock behavior:
81+
82+
| Method | Description | Use Case |
83+
|--------|-------------|----------|
84+
| `.return_once_st(value)` | Returns `value` exactly once | Single API call |
85+
| `.returning_st(closure)` | Calls closure for each invocation | Multiple calls with dynamic behavior |
86+
| `.return_const_st(value)` | Always returns constant value | Simple stubbing |
87+
| `.times(n)` | Expects exactly `n` calls | Verify call count |
88+
89+
The `_st` suffix stands for "single-threaded" — required because mockall's
90+
default methods use thread-local storage incompatible with this test setup.
91+
92+
## Common Patterns
93+
94+
### Pattern 1: Testing Success Cases
95+
96+
```rust
97+
#[test]
98+
fn test_open_success() {
99+
let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock");
100+
101+
let mut raw_handle = MockHandle::new();
102+
let handle = raw_handle.as_handle();
103+
104+
let ctx = mock_api::TEE_OpenPersistentObject_context();
105+
ctx.expect()
106+
.return_once_st(move |_, _, _, _, _, obj| {
107+
unsafe { *obj = handle.clone() };
108+
raw::TEE_SUCCESS
109+
});
110+
111+
let result = PersistentObject::open(
112+
ObjectStorageConstants::Private,
113+
&[0x01],
114+
DataFlag::ACCESS_READ,
115+
);
116+
117+
assert!(result.is_ok());
118+
}
119+
```
120+
121+
### Pattern 2: Testing Error Cases
122+
123+
```rust
124+
#[test]
125+
fn test_open_not_found() {
126+
let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock");
127+
128+
static RETURN_CODE: raw::TEE_Result = raw::TEE_ERROR_ITEM_NOT_FOUND;
129+
let ctx = mock_api::TEE_OpenPersistentObject_context();
130+
ctx.expect().return_const_st(RETURN_CODE);
131+
132+
let result = PersistentObject::open(
133+
ObjectStorageConstants::Private,
134+
&[0x01],
135+
DataFlag::ACCESS_READ,
136+
);
137+
138+
assert!(result.is_err());
139+
assert_eq!(result.unwrap_err().raw_code(), RETURN_CODE);
140+
}
141+
```
142+
143+
### Pattern 3: Testing Drop Behavior
144+
145+
```rust
146+
#[test]
147+
fn test_create_and_drop() {
148+
let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock");
149+
150+
let mut raw_handle = MockHandle::new();
151+
let handle = raw_handle.as_handle();
152+
153+
let create_ctx = mock_api::TEE_CreatePersistentObject_context();
154+
let close_ctx = mock_api::TEE_CloseObject_context();
155+
156+
create_ctx.expect()
157+
.return_once_st(move |_, _, _, _, _, _, _, obj| {
158+
unsafe { *obj = handle.clone() };
159+
raw::TEE_SUCCESS
160+
});
161+
162+
close_ctx.expect().return_once_st(move |obj| {
163+
debug_assert_eq!(obj, handle);
164+
});
165+
166+
{
167+
let _obj = PersistentObject::create(
168+
ObjectStorageConstants::Private,
169+
&[],
170+
DataFlag::ACCESS_WRITE,
171+
None,
172+
&[],
173+
).expect("should succeed");
174+
// _obj dropped here, triggering TEE_CloseObject
175+
}
176+
}
177+
```
178+
179+
### Pattern 4: Multiple API Calls
180+
181+
Use `.times(n)` for checking APIs called multiple times:
182+
183+
```rust
184+
use std::sync::{Arc, Mutex};
185+
186+
#[test]
187+
fn test_multiple_calls() {
188+
let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock");
189+
190+
let call_count = Arc::new(Mutex::new(0));
191+
192+
let ctx = mock_api::TEE_SomeApi_context();
193+
ctx.expect()
194+
.returning_st({
195+
let call_count = call_count.clone();
196+
move |_| {
197+
let mut count = call_count.lock().unwrap();
198+
*count += 1;
199+
}
200+
})
201+
.times(3); // Expect exactly 3 calls
202+
203+
// ... execute code that calls the API 3 times
204+
205+
assert_eq!(*call_count.lock().unwrap(), 3);
206+
}
207+
```
208+
209+
### Pattern 5: Buffer Manipulation
210+
211+
For APIs that read/write through pointers, simulate buffer operations:
212+
213+
```rust
214+
#[test]
215+
fn test_read_data() {
216+
let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock");
217+
218+
let expected_data = vec![0xDE, 0xAD, 0xBE, 0xEF];
219+
let ctx = mock_api::TEE_ReadObjectData_context();
220+
221+
ctx.expect()
222+
.return_once_st(move |_, buf, size, count| {
223+
let buffer: &mut [u8] = unsafe {
224+
core::slice::from_raw_parts_mut(buf as *mut u8, size)
225+
};
226+
let len = expected_data.len().min(size);
227+
buffer[..len].copy_from_slice(&expected_data[..len]);
228+
unsafe { *count = len };
229+
raw::TEE_SUCCESS
230+
});
231+
232+
// ... execute read operation
233+
}
234+
```
235+
236+
## Mocking Object Handles
237+
238+
Use `MockHandle` to create mock object handles for testing:
239+
240+
```rust
241+
let mut raw_handle = MockHandle::new();
242+
let handle = raw_handle.as_handle(); // Returns TEE_ObjectHandle
243+
244+
// Pass `handle` to mock expectations that return or compare object handles
245+
```
246+
247+
## The Serial Test Lock
248+
249+
**Always acquire `SERIAL_TEST_LOCK` at the start of every mock test:**
250+
251+
```rust
252+
let _lock = SERIAL_TEST_LOCK.lock().expect("should get the lock");
253+
```
254+
255+
This is **critical** because mockall's mock contexts use global state.
256+
Without this lock, concurrent tests would interfere with each other's
257+
expectations, causing flaky test failures.
258+
259+
260+
## Examples
261+
262+
Study these existing test implementations for more patterns:
263+
264+
| File | Demonstrates |
265+
|------|-------------|
266+
| `crates/optee-utee/src/extension.rs` | Plugin invocation with buffer manipulation |
267+
| `crates/optee-utee/src/object/persistent_object.rs` | Create/open/drop lifecycle, error cases |
268+
| `crates/optee-utee/src/object/transient_object.rs` | Transient object allocation and freeing |
269+
| `crates/optee-utee/src/object/object_handle.rs` | Handle validation and close behavior |
270+
271+
## Limitations
272+
273+
- **Mock tests only verify API call patterns**, not actual cryptographic operations or hardware behavior
274+
- **Tests must run sequentially** — always use `SERIAL_TEST_LOCK`

0 commit comments

Comments
 (0)