Skip to main content

line_discipline/testing/
mod.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
5use crate::*;
6use serde::Deserialize;
7use starnix_uapi::errors::Errno;
8use std::collections::HashMap;
9
10#[derive(Deserialize, Debug)]
11struct Scenario {
12    name: String,
13    initial_termios: TermiosConfig,
14    events: Vec<Event>,
15    final_termios: TermiosConfig,
16}
17
18#[derive(Deserialize, Debug)]
19struct TermiosConfig {
20    #[serde(default)]
21    c_iflag: Vec<String>,
22    #[serde(default)]
23    c_oflag: Vec<String>,
24    #[serde(default)]
25    c_lflag: Vec<String>,
26    #[allow(dead_code)]
27    c_cflag: Option<u32>, // Keeping cflag simple for now or assume default
28    c_cc: Option<HashMap<String, u8>>,
29}
30
31#[derive(Deserialize, Debug)]
32#[serde(untagged)]
33enum TraceData {
34    Bytes(Vec<u8>),
35    String(String),
36}
37
38impl TraceData {
39    fn to_bytes(&self) -> Vec<u8> {
40        match self {
41            TraceData::Bytes(b) => b.clone(),
42            TraceData::String(s) => s.as_bytes().to_vec(),
43        }
44    }
45}
46
47fn default_read_size() -> usize {
48    4096
49}
50
51#[derive(Deserialize, Debug)]
52#[serde(tag = "type")]
53enum Event {
54    #[serde(rename = "write_to_master")]
55    WriteToMaster {
56        data: TraceData,
57        #[serde(default)]
58        signals: Option<Vec<String>>,
59    },
60    #[serde(rename = "write_to_master_blocked")]
61    WriteToMasterBlocked { data: TraceData },
62    #[serde(rename = "read_from_master")]
63    ReadFromMaster { data: TraceData },
64    #[serde(rename = "read_from_slave")]
65    ReadFromSlave {
66        data: TraceData,
67        #[serde(default)]
68        signals: Option<Vec<String>>,
69    },
70    #[serde(rename = "read_once_from_master")]
71    ReadOnceFromMaster {
72        #[serde(default = "default_read_size")]
73        size: usize,
74        data: TraceData,
75    },
76    #[serde(rename = "read_once_from_master_blocked")]
77    ReadOnceFromMasterBlocked {
78        #[serde(default = "default_read_size")]
79        size: usize,
80    },
81    #[serde(rename = "read_once_from_master_eio")]
82    ReadOnceFromMasterEio {
83        #[serde(default = "default_read_size")]
84        size: usize,
85    },
86    #[serde(rename = "read_once_from_slave")]
87    ReadOnceFromSlave {
88        #[serde(default = "default_read_size")]
89        size: usize,
90        data: TraceData,
91        #[serde(default)]
92        signals: Option<Vec<String>>,
93    },
94    #[serde(rename = "read_once_from_slave_blocked")]
95    ReadOnceFromSlaveBlocked {
96        #[serde(default = "default_read_size")]
97        size: usize,
98    },
99    #[serde(rename = "write_to_slave")]
100    WriteToSlave { data: TraceData },
101    #[serde(rename = "write_to_slave_blocked")]
102    WriteToSlaveBlocked { data: TraceData },
103    #[serde(rename = "write_to_slave_eio")]
104    WriteToSlaveEio { data: TraceData },
105    #[serde(rename = "set_packet_mode")]
106    SetPacketMode { enabled: bool },
107    #[serde(rename = "set_termios")]
108    SetTermios {
109        termios: TermiosConfig,
110        #[serde(default)]
111        signals: Option<Vec<String>>,
112    },
113    #[serde(rename = "flush")]
114    Flush { side: String, queue_selector: String },
115    #[serde(rename = "wait_until_readable")]
116    WaitUntilReadable { side: String },
117    #[serde(rename = "check_readable_size")]
118    CheckReadableSize { side: String, size: usize },
119    #[serde(rename = "check_poll")]
120    CheckPoll { side: String, events: Vec<String> },
121    #[serde(rename = "close")]
122    Close { side: String },
123}
124
125struct TestBuffer {
126    data: Vec<u8>,
127}
128
129impl TestBuffer {
130    fn new(data: Vec<u8>) -> Self {
131        Self { data }
132    }
133}
134
135impl InputBuffer for TestBuffer {
136    fn available(&self) -> usize {
137        self.data.len()
138    }
139    fn read_to_vec_exact(&mut self, size: usize) -> Result<Vec<u8>, Errno> {
140        if size > self.data.len() {
141            return error!(EAGAIN);
142        }
143        let result = self.data.drain(0..size).collect();
144        Ok(result)
145    }
146}
147
148struct TestOutputBuffer {
149    data: Vec<u8>,
150}
151
152impl TestOutputBuffer {
153    fn new() -> Self {
154        Self { data: vec![] }
155    }
156}
157
158impl OutputBuffer for TestOutputBuffer {
159    fn available(&self) -> usize {
160        usize::MAX
161    }
162    fn write(&mut self, data: &[u8]) -> Result<usize, Errno> {
163        self.data.extend_from_slice(data);
164        Ok(data.len())
165    }
166}
167
168struct LimitedTestOutputBuffer {
169    data: Vec<u8>,
170    max_size: usize,
171}
172
173impl LimitedTestOutputBuffer {
174    fn new(max_size: usize) -> Self {
175        Self { data: vec![], max_size }
176    }
177}
178
179impl OutputBuffer for LimitedTestOutputBuffer {
180    fn available(&self) -> usize {
181        self.max_size.saturating_sub(self.data.len())
182    }
183    fn write(&mut self, data: &[u8]) -> Result<usize, Errno> {
184        let to_write = std::cmp::min(self.available(), data.len());
185        self.data.extend_from_slice(&data[..to_write]);
186        Ok(to_write)
187    }
188}
189
190fn parse_flags(flags: &[String], mapping: &[(u32, &str)]) -> u32 {
191    let mut result = 0;
192    for flag in flags {
193        if let Some((val, _)) = mapping.iter().find(|(_, name)| name == flag) {
194            result |= val;
195        } else {
196            panic!("Unknown flag {}", flag);
197        }
198    }
199    result
200}
201
202fn get_iflag_mapping() -> Vec<(u32, &'static str)> {
203    vec![
204        (starnix_uapi::IGNBRK, "IGNBRK"),
205        (starnix_uapi::BRKINT, "BRKINT"),
206        (starnix_uapi::IGNPAR, "IGNPAR"),
207        (starnix_uapi::PARMRK, "PARMRK"),
208        (starnix_uapi::INPCK, "INPCK"),
209        (starnix_uapi::ISTRIP, "ISTRIP"),
210        (starnix_uapi::INLCR, "INLCR"),
211        (starnix_uapi::IGNCR, "IGNCR"),
212        (starnix_uapi::ICRNL, "ICRNL"),
213        (starnix_uapi::IUCLC, "IUCLC"),
214        (starnix_uapi::IXON, "IXON"),
215        (starnix_uapi::IXANY, "IXANY"),
216        (starnix_uapi::IXOFF, "IXOFF"),
217        (starnix_uapi::IMAXBEL, "IMAXBEL"),
218        (starnix_uapi::IUTF8, "IUTF8"),
219    ]
220}
221
222fn get_oflag_mapping() -> Vec<(u32, &'static str)> {
223    vec![
224        (starnix_uapi::OPOST, "OPOST"),
225        (starnix_uapi::OLCUC, "OLCUC"),
226        (starnix_uapi::ONLCR, "ONLCR"),
227        (starnix_uapi::OCRNL, "OCRNL"),
228        (starnix_uapi::ONOCR, "ONOCR"),
229        (starnix_uapi::ONLRET, "ONLRET"),
230        (starnix_uapi::OFILL, "OFILL"),
231        (starnix_uapi::OFDEL, "OFDEL"),
232        (starnix_uapi::XTABS, "XTABS"),
233    ]
234}
235
236fn get_lflag_mapping() -> Vec<(u32, &'static str)> {
237    vec![
238        (starnix_uapi::ISIG, "ISIG"),
239        (starnix_uapi::ICANON, "ICANON"),
240        (starnix_uapi::XCASE, "XCASE"),
241        (starnix_uapi::ECHO, "ECHO"),
242        (starnix_uapi::ECHOE, "ECHOE"),
243        (starnix_uapi::ECHOK, "ECHOK"),
244        (starnix_uapi::ECHONL, "ECHONL"),
245        (starnix_uapi::ECHOCTL, "ECHOCTL"),
246        (starnix_uapi::ECHOPRT, "ECHOPRT"),
247        (starnix_uapi::ECHOKE, "ECHOKE"),
248        (starnix_uapi::FLUSHO, "FLUSHO"),
249        (starnix_uapi::NOFLSH, "NOFLSH"),
250        (starnix_uapi::TOSTOP, "TOSTOP"),
251        (starnix_uapi::PENDIN, "PENDIN"),
252        (starnix_uapi::IEXTEN, "IEXTEN"),
253        (starnix_uapi::EXTPROC, "EXTPROC"),
254    ]
255}
256
257fn get_cc_mapping() -> HashMap<&'static str, usize> {
258    let mut m = HashMap::new();
259    m.insert("VMIN", starnix_uapi::VMIN as usize);
260    m.insert("VTIME", starnix_uapi::VTIME as usize);
261    m.insert("VINTR", starnix_uapi::VINTR as usize);
262    m.insert("VQUIT", starnix_uapi::VQUIT as usize);
263    m.insert("VERASE", starnix_uapi::VERASE as usize);
264    m.insert("VKILL", starnix_uapi::VKILL as usize);
265    m.insert("VEOF", starnix_uapi::VEOF as usize);
266    m.insert("VSTART", starnix_uapi::VSTART as usize);
267    m.insert("VSTOP", starnix_uapi::VSTOP as usize);
268    m.insert("VSUSP", starnix_uapi::VSUSP as usize);
269    m.insert("VEOL", starnix_uapi::VEOL as usize);
270    m.insert("VREPRINT", starnix_uapi::VREPRINT as usize);
271    m.insert("VDISCARD", starnix_uapi::VDISCARD as usize);
272    m.insert("VWERASE", starnix_uapi::VWERASE as usize);
273    m.insert("VLNEXT", starnix_uapi::VLNEXT as usize);
274    m.insert("VEOL2", starnix_uapi::VEOL2 as usize);
275    m
276}
277
278fn signal_to_name(sig: Signal) -> &'static str {
279    if sig == SIGINT {
280        "SIGINT"
281    } else if sig == SIGQUIT {
282        "SIGQUIT"
283    } else if sig == SIGTSTP {
284        "SIGTSTP"
285    } else {
286        panic!("Unexpected signal {:?}", sig);
287    }
288}
289
290fn check_signals(
291    pending_signals: PendingSignals,
292    expected_signals: Option<Vec<String>>,
293    event_name: &str,
294    scenario_name: &str,
295) {
296    let actual: Vec<String> =
297        pending_signals.signals().iter().map(|&s| signal_to_name(s).to_string()).collect();
298    let expected = expected_signals.unwrap_or_default();
299    assert_eq!(actual, expected, "{} signals mismatch in {}", event_name, scenario_name);
300}
301
302fn decompose_fd_events(events: FdEvents) -> Vec<String> {
303    let mut out = Vec::new();
304    if events.contains(FdEvents::POLLIN) {
305        out.push("POLLIN".to_string());
306    }
307    if events.contains(FdEvents::POLLOUT) {
308        out.push("POLLOUT".to_string());
309    }
310    if events.contains(FdEvents::POLLPRI) {
311        out.push("POLLPRI".to_string());
312    }
313    if events.contains(FdEvents::POLLERR) {
314        out.push("POLLERR".to_string());
315    }
316    if events.contains(FdEvents::POLLHUP) {
317        out.push("POLLHUP".to_string());
318    }
319    out
320}
321
322pub fn test_replay_trace(name: &str, json_data: &str) {
323    println!("Running trace: {}", name);
324    let scenario: Scenario = serde_json::from_str(json_data).unwrap_or_else(|e| {
325        panic!("Failed to parse trace {}: {}", name, e);
326    });
327    run_scenario(scenario);
328}
329
330fn apply_termios_config(
331    mut termios: uapi::termios2,
332    config: &TermiosConfig,
333    iflags: &[(u32, &'static str)],
334    oflags: &[(u32, &'static str)],
335    lflags: &[(u32, &'static str)],
336) -> uapi::termios2 {
337    termios.c_iflag = parse_flags(&config.c_iflag, iflags);
338    termios.c_oflag = parse_flags(&config.c_oflag, oflags);
339    termios.c_lflag = parse_flags(&config.c_lflag, lflags);
340    if let Some(cc) = &config.c_cc {
341        let mapping = get_cc_mapping();
342        for (name, &val) in cc {
343            if let Some(&idx) = mapping.get(name.as_str()) {
344                if idx < termios.c_cc.len() {
345                    termios.c_cc[idx] = val;
346                }
347            } else {
348                panic!("Unknown c_cc name {}", name);
349            }
350        }
351    }
352    termios
353}
354
355fn run_scenario(scenario: Scenario) {
356    let iflags = get_iflag_mapping();
357    let oflags = get_oflag_mapping();
358    let lflags = get_lflag_mapping();
359
360    let mut ld = LineDiscipline::default();
361    ld.main_open();
362    ld.replica_open();
363
364    // Set initial termios
365    let termios =
366        apply_termios_config(*ld.termios(), &scenario.initial_termios, &iflags, &oflags, &lflags);
367    let initial_signals = ld.set_termios(termios);
368    assert!(initial_signals.signals().is_empty());
369
370    for event in scenario.events {
371        match event {
372            Event::WriteToMaster { data, signals: expected_signals } => {
373                let bytes = data.to_bytes();
374                let mut buffer = TestBuffer::new(bytes.clone());
375                let (written, pending_signals) =
376                    ld.main_write(&mut buffer).expect("main_write failed");
377                assert_eq!(
378                    written,
379                    bytes.len(),
380                    "WriteToMaster partial write in {}",
381                    scenario.name
382                );
383                check_signals(pending_signals, expected_signals, "WriteToMaster", &scenario.name);
384            }
385            Event::WriteToMasterBlocked { data } => {
386                let mut buffer = TestBuffer::new(data.to_bytes());
387                assert_eq!(
388                    ld.main_write(&mut buffer),
389                    error!(EAGAIN),
390                    "Expected main_write to return EAGAIN in {}",
391                    scenario.name
392                );
393            }
394            Event::ReadFromMaster { data } => {
395                let mut buffer = TestOutputBuffer::new();
396                loop {
397                    match ld.main_read(&mut buffer) {
398                        Ok(_) => {}
399                        Err(e) if e == (error!(EAGAIN) as Result<(), Errno>).unwrap_err() => {
400                            break;
401                        }
402                        Err(e) => panic!("main_read failed: {:?}", e),
403                    }
404                }
405                assert_eq!(
406                    buffer.data,
407                    data.to_bytes(),
408                    "ReadFromMaster mismatch in {} (actual={:?}, expected={:?})",
409                    scenario.name,
410                    String::from_utf8_lossy(&buffer.data),
411                    String::from_utf8_lossy(&data.to_bytes()),
412                );
413            }
414            Event::ReadFromSlave { data, signals: expected_signals } => {
415                let mut buffer = TestOutputBuffer::new();
416                let mut pending_signals = PendingSignals::new();
417                loop {
418                    match ld.replica_read(&mut buffer) {
419                        Ok((_, signals)) => pending_signals.append(signals),
420                        Err(e) if e == (error!(EAGAIN) as Result<(), Errno>).unwrap_err() => {
421                            break;
422                        }
423                        Err(e) => panic!("replica_read failed: {:?}", e),
424                    }
425                }
426                check_signals(pending_signals, expected_signals, "ReadFromSlave", &scenario.name);
427                assert_eq!(
428                    buffer.data,
429                    data.to_bytes(),
430                    "ReadFromSlave mismatch in {} (actual={:?}, expected={:?})",
431                    scenario.name,
432                    String::from_utf8_lossy(&buffer.data),
433                    String::from_utf8_lossy(&data.to_bytes()),
434                );
435            }
436            Event::ReadOnceFromMaster { size, data } => {
437                let mut buffer = LimitedTestOutputBuffer::new(size);
438                let expected = data.to_bytes();
439                let n = ld.main_read(&mut buffer).unwrap_or_else(|e| {
440                    panic!("ReadOnceFromMaster failed in {}: {:?}", scenario.name, e)
441                });
442                assert_eq!(
443                    n,
444                    expected.len(),
445                    "ReadOnceFromMaster length mismatch in {}",
446                    scenario.name
447                );
448                assert_eq!(
449                    buffer.data,
450                    expected,
451                    "ReadOnceFromMaster data mismatch in {} (actual={:?}, expected={:?})",
452                    scenario.name,
453                    String::from_utf8_lossy(&buffer.data),
454                    String::from_utf8_lossy(&expected),
455                );
456            }
457            Event::ReadOnceFromMasterBlocked { size } => {
458                let mut buffer = LimitedTestOutputBuffer::new(size);
459                assert_eq!(
460                    ld.main_read(&mut buffer),
461                    error!(EAGAIN),
462                    "Expected ReadOnceFromMaster to return EAGAIN in {}",
463                    scenario.name
464                );
465            }
466            Event::ReadOnceFromMasterEio { size } => {
467                let mut buffer = LimitedTestOutputBuffer::new(size);
468                assert_eq!(
469                    ld.main_read(&mut buffer),
470                    error!(EIO),
471                    "Expected ReadOnceFromMaster to return EIO in {}",
472                    scenario.name
473                );
474            }
475            Event::ReadOnceFromSlave { size, data, signals: expected_signals } => {
476                let mut buffer = LimitedTestOutputBuffer::new(size);
477                let expected = data.to_bytes();
478                let (n, pending_signals) = ld.replica_read(&mut buffer).unwrap_or_else(|e| {
479                    panic!("ReadOnceFromSlave failed in {}: {:?}", scenario.name, e)
480                });
481                check_signals(
482                    pending_signals,
483                    expected_signals,
484                    "ReadOnceFromSlave",
485                    &scenario.name,
486                );
487                assert_eq!(
488                    n,
489                    expected.len(),
490                    "ReadOnceFromSlave length mismatch in {}",
491                    scenario.name
492                );
493                assert_eq!(
494                    buffer.data,
495                    expected,
496                    "ReadOnceFromSlave data mismatch in {} (actual={:?}, expected={:?})",
497                    scenario.name,
498                    String::from_utf8_lossy(&buffer.data),
499                    String::from_utf8_lossy(&expected),
500                );
501            }
502            Event::ReadOnceFromSlaveBlocked { size } => {
503                let mut buffer = LimitedTestOutputBuffer::new(size);
504                assert_eq!(
505                    ld.replica_read(&mut buffer),
506                    error!(EAGAIN),
507                    "Expected ReadOnceFromSlave to return EAGAIN in {}",
508                    scenario.name
509                );
510            }
511            Event::WriteToSlave { data } => {
512                let bytes = data.to_bytes();
513                let mut buffer = TestBuffer::new(bytes.clone());
514                let written = ld.replica_write(&mut buffer).expect("replica_write failed");
515                assert_eq!(written, bytes.len(), "WriteToSlave partial write in {}", scenario.name);
516            }
517            Event::WriteToSlaveBlocked { data } => {
518                let mut buffer = TestBuffer::new(data.to_bytes());
519                let result = ld.replica_write(&mut buffer);
520                assert!(
521                    result.is_err(),
522                    "Expected replica_write to block/fail in {}, but it succeeded",
523                    scenario.name
524                );
525                assert_eq!(result, error!(EAGAIN), "Expected EAGAIN in {}", scenario.name);
526            }
527            Event::WriteToSlaveEio { data } => {
528                let mut buffer = TestBuffer::new(data.to_bytes());
529                assert_eq!(
530                    ld.replica_write(&mut buffer),
531                    error!(EIO),
532                    "Expected replica_write to return EIO in {}",
533                    scenario.name
534                );
535            }
536            Event::SetPacketMode { enabled } => {
537                ld.set_packet_mode(enabled);
538            }
539            Event::SetTermios { termios: ref termios_config, signals: expected_signals } => {
540                let termios =
541                    apply_termios_config(*ld.termios(), termios_config, &iflags, &oflags, &lflags);
542                let pending_signals = ld.set_termios(termios);
543                check_signals(pending_signals, expected_signals, "SetTermios", &scenario.name);
544            }
545            Event::Flush { side, queue_selector } => {
546                let side = match side.as_str() {
547                    "main" => TerminalSide::Main,
548                    "replica" => TerminalSide::Replica,
549                    _ => panic!("Unknown side {}", side),
550                };
551                let queue_selector_val = match queue_selector.as_str() {
552                    "TCIFLUSH" => starnix_uapi::TCIFLUSH,
553                    "TCOFLUSH" => starnix_uapi::TCOFLUSH,
554                    "TCIOFLUSH" => starnix_uapi::TCIOFLUSH,
555                    _ => panic!("Unknown queue_selector {}", queue_selector),
556                };
557                ld.flush(side, queue_selector_val).expect("flush failed");
558            }
559            Event::WaitUntilReadable { side } => {
560                let events = match side.as_str() {
561                    "main" => ld.main_query_events(),
562                    "replica" => ld.replica_query_events(),
563                    _ => panic!("Unknown side {}", side),
564                };
565                assert!(
566                    events.intersects(FdEvents::POLLIN | FdEvents::POLLHUP),
567                    "Expected {} to be readable in {}, got {:?}",
568                    side,
569                    scenario.name,
570                    events
571                );
572            }
573            Event::CheckReadableSize { side, size } => {
574                let terminal_side = match side.as_str() {
575                    "main" => TerminalSide::Main,
576                    "replica" => TerminalSide::Replica,
577                    _ => panic!("Unknown side {}", side),
578                };
579                assert_eq!(
580                    ld.get_available_read_size(terminal_side),
581                    size,
582                    "CheckReadableSize ({}) mismatch in {}",
583                    side,
584                    scenario.name
585                );
586            }
587            Event::CheckPoll { side, events } => {
588                let actual_events = match side.as_str() {
589                    "main" => ld.main_query_events(),
590                    "replica" => ld.replica_query_events(),
591                    _ => panic!("Unknown side {}", side),
592                };
593                assert_eq!(
594                    decompose_fd_events(actual_events),
595                    events,
596                    "CheckPoll ({}) mismatch in {}",
597                    side,
598                    scenario.name
599                );
600            }
601            Event::Close { side } => match side.as_str() {
602                "main" => ld.main_close(),
603                "replica" => ld.replica_close(),
604                _ => panic!("Unknown side {}", side),
605            },
606        }
607    }
608
609    assert_eq!(
610        ld.termios().c_iflag,
611        parse_flags(&scenario.final_termios.c_iflag, &iflags),
612        "final_termios.c_iflag mismatch in {}",
613        scenario.name
614    );
615    assert_eq!(
616        ld.termios().c_oflag,
617        parse_flags(&scenario.final_termios.c_oflag, &oflags),
618        "final_termios.c_oflag mismatch in {}",
619        scenario.name
620    );
621    assert_eq!(
622        ld.termios().c_lflag,
623        parse_flags(&scenario.final_termios.c_lflag, &lflags),
624        "final_termios.c_lflag mismatch in {}",
625        scenario.name
626    );
627}