diff --git a/.ci/tarpaulin.sh b/.ci/tarpaulin.sh
new file mode 100755
index 0000000..e146b75
--- /dev/null
+++ b/.ci/tarpaulin.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+set -e
+
+readonly version="0.10.0"
+readonly sha256sum="6843be8384bf14385b36a3118efc1ed2d25d531acb8df954cd3f93d44018b09e"
+readonly filename="cargo-tarpaulin-$version-travis"
+readonly tarball="$filename.tar.gz"
+
+cd .ci
+
+echo "$sha256sum  $tarball" > tarpaulin.sha256sum
+curl -OL "https://github.com/xd009642/tarpaulin/releases/download/$version/$tarball"
+sha256sum --check tarpaulin.sha256sum
+tar xf "$tarball"
diff --git a/.cirrus.yml b/.cirrus.yml
index 25df65e..2314e9b 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -19,7 +19,7 @@ rustfmt_task:
 linux_task:
     matrix:
     - container:
-        image: rust:1.34.0
+        image: rust:1.35.0
     - container:
         image: rust:latest
     - allow_failures: true
@@ -31,7 +31,7 @@ linux_task:
         folder: $CARGO_HOME/registry
         fingerprint_script: cat Cargo.lock
     build_script: cargo build
-    test_script: cargo test
+    test_script: cargo test -- --test-threads 1
     before_cache_script: rm -rf $CARGO_HOME/registry/index
 
 minimal_version_task:
@@ -52,13 +52,13 @@ coverage_task:
         CODECOV_TOKEN: ENCRYPTED[1e221ef78a37c960613ff80db7141f3158e3218031934395466f4720f450b7acfd74e587819435ce9be0b13fa1b68f1b]
     keyutils_script: apt-get update && apt-get install libkeyutils-dev
     tarpaulin_cache:
-        folder: $CARGO_HOME/bin
-        populate_script: cargo install --version 0.8.7 cargo-tarpaulin
-        fingerprint_script: cargo install --list
+        folder: .ci
+        populate_script: .ci/tarpaulin.sh
+        fingerprint_script: cat .ci/tarpaulin.sh
     lockfile_script: cargo generate-lockfile
     cargo_cache:
         folder: $CARGO_HOME/registry
         fingerprint_script: cat Cargo.lock
-    coverage_script: cargo tarpaulin --out Xml
+    coverage_script: PATH=$PATH:$PWD/.ci cargo tarpaulin --out Xml
     upload_script: bash <(curl -s https://codecov.io/bash) -X gcov
     before_cache_script: rm -rf $CARGO_HOME/registry/index
diff --git a/Cargo.toml b/Cargo.toml
index 35ce3c6..51decba 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,9 @@ members = ["keyutils-raw"]
 [dev-dependencies]
 lazy_static = "1"
 regex = "1"
+serial_test = "*"
+serial_test_derive = "*"
+semver = "*"
 
 [dependencies]
 bitflags = "1.0.4"
diff --git a/keyutils-raw/src/functions.rs b/keyutils-raw/src/functions.rs
index 2a29a29..cd8bf07 100644
--- a/keyutils-raw/src/functions.rs
+++ b/keyutils-raw/src/functions.rs
@@ -89,7 +89,7 @@ extern "C" {
         ringid:         KeyringSerial,
         type_:          *const libc::c_char,
         description:    *const libc::c_char,
-        destringid:     KeyringSerial)
+        destringid:     Option<KeyringSerial>)
         -> libc::c_long;
     pub fn keyctl_read(
         id:     KeyringSerial,
@@ -100,12 +100,12 @@ extern "C" {
         id:         KeyringSerial,
         payload:    *const libc::c_void,
         plen:       libc::size_t,
-        ringid:     KeyringSerial)
+        ringid:     Option<KeyringSerial>)
         -> libc::c_long;
     pub fn keyctl_negate(
         id:         KeyringSerial,
         timeout:    TimeoutSeconds,
-        ringid:     KeyringSerial)
+        ringid:     Option<KeyringSerial>)
         -> libc::c_long;
     pub fn keyctl_set_reqkey_keyring(
         reqkey_defl:    libc::c_int)
@@ -128,7 +128,7 @@ extern "C" {
         id:         KeyringSerial,
         timeout:    TimeoutSeconds,
         error:      libc::c_uint,
-        ringid:     KeyringSerial)
+        ringid:     Option<KeyringSerial>)
         -> libc::c_long;
     pub fn keyctl_invalidate(
         id: KeyringSerial)
diff --git a/src/api.rs b/src/api.rs
index 4ddcd7c..314f5e4 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -66,6 +66,7 @@ fn into_serial(res: libc::c_long) -> KeyringSerial {
     KeyringSerial::new(res as i32).unwrap()
 }
 
