diff --git a/Cargo.toml b/Cargo.toml index b065d0ee3..3e95d35d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ maintenance = { status = "experimental" } [dev-dependencies] clap = { version = "4.4.11", features = ["derive"] } -tempfile = "3" +sealed_test = "1.0.0" [dependencies] bitflags = "2" diff --git a/docker/Dockerfile.static b/docker/Dockerfile.static index 4c604852a..bbdb472f1 100644 --- a/docker/Dockerfile.static +++ b/docker/Dockerfile.static @@ -16,7 +16,7 @@ ENV SYSTEM_DEPS_LINK static # RUN ./configure --host "$TARGET" --prefix="$PREFIX" --with-pic --enable-fast-install --disable-dependency-tracking --without-emacs --disable-java --disable-csharp --disable-c++ # RUN make -j$(nproc) install -ARG LIBGPG_ERROR_VER=1.46 +ARG LIBGPG_ERROR_VER=1.47 WORKDIR /usr/src ADD https://www.gnupg.org/ftp/gcrypt/libgpg-error/libgpg-error-${LIBGPG_ERROR_VER}.tar.bz2 ./ RUN tar -xjf libgpg-error-${LIBGPG_ERROR_VER}.tar.bz2 @@ -24,7 +24,7 @@ WORKDIR libgpg-error-$LIBGPG_ERROR_VER RUN ./configure --host "$TARGET" --prefix="$PREFIX" --with-pic --enable-fast-install --disable-dependency-tracking --enable-static --disable-shared --disable-nls --disable-doc --disable-languages --disable-tests --enable-install-gpg-error-config RUN make -j$(nproc) install -ARG LIBASSUAN_VER=2.5.5 +ARG LIBASSUAN_VER=2.5.6 WORKDIR /usr/src ADD https://www.gnupg.org/ftp/gcrypt/libassuan/libassuan-${LIBASSUAN_VER}.tar.bz2 ./ RUN tar -xjf libassuan-${LIBASSUAN_VER}.tar.bz2 @@ -32,7 +32,7 @@ WORKDIR libassuan-$LIBASSUAN_VER RUN ./configure --host "$TARGET" --prefix="$PREFIX" --with-pic --enable-fast-install --disable-dependency-tracking --enable-static --disable-shared --disable-doc --with-gpg-error-prefix="$PREFIX" RUN make -j$(nproc) install -ARG GPGME_VER=1.18.0 +ARG GPGME_VER=1.23.2 WORKDIR /usr/src ADD https://www.gnupg.org/ftp/gcrypt/gpgme/gpgme-${GPGME_VER}.tar.bz2 ./ RUN tar -xjf gpgme-${GPGME_VER}.tar.bz2 diff --git a/src/data.rs b/src/data.rs index 4efcff2d4..581949310 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,5 +1,3 @@ -#[cfg(unix)] -use std::os::unix::io::AsRawFd; use std::{ borrow::BorrowMut, error::Error as StdError, @@ -12,8 +10,11 @@ use std::{ str::Utf8Error, }; +#[cfg(unix)] +use std::os::fd::{AsRawFd, BorrowedFd}; + use conv::{UnwrapOrSaturate, ValueInto}; -use ffi; +use ffi::{self, gpgme_off_t}; use libc; use static_assertions::{assert_impl_all, assert_not_impl_any}; @@ -202,6 +203,20 @@ impl<'data> Data<'data> { /// [`gpgme_data_new_from_fd`](https://www.gnupg.org/documentation/manuals/gpgme/File-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew_005ffrom_005ffd) #[inline] #[cfg(unix)] + pub fn from_borrowed_fd(fd: BorrowedFd<'data>) -> Result { + crate::init(); + unsafe { + let mut data = ptr::null_mut(); + convert_err(ffi::gpgme_data_new_from_fd(&mut data, fd.as_raw_fd()))?; + Ok(Data::from_raw(data)) + } + } + + /// Upstream documentation: + /// [`gpgme_data_new_from_fd`](https://www.gnupg.org/documentation/manuals/gpgme/File-Based-Data-Buffers.html#index-gpgme_005fdata_005fnew_005ffrom_005ffd) + #[inline] + #[cfg(unix)] + #[deprecated(note = "Use from_borrowed_fd")] pub fn from_fd(file: &'data (impl AsRawFd + ?Sized)) -> Result { crate::init(); unsafe { @@ -433,11 +448,7 @@ impl Read for Data<'_> { let (buf, len) = (buf.as_mut_ptr(), buf.len()); ffi::gpgme_data_read(self.as_raw(), buf.cast(), len) }; - if result >= 0 { - Ok(result as usize) - } else { - Err(Error::last_os_error().into()) - } + Ok(usize::try_from(result).map_err(|_| Error::last_os_error())?) } } @@ -464,7 +475,7 @@ impl Seek for Data<'_> { io::SeekFrom::Start(off) => { (off.try_into().unwrap_or(gpgme_off_t::MAX), libc::SEEK_SET) } - io::SeekFrom::End(off) => (off.try_into().unwrap_or(gpgme_off_t::MAX), libc::SEEK_END), + io::SeekFrom::End(off) => (off.value_into().unwrap_or_saturate(), libc::SEEK_END), io::SeekFrom::Current(off) => (off.value_into().unwrap_or_saturate(), libc::SEEK_CUR), }; let result = unsafe { ffi::gpgme_data_seek(self.as_raw(), off, whence) }; @@ -487,9 +498,10 @@ unsafe extern "C" fn read_callback( (*handle) .inner .read(slice) - .map(|n| n.try_into().unwrap_or(-1)) + .map_err(Error::from) + .and_then(|n| n.try_into().or(Err(Error::EOVERFLOW))) .unwrap_or_else(|err| { - ffi::gpgme_err_set_errno(Error::from(err).to_errno()); + ffi::gpgme_err_set_errno(err.to_errno()); -1 }) } @@ -504,9 +516,10 @@ unsafe extern "C" fn write_callback( (*handle) .inner .write(slice) - .map(|n| n.try_into().unwrap_or(-1)) + .map_err(Error::from) + .and_then(|n| n.try_into().or(Err(Error::EOVERFLOW))) .unwrap_or_else(|err| { - ffi::gpgme_err_set_errno(Error::from(err).to_errno()); + ffi::gpgme_err_set_errno(err.to_errno()); -1 }) } @@ -529,9 +542,10 @@ unsafe extern "C" fn seek_callback( (*handle) .inner .seek(pos) - .map(|n| n.try_into().unwrap_or(-1)) + .map_err(Error::from) + .and_then(|n| n.try_into().or(Err(Error::EOVERFLOW))) .unwrap_or_else(|err| { - ffi::gpgme_err_set_errno(Error::from(err).to_errno()); + ffi::gpgme_err_set_errno(err.to_errno()); -1 }) } diff --git a/tests/common/data/gpg-agent.conf b/tests/common/data/gpg-agent.conf new file mode 100644 index 000000000..476cbc479 --- /dev/null +++ b/tests/common/data/gpg-agent.conf @@ -0,0 +1,5 @@ +disable-scdaemon +ignore-invalid-option allow-loopback-pinentry +allow-loopback-pinentry +ignore-invalid-option pinentry-mode +pinentry-mode loopback diff --git a/tests/common/data/gpg.conf b/tests/common/data/gpg.conf new file mode 100644 index 000000000..67cd2ea01 --- /dev/null +++ b/tests/common/data/gpg.conf @@ -0,0 +1,7 @@ +ignore-invalid-option no-force-v3-sigs +ignore-invalid-option allow-weak-key-signatures +# This is required for t-sig-notations. +no-force-v3-sigs + +# This is required for t-edit-sign. +allow-weak-key-signatures diff --git a/tests/common/data/ownertrust.txt b/tests/common/data/ownertrust.txt new file mode 100644 index 000000000..0f31f9e49 --- /dev/null +++ b/tests/common/data/ownertrust.txt @@ -0,0 +1,3 @@ +# List of assigned trustvalues, created Mi 08 Feb 2023 09:52:04 CET +# (Use "gpg --import-ownertrust" to restore them) +A0FF4590BB6122EDEF6E3C542D727CC768697734:6: diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d64519bcb..4cd198e54 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -4,44 +4,31 @@ use std::{ io::prelude::*, path::Path, process::{Command, Stdio}, - sync::{ - atomic::{AtomicUsize, Ordering}, - RwLock, - }, }; use gpgme::{self, Context, PassphraseRequest, PinentryMode}; -use tempfile::TempDir; -macro_rules! count { - () => {0usize}; - ($_head:tt $($tail:tt)*) => {1usize + count!($($tail)*)}; -} - -macro_rules! test_case { - (@impl $name:ident($tester:ident) $body:block) => { - #[test] - fn $name() { - let $tester = test_case().new_test(); - $body +#[allow(unused_macros)] +macro_rules! assert_matches { + ($left:expr, $(|)? $($pattern:pat_param)|+ $(if $guard:expr)? $(,)?) => { + match $left { + $($pattern)|+ $(if $guard)? => {} + ref left_val => { + panic!(r#"assertion `(left matches right)` failed: + left: `{left_val:?}` + right: `{}`"#, stringify!($($pattern)|+ $(if $guard)?)) + } } }; - (@impl #[requires($version:tt)] $name:ident($tester:ident) $body:block) => { - test_case!(@impl $name($tester) { - #[cfg(feature = $version)] { - $body + ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $($arg:tt)+) => { + match $left { + $($pattern)|+ $(if $guard)? => {} + ref left_val => { + panic!(r#"assertion `(left matches right)` failed: {} + left: `{left_val:?}` + right: `{}`"#, format_args!($($arg)+), stringify!($($pattern)|+ $(if $guard)?)) } - }); - }; - ($($(#[requires($version:tt)])? $name:ident($tester:ident) $body:block)+) => { - fn test_case() -> &'static $crate::common::TestCase { - use std::sync::OnceLock; - static TEST_CASE: OnceLock<$crate::common::TestCase> = OnceLock::new(); - TEST_CASE.get_or_init(|| { - $crate::common::TestCase::new(count!($($name)+)) - }) } - $(test_case!(@impl $(#[requires($version)])* $name($tester) $body);)+ }; } @@ -50,22 +37,6 @@ pub fn passphrase_cb(_req: PassphraseRequest<'_>, out: &mut dyn Write) -> gpgme: Ok(()) } -fn import_key(key: &[u8]) { - let gpg = env::var_os("GPG").unwrap_or("gpg".into()); - let mut child = Command::new(gpg) - .arg("--no-permission-warning") - .arg("--passphrase") - .arg("abc") - .arg("--import") - .stdin(Stdio::piped()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .unwrap(); - child.stdin.as_mut().unwrap().write_all(key).unwrap(); - assert!(child.wait().unwrap().success()); -} - fn setup_agent(dir: &Path) { env::set_var("GNUPGHOME", dir); env::set_var("GPG_AGENT_INFO", ""); @@ -75,99 +46,88 @@ fn setup_agent(dir: &Path) { } let conf = dir.join("gpg.conf"); - fs::write( - conf, - "ignore-invalid-option allow-weak-key-signatures\n\ - allow-weak-key-signatures\n", - ) - .unwrap(); + fs::write(conf, include_str!("./data/gpg.conf")).unwrap(); let agent_conf = dir.join("gpg-agent.conf"); fs::write( agent_conf, format!( - "ignore-invalid-option allow-loopback-pinentry\n\ - allow-loopback-pinentry\n\ - ignore-invalid-option pinentry-mode\n\ - pinentry-mode loopback\n\ - pinentry-program {}\n", + concat!( + include_str!("./data/gpg-agent.conf"), + "pinentry-program {}\n" + ), pinentry.to_str().unwrap() ), ) .unwrap(); } -pub struct TestCase { - count: AtomicUsize, - homedir: RwLock>, +fn import_key(key: &[u8]) { + let gpg = env::var_os("GPG").unwrap_or("gpg".into()); + let mut child = Command::new(gpg) + .args([ + "--batch", + "--no-permission-warning", + "--passphrase", + "abc", + "--import", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .unwrap(); + child.stdin.as_mut().unwrap().write_all(key).unwrap(); + assert!(child.wait().unwrap().success()); } -impl TestCase { - pub fn new(count: usize) -> TestCase { - let dir = TempDir::new().unwrap(); - setup_agent(dir.path()); - import_key(include_bytes!("./data/pubdemo.asc")); - import_key(include_bytes!("./data/secdemo.asc")); - TestCase { - count: AtomicUsize::new(count), - homedir: RwLock::new(Some(dir)), - } - } - - pub fn new_test(&self) -> Test<'_> { - Test { parent: self } - } - - pub fn kill_agent(&self) { - let socket = { - let homedir = self.homedir.read().unwrap(); - homedir.as_ref().unwrap().path().join("S.gpg-agent") - }; - let mut child = match Command::new("gpg-connect-agent") - .arg("-S") - .arg(socket) - .stdin(Stdio::piped()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - { - Ok(child) => child, - Err(err) => { - println!("Unable to kill agent: {}", err); - return; - } - }; - if let Some(ref mut stdin) = child.stdin { - let _ = stdin.write_all(b"KILLAGENT\nBYE\n"); - let _ = stdin.flush(); - } - if let Err(err) = child.wait() { - println!("Unable to kill agent: {}", err); - } - } - - fn drop(&self) { - if self.count.fetch_sub(1, Ordering::SeqCst) == 1 { - self.kill_agent(); - self.homedir.write().unwrap().take(); - } - } +fn import_ownertrust() { + let gpg = env::var_os("GPG").unwrap_or("gpg".into()); + let mut child = Command::new(gpg) + .args([ + "--batch", + "--no-permission-warning", + "--passphrase", + "abc", + "--import", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .unwrap(); + child + .stdin + .as_mut() + .unwrap() + .write_all(include_bytes!("./data/ownertrust.txt")) + .unwrap(); + let _ = child.wait(); } -pub struct Test<'a> { - parent: &'a TestCase, +pub fn setup() { + let dir = env::current_dir().unwrap(); + setup_agent(&dir); + import_key(include_bytes!("./data/pubdemo.asc")); + import_key(include_bytes!("./data/secdemo.asc")); + import_ownertrust(); + + gpgme::init() + .check_engine_version(gpgme::Protocol::OpenPgp) + .unwrap(); } -impl Drop for Test<'_> { - fn drop(&mut self) { - self.parent.drop(); - } +pub fn teardown() { + let _ = Command::new("gpgconf") + .args(["--kill", "all"]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); } -impl Test<'_> { - pub fn create_context(&self) -> Context { - let mut ctx = Context::from_protocol(gpgme::Protocol::OpenPgp).unwrap(); - let _ = ctx.set_pinentry_mode(PinentryMode::Loopback); - ctx - } +pub fn create_context() -> Context { + let mut ctx = Context::from_protocol(gpgme::Protocol::OpenPgp).unwrap(); + let _ = ctx.set_pinentry_mode(PinentryMode::Loopback); + ctx } diff --git a/tests/context.rs b/tests/context.rs index edba3856b..0b481abf2 100644 --- a/tests/context.rs +++ b/tests/context.rs @@ -1,20 +1,20 @@ use gpgme::{Context, Error, PinentryMode}; +use sealed_test::prelude::*; #[macro_use] mod common; -test_case! { - test_pinentry_mode(test) { - let mode = PinentryMode::Loopback; - let mut ctx = test.create_context(); - match ctx.set_pinentry_mode(mode) { - Ok(()) => { - // NOTE: UFCS form used here as regression test for - // issue #17. - assert_eq!(mode, Context::pinentry_mode(&ctx)); - } - Err(e) if e.code() == Error::NOT_SUPPORTED.code() => (), - e @ Err(_) => e.unwrap(), +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_pinentry_mode() { + let mode = PinentryMode::Loopback; + let mut ctx = common::create_context(); + match ctx.set_pinentry_mode(mode) { + Ok(()) => { + // NOTE: UFCS form used here as regression test for + // issue #17. + assert_eq!(mode, Context::pinentry_mode(&ctx)); } + Err(e) if e.code() == Error::NOT_SUPPORTED.code() => (), + e @ Err(_) => e.unwrap(), } } diff --git a/tests/create_key.rs b/tests/create_key.rs index e75409b34..2f46e6992 100644 --- a/tests/create_key.rs +++ b/tests/create_key.rs @@ -1,78 +1,110 @@ use std::time::{Duration, SystemTime}; use gpgme::CreateKeyFlags; +use sealed_test::prelude::*; use self::common::passphrase_cb; #[macro_use] mod common; -test_case! { - test_create_key(test) { - let mut ctx = test.create_context().set_passphrase_provider(passphrase_cb); - if !ctx.engine_info().check_version("2.1.13") { - return; - } +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_create_key() { + let mut ctx = common::create_context().set_passphrase_provider(passphrase_cb); + if !ctx.engine_info().check_version("2.1.13") { + return; + } - let expiration = Duration::from_secs(3600); - let res = ctx.create_key_with_flags("test user ", - "future-default", expiration, CreateKeyFlags::CERT).unwrap(); - let creation = SystemTime::now(); + let expiration = Duration::from_secs(3600); + let res = ctx + .create_key_with_flags( + "test user ", + "future-default", + expiration, + CreateKeyFlags::CERT, + ) + .unwrap(); + let creation = SystemTime::now(); - let fpr = res.fingerprint_raw().unwrap(); - assert!(res.has_primary_key()); - let mut key = ctx.get_key(fpr).unwrap(); - assert!(!key.is_bad()); - assert!(key.can_certify()); - assert!(!key.can_sign()); - assert!(!key.can_encrypt()); - assert!(!key.can_authenticate()); + let fpr = res.fingerprint_raw().unwrap(); + assert!(res.has_primary_key()); + let mut key = ctx.get_key(fpr).unwrap(); + assert!(!key.is_bad()); + assert!(key.can_certify()); + assert!(!key.can_sign()); + assert!(!key.can_encrypt()); + assert!(!key.can_authenticate()); - let primary = key.primary_key().unwrap(); - assert!(!primary.is_bad()); - assert!(primary.can_certify()); - assert!(!primary.can_sign()); - assert!(!primary.can_encrypt()); - assert!(!primary.can_authenticate()); - assert!(primary.expiration_time().unwrap().duration_since(creation).unwrap() <= expiration); + let primary = key.primary_key().unwrap(); + assert!(!primary.is_bad()); + assert!(primary.can_certify()); + assert!(!primary.can_sign()); + assert!(!primary.can_encrypt()); + assert!(!primary.can_authenticate()); + assert!( + primary + .expiration_time() + .unwrap() + .duration_since(creation) + .unwrap() + <= expiration + ); - assert_eq!(key.subkeys().count(), 1); - assert_eq!(key.user_ids().count(), 1); - let uid = key.user_ids().next().unwrap(); - assert!(!uid.is_bad()); - assert_eq!(uid.name(), Ok("test user")); - assert_eq!(uid.email(), Ok("test@example.com")); - assert_eq!(uid.id(), Ok("test user ")); + assert_eq!(key.subkeys().count(), 1); + assert_eq!(key.user_ids().count(), 1); + let uid = key.user_ids().next().unwrap(); + assert!(!uid.is_bad()); + assert_eq!(uid.name(), Ok("test user")); + assert_eq!(uid.email(), Ok("test@example.com")); + assert_eq!(uid.id(), Ok("test user ")); - let res = ctx.create_subkey_with_flags(&key, "future-default", expiration, - CreateKeyFlags::AUTH).unwrap(); - let creation = SystemTime::now(); - assert!(res.has_sub_key()); - key.update().unwrap(); - assert!(key.can_authenticate()); - assert_eq!(key.subkeys().count(), 2); + let res = ctx + .create_subkey_with_flags(&key, "future-default", expiration, CreateKeyFlags::AUTH) + .unwrap(); + let creation = SystemTime::now(); + assert!(res.has_sub_key()); + key.update().unwrap(); + assert!(key.can_authenticate()); + assert_eq!(key.subkeys().count(), 2); - let sub = key.subkeys().find(|k| k.fingerprint_raw() == res.fingerprint_raw()).unwrap(); - assert!(!sub.is_bad()); - assert!(!sub.can_certify()); - assert!(!sub.can_sign()); - assert!(!sub.can_encrypt()); - assert!(sub.can_authenticate()); - assert!(sub.expiration_time().unwrap().duration_since(creation).unwrap() <= expiration); + let sub = key + .subkeys() + .find(|k| k.fingerprint_raw() == res.fingerprint_raw()) + .unwrap(); + assert!(!sub.is_bad()); + assert!(!sub.can_certify()); + assert!(!sub.can_sign()); + assert!(!sub.can_encrypt()); + assert!(sub.can_authenticate()); + assert!( + sub.expiration_time() + .unwrap() + .duration_since(creation) + .unwrap() + <= expiration + ); - let res = ctx.create_subkey_with_flags(&key, "future-default", expiration, - CreateKeyFlags::ENCR | CreateKeyFlags::NOEXPIRE).unwrap(); - assert!(res.has_sub_key()); - key.update().unwrap(); - assert!(key.can_authenticate()); - assert_eq!(key.subkeys().count(), 3); + let res = ctx + .create_subkey_with_flags( + &key, + "future-default", + expiration, + CreateKeyFlags::ENCR | CreateKeyFlags::NOEXPIRE, + ) + .unwrap(); + assert!(res.has_sub_key()); + key.update().unwrap(); + assert!(key.can_authenticate()); + assert_eq!(key.subkeys().count(), 3); - let sub = key.subkeys().find(|k| k.fingerprint_raw() == res.fingerprint_raw()).unwrap(); - assert!(!sub.is_bad()); - assert!(!sub.can_certify()); - assert!(!sub.can_sign()); - assert!(sub.can_encrypt()); - assert!(!sub.can_authenticate()); - assert_eq!(sub.expiration_time(), None); - } + let sub = key + .subkeys() + .find(|k| k.fingerprint_raw() == res.fingerprint_raw()) + .unwrap(); + assert!(!sub.is_bad()); + assert!(!sub.can_certify()); + assert!(!sub.can_sign()); + assert!(sub.can_encrypt()); + assert!(!sub.can_authenticate()); + assert_eq!(sub.expiration_time(), None); } diff --git a/tests/data.rs b/tests/data.rs new file mode 100644 index 000000000..4331485eb --- /dev/null +++ b/tests/data.rs @@ -0,0 +1,91 @@ +use std::io::{prelude::*, SeekFrom}; + +use gpgme::Data; +use sealed_test::prelude::*; + +#[macro_use] +mod common; + +type Round = u32; +const TEST_INITIALIZER: Round = 0; +const TEST_INOUT_NONE: Round = 1; +const TEST_INOUT_MEM_NO_COPY: Round = 2; +const TEST_INOUT_MEM_COPY: Round = 3; +const TEST_INOUT_MEM_FROM_FILE_COPY: Round = 4; +const TEST_INOUT_MEM_FROM_INEXISTANT_FILE: Round = 5; +const TEST_INOUT_VEC_NONE: Round = 6; +const TEST_INOUT_VEC: Round = 7; +const TEST_END: Round = 8; + +const TEXT: &str = "Just GNU it!\n"; +const TEXT2: &str = "Just GNU it!\nJust GNU it!\n"; + +fn read_once_test(rnd: Round, data: &mut Data) { + let mut buffer = [0u8; 1024]; + + let read = data.read(&mut buffer[..]); + assert_matches!(read, Ok(1..), "round {rnd:?}"); + assert_eq!(&buffer[..read.unwrap()], TEXT.as_bytes(), "round {rnd:?}"); + + let read = data.read(&mut buffer[..]); + assert_matches!(read, Ok(0), "round {rnd:?}"); +} + +fn read_test(rnd: Round, data: &mut Data) { + let mut buffer = [0u8; 1024]; + if matches!(rnd, TEST_INOUT_NONE | TEST_INOUT_VEC_NONE) { + let read = data.read(&mut buffer); + assert_matches!(read, Err(_) | Ok(0), "round {rnd:?}"); + return; + } + read_once_test(rnd, data); + data.seek(SeekFrom::Start(0)).unwrap(); + read_once_test(rnd, data); +} + +fn write_test(rnd: Round, data: &mut Data) { + let mut buffer = [0u8; 1024]; + let amt = data.write(TEXT.as_bytes()).unwrap(); + assert_eq!(amt, TEXT.len(), "round {rnd:?}"); + + data.seek(SeekFrom::Start(0)).unwrap(); + if matches!(rnd, TEST_INOUT_NONE | TEST_INOUT_VEC_NONE) { + read_once_test(rnd, data); + } else { + let amt = data.read(&mut buffer[..]); + assert_matches!(amt, Ok(1..), "round {rnd:?}"); + assert_eq!(&buffer[..amt.unwrap()], TEXT2.as_bytes()); + + let amt = data.read(&mut buffer[..]); + assert_matches!(amt, Ok(0), "round {rnd:?}"); + } +} + +const MISSING_FILE_NAME: &str = "this-file-surely-does-not-exist"; + +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_data() { + let mut rnd = TEST_INITIALIZER; + + loop { + rnd += 1; + let mut data = match rnd { + TEST_INOUT_NONE => Data::new().unwrap(), + TEST_INOUT_MEM_NO_COPY => Data::from_buffer(TEXT.as_bytes()).unwrap(), + TEST_INOUT_MEM_COPY => Data::from_bytes(TEXT.as_bytes()).unwrap(), + TEST_INOUT_MEM_FROM_FILE_COPY => continue, + TEST_INOUT_MEM_FROM_INEXISTANT_FILE => { + let res = Data::load(MISSING_FILE_NAME); + assert_matches!(res, Err(_), "round {rnd}"); + continue; + } + TEST_INOUT_VEC_NONE => Data::try_from(Vec::new()).unwrap(), + TEST_INOUT_VEC => Data::try_from(TEXT.as_bytes().to_vec()).unwrap(), + TEST_END => break, + _ => panic!("unexpected round {rnd}"), + }; + + read_test(rnd, &mut data); + write_test(rnd, &mut data); + } +} diff --git a/tests/edit.rs b/tests/edit.rs index 43be65c7b..e46c7ea6a 100644 --- a/tests/edit.rs +++ b/tests/edit.rs @@ -1,10 +1,11 @@ #![allow(deprecated)] -use std::io::prelude::*; +use std::io::{prelude::*, stdout}; use gpgme::{ edit::{self, EditInteractionStatus, Editor}, Error, Result, }; +use sealed_test::prelude::*; use self::common::passphrase_cb; @@ -77,11 +78,15 @@ impl Editor for TestEditor { } } -test_case! { - test_edit(test) { - test.create_context().with_passphrase_provider(passphrase_cb, |ctx| { - let key = ctx.find_keys(Some("Alpha")).unwrap().next().unwrap().unwrap(); - ctx.edit_key_with(&key, TestEditor, &mut Vec::new()).unwrap(); - }); - } +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_edit() { + common::create_context().with_passphrase_provider(passphrase_cb, |ctx| { + let key = ctx + .find_keys(Some("Alpha")) + .unwrap() + .next() + .unwrap() + .unwrap(); + ctx.edit_key_with(&key, TestEditor, stdout()).unwrap(); + }); } diff --git a/tests/encrypt_simple.rs b/tests/encrypt_simple.rs index 83bf09c83..0ca2e50a2 100644 --- a/tests/encrypt_simple.rs +++ b/tests/encrypt_simple.rs @@ -1,31 +1,43 @@ +use sealed_test::prelude::*; + use self::common::passphrase_cb; #[macro_use] mod common; -test_case! { - test_simple_encrypt_decrypt(test) { - let mut ctx = test.create_context(); - - let key = ctx.find_keys(Some("alfa@example.net")).unwrap().next().unwrap().unwrap(); +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_simple_encrypt_decrypt() { + let mut ctx = common::create_context(); - ctx.set_armor(true); - ctx.set_text_mode(true); + let key = ctx + .find_keys(Some("alfa@example.net")) + .unwrap() + .next() + .unwrap() + .unwrap(); - let mut ciphertext = Vec::new(); - ctx.encrypt_with_flags(Some(&key), "Hello World", &mut ciphertext, gpgme::EncryptFlags::ALWAYS_TRUST).unwrap(); - assert!(ciphertext.starts_with(b"-----BEGIN PGP MESSAGE-----")); - drop(ctx); + ctx.set_armor(true); + ctx.set_text_mode(true); - let mut plaintext = Vec::new(); - test.create_context().with_passphrase_provider(passphrase_cb, |ctx| { - ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); - }); - assert_eq!(plaintext, b"Hello World"); + let mut ciphertext = Vec::new(); + ctx.encrypt_with_flags( + Some(&key), + "Hello World", + &mut ciphertext, + gpgme::EncryptFlags::ALWAYS_TRUST, + ) + .unwrap(); + assert!(ciphertext.starts_with(b"-----BEGIN PGP MESSAGE-----")); + drop(ctx); - let mut plaintext = Vec::new(); - let mut ctx = test.create_context().set_passphrase_provider(passphrase_cb); + let mut plaintext = Vec::new(); + common::create_context().with_passphrase_provider(passphrase_cb, |ctx| { ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); - assert_eq!(plaintext, b"Hello World"); - } + }); + assert_eq!(plaintext, b"Hello World"); + + let mut plaintext = Vec::new(); + let mut ctx = common::create_context().set_passphrase_provider(passphrase_cb); + ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); + assert_eq!(plaintext, b"Hello World"); } diff --git a/tests/encrypt_symmetric.rs b/tests/encrypt_symmetric.rs index f9b49a227..72e2fd35d 100644 --- a/tests/encrypt_symmetric.rs +++ b/tests/encrypt_symmetric.rs @@ -1,28 +1,30 @@ +use sealed_test::prelude::*; + use self::common::passphrase_cb; #[macro_use] mod common; -test_case! { - test_symmetric_encrypt_decrypt(test) { - let mut ciphertext = Vec::new(); - test.create_context().with_passphrase_provider(passphrase_cb, |ctx| { - ctx.set_armor(true); - ctx.set_text_mode(true); - - ctx.encrypt_symmetric("Hello World", &mut ciphertext).unwrap(); - }); - assert!(ciphertext.starts_with(b"-----BEGIN PGP MESSAGE-----")); +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_symmetric_encrypt_decrypt() { + let mut ciphertext = Vec::new(); + common::create_context().with_passphrase_provider(passphrase_cb, |ctx| { + ctx.set_armor(true); + ctx.set_text_mode(true); - let mut plaintext = Vec::new(); - test.create_context().with_passphrase_provider(passphrase_cb, |ctx| { - ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); - }); - assert_eq!(plaintext, b"Hello World"); + ctx.encrypt_symmetric("Hello World", &mut ciphertext) + .unwrap(); + }); + assert!(ciphertext.starts_with(b"-----BEGIN PGP MESSAGE-----")); - let mut plaintext = Vec::new(); - let mut ctx = test.create_context().set_passphrase_provider(passphrase_cb); + let mut plaintext = Vec::new(); + common::create_context().with_passphrase_provider(passphrase_cb, |ctx| { ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); - assert_eq!(plaintext, b"Hello World"); - } + }); + assert_eq!(plaintext, b"Hello World"); + + let mut plaintext = Vec::new(); + let mut ctx = common::create_context().set_passphrase_provider(passphrase_cb); + ctx.decrypt(&ciphertext, &mut plaintext).unwrap(); + assert_eq!(plaintext, b"Hello World"); } diff --git a/tests/export.rs b/tests/export.rs index 2711af8c4..71d86ccc8 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -1,38 +1,51 @@ use gpgme::{CreateKeyFlags, ExportMode}; +use sealed_test::prelude::*; use self::common::passphrase_cb; #[macro_use] mod common; -test_case! { - test_export(test) { - let mut ctx = test.create_context().set_passphrase_provider(passphrase_cb); - ctx.set_offline(true); - ctx.set_armor(true); - - let key = ctx.find_keys(Some("alfa@example.net")).unwrap().next().unwrap().unwrap(); - - let mut data = Vec::new(); - ctx.export(key.fingerprint_raw(), ExportMode::empty(), &mut data).unwrap(); - assert!(!data.is_empty()); - } - - test_export_secret(test) { - let mut ctx = test.create_context(); - ctx.set_offline(true); - ctx.set_armor(true); - - let res = match ctx.create_key_with_flags("test user ", - "future-default", Default::default(), CreateKeyFlags::NOPASSWD) { - Ok(r) => r, - Err(e) if e.code() == gpgme::Error::NOT_SUPPORTED.code() => return, - Err(e) => panic!("error: {:?}", e), - }; - let fpr = res.fingerprint_raw().unwrap(); - - let mut data = Vec::new(); - ctx.export(Some(fpr), ExportMode::SECRET, &mut data).unwrap(); - assert!(!data.is_empty()); - } +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_export() { + let mut ctx = common::create_context().set_passphrase_provider(passphrase_cb); + ctx.set_offline(true); + ctx.set_armor(true); + + let key = ctx + .find_keys(Some("alfa@example.net")) + .unwrap() + .next() + .unwrap() + .unwrap(); + + let mut data = Vec::new(); + println!("{:?}", key.fingerprint()); + ctx.export(key.fingerprint_raw(), ExportMode::empty(), &mut data) + .unwrap(); + assert!(!data.is_empty()); +} + +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_export_secret() { + let mut ctx = common::create_context(); + ctx.set_offline(true); + ctx.set_armor(true); + + let res = match ctx.create_key_with_flags( + "test user ", + "future-default", + Default::default(), + CreateKeyFlags::NOPASSWD, + ) { + Ok(r) => r, + Err(e) if e.code() == gpgme::Error::NOT_SUPPORTED.code() => return, + Err(e) => panic!("error: {e:?}"), + }; + let fpr = res.fingerprint_raw().unwrap(); + + let mut data = Vec::new(); + ctx.export(Some(fpr), ExportMode::SECRET, &mut data) + .unwrap(); + assert!(!data.is_empty()); } diff --git a/tests/keylist.rs b/tests/keylist.rs index 707583e59..ded7f50d6 100644 --- a/tests/keylist.rs +++ b/tests/keylist.rs @@ -1,18 +1,22 @@ +use sealed_test::prelude::*; + #[macro_use] mod common; -test_case! { - test_key_list(test) { - let mut ctx = test.create_context(); - let keys: Vec<_> = ctx.find_keys(Some("alfa@example.net")).unwrap() - .collect::>().unwrap(); - assert_eq!(keys.len(), 1, "incorrect number of keys"); +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_key_list() { + let mut ctx = common::create_context(); + let keys: Vec<_> = ctx + .find_keys(Some("alfa@example.net")) + .unwrap() + .collect::>() + .unwrap(); + assert_eq!(keys.len(), 1, "incorrect number of keys"); - let key = &keys[0]; - assert_eq!(key.id(), Ok("2D727CC768697734")); - assert_eq!(key.subkeys().count(), 2); - let subkeys: Vec<_> = key.subkeys().collect(); - assert_eq!(subkeys[0].algorithm(), gpgme::KeyAlgorithm::Dsa); - assert_eq!(subkeys[1].algorithm(), gpgme::KeyAlgorithm::ElgamalEncrypt); - } + let key = &keys[0]; + assert_eq!(key.id(), Ok("2D727CC768697734")); + assert_eq!(key.subkeys().count(), 2); + let subkeys: Vec<_> = key.subkeys().collect(); + assert_eq!(subkeys[0].algorithm(), gpgme::KeyAlgorithm::Dsa); + assert_eq!(subkeys[1].algorithm(), gpgme::KeyAlgorithm::ElgamalEncrypt); } diff --git a/tests/keysign.rs b/tests/keysign.rs index 0e37722e0..0ce58f437 100644 --- a/tests/keysign.rs +++ b/tests/keysign.rs @@ -1,33 +1,50 @@ use gpgme::KeyListMode; +use sealed_test::prelude::*; use self::common::passphrase_cb; #[macro_use] mod common; -test_case! { - test_sign_key(test) { - let mut ctx = test.create_context(); - if !ctx.engine_info().check_version("2.1.12") { - return; - } +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_sign_key() { + let mut ctx = common::create_context(); + if !ctx.engine_info().check_version("2.1.12") { + return; + } - ctx.add_key_list_mode(KeyListMode::SIGS).unwrap(); - let signer = ctx.find_secret_keys(Some("alfa@example.net")).unwrap().next().unwrap().unwrap(); - ctx.add_signer(&signer).unwrap(); + ctx.add_key_list_mode(KeyListMode::SIGS).unwrap(); + let signer = ctx + .find_secret_keys(Some("alfa@example.net")) + .unwrap() + .next() + .unwrap() + .unwrap(); + ctx.add_signer(&signer).unwrap(); - let mut key = ctx.find_keys(Some("bravo@example.net")).unwrap().next().unwrap().unwrap(); - assert!(!key.user_ids().next().unwrap().signatures().any(|s| { - signer.id_raw() == s.signer_key_id_raw() - })); + let mut key = ctx + .find_keys(Some("bravo@example.net")) + .unwrap() + .next() + .unwrap() + .unwrap(); + assert!(!key + .user_ids() + .next() + .unwrap() + .signatures() + .any(|s| { signer.id_raw() == s.signer_key_id_raw() })); - ctx.with_passphrase_provider(passphrase_cb, |ctx| { - ctx.sign_key(&key, None::, Default::default()).unwrap(); - }); + ctx.with_passphrase_provider(passphrase_cb, |ctx| { + ctx.sign_key(&key, None::, Default::default()) + .unwrap(); + }); - key.update().unwrap(); - assert!(key.user_ids().next().unwrap().signatures().any(|s| { - signer.id_raw() == s.signer_key_id_raw() - })); - } + key.update().unwrap(); + assert!(key + .user_ids() + .next() + .unwrap() + .signatures() + .any(|s| { signer.id_raw() == s.signer_key_id_raw() })); } diff --git a/tests/verify.rs b/tests/verify.rs index 6a28533bd..0f800d26e 100644 --- a/tests/verify.rs +++ b/tests/verify.rs @@ -1,28 +1,29 @@ +use sealed_test::prelude::*; + #[macro_use] mod common; const TEST_MSG1: &[u8] = b"-----BEGIN PGP MESSAGE-----\n\ - \n\ - owGbwMvMwCSoW1RzPCOz3IRxjXQSR0lqcYleSUWJTZOvjVdpcYmCu1+oQmaJIleH\n\ - GwuDIBMDGysTSIqBi1MApi+nlGGuwDeHao53HBr+FoVGP3xX+kvuu9fCMJvl6IOf\n\ - y1kvP4y+8D5a11ang0udywsA\n\ - =Crq6\n\ - -----END PGP MESSAGE-----\n"; + \n\ + owGbwMvMwCSoW1RzPCOz3IRxjXQSR0lqcYleSUWJTZOvjVdpcYmCu1+oQmaJIleH\n\ + GwuDIBMDGysTSIqBi1MApi+nlGGuwDeHao53HBr+FoVGP3xX+kvuu9fCMJvl6IOf\n\ + y1kvP4y+8D5a11ang0udywsA\n\ + =Crq6\n\ + -----END PGP MESSAGE-----\n"; -test_case! { - test_signature_key(test) { - let mut output = Vec::new(); - let mut ctx = test.create_context(); - let result = ctx.verify_opaque(TEST_MSG1, &mut output).unwrap(); - assert_eq!(result.signatures().count(), 1); +#[sealed_test(before = common::setup(), after = common::teardown())] +fn test_signature_key() { + let mut ctx = common::create_context(); + let mut output = Vec::new(); + let result = ctx.verify_opaque(TEST_MSG1, &mut output).unwrap(); + assert_eq!(result.signatures().count(), 1); - let sig = result.signatures().next().unwrap(); - let key = ctx.get_key(sig.fingerprint_raw().unwrap()).unwrap(); - for subkey in key.subkeys() { - if subkey.fingerprint_raw() == sig.fingerprint_raw() { - return; - } + let sig = result.signatures().next().unwrap(); + let key = ctx.get_key(sig.fingerprint_raw().unwrap()).unwrap(); + for subkey in key.subkeys() { + if subkey.fingerprint_raw() == sig.fingerprint_raw() { + return; } - panic!(); } + panic!(); }