Skip to content

Commit a6e99c8

Browse files
authored
Add smoke test in examples. (fortanix#85)
This loads a list of top domain names (e.g. from https://tranco-list.eu/) and tries to fetch them all, in parallel. This can be used to exercise ureq and find panics.
1 parent 3014f58 commit a6e99c8

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,9 @@ native-tls = { version = "0.2", optional = true }
4141

4242
[dev-dependencies]
4343
serde = { version = "1", features = ["derive"] }
44+
rayon = "1.3.0"
45+
rayon-core = "1.7.0"
46+
chrono = "0.4.11"
47+
48+
[[example]]
49+
name = "smoke-test"

examples/smoke-test/main.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use chrono::Local;
2+
use rayon::prelude::*;
3+
use rayon_core;
4+
5+
use std::io::{self, BufRead, BufReader, Read};
6+
use std::iter::Iterator;
7+
use std::time::Duration;
8+
use std::{env, error, fmt, result};
9+
10+
use ureq;
11+
12+
#[derive(Debug)]
13+
struct Oops(String);
14+
15+
impl From<io::Error> for Oops {
16+
fn from(e: io::Error) -> Oops {
17+
Oops(e.to_string())
18+
}
19+
}
20+
21+
impl From<&ureq::Error> for Oops {
22+
fn from(e: &ureq::Error) -> Oops {
23+
Oops(e.to_string())
24+
}
25+
}
26+
27+
impl From<rayon_core::ThreadPoolBuildError> for Oops {
28+
fn from(e: rayon_core::ThreadPoolBuildError) -> Oops {
29+
Oops(e.to_string())
30+
}
31+
}
32+
33+
impl error::Error for Oops {}
34+
35+
impl fmt::Display for Oops {
36+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37+
self.0.fmt(f)
38+
}
39+
}
40+
41+
type Result<T> = result::Result<T, Oops>;
42+
43+
fn get(agent: &ureq::Agent, url: &String) -> Result<Vec<u8>> {
44+
let response = agent
45+
.get(url)
46+
.timeout_connect(5_000)
47+
.timeout(Duration::from_secs(20))
48+
.call();
49+
if let Some(err) = response.synthetic_error() {
50+
return Err(err.into());
51+
}
52+
let mut reader = response.into_reader();
53+
let mut bytes = vec![];
54+
reader.read_to_end(&mut bytes)?;
55+
Ok(bytes)
56+
}
57+
58+
fn get_and_write(agent: &ureq::Agent, url: &String) -> Result<()> {
59+
println!("🕷️ {} {}", Local::now(), url);
60+
match get(agent, url) {
61+
Ok(_) => println!("✔️ {} {}", Local::now(), url),
62+
Err(e) => println!("⚠️ {} {} {}", Local::now(), url, e),
63+
}
64+
Ok(())
65+
}
66+
67+
fn get_many(urls: Vec<String>, simultaneous_fetches: usize) -> Result<()> {
68+
let agent = ureq::Agent::default().build();
69+
let pool = rayon::ThreadPoolBuilder::new()
70+
.num_threads(simultaneous_fetches)
71+
.build()?;
72+
pool.scope(|_| {
73+
urls.par_iter().map(|u| get_and_write(&agent, u)).count();
74+
});
75+
Ok(())
76+
}
77+
78+
fn main() -> Result<()> {
79+
let args = env::args();
80+
if args.len() == 1 {
81+
println!(
82+
r##"Usage: {:#?} top-1m.csv
83+
84+
Where top-1m.csv is a simple, unquoted CSV containing two fields, a rank and
85+
a domain name. For instance you can get such a list from https://tranco-list.eu/.
86+
87+
For each domain, this program will attempt to GET four URLs: The domain name
88+
name with HTTP and HTTPS, and with and without a www prefix. It will fetch
89+
using 50 threads concurrently.
90+
"##,
91+
env::current_exe()?
92+
);
93+
return Ok(());
94+
}
95+
let file = std::fs::File::open(args.skip(1).next().unwrap())?;
96+
let bufreader = BufReader::new(file);
97+
let mut urls = vec![];
98+
for line in bufreader.lines() {
99+
let domain = line?.rsplit(",").next().unwrap().to_string();
100+
urls.push(format!("http://{}/", domain));
101+
urls.push(format!("https://{}/", domain));
102+
urls.push(format!("http://www.{}/", domain));
103+
urls.push(format!("https://www.{}/", domain));
104+
}
105+
get_many(urls, 50)?;
106+
Ok(())
107+
}

0 commit comments

Comments
 (0)