Skip to main content

globally_ordered_mock_mmio/
trace_builder.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! The expectation-declaring API handed to `expect()` closures.
6
7use core::panic::Location;
8use mmio::{
9    IndexedRegister, ReadableIndexedRegister, ReadableRegister, WritableIndexedRegister,
10    WritableRegister,
11};
12
13use crate::data_access::{AccessData, AccessSize};
14use crate::expectation::ExpectationPattern;
15use crate::mmio_operand_value;
16use crate::operation::CompletedOp;
17use crate::scoreboard::Scoreboard;
18use crate::source_info::{ExpectationSourceInfo, RegisterRef};
19
20/// Builder for recording expected MMIO access sequences within a closure.
21///
22/// Adopts a receiver-based closure architecture (`mock.expect(|t| { ...
23/// })`) where expectations are posted to the [`Scoreboard`] via standard Rust
24/// method calls.
25///
26/// The syntax is intended to work seamlessly with standard Rust tooling
27/// (`rustfmt`, `rust-analyzer`, go-to-definition, and autocomplete). The raw
28/// offset methods are spelled out one per access width, rather than generated
29/// by a macro, so that autocomplete lists them.
30///
31/// Each expectation method automatically captures the caller source location
32/// ([`Location::caller`]). This information is included in the diagnostic
33/// information produced by test failures.
34#[derive(Debug)]
35pub struct ExpectedTraceBuilder<'a> {
36    scoreboard: &'a Scoreboard,
37}
38
39impl<'a> ExpectedTraceBuilder<'a> {
40    pub(crate) fn new(scoreboard: &'a Scoreboard) -> Self {
41        Self { scoreboard }
42    }
43
44    // -------------------------------------------------------------------------
45    // Typed register methods. Offset and width are derived from R.
46    //
47    // The methods take `&mut self` even though the scoreboard uses interior
48    // mutability, so that a trace closure cannot interleave expectations from
49    // two aliasing builders.
50    // -------------------------------------------------------------------------
51
52    /// Posts an expectation for a read from register `R`, returning `value`.
53    ///
54    /// The register byte offset (`R::OFFSET`) and access width
55    /// (`AccessSize::of::<R::Value>()`) are automatically deduced from the
56    /// [`ReadableRegister`] definition.
57    #[track_caller]
58    pub fn read<R: ReadableRegister>(&mut self, value: R::Value) {
59        self.post_read(
60            R::OFFSET,
61            Self::register_data::<R::Value>(value),
62            Some(RegisterRef::new(std::any::type_name::<R>())),
63            Location::caller(),
64        );
65    }
66
67    /// Posts an expectation for a write of `value` to register `R`.
68    ///
69    /// The register byte offset (`R::OFFSET`) and access width
70    /// (`AccessSize::of::<R::Value>()`) are automatically deduced from the
71    /// [`WritableRegister`] definition.
72    #[track_caller]
73    pub fn write<R: WritableRegister>(&mut self, value: R::Value) {
74        self.post_write(
75            R::OFFSET,
76            Self::register_data::<R::Value>(value),
77            Some(RegisterRef::new(std::any::type_name::<R>())),
78            Location::caller(),
79        );
80    }
81
82    /// Posts an expectation for reading `value` from register `R` at `index`.
83    ///
84    /// The byte offset is `R::BASE_OFFSET + index * R::STRIDE`.
85    ///
86    /// # Panics
87    ///
88    /// Panics if `index` is greater than or equal to `R::COUNT`.
89    #[track_caller]
90    pub fn read_indexed<R: ReadableIndexedRegister>(&mut self, index: usize, value: R::Value) {
91        self.post_read(
92            Self::indexed_offset::<R>(index),
93            Self::register_data::<R::Value>(value),
94            Some(RegisterRef::indexed(std::any::type_name::<R>(), index)),
95            Location::caller(),
96        );
97    }
98
99    /// Posts an expectation for a write of `value` to register `R` at `index`.
100    ///
101    /// The byte offset is `R::BASE_OFFSET + index * R::STRIDE`.
102    ///
103    /// # Panics
104    ///
105    /// Panics if `index` is greater than or equal to `R::COUNT`.
106    #[track_caller]
107    pub fn write_indexed<R: WritableIndexedRegister>(&mut self, index: usize, value: R::Value) {
108        self.post_write(
109            Self::indexed_offset::<R>(index),
110            Self::register_data::<R::Value>(value),
111            Some(RegisterRef::indexed(std::any::type_name::<R>(), index)),
112            Location::caller(),
113        );
114    }
115
116    /// Posts an expectation for successive reads to register `R` returning `values`.
117    ///
118    /// Models deterministic polling sequences (e.g. status polling until ready):
119    /// * Requires the driver to read each value in the sequence at least once
120    ///   in order.
121    /// * Once the sequence is exhausted, subsequent reads return the last
122    ///   value.
123    /// * The expectation automatically retires when the driver performs the
124    ///   next non-matching access (e.g., writing the next configuration
125    ///   register).
126    ///
127    /// # Panics
128    ///
129    /// Panics if `values` is empty.
130    #[track_caller]
131    pub fn poll<R: ReadableRegister>(&mut self, values: impl IntoIterator<Item = R::Value>) {
132        let values = Self::raw_values::<R::Value>(values, "poll()");
133        self.post_poll(
134            R::OFFSET,
135            &values,
136            AccessSize::of::<R::Value>(),
137            Some(RegisterRef::new(std::any::type_name::<R>())),
138            Location::caller(),
139        );
140    }
141
142    /// Posts an expectation for reading `busy_value` from register `R` one or more times.
143    ///
144    /// Models indefinite polling retry loops (e.g. testing driver timeout and error recovery):
145    ///
146    /// * Requires the driver to read `busy_value` at least once.
147    /// * Returns `busy_value` for any subsequent reads while the driver executes its retry loop.
148    /// * The expectation automatically retires when the driver ceases polling and initiates
149    ///   timeout recovery (e.g., writing a reset command).
150    #[track_caller]
151    pub fn poll_indefinitely<R: ReadableRegister>(&mut self, busy_value: R::Value) {
152        self.post_poll_indefinitely(
153            R::OFFSET,
154            Self::register_data::<R::Value>(busy_value),
155            Some(RegisterRef::new(std::any::type_name::<R>())),
156            Location::caller(),
157        );
158    }
159
160    /// Posts an expectation for reading values` from register `R` at `index`.
161    ///
162    /// The byte offset is `R::BASE_OFFSET + index * R::STRIDE`.
163    ///
164    /// # Panics
165    ///
166    /// Panics if `index` is greater than or equal to `R::COUNT`, or if `values` is empty.
167    #[track_caller]
168    pub fn poll_indexed<R: ReadableIndexedRegister>(
169        &mut self,
170        index: usize,
171        values: impl IntoIterator<Item = R::Value>,
172    ) {
173        let offset = Self::indexed_offset::<R>(index);
174        let values = Self::raw_values::<R::Value>(values, "poll_indexed()");
175        self.post_poll(
176            offset,
177            &values,
178            AccessSize::of::<R::Value>(),
179            Some(RegisterRef::indexed(std::any::type_name::<R>(), index)),
180            Location::caller(),
181        );
182    }
183
184    /// Posts an expectation for reading `busy_value` from register `R` at `index` one or more times.
185    ///
186    /// The byte offset is `R::BASE_OFFSET + index * R::STRIDE`.
187    ///
188    /// # Panics
189    ///
190    /// Panics if `index` is greater than or equal to `R::COUNT`.
191    #[track_caller]
192    pub fn poll_indefinitely_indexed<R: ReadableIndexedRegister>(
193        &mut self,
194        index: usize,
195        busy_value: R::Value,
196    ) {
197        self.post_poll_indefinitely(
198            Self::indexed_offset::<R>(index),
199            Self::register_data::<R::Value>(busy_value),
200            Some(RegisterRef::indexed(std::any::type_name::<R>(), index)),
201            Location::caller(),
202        );
203    }
204
205    // -------------------------------------------------------------------------
206    // Raw offset methods, with an explicit access width.
207    //
208    // Ergonomic fallbacks for interacting with un-typed register spaces or raw
209    // offsets where formal Register definitions are not available.
210    // -------------------------------------------------------------------------
211
212    /// Selects a raw byte `offset` for declaring expectations on un-typed MMIO
213    /// registers.
214    ///
215    /// Returns a [`RawOffsetTraceBuilder`] providing width-annotated access
216    /// methods (`read8`..`read64`, `write8`..`write64`, `poll8`..`poll64`, and
217    /// `poll_indefinitely8`..`poll_indefinitely64`).
218    pub fn at(&mut self, offset: usize) -> RawOffsetTraceBuilder<'a, '_> {
219        RawOffsetTraceBuilder { trace_builder: self, offset }
220    }
221
222    /// Posts an expectation for an MMIO memory write barrier ([`mmio::Mmio::write_barrier`]).
223    #[track_caller]
224    pub fn write_barrier(&mut self) {
225        self.scoreboard.post_expectation(
226            ExpectationPattern::MustMatchOnce(CompletedOp::WriteBarrier),
227            ExpectationSourceInfo::new(None, Location::caller()),
228        );
229    }
230
231    // -------------------------------------------------------------------------
232    // Internal helpers
233    // -------------------------------------------------------------------------
234
235    /// Returns the byte offset of element `index` of the indexed register `R`.
236    ///
237    /// # Panics
238    ///
239    /// Panics if `index` is greater than or equal to `R::COUNT`.
240    #[track_caller]
241    fn indexed_offset<R: IndexedRegister>(index: usize) -> usize {
242        assert!(index < R::COUNT, "Register index {index} out of bounds (count: {})", R::COUNT);
243        R::BASE_OFFSET + index * R::STRIDE
244    }
245
246    /// Returns the data transferred by an access to a register holding a `T`.
247    fn register_data<T: mmio::MmioOperand>(value: T) -> AccessData {
248        AccessData::new(AccessSize::of::<T>(), mmio_operand_value::mmio_operand_to_u64(value))
249    }
250
251    /// Collects the return values of a polling sequence.
252    ///
253    /// # Panics
254    ///
255    /// Panics if `values` is empty, naming `caller` in the message.
256    #[track_caller]
257    fn raw_values<T: mmio::MmioOperand>(
258        values: impl IntoIterator<Item = T>,
259        caller: &str,
260    ) -> Vec<u64> {
261        let values: Vec<u64> =
262            values.into_iter().map(mmio_operand_value::mmio_operand_to_u64).collect();
263        assert!(!values.is_empty(), "{caller} requires at least one return value");
264        values
265    }
266
267    #[track_caller]
268    fn post_read(
269        &mut self,
270        offset: usize,
271        data: AccessData,
272        register: Option<RegisterRef>,
273        location: &'static Location<'static>,
274    ) {
275        self.scoreboard.post_expectation(
276            ExpectationPattern::MustMatchOnce(CompletedOp::Read { offset, data }),
277            ExpectationSourceInfo::new(register, location),
278        );
279    }
280
281    #[track_caller]
282    fn post_write(
283        &mut self,
284        offset: usize,
285        data: AccessData,
286        register: Option<RegisterRef>,
287        location: &'static Location<'static>,
288    ) {
289        self.scoreboard.post_expectation(
290            ExpectationPattern::MustMatchOnce(CompletedOp::Write { offset, data }),
291            ExpectationSourceInfo::new(register, location),
292        );
293    }
294
295    /// Expands a polling sequence into one expectation per value, plus a
296    /// trailing repeating expectation for the last value.
297    ///
298    /// # Panics
299    ///
300    /// Panics if `values` is empty.
301    #[track_caller]
302    fn post_poll(
303        &mut self,
304        offset: usize,
305        values: &[u64],
306        size: AccessSize,
307        register: Option<RegisterRef>,
308        location: &'static Location<'static>,
309    ) {
310        let (&last_value, _) = values.split_last().expect("values must not be empty");
311
312        // Sequence polling requires each value in the sequence to be read at
313        // least once, in the specified order.
314        for &value in values {
315            self.post_read(offset, AccessData::new(size, value), register.clone(), location);
316        }
317
318        // Once the sequence completes, subsequent reads return the last value
319        // until the driver performs a different access, which retires this
320        // repeating expectation.
321        self.scoreboard.post_expectation(
322            ExpectationPattern::WhileMatches(CompletedOp::Read {
323                offset,
324                data: AccessData::new(size, last_value),
325            }),
326            ExpectationSourceInfo::new(register, location),
327        );
328    }
329
330    /// Expands an indefinite poll into one required read plus a repeating
331    /// expectation for the same value.
332    #[track_caller]
333    fn post_poll_indefinitely(
334        &mut self,
335        offset: usize,
336        data: AccessData,
337        register: Option<RegisterRef>,
338        location: &'static Location<'static>,
339    ) {
340        // The driver must read the busy value at least once before timing out.
341        self.post_read(offset, data, register.clone(), location);
342
343        // Subsequent reads keep returning the busy value until the driver
344        // ceases polling and initiates recovery (e.g. reset), which retires
345        // this repeating expectation.
346        self.scoreboard.post_expectation(
347            ExpectationPattern::WhileMatches(CompletedOp::Read { offset, data }),
348            ExpectationSourceInfo::new(register, location),
349        );
350    }
351}
352
353/// Builder for recording expected MMIO accesses at a fixed raw byte offset.
354///
355/// Returned by [`ExpectedTraceBuilder::at`].
356///
357/// Operations on [`RawOffsetTraceBuilder`] return `&mut Self`, allowing
358/// read-modify-write sequences at the same offset to be chained:
359///
360/// ```
361/// # use globally_ordered_mock_mmio::MockMmioRegionBuilder;
362/// # use mmio::Mmio;
363/// let mock_builder = MockMmioRegionBuilder::new(0x1000);
364/// mock_builder.expect(|t| {
365///     t.at(0x08).read32(0x0000_0001).write32(0x0000_0003);
366/// });
367/// let mut mmio = mock_builder.build();
368/// assert_eq!(mmio.load32(0x08), 0x0000_0001);
369/// mmio.store32(0x08, 0x0000_0003);
370/// ```
371#[derive(Debug)]
372pub struct RawOffsetTraceBuilder<'a, 'b> {
373    trace_builder: &'b mut ExpectedTraceBuilder<'a>,
374    offset: usize,
375}
376
377impl<'a, 'b> RawOffsetTraceBuilder<'a, 'b> {
378    /// Posts an expectation for an 8-bit read from this offset, returning `value`.
379    #[track_caller]
380    pub fn read8(&mut self, value: u8) -> &mut Self {
381        let data = AccessData::new(AccessSize::U8, u64::from(value));
382        self.trace_builder.post_read(self.offset, data, None, Location::caller());
383        self
384    }
385
386    /// Posts an expectation for an 8-bit write of `value` to this offset.
387    #[track_caller]
388    pub fn write8(&mut self, value: u8) -> &mut Self {
389        let data = AccessData::new(AccessSize::U8, u64::from(value));
390        self.trace_builder.post_write(self.offset, data, None, Location::caller());
391        self
392    }
393
394    /// Posts an expectation for a 16-bit read from this offset, returning `value`.
395    #[track_caller]
396    pub fn read16(&mut self, value: u16) -> &mut Self {
397        let data = AccessData::new(AccessSize::U16, u64::from(value));
398        self.trace_builder.post_read(self.offset, data, None, Location::caller());
399        self
400    }
401
402    /// Posts an expectation for a 16-bit write of `value` to this offset.
403    #[track_caller]
404    pub fn write16(&mut self, value: u16) -> &mut Self {
405        let data = AccessData::new(AccessSize::U16, u64::from(value));
406        self.trace_builder.post_write(self.offset, data, None, Location::caller());
407        self
408    }
409
410    /// Posts an expectation for a 32-bit read from this offset, returning `value`.
411    #[track_caller]
412    pub fn read32(&mut self, value: u32) -> &mut Self {
413        let data = AccessData::new(AccessSize::U32, u64::from(value));
414        self.trace_builder.post_read(self.offset, data, None, Location::caller());
415        self
416    }
417
418    /// Posts an expectation for a 32-bit write of `value` to this offset.
419    #[track_caller]
420    pub fn write32(&mut self, value: u32) -> &mut Self {
421        let data = AccessData::new(AccessSize::U32, u64::from(value));
422        self.trace_builder.post_write(self.offset, data, None, Location::caller());
423        self
424    }
425
426    /// Posts an expectation for a 64-bit read from this offset, returning `value`.
427    #[track_caller]
428    pub fn read64(&mut self, value: u64) -> &mut Self {
429        let data = AccessData::new(AccessSize::U64, value);
430        self.trace_builder.post_read(self.offset, data, None, Location::caller());
431        self
432    }
433
434    /// Posts an expectation for a 64-bit write of `value` to this offset.
435    #[track_caller]
436    pub fn write64(&mut self, value: u64) -> &mut Self {
437        let data = AccessData::new(AccessSize::U64, value);
438        self.trace_builder.post_write(self.offset, data, None, Location::caller());
439        self
440    }
441
442    /// Posts an expectation for successive 8-bit reads from this offset returning `values`.
443    ///
444    /// See [`ExpectedTraceBuilder::poll`] for the polling semantics.
445    ///
446    /// # Panics
447    ///
448    /// Panics if `values` is empty.
449    #[track_caller]
450    pub fn poll8(&mut self, values: impl IntoIterator<Item = u8>) -> &mut Self {
451        let values = ExpectedTraceBuilder::raw_values::<u8>(values, "poll8()");
452        self.trace_builder.post_poll(
453            self.offset,
454            &values,
455            AccessSize::U8,
456            None,
457            Location::caller(),
458        );
459        self
460    }
461
462    /// Posts an expectation for successive 16-bit reads from this offset returning `values`.
463    ///
464    /// See [`ExpectedTraceBuilder::poll`] for the polling semantics.
465    ///
466    /// # Panics
467    ///
468    /// Panics if `values` is empty.
469    #[track_caller]
470    pub fn poll16(&mut self, values: impl IntoIterator<Item = u16>) -> &mut Self {
471        let values = ExpectedTraceBuilder::raw_values::<u16>(values, "poll16()");
472        self.trace_builder.post_poll(
473            self.offset,
474            &values,
475            AccessSize::U16,
476            None,
477            Location::caller(),
478        );
479        self
480    }
481
482    /// Posts an expectation for successive 32-bit reads from this offset returning `values`.
483    ///
484    /// See [`ExpectedTraceBuilder::poll`] for the polling semantics.
485    ///
486    /// # Panics
487    ///
488    /// Panics if `values` is empty.
489    #[track_caller]
490    pub fn poll32(&mut self, values: impl IntoIterator<Item = u32>) -> &mut Self {
491        let values = ExpectedTraceBuilder::raw_values::<u32>(values, "poll32()");
492        self.trace_builder.post_poll(
493            self.offset,
494            &values,
495            AccessSize::U32,
496            None,
497            Location::caller(),
498        );
499        self
500    }
501
502    /// Posts an expectation for successive 64-bit reads from this offset returning `values`.
503    ///
504    /// See [`ExpectedTraceBuilder::poll`] for the polling semantics.
505    ///
506    /// # Panics
507    ///
508    /// Panics if `values` is empty.
509    #[track_caller]
510    pub fn poll64(&mut self, values: impl IntoIterator<Item = u64>) -> &mut Self {
511        let values = ExpectedTraceBuilder::raw_values::<u64>(values, "poll64()");
512        self.trace_builder.post_poll(
513            self.offset,
514            &values,
515            AccessSize::U64,
516            None,
517            Location::caller(),
518        );
519        self
520    }
521
522    /// Posts an expectation for indefinite 8-bit reads from this offset returning `busy_value`.
523    ///
524    /// See [`ExpectedTraceBuilder::poll_indefinitely`] for the polling semantics.
525    #[track_caller]
526    pub fn poll_indefinitely8(&mut self, busy_value: u8) -> &mut Self {
527        let data = AccessData::new(AccessSize::U8, u64::from(busy_value));
528        self.trace_builder.post_poll_indefinitely(self.offset, data, None, Location::caller());
529        self
530    }
531
532    /// Posts an expectation for indefinite 16-bit reads from this offset returning `busy_value`.
533    ///
534    /// See [`ExpectedTraceBuilder::poll_indefinitely`] for the polling semantics.
535    #[track_caller]
536    pub fn poll_indefinitely16(&mut self, busy_value: u16) -> &mut Self {
537        let data = AccessData::new(AccessSize::U16, u64::from(busy_value));
538        self.trace_builder.post_poll_indefinitely(self.offset, data, None, Location::caller());
539        self
540    }
541
542    /// Posts an expectation for indefinite 32-bit reads from this offset returning `busy_value`.
543    ///
544    /// See [`ExpectedTraceBuilder::poll_indefinitely`] for the polling semantics.
545    #[track_caller]
546    pub fn poll_indefinitely32(&mut self, busy_value: u32) -> &mut Self {
547        let data = AccessData::new(AccessSize::U32, u64::from(busy_value));
548        self.trace_builder.post_poll_indefinitely(self.offset, data, None, Location::caller());
549        self
550    }
551
552    /// Posts an expectation for indefinite 64-bit reads from this offset returning `busy_value`.
553    ///
554    /// See [`ExpectedTraceBuilder::poll_indefinitely`] for the polling semantics.
555    #[track_caller]
556    pub fn poll_indefinitely64(&mut self, busy_value: u64) -> &mut Self {
557        let data = AccessData::new(AccessSize::U64, busy_value);
558        self.trace_builder.post_poll_indefinitely(self.offset, data, None, Location::caller());
559        self
560    }
561}
562
563#[cfg(test)]
564mod tests {
565    use super::*;
566    use mmio::Register;
567
568    mod registers {
569        use mmio::register;
570
571        register! {
572            #[register(offset = 0x10, mode = RW)]
573            pub struct Test(u32);
574
575            #[indexed_register(offset = 0x100, stride = 4, count = 4, mode = RW)]
576            pub struct TestIndexed(u32);
577        }
578    }
579
580    #[fuchsia::test]
581    fn test_raw_reads_and_writes() {
582        let scoreboard = Scoreboard::new(0x1000);
583        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
584        builder.at(0x00).write8(0x12);
585        builder.at(0x01).read8(0x34);
586        builder.at(0x02).write16(0x1234);
587        builder.at(0x04).read16(0x5678);
588        builder.at(0x08).write32(0x1234_5678);
589        builder.at(0x0c).read32(0x9abc_def0);
590        builder.at(0x10).write64(0x0123_4567_89ab_cdef);
591        builder.at(0x18).read64(0xfedc_ba98_7654_3210);
592
593        scoreboard.store(0x00, AccessSize::U8, 0x12);
594        assert_eq!(scoreboard.load(0x1, AccessSize::U8), 0x34);
595        scoreboard.store(0x02, AccessSize::U16, 0x1234);
596        assert_eq!(scoreboard.load(0x4, AccessSize::U16), 0x5678);
597        scoreboard.store(0x08, AccessSize::U32, 0x1234_5678);
598        assert_eq!(scoreboard.load(0xc, AccessSize::U32), 0x9abc_def0);
599        scoreboard.store(0x10, AccessSize::U64, 0x0123_4567_89ab_cdef);
600        assert_eq!(scoreboard.load(0x18, AccessSize::U64), 0xfedc_ba98_7654_3210);
601    }
602
603    #[fuchsia::test]
604    fn test_raw_offset_rmw_chaining() {
605        let scoreboard = Scoreboard::new(0x1000);
606        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
607        builder.at(0x8).read32(0x0000_0001).write32(0x0000_0003);
608
609        assert_eq!(scoreboard.load(0x8, AccessSize::U32), 0x0000_0001);
610        scoreboard.store(0x8, AccessSize::U32, 0x0000_0003);
611    }
612
613    #[fuchsia::test]
614    fn test_write_barrier() {
615        let scoreboard = Scoreboard::new(0x1000);
616        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
617        builder.write_barrier();
618
619        scoreboard.write_barrier();
620    }
621
622    #[fuchsia::test]
623    fn test_raw_polls() {
624        let scoreboard = Scoreboard::new(0x1000);
625        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
626        builder.at(0x0).poll8([1u8, 2u8]);
627        builder.at(0x2).poll16([0x100u16, 0x200u16]);
628        builder.at(0x4).poll32([1u32, 2u32]);
629        builder.at(0x8).poll64([10u64, 20u64]);
630        builder.at(0x1).write8(3);
631
632        // The last value of each sequence repeats until the next access.
633        assert_eq!(scoreboard.load(0x0, AccessSize::U8), 1);
634        assert_eq!(scoreboard.load(0x0, AccessSize::U8), 2);
635        assert_eq!(scoreboard.load(0x0, AccessSize::U8), 2);
636        assert_eq!(scoreboard.load(0x2, AccessSize::U16), 0x100);
637        assert_eq!(scoreboard.load(0x2, AccessSize::U16), 0x200);
638        assert_eq!(scoreboard.load(0x4, AccessSize::U32), 1);
639        assert_eq!(scoreboard.load(0x4, AccessSize::U32), 2);
640        assert_eq!(scoreboard.load(0x8, AccessSize::U64), 10);
641        assert_eq!(scoreboard.load(0x8, AccessSize::U64), 20);
642        scoreboard.store(0x1, AccessSize::U8, 3);
643    }
644
645    #[fuchsia::test]
646    fn test_raw_indefinite_polls() {
647        let scoreboard = Scoreboard::new(0x1000);
648        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
649        builder.at(0x0).poll_indefinitely8(0xaa);
650        builder.at(0x2).poll_indefinitely16(0xaaaa);
651        builder.at(0x4).poll_indefinitely32(0xaaaa_bbbb);
652        builder.at(0x8).poll_indefinitely64(0xaaaa_bbbb_cccc_dddd);
653        builder.at(0x1).write8(0xff);
654
655        assert_eq!(scoreboard.load(0x0, AccessSize::U8), 0xaa);
656        assert_eq!(scoreboard.load(0x0, AccessSize::U8), 0xaa);
657        assert_eq!(scoreboard.load(0x2, AccessSize::U16), 0xaaaa);
658        assert_eq!(scoreboard.load(0x4, AccessSize::U32), 0xaaaa_bbbb);
659        assert_eq!(scoreboard.load(0x8, AccessSize::U64), 0xaaaa_bbbb_cccc_dddd);
660        scoreboard.store(0x1, AccessSize::U8, 0xff);
661    }
662
663    #[fuchsia::test]
664    fn test_typed_read_write() {
665        let scoreboard = Scoreboard::new(0x1000);
666        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
667        builder.write::<registers::Test>(0x1234);
668        builder.read::<registers::Test>(0x5678);
669
670        scoreboard.store(registers::Test::OFFSET, AccessSize::U32, 0x1234);
671        assert_eq!(scoreboard.load(registers::Test::OFFSET, AccessSize::U32), 0x5678);
672    }
673
674    #[fuchsia::test]
675    fn test_typed_poll() {
676        let scoreboard = Scoreboard::new(0x1000);
677        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
678        builder.poll::<registers::Test>([1, 2]);
679        builder.write::<registers::Test>(3);
680
681        assert_eq!(scoreboard.load(registers::Test::OFFSET, AccessSize::U32), 1);
682        assert_eq!(scoreboard.load(registers::Test::OFFSET, AccessSize::U32), 2);
683        scoreboard.store(registers::Test::OFFSET, AccessSize::U32, 3);
684    }
685
686    #[fuchsia::test]
687    fn test_typed_poll_indefinitely() {
688        let scoreboard = Scoreboard::new(0x1000);
689        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
690        builder.poll_indefinitely::<registers::Test>(0xaa);
691        builder.write::<registers::Test>(0xbb);
692
693        assert_eq!(scoreboard.load(registers::Test::OFFSET, AccessSize::U32), 0xaa);
694        scoreboard.store(registers::Test::OFFSET, AccessSize::U32, 0xbb);
695    }
696
697    #[fuchsia::test]
698    fn test_indexed_read_write() {
699        let scoreboard = Scoreboard::new(0x1000);
700        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
701        builder.write_indexed::<registers::TestIndexed>(0, 0x10);
702        builder.read_indexed::<registers::TestIndexed>(1, 0x20);
703
704        scoreboard.store(0x100, AccessSize::U32, 0x10);
705        assert_eq!(scoreboard.load(0x104, AccessSize::U32), 0x20);
706    }
707
708    #[fuchsia::test]
709    fn test_indexed_poll() {
710        let scoreboard = Scoreboard::new(0x1000);
711        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
712        builder.poll_indexed::<registers::TestIndexed>(2, [1, 2]);
713        builder.write_indexed::<registers::TestIndexed>(3, 3);
714
715        assert_eq!(scoreboard.load(0x108, AccessSize::U32), 1);
716        assert_eq!(scoreboard.load(0x108, AccessSize::U32), 2);
717        scoreboard.store(0x10c, AccessSize::U32, 3);
718    }
719
720    #[fuchsia::test]
721    fn test_indexed_poll_indefinitely() {
722        let scoreboard = Scoreboard::new(0x1000);
723        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
724        builder.poll_indefinitely_indexed::<registers::TestIndexed>(1, 0x55);
725        builder.write_indexed::<registers::TestIndexed>(2, 0x66);
726
727        assert_eq!(scoreboard.load(0x104, AccessSize::U32), 0x55);
728        scoreboard.store(0x108, AccessSize::U32, 0x66);
729    }
730
731    /// Checks that the register index reaches the failure report.
732    ///
733    /// The report's layout is covered by the tests in the `formatting` module.
734    /// Fuchsia tests are compiled with `panic = "abort"`, so the panic message
735    /// can only be matched via `#[should_panic]`.
736    #[fuchsia::test]
737    #[should_panic(expected = "TestIndexed[2] (0x0108)")]
738    fn test_indexed_expectations_record_the_index() {
739        let scoreboard = Scoreboard::new(0x1000);
740        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
741        builder.read_indexed::<registers::TestIndexed>(2, 0x30);
742
743        // Mismatched access. The report must name the index, not just the offset.
744        let _ = scoreboard.load(0x0, AccessSize::U32);
745    }
746
747    #[fuchsia::test]
748    #[should_panic(expected = "Register index 4 out of bounds")]
749    fn test_read_indexed_out_of_bounds_panics() {
750        let scoreboard = Scoreboard::new(0x1000);
751        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
752        builder.read_indexed::<registers::TestIndexed>(4, 0x10);
753    }
754
755    #[fuchsia::test]
756    #[should_panic(expected = "Register index 4 out of bounds")]
757    fn test_write_indexed_out_of_bounds_panics() {
758        let scoreboard = Scoreboard::new(0x1000);
759        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
760        builder.write_indexed::<registers::TestIndexed>(4, 0x10);
761    }
762
763    #[fuchsia::test]
764    #[should_panic(expected = "Register index 4 out of bounds")]
765    fn test_poll_indexed_out_of_bounds_panics() {
766        let scoreboard = Scoreboard::new(0x1000);
767        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
768        builder.poll_indexed::<registers::TestIndexed>(4, [1]);
769    }
770
771    #[fuchsia::test]
772    #[should_panic(expected = "Register index 4 out of bounds")]
773    fn test_poll_indefinitely_indexed_out_of_bounds_panics() {
774        let scoreboard = Scoreboard::new(0x1000);
775        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
776        builder.poll_indefinitely_indexed::<registers::TestIndexed>(4, 1);
777    }
778
779    #[fuchsia::test]
780    #[should_panic(expected = "poll() requires at least one return value")]
781    fn test_poll_empty_panics() {
782        let scoreboard = Scoreboard::new(0x1000);
783        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
784        builder.poll::<registers::Test>([]);
785    }
786
787    #[fuchsia::test]
788    #[should_panic(expected = "poll_indexed() requires at least one return value")]
789    fn test_poll_indexed_empty_panics() {
790        let scoreboard = Scoreboard::new(0x1000);
791        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
792        builder.poll_indexed::<registers::TestIndexed>(0, []);
793    }
794
795    #[fuchsia::test]
796    #[should_panic(expected = "poll8() requires at least one return value")]
797    fn test_poll8_empty_panics() {
798        let scoreboard = Scoreboard::new(0x1000);
799        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
800        builder.at(0).poll8([]);
801    }
802
803    #[fuchsia::test]
804    #[should_panic(expected = "poll16() requires at least one return value")]
805    fn test_poll16_empty_panics() {
806        let scoreboard = Scoreboard::new(0x1000);
807        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
808        builder.at(0).poll16([]);
809    }
810
811    #[fuchsia::test]
812    #[should_panic(expected = "poll32() requires at least one return value")]
813    fn test_poll32_empty_panics() {
814        let scoreboard = Scoreboard::new(0x1000);
815        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
816        builder.at(0).poll32([]);
817    }
818
819    #[fuchsia::test]
820    #[should_panic(expected = "poll64() requires at least one return value")]
821    fn test_poll64_empty_panics() {
822        let scoreboard = Scoreboard::new(0x1000);
823        let mut builder = ExpectedTraceBuilder::new(&scoreboard);
824        builder.at(0).poll64([]);
825    }
826}