mirror of
https://github.com/MLSysBook/TinyTorch.git
synced 2026-04-27 17:01:43 -05:00
Reorganize TinyTorch CLI from flat structure to hierarchical organization with subfolders for complex commands with subcommands. Changes: - Create subfolders: module/, system/, package/ - Move module commands: module_workflow.py → module/workflow.py - Move module_reset.py → module/reset.py - Move system commands: system.py → system/system.py - Move system subcommands: info.py, health.py, jupyter.py → system/ - Move package commands: package.py → package/package.py - Move package helpers: reset.py, nbdev.py → package/ - Archive deprecated files: clean.py, help.py, notebooks.py, status.py - Update all imports in moved files and main.py - Add __init__.py exports for each subfolder - Create comprehensive CLI test suite (52 tests) - test_cli_registry.py: Validate command registration - test_cli_execution.py: Smoke tests for all commands - test_cli_help_consistency.py: Help text validation - Update tests to match new structure Benefits: - Clear ownership: Easy to see which helpers belong to which commands - Better organization: Related files grouped together - Scales cleanly: Adding subcommands is straightforward - Zero user impact: All commands work exactly the same All 52 tests passing ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
206 lines
6.7 KiB
Python
206 lines
6.7 KiB
Python
"""
|
|
CLI Execution Tests - Smoke tests for each command
|
|
|
|
This test suite ensures:
|
|
1. Each command can be executed without crashing (help mode)
|
|
2. Commands with subcommands show their subcommand help
|
|
3. Error messages are helpful when commands fail
|
|
"""
|
|
|
|
import pytest
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add tito to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
|
|
from tito.main import TinyTorchCLI
|
|
|
|
|
|
class TestCommandExecution:
|
|
"""Test that all commands can be executed (smoke tests)."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test fixtures."""
|
|
self.cli = TinyTorchCLI()
|
|
self.project_root = Path(__file__).parent.parent.parent
|
|
|
|
def test_bare_tito_command(self):
|
|
"""Test bare 'tito' command shows welcome screen."""
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Should exit successfully
|
|
assert result.returncode == 0, f"Bare tito command failed: {result.stderr}"
|
|
|
|
# Should show welcome message
|
|
assert "Welcome to Tiny" in result.stdout or "TORCH" in result.stdout
|
|
assert "Command Groups:" in result.stdout or "Quick Start:" in result.stdout
|
|
|
|
def test_tito_help(self):
|
|
"""Test 'tito -h' shows help."""
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main', '-h'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
assert "usage: tito" in result.stdout
|
|
assert "positional arguments:" in result.stdout or "COMMAND" in result.stdout
|
|
|
|
def test_tito_version(self):
|
|
"""Test 'tito --version' shows version."""
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main', '--version'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
assert "Tiny" in result.stdout or "CLI" in result.stdout
|
|
|
|
@pytest.mark.parametrize("command", [
|
|
'setup', 'system', 'module', 'src', 'package', 'nbgrader',
|
|
'checkpoint', 'milestones', 'leaderboard', 'olympics',
|
|
'benchmark', 'community', 'export', 'test', 'book',
|
|
'grade', 'demo', 'logo'
|
|
])
|
|
def test_command_help_works(self, command):
|
|
"""Test that each command's help can be displayed."""
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main', command, '-h'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Help should always succeed
|
|
assert result.returncode == 0, (
|
|
f"Command '{command} -h' failed with exit code {result.returncode}\n"
|
|
f"stderr: {result.stderr}"
|
|
)
|
|
|
|
# Should show usage
|
|
assert "usage:" in result.stdout.lower(), (
|
|
f"Command '{command} -h' didn't show usage"
|
|
)
|
|
|
|
@pytest.mark.parametrize("command,subcommand", [
|
|
('system', 'info'),
|
|
('system', 'doctor'),
|
|
('module', 'status'),
|
|
('module', 'list'),
|
|
('checkpoint', 'status'),
|
|
('checkpoint', 'timeline'),
|
|
('leaderboard', 'profile'),
|
|
('milestones', 'status'),
|
|
])
|
|
def test_subcommand_help_works(self, command, subcommand):
|
|
"""Test that subcommands can show help."""
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main', command, subcommand, '-h'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Subcommand help should work
|
|
# Note: Some commands might return non-zero if not fully implemented
|
|
# but should at least not crash
|
|
assert result.returncode in [0, 1, 2], (
|
|
f"Command '{command} {subcommand} -h' crashed with exit code {result.returncode}"
|
|
)
|
|
|
|
|
|
class TestCommandGrouping:
|
|
"""Test that commands are properly grouped and discoverable."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test fixtures."""
|
|
self.project_root = Path(__file__).parent.parent.parent
|
|
|
|
def test_student_facing_commands_discoverable(self):
|
|
"""Test that main student-facing commands are easily discoverable."""
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Key student commands should be visible
|
|
student_commands = ['setup', 'module', 'checkpoint', 'milestones']
|
|
|
|
for cmd in student_commands:
|
|
assert cmd in result.stdout, (
|
|
f"Student command '{cmd}' not visible in welcome screen"
|
|
)
|
|
|
|
def test_developer_commands_documented(self):
|
|
"""Test that developer commands are documented in help."""
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main', '-h'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Developer commands should be in help
|
|
dev_commands = ['src', 'package', 'nbgrader']
|
|
|
|
for cmd in dev_commands:
|
|
assert cmd in result.stdout, (
|
|
f"Developer command '{cmd}' not in help text"
|
|
)
|
|
|
|
|
|
class TestErrorMessages:
|
|
"""Test that error messages are helpful."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test fixtures."""
|
|
self.project_root = Path(__file__).parent.parent.parent
|
|
|
|
def test_invalid_command_shows_help(self):
|
|
"""Test that invalid commands show helpful error."""
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main', 'nonexistent'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Should fail
|
|
assert result.returncode != 0
|
|
|
|
# Should mention the invalid command
|
|
combined_output = result.stdout + result.stderr
|
|
assert 'nonexistent' in combined_output or 'invalid choice' in combined_output.lower()
|
|
|
|
def test_missing_subcommand_shows_help(self):
|
|
"""Test that missing subcommands show help."""
|
|
# Try module command without subcommand
|
|
result = subprocess.run(
|
|
[sys.executable, '-m', 'tito.main', 'module'],
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Should show help or error
|
|
# Some commands might have default behavior, others require subcommand
|
|
combined_output = result.stdout + result.stderr
|
|
assert len(combined_output) > 0, "No output from command without subcommand"
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, '-v'])
|