pub mod backend;
pub mod builder;
pub use backend::{BufferBackend, ReadOnlyBackend, StreamBackend, WriteOnlyBackend};
pub use builder::{IntoStream, StreamBuilder};
use crate::{svn_result, with_tmp_pool, Error};
use std::ffi::OsStr;
use std::marker::PhantomData;
use subversion_sys::svn_io_dirent2_t;
#[allow(dead_code)]
pub struct Dirent(*const svn_io_dirent2_t);
impl From<*const svn_io_dirent2_t> for Dirent {
fn from(ptr: *const svn_io_dirent2_t) -> Self {
Self(ptr)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileDel {
None,
OnClose,
OnPoolCleanup,
}
impl From<FileDel> for subversion_sys::svn_io_file_del_t {
fn from(file_del: FileDel) -> Self {
match file_del {
FileDel::None => subversion_sys::svn_io_file_del_t_svn_io_file_del_none,
FileDel::OnClose => subversion_sys::svn_io_file_del_t_svn_io_file_del_on_close,
FileDel::OnPoolCleanup => {
subversion_sys::svn_io_file_del_t_svn_io_file_del_on_pool_cleanup
}
}
}
}
impl From<subversion_sys::svn_io_file_del_t> for FileDel {
fn from(file_del: subversion_sys::svn_io_file_del_t) -> Self {
match file_del {
subversion_sys::svn_io_file_del_t_svn_io_file_del_none => FileDel::None,
subversion_sys::svn_io_file_del_t_svn_io_file_del_on_close => FileDel::OnClose,
subversion_sys::svn_io_file_del_t_svn_io_file_del_on_pool_cleanup => {
FileDel::OnPoolCleanup
}
_ => unreachable!(),
}
}
}
pub struct Mark {
ptr: *mut subversion_sys::svn_stream_mark_t,
_pool: apr::Pool<'static>,
_phantom: PhantomData<*mut ()>, }
impl Drop for Mark {
fn drop(&mut self) {
}
}
impl Mark {
pub fn as_mut_ptr(&mut self) -> *mut subversion_sys::svn_stream_mark_t {
self.ptr
}
pub fn as_ptr(&self) -> *const subversion_sys::svn_stream_mark_t {
self.ptr
}
pub(crate) unsafe fn from_ptr_and_pool(
ptr: *mut subversion_sys::svn_stream_mark_t,
pool: apr::Pool<'static>,
) -> Self {
Self {
ptr,
_pool: pool,
_phantom: PhantomData,
}
}
}
pub struct StringBuf {
ptr: *mut subversion_sys::svn_stringbuf_t,
_pool: apr::Pool<'static>,
_phantom: PhantomData<*mut ()>, }
impl Drop for StringBuf {
fn drop(&mut self) {
}
}
impl Default for StringBuf {
fn default() -> Self {
Self::new()
}
}
impl TryFrom<&str> for StringBuf {
type Error = std::ffi::NulError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let pool = apr::Pool::new();
let cstr = std::ffi::CString::new(s)?;
let ptr = unsafe { subversion_sys::svn_stringbuf_create(cstr.as_ptr(), pool.as_mut_ptr()) };
Ok(Self {
ptr,
_pool: pool,
_phantom: PhantomData,
})
}
}
impl StringBuf {
pub fn new() -> Self {
let pool = apr::Pool::new();
let ptr = unsafe { subversion_sys::svn_stringbuf_create_empty(pool.as_mut_ptr()) };
Self {
ptr,
_pool: pool,
_phantom: PhantomData,
}
}
pub fn as_bytes(&self) -> &[u8] {
unsafe {
let stringbuf = &*self.ptr;
std::slice::from_raw_parts(stringbuf.data as *const u8, stringbuf.len)
}
}
pub fn len(&self) -> usize {
unsafe { (*self.ptr).len }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn as_mut_ptr(&mut self) -> *mut subversion_sys::svn_stringbuf_t {
self.ptr
}
}
impl std::fmt::Display for StringBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", String::from_utf8_lossy(self.as_bytes()))
}
}
pub struct Stream {
ptr: *mut subversion_sys::svn_stream_t,
_pool: apr::Pool<'static>,
needs_close: bool, _phantom: PhantomData<*mut ()>, }
impl Drop for Stream {
fn drop(&mut self) {
if self.needs_close {
unsafe {
let _ = subversion_sys::svn_stream_close(self.ptr);
}
}
}
}
impl Stream {
pub fn empty() -> Self {
let pool = apr::Pool::new();
let stream = unsafe { subversion_sys::svn_stream_empty(pool.as_mut_ptr()) };
Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
pub fn buffered() -> Self {
let pool = apr::Pool::new();
let stream = unsafe { subversion_sys::svn_stream_buffered(pool.as_mut_ptr()) };
Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
pub fn from_stringbuf(stringbuf: &mut StringBuf) -> Self {
let pool = apr::Pool::new();
let stream = unsafe {
subversion_sys::svn_stream_from_stringbuf(stringbuf.as_mut_ptr(), pool.as_mut_ptr())
};
Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
pub fn from_ptr(ptr: *mut subversion_sys::svn_stream_t, pool: apr::Pool<'static>) -> Self {
Self {
ptr,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
pub fn from_backend<B: StreamBackend>(backend: B) -> Result<Self, Error<'static>> {
let pool = apr::Pool::new();
let supports_reset = backend.supports_reset();
let supports_mark = backend.supports_mark();
let backend_ptr = Box::into_raw(Box::new(backend)) as *mut std::ffi::c_void;
let stream = unsafe { subversion_sys::svn_stream_create(backend_ptr, pool.as_mut_ptr()) };
unsafe {
extern "C" fn read_trampoline<B: StreamBackend>(
baton: *mut std::ffi::c_void,
buffer: *mut std::os::raw::c_char,
len: *mut apr_sys::apr_size_t,
) -> *mut subversion_sys::svn_error_t {
unsafe {
let backend = &mut *(baton as *mut B);
let requested_len = *len;
let buf = std::slice::from_raw_parts_mut(buffer as *mut u8, requested_len);
match backend.read(buf) {
Ok(bytes_read) => {
*len = bytes_read;
std::ptr::null_mut()
}
Err(mut e) => e.detach(),
}
}
}
extern "C" fn write_trampoline<B: StreamBackend>(
baton: *mut std::ffi::c_void,
data: *const std::os::raw::c_char,
len: *mut apr_sys::apr_size_t,
) -> *mut subversion_sys::svn_error_t {
unsafe {
let backend = &mut *(baton as *mut B);
let requested_len = *len;
let buf = std::slice::from_raw_parts(data as *const u8, requested_len);
match backend.write(buf) {
Ok(bytes_written) => {
*len = bytes_written;
std::ptr::null_mut()
}
Err(mut e) => e.detach(),
}
}
}
extern "C" fn close_trampoline<B: StreamBackend>(
baton: *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t {
unsafe {
if !baton.is_null() {
let backend = &mut *(baton as *mut B);
let result = match backend.close() {
Ok(()) => std::ptr::null_mut(),
Err(mut e) => e.detach(),
};
let _ = Box::from_raw(baton as *mut B);
result
} else {
std::ptr::null_mut()
}
}
}
extern "C" fn skip_trampoline<B: StreamBackend>(
baton: *mut std::ffi::c_void,
len: apr_sys::apr_size_t,
) -> *mut subversion_sys::svn_error_t {
unsafe {
let backend = &mut *(baton as *mut B);
match backend.skip(len) {
Ok(_) => std::ptr::null_mut(),
Err(mut e) => e.detach(),
}
}
}
extern "C" fn data_available_trampoline<B: StreamBackend>(
baton: *mut std::ffi::c_void,
data_available: *mut subversion_sys::svn_boolean_t,
) -> *mut subversion_sys::svn_error_t {
unsafe {
let backend = &mut *(baton as *mut B);
match backend.data_available() {
Ok(available) => {
*data_available = if available { 1 } else { 0 };
std::ptr::null_mut()
}
Err(mut e) => e.detach(),
}
}
}
extern "C" fn mark_trampoline<B: StreamBackend>(
baton: *mut std::ffi::c_void,
mark: *mut *mut subversion_sys::svn_stream_mark_t,
pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
unsafe {
let backend = &mut *(baton as *mut B);
match backend.mark() {
Ok(stream_mark) => {
let mark_size =
std::mem::size_of::<subversion_sys::svn_stream_mark_t>();
let mark_ptr = apr_sys::apr_palloc(pool, mark_size)
as *mut subversion_sys::svn_stream_mark_t;
*(mark_ptr as *mut u64) = stream_mark.position;
*mark = mark_ptr;
std::ptr::null_mut()
}
Err(mut e) => e.detach(),
}
}
}
extern "C" fn seek_trampoline<B: StreamBackend>(
baton: *mut std::ffi::c_void,
mark: *const subversion_sys::svn_stream_mark_t,
) -> *mut subversion_sys::svn_error_t {
unsafe {
let backend = &mut *(baton as *mut B);
if mark.is_null() {
match backend.reset() {
Ok(()) => return std::ptr::null_mut(),
Err(mut e) => return e.detach(),
}
}
let position = *(mark as *const u64);
let stream_mark = crate::io::backend::StreamMark { position };
match backend.seek(&stream_mark) {
Ok(()) => std::ptr::null_mut(),
Err(mut e) => e.detach(),
}
}
}
subversion_sys::svn_stream_set_read(stream, Some(read_trampoline::<B>));
subversion_sys::svn_stream_set_write(stream, Some(write_trampoline::<B>));
subversion_sys::svn_stream_set_close(stream, Some(close_trampoline::<B>));
subversion_sys::svn_stream_set_skip(stream, Some(skip_trampoline::<B>));
subversion_sys::svn_stream_set_data_available(
stream,
Some(data_available_trampoline::<B>),
);
if supports_reset && supports_mark {
subversion_sys::svn_stream_set_mark(stream, Some(mark_trampoline::<B>));
subversion_sys::svn_stream_set_seek(stream, Some(seek_trampoline::<B>));
}
}
Ok(Self {
ptr: stream,
_pool: pool,
needs_close: false, _phantom: PhantomData,
})
}
pub fn disown(&mut self) -> Self {
let pool = apr::Pool::new();
let stream = unsafe { subversion_sys::svn_stream_disown(self.ptr, pool.as_mut_ptr()) };
Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
pub unsafe fn from_apr_file(file: *mut apr_sys::apr_file_t, disown: bool) -> Self {
let pool = apr::Pool::new();
let stream = unsafe {
subversion_sys::svn_stream_from_aprfile2(file, disown as i32, pool.as_mut_ptr())
};
Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
pub fn as_mut_ptr(&mut self) -> *mut subversion_sys::svn_stream_t {
self.ptr
}
pub fn as_ptr(&self) -> *const subversion_sys::svn_stream_t {
self.ptr
}
pub(crate) unsafe fn from_ptr_and_pool(
ptr: *mut subversion_sys::svn_stream_t,
pool: apr::Pool<'static>,
) -> Self {
Self {
ptr,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
pub fn open_readonly(path: &std::path::Path) -> Result<Self, Error<'static>> {
let pool = apr::Pool::new();
let mut stream = std::ptr::null_mut();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
with_tmp_pool(|scratch_pool| {
let err = unsafe {
subversion_sys::svn_stream_open_readonly(
&mut stream,
path.as_ptr(),
pool.as_mut_ptr(),
scratch_pool.as_mut_ptr(),
)
};
svn_result(err)
})?;
Ok(Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
})
}
pub fn open_writable(path: &std::path::Path) -> Result<Self, Error<'static>> {
let pool = apr::Pool::new();
let mut stream = std::ptr::null_mut();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
with_tmp_pool(|scratch_pool| {
let err = unsafe {
subversion_sys::svn_stream_open_writable(
&mut stream,
path.as_ptr(),
pool.as_mut_ptr(),
scratch_pool.as_mut_ptr(),
)
};
svn_result(err)
})?;
Ok(Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
})
}
pub fn open_unique(
dirpath: &std::path::Path,
when: FileDel,
) -> Result<(std::path::PathBuf, Self), Error<'static>> {
let dirpath = std::ffi::CString::new(dirpath.to_str().unwrap()).unwrap();
let mut stream = std::ptr::null_mut();
let mut path = std::ptr::null();
let pool = apr::pool::Pool::new();
let scratch_pool = apr::pool::Pool::new();
let err = unsafe {
subversion_sys::svn_stream_open_unique(
&mut stream,
&mut path,
dirpath.as_ptr(),
when.into(),
pool.as_mut_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok((
std::path::PathBuf::from(unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }),
unsafe { Self::from_ptr_and_pool(stream, pool) },
))
}
pub fn stdin(buffered: bool) -> Result<Self, Error<'static>> {
let pool = apr::Pool::new();
let mut stream = std::ptr::null_mut();
let err = unsafe {
subversion_sys::svn_stream_for_stdin2(&mut stream, buffered as i32, pool.as_mut_ptr())
};
svn_result(err)?;
Ok(Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
})
}
pub fn stderr() -> Result<Self, Error<'static>> {
let pool = apr::Pool::new();
let mut stream = std::ptr::null_mut();
let err = unsafe { subversion_sys::svn_stream_for_stderr(&mut stream, pool.as_mut_ptr()) };
svn_result(err)?;
Ok(Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
})
}
pub fn stdout() -> Result<Self, Error<'static>> {
let pool = apr::Pool::new();
let mut stream = std::ptr::null_mut();
let err = unsafe { subversion_sys::svn_stream_for_stdout(&mut stream, pool.as_mut_ptr()) };
svn_result(err)?;
Ok(Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
})
}
pub fn checksummed(
&mut self,
checksum_kind: crate::ChecksumKind,
read_all: bool,
) -> (crate::io::Stream, crate::Checksum<'_>, crate::Checksum<'_>) {
let mut read_checksum = std::ptr::null_mut();
let mut write_checksum = std::ptr::null_mut();
let pool = apr::pool::Pool::new();
let stream = unsafe {
subversion_sys::svn_stream_checksummed2(
self.ptr,
&mut read_checksum,
&mut write_checksum,
checksum_kind.into(),
read_all as i32,
pool.as_mut_ptr(),
)
};
(
unsafe { crate::io::Stream::from_ptr_and_pool(stream, pool) },
crate::Checksum {
ptr: read_checksum,
_pool: std::marker::PhantomData,
},
crate::Checksum {
ptr: write_checksum,
_pool: std::marker::PhantomData,
},
)
}
pub fn compressed(&mut self) -> Self {
let pool = apr::Pool::new();
let stream = unsafe { subversion_sys::svn_stream_compressed(self.ptr, pool.as_mut_ptr()) };
Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
pub fn contents_checksum(
&mut self,
checksum_kind: crate::ChecksumKind,
) -> Result<crate::Checksum<'_>, Error<'_>> {
let mut checksum = std::ptr::null_mut();
let pool = apr::pool::Pool::new();
let scratch_pool = apr::pool::Pool::new();
let err = unsafe {
subversion_sys::svn_stream_contents_checksum(
&mut checksum,
self.ptr,
checksum_kind.into(),
pool.as_mut_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(crate::Checksum {
ptr: checksum,
_pool: std::marker::PhantomData,
})
}
pub fn read_full(&mut self, buf: &mut [u8]) -> Result<usize, Error<'static>> {
let mut len = buf.len();
let err = unsafe {
subversion_sys::svn_stream_read_full(self.ptr, buf.as_mut_ptr() as *mut i8, &mut len)
};
Error::from_raw(err)?;
Ok(len)
}
pub fn supports_partial_read(&mut self) -> bool {
unsafe { subversion_sys::svn_stream_supports_partial_read(self.ptr) != 0 }
}
pub fn skip(&mut self, len: usize) -> Result<(), Error<'static>> {
let err = unsafe { subversion_sys::svn_stream_skip(self.ptr, len) };
Error::from_raw(err)?;
Ok(())
}
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<'static>> {
let mut len = buf.len();
let err = unsafe {
subversion_sys::svn_stream_read(self.ptr, buf.as_mut_ptr() as *mut i8, &mut len)
};
Error::from_raw(err)?;
Ok(len)
}
pub fn write(&mut self, buf: &[u8]) -> Result<(), Error<'static>> {
let mut len = buf.len();
let err = unsafe {
subversion_sys::svn_stream_write(self.ptr, buf.as_ptr() as *const i8, &mut len)
};
Error::from_raw(err)?;
Ok(())
}
pub fn close(&mut self) -> Result<(), Error<'static>> {
self.needs_close = false; let err = unsafe { subversion_sys::svn_stream_close(self.ptr) };
Error::from_raw(err)?;
Ok(())
}
pub fn supports_mark(&mut self) -> bool {
unsafe { subversion_sys::svn_stream_supports_mark(self.ptr) != 0 }
}
pub fn mark(&mut self) -> Result<Mark, Error<'static>> {
let mut mark = std::ptr::null_mut();
let pool = apr::pool::Pool::new();
let err =
unsafe { subversion_sys::svn_stream_mark(self.ptr, &mut mark, pool.as_mut_ptr()) };
svn_result(err)?;
Ok(unsafe { Mark::from_ptr_and_pool(mark, pool) })
}
pub fn seek(&mut self, mark: &Mark) -> Result<(), Error<'static>> {
let err = unsafe { subversion_sys::svn_stream_seek(self.ptr, mark.as_ptr()) };
Error::from_raw(err)?;
Ok(())
}
pub fn reset(&mut self) -> Result<(), Error<'static>> {
let err = unsafe { subversion_sys::svn_stream_reset(self.ptr) };
Error::from_raw(err)?;
Ok(())
}
pub fn supports_reset(&self) -> bool {
unsafe { subversion_sys::svn_stream_supports_reset(self.ptr) != 0 }
}
pub fn data_available(&mut self) -> Result<bool, Error<'static>> {
let mut available = 0;
let err = unsafe { subversion_sys::svn_stream_data_available(self.ptr, &mut available) };
Error::from_raw(err)?;
Ok(available != 0)
}
pub fn readline(&mut self, eol: &str) -> Result<Option<String>, Error<'_>> {
let pool = apr::Pool::new();
let mut stringbuf = std::ptr::null_mut();
let mut eof = 0;
let eol_cstr = std::ffi::CString::new(eol).unwrap();
let err = unsafe {
subversion_sys::svn_stream_readline(
self.ptr,
&mut stringbuf,
eol_cstr.as_ptr(),
&mut eof,
pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
if eof != 0 && stringbuf.is_null() {
Ok(None)
} else {
unsafe {
let svn_stringbuf = &*stringbuf;
let data_slice = std::slice::from_raw_parts(
svn_stringbuf.data as *const u8,
svn_stringbuf.len as usize,
);
Ok(Some(String::from_utf8_lossy(data_slice).into_owned()))
}
}
}
pub fn puts(&mut self, s: &str) -> Result<(), Error<'static>> {
let s = std::ffi::CString::new(s).unwrap();
let err = unsafe { subversion_sys::svn_stream_puts(self.ptr, s.as_ptr()) };
Error::from_raw(err)?;
Ok(())
}
pub fn printf(
&mut self,
format: &str,
args: &[&dyn std::fmt::Display],
) -> Result<(), Error<'static>> {
let mut formatted = String::new();
let mut arg_iter = args.iter();
let mut chars = format.chars().peekable();
while let Some(c) = chars.next() {
if c == '%' {
if chars.peek() == Some(&'%') {
chars.next();
formatted.push('%');
} else {
for spec_char in chars.by_ref() {
if spec_char.is_alphabetic() {
if let Some(arg) = arg_iter.next() {
formatted.push_str(&arg.to_string());
}
break;
}
}
}
} else {
formatted.push(c);
}
}
self.puts(&formatted)
}
pub fn printf_from_utf8(
&mut self,
format: &str,
args: &[&dyn std::fmt::Display],
) -> Result<(), Error<'static>> {
self.printf(format, args)
}
pub fn from_reader<R: std::io::Read + 'static>(mut reader: R) -> Result<Self, Error<'static>> {
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).map_err(Error::from)?;
Ok(Stream::from(buffer))
}
}
impl std::io::Write for Stream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self.write(buf) {
Ok(_) => Ok(buf.len()),
Err(e) => Err(std::io::Error::other(e.to_string())),
}
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl std::io::Read for Stream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self.read(buf) {
Ok(bytes_read) => Ok(bytes_read),
Err(e) => Err(std::io::Error::other(e.to_string())),
}
}
}
pub fn tee(out1: &mut Stream, out2: &mut Stream) -> Result<Stream, Error<'static>> {
let pool = apr::Pool::new();
let stream = unsafe { subversion_sys::svn_stream_tee(out1.ptr, out2.ptr, pool.as_mut_ptr()) };
Ok(Stream {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
})
}
pub fn streams_contents_same(
stream1: &mut Stream,
stream2: &mut Stream,
) -> Result<bool, Error<'static>> {
let mut same = 0;
let pool = apr::Pool::new();
let err = unsafe {
subversion_sys::svn_stream_contents_same2(
&mut same,
stream1.ptr,
stream2.ptr,
pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(same != 0)
}
pub fn copy_stream(
from: &mut Stream,
to: &mut Stream,
cancel_func: Option<&impl Fn() -> Result<(), Error<'static>>>,
) -> Result<(), Error<'static>> {
stream_copy(from, to, cancel_func)
}
pub fn read_stream_to_string(stream: &mut Stream) -> Result<String, Error<'static>> {
string_from_stream(stream)
}
impl From<&[u8]> for Stream {
fn from(bytes: &[u8]) -> Self {
let pool = apr::Pool::new();
let svn_str = unsafe {
subversion_sys::svn_string_ncreate(
bytes.as_ptr() as *const i8,
bytes.len(),
pool.as_mut_ptr(),
)
};
let stream = unsafe { subversion_sys::svn_stream_from_string(svn_str, pool.as_mut_ptr()) };
Self {
ptr: stream,
_pool: pool,
needs_close: false,
_phantom: PhantomData,
}
}
}
impl From<String> for Stream {
fn from(s: String) -> Self {
Stream::from(s.as_bytes())
}
}
impl From<&str> for Stream {
fn from(s: &str) -> Self {
Stream::from(s.as_bytes())
}
}
impl From<Vec<u8>> for Stream {
fn from(bytes: Vec<u8>) -> Self {
Stream::from(bytes.as_slice())
}
}
pub fn remove_file(path: &std::path::Path, ignore_enoent: bool) -> Result<(), Error<'static>> {
let pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_remove_file2(path.as_ptr(), ignore_enoent as i32, pool.as_mut_ptr())
};
Error::from_raw(err)?;
Ok(())
}
pub fn wrap_write(write: &mut dyn std::io::Write) -> Result<Stream, Error<'static>> {
let write = Box::into_raw(Box::new(write));
let pool = apr::Pool::new();
let stream = unsafe {
subversion_sys::svn_stream_create(write as *mut std::ffi::c_void, pool.as_mut_ptr())
};
extern "C" fn write_fn(
baton: *mut std::ffi::c_void,
buffer: *const i8,
len: *mut usize,
) -> *mut subversion_sys::svn_error_t {
let write = unsafe { Box::from_raw(baton as *mut &mut dyn std::io::Write) };
let write = Box::leak(write);
let buffer = unsafe { std::slice::from_raw_parts(buffer as *const u8, *len) };
match write.write(buffer) {
Ok(bytes_written) => {
unsafe { *len = bytes_written };
std::ptr::null_mut()
}
Err(e) => {
let mut e: crate::Error = e.into();
unsafe { e.detach() }
}
}
}
extern "C" fn close_fn(baton: *mut std::ffi::c_void) -> *mut subversion_sys::svn_error_t {
let write = unsafe { Box::from_raw(baton as *mut &mut dyn std::io::Write) };
match write.flush() {
Ok(_) => std::ptr::null_mut(),
Err(e) => {
let mut e: crate::Error = e.into();
unsafe { e.detach() }
}
}
}
unsafe {
subversion_sys::svn_stream_set_write(stream, Some(write_fn));
subversion_sys::svn_stream_set_close(stream, Some(close_fn));
}
Ok(Stream {
ptr: stream,
_pool: pool,
needs_close: true, _phantom: PhantomData,
})
}
pub fn create_uniqe_link(
path: &std::path::Path,
dest: &std::path::Path,
suffix: &OsStr,
) -> Result<std::path::PathBuf, Error<'static>> {
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap();
let suffix = std::ffi::CString::new(suffix.to_str().unwrap()).unwrap();
let pool = apr::pool::Pool::new();
let mut result = std::ptr::null();
let err = unsafe {
subversion_sys::svn_io_create_unique_link(
&mut result,
path.as_ptr(),
dest.as_ptr(),
suffix.as_ptr(),
pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(std::path::PathBuf::from(unsafe {
std::ffi::CStr::from_ptr(result).to_str().unwrap()
}))
}
pub fn read_link(path: &std::path::Path) -> Result<std::path::PathBuf, Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let mut target = std::ptr::null_mut();
let err = unsafe {
subversion_sys::svn_io_read_link(&mut target, path.as_ptr(), scratch_pool.as_mut_ptr())
};
Error::from_raw(err)?;
let ptr = unsafe { (*target).data as *const u8 };
let len = unsafe { (*target).len };
let target = unsafe { std::slice::from_raw_parts(ptr, len) };
Ok(std::path::PathBuf::from(
std::str::from_utf8(target).unwrap(),
))
}
pub fn temp_dir() -> Result<std::path::PathBuf, Error<'static>> {
let pool = apr::pool::Pool::new();
let mut path = std::ptr::null();
let err = unsafe { subversion_sys::svn_io_temp_dir(&mut path, pool.as_mut_ptr()) };
Error::from_raw(err)?;
Ok(std::path::PathBuf::from(unsafe {
std::ffi::CStr::from_ptr(path).to_str().unwrap()
}))
}
pub fn copy_file(
src: &std::path::Path,
dest: &std::path::Path,
copy_perms: bool,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap();
let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_copy_file(
src.as_ptr(),
dest.as_ptr(),
copy_perms as i32,
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn copy_perms(src: &std::path::Path, dest: &std::path::Path) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap();
let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_copy_perms(src.as_ptr(), dest.as_ptr(), scratch_pool.as_mut_ptr())
};
Error::from_raw(err)?;
Ok(())
}
pub fn copy_link(src: &std::path::Path, dest: &std::path::Path) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap();
let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_copy_link(src.as_ptr(), dest.as_ptr(), scratch_pool.as_mut_ptr())
};
Error::from_raw(err)?;
Ok(())
}
pub fn copy_dir_recursively(
src: &std::path::Path,
dst_path: &std::path::Path,
dst_basename: &std::ffi::OsStr,
copy_perms: bool,
cancel_func: Option<&impl Fn() -> Result<(), Error<'static>>>,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap();
let dst_path = std::ffi::CString::new(dst_path.to_str().unwrap()).unwrap();
let dst_basename = std::ffi::CString::new(dst_basename.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_copy_dir_recursively(
src.as_ptr(),
dst_path.as_ptr(),
dst_basename.as_ptr(),
copy_perms as i32,
if cancel_func.is_some() {
Some(crate::wrap_cancel_func)
} else {
None
},
if let Some(cancel_func) = cancel_func {
&cancel_func as *const _ as *mut std::ffi::c_void
} else {
std::ptr::null_mut()
},
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn make_dir_recursively(path: &std::path::Path) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_make_dir_recursively(path.as_ptr(), scratch_pool.as_mut_ptr())
};
Error::from_raw(err)?;
Ok(())
}
pub fn dir_empty(path: &std::path::Path) -> Result<bool, Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let mut empty = 0;
let err = unsafe {
subversion_sys::svn_io_dir_empty(&mut empty, path.as_ptr(), scratch_pool.as_mut_ptr())
};
Error::from_raw(err)?;
Ok(empty != 0)
}
pub fn append_file(src: &std::path::Path, dest: &std::path::Path) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap();
let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_append_file(src.as_ptr(), dest.as_ptr(), scratch_pool.as_mut_ptr())
};
Error::from_raw(err)?;
Ok(())
}
pub fn set_file_read_only(
path: &std::path::Path,
ignore_enoent: bool,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_set_file_read_only(
path.as_ptr(),
ignore_enoent as i32,
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn set_file_read_write(
path: &std::path::Path,
ignore_enoent: bool,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_set_file_read_write(
path.as_ptr(),
ignore_enoent as i32,
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn is_file_executable(path: &std::path::Path) -> Result<bool, Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let mut executable = 0;
let err = unsafe {
subversion_sys::svn_io_is_file_executable(
&mut executable,
path.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(executable != 0)
}
pub fn file_affected_time(path: &std::path::Path) -> Result<apr::time::Time, Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let mut affected_time = 0;
let err = unsafe {
subversion_sys::svn_io_file_affected_time(
&mut affected_time,
path.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(apr::time::Time::from(affected_time))
}
pub fn set_file_affected_time(
path: &std::path::Path,
affected_time: apr::time::Time,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let affected_time = affected_time.into();
let err = unsafe {
subversion_sys::svn_io_set_file_affected_time(
affected_time,
path.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn sleep_for_timestamps(path: &std::path::Path) {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
unsafe { subversion_sys::svn_io_sleep_for_timestamps(path.as_ptr(), scratch_pool.as_mut_ptr()) }
}
pub fn filesizes_different_p(
file1: &std::path::Path,
file2: &std::path::Path,
) -> Result<bool, Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let file1 = std::ffi::CString::new(file1.to_str().unwrap()).unwrap();
let file2 = std::ffi::CString::new(file2.to_str().unwrap()).unwrap();
let mut different = 0;
let err = unsafe {
subversion_sys::svn_io_filesizes_different_p(
&mut different,
file1.as_ptr(),
file2.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(different != 0)
}
pub fn filesizes_three_different_p(
file1: &std::path::Path,
file2: &std::path::Path,
file3: &std::path::Path,
) -> Result<(bool, bool, bool), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let file1 = std::ffi::CString::new(file1.to_str().unwrap()).unwrap();
let file2 = std::ffi::CString::new(file2.to_str().unwrap()).unwrap();
let file3 = std::ffi::CString::new(file3.to_str().unwrap()).unwrap();
let mut different1 = 0;
let mut different2 = 0;
let mut different3 = 0;
let err = unsafe {
subversion_sys::svn_io_filesizes_three_different_p(
&mut different1,
&mut different2,
&mut different3,
file1.as_ptr(),
file2.as_ptr(),
file3.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok((different1 != 0, different2 != 0, different3 != 0))
}
pub fn file_checksum(
file: &std::path::Path,
checksum_kind: crate::ChecksumKind,
) -> Result<crate::Checksum<'_>, Error<'_>> {
let file = std::ffi::CString::new(file.to_str().unwrap()).unwrap();
let mut checksum = std::ptr::null_mut();
let pool = apr::pool::Pool::new();
let err = unsafe {
subversion_sys::svn_io_file_checksum2(
&mut checksum,
file.as_ptr(),
checksum_kind.into(),
pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(crate::Checksum {
ptr: checksum,
_pool: std::marker::PhantomData,
})
}
pub fn files_contents_same_p(
file1: &std::path::Path,
file2: &std::path::Path,
) -> Result<bool, Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let file1 = std::ffi::CString::new(file1.to_str().unwrap()).unwrap();
let file2 = std::ffi::CString::new(file2.to_str().unwrap()).unwrap();
let mut same = 0;
let err = unsafe {
subversion_sys::svn_io_files_contents_same_p(
&mut same,
file1.as_ptr(),
file2.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(same != 0)
}
pub fn files_contents_three_same_p(
file1: &std::path::Path,
file2: &std::path::Path,
file3: &std::path::Path,
) -> Result<(bool, bool, bool), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let file1 = std::ffi::CString::new(file1.to_str().unwrap()).unwrap();
let file2 = std::ffi::CString::new(file2.to_str().unwrap()).unwrap();
let file3 = std::ffi::CString::new(file3.to_str().unwrap()).unwrap();
let mut same1 = 0;
let mut same2 = 0;
let mut same3 = 0;
let err = unsafe {
subversion_sys::svn_io_files_contents_three_same_p(
&mut same1,
&mut same2,
&mut same3,
file1.as_ptr(),
file2.as_ptr(),
file3.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok((same1 != 0, same2 != 0, same3 != 0))
}
pub fn file_create(path: &std::path::Path, contents: &str) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let contents = std::ffi::CString::new(contents).unwrap();
let err = unsafe {
subversion_sys::svn_io_file_create(
path.as_ptr(),
contents.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn file_create_bytes(path: &std::path::Path, contents: &[u8]) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_file_create_bytes(
path.as_ptr(),
contents.as_ptr() as *const std::ffi::c_void,
contents.len(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn file_create_empty(path: &std::path::Path) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_file_create_empty(path.as_ptr(), scratch_pool.as_mut_ptr())
};
Error::from_raw(err)?;
Ok(())
}
pub fn file_lock(
path: &std::path::Path,
exclusive: bool,
nonblocking: bool,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_file_lock2(
path.as_ptr(),
exclusive as i32,
nonblocking as i32,
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn dir_file_copy(
src_path: &std::path::Path,
dest_path: &std::path::Path,
file: &std::ffi::OsStr,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let src_path = std::ffi::CString::new(src_path.to_str().unwrap()).unwrap();
let dest_path = std::ffi::CString::new(dest_path.to_str().unwrap()).unwrap();
let file = std::ffi::CString::new(file.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_dir_file_copy(
src_path.as_ptr(),
dest_path.as_ptr(),
file.as_ptr(),
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn stream_copy(
from: &mut Stream,
to: &mut Stream,
cancel_func: Option<&impl Fn() -> Result<(), Error<'static>>>,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let err = unsafe {
subversion_sys::svn_stream_copy3(
from.ptr,
to.ptr,
if cancel_func.is_some() {
Some(crate::wrap_cancel_func)
} else {
None
},
if let Some(cancel_func) = cancel_func {
&cancel_func as *const _ as *mut std::ffi::c_void
} else {
std::ptr::null_mut()
},
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
pub fn stream_contents_same(
stream1: &mut Stream,
stream2: &mut Stream,
) -> Result<bool, Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let mut same = 0;
let err = unsafe {
subversion_sys::svn_stream_contents_same(
&mut same,
stream1.ptr,
stream2.ptr,
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(same != 0)
}
pub fn string_from_stream(stream: &mut Stream) -> Result<String, Error<'static>> {
let mut str = std::ptr::null_mut();
let pool = apr::pool::Pool::new();
let err = unsafe {
subversion_sys::svn_string_from_stream(
&mut str,
stream.ptr,
pool.as_mut_ptr(),
pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
let data = unsafe { (*str).data };
let len = unsafe { (*str).len };
Ok(unsafe {
std::str::from_utf8(std::slice::from_raw_parts(data as *const u8, len))
.unwrap()
.to_string()
})
}
pub fn remove_dir(
path: &std::path::Path,
ignore_enoent: bool,
cancel_func: Option<&impl Fn() -> Result<(), Error<'static>>>,
) -> Result<(), Error<'static>> {
let scratch_pool = apr::pool::Pool::new();
let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
let err = unsafe {
subversion_sys::svn_io_remove_dir2(
path.as_ptr(),
ignore_enoent as i32,
if cancel_func.is_some() {
Some(crate::wrap_cancel_func)
} else {
None
},
if let Some(cancel_func) = cancel_func {
&cancel_func as *const _ as *mut std::ffi::c_void
} else {
std::ptr::null_mut()
},
scratch_pool.as_mut_ptr(),
)
};
Error::from_raw(err)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_empty() {
let mut stream = Stream::empty();
let mut buf = [0u8; 10];
let bytes_read = stream.read(&mut buf).unwrap();
assert_eq!(bytes_read, 0);
}
#[test]
fn test_stream_from_string() {
let data = "Hello, world!";
let mut stream = Stream::from(data);
let mut buf = vec![0u8; data.len()];
let bytes_read = stream.read_full(&mut buf).unwrap();
assert_eq!(bytes_read, data.len());
assert_eq!(&buf[..bytes_read], data.as_bytes());
}
#[test]
fn test_stream_from_bytes() {
let data = b"Binary \x00 data";
let mut stream = Stream::from(&data[..]);
let mut buf = vec![0u8; data.len()];
let bytes_read = stream.read_full(&mut buf).unwrap();
assert_eq!(bytes_read, data.len());
assert_eq!(&buf[..bytes_read], data);
}
#[test]
fn test_stringbuf_operations() {
let stringbuf = StringBuf::try_from("Initial content").unwrap();
assert_eq!(stringbuf.len(), 15);
assert!(!stringbuf.is_empty());
assert_eq!(stringbuf.to_string(), "Initial content");
assert_eq!(stringbuf.as_bytes(), b"Initial content");
}
#[test]
fn test_stream_buffered() {
let mut stream = Stream::buffered();
let data = b"Test data";
stream.write(data).unwrap();
}
#[test]
fn test_stream_write_read_with_stringbuf() {
let mut stringbuf = StringBuf::new();
{
let mut write_stream = Stream::from_stringbuf(&mut stringbuf);
write_stream.write(b"Hello").unwrap();
write_stream.write(b", ").unwrap();
write_stream.write(b"world!").unwrap();
}
assert_eq!(stringbuf.to_string(), "Hello, world!");
}
#[test]
fn test_stream_puts_printf() {
let mut stringbuf = StringBuf::new();
let mut stream = Stream::from_stringbuf(&mut stringbuf);
stream.puts("Line 1").unwrap();
stream.puts("Line 2").unwrap();
stream.printf("Number: %d", &[&42]).unwrap();
let content = stringbuf.to_string();
assert!(content.contains("Line 1"));
assert!(content.contains("Line 2"));
assert!(content.contains("42"));
}
#[test]
fn test_stream_reset() {
let data = "Test data for reset";
let mut stream = Stream::from(data);
if stream.supports_reset() {
let mut buf1 = vec![0u8; 4];
stream.read_full(&mut buf1).unwrap();
assert_eq!(&buf1, b"Test");
stream.reset().unwrap();
let mut buf2 = vec![0u8; 4];
stream.read_full(&mut buf2).unwrap();
assert_eq!(&buf2, b"Test");
}
}
#[test]
fn test_stream_mark_seek() {
let data = "0123456789";
let mut stream = Stream::from(data);
if stream.supports_mark() {
let mut buf = vec![0u8; 3];
stream.read_full(&mut buf).unwrap();
assert_eq!(&buf, b"012");
let mark = stream.mark().unwrap();
stream.read_full(&mut buf).unwrap();
assert_eq!(&buf, b"345");
stream.seek(&mark).unwrap();
stream.read_full(&mut buf).unwrap();
assert_eq!(&buf, b"345");
}
}
#[test]
fn test_stream_readline() {
let data = "Line 1\nLine 2\rLine 3\r\nLine 4";
let mut stream = Stream::from(data);
let result = stream.readline("\n");
if let Ok(Some(line)) = result {
assert_eq!(line, "Line 1");
};
}
#[test]
fn test_stream_skip() {
let data = "0123456789";
let mut stream = Stream::from(data);
stream.skip(5).unwrap();
let mut buf = vec![0u8; 5];
let bytes_read = stream.read_full(&mut buf).unwrap();
assert_eq!(bytes_read, 5);
assert_eq!(&buf, b"56789");
}
#[test]
fn test_io_read_trait() {
use std::io::Read;
let data = "Hello from Read trait";
let mut stream = Stream::from(data);
let mut buf = String::new();
stream.read_to_string(&mut buf).unwrap();
assert_eq!(buf, data);
}
#[test]
fn test_io_write_trait() {
use std::io::Write;
let mut stringbuf = StringBuf::new();
let mut stream = Stream::from_stringbuf(&mut stringbuf);
write!(stream, "Hello world").unwrap();
stream.flush().unwrap();
assert!(stringbuf.to_string().contains("Hello world"));
}
#[test]
fn test_stream_std_io_write() {
use std::io::Write;
let mut stringbuf = StringBuf::new();
let mut stream = Stream::from_stringbuf(&mut stringbuf);
let data = b"Hello, World!";
let bytes_written = Write::write(&mut stream, data).unwrap();
assert_eq!(bytes_written, data.len());
let result = stringbuf.to_string();
assert_eq!(result, "Hello, World!");
}
#[test]
fn test_stream_std_io_read() {
use std::io::Read;
let data = b"Test data for reading";
let mut stream = Stream::from(data.as_slice());
let mut buffer = vec![0u8; 9];
let bytes_read = Read::read(&mut stream, &mut buffer).unwrap();
assert_eq!(bytes_read, 9);
assert_eq!(&buffer, b"Test data");
let mut buffer2 = vec![0u8; 12];
let bytes_read = Read::read(&mut stream, &mut buffer2).unwrap();
assert_eq!(bytes_read, 12);
assert_eq!(&buffer2, b" for reading");
}
#[test]
fn test_stream_read_write_roundtrip() {
use std::io::{Read, Write};
let mut stringbuf = StringBuf::new();
let mut write_stream = Stream::from_stringbuf(&mut stringbuf);
write_stream.write_all(b"First line\n").unwrap();
write_stream.write_all(b"Second line\n").unwrap();
write_stream.write_all(b"Third line").unwrap();
let written_data = stringbuf.to_string();
let mut read_stream = Stream::from(written_data.as_str());
let mut read_buffer = String::new();
read_stream.read_to_string(&mut read_buffer).unwrap();
assert_eq!(read_buffer, "First line\nSecond line\nThird line");
}
#[test]
fn test_custom_backend_buffer() {
use crate::io::backend::BufferBackend;
let backend = BufferBackend::from_vec(b"Hello from buffer backend!".to_vec());
let mut stream = Stream::from_backend(backend).unwrap();
let mut buf = vec![0u8; 5];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 5);
assert_eq!(&buf, b"Hello");
let mut buf2 = vec![0u8; 21];
let n = stream.read(&mut buf2).unwrap();
assert_eq!(n, 21);
assert_eq!(&buf2, b" from buffer backend!");
}
#[test]
fn test_custom_backend_read_write() {
use crate::io::backend::BufferBackend;
let backend = BufferBackend::new();
let mut stream = Stream::from_backend(backend).unwrap();
stream.write(b"Test data").unwrap();
stream.write(b" for custom backend").unwrap();
stream.reset().unwrap();
let mut buf = vec![0u8; 28];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 28);
assert_eq!(&buf[..n], b"Test data for custom backend");
}
#[test]
fn test_stream_builder() {
use crate::io::backend::BufferBackend;
use crate::io::builder::StreamBuilder;
let backend = BufferBackend::from_vec(b"Builder test".to_vec());
let mut stream = StreamBuilder::new(backend)
.buffer_size(4096)
.build()
.unwrap();
let mut buf = vec![0u8; 12];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 12);
assert_eq!(&buf, b"Builder test");
}
#[test]
fn test_read_only_backend() {
use crate::io::backend::ReadOnlyBackend;
use std::io::Cursor;
let data = b"Read-only data";
let cursor = Cursor::new(data);
let backend = ReadOnlyBackend::new(cursor);
let mut stream = Stream::from_backend(backend).unwrap();
let mut buf = vec![0u8; 14];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 14);
assert_eq!(&buf, b"Read-only data");
let result = stream.write(b"Should fail");
assert!(result.is_err());
}
#[test]
fn test_write_only_backend() {
use crate::io::backend::BufferBackend;
let backend = BufferBackend::new();
let mut stream = Stream::from_backend(backend).unwrap();
stream.write(b"Write test data").unwrap();
stream.reset().unwrap();
let mut buf = vec![0u8; 15];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 15);
assert_eq!(&buf, b"Write test data");
}
#[test]
fn test_from_backend_mark_requires_both_reset_and_mark() {
struct ResetOnlyBackend;
impl crate::io::backend::StreamBackend for ResetOnlyBackend {
fn read(&mut self, _buf: &mut [u8]) -> Result<usize, Error<'static>> {
Ok(0)
}
fn supports_reset(&self) -> bool {
true
}
fn reset(&mut self) -> Result<(), Error<'static>> {
Ok(())
}
}
let mut stream = Stream::from_backend(ResetOnlyBackend).unwrap();
assert!(
!stream.supports_mark(),
"mark must not be enabled unless the backend supports both reset and mark"
);
}
#[test]
fn test_from_backend_skip() {
use crate::io::backend::BufferBackend;
let backend = BufferBackend::from_vec(b"0123456789".to_vec());
let mut stream = Stream::from_backend(backend).unwrap();
stream.skip(4).unwrap();
let mut buf = vec![0u8; 6];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 6);
assert_eq!(&buf, b"456789");
}
#[test]
fn test_from_backend_mark_seek() {
use crate::io::backend::BufferBackend;
let backend = BufferBackend::from_vec(b"0123456789".to_vec());
let mut stream = Stream::from_backend(backend).unwrap();
assert!(stream.supports_mark());
let mut buf = vec![0u8; 3];
stream.read_full(&mut buf).unwrap();
assert_eq!(&buf, b"012");
let mark = stream.mark().unwrap();
stream.read_full(&mut buf).unwrap();
assert_eq!(&buf, b"345");
stream.seek(&mark).unwrap();
stream.read_full(&mut buf).unwrap();
assert_eq!(&buf, b"345");
}
#[test]
fn test_from_backend_close_propagates_error() {
struct ClosesWithError;
impl crate::io::backend::StreamBackend for ClosesWithError {
fn read(&mut self, _buf: &mut [u8]) -> Result<usize, Error<'static>> {
Ok(0)
}
fn close(&mut self) -> Result<(), Error<'static>> {
Err(Error::from_message("close boom"))
}
}
let mut stream = Stream::from_backend(ClosesWithError).unwrap();
let result = stream.close();
assert!(
result.is_err(),
"close() should propagate the backend close error"
);
}
#[test]
fn test_from_backend_no_double_close() {
use crate::io::backend::BufferBackend;
let backend = BufferBackend::from_vec(b"no double close".to_vec());
let mut stream = Stream::from_backend(backend).unwrap();
assert!(!stream.needs_close);
let mut buf = vec![0u8; 15];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 15);
assert_eq!(&buf, b"no double close");
stream.close().unwrap();
drop(stream);
}
#[test]
fn test_from_backend_drop_without_close() {
use crate::io::backend::ReadOnlyBackend;
use std::io::Cursor;
let data = b"drop without close";
let backend = ReadOnlyBackend::new(Cursor::new(data.to_vec()));
let mut stream = Stream::from_backend(backend).unwrap();
let mut buf = vec![0u8; 18];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 18);
assert_eq!(&buf, &b"drop without close"[..]);
drop(stream);
}
#[test]
fn test_from_backend_svn_close_then_drop() {
use crate::io::backend::BufferBackend;
let backend = BufferBackend::from_vec(b"svn close then drop".to_vec());
let mut stream = Stream::from_backend(backend).unwrap();
let mut buf = vec![0u8; 19];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 19);
stream.close().unwrap();
drop(stream);
}
#[test]
fn test_from_backend_read_eof() {
use crate::io::backend::ReadOnlyBackend;
use std::io::Cursor;
let backend = ReadOnlyBackend::new(Cursor::new(b"short".to_vec()));
let mut stream = Stream::from_backend(backend).unwrap();
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 5);
assert_eq!(&buf[..5], b"short");
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 0);
}
#[test]
fn test_from_backend_empty_stream() {
use crate::io::backend::ReadOnlyBackend;
use std::io::Cursor;
let backend = ReadOnlyBackend::new(Cursor::new(Vec::new()));
let mut stream = Stream::from_backend(backend).unwrap();
let mut buf = vec![0u8; 10];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 0);
drop(stream);
}
#[test]
fn test_from_backend_write_then_close() {
use crate::io::backend::BufferBackend;
let backend = BufferBackend::new();
let mut stream = Stream::from_backend(backend).unwrap();
stream.write(b"hello ").unwrap();
stream.write(b"world").unwrap();
stream.reset().unwrap();
let mut buf = vec![0u8; 11];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 11);
assert_eq!(&buf, b"hello world");
stream.close().unwrap();
drop(stream);
}
#[test]
fn test_from_backend_multiple_reads() {
use crate::io::backend::ReadOnlyBackend;
use std::io::Cursor;
let backend = ReadOnlyBackend::new(Cursor::new(b"abcdefghij".to_vec()));
let mut stream = Stream::from_backend(backend).unwrap();
let mut buf = vec![0u8; 3];
assert_eq!(stream.read(&mut buf).unwrap(), 3);
assert_eq!(&buf, b"abc");
assert_eq!(stream.read(&mut buf).unwrap(), 3);
assert_eq!(&buf, b"def");
assert_eq!(stream.read(&mut buf).unwrap(), 3);
assert_eq!(&buf, b"ghi");
assert_eq!(stream.read(&mut buf).unwrap(), 1);
assert_eq!(&buf[..1], b"j");
assert_eq!(stream.read(&mut buf).unwrap(), 0);
}
#[test]
fn test_copy_perms_actually_copies() {
let td = tempfile::tempdir().unwrap();
let src = td.path().join("source.txt");
let dest = td.path().join("dest.txt");
std::fs::write(&src, "source content").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o644);
std::fs::set_permissions(&src, perms).unwrap();
}
std::fs::write(&dest, "dest content").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(0o600);
std::fs::set_permissions(&dest, perms).unwrap();
}
copy_perms(&src, &dest).unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let src_perms = std::fs::metadata(&src).unwrap().permissions().mode();
let dest_perms = std::fs::metadata(&dest).unwrap().permissions().mode();
assert_eq!(
src_perms & 0o777,
dest_perms & 0o777,
"Permissions should be copied from source to dest"
);
}
}
#[test]
#[cfg(unix)]
fn test_set_file_read_write_actually_changes_perms() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("test.txt");
std::fs::write(&path, "test content").unwrap();
use std::os::unix::fs::PermissionsExt;
let readonly_perms = std::fs::Permissions::from_mode(0o444);
std::fs::set_permissions(&path, readonly_perms).unwrap();
let before_perms = std::fs::metadata(&path).unwrap().permissions().mode();
assert_eq!(before_perms & 0o200, 0, "File should be read-only before");
set_file_read_write(&path, false).unwrap();
let after_perms = std::fs::metadata(&path).unwrap().permissions().mode();
assert_ne!(
after_perms & 0o200,
0,
"File should be writable after set_file_read_write"
);
}
#[test]
fn test_remove_file() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("doomed.txt");
std::fs::write(&path, "bye").unwrap();
assert!(path.exists());
remove_file(&path, false).unwrap();
assert!(!path.exists());
}
#[test]
fn test_remove_file_enoent() {
let td = tempfile::tempdir().unwrap();
let missing = td.path().join("nope.txt");
remove_file(&missing, true).unwrap();
assert!(remove_file(&missing, false).is_err());
}
#[test]
fn test_temp_dir() {
let dir = temp_dir().unwrap();
assert!(dir.is_dir(), "temp_dir {:?} should exist", dir);
}
#[test]
fn test_copy_file() {
let td = tempfile::tempdir().unwrap();
let src = td.path().join("src.txt");
let dest = td.path().join("dest.txt");
std::fs::write(&src, "payload").unwrap();
copy_file(&src, &dest, false).unwrap();
assert_eq!(std::fs::read_to_string(&dest).unwrap(), "payload");
}
#[test]
fn test_make_dir_recursively() {
let td = tempfile::tempdir().unwrap();
let nested = td.path().join("a/b/c");
make_dir_recursively(&nested).unwrap();
assert!(nested.is_dir());
}
#[test]
fn test_dir_empty() {
let td = tempfile::tempdir().unwrap();
let empty = td.path().join("empty");
std::fs::create_dir(&empty).unwrap();
assert!(dir_empty(&empty).unwrap());
std::fs::write(empty.join("file.txt"), "x").unwrap();
assert!(!dir_empty(&empty).unwrap());
}
#[test]
fn test_append_file() {
let td = tempfile::tempdir().unwrap();
let src = td.path().join("src.txt");
let dest = td.path().join("dest.txt");
std::fs::write(&src, "world").unwrap();
std::fs::write(&dest, "hello ").unwrap();
append_file(&src, &dest).unwrap();
assert_eq!(std::fs::read_to_string(&dest).unwrap(), "hello world");
}
#[test]
#[cfg(unix)]
fn test_is_file_executable() {
use std::os::unix::fs::PermissionsExt;
let td = tempfile::tempdir().unwrap();
let path = td.path().join("script");
std::fs::write(&path, "#!/bin/sh\n").unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
assert!(!is_file_executable(&path).unwrap());
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).unwrap();
assert!(is_file_executable(&path).unwrap());
}
#[test]
fn test_set_file_affected_time() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("timed.txt");
std::fs::write(&path, "data").unwrap();
let when = apr::time::Time::from(1_000_000_000_000_000i64);
set_file_affected_time(&path, when).unwrap();
let got: i64 = file_affected_time(&path).unwrap().into();
assert_eq!(got, 1_000_000_000_000_000i64);
}
#[test]
fn test_filesizes_different_p() {
let td = tempfile::tempdir().unwrap();
let a = td.path().join("a.txt");
let b = td.path().join("b.txt");
let c = td.path().join("c.txt");
std::fs::write(&a, "1234").unwrap();
std::fs::write(&b, "1234").unwrap();
std::fs::write(&c, "123456").unwrap();
assert!(!filesizes_different_p(&a, &b).unwrap());
assert!(filesizes_different_p(&a, &c).unwrap());
}
#[test]
fn test_filesizes_three_different_p() {
let td = tempfile::tempdir().unwrap();
let a = td.path().join("a.txt");
let b = td.path().join("b.txt");
let c = td.path().join("c.txt");
std::fs::write(&a, "1234").unwrap();
std::fs::write(&b, "1234").unwrap();
std::fs::write(&c, "123456").unwrap();
assert_eq!(
filesizes_three_different_p(&a, &b, &c).unwrap(),
(false, true, true)
);
std::fs::write(&b, "123456").unwrap();
assert_eq!(
filesizes_three_different_p(&a, &b, &c).unwrap(),
(true, false, true)
);
}
#[test]
fn test_files_contents_same_p() {
let td = tempfile::tempdir().unwrap();
let a = td.path().join("a.txt");
let b = td.path().join("b.txt");
let c = td.path().join("c.txt");
std::fs::write(&a, "same").unwrap();
std::fs::write(&b, "same").unwrap();
std::fs::write(&c, "diff").unwrap();
assert!(files_contents_same_p(&a, &b).unwrap());
assert!(!files_contents_same_p(&a, &c).unwrap());
}
#[test]
fn test_files_contents_three_same_p() {
let td = tempfile::tempdir().unwrap();
let a = td.path().join("a.txt");
let b = td.path().join("b.txt");
let c = td.path().join("c.txt");
std::fs::write(&a, "same").unwrap();
std::fs::write(&b, "same").unwrap();
std::fs::write(&c, "diff").unwrap();
assert_eq!(
files_contents_three_same_p(&a, &b, &c).unwrap(),
(true, false, false)
);
std::fs::write(&b, "diff").unwrap();
assert_eq!(
files_contents_three_same_p(&a, &b, &c).unwrap(),
(false, true, false)
);
}
#[test]
fn test_file_create() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("created.txt");
file_create(&path, "contents here").unwrap();
assert_eq!(std::fs::read_to_string(&path).unwrap(), "contents here");
}
#[test]
fn test_file_create_bytes() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("created.bin");
file_create_bytes(&path, b"\x00\x01\x02binary").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"\x00\x01\x02binary");
}
#[test]
fn test_file_create_empty() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("empty.txt");
file_create_empty(&path).unwrap();
assert!(path.exists());
assert_eq!(std::fs::metadata(&path).unwrap().len(), 0);
}
#[test]
fn test_file_lock() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("lock.txt");
std::fs::write(&path, "data").unwrap();
file_lock(&path, true, false).unwrap();
}
#[test]
fn test_dir_file_copy() {
let td = tempfile::tempdir().unwrap();
let src_dir = td.path().join("src");
let dest_dir = td.path().join("dest");
std::fs::create_dir(&src_dir).unwrap();
std::fs::create_dir(&dest_dir).unwrap();
std::fs::write(src_dir.join("file.txt"), "moved").unwrap();
dir_file_copy(&src_dir, &dest_dir, std::ffi::OsStr::new("file.txt")).unwrap();
assert_eq!(
std::fs::read_to_string(dest_dir.join("file.txt")).unwrap(),
"moved"
);
}
#[test]
fn test_remove_dir() {
let td = tempfile::tempdir().unwrap();
let dir = td.path().join("to_remove");
std::fs::create_dir(&dir).unwrap();
std::fs::write(dir.join("inner.txt"), "x").unwrap();
let no_cancel: Option<&fn() -> Result<(), Error<'static>>> = None;
remove_dir(&dir, false, no_cancel).unwrap();
assert!(!dir.exists());
}
#[test]
fn test_remove_dir_enoent() {
let td = tempfile::tempdir().unwrap();
let missing = td.path().join("never");
let no_cancel: Option<&fn() -> Result<(), Error<'static>>> = None;
remove_dir(&missing, true, no_cancel).unwrap();
assert!(remove_dir(&missing, false, no_cancel).is_err());
}
#[test]
fn test_copy_dir_recursively() {
let td = tempfile::tempdir().unwrap();
let src = td.path().join("src");
std::fs::create_dir(&src).unwrap();
std::fs::write(src.join("file.txt"), "nested content").unwrap();
let dst_parent = td.path().join("dstparent");
std::fs::create_dir(&dst_parent).unwrap();
let no_cancel: Option<&fn() -> Result<(), Error<'static>>> = None;
copy_dir_recursively(
&src,
&dst_parent,
std::ffi::OsStr::new("copied"),
false,
no_cancel,
)
.unwrap();
assert_eq!(
std::fs::read_to_string(dst_parent.join("copied/file.txt")).unwrap(),
"nested content"
);
}
#[test]
fn test_stream_copy() {
let mut from = Stream::from("stream payload");
let mut buf = StringBuf::new();
let mut to = Stream::from_stringbuf(&mut buf);
let no_cancel: Option<&fn() -> Result<(), Error<'static>>> = None;
stream_copy(&mut from, &mut to, no_cancel).unwrap();
drop(to);
assert_eq!(buf.to_string(), "stream payload");
}
#[test]
fn test_stream_contents_same() {
let mut a = Stream::from("identical");
let mut b = Stream::from("identical");
assert!(stream_contents_same(&mut a, &mut b).unwrap());
let mut c = Stream::from("identical");
let mut d = Stream::from("different");
assert!(!stream_contents_same(&mut c, &mut d).unwrap());
}
#[test]
fn test_string_from_stream() {
let mut stream = Stream::from("read me back");
assert_eq!(string_from_stream(&mut stream).unwrap(), "read me back");
}
#[test]
fn test_read_stream_to_string() {
let mut stream = Stream::from("via read_stream_to_string");
assert_eq!(
read_stream_to_string(&mut stream).unwrap(),
"via read_stream_to_string"
);
}
#[test]
#[cfg(unix)]
fn test_create_uniqe_link_and_read_link() {
let td = tempfile::tempdir().unwrap();
let target = td.path().join("target.txt");
std::fs::write(&target, "linked").unwrap();
let link_base = td.path().join("mylink");
let link = create_uniqe_link(&link_base, &target, std::ffi::OsStr::new(".tmp")).unwrap();
let read_target = read_link(&link).unwrap();
assert_eq!(read_target, target);
}
#[test]
#[cfg(unix)]
fn test_copy_link() {
let td = tempfile::tempdir().unwrap();
let target = td.path().join("target.txt");
std::fs::write(&target, "data").unwrap();
let src_link = td.path().join("src_link");
std::os::unix::fs::symlink(&target, &src_link).unwrap();
let dest_link = td.path().join("dest_link");
copy_link(&src_link, &dest_link).unwrap();
assert_eq!(std::fs::read_link(&dest_link).unwrap(), target);
}
#[test]
fn test_file_del_conversions() {
for variant in [FileDel::None, FileDel::OnClose, FileDel::OnPoolCleanup] {
let raw: subversion_sys::svn_io_file_del_t = variant.into();
assert_eq!(FileDel::from(raw), variant);
}
assert_eq!(
subversion_sys::svn_io_file_del_t::from(FileDel::None),
subversion_sys::svn_io_file_del_t_svn_io_file_del_none
);
assert_eq!(
subversion_sys::svn_io_file_del_t::from(FileDel::OnClose),
subversion_sys::svn_io_file_del_t_svn_io_file_del_on_close
);
assert_eq!(
subversion_sys::svn_io_file_del_t::from(FileDel::OnPoolCleanup),
subversion_sys::svn_io_file_del_t_svn_io_file_del_on_pool_cleanup
);
}
#[test]
fn test_stringbuf_is_empty() {
assert!(StringBuf::new().is_empty());
assert!(!StringBuf::try_from("x").unwrap().is_empty());
}
#[test]
fn test_stream_data_available() {
let mut stream = Stream::from("payload");
assert!(stream.data_available().unwrap());
let mut buf = vec![0u8; 7];
stream.read_full(&mut buf).unwrap();
assert!(!stream.data_available().unwrap());
}
#[test]
fn test_stream_readline_multiple() {
let mut stream = Stream::from("first line\nsecond line\n");
assert_eq!(
stream.readline("\n").unwrap(),
Some("first line".to_string())
);
assert_eq!(
stream.readline("\n").unwrap(),
Some("second line".to_string())
);
assert_eq!(stream.readline("\n").unwrap(), Some(String::new()));
}
#[test]
fn test_streams_contents_same() {
let mut a = Stream::from("identical bytes");
let mut b = Stream::from("identical bytes");
assert!(streams_contents_same(&mut a, &mut b).unwrap());
let mut c = Stream::from("identical bytes");
let mut d = Stream::from("different bytes");
assert!(!streams_contents_same(&mut c, &mut d).unwrap());
}
#[test]
fn test_copy_stream() {
let mut from = Stream::from("copy_stream payload");
let mut buf = StringBuf::new();
let mut to = Stream::from_stringbuf(&mut buf);
let no_cancel: Option<&fn() -> Result<(), Error<'static>>> = None;
copy_stream(&mut from, &mut to, no_cancel).unwrap();
drop(to);
assert_eq!(buf.to_string(), "copy_stream payload");
}
#[test]
fn test_stream_printf_from_utf8() {
let mut buf = StringBuf::new();
let mut stream = Stream::from_stringbuf(&mut buf);
stream.printf_from_utf8("value=%d", &[&7]).unwrap();
drop(stream);
assert_eq!(buf.to_string(), "value=7");
}
#[test]
#[cfg(unix)]
fn test_set_file_read_only() {
use std::os::unix::fs::PermissionsExt;
let td = tempfile::tempdir().unwrap();
let path = td.path().join("ro.txt");
std::fs::write(&path, "data").unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
set_file_read_only(&path, false).unwrap();
let mode = std::fs::metadata(&path).unwrap().permissions().mode();
assert_eq!(mode & 0o200, 0, "write bit should be cleared");
}
#[test]
fn test_stream_supports_mark() {
assert!(Stream::from("data").supports_mark());
assert!(!Stream::buffered().supports_mark());
}
#[test]
fn test_stream_supports_reset() {
assert!(Stream::from("data").supports_reset());
assert!(!Stream::buffered().supports_reset());
}
#[test]
fn test_stream_supports_partial_read() {
assert!(Stream::from("data").supports_partial_read());
assert!(!Stream::buffered().supports_partial_read());
}
#[test]
fn test_wrap_write() {
let mut sink: Vec<u8> = Vec::new();
{
let mut stream = wrap_write(&mut sink).unwrap();
stream.write(b"wrapped ").unwrap();
stream.write(b"output").unwrap();
stream.close().unwrap();
}
assert_eq!(sink, b"wrapped output");
}
#[test]
fn test_wrap_write_close_propagates_flush_error() {
struct FailFlush;
impl std::io::Write for FailFlush {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Err(std::io::Error::other("flush boom"))
}
}
let mut writer = FailFlush;
let mut stream = wrap_write(&mut writer).unwrap();
let result = stream.close();
assert!(
result.is_err(),
"close() should propagate the flush error from close_fn"
);
}
#[test]
fn test_sleep_for_timestamps() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("file.txt");
std::fs::write(&path, "data").unwrap();
sleep_for_timestamps(&path);
}
}