1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use std::fs;
use std::io::{self, Write};
use std::env;
use std::path::{Path, PathBuf};
use std::fmt::Debug;
use uuid::Uuid;
use std::sync::{Mutex, Arc};

static IRON_INTEGRATION_TEST_DIR : &'static str = "iron-integration-tests";

#[derive(Debug, PartialEq, Clone)]
struct FileBuilder {
    path: PathBuf,
    body: Vec<u8>
}

impl FileBuilder {
    /// creates new instance of FileBuilder
    pub fn new<P: AsRef<Path>, B: Into<Vec<u8>>>(path: P, body: B) -> FileBuilder {
        FileBuilder { path: path.as_ref().to_path_buf(), body: body.into() }
    }

    fn mk(&self) -> Result<(), String> {
        try!(mkdir_recursive(&self.dirname()));

        let mut file = try!(
            fs::File::create(&self.path)
                .with_err_msg(format!("Could not create file; path={}",
                                      self.path.display())));

        file.write_all(&self.body)
            .with_err_msg(format!("Could not write to file; path={}",
                                  self.path.display()))
    }

    // FIXME: Panics if there's no parent
    fn dirname(&self) -> &Path {
        &self.path.parent().expect("parent directory")
    }
}


/// An RAII guard that controls a temporary directory of test files.
///
/// It is also a builder and is used to build up the temporary files,
/// which are then deleted on drop.
#[derive(Debug, Clone)]
pub struct ProjectBuilder {
    name: String,
    root: PathBuf,
    files: Vec<FileBuilder>,
    lock: Arc<Mutex<()>>,
}

impl ProjectBuilder {
    /// Create a ProjectBuilder that will manage a new temporary directory
    /// making use of the current name.
    pub fn new(name: &str) -> ProjectBuilder {
        let id = Uuid::new_v4();
        let path = root(id);

        // Clear out the temp directory.
        path.rm_rf().unwrap();

        ProjectBuilder {
            name: name.to_string(),
            root: path.join(name),
            files: vec!(),
            lock: Arc::new(Mutex::new(())),
        }
    }

    /// Get the root path of the temporary directory.
    pub fn root(&self) -> &Path {
        &self.root
    }

    /// Add a new file to the temporary directory with the given contents.
    pub fn file<P, B>(mut self, path: P, body: B) -> ProjectBuilder
    where P: AsRef<Path>, B: Into<Vec<u8>> {
        self.files.push(FileBuilder::new(self.root.join(&path), body));
        self
    }

    /// Creates the project layout, based on current state of the builder
    pub fn build(&self) -> &ProjectBuilder {
        self.build_with_result().map(|_| self).unwrap()
    }

    /// Creates the project layout, based on current state of the builder
    pub fn build_with_result(&self) -> Result<(), String> {
        let _lock = self.lock.lock().unwrap();
        for file in self.files.iter() {
            try!(file.mk());
        }

        Ok(())
    }
}

impl PartialEq for ProjectBuilder {
    fn eq(&self, other: &ProjectBuilder) -> bool {
        self.name.eq(&other.name) &&
            self.root.eq(&other.root) &&
            self.files.eq(&other.files)
    }
}

impl Drop for ProjectBuilder {
    fn drop(&mut self) {
        let _lock = self.lock.lock().unwrap();
        match self.root().parent().map(BuilderPathExt::rm_rf) {
            Some(Ok(_)) => debug!("Successfully cleaned up the test directory; path = {:?}", self.root().parent().unwrap()),
            Some(Err(e)) => debug!("Failed to cleanup the test directory; path = {:?}; {}", self.root().parent().unwrap(), e),
            None => debug!("Failed to cleanup the test directory; no parent")
        }
    }
}

// Recursively creates the directory with all subdirectories
fn mkdir_recursive(path: &Path) -> Result<(), String> {
    fs::create_dir_all(path)
        .with_err_msg(format!("could not create directory; path={}",
                              path.display()))
}

/// Convenience methods to show errors
trait ErrMsg<T> {
    /// Convenience method on Result to to return either value on Ok,
    /// or value + error on Err
    fn with_err_msg(self, val: String) -> Result<T, String>;
}

impl<T, E: Debug> ErrMsg<T> for Result<T, E> {
    fn with_err_msg(self, val: String) -> Result<T, String> {
        match self {
            Ok(val) => Ok(val),
            Err(err) => Err(format!("{}; original={:?}", val, err))
        }
    }
}

// Current test root path.
// Will be located in target/iron-integration-tests/test-<uuid>
fn root(id: Uuid) -> PathBuf {
    integration_tests_dir().join(&format!("test-{}", id))
}

fn integration_tests_dir() -> PathBuf {
    env::current_exe()
        .map(|mut p| { p.pop(); p.join(IRON_INTEGRATION_TEST_DIR) })
        .unwrap()
}


/// Convenience methods on Path
pub trait BuilderPathExt {
    /// Deletes directory in Path recursively
    fn rm_rf(&self) -> io::Result<()>;
}

impl BuilderPathExt for Path {
    fn rm_rf(&self) -> io::Result<()> {
        if let Ok(_) = fs::metadata(self) {
            fs::remove_dir_all(self)
        } else {
            Ok(())
        }
    }
}