Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/boxplot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,85 @@ pub(crate) fn render_plot(minima: f64, q1: f64, median: f64, q3: f64, maxima: f6

plot
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_generate_axis_labels() {
let labels = generate_axis_labels(0.0, 100.0);
assert!(labels.starts_with("0.00"));
assert!(labels.ends_with("100.00"));
assert!(labels.contains("50.00"));
assert_eq!(labels.len(), PLOT_WIDTH);
}

#[test]
fn test_generate_axis_labels_negative() {
let labels = generate_axis_labels(-50.0, 50.0);
assert!(labels.starts_with("-50.00"));
assert!(labels.ends_with("50.00"));
assert!(labels.contains("0.00"));
}

#[test]
fn test_render_plot_basic() {
let plot = render_plot(0.0, 25.0, 50.0, 75.0, 100.0);

// Should contain boxplot characters
assert!(plot.contains('|'));
assert!(plot.contains('-'));
assert!(plot.contains('='));
assert!(plot.contains(':'));

// Should contain axis labels
assert!(plot.contains("0.00"));
assert!(plot.contains("100.00"));
assert!(plot.contains("50.00"));

// Should have newline separating plot from labels
assert!(plot.contains('\n'));
}

#[test]
fn test_render_plot_same_values() {
let plot = render_plot(50.0, 50.0, 50.0, 50.0, 50.0);

// When all values are the same, should still render
assert!(plot.contains('|'));
assert!(plot.contains(':'));
assert!(plot.contains("50.00"));
}

#[test]
fn test_render_plot_structure() {
let plot = render_plot(10.0, 30.0, 50.0, 70.0, 90.0);
let lines: Vec<&str> = plot.split('\n').collect();

// Should have exactly 2 lines: plot and axis labels
assert_eq!(lines.len(), 2);

// First line should be the boxplot
assert!(lines[0].starts_with('|'));
assert!(lines[0].ends_with('|'));

// Second line should be the axis labels
assert!(lines[1].contains("10.00"));
assert!(lines[1].contains("90.00"));
}

#[test]
fn test_render_plot_quartile_ordering() {
let plot = render_plot(0.0, 20.0, 50.0, 80.0, 100.0);

// Find the positions of key characters
let colon_pos = plot.find(':').unwrap();
let first_pipe = plot.find('|').unwrap();
let last_pipe = plot.rfind('|').unwrap();

// Colon (median) should be between the pipes
assert!(colon_pos > first_pipe);
assert!(colon_pos < last_pipe);
}
}
126 changes: 126 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,129 @@ fn parse_payload_size(input_string: &str) -> Result<PayloadSize, String> {
fn parse_output_format(input_string: &str) -> Result<OutputFormat, String> {
OutputFormat::from(input_string.to_string())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_output_format_from_valid_inputs() {
assert_eq!(OutputFormat::from("csv".to_string()), Ok(OutputFormat::Csv));
assert_eq!(OutputFormat::from("CSV".to_string()), Ok(OutputFormat::Csv));
assert_eq!(
OutputFormat::from("json".to_string()),
Ok(OutputFormat::Json)
);
assert_eq!(
OutputFormat::from("JSON".to_string()),
Ok(OutputFormat::Json)
);
assert_eq!(
OutputFormat::from("json-pretty".to_string()),
Ok(OutputFormat::JsonPretty)
);
assert_eq!(
OutputFormat::from("json_pretty".to_string()),
Ok(OutputFormat::JsonPretty)
);
assert_eq!(
OutputFormat::from("JSON-PRETTY".to_string()),
Ok(OutputFormat::JsonPretty)
);
assert_eq!(
OutputFormat::from("stdout".to_string()),
Ok(OutputFormat::StdOut)
);
assert_eq!(
OutputFormat::from("STDOUT".to_string()),
Ok(OutputFormat::StdOut)
);
}

#[test]
fn test_output_format_from_invalid_inputs() {
assert!(OutputFormat::from("invalid".to_string()).is_err());
assert!(OutputFormat::from("xml".to_string()).is_err());
assert!(OutputFormat::from("".to_string()).is_err());
assert!(OutputFormat::from("json_invalid".to_string()).is_err());

let error_msg = OutputFormat::from("invalid".to_string()).unwrap_err();
assert_eq!(
error_msg,
"Value needs to be one of csv, json or json-pretty"
);
}

#[test]
fn test_output_format_display() {
assert_eq!(format!("{}", OutputFormat::Csv), "Csv");
assert_eq!(format!("{}", OutputFormat::Json), "Json");
assert_eq!(format!("{}", OutputFormat::JsonPretty), "JsonPretty");
assert_eq!(format!("{}", OutputFormat::StdOut), "StdOut");
assert_eq!(format!("{}", OutputFormat::None), "None");
}

#[test]
fn test_cli_options_should_download() {
let mut options = SpeedTestCLIOptions {
nr_tests: 10,
nr_latency_tests: 25,
max_payload_size: speedtest::PayloadSize::M25,
output_format: OutputFormat::StdOut,
verbose: false,
ipv4: None,
ipv6: None,
disable_dynamic_max_payload_size: false,
download_only: false,
upload_only: false,
completion: None,
};

// Default: both download and upload
assert!(options.should_download());
assert!(options.should_upload());

// Download only
options.download_only = true;
assert!(options.should_download());
assert!(!options.should_upload());

// Upload only
options.download_only = false;
options.upload_only = true;
assert!(!options.should_download());
assert!(options.should_upload());
}

#[test]
fn test_cli_options_should_upload() {
let mut options = SpeedTestCLIOptions {
nr_tests: 10,
nr_latency_tests: 25,
max_payload_size: speedtest::PayloadSize::M25,
output_format: OutputFormat::StdOut,
verbose: false,
ipv4: None,
ipv6: None,
disable_dynamic_max_payload_size: false,
download_only: false,
upload_only: false,
completion: None,
};

// Default: both download and upload
assert!(options.should_upload());
assert!(options.should_download());

// Upload only
options.upload_only = true;
assert!(options.should_upload());
assert!(!options.should_download());

// Download only
options.upload_only = false;
options.download_only = true;
assert!(!options.should_upload());
assert!(options.should_download());
}
}
87 changes: 87 additions & 0 deletions src/measurements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,90 @@ pub(crate) fn format_bytes(bytes: usize) -> String {
_ => format!("{bytes} bytes"),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_format_bytes() {
assert_eq!(format_bytes(500), "500 bytes");
assert_eq!(format_bytes(1_000), "1KB");
assert_eq!(format_bytes(100_000), "100KB");
assert_eq!(format_bytes(999_999), "999KB");
assert_eq!(format_bytes(1_000_000), "1MB");
assert_eq!(format_bytes(25_000_000), "25MB");
assert_eq!(format_bytes(100_000_000), "100MB");
assert_eq!(format_bytes(999_999_999), "999MB");
assert_eq!(format_bytes(1_000_000_000), "1000000000 bytes");
}

#[test]
fn test_measurement_display() {
let measurement = Measurement {
test_type: TestType::Download,
payload_size: 1_000_000,
mbit: 50.5,
};

let display_str = format!("{}", measurement);
assert!(display_str.contains("Download"));
assert!(display_str.contains("1MB"));
assert!(display_str.contains("50.5"));
}

#[test]
fn test_calc_stats_empty() {
assert_eq!(calc_stats(vec![]), None);
}

#[test]
fn test_calc_stats_single_value() {
let result = calc_stats(vec![10.0]).unwrap();
assert_eq!(result, (10.0, 10.0, 10.0, 10.0, 10.0, 10.0));
}

#[test]
fn test_calc_stats_two_values() {
let result = calc_stats(vec![10.0, 20.0]).unwrap();
assert_eq!(result.0, 10.0); // min
assert_eq!(result.4, 20.0); // max
assert_eq!(result.2, 15.0); // median
assert_eq!(result.5, 15.0); // avg
}

#[test]
fn test_calc_stats_multiple_values() {
let result = calc_stats(vec![1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
assert_eq!(result.0, 1.0); // min
assert_eq!(result.4, 5.0); // max
assert_eq!(result.2, 3.0); // median
assert_eq!(result.5, 3.0); // avg
}

#[test]
fn test_calc_stats_unsorted() {
let result = calc_stats(vec![5.0, 1.0, 3.0, 2.0, 4.0]).unwrap();
assert_eq!(result.0, 1.0); // min
assert_eq!(result.4, 5.0); // max
assert_eq!(result.2, 3.0); // median
assert_eq!(result.5, 3.0); // avg
}

#[test]
fn test_median_odd_length() {
assert_eq!(median(&[1.0, 2.0, 3.0]), 2.0);
assert_eq!(median(&[1.0, 2.0, 3.0, 4.0, 5.0]), 3.0);
}

#[test]
fn test_median_even_length() {
assert_eq!(median(&[1.0, 2.0]), 1.5);
assert_eq!(median(&[1.0, 2.0, 3.0, 4.0]), 2.5);
}

#[test]
fn test_median_single_value() {
assert_eq!(median(&[5.0]), 5.0);
}
}
Loading
Loading