1#![warn(missing_docs)]
5#![forbid(unsafe_code)]
6use log::LevelFilter;
7use rand::distributions::Alphanumeric;
8use rand::rngs::StdRng;
9use rand::Rng;
10use rand::RngCore;
11use rand::SeedableRng;
12use std::cmp;
13use std::env;
14use std::fs::OpenOptions;
15
16use std::{
17 fs::{self, File},
18 process::Command,
19};
20use std::{
21 io::{self, BufWriter, Write},
22 path::{Path, PathBuf},
23};
24
25use anyhow::Result;
26use xvc_logging::{setup_logging, watch};
27
28#[cfg(unix)]
29use std::os::unix::fs as unix_fs;
30#[cfg(windows)]
31use std::os::windows::fs as windows_fs;
32
33pub fn test_logging(level: LevelFilter) {
37 setup_logging(Some(level), Some(level));
38}
39
40fn interleave_with_dash(s: &str, n: usize) -> String {
41 let chars: Vec<char> = s.chars().collect();
42 let chunks: Vec<String> = chars
43 .chunks(n)
44 .map(|chunk| chunk.iter().collect())
45 .collect();
46 chunks.join("-")
47}
48
49pub fn random_dir_name(prefix: &str, seed: Option<u64>) -> String {
52 let mut rng = if let Some(seed) = seed {
53 rand::rngs::StdRng::seed_from_u64(seed)
54 } else {
55 rand::rngs::StdRng::from_entropy()
56 };
57
58 let rand: u32 = rng.next_u32();
59 format!("{}-{}", prefix, interleave_with_dash(&rand.to_string(), 3))
60}
61
62pub fn random_temp_dir(prefix: Option<&str>) -> PathBuf {
65 let mut temp_dir = env::temp_dir();
66 loop {
67 let cand = temp_dir.join(Path::new(&random_dir_name(
68 prefix.unwrap_or("xvc-repo"),
69 None,
70 )));
71 if !cand.exists() {
72 temp_dir = cand;
73 break;
74 }
75 }
76
77 temp_dir
78}
79
80pub fn seeded_temp_dir(prefix: &str, seed: Option<u64>) -> PathBuf {
84 let temp_dir = env::temp_dir();
85 temp_dir.join(Path::new(&random_dir_name(prefix, seed)))
86}
87
88pub fn create_temp_dir() -> PathBuf {
90 let temp_dir = random_temp_dir(None);
91
92 fs::create_dir_all(&temp_dir).expect("Cannot create directory.");
93 temp_dir
94}
95
96pub fn run_in_temp_dir() -> PathBuf {
98 let temp_dir = create_temp_dir();
99 watch!(temp_dir);
100 env::set_current_dir(&temp_dir).expect("Cannot change directory");
101 temp_dir
102}
103
104pub fn run_in_temp_git_dir() -> PathBuf {
106 let temp_dir = run_in_temp_dir();
107 let output = Command::new("git")
108 .arg("init")
109 .output()
110 .unwrap_or_else(|e| panic!("failed to execute process: {}", e));
111 watch!(output);
112 temp_dir
113}
114
115pub fn temp_git_dir() -> PathBuf {
117 let temp_dir = create_temp_dir();
118 watch!(temp_dir);
119 Command::new("git")
120 .arg("-C")
121 .arg(temp_dir.as_os_str())
122 .arg("init")
123 .output()
124 .unwrap_or_else(|e| panic!("failed to execute process: {}", e));
125 temp_dir
126}
127
128pub fn generate_random_file(filename: &Path, size: usize, seed: Option<u64>) {
130 let f = OpenOptions::new()
131 .create(true)
132 .truncate(true)
133 .write(true)
134 .open(filename)
135 .unwrap();
136 let mut writer = BufWriter::new(f);
137
138 let mut rng: StdRng = seed
139 .map(StdRng::seed_from_u64)
140 .unwrap_or_else(StdRng::from_entropy);
141 let mut buffer = [0u8; 1024];
142 let mut remaining_size = size;
143
144 while remaining_size > 0 {
145 let to_write = cmp::min(remaining_size, buffer.len());
146 let buffer = &mut buffer[0..to_write];
147 rng.fill(buffer);
148 writer.write_all(buffer).unwrap();
149
150 remaining_size -= to_write;
151 }
152}
153
154pub fn generate_filled_file(filename: &Path, size: usize, byte: u8) {
156 let f = File::create(filename).unwrap();
157 let mut writer = BufWriter::new(f);
158 let buffer = [byte; 1024];
159 let mut remaining_size = size;
160 while remaining_size > 0 {
161 let to_write = cmp::min(remaining_size, buffer.len());
162 let buffer = &buffer[0..to_write];
163 writer.write_all(buffer).unwrap();
164 remaining_size -= to_write;
165 }
166}
167
168pub fn generate_random_text_file(filename: &Path, num_lines: usize) {
170 let mut f = File::create(filename).unwrap();
171 let rng = rand::thread_rng();
172 let line_length = 100;
173 for _ in 0..num_lines {
174 let line: String = rng
175 .clone()
176 .sample_iter(&Alphanumeric)
177 .take(line_length)
178 .map(char::from)
179 .collect();
180 writeln!(f, "{}\n", line).expect("Could not write to file.");
181 }
182}
183
184pub fn create_directory_tree(
187 root: &Path,
188 n_dirs: usize,
189 n_files_per_dir: usize,
190 min_size: usize,
191 seed: Option<u64>,
192) -> Result<Vec<PathBuf>> {
193 let mut paths = Vec::<PathBuf>::with_capacity(n_dirs * n_files_per_dir);
194 let dirs: Vec<String> = (1..=n_dirs).map(|i| format!("dir-{:04}", i)).collect();
195 let files: Vec<(String, usize)> = (1..=n_files_per_dir)
196 .map(|i| (format!("file-{:04}.bin", i), min_size + i + 1000))
197 .collect();
198 for dir in dirs {
199 std::fs::create_dir_all(root.join(Path::new(&dir)))?;
200 paths.extend(files.iter().map(|(name, size)| {
201 let filename = PathBuf::from(&format!("{}/{}/{}", root.to_string_lossy(), dir, name));
202 generate_random_file(&filename, *size, seed);
203 filename
204 }));
205 }
206 Ok(paths)
207}
208
209#[cfg(unix)]
210pub fn make_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
212 unix_fs::symlink(original, link)
213}
214
215#[cfg(windows)]
216pub fn make_symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
218 windows_fs::symlink_file(original, link)
219}