+/// Request a key from the kernel.
 fn request_impl<K: KeyType>(
     description: &str,
     info: Option<&str>,
@@ -89,6 +90,10 @@ impl Keyring {
     /// Instantiate a keyring from an ID.
     ///
     /// This is unsafe because no keyring is known to exist with the given ID.
+    ///
+    /// # Safety
+    ///
+    /// This method assumes that the given serial is a valid keyring ID at the kernel level.
     pub unsafe fn new(id: KeyringSerial) -> Self {
         Keyring {
             id,
@@ -101,6 +106,11 @@ impl Keyring {
         }
     }
 
+    #[cfg(test)]
+    pub(crate) fn serial(&self) -> KeyringSerial {
+        self.id
+    }
+
     /// Set the default keyring to use when implicit requests on the current thread.
     ///
     /// Returns the old default keyring.
@@ -117,31 +127,23 @@ impl Keyring {
 
     /// Requests a keyring with the given description by searching the thread, process, and session
     /// keyrings.
-    pub fn request<D>(description: D) -> Result<Self>
+    ///
+    /// If it is not found, the `info` string (if provided) will be handed off to
+    /// `/sbin/request-key` to generate the key.
+    ///
+    /// If `target` is given, the found keyring will be linked into it. If `target` is not given
+    /// and a new key is constructed due to the request, it will be linked into the default
+    /// keyring (see `Keyring::set_default`).
+    pub fn request<'s, 'a, D, I, T>(description: D, info: I, target: T) -> Result<Self>
     where
         D: AsRef<str>,
+        I: Into<Option<&'s str>>,
+        T: Into<Option<TargetKeyring<'a>>>,
     {
         check_call_keyring(request_impl::<keytypes::Keyring>(
             description.as_ref(),
-            None,
-            None,
-        ))
-    }
-
-    /// Requests a keyring with the given description by searching the thread, process, and session
-    /// keyrings.
-    ///
-    /// If it is not found, the `info` string will be handed off to `/sbin/request-key` to generate
-    /// the key.
-    pub fn request_with_fallback<D, I>(description: D, info: I) -> Result<Self>
-    where
-        D: Borrow<<keytypes::Keyring as KeyType>::Description>,
-        I: AsRef<str>,
-    {
-        check_call_keyring(request_impl::<keytypes::Keyring>(
-            &description.borrow().description(),
-            Some(info.as_ref()),
-            None,
+            info.into().as_ref().copied(),
+            target.into().map(TargetKeyring::serial),
         ))
     }
 
@@ -213,14 +215,19 @@ impl Keyring {
         check_call(unsafe { keyctl_unlink(keyring.id, self.id) })
     }
 
-    fn search_impl<K>(&self, description: &str) -> KeyringSerial
+    fn search_impl<K>(&self, description: &str, destination: Option<&mut Keyring>) -> KeyringSerial
     where
         K: KeyType,
     {
         let type_cstr = CString::new(K::name()).unwrap();
         let desc_cstr = CString::new(description).unwrap();
         into_serial(unsafe {
-            keyctl_search(self.id, type_cstr.as_ptr(), desc_cstr.as_ptr(), self.id)
+            keyctl_search(
+                self.id,
+                type_cstr.as_ptr(),
+                desc_cstr.as_ptr(),
+                destination.map(|dest| dest.id),
+            )
         })
     }
 
@@ -229,12 +236,15 @@ impl Keyring {
     /// If it is found, it is attached to the keyring (if `write` permission to the keyring and
     /// `link` permission on the key exist) and return it. Requires the `search` permission on the
     /// keyring. Any children keyrings without the `search` permission are ignored.
-    pub fn search_for_key<K, D>(&self, description: D) -> Result<Key>
+    pub fn search_for_key<'a, K, D, DK>(&self, description: D, destination: DK) -> Result<Key>
     where
         K: KeyType,
         D: Borrow<K::Description>,
+        DK: Into<Option<&'a mut Keyring>>,
     {
-        check_call_key(self.search_impl::<K>(&description.borrow().description()))
+        check_call_key(
+            self.search_impl::<K>(&description.borrow().description(), destination.into()),
+        )
     }
 
     /// Recursively search the keyring for a keyring with the matching description.
@@ -243,19 +253,33 @@ impl Keyring {
     /// `link` permission on the found keyring exist) and return it. Requires the `search`
     /// permission on the keyring. Any children keyrings without the `search` permission are
     /// ignored.
-    pub fn search_for_keyring<D>(&self, description: D) -> Result<Self>
+    pub fn search_for_keyring<'a, D, DK>(&self, description: D, destination: DK) -> Result<Self>
     where
         D: Borrow<<keytypes::Keyring as KeyType>::Description>,
+        DK: Into<Option<&'a mut Keyring>>,
     {
-        check_call_keyring(
-            self.search_impl::<keytypes::Keyring>(&description.borrow().description()),
-        )
+        check_call_keyring(self.search_impl::<keytypes::Keyring>(
+            &description.borrow().description(),
+            destination.into(),
+        ))
     }
 
     /// Return all immediate children of the keyring.
     ///
     /// Requires `read` permission on the keyring.
     pub fn read(&self) -> Result<(Vec<Key>, Vec<Keyring>)> {
+        // The `description` check below hides this error code from the kernel.
+        if self.id.get() == 0 {
+            return Err(errno::Errno(libc::ENOKEY));
+        }
+
+        // Avoid a panic in the code below be ensuring that we actually have a keyring. Parsing
+        // a key's payload as a keyring payload.
+        let desc = self.description()?;
+        if desc.type_ != keytypes::Keyring::name() {
+            return Err(errno::Errno(libc::ENOTDIR));
+        }
+
         let sz = unsafe { keyctl_read(self.id, ptr::null_mut(), 0) };
         check_call(sz)?;
         let mut buffer = Vec::with_capacity((sz as usize) / mem::size_of::<KeyringSerial>());
@@ -344,74 +368,6 @@ impl Keyring {
         check_call_keyring(self.add_key_impl::<keytypes::Keyring>(description.borrow(), &()))
     }
 
-    /// Requests a key with the given description by searching the thread, process, and session
-    /// keyrings.
-    ///
-    /// If it is found, it is attached to the keyring.
-    pub fn request_key<K, D>(&self, description: D) -> Result<Key>
-    where
-        K: KeyType,
-        D: Borrow<K::Description>,
-    {
-        check_call_key(request_impl::<K>(
-            &description.borrow().description(),
-            None,
-            Some(self.id),
-        ))
-    }
-
-    /// Requests a keyring with the given description by searching the thread, process, and session
-    /// keyrings.
-    ///
-    /// If it is found, it is attached to the keyring.
-    pub fn request_keyring<D>(&self, description: D) -> Result<Self>
-    where
-        D: Borrow<<keytypes::Keyring as KeyType>::Description>,
-    {
-        check_call_keyring(request_impl::<keytypes::Keyring>(
-            &description.borrow().description(),
-            None,
-            Some(self.id),
-        ))
-    }
-
-    /// Requests a key with the given description by searching the thread, process, and session
-    /// keyrings.
-    ///
-    /// If it is not found, the `info` string will be handed off to `/sbin/request-key` to generate
-    /// the key. If found, it will be attached to the current keyring. Requires `write` permission
-    /// to the keyring.
-    pub fn request_key_with_fallback<K, D, I>(&self, description: D, info: I) -> Result<Key>
-    where
-        K: KeyType,
-        D: Borrow<K::Description>,
-        I: AsRef<str>,
-    {
-        check_call_key(request_impl::<K>(
-            &description.borrow().description(),
-            Some(info.as_ref()),
-            Some(self.id),
-        ))
-    }
-
-    /// Requests a keyring with the given description by searching the thread, process, and session
-    /// keyrings.
-    ///
-    /// If it is not found, the `info` string will be handed off to `/sbin/request-key` to generate
-    /// the key. If found, it will be attached to the current keyring. Requires `write` permission
-    /// to the keyring.
-    pub fn request_keyring_with_fallback<D, I>(&self, description: D, info: I) -> Result<Self>
-    where
-        D: Borrow<<keytypes::Keyring as KeyType>::Description>,
-        I: AsRef<str>,
-    {
-        check_call_keyring(request_impl::<keytypes::Keyring>(
-            &description.borrow().description(),
-            Some(info.as_ref()),
-            Some(self.id),
-        ))
-    }
-
     /// Revokes the keyring.
     ///
     /// Requires `write` permission on the keyring.
@@ -443,6 +399,11 @@ impl Keyring {
         check_call(unsafe { keyctl_setperm(self.id, perms.bits()) })
     }
 
+    #[cfg(test)]
+    pub(crate) fn set_permissions_raw(&mut self, perms: KeyPermissions) -> Result<()> {
+        check_call(unsafe { keyctl_setperm(self.id, perms) })
+    }
+
     fn description_raw(&self) -> Result<String> {
         let sz = unsafe { keyctl_describe(self.id, ptr::null_mut(), 0) };
         check_call(sz)?;
@@ -514,6 +475,10 @@ impl Key {
     /// Instantiate a key from an ID.
     ///
     /// This is unsafe because no key is known to exist with the given ID.
+    ///
+    /// # Safety
+    ///
+    /// This method assumes that the given serial is a valid key ID at the kernel level.
     pub unsafe fn new(id: KeyringSerial) -> Self {
         Self::new_impl(id)
     }
@@ -524,35 +489,31 @@ impl Key {
         }
     }
 
-    /// Requests a key with the given description by searching the thread, process, and session
-    /// keyrings.
-    pub fn request<K, D>(description: D) -> Result<Self>
-    where
-        K: KeyType,
-        D: Borrow<K::Description>,
-    {
-        check_call_key(request_impl::<K>(
-            &description.borrow().description(),
-            None,
-            None,
-        ))
+    #[cfg(test)]
+    pub(crate) fn serial(&self) -> KeyringSerial {
+        self.id
     }
 
-    /// Requests a key with the given description by searching the thread, process, and session
-    /// keyrings.
+    /// Requests a key with the given type and description by searching the thread, process, and
+    /// session keyrings.
+    ///
+    /// If it is not found, the `info` string (if provided) will be handed off to
+    /// `/sbin/request-key` to generate the key.
     ///
-    /// If it is not found, the `info` string will be handed off to `/sbin/request-key` to generate
-    /// the key.
-    pub fn request_with_fallback<K, D, I>(description: D, info: I) -> Result<Self>
+    /// If `target` is given, the found keyring will be linked into it. If `target` is not given
+    /// and a new key is constructed due to the request, it will be linked into the default
+    /// keyring (see `Keyring::set_default`).
+    pub fn request<'s, 'a, K, D, I, T>(description: D, info: I, target: T) -> Result<Self>
     where
         K: KeyType,
         D: Borrow<K::Description>,
-        I: AsRef<str>,
+        I: Into<Option<&'s str>>,
+        T: Into<Option<TargetKeyring<'a>>>,
     {
-        check_call_key(request_impl::<keytypes::Keyring>(
+        check_call_key(request_impl::<K>(
             &description.borrow().description(),
-            Some(info.as_ref()),
-            None,
+            info.into().as_ref().copied(),
+            target.into().map(TargetKeyring::serial),
         ))
     }
 
@@ -596,6 +557,11 @@ impl Key {
         Keyring::new_impl(self.id).set_permissions(perms)
     }
 
+    #[cfg(test)]
+    pub(crate) fn set_permissions_raw(&mut self, perms: KeyPermissions) -> Result<()> {
+        Keyring::new_impl(self.id).set_permissions_raw(perms)
+    }
+
     /// Retrieve metadata about the key.
     ///
     /// # Panics
@@ -738,6 +704,48 @@ impl Description {
     }
 }
 
+/// The destination keyring of an instantiation request.
+#[derive(Debug)]
+pub enum TargetKeyring<'a> {
+    /// A special keyring.
+    Special(SpecialKeyring),
+    /// A specific keyring.
+    Keyring(&'a mut Keyring),
+}
+
+impl<'a> TargetKeyring<'a> {
+    fn serial(self) -> KeyringSerial {
+        match self {
+            TargetKeyring::Special(special) => special.serial(),
+            TargetKeyring::Keyring(keyring) => keyring.id,
+        }
+    }
+}
+
+impl<'a> From<SpecialKeyring> for TargetKeyring<'a> {
+    fn from(special: SpecialKeyring) -> Self {
+        TargetKeyring::Special(special)
+    }
+}
+
+impl<'a> From<&'a mut Keyring> for TargetKeyring<'a> {
+    fn from(keyring: &'a mut Keyring) -> Self {
+        TargetKeyring::Keyring(keyring)
+    }
+}
+
+impl<'a> From<SpecialKeyring> for Option<TargetKeyring<'a>> {
+    fn from(special: SpecialKeyring) -> Self {
+        Some(special.into())
+    }
+}
+
+impl<'a> From<&'a mut Keyring> for Option<TargetKeyring<'a>> {
+    fn from(keyring: &'a mut Keyring) -> Self {
+        Some(keyring.into())
+    }
+}
+
 /// A manager for a key to respond to instantiate a key request by the kernel.
 #[derive(Debug, PartialEq, Eq)]
 pub struct KeyManager {
@@ -751,6 +759,11 @@ impl KeyManager {
         }
     }
 
+    #[cfg(test)]
+    pub(crate) fn test_new(key: Key) -> Self {
+        Self::new(key)
+    }
+
     /// Requests the authorization key created by `request_key`.
     ///
     /// This key must be present in an available keyring before `Key::manage` may be called.
@@ -766,8 +779,9 @@ impl KeyManager {
     }
 
     /// Instantiate the key with the given payload.
-    pub fn instantiate<P>(self, keyring: &Keyring, payload: P) -> Result<()>
+    pub fn instantiate<'a, T, P>(self, keyring: T, payload: P) -> Result<()>
     where
+        T: Into<Option<TargetKeyring<'a>>>,
         P: AsRef<[u8]>,
     {
         let payload = payload.as_ref();
@@ -776,7 +790,7 @@ impl KeyManager {
                 self.key.id,
                 payload.as_ptr() as *const libc::c_void,
                 payload.len(),
-                keyring.id,
+                keyring.into().map(TargetKeyring::serial),
             )
         })
     }
@@ -787,14 +801,17 @@ impl KeyManager {
     /// seconds are ignored). This is to prevent a denial-of-service by
     /// requesting a non-existant key repeatedly. The requester must have
     /// `write` permission on the keyring.
-    pub fn reject(self, keyring: &Keyring, timeout: Duration, error: errno::Errno) -> Result<()> {
+    pub fn reject<'a, T>(self, keyring: T, timeout: Duration, error: errno::Errno) -> Result<()>
+    where
+        T: Into<Option<TargetKeyring<'a>>>,
+    {
         let errno::Errno(errval) = error;
         check_call(unsafe {
             keyctl_reject(
                 self.key.id,
                 timeout.as_secs() as TimeoutSeconds,
                 errval as u32,
-                keyring.id,
+                keyring.into().map(TargetKeyring::serial),
             )
         })
     }
@@ -805,446 +822,16 @@ impl KeyManager {
     /// seconds are ignored). This is to prevent a denial-of-service by
     /// requesting a non-existant key repeatedly. The requester must have
     /// `write` permission on the keyring.
-    pub fn negate(self, keyring: &Keyring, timeout: Duration) -> Result<()> {
+    pub fn negate<'a, T>(self, keyring: T, timeout: Duration) -> Result<()>
+    where
+        T: Into<Option<TargetKeyring<'a>>>,
+    {
         check_call(unsafe {
-            keyctl_negate(self.key.id, timeout.as_secs() as TimeoutSeconds, keyring.id)
-        })
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::sync::atomic;
-    use std::thread;
-
-    use super::*;
-
-    // For testing, each test gets a new keyring attached to the Thread keyring.
-    // This makes sure tests don't interfere with eachother, and keys are not
-    // prematurely garbage collected.
-    fn new_test_keyring() -> Result<Keyring> {
-        let mut thread_keyring = Keyring::attach_or_create(SpecialKeyring::Thread)?;
-
-        static KEYRING_COUNT: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
-        let num = KEYRING_COUNT.fetch_add(1, atomic::Ordering::SeqCst);
-        thread_keyring.add_keyring(format!("test:rust-keyutils{}", num))
-    }
-
-    #[test]
-    fn test_add_key() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:add_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-        assert_eq!(
-            key.read().unwrap(),
-            payload.as_bytes().iter().cloned().collect::<Vec<_>>()
-        );
-
-        // Clean up.
-        keyring.unlink_key(&key).unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_clear_keyring() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 0);
-
-        // Create a key.
-        keyring
-            .add_key::<keytypes::User, _, _>(
-                "test:rust-keyutils:clear_keyring",
-                "payload".as_bytes(),
-            )
-            .unwrap();
-        keyring.add_keyring("description").unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 1);
-        assert_eq!(keyrings.len(), 1);
-
-        // Clear the keyring.
-        keyring.clear().unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 0);
-
-        // Clean up.
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_describe_key() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let desc = "test:rust-keyutils:describe_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(desc, payload.as_bytes())
-            .unwrap();
-
-        // Check its description.
-        let description = key.description().unwrap();
-        assert_eq!(&description.type_, keytypes::User::name());
-        assert_eq!(&description.description, desc);
-
-        // Clean up.
-        keyring.unlink_key(&key).unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_invalidate_key() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:invalidate_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-        key.invalidate().unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 0);
-
-        // Clean up.
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_link_key() {
-        let mut keyring = new_test_keyring().unwrap();
-        let mut new_keyring = keyring.add_keyring("new_keyring").unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:link_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-
-        new_keyring.link_key(&key).unwrap();
-
-        let (keys, keyrings) = new_keyring.read().unwrap();
-        assert_eq!(keys.len(), 1);
-        assert_eq!(keys[0], key);
-        assert_eq!(keyrings.len(), 0);
-
-        // Clean up.
-        key.invalidate().unwrap();
-        new_keyring.invalidate().unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_link_keyring() {
-        let mut keyring = new_test_keyring().unwrap();
-        let mut new_keyring = keyring.add_keyring("new_keyring").unwrap();
-        let new_inner_keyring = keyring.add_keyring("new_inner_keyring").unwrap();
-
-        new_keyring.link_keyring(&new_inner_keyring).unwrap();
-
-        let (keys, keyrings) = new_keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 1);
-        assert_eq!(keyrings[0], new_inner_keyring);
-
-        // Clean up.
-        new_inner_keyring.invalidate().unwrap();
-        new_keyring.invalidate().unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_read_keyring() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 0);
-
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(
-                "test:rust-keyutils:read_keyring",
-                "payload".as_bytes(),
+            keyctl_negate(
+                self.key.id,
+                timeout.as_secs() as TimeoutSeconds,
+                keyring.into().map(TargetKeyring::serial),
             )
-            .unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 1);
-        assert_eq!(keys[0], key);
-        assert_eq!(keyrings.len(), 0);
-
-        // Clean up.
-        keyring.unlink_key(&key).unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_read_key() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:read_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-        assert_eq!(
-            key.read().unwrap(),
-            payload.as_bytes().iter().cloned().collect::<Vec<_>>()
-        );
-
-        // Clean up.
-        keyring.unlink_key(&key).unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_create_keyring() {
-        let mut keyring = new_test_keyring().unwrap();
-        let new_keyring = keyring.add_keyring("new_keyring").unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 1);
-        assert_eq!(keyrings[0], new_keyring);
-
-        // Clean up.
-        new_keyring.invalidate().unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_chmod_keyring() {
-        let mut keyring = new_test_keyring().unwrap();
-        let description = keyring.description().unwrap();
-        let perms = description.perms;
-        let new_perms = {
-            let mut tmp_perms = perms.clone();
-            let write_bits = Permission::POSSESSOR_WRITE
-                | Permission::USER_WRITE
-                | Permission::GROUP_WRITE
-                | Permission::OTHER_WRITE;
-            tmp_perms.remove(write_bits);
-            tmp_perms
-        };
-        keyring.set_permissions(new_perms).unwrap();
-        let err = keyring.add_keyring("new_keyring").unwrap_err();
-        assert_eq!(err.0, libc::EACCES);
-
-        keyring.set_permissions(perms).unwrap();
-        let new_keyring = keyring.add_keyring("new_keyring").unwrap();
-
-        // Clean up.
-        new_keyring.invalidate().unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_request_key() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:request_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-
-        let found_key = keyring
-            .request_key::<keytypes::User, _>(description)
-            .unwrap();
-        assert_eq!(found_key, key);
-
-        // Clean up.
-        keyring.unlink_key(&key).unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_revoke_key() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:revoke_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-        let key_copy = key.clone();
-
-        key.revoke().unwrap();
-
-        let err = key_copy.read().unwrap_err();
-        assert_eq!(err.0, libc::EKEYREVOKED);
-
-        // Clean up.
-        keyring.unlink_key(&key_copy).unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_search_key() {
-        let mut keyring = new_test_keyring().unwrap();
-        let mut new_keyring = keyring.add_keyring("new_keyring").unwrap();
-        let mut new_inner_keyring = new_keyring.add_keyring("new_inner_keyring").unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:search_key";
-        let payload = "payload";
-        let key = new_inner_keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-
-        let found_key = keyring
-            .search_for_key::<keytypes::User, _>(description)
-            .unwrap();
-        assert_eq!(found_key, key);
-
-        // Clean up.
-        new_inner_keyring.unlink_key(&key).unwrap();
-        new_inner_keyring.invalidate().unwrap();
-        new_keyring.invalidate().unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_search_keyring() {
-        let mut keyring = new_test_keyring().unwrap();
-        let mut new_keyring = keyring.add_keyring("new_keyring").unwrap();
-        let new_inner_keyring = new_keyring.add_keyring("new_inner_keyring").unwrap();
-
-        let found_keyring = keyring.search_for_keyring("new_inner_keyring").unwrap();
-        assert_eq!(found_keyring, new_inner_keyring);
-
-        // Clean up.
-        new_inner_keyring.invalidate().unwrap();
-        new_keyring.invalidate().unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_key_timeout() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:key_timeout";
-        let payload = "payload";
-        let mut key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-
-        // Set the timeout on the key.
-        let duration = Duration::from_secs(1);
-        let timeout_duration = duration + duration;
-        key.set_timeout(duration).unwrap();
-
-        // Sleep the timeout away.
-        thread::sleep(timeout_duration);
-
-        // Try to read the key.
-        let err = key.read().unwrap_err();
-        assert_eq!(err.0, libc::EKEYEXPIRED);
-
-        // Clean up.
-        keyring.unlink_key(&key).unwrap();
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_unlink_keyring() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the keyring.
-        let description = "test:rust-keyutils:unlink_keyring";
-        let new_keyring = keyring.add_keyring(description).unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 1);
-
-        // Unlink the key.
-        keyring.unlink_keyring(&new_keyring).unwrap();
-
-        // Use the keyring.
-        let err = new_keyring.read().unwrap_err();
-        assert_eq!(err.0, libc::EACCES);
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 0);
-
-        // Clean up.
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_unlink_key() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:unlink_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 1);
-        assert_eq!(keyrings.len(), 0);
-
-        // Unlink the key.
-        keyring.unlink_key(&key).unwrap();
-
-        // Use the key.
-        let err = key.read().unwrap_err();
-        assert_eq!(err.0, libc::EACCES);
-
-        let (keys, keyrings) = keyring.read().unwrap();
-        assert_eq!(keys.len(), 0);
-        assert_eq!(keyrings.len(), 0);
-
-        // Clean up.
-        keyring.invalidate().unwrap();
-    }
-
-    #[test]
-    fn test_update_key() {
-        let mut keyring = new_test_keyring().unwrap();
-
-        // Create the key.
-        let description = "test:rust-keyutils:update_key";
-        let payload = "payload";
-        let key = keyring
-            .add_key::<keytypes::User, _, _>(description, payload.as_bytes())
-            .unwrap();
-
-        // Update the key.
-        let new_payload = "new_payload";
-        let updated_key = keyring
-            .add_key::<keytypes::User, _, _>(description, new_payload.as_bytes())
-            .unwrap();
-        assert_eq!(key, updated_key);
-        assert_eq!(
-            updated_key.read().unwrap(),
-            new_payload.as_bytes().iter().cloned().collect::<Vec<_>>()
-        );
-
-        // Clean up.
-        keyring.unlink_key(&key).unwrap();
-        keyring.invalidate().unwrap();
+        })
     }
 }
diff --git a/src/constants.rs b/src/constants.rs
index 6b43190..272bbae 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -28,6 +28,7 @@ use bitflags::bitflags;
 use keyutils_raw::*;
 
 /// Special keyrings predefined for a process.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
 pub enum SpecialKeyring {
     /// A thread-specific keyring.
     Thread,
@@ -65,7 +66,7 @@ bitflags! {
     /// keyring, and a third set which is used when neither of the other two match.
     ///
     /// The fourth set is combined with the permission set used above (priority to user, then
-    /// group, finaly other) where either set granting a permission allows it. This set is,
+    /// group, finally other) where either set granting a permission allows it. This set is,
     /// however, only used if the caller is a "possessor" of they key or keyring. Generally,
     /// "possession" requires the `search` permission, association from the calling thread
     /// (the session, process, and thread keyrings), or is linked to from a possessed keyring. See
diff --git a/src/keytypes/encrypted.rs b/src/keytypes/encrypted.rs
index 46234ad..fef2674 100644
--- a/src/keytypes/encrypted.rs
+++ b/src/keytypes/encrypted.rs
@@ -60,6 +60,11 @@ pub enum Format {
     /// Keys of this format must have a description of exactly 16 hexadecimal characters. The
     /// keylength must also be 64.
     Ecryptfs,
+    /// Encrypted keys with a payload size of 32 bytes.
+    ///
+    /// Intended for nvdimm security, but may be used for other 32-byte payload use cases in the
+    /// future.
+    Enc32,
 }
 
 impl Format {
@@ -68,6 +73,7 @@ impl Format {
         match *self {
             Format::Default => "default",
             Format::Ecryptfs => "ecryptfs",
+            Format::Enc32 => "enc32",
         }
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 303ddd1..6641af3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -30,10 +30,6 @@
 
 #![warn(missing_docs)]
 
-#[cfg(test)]
-#[macro_use]
-extern crate lazy_static;
-
 mod api;
 mod constants;
 mod keytype;
diff --git a/src/tests/add.rs b/src/tests/add.rs
index a145f5b..6182a36 100644
--- a/src/tests/add.rs
+++ b/src/tests/add.rs
@@ -27,49 +27,49 @@
 use std::iter;
 
 use crate::keytypes::User;
-use crate::{Keyring, KeyringSerial, SpecialKeyring};
 
+use super::utils;
 use super::utils::kernel::*;
 use super::utils::keys::*;
 
 #[test]
 fn empty_key_type() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Process).unwrap();
+    let mut keyring = utils::new_test_keyring();
     let err = keyring.add_key::<EmptyKey, _, _>("", ()).unwrap_err();
     assert_eq!(err, errno::Errno(libc::EINVAL));
 }
 
 #[test]
 fn unsupported_key_type() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Process).unwrap();
+    let mut keyring = utils::new_test_keyring();
     let err = keyring.add_key::<UnsupportedKey, _, _>("", ()).unwrap_err();
     assert_eq!(err, errno::Errno(libc::ENODEV));
 }
 
 #[test]
 fn invalid_key_type() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Process).unwrap();
+    let mut keyring = utils::new_test_keyring();
     let err = keyring.add_key::<InvalidKey, _, _>("", ()).unwrap_err();
     assert_eq!(err, errno::Errno(libc::EPERM));
 }
 
 #[test]
 fn maxlen_key_type() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Process).unwrap();
