Files
cs249r_book/tinytorch/tests/environment/test_setup_validation.py
Vijay Janapa Reddi 42face28fb refactor(tests): remove all pytest.skip patterns for honest test results
- Move imports to module level in all *_core.py test files (16 files)
- Remove try/except/skip patterns from integration tests
- Remove @pytest.mark.skip decorators from gradient flow tests
- Convert environment validation skips to warnings for optional checks
- Change milestone tests from skip to fail when scripts missing

Tests now either pass or fail - no silent skipping that hides issues.
This ensures the test suite provides accurate feedback about what works.
2026-01-23 23:06:23 -05:00

436 lines
15 KiB
Python

"""
Environment Setup Validation Tests
These tests verify that the TinyTorch environment is correctly configured
and all dependencies work as expected. Run these after `tito setup` to
ensure students can actually use TinyTorch.
Usage:
pytest tests/environment/test_setup_validation.py -v
Or via TITO:
tito system health --verify
"""
import sys
import os
import subprocess
import tempfile
from pathlib import Path
import pytest
class TestPythonEnvironment:
"""Verify Python environment is correctly configured."""
def test_python_version(self):
"""Python version must be 3.8 or higher."""
assert sys.version_info >= (3, 8), (
f"Python 3.8+ required, got {sys.version_info.major}.{sys.version_info.minor}"
)
print(f"✅ Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")
def test_virtual_environment_active(self):
"""Virtual environment should be active."""
# Check if we're in a virtual environment
in_venv = (
os.environ.get('VIRTUAL_ENV') is not None or
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix) or
hasattr(sys, 'real_prefix')
)
if not in_venv:
print("⚠️ Virtual environment not active (optional but recommended)")
else:
print(f"✅ Virtual environment active: {sys.prefix}")
def test_pip_available(self):
"""pip must be available for package management."""
result = subprocess.run(
[sys.executable, "-m", "pip", "--version"],
capture_output=True,
text=True
)
assert result.returncode == 0, "pip not available"
print(f"✅ pip available: {result.stdout.strip()}")
class TestCoreDependencies:
"""Verify core dependencies are installed and working."""
def test_numpy_import(self):
"""NumPy must be importable."""
import numpy as np
print(f"✅ NumPy {np.__version__} imported")
def test_numpy_operations(self):
"""NumPy must work for basic operations."""
import numpy as np
# Create arrays
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# Basic operations
c = a + b
assert np.allclose(c, [5, 7, 9]), "NumPy addition failed"
# Matrix operations
m = np.array([[1, 2], [3, 4]])
result = m @ m.T
expected = np.array([[5, 11], [11, 25]])
assert np.allclose(result, expected), "NumPy matmul failed"
print("✅ NumPy operations work correctly")
def test_matplotlib_import(self):
"""Matplotlib is optional - warn if not installed."""
try:
import matplotlib
import matplotlib.pyplot as plt
print(f"✅ Matplotlib {matplotlib.__version__} imported (optional)")
except ImportError:
print("⚠️ Matplotlib not installed (optional dependency)")
def test_matplotlib_plotting(self):
"""Matplotlib plotting is optional - warn if not installed."""
try:
import matplotlib
matplotlib.use('Agg') # Non-GUI backend for testing
import matplotlib.pyplot as plt
# Create a simple plot
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
# Save to temporary file
with tempfile.NamedTemporaryFile(suffix='.png', delete=True) as tmp:
fig.savefig(tmp.name)
assert Path(tmp.name).exists(), "Failed to save plot"
plt.close(fig)
print("✅ Matplotlib can create and save plots (optional)")
except ImportError:
print("⚠️ Matplotlib not installed - plotting tests skipped (optional dependency)")
def test_pytest_available(self):
"""pytest must be available for testing."""
result = subprocess.run(
[sys.executable, "-m", "pytest", "--version"],
capture_output=True,
text=True
)
assert result.returncode == 0, "pytest not available"
print(f"✅ pytest available: {result.stdout.strip()}")
def test_yaml_import(self):
"""PyYAML must be importable."""
import yaml
# Test YAML operations
data = {'key': 'value', 'number': 42}
yaml_str = yaml.dump(data)
loaded = yaml.safe_load(yaml_str)
assert loaded == data, "YAML serialization failed"
print(f"✅ PyYAML {yaml.__version__} imported and working")
def test_rich_import(self):
"""Rich must be importable for CLI output."""
from rich.console import Console
from rich.panel import Panel
# Test Rich can create output
console = Console()
panel = Panel("Test", title="Test Panel")
# Render to string to verify it works
with console.capture() as capture:
console.print(panel)
output = capture.get()
assert len(output) > 0, "Rich rendering failed"
print("✅ Rich console library working")
class TestJupyterEnvironment:
"""Verify Jupyter/JupyterLab is correctly configured."""
def test_jupyter_import(self):
"""Jupyter must be importable."""
import jupyter
print("✅ Jupyter installed")
def test_jupyterlab_import(self):
"""JupyterLab must be importable."""
import jupyterlab
print(f"✅ JupyterLab {jupyterlab.__version__} installed")
def test_jupyter_command_available(self):
"""Jupyter command must be available."""
result = subprocess.run(
["jupyter", "--version"],
capture_output=True,
text=True
)
assert result.returncode == 0, "jupyter command not found"
print(f"✅ jupyter command available:\n{result.stdout.strip()}")
def test_jupyter_lab_command(self):
"""JupyterLab command must be available."""
result = subprocess.run(
["jupyter", "lab", "--version"],
capture_output=True,
text=True
)
assert result.returncode == 0, "jupyter lab command not found"
print(f"✅ jupyter lab command available: {result.stdout.strip()}")
def test_jupyter_kernelspec(self):
"""Jupyter kernel must be configured."""
result = subprocess.run(
["jupyter", "kernelspec", "list"],
capture_output=True,
text=True
)
assert result.returncode == 0, "Cannot list Jupyter kernels"
assert "python3" in result.stdout, "Python3 kernel not found"
print(f"✅ Jupyter kernel configured:\n{result.stdout.strip()}")
def test_jupytext_available(self):
"""Jupytext must be available for .py ↔ .ipynb conversion."""
import jupytext
print(f"✅ Jupytext {jupytext.__version__} available")
class TestTinyTorchPackage:
"""Verify TinyTorch package is correctly installed."""
def test_tinytorch_import(self):
"""TinyTorch package must be importable."""
import tinytorch
print(f"✅ TinyTorch package imported from {tinytorch.__file__}")
def test_tinytorch_core_import(self):
"""TinyTorch core modules must be importable."""
from tinytorch import core
print("✅ TinyTorch core module available")
def test_tinytorch_version(self):
"""TinyTorch must have version info."""
import tinytorch
assert hasattr(tinytorch, '__version__'), "TinyTorch version not defined"
print(f"✅ TinyTorch version: {tinytorch.__version__}")
def test_tinytorch_tensor_import(self):
"""Tensor class must be importable."""
from tinytorch import Tensor
assert Tensor is not None, "Tensor class not available"
print("✅ Tensor class available")
class TestProjectStructure:
"""Verify project directory structure is correct."""
def test_root_directory_exists(self):
"""Project root must exist with expected structure."""
project_root = Path.cwd()
assert project_root.exists(), "Project root not found"
print(f"✅ Project root: {project_root}")
def test_tinytorch_package_directory(self):
"""tinytorch/ package directory must exist."""
tinytorch_dir = Path("tinytorch")
assert tinytorch_dir.exists(), "tinytorch/ directory not found"
assert tinytorch_dir.is_dir(), "tinytorch/ is not a directory"
print(f"✅ Package directory: {tinytorch_dir.absolute()}")
def test_tinytorch_init_file(self):
"""tinytorch/__init__.py must exist."""
init_file = Path("tinytorch/__init__.py")
assert init_file.exists(), "tinytorch/__init__.py not found"
print(f"✅ Package init: {init_file.absolute()}")
def test_modules_directory(self):
"""modules/ directory must exist for student work."""
modules_dir = Path("modules")
assert modules_dir.exists(), "modules/ directory not found"
assert modules_dir.is_dir(), "modules/ is not a directory"
print(f"✅ Modules directory: {modules_dir.absolute()}")
def test_src_directory(self):
"""src/ directory must exist with source modules."""
src_dir = Path("src")
assert src_dir.exists(), "src/ directory not found"
assert src_dir.is_dir(), "src/ is not a directory"
# Count module directories
module_dirs = [d for d in src_dir.iterdir() if d.is_dir() and d.name.startswith('0')]
print(f"✅ Source directory: {src_dir.absolute()} ({len(module_dirs)} modules)")
def test_tests_directory(self):
"""tests/ directory must exist."""
tests_dir = Path("tests")
assert tests_dir.exists(), "tests/ directory not found"
assert tests_dir.is_dir(), "tests/ is not a directory"
print(f"✅ Tests directory: {tests_dir.absolute()}")
def test_tito_cli_exists(self):
"""TITO CLI must be available."""
# Try to import tito
try:
import tito
print(f"✅ TITO CLI available: {tito.__file__}")
except ImportError:
pytest.fail("TITO CLI not importable")
class TestSystemResources:
"""Verify system has adequate resources for TinyTorch development."""
def test_disk_space_available(self):
"""At least 1GB disk space should be available."""
import shutil
stat = shutil.disk_usage(Path.cwd())
free_gb = stat.free / (1024**3)
assert free_gb >= 1.0, f"Low disk space: {free_gb:.1f}GB (need at least 1GB)"
print(f"✅ Disk space: {free_gb:.1f}GB available")
def test_memory_available(self):
"""Check available system memory."""
try:
import psutil
mem = psutil.virtual_memory()
free_gb = mem.available / (1024**3)
total_gb = mem.total / (1024**3)
if free_gb < 2.0:
print(f"⚠️ Low memory: {free_gb:.1f}GB free / {total_gb:.1f}GB total (may cause issues)")
else:
print(f"✅ Memory: {free_gb:.1f}GB free / {total_gb:.1f}GB total")
except ImportError:
print("⚠️ psutil not available - memory check skipped (optional)")
def test_python_interpreter_architecture(self):
"""Check Python interpreter architecture."""
import platform
arch = platform.machine()
system = platform.system()
print(f"✅ Architecture: {arch} on {system}")
# Warn about Rosetta on Apple Silicon
if system == "Darwin" and arch == "x86_64":
try:
result = subprocess.run(
["sysctl", "-n", "machdep.cpu.brand_string"],
capture_output=True,
text=True
)
if "Apple" in result.stdout:
print("⚠️ Running x86_64 Python on Apple Silicon (Rosetta)")
print(" Consider using native arm64 Python for better performance")
except:
pass
class TestGitConfiguration:
"""Verify Git is configured for version control."""
def test_git_available(self):
"""Git command must be available."""
result = subprocess.run(
["git", "--version"],
capture_output=True,
text=True
)
assert result.returncode == 0, "git command not found"
print(f"✅ Git available: {result.stdout.strip()}")
def test_git_user_configured(self):
"""Git user.name and user.email should be configured."""
name_result = subprocess.run(
["git", "config", "user.name"],
capture_output=True,
text=True
)
email_result = subprocess.run(
["git", "config", "user.email"],
capture_output=True,
text=True
)
if name_result.returncode != 0 or email_result.returncode != 0:
print("⚠️ Git user not configured (optional but recommended)")
else:
print(f"✅ Git user configured: {name_result.stdout.strip()} <{email_result.stdout.strip()}>")
def test_git_repository_initialized(self):
"""Project should be a git repository."""
git_dir = Path(".git")
if not git_dir.exists():
print("⚠️ Not a git repository (optional)")
else:
print("✅ Git repository initialized")
class TestStudentProtection:
"""Verify student protection system is configured."""
def test_src_directory_readable(self):
"""Source directory should be readable."""
src_dir = Path("src")
assert src_dir.exists(), "src/ directory not found"
# Try to read a file
module_dirs = list(src_dir.glob("0*"))
if module_dirs:
test_file = list(module_dirs[0].glob("*.py"))
if test_file:
content = test_file[0].read_text()
assert len(content) > 0, "Cannot read source files"
print(f"✅ Source files readable: {test_file[0]}")
def run_all_validation_tests():
"""
Run all validation tests and provide a summary.
This is called by `tito system health --verify` to ensure
the environment is correctly configured.
"""
import pytest
# Run tests with verbose output
args = [
__file__,
"-v",
"--tb=short",
"--color=yes"
]
exit_code = pytest.main(args)
if exit_code == 0:
print("\n" + "="*70)
print("🎉 All validation tests passed!")
print("✅ TinyTorch environment is correctly configured")
print("💡 Next: tito module 01")
print("="*70)
else:
print("\n" + "="*70)
print("❌ Some validation tests failed")
print("🔧 Please fix the issues above and run: tito system health --verify")
print("="*70)
return exit_code
if __name__ == "__main__":
import sys
sys.exit(run_all_validation_tests())