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 build_upload_url(server_url: &str) -> String {
22 ServerEndpoints::from_server_url(server_url)
23 .upload()
24 .to_string()
25}
26
27static UPLOAD_PAYLOAD: std::sync::LazyLock<bytes::Bytes> = std::sync::LazyLock::new(|| {
30 let mut data = vec![0u8; 200_000];
31 for (i, byte) in data.iter_mut().enumerate() {
32 *byte = (i % 256) as u8;
33 }
34 bytes::Bytes::from(data)
35});
36
37#[cfg(test)]
39fn generate_upload_data(size: usize) -> Vec<u8> {
40 let mut data = vec![0u8; size];
41 for (i, byte) in data.iter_mut().enumerate() {
42 *byte = (i % 256) as u8;
43 }
44 data
45}
46
47pub async fn run(
55 client: &Client,
56 server: &Server,
57 single: bool,
58 progress: Arc<Tracker>,
59) -> Result<(f64, f64, u64, Vec<f64>), Error> {
60 let config = TestConfig::default();
61 let stream_count = TestConfig::stream_count_for(single);
62 let upload_data: bytes::Bytes = (*UPLOAD_PAYLOAD).clone();
63
64 let result = run_concurrent_streams(
65 config.estimated_upload_bytes,
66 stream_count,
67 progress,
68 "upload",
69 |_, state, sample_interval| {
70 let client = client.clone();
71 let server_url = Arc::new(server.url.clone());
72 let data = Arc::new(upload_data.clone());
73 tokio::spawn(async move {
74 for _ in 0..config.upload_rounds {
75 let upload_url = build_upload_url(&server_url);
76
77 let response = client
78 .post(&upload_url)
79 .body((*data).clone())
80 .send()
81 .await
82 .map_err(Error::UploadTest)?;
83
84 if !response.status().is_success() {
85 return Err(Error::UploadFailure(format!(
86 "server returned {} for {upload_url}",
87 response.status()
88 )));
89 }
90
91 let chunk = u64::try_from(data.len()).unwrap_or(u64::MAX);
92 state.record_bytes(chunk, sample_interval);
93 }
94 Ok(())
95 })
96 },
97 )
98 .await?;
99
100 Ok((
101 result.avg_bps,
102 result.peak_bps,
103 result.total_bytes,
104 result.speed_samples,
105 ))
106}
107
108#[cfg(test)]
109mod tests {
110 use crate::common;
111 use crate::test_config::TestConfig;
112
113 use super::*;
114
115 #[test]
116 fn test_upload_bandwidth_calculation() {
117 let result = common::calculate_bandwidth(1_000_000, 2.0);
118 assert!((result - 4_000_000.0).abs() < f64::EPSILON);
119 }
120
121 #[test]
122 fn test_upload_bandwidth_zero_elapsed() {
123 let result = common::calculate_bandwidth(1_000_000, 0.0);
124 assert!(result.abs() < f64::EPSILON);
125 }
126
127 #[test]
128 fn test_upload_concurrent_count_single() {
129 assert_eq!(TestConfig::stream_count_for(true), 1);
130 }
131
132 #[test]
133 fn test_upload_concurrent_count_multiple() {
134 assert_eq!(TestConfig::stream_count_for(false), 4);
135 }
136
137 #[test]
138 fn test_upload_url_generation() {
139 let url = build_upload_url("http://server.example.com");
140 assert!(url.ends_with("/upload.php"));
141 }
142
143 #[test]
144 fn test_upload_url_generation_full_path() {
145 let url = build_upload_url("http://server.example.com/speedtest/upload.php");
146 assert_eq!(url, "http://server.example.com/speedtest/upload.php");
147 }
148
149 #[test]
150 fn test_generate_upload_data_size() {
151 let data = generate_upload_data(1000);
152 assert_eq!(data.len(), 1000);
153 }
154
155 #[test]
156 fn test_generate_upload_data_pattern() {
157 let data = generate_upload_data(300);
158 for (i, &byte) in data.iter().enumerate() {
159 assert_eq!(byte, (i % 256) as u8);
160 }
161 }
162
163 #[test]
164 fn test_generate_upload_data_wraps_at_256() {
165 let data = generate_upload_data(512);
166 assert_eq!(data[0], 0u8);
167 assert_eq!(data[255], 255u8);
168 assert_eq!(data[256], 0u8);
169 assert_eq!(data[511], 255u8);
170 }
171
172 #[test]
173 fn test_generate_upload_data_empty() {
174 let data = generate_upload_data(0);
175 assert!(data.is_empty());
176 }
177
178 #[test]
179 fn test_upload_data_size_constant() {
180 let data = generate_upload_data(200_000);
182 assert_eq!(data.len(), 200_000);
183 }
184
185 #[test]
186 fn test_upload_payload_lazy_init() {
187 assert_eq!(UPLOAD_PAYLOAD.len(), 200_000);
189 assert_eq!(UPLOAD_PAYLOAD[0], 0u8);
190 assert_eq!(UPLOAD_PAYLOAD[255], 255u8);
191 assert_eq!(UPLOAD_PAYLOAD[256], 0u8);
192 }
193}