+    let mut keyring = utils::new_test_keyring();
     let err = keyring.add_key::<MaxLenKey, _, _>("", ()).unwrap_err();
     assert_eq!(err, errno::Errno(libc::ENODEV));
 }
 
 #[test]
 fn overlong_key_type() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Process).unwrap();
+    let mut keyring = utils::new_test_keyring();
     let err = keyring.add_key::<OverlongKey, _, _>("", ()).unwrap_err();
     assert_eq!(err, errno::Errno(libc::EINVAL));
 }
 
 #[test]
 fn keyring_with_payload() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Process).unwrap();
+    let mut keyring = utils::new_test_keyring();
     let err = keyring
         .add_key::<KeyringShadow, _, _>("", "payload")
         .unwrap_err();
@@ -78,7 +78,7 @@ fn keyring_with_payload() {
 
 #[test]
 fn max_user_description() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Process).unwrap();
+    let mut keyring = utils::new_test_keyring();
     // Subtract one because the NUL is added in the kernel API.
     let maxdesc: String = iter::repeat('a').take(*PAGE_SIZE - 1).collect();
     let res = keyring.add_key::<User, _, _>(maxdesc.as_ref(), "payload".as_bytes());
@@ -94,7 +94,7 @@ fn max_user_description() {
 
 #[test]
 fn overlong_user_description() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Process).unwrap();
+    let mut keyring = utils::new_test_keyring();
     // On MIPS with < 3.19, there is a bug where this is allowed. 3.19 was released in Feb 2015,
     // so this is being ignored here.
     let toolarge: String = iter::repeat('a').take(*PAGE_SIZE).collect();
