use crate::bandwidth_loop::run_concurrent_streams;
use crate::endpoints::ServerEndpoints;
use crate::error::Error;
use crate::progress::Tracker;
use crate::test_config::TestConfig;
use crate::types::Server;
use reqwest::Client;
use std::sync::Arc;
#[must_use]
pub fn build_upload_url(server_url: &str) -> String {
ServerEndpoints::from_server_url(server_url)
.upload()
.to_string()
}
static UPLOAD_PAYLOAD: std::sync::LazyLock<bytes::Bytes> = std::sync::LazyLock::new(|| {
let mut data = vec![0u8; 200_000];
for (i, byte) in data.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
bytes::Bytes::from(data)
});
#[cfg(test)]
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
}
pub async fn run(
client: &Client,
server: &Server,
single: bool,
progress: Arc<Tracker>,
) -> Result<(f64, f64, u64, Vec<f64>), Error> {
let config = TestConfig::default();
let stream_count = TestConfig::stream_count_for(single);
let upload_data: bytes::Bytes = (*UPLOAD_PAYLOAD).clone();
let result = run_concurrent_streams(
config.estimated_upload_bytes,
stream_count,
progress,
"upload",
|_, state, sample_interval| {
let client = client.clone();
let server_url = Arc::new(server.url.clone());
let data = Arc::new(upload_data.clone());
tokio::spawn(async move {
for _ in 0..config.upload_rounds {
let upload_url = build_upload_url(&server_url);
let response = client
.post(&upload_url)
.body((*data).clone())
.send()
.await
.map_err(Error::UploadTest)?;
if !response.status().is_success() {
return Err(Error::UploadFailure(format!(
"server returned {} for {upload_url}",
response.status()
)));
}
let chunk = u64::try_from(data.len()).unwrap_or(u64::MAX);
state.record_bytes(chunk, sample_interval);
}
Ok(())
})
},
)
.await?;
Ok((
result.avg_bps,
result.peak_bps,
result.total_bytes,
result.speed_samples,
))
}
#[cfg(test)]
mod tests {
use crate::common;
use crate::test_config::TestConfig;
use super::*;
#[test]
fn test_upload_bandwidth_calculation() {
let result = common::calculate_bandwidth(1_000_000, 2.0);
assert!((result - 4_000_000.0).abs() < f64::EPSILON);
}
#[test]
fn test_upload_bandwidth_zero_elapsed() {
let result = common::calculate_bandwidth(1_000_000, 0.0);
assert!(result.abs() < f64::EPSILON);
}
#[test]
fn test_upload_concurrent_count_single() {
assert_eq!(TestConfig::stream_count_for(true), 1);
}
#[test]
fn test_upload_concurrent_count_multiple() {
assert_eq!(TestConfig::stream_count_for(false), 4);
}
#[test]
fn test_upload_url_generation() {
let url = build_upload_url("http://server.example.com");
assert!(url.ends_with("/upload.php"));
}
#[test]
fn test_upload_url_generation_full_path() {
let url = build_upload_url("http://server.example.com/speedtest/upload.php");
assert_eq!(url, "http://server.example.com/speedtest/upload.php");
}
#[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);
}
#[test]
fn test_upload_payload_lazy_init() {
assert_eq!(UPLOAD_PAYLOAD.len(), 200_000);
assert_eq!(UPLOAD_PAYLOAD[0], 0u8);
assert_eq!(UPLOAD_PAYLOAD[255], 255u8);
assert_eq!(UPLOAD_PAYLOAD[256], 0u8);
}
}