1use crate::bandwidth_loop::BandwidthLoopState;
11use crate::common;
12use crate::error::SpeedtestError;
13use crate::progress::{SpeedProgress, no_color};
14use crate::types::Server;
15use owo_colors::OwoColorize;
16use reqwest::Client;
17use std::sync::Arc;
18
19#[must_use]
21pub fn build_upload_url(server_url: &str) -> String {
22 format!("{server_url}/upload")
23}
24
25fn generate_upload_data(size: usize) -> Vec<u8> {
26 let mut data = vec![0u8; size];
27 for (i, byte) in data.iter_mut().enumerate() {
28 *byte = (i % 256) as u8;
29 }
30 data
31}
32
33const UPLOAD_TEST_ROUNDS: usize = 4;
35
36const ESTIMATED_UPLOAD_BYTES: u64 = 4_000_000; pub async fn upload_test(
47 client: &Client,
48 server: &Server,
49 single: bool,
50 progress: Arc<SpeedProgress>,
51) -> Result<(f64, f64, u64, Vec<f64>), SpeedtestError> {
52 let concurrent_uploads = common::determine_stream_count(single);
53 let state = Arc::new(BandwidthLoopState::new(ESTIMATED_UPLOAD_BYTES, progress));
54 let upload_data = generate_upload_data(200_000); let mut handles = Vec::new();
57
58 for _ in 0..concurrent_uploads {
59 let client = client.clone();
60 let server_url = server.url.clone();
61 let data = upload_data.clone();
62 let state = Arc::clone(&state);
63
64 let handle = tokio::spawn(async move {
65 let mut uploaded_bytes = 0u64;
66
67 for _ in 0..UPLOAD_TEST_ROUNDS {
68 let upload_url = build_upload_url(&server_url);
69
70 if let Ok(response) = client.post(&upload_url).body(data.clone()).send().await {
71 if response.status().is_success() {
72 let chunk = data.len() as u64;
73 uploaded_bytes += chunk;
74 state.record_bytes(chunk);
75 }
76 }
77 }
78
79 uploaded_bytes
80 });
81
82 handles.push(handle);
83 }
84
85 for (i, handle) in handles.into_iter().enumerate() {
88 if let Err(e) = handle.await {
89 let msg = format!("Warning: upload task {i} failed: {e}");
90 if no_color() {
91 eprintln!("\n{msg}");
92 } else {
93 eprintln!("\n{}", msg.yellow().bold());
94 }
95 }
96 }
97
98 let final_result = state.finish();
99 Ok((
100 final_result.avg_bps,
101 final_result.peak_bps,
102 final_result.total_bytes,
103 final_result.speed_samples,
104 ))
105}
106
107#[cfg(test)]
108mod tests {
109 use crate::common;
110
111 use super::*;
112
113 #[test]
114 fn test_upload_bandwidth_calculation() {
115 let result = common::calculate_bandwidth(1_000_000, 2.0);
116 assert_eq!(result, 4_000_000.0);
117 }
118
119 #[test]
120 fn test_upload_bandwidth_zero_elapsed() {
121 let result = common::calculate_bandwidth(1_000_000, 0.0);
122 assert_eq!(result, 0.0);
123 }
124
125 #[test]
126 fn test_upload_concurrent_count_single() {
127 assert_eq!(common::determine_stream_count(true), 1);
128 }
129
130 #[test]
131 fn test_upload_concurrent_count_multiple() {
132 assert_eq!(common::determine_stream_count(false), 4);
133 }
134
135 #[test]
136 fn test_upload_url_generation() {
137 let url = build_upload_url("http://server.example.com");
138 assert!(url.ends_with("/upload"));
139 }
140
141 #[test]
142 fn test_upload_url_generation_full_path() {
143 let url = build_upload_url("http://server.example.com/speedtest");
144 assert_eq!(url, "http://server.example.com/speedtest/upload");
145 }
146
147 #[test]
148 fn test_generate_upload_data_size() {
149 let data = generate_upload_data(1000);
150 assert_eq!(data.len(), 1000);
151 }
152
153 #[test]
154 fn test_generate_upload_data_pattern() {
155 let data = generate_upload_data(300);
156 for (i, &byte) in data.iter().enumerate() {
157 assert_eq!(byte, (i % 256) as u8);
158 }
159 }
160
161 #[test]
162 fn test_generate_upload_data_wraps_at_256() {
163 let data = generate_upload_data(512);
164 assert_eq!(data[0], 0u8);
165 assert_eq!(data[255], 255u8);
166 assert_eq!(data[256], 0u8);
167 assert_eq!(data[511], 255u8);
168 }
169
170 #[test]
171 fn test_generate_upload_data_empty() {
172 let data = generate_upload_data(0);
173 assert!(data.is_empty());
174 }
175
176 #[test]
177 fn test_upload_data_size_constant() {
178 let data = generate_upload_data(200_000);
180 assert_eq!(data.len(), 200_000);
181 }
182}