Skip to content
This repository has been archived by the owner on Nov 8, 2021. It is now read-only.

Commit

Permalink
fix(command_test): stamps.rs check driver once per day, update RELEAS…
Browse files Browse the repository at this point in the history
…E_CHECKLIST
  • Loading branch information
MartinKavik committed Aug 28, 2019
1 parent 2facd17 commit a39a278
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 28 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ chrono = "0.4.6"
assert_cmd = "0.11"
lazy_static = "1.1.0"
predicates = "1.0.0"
serial_test = "0.2"
serial_test_derive = "0.2"
tempfile = "3"

[features]
Expand Down
4 changes: 4 additions & 0 deletions RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ This is a list of the things that need to happen during a release.
1. Create a new branch "#.#.#" where "#.#.#" is the release's version.
1. Add this release to the `CHANGELOG.md`. Use the structure of previous
entries.
1. Update `DEFAULT_CHROMEDRIVER_VERSION` in `chromedriver.rs`.
Version is the response of `https://chromedriver.storage.googleapis.com/LATEST_RELEASE`.
1. Update `DEFAULT_GECKODRIVER_VERSION` in `geckodriver.rs`.
Version is the name of the latest tag - `https://github.com/mozilla/geckodriver/releases/latest`.
1. Update the version in `Cargo.toml`.
1. Update the version number and date in `docs/index.html`.
1. Run `cargo update`.
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub mod manifest;
pub mod npm;
pub mod progressbar;
pub mod readme;
pub mod stamps;
pub mod target;
pub mod test;
pub mod wasm_opt;
Expand Down
58 changes: 58 additions & 0 deletions src/stamps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Key-value store in `*.stamps` file.
use failure::{self, ResultExt};
use std::{env, fs, path::PathBuf};

/// Get a value corresponding to the key from the JSON value.
///
/// You should use return value of function `read_stamps_file_to_json()` as `json` argument.
pub fn get_stamp_value(
key: impl AsRef<str>,
json: &serde_json::Value,
) -> Result<String, failure::Error> {
json.get(key.as_ref())
.and_then(|value| value.as_str().map(ToOwned::to_owned))
.ok_or_else(|| {
failure::err_msg(format!("cannot get stamp value for key '{}'", key.as_ref()))
})
}

/// Save the key-value pair to the store.
pub fn save_stamp_value(
key: impl Into<String>,
value: impl AsRef<str>,
) -> Result<(), failure::Error> {
let mut json = read_stamps_file_to_json().unwrap_or_else(|_| serde_json::Map::new().into());

let stamps = json
.as_object_mut()
.ok_or_else(|| failure::err_msg("stamps file doesn't contain JSON object"))?;
stamps.insert(key.into(), value.as_ref().into());

write_to_stamps_file(json)
}

/// Get the path of the `*.stamps` file that is used as the store.
pub fn get_stamps_file_path() -> Result<PathBuf, failure::Error> {
let path = env::current_exe()
.map(|path| path.with_extension("stamps"))
.context("cannot get stamps file path")?;
Ok(path)
}

/// Read `*.stamps` file and convert its content to the JSON value.
pub fn read_stamps_file_to_json() -> Result<serde_json::Value, failure::Error> {
let stamps_file_path = get_stamps_file_path()?;
let stamps_file_content =
fs::read_to_string(stamps_file_path).context("cannot find or read stamps file")?;
let json: serde_json::Value = serde_json::from_str(&stamps_file_content)
.context("stamps file doesn't contain valid JSON")?;
Ok(json)
}