@@ -106,26 +106,94 @@ fn overlong_user_description() {
 
 #[test]
 fn invalid_keyring() {
-    // Yes, we're explicitly breaking the NonZeroI32 rules here. However, it is not passing
-    // through any bits which care (e.g., `Option`), so this is purely to test that using an
-    // invalid keyring ID gives back `EINVAL` as expected.
-    let mut keyring = unsafe { Keyring::new(KeyringSerial::new_unchecked(0)) };
+    let mut keyring = utils::invalid_keyring();
     let err = keyring
-        .add_key::<User, _, _>("desc", "payload".as_bytes())
+        .add_key::<User, _, _>("invalid_keyring", "payload".as_bytes())
         .unwrap_err();
     assert_eq!(err, errno::Errno(libc::EINVAL));
 }
 
 #[test]
-fn add_key_to_session() {
-    let mut keyring = Keyring::attach_or_create(SpecialKeyring::Session).unwrap();
+fn add_key_to_non_keyring() {
+    let mut keyring = utils::new_test_keyring();
     let expected = "stuff".as_bytes();
-    let mut key = keyring.add_key::<User, _, _>("wibble", expected).unwrap();
-    let payload = key.read().unwrap();
-    assert_eq!(payload, expected);
-    let new_expected = "lizard".as_bytes();
-    key.update(new_expected).unwrap();
-    let new_payload = key.read().unwrap();
-    assert_eq!(new_payload, new_expected);
-    keyring.unlink_key(&key).unwrap();
+    let key = keyring
+        .add_key::<User, _, _>("add_key_to_non_keyring", expected)
+        .unwrap();
+
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+    let err = not_a_keyring
+        .add_key::<User, _, _>("add_key_to_non_keyring", expected)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+}
+
+#[test]
+fn add_keyring_to_non_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let expected = "stuff".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("add_keyring_to_non_keyring", expected)
+        .unwrap();
+
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+    let err = not_a_keyring
+        .add_keyring("add_keyring_to_non_keyring")
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+}
+
+#[test]
+fn add_key() {
+    let mut keyring = utils::new_test_keyring();
+
+    let payload = "payload".as_bytes();
+    let key = keyring.add_key::<User, _, _>("add_key", payload).unwrap();
+    assert_eq!(key.read().unwrap(), payload);
+}
+
+#[test]
+fn add_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let new_keyring = keyring.add_keyring("add_keyring").unwrap();
+
+    let (keys, keyrings) = new_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn add_key_replace() {
+    let mut keyring = utils::new_test_keyring();
+
+    let description = "add_key_replace";
+
+    let payload = "payload".as_bytes();
+    let key = keyring.add_key::<User, _, _>(description, payload).unwrap();
+    assert_eq!(key.read().unwrap(), payload);
+
+    let payload = "updated_payload".as_bytes();
+    let key_updated = keyring.add_key::<User, _, _>(description, payload).unwrap();
+    assert_eq!(key, key_updated);
+    assert_eq!(key.read().unwrap(), payload);
+    assert_eq!(key_updated.read().unwrap(), payload);
+}
+
+#[test]
+fn add_keyring_replace() {
+    let mut keyring = utils::new_test_keyring();
+
+    let description = "add_keyring_replace";
+    let new_keyring = keyring.add_keyring(description).unwrap();
+
+    let (keys, keyrings) = new_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+
+    let updated_keyring = keyring.add_keyring(description).unwrap();
+    assert_ne!(new_keyring, updated_keyring);
+
+    let (keys, keyrings) = updated_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
 }
diff --git a/src/tests/clear.rs b/src/tests/clear.rs
new file mode 100644
index 0000000..c617f01
--- /dev/null
+++ b/src/tests/clear.rs
@@ -0,0 +1,181 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::keytypes::User;
+use crate::Keyring;
+
+use super::utils;
+
+#[test]
+fn invalid_keyring() {
+    let mut keyring = utils::invalid_keyring();
+    let err = keyring.clear().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn clear_non_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let key = keyring
+        .add_key::<User, _, _>("clear_non_keyring", "payload".as_bytes())
+        .unwrap();
+
+    // Try clearing a non-keyring.
+    let mut not_a_keyring = unsafe { Keyring::new(key.serial()) };
+    let err = not_a_keyring.clear().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+
+    keyring.unlink_key(&key).unwrap();
+}
+
+#[test]
+fn clear_deleted_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let mut sub_keyring = keyring.add_keyring("clear_deleted_keyring").unwrap();
+
+    keyring.unlink_keyring(&sub_keyring).unwrap();
+
+    // Keys are deleted asynchronously; permissions are revoked until it is actually deleted.
+    loop {
+        let err = sub_keyring.clear().unwrap_err();
+        if err == errno::Errno(libc::EACCES) {
+            continue;
+        }
+        assert_eq!(err, errno::Errno(libc::ENOKEY));
+        break;
+    }
+}
+
+#[test]
+fn clear_empty_keyring() {
+    let mut keyring = utils::new_test_keyring();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 0);
+
+    // Clear the keyring.
+    keyring.clear().unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 0);
+}
+
+#[test]
+fn clear_keyring_one_key() {
+    let mut keyring = utils::new_test_keyring();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 0);
+
+    let key_desc = "clear_keyring:key";
+
+    // Create a key.
+    let payload = "payload".as_bytes();
+    keyring.add_key::<User, _, _>(key_desc, payload).unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keyrings.len(), 0);
+
+    assert_eq!(keys[0].description().unwrap().description, key_desc);
+
+    // Clear the keyring.
+    keyring.clear().unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 0);
+}
+
+#[test]
+fn clear_keyring_many_keys() {
+    let mut keyring = utils::new_test_keyring();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 0);
+
+    let count = 40;
+    let payload = "payload".as_bytes();
+    let mut descs = Vec::with_capacity(count);
+    for i in 0..count {
+        let key_desc = format!("clear_keyring:key{:02}", i);
+
+        // Create a key.
+        keyring
+            .add_key::<User, _, _>(key_desc.as_ref(), payload)
+            .unwrap();
+        descs.push(key_desc);
+    }
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), count);
+    assert_eq!(keyrings.len(), 0);
+
+    let mut actual_descs = keys
+        .iter()
+        .map(|key| key.description().unwrap().description)
+        .collect::<Vec<_>>();
+    actual_descs.sort();
+    assert_eq!(actual_descs, descs);
+
+    // Clear the keyring.
+    keyring.clear().unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 0);
+}
+
+#[test]
+fn clear_keyring_keyring() {
+    let mut keyring = utils::new_test_keyring();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 0);
+
+    let keyring_desc = "clear_keyring:keyring";
+
+    // Create a key.
+    keyring.add_keyring(keyring_desc).unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 1);
+
+    assert_eq!(keyrings[0].description().unwrap().description, keyring_desc);
+
+    // Clear the keyring.
+    keyring.clear().unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 0);
+}
diff --git a/src/tests/describe.rs b/src/tests/describe.rs
new file mode 100644
index 0000000..ecff11c
--- /dev/null
+++ b/src/tests/describe.rs
@@ -0,0 +1,127 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::keytypes::{Keyring, User};
+use crate::{Key, KeyType, Permission};
+
+use super::utils;
+use super::utils::kernel::*;
+
+#[test]
+fn invalid_key() {
+    let key = utils::invalid_key();
+    let err = key.description().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring() {
+    let keyring = utils::invalid_keyring();
+    let err = keyring.description().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn non_existent_key() {
+    let mut keyring = utils::new_test_keyring();
+    let key = keyring
+        .add_key::<User, _, _>("non_existent_key", "payload".as_bytes())
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+}
+
+#[test]
+fn describe_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let description = "describe_keyring";
+    let keyring = keyring.add_keyring(description).unwrap();
+
+    let perms = Permission::POSSESSOR_ALL | Permission::USER_VIEW;
+
+    let desc = keyring.description().unwrap();
+    assert_eq!(desc.type_, Keyring::name());
+    assert_eq!(desc.uid, *UID);
+    assert_eq!(desc.gid, *GID);
+    assert_eq!(desc.perms, perms);
+    assert_eq!(desc.description, description);
+
+    keyring.invalidate().unwrap()
+}
+
+#[test]
+fn describe_key() {
+    let mut keyring = utils::new_test_keyring();
+    let description = "describe_key";
+    let key = keyring
+        .add_key::<User, _, _>(description, "payload".as_bytes())
+        .unwrap();
+
+    let perms = Permission::POSSESSOR_ALL | Permission::USER_VIEW;
+
+    let desc = key.description().unwrap();
+    assert_eq!(desc.type_, User::name());
+    assert_eq!(desc.uid, *UID);
+    assert_eq!(desc.gid, *GID);
+    assert_eq!(desc.perms, perms);
+    assert_eq!(desc.description, description);
+}
+
+#[test]
+fn describe_key_no_perm() {
+    let mut keyring = utils::new_test_keyring();
+    let description = "describe_key_no_perm";
+    let mut key = keyring
+        .add_key::<User, _, _>(description, "payload".as_bytes())
+        .unwrap();
+
+    let old_perms = key.description().unwrap().perms;
+    let perms = {
+        let mut perms = old_perms;
+        let view_bits = Permission::POSSESSOR_VIEW | Permission::USER_VIEW;
+        perms.remove(view_bits);
+        perms
+    };
+    key.set_permissions(perms).unwrap();
+
+    let err = key.description().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+}
+
+#[test]
+fn describe_revoked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let key = keyring
+        .add_key::<User, _, _>("describe_revoked_key", "payload".as_bytes())
+        .unwrap();
+
+    let key_mirror = unsafe { Key::new(key.serial()) };
+    key.revoke().unwrap();
+
+    let err = key_mirror.description().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+}
diff --git a/src/tests/instantiate.rs b/src/tests/instantiate.rs
new file mode 100644
index 0000000..4c935c0
--- /dev/null
+++ b/src/tests/instantiate.rs
@@ -0,0 +1,212 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use std::time::Duration;
+
+use crate::keytypes::User;
+use crate::KeyManager;
+
+use super::utils;
+
+#[test]
+fn instantiate_invalid_key() {
+    let key = utils::invalid_key();
+    let manager = KeyManager::test_new(key);
+
+    let payload = "payload".as_bytes();
+    let err = manager.instantiate(None, payload).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn reject_invalid_key() {
+    let key = utils::invalid_key();
+    let manager = KeyManager::test_new(key);
+
+    let duration = Duration::from_secs(1);
+    let errno = errno::Errno(libc::EKEYREJECTED);
+    let err = manager.reject(None, duration, errno).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn negate_invalid_key() {
+    let key = utils::invalid_key();
+    let manager = KeyManager::test_new(key);
+
+    let duration = Duration::from_secs(1);
+    let err = manager.negate(None, duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn instantiate_into_not_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("instantiate_into_not_key", payload)
+        .unwrap();
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+    let manager = KeyManager::test_new(key);
+
+    let payload = "payload".as_bytes();
+    let err = manager
+        .instantiate(&mut not_a_keyring, payload)
+        .unwrap_err();
+    // Should be ENOTDIR, but the kernel doesn't have an authorization key for us to use.
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn reject_into_not_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("reject_into_not_key", payload)
+        .unwrap();
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+    let manager = KeyManager::test_new(key);
+
+    let duration = Duration::from_secs(1);
+    let errno = errno::Errno(libc::EKEYREJECTED);
+    let err = manager
+        .reject(&mut not_a_keyring, duration, errno)
+        .unwrap_err();
+    // Should be ENOTDIR, but the kernel doesn't have an authorization key for us to use.
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn negate_into_not_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("negate_into_not_key", payload)
+        .unwrap();
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+    let manager = KeyManager::test_new(key);
+
+    let duration = Duration::from_secs(1);
+    let err = manager.negate(&mut not_a_keyring, duration).unwrap_err();
+    // Should be ENOTDIR, but the kernel doesn't have an authorization key for us to use.
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn instantiate_already_instantiated() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("instantiate_already_instantiated", payload)
+        .unwrap();
+    let manager = KeyManager::test_new(key);
+
+    let err = manager.instantiate(None, payload).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn reject_already_instantiated() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("reject_already_instantiated", payload)
+        .unwrap();
+    let manager = KeyManager::test_new(key);
+
+    let duration = Duration::from_secs(1);
+    let errno = errno::Errno(libc::EKEYREJECTED);
+    let err = manager.reject(None, duration, errno).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn negate_already_instantiated() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("negate_already_instantiated", payload)
+        .unwrap();
+    let manager = KeyManager::test_new(key);
+
+    let duration = Duration::from_secs(1);
+    let err = manager.negate(None, duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn instantiate_unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("instantiate_unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let manager = KeyManager::test_new(key);
+
+    let err = manager.instantiate(None, payload).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn reject_unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("reject_unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let manager = KeyManager::test_new(key);
+
+    let duration = Duration::from_secs(1);
+    let errno = errno::Errno(libc::EKEYREJECTED);
+    let err = manager.reject(None, duration, errno).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn negate_unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("negate_unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let manager = KeyManager::test_new(key);
+
+    let duration = Duration::from_secs(1);
+    let err = manager.negate(None, duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
diff --git a/src/tests/invalidate.rs b/src/tests/invalidate.rs
new file mode 100644
index 0000000..e8b351b
--- /dev/null
+++ b/src/tests/invalidate.rs
@@ -0,0 +1,124 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::keytypes::User;
+
+use super::utils;
+use super::utils::kernel::*;
+
+#[test]
+fn have_invalidate() {
+    let can_test = *HAVE_INVALIDATE;
+    if !can_test {
+        eprintln!(
+            "This kernel does not support key invalidation. Please ignore test failures in \
+             this test failure."
+        );
+    }
+}
+
+#[test]
+fn invalid_key() {
+    let key = utils::invalid_key();
+    let err = key.invalidate().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring() {
+    let keyring = utils::invalid_keyring();
+    let err = keyring.invalidate().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let err = key.invalidate().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn invalidate_key() {
+    let mut keyring = utils::new_test_keyring();
+
+    {
+        let (keys, keyrings) = keyring.read().unwrap();
+        assert!(keys.is_empty());
+        assert!(keyrings.is_empty());
+    }
+
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("invalidate_key", payload)
+        .unwrap();
+    let key_observer = key.clone();
+
+    key.invalidate().unwrap();
+    utils::wait_for_key_gc(&key_observer);
+
+    {
+        let (keys, keyrings) = keyring.read().unwrap();
+        assert!(keys.is_empty());
+        assert!(keyrings.is_empty());
+    }
+}
+
+#[test]
+fn invalidate_keyring() {
+    let mut keyring = utils::new_test_keyring_manual();
+
+    {
+        let (keys, keyrings) = keyring.read().unwrap();
+        assert!(keys.is_empty());
+        assert!(keyrings.is_empty());
+    }
+
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("invalidate_keyring", payload)
+        .unwrap();
+    let keyring_observer = keyring.clone();
+
+    keyring.invalidate().unwrap();
+    utils::wait_for_keyring_gc(&keyring_observer);
+
+    let err = keyring_observer.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+
+    utils::wait_for_key_gc(&key);
+
+    let err = key.description().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
diff --git a/src/tests/link.rs b/src/tests/link.rs
new file mode 100644
index 0000000..c8c6e07
--- /dev/null
+++ b/src/tests/link.rs
@@ -0,0 +1,299 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::keytypes::User;
+use crate::Permission;
+
+use super::utils;
+
+#[test]
+fn invalid_target() {
+    let mut invalid_keyring = utils::invalid_keyring();
+    let keyring = utils::new_test_keyring();
+
+    let err = invalid_keyring.link_keyring(&keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_source() {
+    let invalid_keyring = utils::invalid_keyring();
+    let mut keyring = utils::new_test_keyring();
+
+    let err = keyring.link_keyring(&invalid_keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn link_to_non_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("link_to_non_keyring", payload)
+        .unwrap();
+    let linked_key = keyring
+        .add_key::<User, _, _>("link_to_non_keyring_linked", payload)
+        .unwrap();
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+
+    let err = not_a_keyring.link_key(&linked_key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+}
+
+#[test]
+fn link_unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("link_unlinked_key", payload)
+        .unwrap();
+    let mut target_keyring = keyring.add_keyring("link_unlinked_key_target").unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let err = target_keyring.link_key(&key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn link_into_unlinked_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("link_into_unlinked_keyring", payload)
+        .unwrap();
+    let mut target_keyring = keyring
+        .add_keyring("link_into_unlinked_keyring_target")
+        .unwrap();
+
+    keyring.unlink_keyring(&target_keyring).unwrap();
+    utils::wait_for_keyring_gc(&target_keyring);
+
+    let err = target_keyring.link_key(&key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn link_self() {
+    let mut keyring = utils::new_test_keyring();
+    let keyring_observer = keyring.clone();
+
+    let err = keyring.link_keyring(&keyring_observer).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EDEADLK));
+}
+
+#[test]
+fn link_self_via_child() {
+    let mut keyring = utils::new_test_keyring();
+    let mut target_keyring = keyring.add_keyring("link_self_via_child").unwrap();
+
+    let err = target_keyring.link_keyring(&keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EDEADLK));
+}
+
+#[test]
+fn link_self_via_child_chains() {
+    let mut keyring = utils::new_test_keyring();
+    let mut target_keyring = keyring.clone();
+    let perms = Permission::POSSESSOR_ALL | Permission::USER_ALL;
+    target_keyring.set_permissions(perms).unwrap();
+
+    let maxdepth = 8;
+    for depth in 1..maxdepth {
+        let mut new_keyring = keyring
+            .add_keyring(format!("link_self_via_child_chains{}", depth))
+            .unwrap();
+        new_keyring.set_permissions(perms).unwrap();
+
+        target_keyring.link_keyring(&new_keyring).unwrap();
+        target_keyring = new_keyring;
+
+        let err = target_keyring.link_keyring(&keyring).unwrap_err();
+        assert_eq!(err, errno::Errno(libc::EDEADLK));
+    }
+
+    let mut new_keyring = keyring
+        .add_keyring(format!("link_self_via_child_chains{}", maxdepth))
+        .unwrap();
+    new_keyring.set_permissions(perms).unwrap();
+
+    target_keyring.link_keyring(&new_keyring).unwrap();
+    keyring.unlink_keyring(&new_keyring).unwrap();
+    target_keyring = new_keyring;
+
+    let err = target_keyring.link_keyring(&keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ELOOP));
+}
+
+#[test]
+fn link_self_via_keyring_stacks() {
+    let mut keyring = utils::new_test_keyring();
+    let keyring_a_root = keyring
+        .add_keyring("link_self_via_keyring_stacks_a")
+        .unwrap();
+    let keyring_b_root = keyring
+        .add_keyring("link_self_via_keyring_stacks_b")
+        .unwrap();
+    let mut keyring_a = keyring_a_root.clone();
+    let mut keyring_b = keyring_b_root.clone();
+
+    let maxdepth = 4;
+    for depth in 1..maxdepth {
+        keyring_a = keyring_a
+            .add_keyring(format!("link_self_via_keyring_stacks_a{}", depth))
+            .unwrap();
+        keyring_b = keyring_b
+            .add_keyring(format!("link_self_via_keyring_stacks_b{}", depth))
+            .unwrap();
+    }
+
+    keyring_b.link_keyring(&keyring_a_root).unwrap();
+
+    let err = keyring_a.link_keyring(&keyring_b_root).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EDEADLK));
+
+    keyring_b.unlink_keyring(&keyring_a_root).unwrap();
+
+    keyring_a.link_keyring(&keyring_b_root).unwrap();
+
+    let err = keyring_b.link_keyring(&keyring_a_root).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EDEADLK));
+}
+
+#[test]
+fn link_self_via_keyring_deep_stacks() {
+    let mut keyring = utils::new_test_keyring();
+    let keyring_a_root = keyring
+        .add_keyring("link_self_via_keyring_deep_stacks_a")
+        .unwrap();
+    let keyring_b_root = keyring
+        .add_keyring("link_self_via_keyring_deep_stacks_b")
+        .unwrap();
+    let mut keyring_a = keyring_a_root.clone();
+    let mut keyring_b = keyring_b_root.clone();
+
+    let maxdepth = 5;
+    for depth in 1..maxdepth {
+        keyring_a = keyring_a
+            .add_keyring(format!("link_self_via_keyring_deep_stacks_a{}", depth))
+            .unwrap();
+        keyring_b = keyring_b
+            .add_keyring(format!("link_self_via_keyring_deep_stacks_b{}", depth))
+            .unwrap();
+    }
+
+    keyring_b.link_keyring(&keyring_a_root).unwrap();
+
+    let err = keyring_a.link_keyring(&keyring_b_root).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ELOOP));
+
+    keyring_b.unlink_keyring(&keyring_a_root).unwrap();
+
+    keyring_a.link_keyring(&keyring_b_root).unwrap();
+
+    let err = keyring_b.link_keyring(&keyring_a_root).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ELOOP));
+}
+
+#[test]
+fn multiply_link_key_into_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("multiply_link_key_into_keyring")
+        .unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], new_keyring);
+
+    let payload = "payload".as_bytes();
+    let key = new_keyring
+        .add_key::<User, _, _>("multiply_link_key_into_keyring_key", payload)
+        .unwrap();
+
+    let (keys, keyrings) = new_keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], key);
+    assert!(keyrings.is_empty());
+
+    keyring.link_key(&key).unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], key);
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], new_keyring);
+
+    // Linking the same key should not change the result.
+    keyring.link_key(&key).unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], key);
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], new_keyring);
+}
+
+#[test]
+fn multiply_link_keyring_into_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("multiply_link_keyring_into_keyring")
+        .unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], new_keyring);
+
+    let inner_keyring = new_keyring
+        .add_keyring("multiply_link_keyring_into_keyring_keyring_inner")
+        .unwrap();
+
+    let (keys, keyrings) = new_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], inner_keyring);
+
+    keyring.link_keyring(&inner_keyring).unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 2);
+    assert_eq!(keyrings[0], new_keyring);
+    assert_eq!(keyrings[1], inner_keyring);
+
+    // Linking the same keyring should not change the result.
+    keyring.link_keyring(&inner_keyring).unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 2);
+    assert_eq!(keyrings[0], new_keyring);
+    assert_eq!(keyrings[1], inner_keyring);
+}
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index ad7f5bb..3e93f17 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -26,6 +26,21 @@
 
 //! The test structure here comes from the structure in libkeyutils.
 
