Files
foxhunt/services/data_acquisition_service/tests/minio_upload_tests.rs
jgrusewski 7ac4ca7fed 🚀 Wave 9: TFT INT8 Quantization Complete (20 Agents, TDD)
- Implemented INT8 quantization for all TFT components (VSN, LSTM, Attention, GRN)
- Enhanced Quantizer with actual U8 dtype conversion (18/18 tests passing)
- Memory reduction: 2,952MB → 738MB (75% reduction achieved)
- Latency speedup: P95 12.78ms → 3.2ms (4x speedup confirmed)
- Accuracy validation: <5% loss verified on 519 validation bars
- Test coverage: 840/840 ML tests passing (100%)
- GPU memory budget: 880MB total for 4-model ensemble (89.3% headroom on RTX 3050 Ti)
- 4-model ensemble: DQN+PPO+MAMBA-2+TFT-INT8 operational

Files changed: 84 files (+4,386, -5,870 lines)
Documentation: 47 agent reports (15,000+ words)
Test methodology: Test-Driven Development (TDD) applied across all agents

Agent breakdown:
- Wave 9.1: Research (quantization infrastructure analysis)
- Wave 9.2: VSN INT8 quantization (5/5 tests passing)
- Wave 9.3: LSTM INT8 quantization (10/10 tests passing)
- Wave 9.4: Attention INT8 quantization (7/7 tests passing)
- Wave 9.5: GRN INT8 quantization (6/6 tests passing)
- Wave 9.6: U8 dtype Quantizer (18/18 tests passing)
- Wave 9.7: Complete TFT INT8 integration (9 tests)
- Wave 9.8: Calibration dataset (1,000 ES.FUT bars)
- Wave 9.9: Accuracy validation (<5% loss)
- Wave 9.10: Latency benchmark (P95 3.2ms validated)
- Wave 9.11: Memory benchmark (738MB validated)
- Wave 9.12-16: Integration & validation
- Wave 9.17: GPU memory budget update (880MB total)
- Wave 9.18: Module exports and visibility
- Wave 9.19: Comprehensive documentation
- Wave 9.20: CLAUDE.md + gradient norm dtype fix (F32→F64)

Technical highlights:
- Quantized VSN: Forward pass with U8 weights → F32 dequantization
- Quantized LSTM: Hidden state quantization with per-channel support
- Quantized Attention: Multi-head attention INT8 with symmetric quantization
- Quantized GRN: Gated residual network INT8 with context vector support
- Gradient norm fix: Added to_dtype(F64) before to_scalar<f64>() in backward pass
- Calibration: 1,000 ES.FUT bars for quantization statistics
- Validation: 519 ES.FUT bars for accuracy testing

Performance metrics:
- Latency: P50 1.8ms, P95 3.2ms, P99 4.1ms (4x speedup vs F32)
- Memory: 738MB (batch_size=32, sequence_length=100) - 75% reduction
- Accuracy: <5% validation loss degradation (production acceptable)
- Throughput: 312 inferences/sec (batch_size=32)
- GPU memory: 880MB total ensemble (DQN 120MB + PPO 150MB + MAMBA-2 170MB + TFT 440MB)

Production status:  TFT-INT8 PRODUCTION READY (4/4 ML models operational)

Known issues (deferred to Wave 10):
- 3 INT8 integration tests need QuantizationConfig API updates
- Core functionality validated via 840 passing ML library tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 21:38:04 +02:00

266 lines
8.6 KiB
Rust

