yash_env_test_helper/lib.rs
1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! This crate contains utility functions for use in tests that interact with
18//! the shell environment ([`yash_env::Env`]).
19
20use assert_matches::assert_matches;
21use futures_executor::LocalSpawner;
22use futures_util::FutureExt as _;
23use futures_util::task::LocalSpawnExt as _;
24use std::cell::RefCell;
25use std::pin::Pin;
26use std::rc::Rc;
27use std::str::from_utf8;
28use yash_env::Env;
29use yash_env::system::r#virtual::{Executor, FileBody, Inode, SystemState, VirtualSystem};
30
31/// Adapter for [`LocalSpawner`] to [`Executor`]
32#[derive(Clone, Debug)]
33pub struct LocalExecutor(pub LocalSpawner);
34
35impl Executor for LocalExecutor {
36 fn spawn(
37 &self,
38 task: Pin<Box<dyn Future<Output = ()>>>,
39 ) -> Result<(), Box<dyn std::error::Error>> {
40 Ok(self.0.spawn_local(task)?)
41 }
42}
43
44/// Runs an asynchronous function in a virtual system with a local executor.
45///
46/// This function creates a [`VirtualSystem`] and installs a [`LocalExecutor`]
47/// in it. The argument function `f` is called with an [`Env`] with the virtual
48/// system. The executor is run until the task returned by `f` completes, and
49/// the result is returned.
50///
51/// Function `f` is called with two arguments: the [`Env`] and a shared
52/// reference to the system state. The system state can be used to interact
53/// with the virtual system, e.g. to create files or concurrent tasks.
54///
55/// This function is useful for testing asynchronous code that spawns tasks
56/// that need to be run concurrently with the main task.
57pub fn in_virtual_system<F, Fut, T>(f: F) -> T
58where
59 F: FnOnce(Env<VirtualSystem>, Rc<RefCell<SystemState>>) -> Fut,
60 Fut: Future<Output = T> + 'static,
61 T: 'static,
62{
63 let system = VirtualSystem::new();
64 let state = Rc::clone(&system.state);
65 let mut executor = futures_executor::LocalPool::new();
66 state.borrow_mut().executor = Some(Rc::new(LocalExecutor(executor.spawner())));
67
68 let env = Env::with_system(system);
69 let shared_system = env.system.clone();
70 let task = f(env, Rc::clone(&state));
71 let mut task = executor.spawner().spawn_local_with_handle(task).unwrap();
72 loop {
73 if let Some(result) = (&mut task).now_or_never() {
74 return result;
75 }
76 executor.run_until_stalled();
77 shared_system.select(false).unwrap();
78 SystemState::select_all(&state);
79 }
80}
81
82/// Creates a dummy file at /dev/tty.
83pub fn stub_tty(state: &RefCell<SystemState>) {
84 state
85 .borrow_mut()
86 .file_system
87 .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
88 .unwrap();
89}
90
91/// Helper function for asserting on the content of /dev/stdout
92///
93/// This function asserts on the content of /dev/stdout. The argument function
94/// `f` is called with the content of /dev/stdout as a string slice.
95///
96/// This function panics if /dev/stdout does not exist, is not a regular file,
97/// or does not contain a valid UTF-8 string.
98///
99/// # Example
100///
101/// ```
102/// # use std::rc::Rc;
103/// # use yash_env::Env;
104/// # use yash_env::io::Fd;
105/// # use yash_env::system::Write as _;
106/// # use yash_env::system::r#virtual::VirtualSystem;
107/// # use yash_env_test_helper::assert_stdout;
108/// let system = VirtualSystem::new();
109/// let state = Rc::clone(&system.state);
110/// let mut env = Env::with_system(system);
111/// env.system.write(Fd::STDOUT, b"Hello, world!\n").unwrap();
112/// assert_stdout(&state, |stdout| assert_eq!(stdout, "Hello, world!\n"));
113/// ```
114pub fn assert_stdout<F, T>(state: &RefCell<SystemState>, f: F) -> T
115where
116 F: FnOnce(&str) -> T,
117{
118 let stdout = state.borrow().file_system.get("/dev/stdout").unwrap();
119 let stdout = stdout.borrow();
120 assert_matches!(&stdout.body, FileBody::Regular { content, .. } => {
121 f(from_utf8(content).unwrap())
122 })
123}
124
125/// Helper function for asserting on the content of /dev/stderr
126///
127/// This function asserts on the content of /dev/stderr. The argument function
128/// `f` is called with the content of /dev/stderr as a string slice.
129///
130/// This function panics if /dev/stderr does not exist, is not a regular file,
131/// or does not contain a valid UTF-8 string.
132///
133/// This function is analogous to [`assert_stdout`]. See its documentation for
134/// an example.
135pub fn assert_stderr<F, T>(state: &RefCell<SystemState>, f: F) -> T
136where
137 F: FnOnce(&str) -> T,
138{
139 let stderr = state.borrow().file_system.get("/dev/stderr").unwrap();
140 let stderr = stderr.borrow();
141 assert_matches!(&stderr.body, FileBody::Regular { content, .. } => {
142 f(from_utf8(content).unwrap())
143 })
144}
145
146pub mod function;