-mod utils;
+pub(crate) mod utils;
 
 mod add;
+mod clear;
+mod describe;
+mod instantiate;
+mod invalidate;
+mod link;
+mod newring;
+mod permitting;
+mod reading;
+mod revoke;
+mod search;
+// FIXME(#39): These tests fail when run in the same process. Something should be done about this.
+// mod session;
+mod timeout;
+mod unlink;
+mod update;
diff --git a/src/tests/newring.rs b/src/tests/newring.rs
new file mode 100644
index 0000000..6df9669
--- /dev/null
+++ b/src/tests/newring.rs
@@ -0,0 +1,132 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use std::iter;
+
+use crate::keytypes::User;
+
+use super::utils;
+use super::utils::kernel::*;
+
+#[test]
+fn invalid_keyring() {
+    let mut keyring = utils::invalid_keyring();
+    let err = keyring.add_keyring("invalid_keyring").unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn unlinked_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let mut unlinked_keyring = keyring.add_keyring("unlinked_keyring_unlinked").unwrap();
+
+    keyring.unlink_keyring(&unlinked_keyring).unwrap();
+    utils::wait_for_keyring_gc(&unlinked_keyring);
+
+    let err = unlinked_keyring
+        .add_keyring("unlinked_keyring")
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn not_a_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("not_a_keyring_key", payload)
+        .unwrap();
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+
+    let err = not_a_keyring.add_keyring("not_a_keyring").unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+}
+
+#[test]
+fn empty_keyring_description() {
+    let mut keyring = utils::new_test_keyring();
+    let err = keyring.add_keyring("").unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn max_keyring_description() {
+    let mut keyring = utils::new_test_keyring();
+    // Subtract one because the NUL is added in the kernel API.
+    let maxdesc: String = iter::repeat('a').take(*PAGE_SIZE - 1).collect();
+    let res = keyring.add_keyring(maxdesc.as_ref());
+    // If the user's quota is smaller than this, it's an error.
+    if KEY_INFO.maxbytes < *PAGE_SIZE {
+        assert_eq!(res.unwrap_err(), errno::Errno(libc::EDQUOT));
+    } else {
+        let keyring = res.unwrap();
+        assert_eq!(keyring.description().unwrap().description, maxdesc);
+        keyring.invalidate().unwrap();
+    }
+}
+
+#[test]
+fn overlong_keyring_description() {
+    let mut keyring = utils::new_test_keyring();
+    // On MIPS with < 3.19, there is a bug where this is allowed. 3.19 was released in Feb 2015,
+    // so this is being ignored here.
+    let maxdesc: String = iter::repeat('a').take(*PAGE_SIZE).collect();
+    let err = keyring.add_keyring(maxdesc.as_ref()).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn new_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let new_keyring = keyring.add_keyring("new_keyring").unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(keys.len(), 0);
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], new_keyring);
+}
+
+#[test]
+fn duplicate_keyring_names() {
+    let mut keyring = utils::new_test_keyring();
+    let new_keyring1 = keyring.add_keyring("duplicate_keyring_names").unwrap();
+    let new_keyring2 = keyring.add_keyring("duplicate_keyring_names").unwrap();
+
+    // The keyring should have been displaced.
+    assert_ne!(new_keyring1, new_keyring2);
+
+    // The original keyring should not be in the parent keyring.
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(1, keyrings.len());
+    assert_eq!(new_keyring2, keyrings[0]);
+
+    utils::wait_for_keyring_gc(&new_keyring1);
+
+    // It should be inaccessible.
+    let err = new_keyring1.description().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
diff --git a/src/tests/permitting.rs b/src/tests/permitting.rs
new file mode 100644
index 0000000..a081b9f
--- /dev/null
+++ b/src/tests/permitting.rs
@@ -0,0 +1,270 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::keytypes::User;
+use crate::{KeyPermissions, Permission};
+
+use super::utils;
+use super::utils::kernel::*;
+
+#[test]
+fn invalid_key_chown() {
+    let mut key = utils::invalid_key();
+    let err = key.chown(*UID).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_key_chgrp() {
+    let mut key = utils::invalid_key();
+    let err = key.chgrp(*GID).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_key_chmod() {
+    let mut key = utils::invalid_key();
+    let err = key.set_permissions(Permission::POSSESSOR_VIEW).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring_chown() {
+    let mut keyring = utils::invalid_key();
+    let err = keyring.chown(*UID).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring_chgrp() {
+    let mut keyring = utils::invalid_key();
+    let err = keyring.chgrp(*GID).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring_chmod() {
+    let mut keyring = utils::invalid_keyring();
+    let err = keyring.set_permissions(Permission::empty()).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_key_permissions() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("invalid_key_permissions", payload)
+        .unwrap();
+
+    let err = key
+        .set_permissions_raw(KeyPermissions::max_value())
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring_permissions() {
+    let mut keyring = utils::new_test_keyring();
+
+    let err = keyring
+        .set_permissions_raw(KeyPermissions::max_value())
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn unlinked_key_chown() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("unlinked_key_chown", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let err = key.chown(*UID).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn unlinked_key_chgrp() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("unlinked_key_chgrp", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let err = key.chgrp(*GID).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn unlinked_key_chmod() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("unlinked_key_chmod", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let err = key.set_permissions(Permission::POSSESSOR_VIEW).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn chown_keyring() {
+    let mut keyring = utils::new_test_keyring();
+
+    if *UID == 0 {
+        match keyring.chown(1) {
+            // If that worked, make sure we can move it back.
+            Ok(_) => keyring.chown(0).unwrap(),
+            // Otherwise, we got the right error.
+            Err(err) => assert_eq!(err, errno::Errno(libc::EACCES)),
+        }
+    } else {
+        let err = keyring.chown(1).unwrap_err();
+        assert_eq!(err, errno::Errno(libc::EACCES));
+    }
+}
+
+#[test]
+fn chown_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring.add_key::<User, _, _>("chown_key", payload).unwrap();
+
+    if *UID == 0 {
+        match key.chown(1) {
+            // If that worked, make sure we can move it back.
+            Ok(_) => key.chown(0).unwrap(),
+            // Otherwise, we got the right error.
+            Err(err) => assert_eq!(err, errno::Errno(libc::EACCES)),
+        }
+        let err = key.chown(1).unwrap_err();
+        assert_eq!(err, errno::Errno(libc::EACCES));
+    }
+}
+
+#[test]
+fn set_each_permission_bit() {
+    let permission_bits = [
+        Permission::OTHER_VIEW,
+        Permission::OTHER_READ,
+        Permission::OTHER_WRITE,
+        Permission::OTHER_SEARCH,
+        Permission::OTHER_LINK,
+        Permission::OTHER_SET_ATTRIBUTE,
+        Permission::GROUP_VIEW,
+        Permission::GROUP_READ,
+        Permission::GROUP_WRITE,
+        Permission::GROUP_SEARCH,
+        Permission::GROUP_LINK,
+        Permission::GROUP_SET_ATTRIBUTE,
+        Permission::USER_VIEW,
+        Permission::USER_READ,
+        Permission::USER_WRITE,
+        Permission::USER_SEARCH,
+        Permission::USER_LINK,
+        Permission::USER_SET_ATTRIBUTE,
+        Permission::POSSESSOR_VIEW,
+        Permission::POSSESSOR_READ,
+        Permission::POSSESSOR_WRITE,
+        Permission::POSSESSOR_SEARCH,
+        Permission::POSSESSOR_LINK,
+        Permission::POSSESSOR_SET_ATTRIBUTE,
+    ];
+    let required_permissions = Permission::USER_SET_ATTRIBUTE | Permission::USER_VIEW;
+
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("set_each_permission_bit", payload)
+        .unwrap();
+
+    for permission_bit in &permission_bits {
+        let perms = required_permissions | *permission_bit;
+        key.set_permissions(perms).unwrap();
+        let description = key.description().unwrap();
+        assert_eq!(perms, description.perms);
+    }
+}
+
+#[test]
+fn cannot_view_via_group() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("cannot_view_via_group", payload)
+        .unwrap();
+
+    let perms = Permission::GROUP_ALL | Permission::USER_SET_ATTRIBUTE;
+    key.set_permissions(perms).unwrap();
+
+    let err = key.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+}
+
+#[test]
+fn cannot_view_via_other() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("cannot_view_via_other", payload)
+        .unwrap();
+
+    let perms = Permission::OTHER_ALL | Permission::USER_SET_ATTRIBUTE;
+    key.set_permissions(perms).unwrap();
+
+    let err = key.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+}
+
+#[test]
+fn remove_setattr() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("remove_setattr", payload)
+        .unwrap();
+
+    let perms = Permission::all()
+        - (Permission::POSSESSOR_SET_ATTRIBUTE
+            | Permission::USER_SET_ATTRIBUTE
+            | Permission::GROUP_SET_ATTRIBUTE
+            | Permission::OTHER_SET_ATTRIBUTE);
+    key.set_permissions(perms).unwrap();
+
+    let err = key.set_permissions(Permission::all()).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+}
diff --git a/src/tests/reading.rs b/src/tests/reading.rs
new file mode 100644
index 0000000..cb308f6
--- /dev/null
+++ b/src/tests/reading.rs
@@ -0,0 +1,189 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::keytypes::User;
+use crate::Permission;
+
+use super::utils;
+
+#[test]
+fn invalid_key() {
+    let key = utils::invalid_key();
+    let err = key.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn invalid_keyring() {
+    let keyring = utils::invalid_keyring();
+    let err = keyring.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let err = key.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn unlinked_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let new_keyring = keyring.add_keyring("unlinked_keyring").unwrap();
+
+    keyring.unlink_keyring(&new_keyring).unwrap();
+    utils::wait_for_keyring_gc(&new_keyring);
+
+    let err = new_keyring.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn read_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring.add_key::<User, _, _>("read_key", payload).unwrap();
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+}
+
+#[test]
+fn read_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("read_keyring", payload)
+        .unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert_eq!(1, keys.len());
+    assert_eq!(key, keys[0]);
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn read_key_as_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("read_key_as_keyring", payload)
+        .unwrap();
+    let not_a_keyring = utils::key_as_keyring(&key);
+
+    let err = not_a_keyring.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+}
+
+#[test]
+fn read_keyring_as_key() {
+    let keyring = utils::new_test_keyring();
+    let not_a_key = utils::keyring_as_key(&keyring);
+
+    let payload = not_a_key.read().unwrap();
+    assert_eq!(b"", payload.as_slice());
+}
+
+#[test]
+fn read_no_read_perm_with_search() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("read_no_read_perm_with_search", payload)
+        .unwrap();
+
+    // Remove the "read" permission from the key.
+    let no_read_search_perms = Permission::USER_ALL - Permission::USER_READ;
+    key.set_permissions(no_read_search_perms).unwrap();
+
+    // This should still work because we have "search" permission on its keyring.
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+}
+
+#[test]
+fn read_no_read_search_perm_with_search() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("read_no_read_search_perm_with_search", payload)
+        .unwrap();
+
+    // Remove the "read" and "search" permissions from the key.
+    let no_read_perms = Permission::USER_ALL - Permission::USER_READ - Permission::USER_SEARCH;
+    key.set_permissions(no_read_perms).unwrap();
+
+    let err = key.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+}
+
+#[test]
+fn read_rely_on_possessor() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("read_rely_on_possessor", payload)
+        .unwrap();
+
+    // Remove the "read" and "search" permissions from the key.
+    let no_read_perms = Permission::POSSESSOR_ALL - Permission::POSSESSOR_READ;
+    key.set_permissions(no_read_perms).unwrap();
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+}
+
+#[test]
+fn reinstated_read_perm() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("reinstated_read_perm", payload)
+        .unwrap();
+
+    // Remove the "read" and "search" permissions from the key.
+    let no_read_perms = Permission::USER_ALL - Permission::USER_READ - Permission::USER_SEARCH;
+    key.set_permissions(no_read_perms).unwrap();
+
+    let err = key.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+
+    // Reinstate "read" permissions.
+    let no_read_perms = Permission::USER_ALL - Permission::USER_SEARCH;
+    key.set_permissions(no_read_perms).unwrap();
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+}
diff --git a/src/tests/revoke.rs b/src/tests/revoke.rs
new file mode 100644
index 0000000..0005894
--- /dev/null
+++ b/src/tests/revoke.rs
@@ -0,0 +1,107 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use std::time::Duration;
+
+use crate::keytypes::User;
+
+use super::utils;
+
+#[test]
+fn invalid_key() {
+    let key = utils::invalid_key();
+    let err = key.revoke().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring() {
+    let keyring = utils::invalid_keyring();
+    let err = keyring.revoke().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let err = key.revoke().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn revoked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("revoked_key", payload)
+        .unwrap();
+    let mut key_observer = key.clone();
+
+    key.revoke().unwrap();
+
+    let err = key_observer.description().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+
+    let err = key_observer.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+
+    let duration = Duration::from_secs(1);
+    let err = key_observer.set_timeout(duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+
+    let err = key_observer.invalidate().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+}
+
+#[test]
+fn revoked_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let new_keyring = keyring.add_keyring("revoked_keyring").unwrap();
+    let mut keyring_observer = new_keyring.clone();
+
+    new_keyring.revoke().unwrap();
+
+    let err = keyring_observer.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+
+    let err = keyring_observer.description().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+
+    let duration = Duration::from_secs(1);
+    let err = keyring_observer.set_timeout(duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+
+    let err = keyring_observer.invalidate().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYREVOKED));
+}
diff --git a/src/tests/search.rs b/src/tests/search.rs
new file mode 100644
index 0000000..fa5c110
--- /dev/null
+++ b/src/tests/search.rs
@@ -0,0 +1,703 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use std::iter;
+
+use crate::keytypes::User;
+use crate::Permission;
+
+use super::utils;
+use super::utils::kernel::*;
+use super::utils::keys::*;
+
+#[test]
+fn empty_key_type() {
+    let keyring = utils::new_test_keyring();
+
+    let err = keyring
+        .search_for_key::<EmptyKey, _, _>("empty_key_type", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn unsupported_key_type() {
+    let keyring = utils::new_test_keyring();
+
+    let err = keyring
+        .search_for_key::<UnsupportedKey, _, _>("unsupported_key_type", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn invalid_key_type() {
+    let keyring = utils::new_test_keyring();
+
+    let err = keyring
+        .search_for_key::<InvalidKey, _, _>("invalid_key_type", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EPERM));
+}
+
+#[test]
+fn max_key_type() {
+    let keyring = utils::new_test_keyring();
+
+    let err = keyring
+        .search_for_key::<MaxLenKey, _, _>("invalid_key_type", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn overlong_key_type() {
+    let keyring = utils::new_test_keyring();
+
+    let err = keyring
+        .search_for_key::<OverlongKey, _, _>("overlong_key_type", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn max_user_description() {
+    let keyring = utils::new_test_keyring();
+
+    // Subtract one because the NUL is added in the kernel API.
+    let maxdesc: String = iter::repeat('a').take(*PAGE_SIZE - 1).collect();
+    let err = keyring
+        .search_for_key::<User, _, _>(maxdesc, None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn overlong_user_description() {
+    let keyring = utils::new_test_keyring();
+
+    // On MIPS with < 3.19, there is a bug where this is allowed. 3.19 was released in Feb 2015,
+    // so this is being ignored here.
+    let maxdesc: String = iter::repeat('a').take(*PAGE_SIZE).collect();
+    let err = keyring
+        .search_for_key::<User, _, _>(maxdesc, None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring() {
+    let keyring = utils::invalid_keyring();
+
+    let err = keyring
+        .search_for_key::<User, _, _>("invalid_keyring", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn search_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("search_key", payload)
+        .unwrap();
+    let not_a_keyring = utils::key_as_keyring(&key);
+
+    let err = not_a_keyring
+        .search_for_key::<User, _, _>("search_key", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+}
+
+#[test]
+fn search_key_no_result() {
+    let keyring = utils::new_test_keyring();
+
+    let err = keyring
+        .search_for_key::<User, _, _>("search_key_no_result", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn search_keyring_no_result() {
+    let keyring = utils::new_test_keyring();
+
+    let err = keyring
+        .search_for_keyring("search_keyring_no_result", None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn search_key_mismatched_type() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring.add_keyring("search_key_mismatched_type").unwrap();
+    let description = "search_key_mismatched_type_keyring";
+    let _ = new_keyring.add_keyring(description).unwrap();
+
+    let err = keyring
+        .search_for_key::<User, _, _>(description, None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn search_keyring_mismatched_type() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_keyring_mismatched_type")
+        .unwrap();
+    let description = "search_keyring_mismatched_type_key";
+    let payload = "payload".as_bytes();
+    let _ = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let err = keyring.search_for_keyring(description, None).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn search_and_find_key() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring.add_keyring("search_and_find_key").unwrap();
+    let description = "search_and_find_key_key";
+    let payload = "payload".as_bytes();
+    let key = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let found_key = keyring
+        .search_for_key::<User, _, _>(description, None)
+        .unwrap();
+    assert_eq!(found_key, key);
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+}
+
+#[test]
+fn search_and_find_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring.add_keyring("search_and_find_keyring").unwrap();
+    let description = "search_and_find_keyring_keyring";
+    let target_keyring = new_keyring.add_keyring(description).unwrap();
+
+    let found_keyring = keyring.search_for_keyring(description, None).unwrap();
+    assert_eq!(found_keyring, target_keyring);
+}
+
+#[test]
+fn search_and_find_key_no_search_perm_interm() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_key_no_search_perm_interm")
+        .unwrap();
+    let description = "search_and_find_key_no_search_perm_interm_key";
+    let payload = "payload".as_bytes();
+    let _ = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let perms = {
+        let mut orig_perms = new_keyring.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_SEARCH);
+        orig_perms.remove(Permission::USER_SEARCH);
+        orig_perms.remove(Permission::GROUP_SEARCH);
+        orig_perms.remove(Permission::OTHER_SEARCH);
+        orig_perms
+    };
+    new_keyring.set_permissions(perms).unwrap();
+
+    let err = keyring
+        .search_for_key::<User, _, _>(description, None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn search_and_find_keyring_no_search_perm_interm() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_keyring_no_search_perm_interm")
+        .unwrap();
+    let description = "search_and_find_keyring_no_search_perm_interm_keyring";
+    let _ = new_keyring.add_keyring(description).unwrap();
+
+    let perms = {
+        let mut orig_perms = new_keyring.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_SEARCH);
+        orig_perms.remove(Permission::USER_SEARCH);
+        orig_perms.remove(Permission::GROUP_SEARCH);
+        orig_perms.remove(Permission::OTHER_SEARCH);
+        orig_perms
+    };
+    new_keyring.set_permissions(perms).unwrap();
+
+    let err = keyring.search_for_keyring(description, None).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn search_and_find_key_no_search_perm_direct() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_key_no_search_perm_direct")
+        .unwrap();
+    let description = "search_and_find_key_no_search_perm_direct_key";
+    let payload = "payload".as_bytes();
+    let mut key = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let perms = {
+        let mut orig_perms = key.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_SEARCH);
+        orig_perms.remove(Permission::USER_SEARCH);
+        orig_perms.remove(Permission::GROUP_SEARCH);
+        orig_perms.remove(Permission::OTHER_SEARCH);
+        orig_perms
+    };
+    key.set_permissions(perms).unwrap();
+
+    let err = keyring
+        .search_for_key::<User, _, _>(description, None)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+}
+
+#[test]
+fn search_and_find_keyring_no_search_perm_direct() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_keyring_no_search_perm_direct")
+        .unwrap();
+    let description = "search_and_find_keyring_no_search_perm_direct_keyring";
+    let mut target_keyring = new_keyring.add_keyring(description).unwrap();
+
+    let perms = {
+        let mut orig_perms = target_keyring.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_SEARCH);
+        orig_perms.remove(Permission::USER_SEARCH);
+        orig_perms.remove(Permission::GROUP_SEARCH);
+        orig_perms.remove(Permission::OTHER_SEARCH);
+        orig_perms
+    };
+    target_keyring.set_permissions(perms).unwrap();
+
+    let err = keyring.search_for_keyring(description, None).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+}
+
+#[test]
+fn search_and_find_key_link() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring.add_keyring("search_and_find_key_link").unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_key_link_destination")
+        .unwrap();
+    let description = "search_and_find_key_link_key";
+    let payload = "payload".as_bytes();
+    let key = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+
+    let found_key = keyring
+        .search_for_key::<User, _, _>(description, &mut destination_keyring)
+        .unwrap();
+    assert_eq!(found_key, key);
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], key);
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn search_and_find_keyring_link() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring.add_keyring("search_and_find_keyring_link").unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_keyring_link_destination")
+        .unwrap();
+    let description = "search_and_find_keyring_link_keyring";
+    let target_keyring = new_keyring.add_keyring(description).unwrap();
+
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+
+    let found_keyring = keyring
+        .search_for_keyring(description, &mut destination_keyring)
+        .unwrap();
+    assert_eq!(found_keyring, target_keyring);
+
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], target_keyring);
+}
+
+#[test]
+fn search_and_find_key_link_replace() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_key_link_replace")
+        .unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_key_link_replace_destination")
+        .unwrap();
+    let description = "search_and_find_key_link_replace_key";
+    let payload = "payload".as_bytes();
+    let key = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+    let other_payload = "payload".as_bytes();
+    let orig_key = destination_keyring
+        .add_key::<User, _, _>(description, other_payload)
+        .unwrap();
+
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], orig_key);
+    assert!(keyrings.is_empty());
+
+    let found_key = keyring
+        .search_for_key::<User, _, _>(description, &mut destination_keyring)
+        .unwrap();
+    assert_eq!(found_key, key);
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+
+    // The original key should have been replaced.
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], key);
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn search_and_find_key_link_replace_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_key_link_replace_keyring")
+        .unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_key_link_replace_keyring_destination")
+        .unwrap();
+    let description = "search_and_find_key_link_replace_keyring_key";
+    let payload = "payload".as_bytes();
+    let key = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+    let orig_keyring = destination_keyring.add_keyring(description).unwrap();
+
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], orig_keyring);
+
+    let found_key = keyring
+        .search_for_key::<User, _, _>(description, &mut destination_keyring)
+        .unwrap();
+    assert_eq!(found_key, key);
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+
+    // The original keyring should not have been replaced.
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], key);
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], orig_keyring);
+}
+
+#[test]
+fn search_and_find_keyring_link_replace() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_keyring_link_replace")
+        .unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_keyring_link_replace_destination")
+        .unwrap();
+    let description = "search_and_find_keyring_link_replace_keyring";
+    let target_keyring = new_keyring.add_keyring(description).unwrap();
+    let orig_keyring = destination_keyring.add_keyring(description).unwrap();
+
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], orig_keyring);
+
+    let found_keyring = keyring
+        .search_for_keyring(description, &mut destination_keyring)
+        .unwrap();
+    assert_eq!(found_keyring, target_keyring);
+
+    // The original keyring should have been replaced.
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], target_keyring);
+}
+
+#[test]
+fn search_and_find_keyring_link_replace_key() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_keyring_link_replace_key")
+        .unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_keyring_link_replace_key_destination")
+        .unwrap();
+    let description = "search_and_find_keyring_link_replace_key_keyring";
+    let target_keyring = new_keyring.add_keyring(description).unwrap();
+    let payload = "payload".as_bytes();
+    let orig_key = destination_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], orig_key);
+    assert!(keyrings.is_empty());
+
+    let found_keyring = keyring
+        .search_for_keyring(description, &mut destination_keyring)
+        .unwrap();
+    assert_eq!(found_keyring, target_keyring);
+
+    // The original keyring should not have been replaced.
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert_eq!(keys.len(), 1);
+    assert_eq!(keys[0], orig_key);
+    assert_eq!(keyrings.len(), 1);
+    assert_eq!(keyrings[0], target_keyring);
+}
+
+#[test]
+fn search_and_find_key_no_link_perm_no_dest() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_key_no_link_perm_no_dest")
+        .unwrap();
+    let description = "search_and_find_key_no_link_perm_no_dest_key";
+    let payload = "payload".as_bytes();
+    let mut key = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let perms = {
+        let mut orig_perms = key.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_LINK);
+        orig_perms.remove(Permission::USER_LINK);
+        orig_perms.remove(Permission::GROUP_LINK);
+        orig_perms.remove(Permission::OTHER_LINK);
+        orig_perms
+    };
+    key.set_permissions(perms).unwrap();
+
+    let found_key = keyring
+        .search_for_key::<User, _, _>(description, None)
+        .unwrap();
+    assert_eq!(found_key, key);
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+}
+
+#[test]
+fn search_and_find_keyring_no_link_perm_no_dest() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_keyring_no_link_perm_no_dest")
+        .unwrap();
+    let description = "search_and_find_keyring_no_link_perm_no_dest_keyring";
+    let mut target_keyring = new_keyring.add_keyring(description).unwrap();
+
+    let perms = {
+        let mut orig_perms = target_keyring.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_LINK);
+        orig_perms.remove(Permission::USER_LINK);
+        orig_perms.remove(Permission::GROUP_LINK);
+        orig_perms.remove(Permission::OTHER_LINK);
+        orig_perms
+    };
+    target_keyring.set_permissions(perms).unwrap();
+
+    let found_keyring = keyring.search_for_keyring(description, None).unwrap();
+    assert_eq!(found_keyring, target_keyring);
+}
+
+#[test]
+fn search_and_find_key_no_link_perm() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_key_no_link_perm")
+        .unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_key_no_link_perm_destination")
+        .unwrap();
+    let description = "search_and_find_key_no_link_perm_key";
+    let payload = "payload".as_bytes();
+    let mut key = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let perms = {
+        let mut orig_perms = key.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_LINK);
+        orig_perms.remove(Permission::USER_LINK);
+        orig_perms.remove(Permission::GROUP_LINK);
+        orig_perms.remove(Permission::OTHER_LINK);
+        orig_perms
+    };
+    key.set_permissions(perms).unwrap();
+
+    let err = keyring
+        .search_for_key::<User, _, _>(description, &mut destination_keyring)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+
+    // Assert that it was not linked to the destination keyring.
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn search_and_find_keyring_no_link_perm() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_keyring_no_link_perm")
+        .unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_keyring_no_link_perm_destination")
+        .unwrap();
+    let description = "search_and_find_keyring_no_link_perm_keyring";
+    let mut target_keyring = new_keyring.add_keyring(description).unwrap();
+
+    let perms = {
+        let mut orig_perms = target_keyring.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_LINK);
+        orig_perms.remove(Permission::USER_LINK);
+        orig_perms.remove(Permission::GROUP_LINK);
+        orig_perms.remove(Permission::OTHER_LINK);
+        orig_perms
+    };
+    target_keyring.set_permissions(perms).unwrap();
+
+    let err = keyring
+        .search_for_keyring(description, &mut destination_keyring)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+
+    // Assert that it was not linked to the destination keyring.
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn search_and_find_key_no_write_perm() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_key_no_write_perm")
+        .unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_key_no_write_perm_destination")
+        .unwrap();
+    let description = "search_and_find_key_no_write_perm_key";
+    let payload = "payload".as_bytes();
+    let _ = new_keyring
+        .add_key::<User, _, _>(description, payload)
+        .unwrap();
+
+    let perms = {
+        let mut orig_perms = destination_keyring.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_WRITE);
+        orig_perms.remove(Permission::USER_WRITE);
+        orig_perms.remove(Permission::GROUP_WRITE);
+        orig_perms.remove(Permission::OTHER_WRITE);
+        orig_perms
+    };
+    destination_keyring.set_permissions(perms).unwrap();
+
+    let err = keyring
+        .search_for_key::<User, _, _>(description, &mut destination_keyring)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+
+    // Assert that it was not linked to the destination keyring.
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn search_and_find_keyring_no_write_perm() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring
+        .add_keyring("search_and_find_keyring_no_write_perm")
+        .unwrap();
+    let mut destination_keyring = keyring
+        .add_keyring("search_and_find_keyring_no_write_perm_destination")
+        .unwrap();
+    let description = "search_and_find_keyring_no_write_perm_keyring";
+    let _ = new_keyring.add_keyring(description).unwrap();
+
+    let perms = {
+        let mut orig_perms = destination_keyring.description().unwrap().perms;
+        orig_perms.remove(Permission::POSSESSOR_WRITE);
+        orig_perms.remove(Permission::USER_WRITE);
+        orig_perms.remove(Permission::GROUP_WRITE);
+        orig_perms.remove(Permission::OTHER_WRITE);
+        orig_perms
+    };
+    destination_keyring.set_permissions(perms).unwrap();
+
+    let err = keyring
+        .search_for_keyring(description, &mut destination_keyring)
+        .unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EACCES));
+
+    // Assert that it was not linked to the destination keyring.
+    let (keys, keyrings) = destination_keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+}
diff --git a/src/tests/session.rs b/src/tests/session.rs
new file mode 100644
index 0000000..3831629
--- /dev/null
+++ b/src/tests/session.rs
@@ -0,0 +1,110 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use serial_test_derive::serial;
+
+use crate::keytypes;
+use crate::{KeyType, Keyring, Permission, SpecialKeyring};
+
+use super::utils::kernel::*;
+
+#[test]
+#[serial(join_session)]
+fn join_anonymous_session() {
+    let session_before = Keyring::attach_or_create(SpecialKeyring::Session).unwrap();
+    let keyring = Keyring::join_anonymous_session().unwrap();
+    let session_after = Keyring::attach_or_create(SpecialKeyring::Session).unwrap();
+
+    assert_ne!(session_before, keyring);
+    assert_eq!(session_after, keyring);
+
+    let desc = keyring.description().unwrap();
+    assert_eq!(desc.type_, keytypes::Keyring::name());
+    assert_eq!(desc.uid, *UID);
+    assert_eq!(desc.gid, *GID);
+    assert_eq!(
+        desc.perms,
+        Permission::POSSESSOR_ALL | Permission::USER_VIEW | Permission::USER_READ
+    );
+    assert_eq!(desc.description, "_ses");
+
+    keyring.invalidate().unwrap()
+}
+
+#[test]
+#[serial(join_session)]
+fn join_new_named_session() {
+    let session_before = Keyring::attach_or_create(SpecialKeyring::Session).unwrap();
+    let name = "join_new_named_session";
+    let keyring = Keyring::join_session(name).unwrap();
+    let session_after = Keyring::attach_or_create(SpecialKeyring::Session).unwrap();
+
+    assert_ne!(session_before, keyring);
+    assert_eq!(session_after, keyring);
+
+    let desc = keyring.description().unwrap();
+    assert_eq!(desc.type_, keytypes::Keyring::name());
+    assert_eq!(desc.uid, *UID);
+    assert_eq!(desc.gid, *GID);
+    assert_eq!(
+        desc.perms,
+        Permission::POSSESSOR_ALL
+            | Permission::USER_VIEW
+            | Permission::USER_READ
+            | Permission::USER_LINK
+    );
+    assert_eq!(desc.description, name);
+
+    keyring.invalidate().unwrap()
+}
+
+#[test]
+#[serial(join_session)]
+fn join_existing_named_session() {
+    let name = "join_existing_named_session";
+
+    let session_before = Keyring::attach_or_create(SpecialKeyring::Session).unwrap();
+    let keyring = Keyring::join_session(name).unwrap();
+    let session_after = Keyring::attach_or_create(SpecialKeyring::Session).unwrap();
+
+    assert_ne!(session_before, keyring);
+    assert_eq!(session_after, keyring);
+
+    let desc = keyring.description().unwrap();
+    assert_eq!(desc.type_, keytypes::Keyring::name());
+    assert_eq!(desc.uid, *UID);
+    assert_eq!(desc.gid, *GID);
+    assert_eq!(
+        desc.perms,
+        Permission::POSSESSOR_ALL
+            | Permission::USER_VIEW
+            | Permission::USER_READ
+            | Permission::USER_LINK
+    );
+    assert_eq!(desc.description, name);
+
+    keyring.invalidate().unwrap()
+}
diff --git a/src/tests/timeout.rs b/src/tests/timeout.rs
new file mode 100644
index 0000000..003f834
--- /dev/null
+++ b/src/tests/timeout.rs
@@ -0,0 +1,146 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use std::thread;
+use std::time::Duration;
+
+use crate::keytypes::User;
+
+use super::utils;
+
+#[test]
+fn invalid_key() {
+    let mut key = utils::invalid_key();
+    let duration = Duration::from_secs(1);
+    let err = key.set_timeout(duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_keyring() {
+    let mut keyring = utils::invalid_keyring();
+    let duration = Duration::from_secs(1);
+    let err = keyring.set_timeout(duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let duration = Duration::from_secs(1);
+    let err = key.set_timeout(duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn big_timeout_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("unlinked_key", payload)
+        .unwrap();
+
+    let duration = Duration::from_secs(1024);
+    key.set_timeout(duration).unwrap();
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+}
+
+#[test]
+fn big_timeout_keyring() {
+    let mut keyring = utils::new_test_keyring();
+
+    let duration = Duration::from_secs(1024);
+    keyring.set_timeout(duration).unwrap();
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn expired_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("expired_key", payload)
+        .unwrap();
+    let key_observer1 = key.clone();
+    let key_observer2 = key.clone();
+
+    let duration = Duration::from_secs(1);
+    key.set_timeout(duration).unwrap();
+
+    thread::sleep(duration);
+    thread::sleep(duration);
+
+    let err = key.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYEXPIRED));
+
+    let err = key.set_timeout(duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYEXPIRED));
+
+    let err = key.invalidate().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYEXPIRED));
+
+    let err = key_observer1.revoke().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYEXPIRED));
+
+    keyring.unlink_key(&key_observer2).unwrap();
+}
+
+#[test]
+fn expired_keyring() {
+    let mut keyring = utils::new_test_keyring_manual();
+    let keyring_observer = keyring.clone();
+
+    let duration = Duration::from_secs(1);
+    keyring.set_timeout(duration).unwrap();
+
+    thread::sleep(duration);
+    thread::sleep(duration);
+
+    let err = keyring.read().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYEXPIRED));
+
+    let err = keyring.set_timeout(duration).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYEXPIRED));
+
+    let err = keyring.invalidate().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYEXPIRED));
+
+    let err = keyring_observer.revoke().unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EKEYEXPIRED));
+}
diff --git a/src/tests/unlink.rs b/src/tests/unlink.rs
new file mode 100644
index 0000000..92fabe3
--- /dev/null
+++ b/src/tests/unlink.rs
@@ -0,0 +1,228 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::keytypes::User;
+
+use super::utils;
+
+#[test]
+fn invalid_target_key() {
+    let mut invalid_keyring = utils::invalid_keyring();
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("invalid_target_key", payload)
+        .unwrap();
+
+    let err = invalid_keyring.unlink_key(&key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_target_keyring() {
+    let mut invalid_keyring = utils::invalid_keyring();
+    let keyring = utils::new_test_keyring();
+
+    let err = invalid_keyring.unlink_keyring(&keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_source_key() {
+    let mut keyring = utils::new_test_keyring();
+    let invalid_key = utils::invalid_key();
+
+    let err = keyring.unlink_key(&invalid_key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn invalid_source_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let invalid_keyring = utils::invalid_keyring();
+
+    let err = keyring.unlink_keyring(&invalid_keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn unlink_key_from_non_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlink_key_from_non_keyring", payload)
+        .unwrap();
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+
+    let err = not_a_keyring.unlink_key(&key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+}
+
+#[test]
+fn unlink_keyring_from_non_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlink_keyring_from_non_keyring", payload)
+        .unwrap();
+    let mut not_a_keyring = utils::key_as_keyring(&key);
+
+    let err = not_a_keyring.unlink_keyring(&keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOTDIR));
+}
+
+#[test]
+fn unlink_key_as_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlink_keyring_from_non_keyring", payload)
+        .unwrap();
+    let not_a_keyring = utils::key_as_keyring(&key);
+
+    // This is OK because the kernel doesn't have the type knowledge that our API does.
+    keyring.unlink_keyring(&not_a_keyring).unwrap();
+}
+
+#[test]
+fn unlink_keyring_as_key() {
+    let mut keyring = utils::new_test_keyring();
+    let new_keyring = keyring.add_keyring("unlink_keyring_as_key").unwrap();
+    let not_a_key = utils::keyring_as_key(&new_keyring);
+
+    // This is OK because the kernel doesn't have the type knowledge that our API does.
+    keyring.unlink_key(&not_a_key).unwrap();
+}
+
+#[test]
+fn unlink_unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlink_unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let err = keyring.unlink_key(&key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn unlink_unlinked_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let new_keyring = keyring.add_keyring("unlink_unlinked_keyring").unwrap();
+
+    keyring.unlink_keyring(&new_keyring).unwrap();
+    utils::wait_for_keyring_gc(&new_keyring);
+
+    let err = keyring.unlink_keyring(&new_keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn unlink_key_from_unlinked_keyring() {
+    let mut keyring = utils::new_test_keyring_manual();
+    let mut keyring_observer = keyring.clone();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlink_key_from_unlinked_keyring", payload)
+        .unwrap();
+
+    keyring.invalidate().unwrap();
+    utils::wait_for_keyring_gc(&keyring_observer);
+
+    let err = keyring_observer.unlink_key(&key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn unlink_keyring_from_unlinked_keyring() {
+    let mut keyring = utils::new_test_keyring_manual();
+    let mut keyring_observer = keyring.clone();
+    let new_keyring = keyring.add_keyring("unlink_from_unlinked_keyring").unwrap();
+
+    keyring.invalidate().unwrap();
+    utils::wait_for_keyring_gc(&keyring_observer);
+
+    let err = keyring_observer.unlink_keyring(&new_keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn unlink_unassociated_key() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring.add_keyring("unlink_unassociated_key").unwrap();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlink_unassociated_key", payload)
+        .unwrap();
+
+    let err = new_keyring.unlink_key(&key).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOENT));
+}
+
+#[test]
+fn unlink_unassociated_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let mut new_keyring = keyring.add_keyring("unlink_unassociated_keyring").unwrap();
+    let inner_keyring = keyring
+        .add_keyring("unlink_unassociated_keyring_keyring")
+        .unwrap();
+
+    let err = new_keyring.unlink_keyring(&inner_keyring).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOENT));
+}
+
+#[test]
+fn unlink_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let key = keyring
+        .add_key::<User, _, _>("unlink_unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+}
+
+#[test]
+fn unlink_keyring() {
+    let mut keyring = utils::new_test_keyring();
+    let new_keyring = keyring.add_keyring("unlink_keyring").unwrap();
+
+    keyring.unlink_keyring(&new_keyring).unwrap();
+    utils::wait_for_keyring_gc(&new_keyring);
+
+    let (keys, keyrings) = keyring.read().unwrap();
+    assert!(keys.is_empty());
+    assert!(keyrings.is_empty());
+}
diff --git a/src/tests/update.rs b/src/tests/update.rs
new file mode 100644
index 0000000..47b8ea5
--- /dev/null
+++ b/src/tests/update.rs
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, Ben Boeckel
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright notice,
+//       this list of conditions and the following disclaimer in the documentation
+//       and/or other materials provided with the distribution.
+//     * Neither the name of this project nor the names of its contributors
+//       may be used to endorse or promote products derived from this software
+//       without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::keytypes::User;
+
+use super::utils;
+
+#[test]
+fn keyring() {
+    let keyring = utils::new_test_keyring();
+    let mut key = utils::keyring_as_key(&keyring);
+
+    let payload = "payload".as_bytes();
+    let err = key.update(payload).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EOPNOTSUPP));
+}
+
+#[test]
+fn invalid_key() {
+    let mut key = utils::invalid_key();
+
+    let payload = "payload".as_bytes();
+    let err = key.update(payload).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::EINVAL));
+}
+
+#[test]
+fn unlinked_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring
+        .add_key::<User, _, _>("unlinked_key", payload)
+        .unwrap();
+
+    keyring.unlink_key(&key).unwrap();
+    utils::wait_for_key_gc(&key);
+
+    let payload = "payload".as_bytes();
+    let err = key.update(payload).unwrap_err();
+    assert_eq!(err, errno::Errno(libc::ENOKEY));
+}
+
+#[test]
+fn user_key() {
+    let mut keyring = utils::new_test_keyring();
+    let payload = "payload".as_bytes();
+    let mut key = keyring.add_key::<User, _, _>("user_key", payload).unwrap();
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+
+    let payload = "updated_payload".as_bytes();
+    key.update(payload).unwrap();
+
+    let actual_payload = key.read().unwrap();
+    assert_eq!(payload, actual_payload.as_slice());
+}
diff --git a/src/tests/utils/kernel.rs b/src/tests/utils/kernel.rs
index c39a80f..2f6c196 100644
--- a/src/tests/utils/kernel.rs
+++ b/src/tests/utils/kernel.rs
@@ -25,16 +25,63 @@
 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 use std::collections::HashMap;
