~matthiasbeyer/maildir

a72f95dc3f22b45497edbcb591f026a02bcab5df — Andreas Rottmann 2 years ago 3ace2c9
Make tests concurrency-safe

The tests now all operate on a temporary directory, potentially
containing the contents of the "testdata" directory. The contents is
not copied as-is, but destination filenames are derived via
percent-decoding from the source filenames.

This allows the tests to safely run concurrently, so the flags
enforcing single-threaded execution have been removed from the CI
config.

Extending the API slightly, this commit also adds a `Maildir::path`
method, which came handy when refactoring the tests.

Fixes #6.
5 files changed, 141 insertions(+), 125 deletions(-)

M .travis.yml
M Cargo.toml
M src/lib.rs
R testdata/maildir1/cur/{1463868505.38518452d49213cb409aa1db32f53184_2_S => 1463868505.38518452d49213cb409aa1db32f53184%3A2%2CS}
M tests/smoke.rs
M .travis.yml => .travis.yml +0 -2
@@ 21,8 21,6 @@ matrix:
      script:
        - cargo clippy --all-features --all-targets -- -D warnings

env:
  - RUST_TEST_THREADS=1 RUST_TEST_TASKS=1
notifications:
  email: false


M Cargo.toml => Cargo.toml +5 -0
@@ 19,3 19,8 @@ travis-ci = { repository = "staktrace/maildir", branch = "master" }
[dependencies]
mailparse = "0.6"
nix  = "^0.8"

[dev-dependencies]
tempfile = "3.0.8"
walkdir = "2.2.7"
percent-encoding = "1.0.1"

M src/lib.rs => src/lib.rs +6 -1
@@ 4,7 4,7 @@ use std::fs;
use std::io::prelude::*;
use std::ops::Deref;
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::thread;
use std::time;



@@ 308,6 308,11 @@ pub struct Maildir {
}

