""" Integration tests for DataLoader with training workflows. These tests verify that DataLoader works correctly when integrated with actual training pipelines, not just in isolation. """ import numpy as np import sys import os # Add project root to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) # Try to import from package, fall back to dev module if not exported yet try: from tinytorch import Tensor from tinytorch.data.loader import Dataset, TensorDataset, DataLoader except (ImportError, ModuleNotFoundError): # Module not exported yet, use dev version sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'modules', 'source', '08_dataloader')) from dataloader_dev import Dataset, TensorDataset, DataLoader sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'modules', 'source', '01_tensor')) from tensor_dev import Tensor def test_training_workflow_integration(): """ Test DataLoader integration with realistic training workflow. Simulates: - Train/val split - DataLoader creation - Batch iteration - Complete epoch processing """ print("๐Ÿ”ฌ Integration Test: DataLoader + Training Workflow...") # Create synthetic dataset (simulate real data) num_samples = 1000 num_features = 20 num_classes = 5 features = np.random.randn(num_samples, num_features).astype(np.float32) labels = np.random.randint(0, num_classes, num_samples).astype(np.int64) dataset_full = TensorDataset(Tensor(features), Tensor(labels)) # Split into train/val (80/20 split) train_size = int(0.8 * num_samples) val_size = num_samples - train_size train_samples = [dataset_full[i] for i in range(train_size)] val_samples = [dataset_full[i] for i in range(train_size, num_samples)] # Create tensors from samples train_features = Tensor(np.stack([sample[0].data for sample in train_samples])) train_labels = Tensor(np.stack([sample[1].data for sample in train_samples])) val_features = Tensor(np.stack([sample[0].data for sample in val_samples])) val_labels = Tensor(np.stack([sample[1].data for sample in val_samples])) train_dataset = TensorDataset(train_features, train_labels) val_dataset = TensorDataset(val_features, val_labels) # Create DataLoaders batch_size = 32 train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False) print(f"๐Ÿ“Š Dataset splits:") print(f" Training: {len(train_dataset)} samples, {len(train_loader)} batches") print(f" Validation: {len(val_dataset)} samples, {len(val_loader)} batches") # Simulate training loop print("\n๐Ÿƒ Simulated Training Loop:") epoch_samples = 0 batch_count = 0 for batch_idx, (batch_features, batch_labels) in enumerate(train_loader): batch_count += 1 epoch_samples += len(batch_features.data) # Simulate forward pass (just check shapes) assert batch_features.data.shape[0] <= batch_size, "Batch size exceeded" assert batch_features.data.shape[1] == num_features, "Wrong feature count" assert len(batch_labels.data) == len(batch_features.data), "Mismatched batch sizes" if batch_idx < 3: # Show first few batches print(f" Batch {batch_idx + 1}: {batch_features.data.shape[0]} samples") print(f" Total: {batch_count} batches, {epoch_samples} samples processed") # Validate that all samples were seen assert epoch_samples == len(train_dataset), f"Expected {len(train_dataset)}, processed {epoch_samples}" print("โœ… Training workflow integration works correctly!") def test_dataloader_shuffle_consistency(): """Test that shuffle produces different orders across epochs.""" print("\n๐Ÿ”ฌ Integration Test: Shuffle Consistency...") # Create simple sequential dataset data = Tensor(np.arange(100).reshape(-1, 1).astype(np.float32)) labels = Tensor(np.arange(100).astype(np.int64)) dataset = TensorDataset(data, labels) loader = DataLoader(dataset, batch_size=10, shuffle=True) # Get first batch from two epochs epoch1_first = next(iter(loader))[0].data epoch2_first = next(iter(loader))[0].data # Should be different due to shuffle (very high probability) different = not np.array_equal(epoch1_first, epoch2_first) assert different, "Shuffle should produce different orders across epochs" print("โœ… Shuffle produces different orders across epochs") def test_dataloader_memory_efficiency(): """Test that DataLoader doesn't load entire dataset into memory at once.""" print("\n๐Ÿ”ฌ Integration Test: Memory Efficiency...") # Create large-ish dataset large_size = 10000 features = Tensor(np.random.randn(large_size, 50).astype(np.float32)) labels = Tensor(np.random.randint(0, 10, large_size).astype(np.int64)) dataset = TensorDataset(features, labels) loader = DataLoader(dataset, batch_size=64, shuffle=False) # Should be able to iterate without loading all at once batch_count = 0 for batch in loader: batch_count += 1 # Check batch is reasonable size assert batch[0].data.shape[0] <= 64 if batch_count > 10: # Just verify first few batches break print(f"โœ… Processed {batch_count} batches without loading entire dataset") if __name__ == "__main__": print("=" * 60) print("๐Ÿงช DATALOADER INTEGRATION TESTS") print("=" * 60) test_training_workflow_integration() test_dataloader_shuffle_consistency() test_dataloader_memory_efficiency() print("\n" + "=" * 60) print("๐ŸŽ‰ ALL INTEGRATION TESTS PASSED!") print("=" * 60)