From 637bc162ed6e5864e8075a6cbb88b11a5e3e9f12 Mon Sep 17 00:00:00 2001 From: SurName <472256532@qq.com> Date: Thu, 31 Oct 2024 15:19:48 +0800 Subject: [PATCH] Add UT test case for ylong_http_client Signed-off-by: xujunyang <472256532@qq.com> --- ylong_http_client/src/util/c_openssl/bio.rs | 317 +++++++++++++++ ylong_http_client/src/util/c_openssl/error.rs | 142 +++++++ ylong_http_client/src/util/c_openssl/x509.rs | 80 +++- .../src/util/h2/buffer/settings.rs | 101 +++++ .../src/util/h2/buffer/window.rs | 209 ++++++++++ ylong_http_client/src/util/h2/streams.rs | 370 +++++++++++++++++- 6 files changed, 1215 insertions(+), 4 deletions(-) diff --git a/ylong_http_client/src/util/c_openssl/bio.rs b/ylong_http_client/src/util/c_openssl/bio.rs index a24dbd2..e67c228 100644 --- a/ylong_http_client/src/util/c_openssl/bio.rs +++ b/ylong_http_client/src/util/c_openssl/bio.rs @@ -273,3 +273,320 @@ unsafe extern "C" fn bread(bio: *mut BIO, buf: *mut c_char, len: c_int) unsafe extern "C" fn bputs(bio: *mut BIO, buf: *const c_char) -> c_int { bwrite::(bio, buf, strlen(buf) as c_int) } + +#[cfg(test)] +mod ut_bio_slice { + use super::*; + + /// UT test case for `BioSlice::from_byte`. + /// + /// # Brief + /// 1. Calls `BioSlice::from_byte` with a byte slice. + /// 2. Verifies if the slice is created successfully. + /// 3. Retrieves the pointer. + /// 4. Checks if the pointer is not null; + #[test] + fn ut_from_byte() { + let data = b"TEST"; + let slice = BioSlice::from_byte(data); + assert!(slice.is_ok()); + let ptr = slice.unwrap().as_ptr(); + assert!(!ptr.is_null()); + } +} + +#[cfg(test)] +mod ut_bio_method_inner { + use std::io::Cursor; + + use super::*; + + /// UT test case for `BioMethodInner::new` and `BioMethodInner::get`. + /// + /// # Brief + /// 1. Creates a new `BioMethodInner` and check it successfully. + /// 2. Checks if the internal pointer is not null. + #[test] + fn ut_new_get() { + let inner = BioMethodInner::new::>>(); + assert!(inner.is_ok()); + let inner = inner.unwrap(); + assert!(!inner.get().is_null()); + drop(inner); + } +} + +#[cfg(test)] +mod ut_bio_method { + use std::io::Cursor; + + use super::*; + + /// UT test case for `BioMethod::new` and `BioMethod::get`. + /// + /// # Brief + /// 1. Creates a new `BioMethod` and check it successfully. + /// 2. Checks if the internal pointer is not null. + #[test] + fn ut_new_get() { + let method = BioMethod::new::>>(); + assert!(method.is_ok()); + let method = method.unwrap(); + assert!(!method.get().is_null()); + } +} + +#[cfg(test)] +mod ut_bio { + use std::io::Cursor; + + use super::*; + + /// UT test case for `Bio::new`. + /// + /// # Brief + /// 1. Create a BIO with a cursor stream. + /// 2. Verify if the BIO is created successfully. + #[test] + fn ut_new() { + let stream = Cursor::new(vec![0u8; 10]); + let bio = new(stream); + assert!(bio.is_ok()); + } + + /// UT test case for `Bio::get_state`. + /// + /// # Brief + /// 1. Create a BIO and retrieve the BIO state. + /// 2. Check if the stream length matches the expected value. + #[test] + fn ut_get_state() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + unsafe { + let state = get_state::>>(bio); + assert_eq!(state.stream.get_ref().len(), 10); + } + } + + /// UT test case for `BIO::get_error`. + /// + /// # Brief + /// 1. Calls `get_error` to retrieve the error state. + /// 2. Verify that errors is as expected. + #[test] + fn ut_get_error() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + unsafe { + let error = get_error::>>(bio); + assert!(error.is_none()); + let state = get_state::>>(bio); + state.error = Some(io::Error::new(io::ErrorKind::Other, "ERROR TEST")); + let error = get_error::>>(bio); + assert!(error.is_some()); + let msg = error.unwrap().to_string(); + assert_eq!(msg, "ERROR TEST"); + } + } + + /// UT test case for `BIO::get_panic`. + /// + /// # Brief + /// 1. Calls `get_panic` to retrieve the panic state. + /// 2. Verify that the panic is as expected. + #[test] + fn ut_get_panic() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + unsafe { + let panic = get_panic::>>(bio); + assert!(panic.is_none()); + let state = get_state::>>(bio); + state.panic = Some(Box::new("PANIC TEST")); + let panic = get_panic::>>(bio); + assert!(panic.is_some()); + assert_eq!(panic.unwrap().downcast_ref::<&str>(), Some(&"PANIC TEST")); + } + } + + /// UT test case for `BIO::get_panic`. + /// + /// # Brief + /// 1. Calls `get_stream_ref` and `get_stream_mut` to retrieve the stream + /// references. + /// 2. Verify that the stream length matches expected. + #[test] + fn ut_get_stream() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + unsafe { + let stream_ref = get_stream_ref::>>(bio); + assert_eq!(stream_ref.get_ref().len(), 10); + let stream_mut = get_stream_mut::>>(bio); + assert_eq!(stream_mut.get_mut().len(), 10); + } + } + + /// UT test case for `BIO::retry_error`. + /// + /// # Brief + /// 1. Calls `retry_error` with some IO errors. + /// 2. Verify that the result matches the error kind. + #[test] + fn ut_try_error() { + let error = io::Error::new(io::ErrorKind::WouldBlock, "operation would back"); + assert!(retry_error(&error)); + let error = io::Error::new(io::ErrorKind::NotConnected, "not connected"); + assert!(retry_error(&error)); + let error = io::Error::new(io::ErrorKind::Other, "some other error"); + assert!(!retry_error(&error)); + } + + /// UT test case for `ctrl` with `BIO_CTRL_FLUSH`. + /// + /// # Brief + /// 1. Calls `ctrl` with `BIO_CTRL_FLUSH. + /// 2. Verify that the flush operation returns the expected result. + #[test] + fn ut_ctrl_flush() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + unsafe { + let res = ctrl::>>(bio, BIO_CTRL_FLUSH, 0, std::ptr::null_mut()); + assert_eq!(res, 1); + } + } + + /// UT test case for `ctrl` with `BIO_CTRL_DGRAM_QUERY`. + /// + /// # Brief + /// 1. Injects an MTU size into the BIO state. + /// 2. Calls `ctrl` with `BIO_CTRL_DGRAM_QUERY`. + /// 3. Verify that the MTU size is returned correctly. + #[test] + fn ut_ctrl_dgram_query() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + unsafe { + let state = get_state::>>(bio); + state.dtls_mtu_size = 100; + let res = ctrl::>>(bio, BIO_CTRL_DGRAM_QUERY, 0, std::ptr::null_mut()); + assert_eq!(res, 100); + } + } + + /// UT test case for `ctrl` with unknow command. + /// + /// # Brief + /// 1. Calls `ctrl` with an unknown command. + /// 2. Verify that the default result is returned. + #[test] + fn ut_ctrl_default() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + unsafe { + let res = ctrl::>>(bio, 99, 0, std::ptr::null_mut()); + assert_eq!(res, 0); + } + } + + /// UT test case for `create`. + /// + /// # Brief + /// 1. Initialize a BIO by `create`. + /// 2. Verify that created successfully. + #[test] + fn ut_create() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + unsafe { + let res = create(bio); + assert_eq!(res, 1); + } + } + + /// UT test case for `destroy`. + /// + /// # Brief + /// 1. Clean up the BIO by `destroy`. + /// 2. Verify that destroy successfully. + /// 3. Ensures that the BIO data is cleared. + #[test] + fn ut_destroy() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + assert!(!bio.is_null()); + unsafe { + let res = destroy::>>(bio); + assert_eq!(res, 1); + let data_ptr = BIO_get_data(bio); + assert!(data_ptr.is_null()); + } + unsafe { + let res = destroy::>>(std::ptr::null_mut()); + assert_eq!(res, 0); + } + } + + /// UT test case for `bwrite`. + /// + /// # Brief + /// 1. Write data to the BIO. + /// 2. Verify that the data is written correctly. + #[test] + fn ut_bwrite() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + let data = b"TEST TEST"; + let len = data.len() as c_int; + unsafe { + let res = bwrite::>>(bio, data.as_ptr() as *const c_char, len); + assert_eq!(res, len); + let state = get_stream_ref::>>(bio); + let write_data = state.get_ref(); + assert_eq!(&write_data[..len as usize], b"TEST TEST"); + } + } + + /// UT test case for `bread`. + /// + /// # Brief + /// 1. Read data to the BIO. + /// 2. Verify that the data is read correctly. + #[test] + fn ut_bread() { + let data = b"TEST TEST".to_vec(); + let stream = Cursor::new(data.clone()); + let (bio, _method) = new(stream).unwrap(); + let mut buf = vec![0u8; data.len()]; + let len = data.len() as c_int; + unsafe { + let res = bread::>>(bio, buf.as_mut_ptr() as *mut c_char, len); + assert_eq!(res, len); + assert_eq!(buf, data); + } + } + + /// UT test case for `bputs`. + /// + /// # Brief + /// 1. Write a null-terminated string to the BIO. + /// 2. Verify that the string is written correctly. + #[test] + fn ut_bput() { + let stream = Cursor::new(vec![0u8; 10]); + let (bio, _method) = new(stream).unwrap(); + let data = "TEST TEST\0"; + unsafe { + let res = bputs::>>(bio, data.as_ptr() as *const c_char); + assert_eq!(res, strlen(data.as_ptr() as *const c_char) as c_int); + let state = get_stream_ref::>>(bio); + let write_data = state.get_ref(); + assert_eq!( + &write_data[..data.len() - 1], + data.as_bytes().strip_suffix(&[0]).unwrap() + ) + } + } +} diff --git a/ylong_http_client/src/util/c_openssl/error.rs b/ylong_http_client/src/util/c_openssl/error.rs index 9622958..23f5018 100644 --- a/ylong_http_client/src/util/c_openssl/error.rs +++ b/ylong_http_client/src/util/c_openssl/error.rs @@ -365,3 +365,145 @@ mod ut_c_openssl_error { assert_eq!(format!("{error}"), "Public Key Pinning Error: error"); } } + +#[cfg(test)] +mod ut_stack_error { + use super::*; + + /// UT test case for `StackError::clone`. + /// + /// # Brief + /// 1. Creates a mock `StackError` and clone the error message. + /// 2. Verift the cloned error messsage is the same as the original. + #[test] + #[cfg(feature = "__c_openssl")] + fn ut_clone() { + let error1 = StackError { + code: 0x00000001, + #[cfg(feature = "c_openssl_1_1")] + file: ptr::null(), + #[cfg(feature = "c_openssl_3_0")] + file: CString::new("TEST").unwrap(), + line: 1, + #[cfg(feature = "c_openssl_3_0")] + func: None, + data: Some(Cow::Borrowed("Test")), + }; + let error2 = error1.clone(); + let msg = format!("{}", error2); + #[cfg(feature = "c_openssl_1_1")] + assert_eq!( + msg, + "error:00000001lib: (0), func: (0), reason: (1), :0x0:1:Test" + ); + #[cfg(feature = "c_openssl_3_0")] + assert_eq!( + msg, + "error:00000001lib: (0), :func(0)reason(1), :\"TEST\":1:Test" + ); + } + + /// UT test case for `StackError::fmt`. + /// + /// # Brief + /// 1. Creates a mock `StackError` and format the error message. + /// 2. Verify that the formatted message is correct. + #[test] + #[cfg(feature = "__c_openssl")] + fn ut_fmt() { + let error = StackError { + code: 0x00000001, + #[cfg(feature = "c_openssl_1_1")] + file: ptr::null(), + #[cfg(feature = "c_openssl_3_0")] + file: CString::new("TEST").unwrap(), + line: 1, + #[cfg(feature = "c_openssl_3_0")] + func: None, + data: Some(Cow::Borrowed("Test")), + }; + let msg = format!("{}", error); + #[cfg(feature = "c_openssl_1_1")] + assert_eq!( + msg, + "error:00000001lib: (0), func: (0), reason: (1), :0x0:1:Test" + ); + #[cfg(feature = "c_openssl_3_0")] + assert_eq!( + msg, + "error:00000001lib: (0), :func(0)reason(1), :\"TEST\":1:Test" + ); + } + + /// UT test case for `error_get_func`. + /// + /// # Brief + /// 1. Creates a error code and return error get code by `error_get_func` + /// 2. Verify the error get code is correct. + #[test] + fn ut_ut_error_get_func() { + let code = 0x12345; + let get_code = error_get_func(code); + #[cfg(feature = "c_openssl_1_1")] + assert_eq!(get_code, 18); + #[cfg(feature = "c_openssl_3_0")] + assert_eq!(get_code, 0); + } +} + +#[cfg(test)] +mod ut_error_stack { + use super::*; + + /// UT test case for `ErrorStack::fmt`. + /// + /// # Brief + /// 1. Create an `ErrorStack` with multiple errors. + /// 2. Formats the entire error stack. + /// 3. Verify if the formatted message is correct. + #[test] + #[cfg(feature = "__c_openssl")] + fn ut_fmt() { + let error1 = StackError { + code: 0x00000001, + #[cfg(feature = "c_openssl_1_1")] + file: ptr::null(), + #[cfg(feature = "c_openssl_3_0")] + file: CString::new("TEST").unwrap(), + line: 1, + #[cfg(feature = "c_openssl_3_0")] + func: None, + data: Some(Cow::Borrowed("Error 1")), + }; + let error2 = StackError { + code: 0x00000002, + #[cfg(feature = "c_openssl_1_1")] + file: ptr::null(), + #[cfg(feature = "c_openssl_3_0")] + file: CString::new("TEST").unwrap(), + line: 2, + #[cfg(feature = "c_openssl_3_0")] + func: None, + data: Some(Cow::Borrowed("Error 2")), + }; + let error3 = StackError { + code: 0x00000003, + #[cfg(feature = "c_openssl_1_1")] + file: ptr::null(), + #[cfg(feature = "c_openssl_3_0")] + file: CString::new("TEST").unwrap(), + line: 3, + #[cfg(feature = "c_openssl_3_0")] + func: None, + data: Some(Cow::Borrowed("Error 3")), + }; + let error_stack = ErrorStack(vec![error1, error2, error3]); + let msg = format!("{}", error_stack); + assert!(msg.contains("error:00000001")); + assert!(msg.contains("error:00000002")); + assert!(msg.contains("error:00000003")); + assert!(msg.contains("Error 1")); + assert!(msg.contains("Error 2")); + assert!(msg.contains("Error 3")); + } +} diff --git a/ylong_http_client/src/util/c_openssl/x509.rs b/ylong_http_client/src/util/c_openssl/x509.rs index 54f4bee..19f21b4 100644 --- a/ylong_http_client/src/util/c_openssl/x509.rs +++ b/ylong_http_client/src/util/c_openssl/x509.rs @@ -308,7 +308,7 @@ foreign_type!( #[cfg(test)] mod ut_x509 { - + use crate::util::c_openssl::x509::X509; /// UT test cases for `X509::clone`. /// /// # Brief @@ -318,10 +318,84 @@ mod ut_x509 { #[test] #[allow(clippy::redundant_clone)] fn ut_x509_clone() { - use crate::util::c_openssl::x509::X509; - let pem = include_bytes!("../../../tests/file/root-ca.pem"); let x509 = X509::from_pem(pem).unwrap(); drop(x509.clone()); } + + /// UT test case for `X509::get_cert_version` and `X509::get_cert` + /// + /// # Brief + /// 1. Creates a `X509` by calling `X509::from_pem`. + /// 2. Retrieve the certificate version using `get_cert_version`. + /// 3. Verify that the returned version is as expected. + #[test] + fn ut_get_cert_version() { + let pem = include_bytes!("../../../tests/file/root-ca.pem"); + let x509 = X509::from_pem(pem).unwrap(); + assert!(x509.get_cert().is_ok()); + assert_eq!(x509.get_cert_version(), 2); + } + + /// UT test case for `X509::cmp_certs`. + /// + /// # Brief + /// 1. Creates a `X509` by calling `X509::from_pem`. + /// 2. Retrieves the public key using `get_cert`. + /// 3. Compares the certificate using `cmp_certs` and verify that the + /// comparison is valid + #[test] + fn ut_cmp_certs() { + let pem = include_bytes!("../../../tests/file/root-ca.pem"); + let x509 = X509::from_pem(pem).unwrap(); + let key = x509.get_cert().unwrap(); + assert!(x509.cmp_certs(key) != 0); + } +} + +#[cfg(test)] +mod ut_x509_verify_result { + use super::*; + + /// UT test case for `X509VerifyResult::error_string`. + /// + /// # Brief + /// 1. Creates a `X509VerifyResult` using a known error code. + /// 2. Verify that the `error_string` returns a non-empty string. + #[test] + fn ut_error_string() { + let res = X509VerifyResult::from_raw(10); + let string = res.error_string(); + assert!(!string.is_empty()); + } + + /// UT test case for `X509VerifyResult::from_raw`. + /// + /// # Brief + /// 1. Creates a `X509VerifyResult` using a raw error code. + /// 2. Verify that the error code is correct. + #[test] + fn ut_from_raw() { + let code = 20; + let res = X509VerifyResult::from_raw(code); + assert_eq!(res.0, code); + } + + /// UT test code for `fmt::Display` and `fmt::Debug`. + /// + /// # Brief + /// 1. Creates a `X509VerifyResult` using an error code. + /// 2. Uses the `fmt::Display` and `fmt::Debug` to format the result as a + /// string. + /// 3. Verify that the output string is correct. + #[test] + fn ut_fmt_display() { + let res = X509VerifyResult::from_raw(10); + let fmt_dis = format!("{}", res); + assert!(!fmt_dis.is_empty()); + let dbg_dis = format!("{:?}", res); + assert!(dbg_dis.contains("X509VerifyResult")); + assert!(dbg_dis.contains("code")); + assert!(dbg_dis.contains("error")); + } } diff --git a/ylong_http_client/src/util/h2/buffer/settings.rs b/ylong_http_client/src/util/h2/buffer/settings.rs index d6b4124..e65bb07 100644 --- a/ylong_http_client/src/util/h2/buffer/settings.rs +++ b/ylong_http_client/src/util/h2/buffer/settings.rs @@ -67,3 +67,104 @@ impl FlowControl { self.recv_window.recv_data(size) } } + +#[cfg(test)] +mod ut_flow_control { + + use super::*; + + /// UT test case for `FlowControl::new`. + /// + /// # Brief + /// 1. Create a new `FlowControl` instance with specific receive and send + /// window sizes. + /// 2. Asserts that the initial sizes are correctly set in both windows. + #[test] + fn ut_fc_new() { + let fc = FlowControl::new(100, 200); + assert_eq!(fc.recv_window.actual_size(), 100); + assert_eq!(fc.send_window.size_available(), 200); + } + + /// UT test case for `FlowControl::check_conn_recv_window_update`. + /// + /// # Brief + /// 1. Create a `FlowControl` instance. + /// 2. Simulates the behavior of `recv_window` and Asserts that + /// `check_conn_recv_window_update` returns `None`. + #[test] + fn ut_fc_check_conn_recv_window_update() { + let mut fc = FlowControl::new(100, 200); + let frame = fc.check_conn_recv_window_update(); + assert!(frame.is_none()); + } + + /// UT test case for `FlowControl::setup_recv_window` + /// + /// # Brief + /// 1. Set receive window size. + /// 2. Asserts that the receive window size is correctly. + #[test] + fn ut_fc_setup_recv_window() { + let mut fc = FlowControl::new(100, 200); + fc.setup_recv_window(200); + assert_eq!(fc.recv_window.actual_size(), 200); + fc.setup_recv_window(100); + assert_eq!(fc.recv_window.actual_size(), 100); + } + + /// UT test case for `FlowControl::increase_send_size`. + /// + /// # Brief + /// 1. Increases the send window size by a specified amount. + /// 2. Asserts that the size increase is successful and the window is + /// updated correctly. + #[test] + fn ut_fc_increase_send_size() { + let mut fc = FlowControl::new(100, 200); + let res = fc.increase_send_size(50); + assert!(res.is_ok()); + assert_eq!(fc.send_window.size_available(), 250); + } + + /// UT test case for `FlowControl::send_size_available`. + /// + /// # Brief + /// 1. Check the available size for sending data in the send window. + /// 2. Asserts that the available size is as expected. + #[test] + fn ut_fc_send_size_available() { + let fc = FlowControl::new(100, 200); + assert_eq!(fc.send_size_available(), 200); + } + + /// UT test case for `FlowControl::recv_notification_size_available`. + /// + /// # Brief + /// 1. Checks the available size for receiving notificaitons in the receive + /// window. + /// 2. Asserts that the available notification size matches the value in + /// `recv_window`. + #[test] + fn ut_fc_recv_notification_size_available() { + let fc = FlowControl::new(100, 200); + let notificaiton_size = fc.recv_notification_size_available(); + assert_eq!(notificaiton_size, fc.recv_window.notification_available()); + } + + /// UT test case for `FlowControl::send_data` and `FlowControl::recv_data`. + /// + /// # Brief + /// 1. Sends data using the send window. + /// 2. Receives data using the receive window. + /// 3. Asserts that the available send window and available notification + /// size is reduced correctly. + #[test] + fn ut_fc_send_and_recv_data() { + let mut fc = FlowControl::new(100, 200); + fc.send_data(50); + fc.recv_data(50); + assert_eq!(fc.send_window.size_available(), 150); + assert_eq!(fc.recv_window.notification_available(), 50); + } +} diff --git a/ylong_http_client/src/util/h2/buffer/window.rs b/ylong_http_client/src/util/h2/buffer/window.rs index 9329ece..0459a40 100644 --- a/ylong_http_client/src/util/h2/buffer/window.rs +++ b/ylong_http_client/src/util/h2/buffer/window.rs @@ -133,3 +133,212 @@ impl RecvWindow { self.notification -= size as i32; } } + +#[cfg(test)] +mod ut_send_window { + use ylong_http::h2::{ErrorCode, H2Error}; + + use super::*; + + /// UT test case for `SendWindow::new`. + /// + /// # Brief + /// 1. Creates a new `SendWindow` instance. + /// 2. Asserts that the window size is initialized to the provided value. + #[test] + fn ut_sw_new() { + let sw = SendWindow::new(100); + assert_eq!(sw.size, 100); + } + + /// UT test case for `SendWindow::size_available`. + /// + /// # Brief + /// 1. Creates a new `SendWindow` instance. + /// 2. Checks that the available size is returns correctly. + #[test] + fn ut_sw_size_available() { + let sw = SendWindow::new(100); + assert_eq!(sw.size_available(), 100); + let sw = SendWindow::new(-1); + assert_eq!(sw.size_available(), 0); + } + + /// UT test case for `SendWindow::reduce_size`. + /// + /// # Brief + /// 1. Reduces the send window size by a specified value. + /// 2. Asserts that the size is correctly reduce. + #[test] + fn ut_sw_reduce_size() { + let mut sw = SendWindow::new(100); + sw.reduce_size(50); + assert_eq!(sw.size, 50); + } + + /// UT test case for `SendWindow::increase_size`. + /// + /// # Brief + /// 1. Increases the send window size. + /// 2. Asserts that the size is increased correctly. + /// 3. Attempts to increase the window size beyond the maximum allowable + /// value or beyond the maximum flow control window size. + /// 4. Asserts that the operation fails. + #[test] + fn ut_sw_window_increase_size() { + let mut sw = SendWindow::new(100); + assert!(sw.increase_size(50).is_ok()); + assert_eq!(sw.size, 150); + + let mut sw = SendWindow::new(i32::MAX); + let res = sw.increase_size(1); + assert!(res.is_err()); + if let Err(H2Error::ConnectionError(code)) = res { + assert_eq!(code, ErrorCode::FlowControlError); + } + + let mut sw = SendWindow::new(1); + let res = sw.increase_size(crate::util::h2::MAX_FLOW_CONTROL_WINDOW); + assert!(res.is_err()); + assert_eq!( + res, + Err(H2Error::ConnectionError(ErrorCode::FlowControlError)) + ); + } + + /// UT test case for `SendWindow::send_data`. + /// + /// # Brief + /// 1. Sends data by reducing the send window size. + /// 2. Asserts that the send window size is correctly redueced after the + /// data is sent. + #[test] + fn ut_sw_send_data() { + let mut sw = SendWindow::new(100); + sw.send_data(50); + assert_eq!(sw.size, 50); + } +} + +#[cfg(test)] +mod ut_recv_window { + use super::*; + + /// UT test case for `RecvWindow::new`. + /// + /// # Brief + /// 1. Creates a new `RecvWindow` instance. + /// 2. Asserts that both the `notification` and `actual` sizes are + /// initialized to the provided value. + #[test] + fn ut_rw_new() { + let rw = RecvWindow::new(100); + assert_eq!(rw.notification, 100); + assert_eq!(rw.actual, 100); + } + + /// UT test case for `RecvWindow::unreleased_size`. + /// + /// # Brief + /// 1. Creates a `RecvWindow` instance. + /// 2. Asserts that no unreleased size is reported when the notification is + /// greater than or equal to the actual size. + /// 3. Simulate a scenario where the notification size is smaller than the + /// actual size and asserts that unreleased size is repoeted. + /// 4. Simulate a scenario where the notification size is slightly less than + /// the actual size but still no unreleased size is reported. + #[test] + fn ut_rw_unreleased_size() { + let mut rw = RecvWindow::new(100); + assert_eq!(rw.unreleased_size(), None); + rw.notification = 50; + assert_eq!(rw.unreleased_size(), Some(50)); + rw.notification = 80; + assert_eq!(rw.unreleased_size(), None); + } + + /// UT test case for `RecvWindow::actual_size`. + /// + /// # Brief + /// 1. Retrieves the actual window size. + /// 2. Asserts that the size is returned correctly. + #[test] + fn ut_rw_actual_size() { + let rw = RecvWindow::new(100); + assert_eq!(rw.actual_size(), 100); + } + + /// UT test case for `RecvWindow::notification_available`. + /// + /// # Brief + /// 1. Asserts that the available notification size is correctly reported + /// for a positive value. + /// 2. Simulates a scenario where the notification size is negative and + /// asserts that the available notification size reported as zero. + #[test] + fn ut_rw_notification_available() { + let rw = RecvWindow::new(100); + assert_eq!(rw.notification_available(), 100); + let rw = RecvWindow::new(-1); + assert_eq!(rw.notification_available(), 0); + } + + /// UT test case for `RecvWindow::{reduce_actual,increase_actual}` + /// + /// # Brief + /// 1. Reduces and increase the actual window size. + /// 2. Asserts that the size is correctly reduced and increased. + #[test] + fn ut_rw_reduce_and_increase_actual() { + let mut rw = RecvWindow::new(100); + rw.reduce_actual(50); + assert_eq!(rw.actual, 50); + rw.increase_actual(50); + assert_eq!(rw.actual, 100); + } + + /// UT test case for + /// `RecvWindow::{reduce_notification,increase_notificaiton}` + /// + /// # Brief + /// 1. Reduces and increase the notification window size. + /// 2. Asserts that the size is correctly reduced and increased. + #[test] + fn ut_rw_reduce_and_increase_notification() { + let mut rw = RecvWindow::new(100); + rw.reduce_notification(50); + assert_eq!(rw.notification, 50); + rw.increase_notification(50); + assert_eq!(rw.notification, 100); + } + + /// UT test case for `RecvWindow::check_window_update`. + /// + /// # Brief + /// 1. Checks for a window update when there is no unreleased size. + /// 2. Asserts that no `Frame` is generated. + /// 3. Checks for a window update when there is unreleased size available. + /// 4. Asserts that a `Frame` is generated for the window update. + #[test] + fn ut_rw_check_window_update() { + let mut rw = RecvWindow::new(100); + let frame = rw.check_window_update(1); + assert!(frame.is_none()); + rw.notification = 50; + let frame = rw.check_window_update(1); + assert!(frame.is_some()); + } + + /// UT test case for `RecvWindow::recv_data`. + /// + /// # Brief + /// 1. Simulates receiving data, whice reduces the notification size. + /// 2. Asserts that the notification size is correctly reduced after + /// receiving data. + #[test] + fn ut_rw_send_data() { + let mut rw = RecvWindow::new(100); + rw.recv_data(50); + assert_eq!(rw.notification, 50); + } +} diff --git a/ylong_http_client/src/util/h2/streams.rs b/ylong_http_client/src/util/h2/streams.rs index 31cda9b..c7cffb8 100644 --- a/ylong_http_client/src/util/h2/streams.rs +++ b/ylong_http_client/src/util/h2/streams.rs @@ -31,6 +31,7 @@ const DEFAULT_MAX_STREAM_ID: StreamId = u32::MAX >> 1; const INITIAL_LATEST_REMOTE_ID: StreamId = 0; const DEFAULT_MAX_CONCURRENT_STREAMS: u32 = 100; +#[cfg_attr(test, derive(Debug, PartialEq))] pub(crate) enum FrameRecvState { OK, Ignore, @@ -44,7 +45,7 @@ pub(crate) enum DataReadState { Ready(Frame), Finish(Frame), } - +#[cfg_attr(test, derive(Debug, PartialEq))] pub(crate) enum StreamEndState { OK, Ignore, @@ -80,6 +81,7 @@ pub(crate) enum StreamEndState { // `----------------------->| |<----------------------' // +--------+ #[derive(Copy, Clone, Debug)] +#[cfg_attr(test, derive(PartialEq))] pub(crate) enum H2StreamState { Idle, // When response does not depend on request, @@ -100,6 +102,7 @@ pub(crate) enum H2StreamState { } #[derive(Copy, Clone, Debug)] +#[cfg_attr(test, derive(PartialEq))] pub(crate) enum CloseReason { LocalRst, RemoteRst, @@ -109,6 +112,7 @@ pub(crate) enum CloseReason { } #[derive(Copy, Clone, Debug)] +#[cfg_attr(test, derive(PartialEq))] pub(crate) enum ActiveState { WaitHeaders, WaitData, @@ -780,3 +784,367 @@ impl Stream { ) } } + +#[cfg(test)] +mod ut_h2streamstate { + use super::*; + + /// UT test case for `H2StreamState` with some states. + /// + /// # Brief + /// 1. Creates an H2StreamState with open, LocalHalfClosed, Closed state. + /// 2. Asserts that the send and recv field are as expected. + #[test] + fn ut_hss() { + let state = H2StreamState::Open { + send: ActiveState::WaitHeaders, + recv: ActiveState::WaitData, + }; + if let H2StreamState::Open { send, recv } = state { + assert_eq!(send, ActiveState::WaitHeaders); + assert_eq!(recv, ActiveState::WaitData); + }; + + let state = H2StreamState::LocalHalfClosed(ActiveState::WaitData); + if let H2StreamState::LocalHalfClosed(recv) = state { + assert_eq!(recv, ActiveState::WaitData); + }; + + let state = H2StreamState::Closed(CloseReason::EndStream); + if let H2StreamState::Closed(reason) = state { + assert_eq!(reason, CloseReason::EndStream); + } + } +} + +#[cfg(test)] +mod ut_streams { + use super::*; + use crate::async_impl::{Body, Request}; + use crate::request::RequestArc; + + fn stream_new(state: H2StreamState) -> Stream { + Stream { + send_window: SendWindow::new(100), + recv_window: RecvWindow::new(100), + state, + header: None, + data: BodyDataRef::new(RequestArc::new( + Request::builder().body(Body::empty()).unwrap(), + )), + } + } + + /// UT test case for `Streams::apply_max_concurrent_streams`. + /// + /// # Brief + /// 1. Sets the max concurrent streams to 2. + /// 2. Increases current concurrency twice and checks if it reaches max + /// concurrency. + #[test] + fn ut_streams_apply_max_concurrent_streams() { + let mut streams = Streams::new(100, 200, FlowControl::new(300, 400)); + streams.apply_max_concurrent_streams(2); + streams.increase_current_concurrency(); + assert!(!streams.reach_max_concurrency()); + streams.increase_current_concurrency(); + assert!(streams.reach_max_concurrency()); + } + + /// UT test case for `Streams::apply_send_initial_window_size` and + /// `Streams::apply_recv_initial_window_size`. + /// + /// # Brief + /// 1. Adjusts the initial send and recv window size and checks for correct + /// application. + /// 2. Asserts correct window sizes and that `pending_send` queue is empty + /// and correct notification window sizes. + #[test] + fn ut_streams_apply_send_initial_window_size() { + let mut streams = Streams::new(100, 100, FlowControl::new(100, 100)); + streams + .stream_map + .insert(1, stream_new(H2StreamState::Idle)); + + assert!(streams.apply_send_initial_window_size(200).is_ok()); + let stream = streams.stream_map.get(&1).unwrap(); + assert_eq!(stream.send_window.size_available(), 200); + assert!(streams.pending_send.is_empty()); + + assert!(streams.apply_send_initial_window_size(50).is_ok()); + let stream = streams.stream_map.get(&1).unwrap(); + assert_eq!(stream.send_window.size_available(), 50); + assert!(streams.pending_send.is_empty()); + + assert!(streams.apply_send_initial_window_size(100).is_ok()); + let stream = streams.stream_map.get(&1).unwrap(); + assert_eq!(stream.send_window.size_available(), 100); + assert!(streams.pending_send.is_empty()); + + streams.apply_recv_initial_window_size(200); + let stream = streams.stream_map.get(&1).unwrap(); + assert_eq!(stream.recv_window.notification_available(), 200); + + streams.apply_recv_initial_window_size(50); + let stream = streams.stream_map.get(&1).unwrap(); + assert_eq!(stream.recv_window.notification_available(), 50); + + streams.apply_recv_initial_window_size(100); + let stream = streams.stream_map.get(&1).unwrap(); + assert_eq!(stream.recv_window.notification_available(), 100); + } + + /// UT test case for `Streams::get_go_away_streams`. + /// + /// # Brief + /// 1. Insert streams with different states and sends go_away with a stream + /// id. + /// 2. Asserts that only streams with IDs greater than or equal to the + /// go_away ID are closed. + #[test] + fn ut_streams_get_go_away_streams() { + let mut streams = Streams::new(100, 100, FlowControl::new(100, 100)); + streams.apply_max_concurrent_streams(4); + streams + .stream_map + .insert(1, stream_new(H2StreamState::Idle)); + streams.increase_current_concurrency(); + streams + .stream_map + .insert(2, stream_new(H2StreamState::Idle)); + streams.increase_current_concurrency(); + streams.stream_map.insert( + 3, + stream_new(H2StreamState::Open { + send: ActiveState::WaitHeaders, + recv: ActiveState::WaitData, + }), + ); + streams.increase_current_concurrency(); + streams + .stream_map + .insert(4, stream_new(H2StreamState::Closed(CloseReason::EndStream))); + streams.increase_current_concurrency(); + + let go_away_streams = streams.get_go_away_streams(2); + assert!([2, 3, 4].iter().all(|&e| go_away_streams.contains(&e))); + + let state = streams.stream_state(1).unwrap(); + assert_eq!(state, H2StreamState::Idle); + let state = streams.stream_state(2).unwrap(); + assert_eq!(state, H2StreamState::Closed(CloseReason::RemoteGoAway)); + let state = streams.stream_state(3).unwrap(); + assert_eq!(state, H2StreamState::Closed(CloseReason::RemoteGoAway)); + let state = streams.stream_state(4).unwrap(); + assert_eq!(state, H2StreamState::Closed(CloseReason::EndStream)); + } + + /// UT test case for `Streams::get_all_unclosed_streams`. + /// + /// # Brief + /// 1. Inserts streams with different states. + /// 2. Asserts that only unclosed streams are returned. + #[test] + fn ut_streams_get_all_unclosed_streams() { + let mut streams = Streams::new(1000, 1000, FlowControl::new(1000, 1000)); + streams.apply_max_concurrent_streams(2); + streams + .stream_map + .insert(1, stream_new(H2StreamState::Idle)); + streams.increase_current_concurrency(); + streams + .stream_map + .insert(2, stream_new(H2StreamState::Closed(CloseReason::EndStream))); + streams.increase_current_concurrency(); + assert_eq!(streams.get_all_unclosed_streams(), [1]); + } + + /// UT test case for `Streams::clear_streams_states`. + /// + /// # Brief + /// 1. Clears all the pending and window updating stream states. + /// 2. Asserts that all relevant collections are empty after clearing. + #[test] + fn ut_streams_clear_streams_states() { + let mut streams = Streams::new(1000, 1000, FlowControl::new(1000, 1000)); + streams.clear_streams_states(); + assert!(streams.window_updating_streams.is_empty()); + assert!(streams.pending_stream_window.is_empty()); + assert!(streams.pending_send.is_empty()); + assert!(streams.pending_conn_window.is_empty()); + assert!(streams.pending_concurrency.is_empty()); + } + + /// UT test case for `Streams::send_local_reset`. + /// + /// # Brief + /// 1. Sends local reset on streams with different states. + /// 2. Asserts correct handing o each state. + #[test] + fn ut_streams_send_local_reset() { + let mut streams = Streams::new(1000, 1000, FlowControl::new(1000, 1000)); + streams.apply_max_concurrent_streams(3); + streams + .stream_map + .insert(1, stream_new(H2StreamState::Idle)); + streams.increase_current_concurrency(); + streams.stream_map.insert( + 2, + stream_new(H2StreamState::Closed(CloseReason::RemoteGoAway)), + ); + streams.increase_current_concurrency(); + streams + .stream_map + .insert(3, stream_new(H2StreamState::Closed(CloseReason::EndStream))); + streams.increase_current_concurrency(); + assert_eq!( + streams.send_local_reset(4), + StreamEndState::Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + ); + assert_eq!(streams.send_local_reset(3), StreamEndState::Ignore); + assert_eq!(streams.send_local_reset(2), StreamEndState::Ignore); + assert_eq!(streams.send_local_reset(1), StreamEndState::OK); + } + + /// UT test case for `Streams::send_headers_frame`. + /// + /// # Brief + /// 1. Send headers frame on a stream. + /// 2. Asserts correct handling of frame and stream state changes. + #[test] + fn ut_streams_send_headers_frame() { + let mut streams = Streams::new(100, 100, FlowControl::new(100, 100)); + streams.apply_max_concurrent_streams(1); + streams + .stream_map + .insert(1, stream_new(H2StreamState::Idle)); + streams.increase_current_concurrency(); + let res = streams.send_headers_frame(1, true); + assert_eq!(res, FrameRecvState::OK); + assert_eq!( + streams.stream_state(1).unwrap(), + H2StreamState::LocalHalfClosed(ActiveState::WaitHeaders) + ); + let res = streams.send_headers_frame(1, true); + assert_eq!( + res, + FrameRecvState::Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + ); + } + + /// UT test case for `Streams::send_data_frame`. + /// + /// # Brief + /// 1. Sends data frame on a stream. + /// 2. Asserts correct handling of frame and stream state changes. + #[test] + fn ut_streams_send_data_frame() { + let mut streams = Streams::new(100, 100, FlowControl::new(100, 100)); + streams.stream_map.insert( + 1, + stream_new(H2StreamState::Open { + send: ActiveState::WaitData, + recv: ActiveState::WaitHeaders, + }), + ); + streams.increase_current_concurrency(); + let res = streams.send_data_frame(1, true); + assert_eq!(res, FrameRecvState::OK); + assert_eq!( + streams.stream_state(1).unwrap(), + H2StreamState::LocalHalfClosed(ActiveState::WaitHeaders) + ); + let res = streams.send_data_frame(1, true); + assert_eq!( + res, + FrameRecvState::Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + ); + } + + /// UT test for `Streams::recv_remote_reset`. + /// + /// # Brief + /// 1. Receives remote reset on streams with different states. + /// 2. Asserts correct handling of each state. + #[test] + fn ut_streams_recv_remote_reset() { + let mut streams = Streams::new(100, 100, FlowControl::new(100, 100)); + streams.apply_max_concurrent_streams(1); + streams.stream_map.insert( + 1, + stream_new(H2StreamState::Open { + send: ActiveState::WaitData, + recv: ActiveState::WaitHeaders, + }), + ); + streams.increase_current_concurrency(); + let res = streams.recv_remote_reset(1); + assert_eq!(res, StreamEndState::OK); + assert_eq!( + streams.stream_state(1).unwrap(), + H2StreamState::Closed(CloseReason::RemoteRst) + ); + let res = streams.recv_remote_reset(1); + assert_eq!(res, StreamEndState::Ignore); + } + + /// UT test case for `Streams::recv_headers`. + /// + /// # Brief + /// 1. Receives headers on a stream and checks for state changes. + /// 2. Asserts error handling when headers are received again. + #[test] + fn ut_streams_recv_headers() { + let mut streams = Streams::new(100, 100, FlowControl::new(100, 100)); + streams.apply_max_concurrent_streams(1); + streams + .stream_map + .insert(1, stream_new(H2StreamState::Idle)); + let res = streams.recv_headers(1, false); + assert_eq!(res, FrameRecvState::OK); + assert_eq!( + streams.stream_state(1).unwrap(), + H2StreamState::Open { + send: ActiveState::WaitHeaders, + recv: ActiveState::WaitData, + } + ); + let res = streams.recv_headers(1, false); + assert_eq!( + res, + FrameRecvState::Err(H2Error::ConnectionError(ErrorCode::ProtocolError)) + ); + } + + /// UT test case for `Streams::recv_data`. + /// + /// # Brief + /// 1. Receives data on a stream and checks for state changes. + /// 2. Assert correct state when data is received with eos flag. + #[test] + fn ut_streams_recv_data() { + let mut streams = Streams::new(100, 100, FlowControl::new(100, 100)); + streams.stream_map.insert( + 1, + stream_new(H2StreamState::Open { + send: ActiveState::WaitHeaders, + recv: ActiveState::WaitData, + }), + ); + let res = streams.recv_data(1, false); + assert_eq!(res, FrameRecvState::OK); + assert_eq!( + streams.stream_state(1).unwrap(), + H2StreamState::Open { + send: ActiveState::WaitHeaders, + recv: ActiveState::WaitData, + } + ); + let res = streams.recv_data(1, true); + assert_eq!(res, FrameRecvState::OK); + assert_eq!( + streams.stream_state(1).unwrap(), + H2StreamState::RemoteHalfClosed(ActiveState::WaitHeaders) + ); + } +} -- Gitee