feat(tests): implement dav-server instead of mock for stress testing

This commit is contained in:
perfectra1n
2025-12-15 12:11:34 -08:00
parent c3020c1813
commit d4a15f09db
4 changed files with 434 additions and 96 deletions

143
Cargo.lock generated
View File

@@ -400,7 +400,7 @@ dependencies = [
"http 0.2.12",
"http 1.3.1",
"http-body 0.4.6",
"lru",
"lru 0.12.5",
"percent-encoding",
"regex-lite",
"sha2",
@@ -1382,6 +1382,41 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "dav-server"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1325ec68fa2627b069d06d5096d6109da5ebf46d45099e01e2dc59a4bcc4641e"
dependencies = [
"bytes",
"derivative",
"dyn-clone",
"futures-channel",
"futures-util",
"headers",
"htmlescape",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
"lazy_static",
"libc",
"log",
"lru 0.14.0",
"mime_guess",
"parking_lot",
"percent-encoding",
"pin-project",
"pin-utils",
"reflink-copy",
"regex",
"time",
"tokio",
"url",
"uuid",
"xml-rs",
"xmltree",
]
[[package]]
name = "deadpool"
version = "0.12.2"
@@ -1430,6 +1465,17 @@ dependencies = [
"serde",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.1"
@@ -1993,6 +2039,30 @@ dependencies = [
"hashbrown 0.15.4",
]
[[package]]
name = "headers"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
dependencies = [
"base64 0.22.1",
"bytes",
"headers-core",
"http 1.3.1",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http 1.3.1",
]
[[package]]
name = "heck"
version = "0.5.0"
@@ -2049,6 +2119,12 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "htmlescape"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
[[package]]
name = "http"
version = "0.2.12"
@@ -2799,6 +2875,15 @@ dependencies = [
"hashbrown 0.15.4",
]
[[package]]
name = "lru"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198"
dependencies = [
"hashbrown 0.15.4",
]
[[package]]
name = "matchers"
version = "0.2.0"
@@ -3376,6 +3461,26 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -3716,12 +3821,17 @@ dependencies = [
"axum",
"base64ct",
"bcrypt",
"bytes",
"chrono",
"clap",
"dav-server",
"dotenvy",
"futures",
"futures-util",
"hostname",
"http-body-util",
"hyper 1.7.0",
"hyper-util",
"image",
"imageproc",
"infer",
@@ -3802,6 +3912,18 @@ dependencies = [
"syn 2.0.103",
]
[[package]]
name = "reflink-copy"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbed272e39c47a095a5242218a67412a220006842558b03fe2935e8f3d7b92"
dependencies = [
"cfg-if",
"libc",
"rustix 1.0.7",
"windows",
]
[[package]]
name = "regex"
version = "1.12.2"
@@ -4907,7 +5029,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix 1.0.7",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -5746,7 +5868,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -6171,12 +6293,27 @@ dependencies = [
"rustix 1.0.7",
]
[[package]]
name = "xml-rs"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
[[package]]
name = "xmlparser"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
[[package]]
name = "xmltree"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b619f8c85654798007fb10afa5125590b43b088c225a25fc2fec100a9fad0fc6"
dependencies = [
"xml-rs",
]
[[package]]
name = "y4m"
version = "0.8.0"

View File

@@ -80,7 +80,7 @@ stress-testing = ["test-utils"]
[dev-dependencies]
tempfile = "3"
wiremock = "0.6"
wiremock = "0.6"
tokio-test = "0.4"
futures = "0.3"
rand = "0.8"
@@ -89,6 +89,12 @@ testcontainers = "0.24"
testcontainers-modules = { version = "0.12", features = ["postgres"] }
# Dependencies for creating proper test Office documents
rust_xlsxwriter = "0.92" # For creating proper XLSX test files
# WebDAV server for realistic stress testing
dav-server = { version = "0.8", features = ["memfs"] }
hyper = { version = "1", features = ["server", "http1"] }
hyper-util = { version = "0.1", features = ["tokio"] }
http-body-util = "0.1"
bytes = "1"
# Enable test-utils feature for all tests
readur = { path = ".", features = ["test-utils"] }

16
stress-test-metrics.json Normal file
View File

@@ -0,0 +1,16 @@
{
"test_suite_version": "2.5.3",
"test_timestamp": "2025-12-15T20:10:14.695379292Z",
"overall_result": "PASSED",
"test_summary": {
"total_tests": 4,
"passed_tests": 4,
"failed_tests": 0,
"skipped_tests": 0
},
"recommendations": [
"WebDAV sync appears to be functioning correctly under stress conditions",
"No infinite loop patterns detected in current test scenarios",
"Consider running more intensive stress tests in staging environment"
]
}

View File

@@ -1,6 +1,6 @@
/*
* WebDAV Stress Testing Suite
*
*
* Comprehensive stress tests for WebDAV sync functionality with infinite loop detection.
* These tests create complex directory structures and monitor for problematic behavior
* patterns that could indicate infinite loops or performance issues.
@@ -15,9 +15,15 @@ use std::time::{Duration, Instant};
use tokio::sync::{Mutex, RwLock, Semaphore};
use tokio::time::{sleep, timeout, interval};
use tokio::net::TcpListener;
use axum::{Router, routing::any, http::StatusCode, response::IntoResponse};
use tracing::{debug, error, info, warn};
// dav-server imports for realistic WebDAV testing
use dav_server::{fakels::FakeLs, memfs::MemFs, DavHandler};
use http_body_util::{BodyExt, Full};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
#[cfg(feature = "stress-testing")]
use readur::services::webdav::{WebDAVService, WebDAVConfig};
@@ -31,17 +37,26 @@ fn init_tracing() {
});
}
/// Mock WebDAV server for testing
/// Realistic WebDAV server for testing using dav-server with in-memory filesystem
struct MockWebDAVServer {
port: u16,
server_handle: Option<tokio::task::JoinHandle<()>>,
shutdown_signal: Option<tokio::sync::oneshot::Sender<()>>,
}
impl MockWebDAVServer {
async fn start() -> Result<Self> {
let app = Router::new()
.route("/", any(mock_webdav_handler))
.fallback(mock_webdav_handler);
// Create in-memory filesystem with realistic test structure
let memfs = MemFs::new();
// Create the test directory structure that matches the test paths
Self::create_test_structure(&memfs).await?;
// Build the WebDAV handler
let dav_handler = DavHandler::builder()
.filesystem(memfs)
.locksystem(FakeLs::new())
.build_handler();
let listener = TcpListener::bind("127.0.0.1:0").await
.map_err(|e| anyhow!("Failed to bind to port: {}", e))?;
@@ -49,97 +64,197 @@ impl MockWebDAVServer {
.map_err(|e| anyhow!("Failed to get local address: {}", e))?
.port();
let (shutdown_tx, mut shutdown_rx) = tokio::sync::oneshot::channel::<()>();
let server_handle = tokio::spawn(async move {
if let Err(e) = axum::serve(listener, app).await {
error!("Mock WebDAV server error: {}", e);
loop {
tokio::select! {
result = listener.accept() => {
match result {
Ok((stream, _addr)) => {
let io = TokioIo::new(stream);
let handler = dav_handler.clone();
tokio::spawn(async move {
let service = service_fn(move |req| {
let handler = handler.clone();
async move {
let response = handler.handle(req).await;
// Convert DavResponse to hyper Response
let (parts, body) = response.into_parts();
let body_bytes = body.collect().await
.map(|c| c.to_bytes())
.unwrap_or_default();
Ok::<_, std::convert::Infallible>(
hyper::Response::from_parts(parts, Full::new(body_bytes))
)
}
});
if let Err(e) = http1::Builder::new()
.serve_connection(io, service)
.await
{
debug!("WebDAV connection error: {}", e);
}
});
}
Err(e) => {
error!("Failed to accept connection: {}", e);
}
}
}
_ = &mut shutdown_rx => {
info!("WebDAV server shutting down");
break;
}
}
}
});
// Give the server a moment to start
sleep(Duration::from_millis(100)).await;
info!("Mock WebDAV server started on port {}", port);
info!("Realistic WebDAV server (dav-server) started on port {}", port);
Ok(Self {
port,
server_handle: Some(server_handle),
shutdown_signal: Some(shutdown_tx),
})
}
/// Create a realistic test directory structure in the in-memory filesystem
async fn create_test_structure(memfs: &MemFs) -> Result<()> {
use dav_server::davpath::DavPath;
use dav_server::fs::{DavFile, DavFileSystem, OpenOptions};
use bytes::Bytes;
// Define the directory structure matching our test paths
let directories = vec![
"/main-structure",
"/main-structure/documents",
"/main-structure/images",
"/main-structure/archives",
"/loop-traps",
"/loop-traps/deep-nesting",
"/loop-traps/deep-nesting/level1",
"/loop-traps/deep-nesting/level1/level2",
"/loop-traps/deep-nesting/level1/level2/level3",
"/symlink-test",
"/symlink-test/folder1",
"/symlink-test/folder2",
"/test-repo-1",
"/test-repo-1/src",
"/test-repo-1/docs",
"/large-directory",
"/unicode-test",
"/unicode-test/subfolder1",
"/unicode-test/subfolder2",
];
// Create directories
for dir_path in &directories {
let path = DavPath::new(dir_path)
.map_err(|e| anyhow!("Invalid path {}: {:?}", dir_path, e))?;
if let Err(e) = memfs.create_dir(&path).await {
debug!("Directory {} may already exist: {:?}", dir_path, e);
}
}
// Helper to create a file with content
async fn create_file(memfs: &MemFs, file_path: &str, content: &str) -> Result<()> {
let path = DavPath::new(file_path)
.map_err(|e| anyhow!("Invalid path {}: {:?}", file_path, e))?;
let options = OpenOptions {
read: false,
write: true,
append: false,
truncate: true,
create: true,
create_new: false,
size: Some(content.len() as u64),
checksum: None,
};
let mut file = memfs.open(&path, options).await
.map_err(|e| anyhow!("Failed to create file {}: {:?}", file_path, e))?;
file.write_bytes(Bytes::from(content.to_string())).await
.map_err(|e| anyhow!("Failed to write to file {}: {:?}", file_path, e))?;
Ok(())
}
// Create some test files in various directories
let files = vec![
("/main-structure/readme.txt", "Main structure readme content"),
("/main-structure/documents/report.pdf", "Fake PDF content for testing"),
("/main-structure/documents/notes.txt", "Some notes here"),
("/main-structure/images/photo.jpg", "Fake JPEG data"),
("/loop-traps/trap-file.txt", "Loop trap test file"),
("/loop-traps/deep-nesting/nested-file.txt", "Deeply nested file"),
("/loop-traps/deep-nesting/level1/level2/level3/bottom.txt", "Bottom of nesting"),
("/symlink-test/test.txt", "Symlink test file"),
("/symlink-test/folder1/file1.txt", "File in folder1"),
("/symlink-test/folder2/file2.txt", "File in folder2"),
("/test-repo-1/README.md", "# Test Repository\n\nThis is a test."),
("/test-repo-1/src/main.rs", "fn main() { println!(\"Hello\"); }"),
("/test-repo-1/docs/guide.md", "# User Guide"),
("/unicode-test/subfolder1/test1.txt", "Unicode test content 1"),
("/unicode-test/subfolder2/test2.txt", "Unicode test content 2"),
];
let num_files = files.len();
// Add many files to large-directory for stress testing
for i in 0..50 {
let path_str = format!("/large-directory/file_{:04}.txt", i);
let content = format!("Content of file {}", i);
if let Err(e) = create_file(memfs, &path_str, &content).await {
debug!("Failed to create file {}: {:?}", path_str, e);
}
}
// Create the regular test files
for (file_path, content) in files {
if let Err(e) = create_file(memfs, file_path, content).await {
debug!("Failed to create file {}: {:?}", file_path, e);
}
}
info!("Created test directory structure with {} directories and {} files",
directories.len(), num_files + 50);
Ok(())
}
fn url(&self) -> String {
format!("http://127.0.0.1:{}", self.port)
}
async fn stop(&mut self) {
// Send shutdown signal
if let Some(tx) = self.shutdown_signal.take() {
let _ = tx.send(());
}
// Wait for server to stop
if let Some(handle) = self.server_handle.take() {
handle.abort();
let _ = handle.await; // Ignore the abort error
let _ = handle.await;
}
}
}
impl Drop for MockWebDAVServer {
fn drop(&mut self) {
if let Some(tx) = self.shutdown_signal.take() {
let _ = tx.send(());
}
if let Some(handle) = self.server_handle.take() {
handle.abort();
}
}
}
/// Mock WebDAV request handler that responds with valid WebDAV XML
async fn mock_webdav_handler(req: axum::extract::Request) -> impl IntoResponse {
let path = req.uri().path();
let method = req.method();
debug!("Mock WebDAV request: {} {}", method, path);
match method.as_str() {
"PROPFIND" => {
// Return a valid WebDAV PROPFIND response
let response_body = format!(r#"<?xml version="1.0" encoding="UTF-8"?>
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>{}</d:href>
<d:propstat>
<d:prop>
<d:resourcetype><d:collection/></d:resourcetype>
<d:displayname>{}</d:displayname>
<d:getlastmodified>Wed, 01 Jan 2025 12:00:00 GMT</d:getlastmodified>
<d:creationdate>2025-01-01T12:00:00Z</d:creationdate>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>{}/test-file.txt</d:href>
<d:propstat>
<d:prop>
<d:resourcetype/>
<d:displayname>test-file.txt</d:displayname>
<d:getcontentlength>42</d:getcontentlength>
<d:getlastmodified>Wed, 01 Jan 2025 12:00:00 GMT</d:getlastmodified>
<d:creationdate>2025-01-01T12:00:00Z</d:creationdate>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>"#, path, path.trim_start_matches('/'), path);
(StatusCode::MULTI_STATUS, [("Content-Type", "application/xml; charset=utf-8")], response_body)
},
"GET" => {
if path.ends_with(".txt") {
(StatusCode::OK, [("Content-Type", "text/plain")], "Mock file content".to_string())
} else {
(StatusCode::NOT_FOUND, [("Content-Type", "text/plain")], "Not found".to_string())
}
},
_ => {
(StatusCode::METHOD_NOT_ALLOWED, [("Content-Type", "text/plain")], "Method not allowed".to_string())
}
}
}
/// Circuit breaker for protecting against infinite loops and cascading failures
#[derive(Debug)]
pub struct CircuitBreaker {
@@ -755,6 +870,14 @@ fn get_stress_test_config(mock_server_url: Option<String>) -> Result<StressTestC
.unwrap_or_else(|_| "120".to_string()) // Shorter timeout for tests
.parse::<u64>()?;
// Calculate loop detection threshold based on stress level
// The test cycles through 8 paths, so each path is accessed (operation_count / 8) times
// We set the threshold to 3x the expected accesses to allow for legitimate concurrent access
// while still detecting actual infinite loops (which would have much higher access counts)
let num_test_paths = 8;
let expected_accesses_per_path = stress_level.operation_count() / num_test_paths;
let loop_detection_threshold = std::cmp::max(expected_accesses_per_path * 3, 20);
Ok(StressTestConfig {
webdav_server_url,
username,
@@ -762,7 +885,7 @@ fn get_stress_test_config(mock_server_url: Option<String>) -> Result<StressTestC
stress_level,
test_timeout_seconds,
max_concurrent_operations: 4, // Reduced for tests
loop_detection_threshold: 20, // Lower threshold for tests
loop_detection_threshold,
scan_timeout_seconds: 15, // Shorter scan timeout
})
}
@@ -774,11 +897,22 @@ async fn test_infinite_loop_detection() -> Result<()> {
info!("Starting infinite loop detection stress test");
// Start mock WebDAV server
let mut mock_server = MockWebDAVServer::start().await
.map_err(|e| anyhow!("Failed to start mock server: {}", e))?;
// Use real WebDAV server if WEBDAV_SERVER_URL is set (e.g., in CI with Dufs),
// otherwise fall back to mock server for local testing
let mut mock_server: Option<MockWebDAVServer> = None;
let config = if std::env::var("WEBDAV_SERVER_URL").is_ok() {
info!("Using real WebDAV server from WEBDAV_SERVER_URL environment variable");
get_stress_test_config(None)?
} else {
info!("No WEBDAV_SERVER_URL set, starting mock WebDAV server for local testing");
let server = MockWebDAVServer::start().await
.map_err(|e| anyhow!("Failed to start mock server: {}", e))?;
let url = server.url();
mock_server = Some(server);
get_stress_test_config(Some(url))?
};
let config = get_stress_test_config(Some(mock_server.url()))?;
info!("WebDAV server URL: {}", config.webdav_server_url);
let webdav_service = create_stress_test_webdav_service(&config)?;
let loop_monitor = Arc::new(LoopDetectionMonitor::new(config.loop_detection_threshold));
@@ -823,7 +957,9 @@ async fn test_infinite_loop_detection() -> Result<()> {
// Clean up monitoring resources
loop_monitor.stop_monitoring().await;
mock_server.stop().await;
if let Some(mut server) = mock_server {
server.stop().await;
}
cleanup_result
}
@@ -888,9 +1024,13 @@ async fn perform_loop_detection_test(
result.files.len(), result.directories.len(), path);
// If we find subdirectories, recursively scan some of them
for subdir in result.directories.iter().take(3) {
// Skip directories that match the parent path (mock server returns parent as a directory)
for subdir in result.directories.iter()
.filter(|d| d.relative_path != path && d.relative_path.trim_end_matches('/') != path)
.take(3)
{
monitor.record_directory_access(&subdir.relative_path).await;
match service.discover_files(&subdir.relative_path, false).await {
Ok(files) => {
debug!("Found {} files in subdirectory {}", files.len(), subdir.relative_path);
@@ -960,11 +1100,22 @@ async fn test_directory_scanning_stress() -> Result<()> {
info!("Starting directory scanning stress test");
// Start mock WebDAV server
let mut mock_server = MockWebDAVServer::start().await
.map_err(|e| anyhow!("Failed to start mock server: {}", e))?;
// Use real WebDAV server if WEBDAV_SERVER_URL is set (e.g., in CI with Dufs),
// otherwise fall back to mock server for local testing
let mut mock_server: Option<MockWebDAVServer> = None;
let config = if std::env::var("WEBDAV_SERVER_URL").is_ok() {
info!("Using real WebDAV server from WEBDAV_SERVER_URL environment variable");
get_stress_test_config(None)?
} else {
info!("No WEBDAV_SERVER_URL set, starting mock WebDAV server for local testing");
let server = MockWebDAVServer::start().await
.map_err(|e| anyhow!("Failed to start mock server: {}", e))?;
let url = server.url();
mock_server = Some(server);
get_stress_test_config(Some(url))?
};
let config = get_stress_test_config(Some(mock_server.url()))?;
info!("WebDAV server URL: {}", config.webdav_server_url);
let webdav_service = create_stress_test_webdav_service(&config)?;
// Test deep recursive scanning
@@ -1017,8 +1168,10 @@ async fn test_directory_scanning_stress() -> Result<()> {
}
};
// Clean up mock server
mock_server.stop().await;
// Clean up mock server if we started one
if let Some(mut server) = mock_server {
server.stop().await;
}
result
}
@@ -1233,11 +1386,22 @@ async fn test_concurrent_webdav_access() -> Result<()> {
info!("Starting concurrent WebDAV access stress test");
// Start mock WebDAV server
let mut mock_server = MockWebDAVServer::start().await
.map_err(|e| anyhow!("Failed to start mock server: {}", e))?;
// Use real WebDAV server if WEBDAV_SERVER_URL is set (e.g., in CI with Dufs),
// otherwise fall back to mock server for local testing
let mut mock_server: Option<MockWebDAVServer> = None;
let config = if std::env::var("WEBDAV_SERVER_URL").is_ok() {
info!("Using real WebDAV server from WEBDAV_SERVER_URL environment variable");
get_stress_test_config(None)?
} else {
info!("No WEBDAV_SERVER_URL set, starting mock WebDAV server for local testing");
let server = MockWebDAVServer::start().await
.map_err(|e| anyhow!("Failed to start mock server: {}", e))?;
let url = server.url();
mock_server = Some(server);
get_stress_test_config(Some(url))?
};
let config = get_stress_test_config(Some(mock_server.url()))?;
info!("WebDAV server URL: {}", config.webdav_server_url);
let webdav_service = create_stress_test_webdav_service(&config)?;
let concurrent_operations = config.stress_level.concurrent_operations();
@@ -1266,8 +1430,8 @@ async fn test_concurrent_webdav_access() -> Result<()> {
let test_paths = vec![
"/",
"/main-structure",
"/docs-structure",
"/images-structure",
"/loop-traps",
"/test-repo-1",
"/large-directory",
"/unicode-test",
];
@@ -1354,8 +1518,10 @@ async fn test_concurrent_webdav_access() -> Result<()> {
info!("Total operations: {} ({}% success rate)", total_operations, success_rate);
info!("Operations per second: {:.2}", total_operations as f64 / total_time.as_secs_f64());
// Clean up mock server
mock_server.stop().await;
// Clean up mock server if we started one
if let Some(mut server) = mock_server {
server.stop().await;
}
// Test passes if success rate is reasonable (>= 80%)
if success_rate >= 80.0 {
@@ -1374,11 +1540,22 @@ async fn test_edge_case_handling() -> Result<()> {
info!("Starting edge case handling stress test");
// Start mock WebDAV server
let mut mock_server = MockWebDAVServer::start().await
.map_err(|e| anyhow!("Failed to start mock server: {}", e))?;
// Use real WebDAV server if WEBDAV_SERVER_URL is set (e.g., in CI with Dufs),
// otherwise fall back to mock server for local testing
let mut mock_server: Option<MockWebDAVServer> = None;
let config = if std::env::var("WEBDAV_SERVER_URL").is_ok() {
info!("Using real WebDAV server from WEBDAV_SERVER_URL environment variable");
get_stress_test_config(None)?
} else {
info!("No WEBDAV_SERVER_URL set, starting mock WebDAV server for local testing");
let server = MockWebDAVServer::start().await
.map_err(|e| anyhow!("Failed to start mock server: {}", e))?;
let url = server.url();
mock_server = Some(server);
get_stress_test_config(Some(url))?
};
let config = get_stress_test_config(Some(mock_server.url()))?;
info!("WebDAV server URL: {}", config.webdav_server_url);
let webdav_service = create_stress_test_webdav_service(&config)?;
// Test various edge cases that might cause infinite loops or crashes
@@ -1452,8 +1629,10 @@ async fn test_edge_case_handling() -> Result<()> {
info!(" - Expected failures: {}", expected_failures);
info!(" - Timeouts: {}", timeouts);
// Clean up mock server
mock_server.stop().await;
// Clean up mock server if we started one
if let Some(mut server) = mock_server {
server.stop().await;
}
// Test passes if no timeouts occurred (timeouts suggest infinite loops)
if timeouts == 0 {