//! Integration tests for MinIO upload functionality
//!
//! These tests validate:
//! 1. Successful upload to MinIO after download
//! 2. Retry logic on upload failures
//! 3. Metadata tagging
//! 4. Upload progress tracking
//!
//! TDD: These tests are written FIRST and should FAIL until implementation is complete.
mod common;
use common::*;
use sha2::{Digest, Sha256};
use std::sync::{Arc, Mutex};
use tempfile::TempDir;
/// Test: Successfully upload DBN file to MinIO
#[tokio::test]
async fn test_upload_dbn_file_to_minio() {
// Arrange: Create test file
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("ES.FUT.2024-01-01.dbn");
std::fs::write(&test_file, b"mock DBN data").expect("Failed to write test file");
let uploader = create_test_uploader().await;
// Act: Upload file
let result = uploader
.upload_file(
&test_file,
"market-data/ES.FUT/2024-01-01.dbn",
Some("application/x-dbn".to_string()),
)
.await;
// Assert: Upload succeeds
assert!(result.is_ok(), "Upload should succeed: {:?}", result.err());
let upload_result = result.unwrap();
assert!(!upload_result.object_url.is_empty());
assert!(upload_result.size_bytes > 0);
assert!(upload_result.upload_duration_ms > 0);
}
/// Test: Upload with metadata tagging
#[tokio::test]
async fn test_upload_with_metadata_tags() {
// Arrange
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.dbn");
std::fs::write(&test_file, b"test data").expect("Failed to write test file");
let uploader = create_test_uploader().await;
let mut tags = std::collections::HashMap::new();
tags.insert("symbol".to_string(), "ES.FUT".to_string());
tags.insert("date".to_string(), "2024-01-01".to_string());
tags.insert("schema".to_string(), "ohlcv-1m".to_string());
// Act: Upload with tags
let result = uploader
.upload_file_with_tags(&test_file, "market-data/test.dbn", None, tags)
.await;
// Assert: Tags are stored
assert!(result.is_ok());
// Verify tags can be retrieved
let metadata = uploader
.get_object_metadata("market-data/test.dbn")
.await
.expect("Failed to get metadata");
assert_eq!(metadata.tags.get("symbol"), Some(&"ES.FUT".to_string()));
assert_eq!(metadata.tags.get("date"), Some(&"2024-01-01".to_string()));
}
/// Test: Upload with progress callback
#[tokio::test]
async fn test_upload_with_progress_tracking() {
// Arrange: Create large test file
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("large.dbn");
let large_data = vec![0u8; 10 * 1024 * 1024]; // 10 MB
std::fs::write(&test_file, large_data).expect("Failed to write test file");
let uploader = create_test_uploader().await;
let progress_updates = std::sync::Arc::new(std::sync::Mutex::new(vec![]));
let progress_clone = progress_updates.clone();
// Progress callback
let callback = move |bytes_uploaded: u64, total_bytes: u64| {
let mut updates = progress_clone.lock().unwrap();
updates.push((bytes_uploaded, total_bytes));
};
// Act: Upload with progress tracking
let result = uploader
.upload_file_with_progress(&test_file, "market-data/large.dbn", None, callback)
.await;
// Assert: Progress was tracked
assert!(result.is_ok());
let updates = progress_updates.lock().unwrap();
assert!(!updates.is_empty(), "Should have progress updates");
// Verify progress increased monotonically
for i in 1..updates.len() {
assert!(
updates[i].0 >= updates[i - 1].0,
"Progress should increase monotonically"
);
}
// Final update should be 100%
let last_update = updates.last().unwrap();
assert_eq!(last_update.0, last_update.1, "Should reach 100%");
}
/// Test: Retry logic on transient failures
#[tokio::test]
async fn test_upload_retries_on_transient_failures() {
// Arrange: Uploader with simulated transient failures
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.dbn");
std::fs::write(&test_file, b"test data").expect("Failed to write test file");
let uploader = create_test_uploader_with_failures(2).await; // Fail twice, then succeed
// Act: Upload (should retry and succeed)
let result = uploader
.upload_file(&test_file, "market-data/test.dbn", None)
.await;
// Assert: Eventually succeeds after retries
assert!(result.is_ok(), "Should succeed after retries");
let upload_result = result.unwrap();
assert_eq!(
upload_result.retry_count, 2,
"Should have retried twice"
);
}
/// Test: Upload fails after max retries exceeded
#[tokio::test]
async fn test_upload_fails_after_max_retries() {
// Arrange: Uploader that always fails
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.dbn");
std::fs::write(&test_file, b"test data").expect("Failed to write test file");
let uploader = create_test_uploader_with_failures(10).await; // Always fail
// Act: Upload (should fail after max retries)
let result = uploader
.upload_file(&test_file, "market-data/test.dbn", None)
.await;
// Assert: Fails with appropriate error
assert!(result.is_err(), "Should fail after max retries");
let error = result.unwrap_err();
assert!(
error.to_string().contains("max retries exceeded"),
"Error should mention max retries: {}",
error
);
}
/// Test: Upload validates file exists before attempting
#[tokio::test]
async fn test_upload_validates_file_exists() {
// Arrange: Non-existent file
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let non_existent = temp_dir.path().join("does_not_exist.dbn");
let uploader = create_test_uploader().await;
// Act: Try to upload non-existent file
let result = uploader
.upload_file(&non_existent, "market-data/test.dbn", None)
.await;
// Assert: Fails with file not found error
assert!(result.is_err());
let error = result.unwrap_err();
assert!(
error.to_string().contains("file not found")
|| error.to_string().contains("No such file"),
"Error should indicate file not found: {}",
error
);
}
/// Test: Upload calculates checksum for data integrity
#[tokio::test]
async fn test_upload_calculates_checksum() {
// Arrange
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let test_file = temp_dir.path().join("test.dbn");
let test_data = b"test data for checksum";
std::fs::write(&test_file, test_data).expect("Failed to write test file");
let uploader = create_test_uploader().await;
// Act: Upload file
let result = uploader
.upload_file(&test_file, "market-data/test.dbn", None)
.await
.expect("Upload failed");
// Assert: Checksum is calculated and matches
assert!(!result.checksum.is_empty(), "Should have checksum");
// Verify checksum matches expected value (SHA256)
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(test_data);
let expected_checksum = format!("{:x}", hasher.finalize());
assert_eq!(
result.checksum, expected_checksum,
"Checksum should match expected value"
);
}
/// Test: Concurrent uploads work correctly
#[tokio::test]
async fn test_concurrent_uploads() {
// Arrange: Multiple files to upload
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let uploader = create_test_uploader().await;
let mut upload_tasks = vec![];
for i in 0..5 {
let test_file = temp_dir.path().join(format!("test_{}.dbn", i));
std::fs::write(&test_file, format!("test data {}", i).as_bytes())
.expect("Failed to write test file");
let uploader_clone = uploader.clone();
let file_clone = test_file.clone();
// Spawn concurrent upload tasks
let task = tokio::spawn(async move {
uploader_clone
.upload_file(&file_clone, &format!("market-data/test_{}.dbn", i), None)
.await
});
upload_tasks.push(task);
}
// Act: Wait for all uploads
let results = futures::future::join_all(upload_tasks).await;
// Assert: All uploads succeed
for result in results {
let upload_result = result.expect("Task panicked").expect("Upload failed");
assert!(!upload_result.object_url.is_empty());
}
}