Auto merge of #146043 - tgross35:rollup-hdumq5v, r=tgross35

Rollup of 4 pull requests

Successful merges:

 - rust-lang/rust#144964 (std: clarify `OpenOptions` error for create without write access)
 - rust-lang/rust#146030 (Fix `sys::process::windows::tests::test_thread_handle` spurious failure)
 - rust-lang/rust#146035 (Update `browser-ui-test` version to `0.21.3`)
 - rust-lang/rust#146036 (Use move_file for rename in tracing)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors
2025-08-30 23:51:29 +00:00
8 changed files with 117 additions and 32 deletions
+6 -1
View File
@@ -1614,6 +1614,10 @@ pub fn truncate(&mut self, truncate: bool) -> &mut Self {
/// See also [`std::fs::write()`][self::write] for a simple function to
/// create a file with some given data.
///
/// # Errors
///
/// If `.create(true)` is set without `.write(true)` or `.append(true)`,
/// calling [`open`](Self::open) will fail with [`InvalidInput`](io::ErrorKind::InvalidInput) error.
/// # Examples
///
/// ```no_run
@@ -1685,7 +1689,8 @@ pub fn create_new(&mut self, create_new: bool) -> &mut Self {
/// * [`AlreadyExists`]: `create_new` was specified and the file already
/// exists.
/// * [`InvalidInput`]: Invalid combinations of open options (truncate
/// without write access, no access mode set, etc.).
/// without write access, create without write or append access,
/// no access mode set, etc.).
///
/// The following errors don't match any existing [`io::ErrorKind`] at the moment:
/// * One of the directory components of the specified file path
+41 -15
View File
@@ -1265,12 +1265,7 @@ fn c<T: Clone>(t: &T) -> T {
let mut ra = OO::new();
ra.read(true).append(true);
#[cfg(windows)]
let invalid_options = 87; // ERROR_INVALID_PARAMETER
#[cfg(all(unix, not(target_os = "vxworks")))]
let invalid_options = "Invalid argument";
#[cfg(target_os = "vxworks")]
let invalid_options = "invalid argument";
let invalid_options = "creating or truncating a file requires write or append access";
// Test various combinations of creation modes and access modes.
//
@@ -1293,10 +1288,10 @@ fn c<T: Clone>(t: &T) -> T {
check!(c(&w).open(&tmpdir.join("a")));
// read-only
error!(c(&r).create_new(true).open(&tmpdir.join("b")), invalid_options);
error!(c(&r).create(true).truncate(true).open(&tmpdir.join("b")), invalid_options);
error!(c(&r).truncate(true).open(&tmpdir.join("b")), invalid_options);
error!(c(&r).create(true).open(&tmpdir.join("b")), invalid_options);
error_contains!(c(&r).create_new(true).open(&tmpdir.join("b")), invalid_options);
error_contains!(c(&r).create(true).truncate(true).open(&tmpdir.join("b")), invalid_options);
error_contains!(c(&r).truncate(true).open(&tmpdir.join("b")), invalid_options);
error_contains!(c(&r).create(true).open(&tmpdir.join("b")), invalid_options);
check!(c(&r).open(&tmpdir.join("a"))); // try opening the file created with write_only
// read-write
@@ -1308,21 +1303,21 @@ fn c<T: Clone>(t: &T) -> T {
// append
check!(c(&a).create_new(true).open(&tmpdir.join("d")));
error!(c(&a).create(true).truncate(true).open(&tmpdir.join("d")), invalid_options);
error!(c(&a).truncate(true).open(&tmpdir.join("d")), invalid_options);
error_contains!(c(&a).create(true).truncate(true).open(&tmpdir.join("d")), invalid_options);
error_contains!(c(&a).truncate(true).open(&tmpdir.join("d")), invalid_options);
check!(c(&a).create(true).open(&tmpdir.join("d")));
check!(c(&a).open(&tmpdir.join("d")));
// read-append
check!(c(&ra).create_new(true).open(&tmpdir.join("e")));
error!(c(&ra).create(true).truncate(true).open(&tmpdir.join("e")), invalid_options);
error!(c(&ra).truncate(true).open(&tmpdir.join("e")), invalid_options);
error_contains!(c(&ra).create(true).truncate(true).open(&tmpdir.join("e")), invalid_options);
error_contains!(c(&ra).truncate(true).open(&tmpdir.join("e")), invalid_options);
check!(c(&ra).create(true).open(&tmpdir.join("e")));
check!(c(&ra).open(&tmpdir.join("e")));
// Test opening a file without setting an access mode
let mut blank = OO::new();
error!(blank.create(true).open(&tmpdir.join("f")), invalid_options);
error_contains!(blank.create(true).open(&tmpdir.join("f")), invalid_options);
// Test write works
check!(check!(File::create(&tmpdir.join("h"))).write("foobar".as_bytes()));
@@ -2084,3 +2079,34 @@ fn test_rename_junction() {
// Junction links are always absolute so we just check the file name is correct.
assert_eq!(fs::read_link(&dest).unwrap().file_name(), Some(not_exist.as_os_str()));
}
#[test]
fn test_open_options_invalid_combinations() {
use crate::fs::OpenOptions as OO;
let test_cases: &[(fn() -> OO, &str)] = &[
(|| OO::new().create(true).read(true).clone(), "create without write"),
(|| OO::new().create_new(true).read(true).clone(), "create_new without write"),
(|| OO::new().truncate(true).read(true).clone(), "truncate without write"),
(|| OO::new().truncate(true).append(true).clone(), "truncate with append"),
];
for (make_opts, desc) in test_cases {
let opts = make_opts();
let result = opts.open("nonexistent.txt");
assert!(result.is_err(), "{desc} should fail");
let err = result.unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput, "{desc} - wrong error kind");
assert_eq!(
err.to_string(),
"creating or truncating a file requires write or append access",
"{desc} - wrong error message"
);
}
let result = OO::new().open("nonexistent.txt");
assert!(result.is_err(), "no access mode should fail");
let err = result.unwrap_err();
assert_eq!(err.kind(), ErrorKind::InvalidInput);
assert_eq!(err.to_string(), "must specify at least one of read, write, or append access");
}
+23 -3
View File
@@ -1123,7 +1123,21 @@ fn get_access_mode(&self) -> io::Result<c_int> {
(true, true, false) => Ok(libc::O_RDWR),
(false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND),
(true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND),
(false, false, false) => Err(Error::from_raw_os_error(libc::EINVAL)),
(false, false, false) => {
// If no access mode is set, check if any creation flags are set
// to provide a more descriptive error message
if self.create || self.create_new || self.truncate {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"creating or truncating a file requires write or append access",
))
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"must specify at least one of read, write, or append access",
))
}
}
}
}
@@ -1132,12 +1146,18 @@ fn get_creation_mode(&self) -> io::Result<c_int> {
(true, false) => {}
(false, false) => {
if self.truncate || self.create || self.create_new {
return Err(Error::from_raw_os_error(libc::EINVAL));
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"creating or truncating a file requires write or append access",
));
}
}
(_, true) => {
if self.truncate && !self.create_new {
return Err(Error::from_raw_os_error(libc::EINVAL));
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"creating or truncating a file requires write or append access",
));
}
}
}
+21 -3
View File
@@ -258,7 +258,19 @@ fn get_access_mode(&self) -> io::Result<u32> {
Ok(c::GENERIC_READ | (c::FILE_GENERIC_WRITE & !c::FILE_WRITE_DATA))
}
(false, false, false, None) => {
Err(Error::from_raw_os_error(c::ERROR_INVALID_PARAMETER as i32))
// If no access mode is set, check if any creation flags are set
// to provide a more descriptive error message
if self.create || self.create_new || self.truncate {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"creating or truncating a file requires write or append access",
))
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"must specify at least one of read, write, or append access",
))
}
}
}
}
@@ -268,12 +280,18 @@ fn get_creation_mode(&self) -> io::Result<u32> {
(true, false) => {}
(false, false) => {
if self.truncate || self.create || self.create_new {
return Err(Error::from_raw_os_error(c::ERROR_INVALID_PARAMETER as i32));
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"creating or truncating a file requires write or append access",
));
}
}
(_, true) => {
if self.truncate && !self.create_new {
return Err(Error::from_raw_os_error(c::ERROR_INVALID_PARAMETER as i32));
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"creating or truncating a file requires write or append access",
));
}
}
}
+18 -6
View File
@@ -1,7 +1,8 @@
use super::{Arg, make_command_line};
use crate::env;
use crate::ffi::{OsStr, OsString};
use crate::process::Command;
use crate::os::windows::io::AsHandle;
use crate::process::{Command, Stdio};
#[test]
fn test_raw_args() {
@@ -29,19 +30,30 @@ fn test_thread_handle() {
use crate::os::windows::process::{ChildExt, CommandExt};
const CREATE_SUSPENDED: u32 = 0x00000004;
let p = Command::new("cmd").args(&["/C", "exit 0"]).creation_flags(CREATE_SUSPENDED).spawn();
let p = Command::new("whoami").stdout(Stdio::null()).creation_flags(CREATE_SUSPENDED).spawn();
assert!(p.is_ok());
let mut p = p.unwrap();
// Ensure the process is killed in the event something goes wrong.
struct DropGuard(crate::process::Child);
impl Drop for DropGuard {
fn drop(&mut self) {
let _ = self.0.kill();
}
}
let mut p = DropGuard(p.unwrap());
let p = &mut p.0;
unsafe extern "system" {
fn ResumeThread(_: BorrowedHandle<'_>) -> u32;
unsafe fn ResumeThread(hHandle: BorrowedHandle<'_>) -> u32;
unsafe fn WaitForSingleObject(hHandle: BorrowedHandle<'_>, dwMilliseconds: u32) -> u32;
}
unsafe {
ResumeThread(p.main_thread_handle());
// Wait until the process exits or 1 minute passes.
// We don't bother checking the result here as that's done below using `try_wait`.
WaitForSingleObject(p.as_handle(), 1000 * 60);
}
crate::thread::sleep(crate::time::Duration::from_millis(100));
let res = p.try_wait();
assert!(res.is_ok());
assert!(res.unwrap().is_some());
+2 -2
View File
@@ -5,7 +5,7 @@
"packages": {
"": {
"dependencies": {
"browser-ui-test": "^0.21.1",
"browser-ui-test": "^0.21.3",
"es-check": "^6.2.1",
"eslint": "^8.57.1",
"eslint-js": "github:eslint/js",
@@ -555,7 +555,7 @@
}
},
"node_modules/browser-ui-test": {
"version": "0.21.1",
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/browser-ui-test/-/browser-ui-test-0.21.1.tgz",
"integrity": "sha512-b3a9mhALAmbP+GifoN/c295RPyuyfIUFMz0DtlBHbeDW5PYQTMHZZJtm7R2UyP6JiIQSkR+7227sS3maMGMUTg==",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"dependencies": {
"browser-ui-test": "^0.21.1",
"browser-ui-test": "^0.21.3",
"es-check": "^6.2.1",
"eslint": "^8.57.1",
"eslint-js": "github:eslint/js",
+5 -1
View File
@@ -168,7 +168,11 @@ pub struct TracingGuard {
impl TracingGuard {
pub fn copy_to_dir(self, dir: &std::path::Path) {
drop(self.guard);
std::fs::rename(&self.chrome_tracing_path, dir.join("chrome-trace.json")).unwrap();
crate::utils::helpers::move_file(
&self.chrome_tracing_path,
dir.join("chrome-trace.json"),
)
.unwrap();
}
}