+use std::ffi::CStr;
 use std::fs;
+use std::mem;
 use std::str::FromStr;
 
+use lazy_static::lazy_static;
 use regex::{Captures, Regex};
+use semver::{Version, VersionReq};
 
 lazy_static! {
+    pub static ref KERNEL_VERSION: String = kernel_version();
+    pub static ref SEMVER_KERNEL_VERSION: &'static str = semver_kernel_version();
+    pub static ref HAVE_INVALIDATE: bool = have_invalidate();
     pub static ref PAGE_SIZE: usize = page_size();
+    pub static ref UID: libc::uid_t = getuid();
+    pub static ref GID: libc::gid_t = getgid();
     pub static ref KEY_INFO: KeyQuota = key_user_info();
 }
 
+// The full version of the running kernel.
+fn kernel_version() -> String {
+    let mut utsname = unsafe { mem::zeroed() };
+    let ret = unsafe { libc::uname(&mut utsname) };
+    if ret < 0 {
+        panic!("failed to query the kernel version: {}", errno::errno());
+    }
+    let cstr = unsafe { CStr::from_ptr(utsname.release.as_ptr()) };
+    cstr.to_str()
+        .expect("kernel version should be ASCII")
+        .into()
+}
+
+// A semver-compatible string for the kernel version.
+fn semver_kernel_version() -> &'static str {
+    match (*KERNEL_VERSION).find('-') {
+        Some(pos) => &(*KERNEL_VERSION)[..pos],
+        None => &*KERNEL_VERSION,
+    }
+}
+
+// Whether the kernel supports the `invalidate` action on a key.
+fn have_invalidate() -> bool {
+    match Version::parse(*SEMVER_KERNEL_VERSION) {
+        Ok(ver) => {
+            let minver = VersionReq::parse(">=3.5").unwrap();
+            minver.matches(&ver)
+        },
+        Err(err) => {
+            eprintln!(
+                "failed to parse kernel version `{}` ({}): assuming incompatibility",
+                *SEMVER_KERNEL_VERSION, err
+            );
+            false
+        },
+    }
+}
+
 fn page_size() -> usize {
     errno::set_errno(errno::Errno(0));
     let ret = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
@@ -120,3 +167,11 @@ fn key_user_info() -> KeyQuota {
         .get(&uid)
         .expect("the current user has no keys?")
 }
+
+fn getuid() -> libc::uid_t {
+    unsafe { libc::getuid() }
+}
+
+fn getgid() -> libc::gid_t {
+    unsafe { libc::getgid() }
+}
diff --git a/src/tests/utils/mod.rs b/src/tests/utils/mod.rs
index 36a3282..4f669b3 100644
--- a/src/tests/utils/mod.rs
+++ b/src/tests/utils/mod.rs
@@ -24,5 +24,109 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+use std::ops::{Deref, DerefMut};
+use std::sync::atomic;
+
+use crate::{Key, Keyring, KeyringSerial, SpecialKeyring};
+
 pub mod kernel;
 pub mod keys;
+
+#[derive(Debug)]
+pub struct ScopedKeyring {
+    keyring: Keyring,
+}
+
+impl Drop for ScopedKeyring {
+    fn drop(&mut self) {
+        self.keyring.clone().invalidate().unwrap();
+        wait_for_keyring_gc(&self.keyring);
+    }
+}
+
+impl Deref for ScopedKeyring {
+    type Target = Keyring;
+
+    fn deref(&self) -> &Self::Target {
+        &self.keyring
+    }
+}
+
+impl DerefMut for ScopedKeyring {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.keyring
+    }
+}
+
+// For testing, each test gets a new keyring attached to the Thread keyring. This makes sure tests
+// don't interfere with each other, and keys are not prematurely garbage collected.
+pub fn new_test_keyring_manual() -> Keyring {
+    let mut thread_keyring = Keyring::attach_or_create(SpecialKeyring::Thread).unwrap();
+
+    static KEYRING_COUNT: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
+    let num = KEYRING_COUNT.fetch_add(1, atomic::Ordering::SeqCst);
+    thread_keyring
+        .add_keyring(format!("test:rust-keyutils{}", num))
+        .unwrap()
+}
+
+// For testing, each test gets a new keyring attached to the Thread keyring. This makes sure tests
+// don't interfere with each other, and keys are not prematurely garbage collected.
+pub fn new_test_keyring() -> ScopedKeyring {
+    ScopedKeyring {
+        keyring: new_test_keyring_manual(),
+    }
+}
+
+unsafe fn invalid_serial() -> KeyringSerial {
+    // Yes, we're explicitly breaking the NonZeroI32 rules here. However, it is not passing through
+    // any bits which care (e.g., `Option`), so this is purely to test that using an invalid
+    // keyring ID gives back `EINVAL` as expected.
+    KeyringSerial::new_unchecked(0)
+}
+
+pub fn invalid_keyring() -> Keyring {
+    unsafe { Keyring::new(invalid_serial()) }
+}
+
+pub fn invalid_key() -> Key {
+    unsafe { Key::new(invalid_serial()) }
+}
+
+pub fn keyring_as_key(keyring: &Keyring) -> Key {
+    unsafe { Key::new(keyring.serial()) }
+}
+
+pub fn key_as_keyring(key: &Key) -> Keyring {
+    unsafe { Keyring::new(key.serial()) }
+}
+
+/// Keys are deleted asynchronously; describing the key succeeds until it has been garbage
+/// collected.
+pub fn wait_for_key_gc(key: &Key) {
+    loop {
+        match key.description() {
+            Ok(_) => (),
+            Err(errno::Errno(libc::ENOKEY)) => break,
+            e @ Err(_) => {
+                e.unwrap();
+                unreachable!()
+            },
+        }
+    }
+}
+
+/// Keys are deleted asynchronously; describing the key succeeds until it has been garbage
+/// collected.
+pub fn wait_for_keyring_gc(keyring: &Keyring) {
+    loop {
+        match keyring.read() {
+            Ok(_) | Err(errno::Errno(libc::EACCES)) => (),
+            Err(errno::Errno(libc::ENOKEY)) => break,
+            e @ Err(_) => {
+                e.unwrap();
+                unreachable!()
+            },
+        }
+    }
+}