diff --git a/binary-install/Cargo.toml b/binary-install/Cargo.toml index b735cea28..ca2044da8 100644 --- a/binary-install/Cargo.toml +++ b/binary-install/Cargo.toml @@ -18,3 +18,6 @@ is_executable = "0.1.2" siphasher = "0.2.3" tar = "0.4.16" zip = "0.5.0" + +[dev-dependencies] +tempfile = "3.0.5" diff --git a/binary-install/src/lib.rs b/binary-install/src/lib.rs index eac1c9893..68510c654 100644 --- a/binary-install/src/lib.rs +++ b/binary-install/src/lib.rs @@ -23,11 +23,13 @@ use std::path::{Path, PathBuf}; /// Global cache for wasm-pack, currently containing binaries downloaded from /// urls like wasm-bindgen and such. +#[derive(Debug)] pub struct Cache { destination: PathBuf, } /// Representation of a downloaded tarball/zip +#[derive(Debug)] pub struct Download { root: PathBuf, } @@ -81,22 +83,10 @@ impl Cache { binaries: &[&str], url: &str, ) -> Result, Error> { - let mut hasher = SipHasher13::new(); - url.hash(&mut hasher); - let result = hasher.finish(); - let hex = hex::encode(&[ - (result >> 0) as u8, - (result >> 8) as u8, - (result >> 16) as u8, - (result >> 24) as u8, - (result >> 32) as u8, - (result >> 40) as u8, - (result >> 48) as u8, - (result >> 56) as u8, - ]); - let dirname = format!("{}-{}", name, hex); + let dirname = hashed_dirname(url, name); let destination = self.destination.join(&dirname); + if destination.exists() { return Ok(Some(Download { root: destination })); } @@ -270,3 +260,82 @@ fn curl(url: &str) -> Result, Error> { ) } } + +fn hashed_dirname(url: &str, name: &str) -> String { + let mut hasher = SipHasher13::new(); + url.hash(&mut hasher); + let result = hasher.finish(); + let hex = hex::encode(&[ + (result >> 0) as u8, + (result >> 8) as u8, + (result >> 16) as u8, + (result >> 24) as u8, + (result >> 32) as u8, + (result >> 40) as u8, + (result >> 48) as u8, + (result >> 56) as u8, + ]); + format!("{}-{}", name, hex) +} + +#[test] +fn it_returns_same_hash_for_same_name_and_url() { + let name = "wasm-pack"; + let url = "http://localhost:7878/wasm-pack-v0.6.0.tar.gz"; + + let first = hashed_dirname(url, name); + let second = hashed_dirname(url, name); + + assert!(!first.is_empty()); + assert!(!second.is_empty()); + assert_eq!(first, second); +} + +#[test] +fn it_returns_different_hashes_for_different_urls() { + let name = "wasm-pack"; + let url = "http://localhost:7878/wasm-pack-v0.5.1.tar.gz"; + let second_url = "http://localhost:7878/wasm-pack-v0.6.0.tar.gz"; + + let first = hashed_dirname(url, name); + let second = hashed_dirname(second_url, name); + + assert_ne!(first, second); +} + +#[test] +fn it_returns_cache_dir() { + let name = "wasm-pack"; + let cache = Cache::new(name); + + let expected = dirs::cache_dir() + .unwrap() + .join(PathBuf::from(".".to_owned() + name)); + + assert!(cache.is_ok()); + assert_eq!(cache.unwrap().destination, expected); +} + +#[test] +fn it_returns_destination_if_binary_already_exists() { + use std::fs; + + let binary_name = "wasm-pack"; + let binaries = vec![binary_name]; + + let dir = tempfile::TempDir::new().unwrap(); + let cache = Cache::at(dir.path()); + let url = &format!("{}/{}.tar.gz", "http://localhost:7878", binary_name); + + let dirname = hashed_dirname(&url, &binary_name); + let full_path = dir.path().join(dirname); + + // Create temporary directory and binary to simulate that + // a cached binary already exists. + fs::create_dir_all(full_path).unwrap(); + + let dl = cache.download(true, binary_name, &binaries, url); + + assert!(dl.is_ok()); + assert!(dl.unwrap().is_some()) +} diff --git a/binary-install/tests/all/cache.rs b/binary-install/tests/all/cache.rs new file mode 100644 index 000000000..1c7c9093d --- /dev/null +++ b/binary-install/tests/all/cache.rs @@ -0,0 +1,114 @@ +use binary_install::Cache; +use utils; + +#[test] +fn it_returns_none_if_install_is_not_permitted() { + let binary_name = "wasm-pack"; + let binaries = vec![binary_name]; + + let dir = tempfile::TempDir::new().unwrap(); + let cache = Cache::at(dir.path()); + + let dl = cache.download( + false, + binary_name, + &binaries, + &format!("{}/{}.tar.gz", "", binary_name), + ); + + assert!(dl.is_ok()); + assert!(dl.unwrap().is_none()) +} + +#[test] +fn it_downloads_tarball() { + let server_port = 7880; + let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port); + let binary_name = "wasm-pack"; + let binaries = vec![binary_name]; + + // Create a temporary tarball. + let tarball = utils::create_tarball(binary_name).ok(); + + // Spin up a local TcpListener. + utils::start_server(server_port, tarball); + + let dir = tempfile::TempDir::new().unwrap(); + let cache = Cache::at(dir.path()); + + let dl = cache.download( + true, + binary_name, + &binaries, + &format!("{}/{}.tar.gz", &url, binary_name), + ); + + assert!(dl.is_ok()); + assert!(dl.unwrap().is_some()) +} + +#[test] +fn it_returns_error_that_it_failed_to_download() { + let server_port = 7881; + let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port); + let binary_name = "wasm-pack"; + let binaries = vec![binary_name]; + + let dir = tempfile::TempDir::new().unwrap(); + let cache = Cache::at(dir.path()); + let full_url = &format!("{}/{}.tar.gz", &url, binary_name); + + let dl = cache.download(true, binary_name, &binaries, full_url); + + assert!(dl.is_err()); + assert_eq!( + &format!("failed to download from {}", full_url), + &format!("{}", dl.unwrap_err()) + ); +} + +#[test] +fn it_returns_error_when_it_failed_to_extract_tarball() { + let server_port = 7882; + let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port); + let binary_name = "wasm-pack"; + let binaries = vec![binary_name]; + + let dir = tempfile::TempDir::new().unwrap(); + let cache = Cache::at(dir.path()); + let full_url = &format!("{}/{}.tar.gz", &url, binary_name); + + // Spin up a local TcpListener. + utils::start_server(server_port, None); + + let dl = cache.download(true, binary_name, &binaries, full_url); + + assert!(dl.is_err()); + assert_eq!( + &format!("failed to extract tarball from {}", full_url), + &format!("{}", dl.unwrap_err()) + ); +} + +#[test] +fn it_returns_error_when_it_failed_to_extract_zip() { + let server_port = 7883; + let url = format!("http://{}:{}", utils::TEST_SERVER_HOST, server_port); + let binary_name = "wasm-pack"; + let binaries = vec![binary_name]; + + let dir = tempfile::TempDir::new().unwrap(); + let cache = Cache::at(dir.path()); + let full_url = &format!("{}/{}.zip", &url, binary_name); + + // Spin up a local TcpListener. + utils::start_server(server_port, None); + + let dl = cache.download(true, binary_name, &binaries, full_url); + + assert!(dl.is_err()); + assert_eq!( + &format!("failed to extract zip from {}", full_url), + &format!("{}", dl.unwrap_err()) + ); +} diff --git a/binary-install/tests/all/main.rs b/binary-install/tests/all/main.rs new file mode 100644 index 000000000..74aaa3a16 --- /dev/null +++ b/binary-install/tests/all/main.rs @@ -0,0 +1,6 @@ +extern crate binary_install; +extern crate flate2; +extern crate tar; + +pub mod cache; +pub mod utils; diff --git a/binary-install/tests/all/utils/mod.rs b/binary-install/tests/all/utils/mod.rs new file mode 100644 index 000000000..48dc2f27f --- /dev/null +++ b/binary-install/tests/all/utils/mod.rs @@ -0,0 +1,67 @@ +use flate2::write::GzEncoder; +use flate2::Compression; +use std::fs::{File, OpenOptions}; +use std::io::{self, Read, Write}; +use std::net::TcpListener; +use std::thread; + +pub const TEST_SERVER_HOST: &'static str = "localhost"; + +pub fn start_server(port: u32, tarball: Option>) -> thread::JoinHandle { + thread::spawn(move || { + let listener = TcpListener::bind(format!("{}:{}", TEST_SERVER_HOST, port)).unwrap(); + + for stream in listener.incoming() { + let mut stream = stream.unwrap(); + + let mut buffer = [0; 512]; + + stream.read(&mut buffer).unwrap(); + + let response = "HTTP/1.1 200 OK\r\n\r\n"; + + stream.write(response.as_bytes()).unwrap(); + + match tarball.to_owned() { + Some(tar) => { + stream.write(tar.as_ref()).unwrap(); + } + None => {} + } + + stream.flush().unwrap(); + } + listener + }) +} + +pub fn create_tarball(binary_name: &str) -> Result, io::Error> { + let temp_dir = tempfile::TempDir::new().unwrap(); + let full_path = temp_dir.path().join(binary_name.to_owned() + ".tar.gz"); + + let tar = OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(&full_path)?; + + let mut file = OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(temp_dir.path().join(binary_name))?; + + let mut encoder = GzEncoder::new(tar, Compression::default()); + { + let mut archive = tar::Builder::new(&mut encoder); + archive.append_file(binary_name, &mut file)?; + } + + let mut contents = vec![]; + + encoder.finish()?; + + File::open(temp_dir.path().join(&full_path))?.read_to_end(&mut contents)?; + + Ok(contents) +}