-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create smoke tests for win.rustup.rs
The distribution win.rustup.rs provides convenient access to the latest Rustup installers for Windows. The distribution has two paths, one for each architecture that is currently supported. The smoke tests send a `HEAD` request to both and check that the response sets the correct headers to indicate the payload in its body. Because the Rustup installer changes with every release, we are not checking the `Content-Length` of the response or the installer's SHA checksum.
- Loading branch information
Showing
5 changed files
with
484 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
//! Configuration to test `win.rustup.rs` | ||
use getset::Getters; | ||
#[cfg(test)] | ||
use typed_builder::TypedBuilder; | ||
|
||
use crate::environment::Environment; | ||
|
||
/// Configuration to test `win.rustup.rs` | ||
/// | ||
/// `win.rustup.rs` is only served by CloudFront, thus only the CloudFront URL is needed. | ||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Getters)] | ||
#[cfg_attr(test, derive(TypedBuilder))] | ||
pub struct Config { | ||
/// The URL for the CloudFront CDN | ||
#[getset(get = "pub")] | ||
cloudfront_url: String, | ||
} | ||
|
||
impl Config { | ||
/// Return the configuration for the given environment | ||
pub fn for_env(env: Environment) -> Self { | ||
match env { | ||
Environment::Staging => Self { | ||
cloudfront_url: "https://dev-win.rustup.rs".into(), | ||
}, | ||
Environment::Production => Self { | ||
cloudfront_url: "https://win.rustup.rs".into(), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::test_utils::*; | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn trait_send() { | ||
assert_send::<Config>(); | ||
} | ||
|
||
#[test] | ||
fn trait_sync() { | ||
assert_sync::<Config>(); | ||
} | ||
|
||
#[test] | ||
fn trait_unpin() { | ||
assert_unpin::<Config>(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
//! Test `win.rustup.rs/i686` | ||
use async_trait::async_trait; | ||
|
||
use crate::rustup::win_rustup_rs::request_installer_and_expect_attachment; | ||
use crate::test::{Test, TestResult}; | ||
|
||
use super::config::Config; | ||
|
||
/// The name of the test | ||
const NAME: &str = "i686"; | ||
|
||
/// Test that `win.rustup.rs/i686` serves the Rustup installer | ||
/// | ||
/// This test requests the installer from `win.rustup.rs/i686` and expects the response to contain | ||
/// the correct file as an attachment. | ||
pub struct I686<'a> { | ||
/// Configuration for this test | ||
config: &'a Config, | ||
} | ||
|
||
impl<'a> I686<'a> { | ||
/// Create a new instance of the test | ||
pub fn new(config: &'a Config) -> Self { | ||
Self { config } | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl<'a> Test for I686<'a> { | ||
async fn run(&self) -> TestResult { | ||
request_installer_and_expect_attachment( | ||
NAME, | ||
&format!("{}/i686", self.config.cloudfront_url()), | ||
) | ||
.await | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::test_utils::*; | ||
|
||
use super::*; | ||
|
||
#[tokio::test] | ||
async fn succeeds_with_http_200_and_attachment() { | ||
let mut server = mockito::Server::new_async().await; | ||
|
||
let config = Config::builder().cloudfront_url(server.url()).build(); | ||
|
||
let mock = server | ||
.mock("HEAD", "/i686") | ||
.with_status(200) | ||
.with_header("Content-Type", "application/x-msdownload") | ||
.with_header( | ||
"Content-Disposition", | ||
r#"attachment; filename="rustup-init.exe""#, | ||
) | ||
.create(); | ||
|
||
let result = I686::new(&config).run().await; | ||
|
||
// Assert that the mock was called | ||
mock.assert(); | ||
|
||
assert_eq!(&None, result.message()); | ||
assert!(result.success()); | ||
} | ||
|
||
#[tokio::test] | ||
async fn fails_without_content_type() { | ||
let mut server = mockito::Server::new_async().await; | ||
|
||
let config = Config::builder().cloudfront_url(server.url()).build(); | ||
|
||
let mock = server | ||
.mock("HEAD", "/i686") | ||
.with_status(200) | ||
.with_header( | ||
"Content-Disposition", | ||
r#"attachment; filename="rustup-init.exe""#, | ||
) | ||
.create(); | ||
|
||
let result = I686::new(&config).run().await; | ||
|
||
// Assert that the mock was called | ||
mock.assert(); | ||
|
||
let message = result.message().as_ref().unwrap(); | ||
|
||
assert!(message.contains("Content-Type")); | ||
assert!(!result.success()); | ||
} | ||
|
||
#[tokio::test] | ||
async fn fails_without_content_disposition() { | ||
let mut server = mockito::Server::new_async().await; | ||
|
||
let config = Config::builder().cloudfront_url(server.url()).build(); | ||
|
||
let mock = server | ||
.mock("HEAD", "/i686") | ||
.with_status(200) | ||
.with_header("Content-Type", "application/x-msdownload") | ||
.create(); | ||
|
||
let result = I686::new(&config).run().await; | ||
|
||
// Assert that the mock was called | ||
mock.assert(); | ||
|
||
let message = result.message().as_ref().unwrap(); | ||
|
||
assert!(message.contains("Content-Disposition")); | ||
assert!(!result.success()); | ||
} | ||
|
||
#[test] | ||
fn trait_send() { | ||
assert_send::<I686>(); | ||
} | ||
|
||
#[test] | ||
fn trait_sync() { | ||
assert_sync::<I686>(); | ||
} | ||
|
||
#[test] | ||
fn trait_unpin() { | ||
assert_unpin::<I686>(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
//! Serve Rustup for Windows from short URLs | ||
//! | ||
//! This module tests the three artifacts that can be downloaded from `win.rustup.rs`. | ||
use std::fmt::{Display, Formatter}; | ||
|
||
use async_trait::async_trait; | ||
use reqwest::Client; | ||
|
||
use crate::environment::Environment; | ||
use crate::test::{Test, TestGroup, TestGroupResult, TestResult}; | ||
|
||
pub use self::config::Config; | ||
pub use self::i686::I686; | ||
pub use self::x86_64::X86_64; | ||
|
||
mod config; | ||
mod i686; | ||
mod x86_64; | ||
|
||
/// The name of the test group | ||
const NAME: &str = "win.rustup.rs"; | ||
|
||
/// Serve Rustup for Windows from short URLs | ||
/// | ||
/// This test group tests the three artifacts that can be downloaded from `win.rustup.rs`. Each path | ||
/// on the domain represents a specific architecture and serves the installer as an attachment. | ||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] | ||
pub struct WinRustupRs { | ||
/// Configuration for the test group | ||
config: Config, | ||
} | ||
|
||
impl WinRustupRs { | ||
/// Create a new instance of the test group | ||
pub fn new(env: Environment) -> Self { | ||
Self { | ||
config: Config::for_env(env), | ||
} | ||
} | ||
} | ||
|
||
impl Display for WinRustupRs { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", NAME) | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl TestGroup for WinRustupRs { | ||
async fn run(&self) -> TestGroupResult { | ||
let tests: Vec<Box<dyn Test>> = vec![ | ||
Box::new(I686::new(&self.config)), | ||
Box::new(X86_64::new(&self.config)), | ||
]; | ||
|
||
let mut results = Vec::new(); | ||
for test in tests { | ||
results.push(test.run().await); | ||
} | ||
|
||
TestGroupResult::builder() | ||
.name(NAME) | ||
.results(results) | ||
.build() | ||
} | ||
} | ||
|
||
/// Request an artifact from `win.rustup.rs` and expect the correct response | ||
/// | ||
/// This function requests the given path and expects the response to contain the correct file as an | ||
/// attachment. | ||
async fn request_installer_and_expect_attachment(name: &'static str, url: &str) -> TestResult { | ||
let test_result = TestResult::builder().name(name).success(false); | ||
|
||
let response = match Client::builder() | ||
.build() | ||
.expect("failed to build reqwest client") | ||
.head(url) | ||
.send() | ||
.await | ||
{ | ||
Ok(response) => response, | ||
Err(error) => { | ||
return test_result.message(Some(error.to_string())).build(); | ||
} | ||
}; | ||
|
||
if response.status() != 200 { | ||
return test_result | ||
.message(Some(format!( | ||
"Expected HTTP 200, got HTTP {}", | ||
response.status() | ||
))) | ||
.build(); | ||
} | ||
|
||
if !response | ||
.headers() | ||
.get("Content-Type") | ||
.and_then(|header| header.to_str().ok()) | ||
.is_some_and(|header| header == "application/x-msdownload") | ||
{ | ||
return test_result | ||
.message(Some( | ||
"Expected the Content-Type header to be set to 'application/x-msdownload'".into(), | ||
)) | ||
.build(); | ||
} | ||
|
||
if !response | ||
.headers() | ||
.get("Content-Disposition") | ||
.and_then(|header| header.to_str().ok()) | ||
.is_some_and(|header| header.contains(r#"attachment; filename="rustup-init.exe""#)) | ||
{ | ||
return test_result | ||
.message(Some( | ||
"Expected the Content-Disposition header to indicate an attachment".into(), | ||
)) | ||
.build(); | ||
} | ||
|
||
TestResult::builder().name(name).success(true).build() | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use pretty_assertions::assert_eq; | ||
|
||
use crate::test_utils::*; | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn trait_display() { | ||
let rustup_sh = WinRustupRs::new(Environment::Staging); | ||
|
||
assert_eq!("win.rustup.rs", rustup_sh.to_string()); | ||
} | ||
|
||
#[test] | ||
fn trait_send() { | ||
assert_send::<WinRustupRs>(); | ||
} | ||
|
||
#[test] | ||
fn trait_sync() { | ||
assert_sync::<WinRustupRs>(); | ||
} | ||
|
||
#[test] | ||
fn trait_unpin() { | ||
assert_unpin::<WinRustupRs>(); | ||
} | ||
} |
Oops, something went wrong.