regd_testing/
io.rs

1// Copyright 2025 Shingo OKAWA. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This module contains a set of testing utilities of IO operations.
16
17use std::io::BufRead;
18use std::io::Write;
19use std::{fs, io, path};
20
21use tempfile::NamedTempFile;
22
23/// Reads the contents of a file line by line using buffered I/O.
24///
25/// This function opens the file at the specified path and returns an iterator over its lines,
26/// where each line is lazily read and returned as a `Result<String, io::Error>`. It utilizes
27/// a `BufReader` for efficient I/O operations.
28///
29/// # Parameters
30/// - `path`: The path to the file to be read. Accepts any type that implements `AsRef<Path>`.
31///
32/// # Returns
33/// - An `Result` containing an iterator over the lines of the file, where each line
34///   is represented as a `Result<String, io::Error>`.
35///
36/// # Examples
37/// ```no_run
38/// use regd_testing;
39///
40/// let lines = regd_testing::io::read_lines("Cargo.toml").expect("failed to open file");
41/// for line in lines {
42///     let line = line.expect("failed to read line");
43///     println!("{}", line);
44/// }
45/// ```
46pub fn read_lines(path: impl AsRef<path::Path>) -> io::Result<io::Lines<io::BufReader<fs::File>>> {
47    let file = fs::File::open(path.as_ref())?;
48    Ok(io::BufReader::new(file).lines())
49}
50
51/// Creates a new file at the specified path and writes the given content to it.
52///
53/// This function attempts to create a file at the provided path, writes the specified string
54/// content to it, and flushes the buffer to ensure all data is persisted to disk.
55///
56/// # Parameters
57/// - `path`: The target path for the new file. Accepts any type implementing `AsRef<Path>`.
58/// - `content`: The string content to write into the newly created file. Accepts any type implementing `AsRef<str>`.
59///
60/// # Returns
61/// - An `Result` containing the created `File` handle if successful, or an error if file creation or writing fails.
62///
63/// # Examples
64/// ```no_run
65/// use regd_testing;
66///
67/// let file = regd_testing::io::try_new_file("output.txt", "Hello, world!")
68///     .expect("failed to create or write to file");
69/// ```
70pub fn try_new_file(
71    path: impl AsRef<path::Path>,
72    content: impl AsRef<str>,
73) -> io::Result<fs::File> {
74    let mut file = fs::File::create(path.as_ref())?;
75    file.write_all(content.as_ref().as_bytes())?;
76    file.sync_all()?;
77    Ok(file)
78}
79
80/// Creates a new temporary file and writes the given content into it.
81///
82/// This function generates a temporary file using the system’s default temporary
83/// directory, writes the provided string content into it, and returns a handle to the file.
84/// The temporary file will be automatically deleted when dropped.
85///
86/// # Parameters
87/// - `content`: The string content to write into the temporary file. Accepts any type implementing `AsRef<str>`.
88///
89/// # Returns
90/// - An `Result` containing a `NamedTempFile` handle if the operation succeeds, or an error if it fails.
91///
92/// # Examples
93/// ```no_run
94/// use regd_testing;
95///
96/// let tempfile = regd_testing::io::try_new_tempfile("Temporary data")
97///     .expect("failed to create temporary file");
98///
99/// println!("Temp file path: {:?}", tempfile.path());
100/// ```
101pub fn try_new_tempfile(content: impl AsRef<str>) -> io::Result<NamedTempFile> {
102    let mut file = NamedTempFile::new()?;
103    writeln!(file, "{}", content.as_ref())?;
104    Ok(file)
105}
106
107/// Attempts to remove a file at the specified path, retrying up to 4 times on failure.
108///
109/// This function tries to delete the file located at the given path. If the removal
110/// fails (e.g., due to temporary filesystem locks or race conditions), it will retry
111/// up to four times before returning the final error.
112///
113/// # Parameters
114/// - `path`: The path to the file to be removed. Accepts any type implementing `AsRef<Path>`.
115///
116/// # Returns
117/// - An `Result` indicating success or failure. Returns `Ok(())` if the file is removed,
118///   or an `Err` if all attempts fail.
119///
120/// # Examples
121/// ```no_run
122/// use regd_testing;
123///
124/// regd_testing::io::try_remove_file("temp.txt").expect("failed to remove file");
125/// ```
126pub fn try_remove_file(path: impl AsRef<path::Path>) -> io::Result<()> {
127    for attempt in 1..=4 {
128        match fs::remove_file(path.as_ref()) {
129            Ok(_) => {
130                return Ok(());
131            }
132            Err(e) => {
133                if attempt < 4 {
134                    continue;
135                } else {
136                    return Err(e);
137                }
138            }
139        }
140    }
141    Ok(())
142}