Skip to content

Commit

Permalink
feature(test): Add a wasm-pack test subcommand
Browse files Browse the repository at this point in the history
This uses `wasm-bindgen-test` under the hood.

Fixes #248
  • Loading branch information
fitzgen authored and ashleygwilliams committed Oct 1, 2018
1 parent be4bac8 commit c8aac65
Show file tree
Hide file tree
Showing 38 changed files with 1,631 additions and 183 deletions.
3 changes: 3 additions & 0 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ environment:
RUSTFLAGS: -C target-feature=+crt-static

install:
- ps: Install-Product node 10
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- set RUST_BACKTRACE=1
- rustup target add wasm32-unknown-unknown --toolchain nightly
- rustc -V
- cargo -V

Expand Down
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
target/
**/target
**/*.rs.bk
**/pkg
tests/fixtures/**/Cargo.lock
tests/.crates.toml
tests/bin
wasm-pack.log
13 changes: 12 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
language: rust
sudo: false

INSTALL_NODE_VIA_NVM: &INSTALL_NODE_VIA_NVM
|
rustup target add wasm32-unknown-unknown
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
source ~/.nvm/nvm.sh
nvm install lts/carbon

# Cache `cargo install`ed tools, but don't cache the project's `target`
# directory (which ends up over-caching and filling all disk space!)
cache:
Expand Down Expand Up @@ -32,9 +39,13 @@ matrix:
include:

# tests pass

- env: JOB=test RUST_BACKTRACE=1
rust: nightly
addons:
firefox: latest
chrome: stable
install:
- *INSTALL_NODE_VIA_NVM
script:
- cargo test --locked
- rustup component add rustfmt-preview
Expand Down
130 changes: 112 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ structopt = "0.2"
tar = "0.4.16"
toml = "0.4"
which = "2.0.0"
zip = "0.4.2"

[dev-dependencies]
copy_dir = "0.1.2"
Expand Down
243 changes: 243 additions & 0 deletions src/binaries.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
//! Utilities for finding and installing binaries that we depend on.

use curl;
use error::Error;
use failure;
use flate2;
use slog::Logger;
use std::collections::HashSet;
use std::ffi;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use tar;
use target;
use which::which;
use zip;

/// Get the path for a crate's directory of locally-installed binaries.
///
/// This does not check whether or ensure that the directory exists.
pub fn local_bin_dir(crate_path: &Path) -> PathBuf {
crate_path.join("bin")
}

/// Ensure that the crate's directory for locally-installed binaries exists.
pub fn ensure_local_bin_dir(crate_path: &Path) -> io::Result<()> {
fs::create_dir_all(local_bin_dir(crate_path))
}

/// Get the path for where `bin` would be if we have a crate-local install for
/// it.
///
/// This does *not* check whether there is a file at that path or not.
///
/// This will automatically add the `.exe` extension for windows.
pub fn local_bin_path(crate_path: &Path, bin: &str) -> PathBuf {
let mut p = local_bin_dir(crate_path).join(bin);
if target::WINDOWS {
p.set_extension("exe");
}
p
}

/// Get the local (at `$CRATE/bin/$BIN`; preferred) or global (on `$PATH`) path
/// for the given binary.
///
/// If this function returns `Some(path)`, then a file at that path exists (or
/// at least existed when we checked! In general, we aren't really worried about
/// racing with an uninstall of a tool that we rely on.)
pub fn bin_path(log: &Logger, crate_path: &Path, bin: &str) -> Option<PathBuf> {
assert!(!bin.ends_with(".exe"));
debug!(log, "Searching for {} binary...", bin);

// Return the path to the local binary, if it exists.
let local_path = |crate_path: &Path| -> Option<PathBuf> {
let p = local_bin_path(crate_path, bin);
debug!(log, "Checking for local {} binary at {}", bin, p.display());
if p.is_file() {
Some(p)
} else {
None
}
};

// Return the path to the global binary, if it exists.
let global_path = || -> Option<PathBuf> {
debug!(log, "Looking for global {} binary on $PATH", bin);
if let Ok(p) = which(bin) {
Some(p)
} else {
None
}
};

local_path(crate_path)
.or_else(global_path)
.map(|p| {
let p = p.canonicalize().unwrap_or(p);
debug!(log, "Using {} binary at {}", bin, p.display());
p
}).or_else(|| {
debug!(log, "Could not find {} binary.", bin);
None
})
}

