Skip to content

Commit eb2c471

Browse files
committed
Additional branch coverage for gz tests
1 parent 31b000d commit eb2c471

File tree

5 files changed

+169
-27
lines changed

5 files changed

+169
-27
lines changed

libz-rs-sys/src/gz.rs

Lines changed: 166 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use zlib_rs::c_api::*;
66
use crate::{deflateEnd, inflateEnd, inflateInit2, inflateReset};
77
use core::ffi::{c_char, c_int, c_uint, CStr};
88
use core::ptr;
9-
use libc::{size_t, O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_TRUNC, O_WRONLY, SEEK_CUR, SEEK_END};
9+
use libc::{O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_TRUNC, O_WRONLY, SEEK_CUR, SEEK_END};
1010
use zlib_rs::deflate::Strategy;
1111
use zlib_rs::MAX_WBITS;
1212

@@ -754,20 +754,20 @@ pub unsafe extern "C-unwind" fn gzdirect(file: gzFile) -> c_int {
754754
//
755755
// # Returns
756756
//
757-
// * 0 on success
758-
// * -1 on failure
757+
// * `Ok` on success
758+
// * `Err` on failure
759759
//
760760
// # Safety
761761
//
762762
// `state` must have been properly initialized, e.g. by [`gzopen_help`].
763-
unsafe fn gz_look(state: &mut GzState) -> c_int {
763+
unsafe fn gz_look(state: &mut GzState) -> Result<(), ()> {
764764
// Allocate buffers if needed.
765765
if state.input.is_null() {
766766
state.in_size = state.want;
767767
let Some(input) = ALLOCATOR.allocate_slice_raw::<u8>(state.in_size) else {
768768
// Safety:
769769
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
770-
return -1;
770+
return Err(());
771771
};
772772
state.input = input.as_ptr();
773773
}
@@ -779,7 +779,7 @@ unsafe fn gz_look(state: &mut GzState) -> c_int {
779779
unsafe { free_buffers(state) };
780780
// Safety: The caller confirmed the validity of state.
781781
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
782-
return -1;
782+
return Err(());
783783
};
784784
state.output = output.as_ptr();
785785
}
@@ -795,17 +795,17 @@ unsafe fn gz_look(state: &mut GzState) -> c_int {
795795
unsafe { free_buffers(state) };
796796
// Safety: The caller confirmed the validity of `state`.
797797
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
798-
return -1;
798+
return Err(());
799799
}
800800

801801
// Get at least the magic bytes in the input buffer.
802802
if state.stream.avail_in < 2 {
803803
// Safety: The caller confirmed the validity of `state`.
804-
if unsafe { gz_avail(state) } == -1 {
805-
return -1;
806-
}
804+
let Ok(_) = (unsafe { gz_avail(state) }) else {
805+
return Err(());
806+
};
807807
if state.stream.avail_in == 0 {
808-
return 0;
808+
return Ok(());
809809
}
810810
}
811811

@@ -819,16 +819,17 @@ unsafe fn gz_look(state: &mut GzState) -> c_int {
819819
unsafe { inflateReset(&mut state.stream as *mut z_stream) };
820820
state.how = How::Gzip;
821821
state.direct = false;
822-
return 0;
822+
return Ok(());
823823
}
824824

825825
// No gzip header. If we were decoding gzip before, the remaining bytes
826826
// are trailing garbage that can be ignored.
827+
// FIXME: Add test coverage for this case (which may require the remaining decompression logic).
827828
if !state.direct {
828829
state.stream.avail_in = 0;
829830
state.eof = true;
830831
state.have = 0;
831-
return 0;
832+
return Ok(());
832833
}
833834

834835
// The file is not in gzip format, so enable direct mode, and copy all
@@ -837,7 +838,7 @@ unsafe fn gz_look(state: &mut GzState) -> c_int {
837838
// * `state.output` was allocated above.
838839
// * `gz_avail` ensures that `next_in` points to at least `avail_in` readable bytes.
839840
unsafe {
840-
ptr::copy_nonoverlapping(
841+
ptr::copy(
841842
state.stream.next_in,
842843
state.output,
843844
state.stream.avail_in as usize,
@@ -849,59 +850,74 @@ unsafe fn gz_look(state: &mut GzState) -> c_int {
849850
state.how = How::Copy;
850851
state.direct = true;
851852

852-
0
853+
Ok(())
853854
}
854855

855856
// Load data into the input buffer and set the eof flag if the last of the data has been
856857
// loaded.
857858
//
858859
// # Returns
859860
//
860-
// * -1 if an error occurs.
861-
// * 0 otherwise.
861+
// * `Ok(n)` on success, where `n` is the number of bytes available (`state.stream.avail_in`)
862+
// * `Err` on error
862863
//
863864
// # Safety
864865
//
865866
// `state` must have been properly initialized, e.g. by [`gzopen_help`].
866-
unsafe fn gz_avail(state: &mut GzState) -> c_int {
867+
unsafe fn gz_avail(state: &mut GzState) -> Result<usize, ()> {
867868
if state.err != Z_OK && state.err != Z_BUF_ERROR {
868-
return -1;
869+
return Err(());
869870
}
870871
if !state.eof {
871872
if state.stream.avail_in != 0 {
872873
// Copy any remaining input to the start.
873874
unsafe {
874-
ptr::copy_nonoverlapping(
875+
ptr::copy(
875876
state.stream.next_in,
876877
state.input,
877878
state.stream.avail_in as usize,
878879
)
879880
};
880881
}
881-
let Ok(got) = (unsafe {
882+
let got = unsafe {
882883
gz_load(
883884
state,
884885
state.input.add(state.stream.avail_in as usize),
885886
state.in_size - state.stream.avail_in as usize,
886887
)
887-
}) else {
888-
return -1;
889-
};
888+
}?;
890889
state.stream.avail_in += got as uInt;
891890
state.stream.next_in = state.input;
892891
}
893-
0
892+
Ok(state.stream.avail_in as usize)
894893
}
895894

896-
unsafe fn gz_load(state: &mut GzState, buf: *mut u8, len: size_t) -> Result<size_t, ()> {
895+
// Read data from `state`'s underlying file descriptor into a buffer.
896+
//
897+
// # Returns
898+
//
899+
// * `Ok(n)` on success, where `n` is the number of bytes read.
900+
// * `Err` on error
901+
//
902+
// # Arguments
903+
//
904+
// * `state` - gzip file handle.
905+
// * `buf` - address at which the data read from the file should be stored.
906+
// * `size` - number of bytes to read
907+
//
908+
// # Safety
909+
//
910+
// * `state` must have been properly initialized, e.g. by [`gzopen_help`].
911+
// * `buf` mut point to a writable block of at least `len` bytes.
912+
unsafe fn gz_load(state: &mut GzState, buf: *mut u8, len: usize) -> Result<usize, ()> {
897913
let mut have = 0;
898914
let mut ret = 0;
899915
while have < len {
900916
ret = unsafe { libc::read(state.fd, buf.add(have).cast::<_>(), (len - have) as _) };
901917
if ret <= 0 {
902918
break;
903919
}
904-
have += ret as size_t;
920+
have += ret as usize;
905921
}
906922
if ret < 0 {
907923
unsafe { gz_error(state, Some((Z_ERRNO, "read error"))) }; // FIXME implement `zstrerror`
@@ -973,6 +989,19 @@ unsafe fn gz_strcat(strings: &[&str]) -> *mut c_char {
973989
#[cfg(test)]
974990
mod tests {
975991
use super::*;
992+
use std::ffi::CString;
993+
use std::path::Path;
994+
995+
// Generate a file path relative to the project's root
996+
fn crate_path(file: &str) -> String {
997+
path(Path::new(env!("CARGO_MANIFEST_DIR")), file)
998+
}
999+
1000+
fn path(prefix: &Path, file: &str) -> String {
1001+
let mut path_buf = prefix.to_path_buf();
1002+
path_buf.push(file);
1003+
path_buf.as_path().to_str().unwrap().to_owned()
1004+
}
9761005

9771006
#[test]
9781007
fn test_configure() {
@@ -1102,6 +1131,9 @@ mod tests {
11021131
#[test]
11031132
#[cfg_attr(not(target_os = "linux"), ignore = "lseek is not implemented")]
11041133
fn test_gz_error() {
1134+
// gzerror(null) should return null.
1135+
assert!(unsafe { gzerror(ptr::null_mut(), ptr::null_mut()) }.is_null());
1136+
11051137
// Open a gzip stream with an invalid file handle. Initially, no error
11061138
// status should be set.
11071139
let handle = unsafe { gzdopen(-2, c!(b"r\0")) };
@@ -1270,4 +1302,111 @@ mod tests {
12701302
assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
12711303
}
12721304
}
1305+
1306+
#[test]
1307+
fn test_gzdirect() {
1308+
// gzdirect(null) should return 0.
1309+
assert_eq!(unsafe { gzdirect(ptr::null_mut()) }, 0);
1310+
1311+
// Open a gzip stream from an invalid file descriptor. gzdirect should return 1, but
1312+
// it should cause an error condition to be set within the stream.
1313+
let file = unsafe { gzdopen(-2, CString::new("r").unwrap().as_ptr()) };
1314+
assert!(!file.is_null());
1315+
assert_eq!(unsafe { gzdirect(file) }, 1);
1316+
let mut err = Z_OK;
1317+
let msg = unsafe { gzerror(file, &mut err as *mut c_int) };
1318+
assert!(!msg.is_null());
1319+
assert_eq!(err, Z_ERRNO);
1320+
assert_eq!(unsafe { gzclose(file) }, Z_ERRNO);
1321+
1322+
// Open a gzip file for reading. gzdirect should return 0.
1323+
let file = unsafe {
1324+
gzopen(
1325+
CString::new(crate_path("src/test-data/example.gz"))
1326+
.unwrap()
1327+
.as_ptr(),
1328+
CString::new("r").unwrap().as_ptr(),
1329+
)
1330+
};
1331+
assert!(!file.is_null());
1332+
// Set a smaller read batch size to exercise the buffer management code paths.
1333+
const FILE_SIZE: usize = 48; // size of test-data/example.gz
1334+
const BLOCK_SIZE: usize = 40;
1335+
unsafe { file.cast::<GzState>().as_mut().unwrap().want = BLOCK_SIZE };
1336+
assert_eq!(unsafe { gzdirect(file) }, 0);
1337+
// gzdirect should have pulled the first `BLOCK_SIZE` bytes of the file into `file`'s internal input buffer.
1338+
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 0);
1339+
assert_eq!(
1340+
unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
1341+
BLOCK_SIZE as uInt
1342+
);
1343+
// Consume some of the buffered input and call gz_avail. It should move the remaining
1344+
// input to the front of the input buffer.
1345+
unsafe {
1346+
let state = file.cast::<GzState>().as_mut().unwrap();
1347+
const CONSUME: usize = 10;
1348+
state.stream.next_in = state.stream.next_in.add(CONSUME);
1349+
state.stream.avail_in -= CONSUME as uInt;
1350+
let expected_avail = BLOCK_SIZE - CONSUME + (FILE_SIZE - BLOCK_SIZE);
1351+
assert_eq!(gz_avail(state), Ok(expected_avail));
1352+
assert_eq!(state.stream.avail_in as usize, expected_avail);
1353+
};
1354+
assert_eq!(unsafe { gzclose(file) }, Z_OK);
1355+
1356+
// Open a non-gzip file for reading. gzdirect should return 1.
1357+
let file = unsafe {
1358+
gzopen(
1359+
CString::new(crate_path("src/test-data/example.txt"))
1360+
.unwrap()
1361+
.as_ptr(),
1362+
CString::new("r").unwrap().as_ptr(),
1363+
)
1364+
};
1365+
assert!(!file.is_null());
1366+
assert_eq!(unsafe { gzdirect(file) }, 1);
1367+
// gzdirect should have pulled the entire contents of the file (which is smaller than
1368+
// GZBUFSIZE) into `file`'s internal output buffer.
1369+
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 20);
1370+
assert_eq!(
1371+
unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
1372+
0
1373+
);
1374+
assert_eq!(unsafe { gzclose(file) }, Z_OK);
1375+
1376+
// Open a file containing only the gzip magic number. gzdirect should return 0.
1377+
let file = unsafe {
1378+
gzopen(
1379+
CString::new(crate_path("src/test-data/magic-only.gz"))
1380+
.unwrap()
1381+
.as_ptr(),
1382+
CString::new("r").unwrap().as_ptr(),
1383+
)
1384+
};
1385+
assert!(!file.is_null());
1386+
assert_eq!(unsafe { gzdirect(file) }, 0);
1387+
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 0);
1388+
assert_eq!(
1389+
unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
1390+
2
1391+
);
1392+
assert_eq!(unsafe { gzclose(file) }, Z_OK);
1393+
1394+
// Open a file containing only the first byte of the gzip magic number. gzdirect should return 1.
1395+
let file = unsafe {
1396+
gzopen(
1397+
CString::new(crate_path("src/test-data/incomplete-magic.gz"))
1398+
.unwrap()
1399+
.as_ptr(),
1400+
CString::new("r").unwrap().as_ptr(),
1401+
)
1402+
};
1403+
assert!(!file.is_null());
1404+
assert_eq!(unsafe { gzdirect(file) }, 1);
1405+
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 1);
1406+
assert_eq!(
1407+
unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
1408+
0
1409+
);
1410+
assert_eq!(unsafe { gzclose(file) }, Z_OK);
1411+
}
12731412
}
48 Bytes
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
zlib-rs gzip example
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
�

0 commit comments

Comments
 (0)