use crate::bandwidth_loop::BandwidthLoopState;
use crate::common;
use crate::error::SpeedtestError;
use crate::progress::{SpeedProgress, no_color};
use crate::types::Server;
use owo_colors::OwoColorize;
use reqwest::Client;
use std::sync::Arc;
#[must_use]
pub fn build_upload_url(server_url: &str) -> String {
format!("{server_url}/upload")
}
fn generate_upload_data(size: usize) -> Vec<u8> {
let mut data = vec![0u8; size];
for (i, byte) in data.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
data
}
const UPLOAD_TEST_ROUNDS: usize = 4;
const ESTIMATED_UPLOAD_BYTES: u64 = 4_000_000;
pub async fn upload_test(
client: &Client,
server: &Server,
single: bool,
progress: Arc<SpeedProgress>,
) -> Result<(f64, f64, u64, Vec<f64>), SpeedtestError> {
let concurrent_uploads = common::determine_stream_count(single);
let state = Arc::new(BandwidthLoopState::new(ESTIMATED_UPLOAD_BYTES, progress));
let upload_data = generate_upload_data(200_000);
let mut handles = Vec::new();
for _ in 0..concurrent_uploads {
let client = client.clone();
let server_url = server.url.clone();
let data = upload_data.clone();
let state = Arc::clone(&state);
let handle = tokio::spawn(async move {
let mut uploaded_bytes = 0u64;
for _ in 0..UPLOAD_TEST_ROUNDS {
let upload_url = build_upload_url(&server_url);
if let Ok(response) = client.post(&upload_url).body(data.clone()).send().await {
if response.status().is_success() {
let chunk = data.len() as u64;
uploaded_bytes += chunk;
state.record_bytes(chunk);
}
}
}
uploaded_bytes
});
handles.push(handle);
}
for (i, handle) in handles.into_iter().enumerate() {
if let Err(e) = handle.await {
let msg = format!("Warning: upload task {i} failed: {e}");
if no_color() {
eprintln!("\n{msg}");
} else {
eprintln!("\n{}", msg.yellow().bold());
}
}
}
let final_result = state.finish();
Ok((
final_result.avg_bps,
final_result.peak_bps,
final_result.total_bytes,
final_result.speed_samples,
))
}
#[cfg(test)]
mod tests {
use crate::common;
use super::*;
#[test]
fn test_upload_bandwidth_calculation() {
let result = common::calculate_bandwidth(1_000_000, 2.0);
assert_eq!(result, 4_000_000.0);
}
#[test]
fn test_upload_bandwidth_zero_elapsed() {
let result = common::calculate_bandwidth(1_000_000, 0.0);
assert_eq!(result, 0.0);
}
#[test]
fn test_upload_concurrent_count_single() {
assert_eq!(common::determine_stream_count(true), 1);
}
#[test]
fn test_upload_concurrent_count_multiple() {
assert_eq!(common::determine_stream_count(false), 4);
}
#[test]
fn test_upload_url_generation() {
let url = build_upload_url("http://server.example.com");
assert!(url.ends_with("/upload"));
}
#[test]
fn test_upload_url_generation_full_path() {
let url = build_upload_url("http://server.example.com/speedtest");
assert_eq!(url, "http://server.example.com/speedtest/upload");
}
#[test]
fn test_generate_upload_data_size() {
let data = generate_upload_data(1000);
assert_eq!(data.len(), 1000);
}
#[test]
fn test_generate_upload_data_pattern() {
let data = generate_upload_data(300);
for (i, &byte) in data.iter().enumerate() {
assert_eq!(byte, (i % 256) as u8);
}
}
#[test]
fn test_generate_upload_data_wraps_at_256() {
let data = generate_upload_data(512);
assert_eq!(data[0], 0u8);
assert_eq!(data[255], 255u8);
assert_eq!(data[256], 0u8);
assert_eq!(data[511], 255u8);
}
#[test]
fn test_generate_upload_data_empty() {
let data = generate_upload_data(0);
assert!(data.is_empty());
}
#[test]
fn test_upload_data_size_constant() {
let data = generate_upload_data(200_000);
assert_eq!(data.len(), 200_000);
}
}