fn with_url_context<T, E>(url: &str, r: Result<T, E>) -> Result<T, impl failure::Fail>
where
Result<T, E>: failure::ResultExt<T, E>,
{
use failure::ResultExt;
r.with_context(|_| format!("when requesting {}", url))
}

fn transfer(
url: &str,
easy: &mut curl::easy::Easy,
data: &mut Vec<u8>,
) -> Result<(), failure::Error> {
let mut transfer = easy.transfer();
with_url_context(
url,
transfer.write_function(|part| {
data.extend_from_slice(part);
Ok(part.len())
}),
)?;
with_url_context(url, transfer.perform())?;
Ok(())
}

fn curl(url: &str) -> Result<Vec<u8>, failure::Error> {
let mut data = Vec::new();

let mut easy = curl::easy::Easy::new();
with_url_context(url, easy.follow_location(true))?;
with_url_context(url, easy.url(url))?;
transfer(url, &mut easy, &mut data)?;

let status_code = with_url_context(url, easy.response_code())?;
if 200 <= status_code && status_code < 300 {
Ok(data)
} else {
Err(Error::http(&format!(
"received a bad HTTP status code ({}) when requesting {}",
status_code, url
)).into())
}
}

/// Download the `.tar.gz` file at the given URL and unpack the given binaries
/// from it into the given crate.
///
/// Upon success, every `$BIN` in `binaries` will be at `$CRATE/bin/$BIN`.
pub fn install_binaries_from_targz_at_url<'a, I>(
crate_path: &Path,
url: &str,
binaries: I,
) -> Result<(), Error>
where
I: IntoIterator<Item = &'a str>,
{
let mut binaries: HashSet<_> = binaries.into_iter().map(ffi::OsStr::new).collect();

let tarball = curl(&url).map_err(|e| Error::http(&e.to_string()))?;
let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(&tarball[..]));

ensure_local_bin_dir(crate_path)?;
let bin = local_bin_dir(crate_path);

for entry in archive.entries()? {
let mut entry = entry?;

let dest = match entry.path()?.file_stem() {
Some(f) if binaries.contains(f) => {
binaries.remove(f);
bin.join(entry.path()?.file_name().unwrap())
}
_ => continue,
};

entry.unpack(dest)?;
}

if binaries.is_empty() {
Ok(())
} else {
Err(Error::archive(&format!(
"the tarball at {} was missing expected executables: {}",
url,
binaries
.into_iter()
.map(|s| s.to_string_lossy())
.collect::<Vec<_>>()
.join(", "),
)))
}
}

/// Install binaries from within the given zip at the given URL.
///
/// Upon success, the binaries will be at the `$CRATE/bin/$BIN` path.
pub fn install_binaries_from_zip_at_url<'a, I>(
crate_path: &Path,
url: &str,
binaries: I,
) -> Result<(), Error>
where
I: IntoIterator<Item = &'a str>,
{
let mut binaries: HashSet<_> = binaries.into_iter().map(ffi::OsStr::new).collect();

let data = curl(&url).map_err(|e| Error::http(&e.to_string()))?;
let data = io::Cursor::new(data);
let mut zip = zip::ZipArchive::new(data)?;

ensure_local_bin_dir(crate_path)?;
let bin = local_bin_dir(crate_path);

for i in 0..zip.len() {
let mut entry = zip.by_index(i).unwrap();
let entry_path = entry.sanitized_name();
match entry_path.file_stem() {
Some(f) if binaries.contains(f) => {
binaries.remove(f);
let mut dest = bin_open_options()
.write(true)
.create_new(true)
.open(bin.join(entry_path.file_name().unwrap()))?;
io::copy(&mut entry, &mut dest)?;
}
_ => continue,
};
}

if binaries.is_empty() {
Ok(())
} else {
Err(Error::archive(&format!(
"the zip at {} was missing expected executables: {}",
url,
binaries
.into_iter()
.map(|s| s.to_string_lossy())
.collect::<Vec<_>>()
.join(", "),
)))
}
}

#[cfg(unix)]
fn bin_open_options() -> fs::OpenOptions {
use std::os::unix::fs::OpenOptionsExt;

let mut opts = fs::OpenOptions::new();
opts.mode(0o755);
opts
}

#[cfg(not(unix))]
fn bin_open_options() -> fs::OpenOptions {
fs::OpenOptions::new()
}
Loading

0 comments on commit c8aac65

Please sign in to comment.