1use crate::bandwidth_loop::run_concurrent_streams;
11use crate::endpoints::ServerEndpoints;
12use crate::error::Error;
13use crate::progress::Tracker;
14use crate::test_config::TestConfig;
15use crate::types::Server;
16use reqwest::Client;
17use std::sync::Arc;
18
19#[must_use]
21pub fn extract_base_url(url: &str) -> String {
22 ServerEndpoints::from_server_url(url).base().to_string()
23}
24
25#[must_use]
27pub fn build_test_url(server_url: &str, file_index: usize) -> String {
28 let sizes = ["2000x2000", "3000x3000", "3500x3500", "4000x4000"];
29 let size = sizes[file_index % sizes.len()];
30 ServerEndpoints::from_server_url(server_url).download_asset(&format!("random{size}.jpg"))
31}
32
33use futures_util::StreamExt;
34
35pub async fn run(
44 client: &Client,
45 server: &Server,
46 single: bool,
47 progress: Arc<Tracker>,
48) -> Result<(f64, f64, u64, Vec<f64>), Error> {
49 let config = TestConfig::default();
50 let stream_count = TestConfig::stream_count_for(single);
51
52 let result = run_concurrent_streams(
53 config.estimated_download_bytes,
54 stream_count,
55 progress,
56 "download",
57 |_, state, sample_interval| {
58 let client = client.clone();
59 let server_url = Arc::new(server.url.clone());
60 tokio::spawn(async move {
61 for j in 0..config.download_rounds {
62 let test_url = build_test_url(&server_url, j);
63
64 let response = client
65 .get(&test_url)
66 .send()
67 .await
68 .map_err(Error::DownloadTest)?;
69
70 if !response.status().is_success() {
71 return Err(Error::DownloadFailure(format!(
72 "server returned {} for {test_url}",
73 response.status()
74 )));
75 }
76
77 let mut stream = response.bytes_stream();
78 while let Some(item) = stream.next().await {
79 let chunk = item.map_err(Error::DownloadTest)?;
80 let len = u64::try_from(chunk.len()).unwrap_or(u64::MAX);
81 if len > 0 {
82 state.record_bytes(len, sample_interval);
83 }
84 }
85 }
86 Ok(())
87 })
88 },
89 )
90 .await?;
91
92 Ok((
93 result.avg_bps,
94 result.peak_bps,
95 result.total_bytes,
96 result.speed_samples,
97 ))
98}
99
100#[cfg(test)]
101mod tests {
102 use crate::common;
103 use crate::test_config::TestConfig;
104
105 use super::*;
106
107 #[test]
108 fn test_download_bandwidth_calculation() {
109 let result = common::calculate_bandwidth(10_000_000, 2.0);
110 assert!((result - 40_000_000.0).abs() < f64::EPSILON);
111 }
112
113 #[test]
114 fn test_download_bandwidth_zero_elapsed() {
115 let result = common::calculate_bandwidth(10_000_000, 0.0);
116 assert!(result.abs() < f64::EPSILON);
117 }
118
119 #[test]
120 fn test_download_concurrent_streams_single() {
121 assert_eq!(TestConfig::stream_count_for(true), 1);
122 }
123
124 #[test]
125 fn test_download_concurrent_streams_multiple() {
126 assert_eq!(TestConfig::stream_count_for(false), 4);
127 }
128
129 #[test]
130 fn test_download_url_generation() {
131 let server_url = "http://server.example.com/speedtest/upload.php";
132 let test_url = build_test_url(server_url, 0);
133 assert_eq!(
134 test_url,
135 "http://server.example.com/speedtest/random2000x2000.jpg"
136 );
137 }
138
139 #[test]
140 fn test_download_url_generation_cycles() {
141 let server_url = "http://server.example.com/speedtest/upload.php";
142 let url_0 = build_test_url(server_url, 0);
143 let url_4 = build_test_url(server_url, 4);
144 assert_eq!(url_0, url_4);
145 }
146
147 #[test]
148 fn test_download_url_generation_all_sizes() {
149 let server_url = "http://server.example.com/speedtest/upload.php";
150 let expected = [
151 "http://server.example.com/speedtest/random2000x2000.jpg",
152 "http://server.example.com/speedtest/random3000x3000.jpg",
153 "http://server.example.com/speedtest/random3500x3500.jpg",
154 "http://server.example.com/speedtest/random4000x4000.jpg",
155 ];
156
157 for (i, expected_url) in expected.iter().enumerate() {
158 assert_eq!(build_test_url(server_url, i), *expected_url);
159 }
160 }
161
162 #[test]
163 fn test_extract_base_url() {
164 let url = "http://server.example.com:8080/speedtest/upload.php";
165 assert_eq!(
166 extract_base_url(url),
167 "http://server.example.com:8080/speedtest"
168 );
169 }
170
171 #[test]
172 fn test_extract_base_url_no_suffix() {
173 let url = "http://server.example.com/speedtest";
174 assert_eq!(extract_base_url(url), "http://server.example.com/speedtest");
175 }
176
177 #[test]
178 fn test_extract_base_url_different_path() {
179 let url = "https://cdn.speedtest.net/upload.php";
180 assert_eq!(extract_base_url(url), "https://cdn.speedtest.net");
181 }
182
183 #[test]
184 fn test_estimated_download_bytes_from_config() {
185 let config = TestConfig::default();
187 assert!(config.estimated_download_bytes > 10_000_000);
188 assert!(config.estimated_download_bytes < 20_000_000);
189 }
190
191 #[test]
192 fn test_sample_interval_constant() {
193 const _: () = assert!(crate::bandwidth_loop::SAMPLE_INTERVAL_MS == 50);
195 }
196}