fn write_to_stamps_file(json: serde_json::Value) -> Result<(), failure::Error> {
let stamps_file_path = get_stamps_file_path()?;
let pretty_json = serde_json::to_string_pretty(&json).context("JSON serialization failed")?;
fs::write(stamps_file_path, pretty_json).context("cannot write to stamps file")?;
Ok(())
}
7 changes: 7 additions & 0 deletions src/test/webdriver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ fn get_and_notify(

struct Collector(Vec<u8>);

impl Collector {
pub fn take_content(&mut self) -> Vec<u8> {
// TODO: replace with `std::mem::take` once stable
std::mem::replace(&mut self.0, Vec::default())
}
}

impl curl::easy::Handler for Collector {
fn write(&mut self, data: &[u8]) -> Result<usize, curl::easy::WriteError> {
self.0.extend_from_slice(data);
Expand Down
89 changes: 79 additions & 10 deletions src/test/webdriver/chromedriver.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
use super::{get_and_notify, Collector};
use binary_install::Cache;
use failure;
use chrono::DateTime;
use failure::{self, ResultExt};
use install::InstallMode;
use stamps;
use std::path::PathBuf;
use target;

// Keep it up to date with each `wasm-pack` release.
// https://chromedriver.storage.googleapis.com/LATEST_RELEASE
const DEFAULT_CHROMEDRIVER_VERSION: &str = "76.0.3809.126";

const CHROMEDRIVER_LAST_UPDATED_STAMP: &str = "chromedriver_last_updated";
const CHROMEDRIVER_VERSION_STAMP: &str = "chromedriver_version";

/// Get the path to an existing `chromedriver`, or install it if no existing
/// binary is found or if there is a new binary version.
pub fn get_or_install_chromedriver(
Expand Down Expand Up @@ -32,7 +41,7 @@ pub fn install_chromedriver(
bail!("chromedriver binaries are unavailable for this target")
};

let url = get_chromedriver_url(target)?;
let url = get_chromedriver_url(target);

match get_and_notify(cache, installation_allowed, "chromedriver", &url)? {
Some(path) => Ok(path),
Expand All @@ -46,26 +55,86 @@ pub fn install_chromedriver(

/// Get `chromedriver` download URL.
///
/// _Algorithm_:
/// 1. Try to open `*.stamps` file and deserialize its content to JSON object.
/// 2. Try to compare current time with the saved one.
/// 3. If the saved time is older than 1 day or something failed
/// => fetch a new version and save version & time.
/// 4. If everything failed, use the default version.
/// 5. Return URL.
///
/// _Notes:_
///
/// It returns the latest one without checking the installed `Chrome` version
/// because it's not easy to find out `Chrome` version on `Windows` -
/// https://bugs.chromium.org/p/chromium/issues/detail?id=158372
///
/// The official algorithm for `chromedriver` version selection:
/// https://chromedriver.chromium.org/downloads/version-selection
fn get_chromedriver_url(target: &str) -> Result<String, failure::Error> {
let chromedriver_version = fetch_chromedriver_version()?;
Ok(assemble_chromedriver_url(&chromedriver_version, target))
fn get_chromedriver_url(target: &str) -> String {
let fetch_and_save_version =
|| fetch_chromedriver_version().and_then(save_chromedriver_version);

let chromedriver_version = match stamps::read_stamps_file_to_json() {
Ok(json) => {
if should_load_chromedriver_version_from_stamp(&json) {
stamps::get_stamp_value(CHROMEDRIVER_VERSION_STAMP, &json)
} else {
fetch_and_save_version()
}
}
Err(_) => fetch_and_save_version(),
}
.unwrap_or_else(|error| {
log::warn!(
"Cannot load or fetch chromedriver's latest version data, \
the default version {} will be used. Error: {}",
DEFAULT_CHROMEDRIVER_VERSION,
error
);
DEFAULT_CHROMEDRIVER_VERSION.to_owned()
});
assemble_chromedriver_url(&chromedriver_version, target)
}

// ------ `get_chromedriver_url` helpers ------

fn save_chromedriver_version(version: String) -> Result<String, failure::Error> {
stamps::save_stamp_value(CHROMEDRIVER_VERSION_STAMP, &version)?;

let current_time = chrono::offset::Local::now().to_rfc3339();
stamps::save_stamp_value(CHROMEDRIVER_LAST_UPDATED_STAMP, current_time)?;

Ok(version)
}

// ------ `get_chromedriver_url` steps ------
fn should_load_chromedriver_version_from_stamp(json: &serde_json::Value) -> bool {
let last_updated = stamps::get_stamp_value(CHROMEDRIVER_LAST_UPDATED_STAMP, json)
.ok()
.and_then(|last_updated| DateTime::parse_from_rfc3339(&last_updated).ok());

match last_updated {
None => false,
Some(last_updated) => {
let current_time = chrono::offset::Local::now();
current_time.signed_duration_since(last_updated).num_hours() < 24
}
}
}

fn fetch_chromedriver_version() -> Result<String, failure::Error> {
let mut handle = curl::easy::Easy2::new(Collector(Vec::new()));
handle.url("https://chromedriver.storage.googleapis.com/LATEST_RELEASE")?;
handle.perform()?;
handle
.url("https://chromedriver.storage.googleapis.com/LATEST_RELEASE")
.context("URL to fetch chromedriver's LATEST_RELEASE is invalid")?;
handle
.perform()
.context("fetching of chromedriver's LATEST_RELEASE failed")?;

let contents = handle.get_ref();
Ok(String::from_utf8_lossy(&contents.0).into_owned())
let content = handle.get_mut().take_content();
let version =
String::from_utf8(content).context("chromedriver's LATEST_RELEASE is not valid UTF-8")?;
Ok(version)
}

fn assemble_chromedriver_url(chromedriver_version: &str, target: &str) -> String {
Expand Down
Loading

0 comments on commit a39a278

Please sign in to comment.