1+ name : Test with Optional Dependencies
2+
3+ on :
4+ push :
5+ branches : [ master, main ]
6+ pull_request :
7+ branches : [ master, main ]
8+ schedule :
9+ # Run weekly to catch issues with dependency updates
10+ - cron : ' 0 0 * * 0'
11+
12+ jobs :
13+ test-extras :
14+ name : Test with ${{ matrix.extras }} on Python ${{ matrix.python-version }}
15+ runs-on : ubuntu-latest
16+ strategy :
17+ fail-fast : false
18+ matrix :
19+ python-version : ['3.9', '3.11']
20+ extras :
21+ - ' tornado'
22+ - ' flask'
23+ - ' jinja2'
24+ - ' redis'
25+ - ' twisted'
26+ - ' tornado,flask,jinja2'
27+
28+ steps :
29+ - uses : actions/checkout@v4
30+
31+ - name : Set up Python ${{ matrix.python-version }}
32+ uses : actions/setup-python@v4
33+ with :
34+ python-version : ${{ matrix.python-version }}
35+
36+ - name : Install dependencies with ${{ matrix.extras }}
37+ run : |
38+ python -m pip install --upgrade pip
39+ pip install -e ".[${{ matrix.extras }}]"
40+
41+ - name : Create test runner script
42+ run : |
43+ cat > run_tests_with_extras.py << 'EOF'
44+ import sys
45+ import unittest
46+ import importlib
47+ import os
48+
49+ # Define which tests require which packages
50+ EXTRA_TEST_REQUIREMENTS = {
51+ 'tests.ioc.extra.tornado.test_handler': ['tornado'],
52+ 'tests.ioc.extra.tornado.test_router': ['tornado', 'werkzeug'],
53+ 'tests.ioc.extra.jinja.test_helper': ['jinja2'],
54+ }
55+
56+ def check_module_available(module_name):
57+ """Check if a module can be imported."""
58+ try:
59+ importlib.import_module(module_name)
60+ return True
61+ except ImportError:
62+ return False
63+
64+ def should_skip_test(test_module):
65+ """Check if a test should be skipped due to missing dependencies."""
66+ if test_module in EXTRA_TEST_REQUIREMENTS:
67+ required_modules = EXTRA_TEST_REQUIREMENTS[test_module]
68+ for req in required_modules:
69+ if not check_module_available(req):
70+ return True, req
71+ return False, None
72+
73+ def discover_and_run_tests():
74+ """Discover and run tests, skipping those with missing dependencies."""
75+ loader = unittest.TestLoader()
76+ suite = unittest.TestSuite()
77+
78+ # Discover all tests
79+ discovered_suite = loader.discover('tests', pattern='test_*.py')
80+
81+ # Track skipped tests
82+ skipped_tests = []
83+
84+ # Filter tests based on available dependencies
85+ for test_group in discovered_suite:
86+ for test_case in test_group:
87+ if hasattr(test_case, '__module__'):
88+ module_name = test_case.__module__
89+ should_skip, missing_module = should_skip_test(module_name)
90+ if should_skip:
91+ skipped_tests.append((module_name, missing_module))
92+ else:
93+ suite.addTest(test_case)
94+ elif hasattr(test_case, '_tests'):
95+ # Handle test suites
96+ for test in test_case._tests:
97+ if hasattr(test, '__module__'):
98+ module_name = test.__module__
99+ should_skip, missing_module = should_skip_test(module_name)
100+ if should_skip:
101+ skipped_tests.append((module_name, missing_module))
102+ else:
103+ suite.addTest(test)
104+ else:
105+ suite.addTest(test)
106+ else:
107+ # Fallback: try to check if it's a failed import
108+ test_str = str(test_case)
109+ if 'FailedTest' in test_str:
110+ # This is a failed import, skip it
111+ skipped_tests.append((test_str, 'import failed'))
112+ else:
113+ suite.addTest(test_case)
114+
115+ # Print summary of skipped tests
116+ if skipped_tests:
117+ print("\n" + "="*70)
118+ print("SKIPPED TESTS DUE TO MISSING DEPENDENCIES:")
119+ for test_module, missing in set(skipped_tests):
120+ print(f" - {test_module} (missing: {missing})")
121+ print("="*70 + "\n")
122+
123+ # Run the filtered test suite
124+ runner = unittest.TextTestRunner(verbosity=2)
125+ result = runner.run(suite)
126+
127+ # Return appropriate exit code
128+ if result.wasSuccessful():
129+ return 0
130+ else:
131+ # Check if all failures are import errors
132+ if hasattr(result, 'errors') and result.errors:
133+ import_errors = sum(1 for error in result.errors
134+ if 'ImportError' in str(error[1]) or 'ModuleNotFoundError' in str(error[1]))
135+ if import_errors == len(result.errors) and result.failures == []:
136+ print("\nAll errors were import errors - this is expected for optional dependencies")
137+ return 0
138+ return 1
139+
140+ if __name__ == '__main__':
141+ sys.exit(discover_and_run_tests())
142+ EOF
143+
144+ - name : Run tests with smart dependency detection
145+ run : |
146+ python run_tests_with_extras.py
0 commit comments