aboutsummaryrefslogtreecommitdiff
path: root/embassy-stm32/src/dma/ringbuffer/tests
diff options
context:
space:
mode:
authorAlexandros Liarokapis <[email protected]>2024-09-15 18:47:33 +0300
committerAlexandros Liarokapis <[email protected]>2024-10-15 12:29:12 +0300
commit2b10caafd488987b6cf9a2cd69820c81d0a0826e (patch)
treeef5eac794f99033898b9580623d86422b055f2d7 /embassy-stm32/src/dma/ringbuffer/tests
parent4f08d5bc5ff0f765198665b1f37b6372f43b8567 (diff)
stm32: initial support for alternative ringbuffer implementation
Diffstat (limited to 'embassy-stm32/src/dma/ringbuffer/tests')
-rw-r--r--embassy-stm32/src/dma/ringbuffer/tests/mod.rs165
-rw-r--r--embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs50
-rw-r--r--embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs122
-rw-r--r--embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs121
4 files changed, 458 insertions, 0 deletions
diff --git a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs
new file mode 100644
index 000000000..9768e1df8
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs
@@ -0,0 +1,165 @@
1use std::{cell, vec};
2
3use super::*;
4
5#[allow(dead_code)]
6#[derive(PartialEq, Debug)]
7enum TestCircularTransferRequest {
8 ResetCompleteCount(usize),
9 PositionRequest(usize),
10}
11
12struct TestCircularTransfer {
13 len: usize,
14 requests: cell::RefCell<vec::Vec<TestCircularTransferRequest>>,
15}
16
17impl DmaCtrl for TestCircularTransfer {
18 fn get_remaining_transfers(&self) -> usize {
19 match self.requests.borrow_mut().pop().unwrap() {
20 TestCircularTransferRequest::PositionRequest(pos) => {
21 let len = self.len;
22
23 assert!(len >= pos);
24
25 len - pos
26 }
27 _ => unreachable!(),
28 }
29 }
30
31 fn reset_complete_count(&mut self) -> usize {
32 match self.requests.get_mut().pop().unwrap() {
33 TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count,
34 _ => unreachable!(),
35 }
36 }
37
38 fn set_waker(&mut self, _waker: &Waker) {}
39}
40
41impl TestCircularTransfer {
42 pub fn new(len: usize) -> Self {
43 Self {
44 requests: cell::RefCell::new(vec![]),
45 len,
46 }
47 }
48
49 pub fn setup(&self, mut requests: vec::Vec<TestCircularTransferRequest>) {
50 requests.reverse();
51 self.requests.replace(requests);
52 }
53}
54
55const CAP: usize = 16;
56
57#[test]
58fn dma_index_dma_sync_syncs_position_to_last_read_if_sync_takes_place_on_same_dma_cycle() {
59 let mut dma = TestCircularTransfer::new(CAP);
60 dma.setup(vec![
61 TestCircularTransferRequest::PositionRequest(4),
62 TestCircularTransferRequest::ResetCompleteCount(0),
63 TestCircularTransferRequest::PositionRequest(7),
64 ]);
65 let mut index = DmaIndex::default();
66 index.dma_sync(CAP, &mut dma);
67 assert_eq!(index.completion_count, 0);
68 assert_eq!(index.pos, 7);
69}
70
71#[test]
72fn dma_index_dma_sync_updates_completion_count_properly_if_sync_takes_place_on_same_dma_cycle() {
73 let mut dma = TestCircularTransfer::new(CAP);
74 dma.setup(vec![
75 TestCircularTransferRequest::PositionRequest(4),
76 TestCircularTransferRequest::ResetCompleteCount(2),
77 TestCircularTransferRequest::PositionRequest(7),
78 ]);
79 let mut index = DmaIndex::default();
80 index.completion_count = 1;
81 index.dma_sync(CAP, &mut dma);
82 assert_eq!(index.completion_count, 3);
83 assert_eq!(index.pos, 7);
84}
85
86#[test]
87fn dma_index_dma_sync_syncs_to_last_position_if_reads_occur_on_different_dma_cycles() {
88 let mut dma = TestCircularTransfer::new(CAP);
89 dma.setup(vec![
90 TestCircularTransferRequest::PositionRequest(10),
91 TestCircularTransferRequest::ResetCompleteCount(1),
92 TestCircularTransferRequest::PositionRequest(5),
93 TestCircularTransferRequest::ResetCompleteCount(0),
94 ]);
95 let mut index = DmaIndex::default();
96 index.dma_sync(CAP, &mut dma);
97 assert_eq!(index.completion_count, 1);
98 assert_eq!(index.pos, 5);
99}
100
101#[test]
102fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_completion_count_occurs_on_first_cycle(
103) {
104 let mut dma = TestCircularTransfer::new(CAP);
105 dma.setup(vec![
106 TestCircularTransferRequest::PositionRequest(10),
107 TestCircularTransferRequest::ResetCompleteCount(1),
108 TestCircularTransferRequest::PositionRequest(5),
109 TestCircularTransferRequest::ResetCompleteCount(1),
110 ]);
111 let mut index = DmaIndex::default();
112 index.completion_count = 1;
113 index.dma_sync(CAP, &mut dma);
114 assert_eq!(index.completion_count, 3);
115 assert_eq!(index.pos, 5);
116}
117
118#[test]
119fn dma_index_dma_sync_detects_new_cycle_if_later_position_is_less_than_first_and_first_completion_count_occurs_on_later_cycle(
120) {
121 let mut dma = TestCircularTransfer::new(CAP);
122 dma.setup(vec![
123 TestCircularTransferRequest::PositionRequest(10),
124 TestCircularTransferRequest::ResetCompleteCount(2),
125 TestCircularTransferRequest::PositionRequest(5),
126 TestCircularTransferRequest::ResetCompleteCount(0),
127 ]);
128 let mut index = DmaIndex::default();
129 index.completion_count = 1;
130 index.dma_sync(CAP, &mut dma);
131 assert_eq!(index.completion_count, 3);
132 assert_eq!(index.pos, 5);
133}
134
135#[test]
136fn dma_index_as_index_returns_index_mod_cap_by_default() {
137 let index = DmaIndex::default();
138 assert_eq!(index.as_index(CAP, 0), 0);
139 assert_eq!(index.as_index(CAP, 1), 1);
140 assert_eq!(index.as_index(CAP, 2), 2);
141 assert_eq!(index.as_index(CAP, 3), 3);
142 assert_eq!(index.as_index(CAP, 4), 4);
143 assert_eq!(index.as_index(CAP, CAP), 0);
144 assert_eq!(index.as_index(CAP, CAP + 1), 1);
145}
146
147#[test]
148fn dma_index_advancing_increases_as_index() {
149 let mut index = DmaIndex::default();
150 assert_eq!(index.as_index(CAP, 0), 0);
151 index.advance(CAP, 1);
152 assert_eq!(index.as_index(CAP, 0), 1);
153 index.advance(CAP, 1);
154 assert_eq!(index.as_index(CAP, 0), 2);
155 index.advance(CAP, 1);
156 assert_eq!(index.as_index(CAP, 0), 3);
157 index.advance(CAP, 1);
158 assert_eq!(index.as_index(CAP, 0), 4);
159 index.advance(CAP, CAP - 4);
160 assert_eq!(index.as_index(CAP, 0), 0);
161 index.advance(CAP, 1);
162 assert_eq!(index.as_index(CAP, 0), 1);
163}
164
165mod prop_test;
diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs
new file mode 100644
index 000000000..661fb1728
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs
@@ -0,0 +1,50 @@
1use std::task::Waker;
2
3use proptest::prop_oneof;
4use proptest::strategy::{self, BoxedStrategy, Strategy as _};
5use proptest_state_machine::{prop_state_machine, ReferenceStateMachine, StateMachineTest};
6
7use super::*;
8
9const CAP: usize = 128;
10
11#[derive(Debug, Default)]
12struct DmaMock {
13 pos: usize,
14 wraps: usize,
15}
16
17impl DmaMock {
18 pub fn advance(&mut self, steps: usize) {
19 let next = self.pos + steps;
20 self.pos = next % CAP;
21 self.wraps += next / CAP;
22 }
23}
24
25impl DmaCtrl for DmaMock {
26 fn get_remaining_transfers(&self) -> usize {
27 CAP - self.pos
28 }
29
30 fn reset_complete_count(&mut self) -> usize {
31 core::mem::replace(&mut self.wraps, 0)
32 }
33
34 fn set_waker(&mut self, _waker: &Waker) {}
35}
36
37#[derive(Debug, Clone)]
38enum Status {
39 Available(usize),
40 Failed,
41}
42
43impl Status {
44 pub fn new(capacity: usize) -> Self {
45 Self::Available(capacity)
46 }
47}
48
49mod reader;
50mod writer;
diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs
new file mode 100644
index 000000000..6555ebfb0
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs
@@ -0,0 +1,122 @@
1use core::fmt::Debug;
2
3use super::*;
4
5#[derive(Debug, Clone)]
6enum ReaderTransition {
7 Write(usize),
8 Clear,
9 ReadUpTo(usize),
10}
11
12struct ReaderSM;
13
14impl ReferenceStateMachine for ReaderSM {
15 type State = Status;
16 type Transition = ReaderTransition;
17
18 fn init_state() -> BoxedStrategy<Self::State> {
19 strategy::Just(Status::new(0)).boxed()
20 }
21
22 fn transitions(_state: &Self::State) -> BoxedStrategy<Self::Transition> {
23 prop_oneof![
24 (1..50_usize).prop_map(ReaderTransition::Write),
25 (1..50_usize).prop_map(ReaderTransition::ReadUpTo),
26 strategy::Just(ReaderTransition::Clear),
27 ]
28 .boxed()
29 }
30
31 fn apply(status: Self::State, transition: &Self::Transition) -> Self::State {
32 match (status, transition) {
33 (_, ReaderTransition::Clear) => Status::Available(0),
34 (Status::Available(x), ReaderTransition::Write(y)) => {
35 if x + y > CAP {
36 Status::Failed
37 } else {
38 Status::Available(x + y)
39 }
40 }
41 (Status::Available(x), ReaderTransition::ReadUpTo(y)) => Status::Available(x.saturating_sub(*y)),
42 (Status::Failed, _) => Status::Failed,
43 }
44 }
45}
46
47struct ReaderSut {
48 status: Status,
49 buffer: *mut [u8],
50 producer: DmaMock,
51 consumer: ReadableDmaRingBuffer<'static, u8>,
52}
53
54impl Debug for ReaderSut {
55 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
56 <DmaMock as Debug>::fmt(&self.producer, f)
57 }
58}
59
60struct ReaderTest;
61
62impl StateMachineTest for ReaderTest {
63 type SystemUnderTest = ReaderSut;
64 type Reference = ReaderSM;
65
66 fn init_test(ref_status: &<Self::Reference as ReferenceStateMachine>::State) -> Self::SystemUnderTest {
67 let buffer = Box::into_raw(Box::new([0; CAP]));
68 ReaderSut {
69 status: ref_status.clone(),
70 buffer,
71 producer: DmaMock::default(),
72 consumer: ReadableDmaRingBuffer::new(unsafe { &mut *buffer }),
73 }
74 }
75
76 fn teardown(state: Self::SystemUnderTest) {
77 unsafe {
78 let _ = Box::from_raw(state.buffer);
79 };
80 }
81
82 fn apply(
83 mut sut: Self::SystemUnderTest,
84 ref_state: &<Self::Reference as ReferenceStateMachine>::State,
85 transition: <Self::Reference as ReferenceStateMachine>::Transition,
86 ) -> Self::SystemUnderTest {
87 match transition {
88 ReaderTransition::Write(x) => sut.producer.advance(x),
89 ReaderTransition::Clear => {
90 sut.consumer.clear(&mut sut.producer);
91 }
92 ReaderTransition::ReadUpTo(x) => {
93 let status = sut.status;
94 let ReaderSut {
95 ref mut producer,
96 ref mut consumer,
97 ..
98 } = sut;
99 let mut buf = vec![0; x];
100 let res = consumer.read(producer, &mut buf);
101 match status {
102 Status::Available(n) => {
103 let readable = x.min(n);
104
105 assert_eq!(res.unwrap().0, readable);
106 }
107 Status::Failed => assert!(res.is_err()),
108 }
109 }
110 }
111
112 ReaderSut {
113 status: ref_state.clone(),
114 ..sut
115 }
116 }
117}
118
119prop_state_machine! {
120 #[test]
121 fn reader_state_test(sequential 1..20 => ReaderTest);
122}
diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs
new file mode 100644
index 000000000..15f54c672
--- /dev/null
+++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs
@@ -0,0 +1,121 @@
1use core::fmt::Debug;
2
3use super::*;
4
5#[derive(Debug, Clone)]
6enum WriterTransition {
7 Read(usize),
8 WriteUpTo(usize),
9 Clear,
10}
11
12struct WriterSM;
13
14impl ReferenceStateMachine for WriterSM {
15 type State = Status;
16 type Transition = WriterTransition;
17
18 fn init_state() -> BoxedStrategy<Self::State> {
19 strategy::Just(Status::new(CAP)).boxed()
20 }
21
22 fn transitions(_state: &Self::State) -> BoxedStrategy<Self::Transition> {
23 prop_oneof![
24 (1..50_usize).prop_map(WriterTransition::Read),
25 (1..50_usize).prop_map(WriterTransition::WriteUpTo),
26 strategy::Just(WriterTransition::Clear),
27 ]
28 .boxed()
29 }
30
31 fn apply(status: Self::State, transition: &Self::Transition) -> Self::State {
32 match (status, transition) {
33 (_, WriterTransition::Clear) => Status::Available(CAP),
34 (Status::Available(x), WriterTransition::Read(y)) => {
35 if x < *y {
36 Status::Failed
37 } else {
38 Status::Available(x - y)
39 }
40 }
41 (Status::Available(x), WriterTransition::WriteUpTo(y)) => Status::Available((x + *y).min(CAP)),
42 (Status::Failed, _) => Status::Failed,
43 }
44 }
45}
46
47struct WriterSut {
48 status: Status,
49 buffer: *mut [u8],
50 producer: WritableDmaRingBuffer<'static, u8>,
51 consumer: DmaMock,
52}
53
54impl Debug for WriterSut {
55 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
56 <DmaMock as Debug>::fmt(&self.consumer, f)
57 }
58}
59
60struct WriterTest;
61
62impl StateMachineTest for WriterTest {
63 type SystemUnderTest = WriterSut;
64 type Reference = WriterSM;
65
66 fn init_test(ref_status: &<Self::Reference as ReferenceStateMachine>::State) -> Self::SystemUnderTest {
67 let buffer = Box::into_raw(Box::new([0; CAP]));
68 WriterSut {
69 status: ref_status.clone(),
70 buffer,
71 producer: WritableDmaRingBuffer::new(unsafe { &mut *buffer }),
72 consumer: DmaMock::default(),
73 }
74 }
75
76 fn teardown(state: Self::SystemUnderTest) {
77 unsafe {
78 let _ = Box::from_raw(state.buffer);
79 };
80 }
81
82 fn apply(
83 mut sut: Self::SystemUnderTest,
84 ref_status: &<Self::Reference as ReferenceStateMachine>::State,
85 transition: <Self::Reference as ReferenceStateMachine>::Transition,
86 ) -> Self::SystemUnderTest {
87 match transition {
88 WriterTransition::Read(x) => sut.consumer.advance(x),
89 WriterTransition::Clear => {
90 sut.producer.clear(&mut sut.consumer);
91 }
92 WriterTransition::WriteUpTo(x) => {
93 let status = sut.status;
94 let WriterSut {
95 ref mut producer,
96 ref mut consumer,
97 ..
98 } = sut;
99 let mut buf = vec![0; x];
100 let res = producer.write(consumer, &mut buf);
101 match status {
102 Status::Available(n) => {
103 let writable = x.min(CAP - n.min(CAP));
104 assert_eq!(res.unwrap().0, writable);
105 }
106 Status::Failed => assert!(res.is_err()),
107 }
108 }
109 }
110
111 WriterSut {
112 status: ref_status.clone(),
113 ..sut
114 }
115 }
116}
117
118prop_state_machine! {
119 #[test]
120 fn writer_state_test(sequential 1..20 => WriterTest);
121}