impl Maildir {
    /// Returns the path of the maildir base folder.
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Returns the number of messages found inside the `new`
    /// maildir folder.
    pub fn count_new(&self) -> usize {

R testdata/maildir1/cur/1463868505.38518452d49213cb409aa1db32f53184_2_S => testdata/maildir1/cur/1463868505.38518452d49213cb409aa1db32f53184%3A2%2CS +0 -0
M tests/smoke.rs => tests/smoke.rs +130 -122
@@ 1,135 1,145 @@
use maildir::*;

use std::fs;
use std::path::*;
use std::os::unix::ffi::OsStrExt;

use mailparse::MailHeaderMap;

// `cargo package` doesn't package files with colons in the
// name, so we have to resort to naming it something else
// and renaming for the tests. Talk about ugly!
fn setup() {
    fs::rename(
        "testdata/maildir1/cur/1463868505.38518452d49213cb409aa1db32f53184_2_S",
        "testdata/maildir1/cur/1463868505.38518452d49213cb409aa1db32f53184:2,S",
    )
    .unwrap();
use percent_encoding::percent_decode;
use tempfile::tempdir;
use walkdir::WalkDir;

static TESTDATA_DIR: &str = "testdata";

// `cargo package` doesn't package files with certain characters, such as
// colons, in the name, so we percent-decode the file names when copying the
// data for the tests.
fn with_maildir<F>(name: &str, func: F)
where
    F: FnOnce(Maildir),
{
    let tmp_dir = tempdir().expect("could not create temporary directory");
    let tmp_path = tmp_dir.path();
    for entry in WalkDir::new(TESTDATA_DIR) {
        let entry = entry.expect("directory walk error");
        let relative = entry.path().strip_prefix(TESTDATA_DIR).unwrap();
        if relative.parent().is_none() {
            continue;
        }
        let decoded = percent_decode(relative.as_os_str().as_bytes())
            .decode_utf8()
            .unwrap();
        if entry.path().is_dir() {
            fs::create_dir(tmp_path.join(decoded.as_ref())).expect("could not create directory");
        } else {
            fs::copy(entry.path(), tmp_path.join(decoded.as_ref()))
                .expect("could not copy test data");
        }
    }
    func(Maildir::from(tmp_path.join(name)));
}

fn teardown() {
    fs::rename(
        "testdata/maildir1/cur/1463868505.38518452d49213cb409aa1db32f53184:2,S",
        "testdata/maildir1/cur/1463868505.38518452d49213cb409aa1db32f53184_2_S",
    )
    .unwrap();
fn with_maildir_empty<F>(name: &str, func: F)
where
    F: FnOnce(Maildir),
{
    let tmp_dir = tempdir().expect("could not create temporary directory");
    let tmp_path = tmp_dir.path();
    func(Maildir::from(tmp_path.join(name)));
}

#[test]
fn maildir_count() {
    setup();
    let maildir = Maildir::from("testdata/maildir1");
    assert_eq!(maildir.count_cur(), 1);
    assert_eq!(maildir.count_new(), 1);
    teardown();
    with_maildir("maildir1", |maildir| {
        assert_eq!(maildir.count_cur(), 1);
        assert_eq!(maildir.count_new(), 1);
    });
}

#[test]
fn maildir_list() {
    setup();
    let maildir = Maildir::from("testdata/maildir1");
    let mut iter = maildir.list_new();
    let mut first = iter.next().unwrap().unwrap();
    assert_eq!(first.id(), "1463941010.5f7fa6dd4922c183dc457d033deee9d7");
    assert_eq!(
        first.headers().unwrap().get_first_value("Subject").unwrap(),
        Some(String::from("test"))
    );
    assert_eq!(first.is_seen(), false);
    let second = iter.next();
    assert!(second.is_none());

    let mut iter = maildir.list_cur();
    let mut first = iter.next().unwrap().unwrap();
    assert_eq!(first.id(), "1463868505.38518452d49213cb409aa1db32f53184");
    assert_eq!(
        first
            .parsed()
            .unwrap()
            .headers
            .get_first_value("Subject")
            .unwrap(),
        Some(String::from("test"))
    );
    assert_eq!(first.is_seen(), true);
    let second = iter.next();
    assert!(second.is_none());
    teardown();
    with_maildir("maildir1", |maildir| {
        let mut iter = maildir.list_new();
        let mut first = iter.next().unwrap().unwrap();
        assert_eq!(first.id(), "1463941010.5f7fa6dd4922c183dc457d033deee9d7");
        assert_eq!(
            first.headers().unwrap().get_first_value("Subject").unwrap(),
            Some(String::from("test"))
        );
        assert_eq!(first.is_seen(), false);
        let second = iter.next();
        assert!(second.is_none());

        let mut iter = maildir.list_cur();
        let mut first = iter.next().unwrap().unwrap();
        assert_eq!(first.id(), "1463868505.38518452d49213cb409aa1db32f53184");
        assert_eq!(
            first
                .parsed()
                .unwrap()
                .headers
                .get_first_value("Subject")
                .unwrap(),
            Some(String::from("test"))
        );
        assert_eq!(first.is_seen(), true);
        let second = iter.next();
        assert!(second.is_none());
    })
}

#[test]
fn maildir_find() {
    setup();
    let maildir = Maildir::from("testdata/maildir1");
    assert_eq!(maildir.find("bad_id").is_some(), false);
    assert_eq!(
        maildir
            .find("1463941010.5f7fa6dd4922c183dc457d033deee9d7")
            .is_some(),
        true
    );
    assert_eq!(
        maildir
            .find("1463868505.38518452d49213cb409aa1db32f53184")
            .is_some(),
        true
    );
    teardown();
    with_maildir("maildir1", |maildir| {
        assert_eq!(
            maildir
                .find("1463941010.5f7fa6dd4922c183dc457d033deee9d7")
                .is_some(),
            true
        );
        assert_eq!(
            maildir
                .find("1463868505.38518452d49213cb409aa1db32f53184")
                .is_some(),
            true
        );
    })
}

#[test]
fn mark_read() {
    setup();
    let maildir = Maildir::from("testdata/maildir1");
    assert_eq!(
        maildir
            .move_new_to_cur("1463941010.5f7fa6dd4922c183dc457d033deee9d7")
            .unwrap(),
        ()
    );
    // Reset the filesystem
    fs::rename(
        "testdata/maildir1/cur/1463941010.5f7fa6dd4922c183dc457d033deee9d7:2,",
        "testdata/maildir1/new/1463941010.5f7fa6dd4922c183dc457d033deee9d7",
    )
    .unwrap();
    teardown();
    with_maildir("maildir1", |maildir| {
        assert_eq!(
            maildir
                .move_new_to_cur("1463941010.5f7fa6dd4922c183dc457d033deee9d7")
                .unwrap(),
            ()
        );
    });
}

#[test]
fn check_received() {
    setup();
    let maildir = Maildir::from("testdata/maildir1");
    let mut iter = maildir.list_cur();
    let mut first = iter.next().unwrap().unwrap();
    assert_eq!(first.received().unwrap(), 1_463_868_507);
    teardown();
    with_maildir("maildir1", |maildir| {
        let mut iter = maildir.list_cur();
        let mut first = iter.next().unwrap().unwrap();
        assert_eq!(first.received().unwrap(), 1_463_868_507);
    });
}

#[test]
fn check_create_dirs() {
    let maildir = Maildir::from("testdata/maildir2");
    assert!(!Path::new("testdata/maildir2").exists());
    assert!(!Path::new("testdata/maildir2/cur").exists());
    assert!(!Path::new("testdata/maildir2/new").exists());
    assert!(!Path::new("testdata/maildir2/tmp").exists());

    maildir.create_dirs().unwrap();
    assert!(Path::new("testdata/maildir2").exists());
    assert!(Path::new("testdata/maildir2/cur").exists());
    assert!(Path::new("testdata/maildir2/new").exists());
    assert!(Path::new("testdata/maildir2/tmp").exists());

    fs::remove_dir_all("testdata/maildir2").unwrap();
    with_maildir_empty("maildir2", |maildir| {
        assert!(!maildir.path().exists());
        for name in &["cur", "new", "tmp"] {
            assert!(!maildir.path().join(name).exists());
        }

        maildir.create_dirs().unwrap();
        assert!(maildir.path().exists());
        for name in &["cur", "new", "tmp"] {
            assert!(maildir.path().join(name).exists());
        }
    });
}

const TEST_MAIL_BODY: &[u8] = b"Return-Path: <of82ecuq@cip.cs.fau.de>


@@ 153,31 163,29 @@ y is Boomtime, the 59th day of Discord in the YOLD 3183";

#[test]
fn check_store_new() {
    let maildir = Maildir::from("testdata/maildir2");
    maildir.create_dirs().unwrap();
    with_maildir_empty("maildir2", |maildir| {
        maildir.create_dirs().unwrap();

    assert_eq!(maildir.count_new(), 0);
    maildir.store_new(TEST_MAIL_BODY).unwrap();
    assert_eq!(maildir.count_new(), 1);

    fs::remove_dir_all("testdata/maildir2").unwrap();
        assert_eq!(maildir.count_new(), 0);
        maildir.store_new(TEST_MAIL_BODY).unwrap();
        assert_eq!(maildir.count_new(), 1);
    });
}

#[test]
fn check_store_cur() {
    let maildir = Maildir::from("testdata/maildir2");
    maildir.create_dirs().unwrap();
    let testflags = "FRS";

    assert_eq!(maildir.count_cur(), 0);
    maildir
        .store_cur_with_flags(TEST_MAIL_BODY, testflags)
        .unwrap();
    assert_eq!(maildir.count_cur(), 1);
    with_maildir_empty("maildir2", |maildir| {
        maildir.create_dirs().unwrap();
        let testflags = "FRS";

    let mut iter = maildir.list_cur();
    let first = iter.next().unwrap().unwrap();
    assert_eq!(first.flags(), testflags);

    fs::remove_dir_all("testdata/maildir2").unwrap();
        assert_eq!(maildir.count_cur(), 0);
        maildir
            .store_cur_with_flags(TEST_MAIL_BODY, testflags)
            .unwrap();
        assert_eq!(maildir.count_cur(), 1);

        let mut iter = maildir.list_cur();
        let first = iter.next().unwrap().unwrap();
        assert_eq!(first.flags(), testflags);
    });
}