Skip to content

Commit 8e7fb99

Browse files
committed
feat: Add gix_testtools::umask, safe but only meant for tests
This implements a function for tests to safely read the current process umask without the usual race condition of doing so, at the expense of using subprocesses to do it. This just calls a shell and runs `umask` from it (which is expected to be a builtin and, on many systems, is available as a builtin but not an executable). Even though this is safe, including thread-safe, it is unlikely to be suitable for use outside of tests, because of its use of `expect` and assertions when there are errors, combined with the possibly slow speed of using subprocesses. Given that this is effecitvely running a tiny shell script to do the work, why is it not instead a fixture script that is named in a `.gitignore` file so that it is not tracked? The reason is that the outcomes of running such fixture scripts are still saved across separate test runs, but it is useful to be able to run the tests with differnt umasks, e.g. `(umask 077; cargo nextest run ...)`. The immediate purpose is in forthcoming tests that, when checkout sets +x on an existing file, it doesn't set excessive permissions. The fix to pass such a test is not currently planned to use the umask explicitly. But the tests will use it, at least to detect when they cannot really verify the code under test on the grounds that they are running with an excessively permissive umask that doesn't allow behavior that only occurs with a generally reasonable umask to be observed.
1 parent 7ec21bb commit 8e7fb99

File tree

3 files changed

+42
-0
lines changed

3 files changed

+42
-0
lines changed

tests/tools/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,19 @@ pub fn size_ok(actual_size: usize, expected_64_bit_size: usize) -> bool {
944944
return actual_size <= expected_64_bit_size;
945945
}
946946

947+
/// Get the umask in a way that is safe, but may be too slow for use outside of tests.
948+
#[cfg(unix)]
949+
pub fn umask() -> u32 {
950+
let output = std::process::Command::new("/bin/sh")
951+
.args(["-c", "umask"])
952+
.output()
953+
.expect("can execute `sh -c umask`");
954+
assert!(output.status.success(), "`sh -c umask` failed");
955+
assert!(output.stderr.is_empty(), "`sh -c umask` unexpected message");
956+
let text = output.stdout.trim_ascii().to_str().expect("valid Unicode");
957+
u32::from_str_radix(text, 8).expect("parses as octal number")
958+
}
959+
947960
#[cfg(test)]
948961
mod tests {
949962
use super::*;

tests/tools/src/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
use std::{fs, io, io::prelude::*, path::PathBuf};
2+
23
fn mess_in_the_middle(path: PathBuf) -> io::Result<()> {
34
let mut file = fs::OpenOptions::new().read(false).write(true).open(path)?;
45
file.seek(io::SeekFrom::Start(file.metadata()?.len() / 2))?;
56
file.write_all(b"hello")?;
67
Ok(())
78
}
89

10+
#[cfg(unix)]
11+
fn umask() -> io::Result<()> {
12+
println!("{:04o}", gix_testtools::umask());
13+
Ok(())
14+
}
15+
916
fn main() -> Result<(), Box<dyn std::error::Error>> {
1017
let mut args = std::env::args().skip(1);
1118
let scmd = args.next().expect("sub command");
1219
match &*scmd {
1320
"mess-in-the-middle" => mess_in_the_middle(PathBuf::from(args.next().expect("path to file to mess with")))?,
21+
#[cfg(unix)]
22+
"umask" => umask()?,
1423
_ => unreachable!("Unknown subcommand: {}", scmd),
1524
};
1625
Ok(())

tests/tools/tests/umask.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::fs::File;
2+
use std::io::{BufRead, BufReader};
3+
4+
use bstr::ByteSlice;
5+
6+
#[test]
7+
#[cfg(unix)]
8+
#[cfg_attr(not(target_os = "linux"), ignore = "The test itself uses /proc")]
9+
fn umask() {
10+
// Check against the umask obtained via a less portable but also completely safe method.
11+
let less_portable = BufReader::new(File::open("/proc/self/status").expect("can open"))
12+
.split(b'\n')
13+
.find_map(|line| line.expect("can read").strip_prefix(b"Umask:\t").map(ToOwned::to_owned))
14+
.expect("has umask line")
15+
.to_str()
16+
.expect("umask line is valid UTF-8")
17+
.to_owned();
18+
let more_portable = format!("{:04o}", gix_testtools::umask());
19+
assert_eq!(more_portable, less_portable);
20+
}

0 commit comments

Comments
 (0)