Files
automatic/test/test-compile.py
Vladimir Mandic 35aa8c6422 fix openpose and add test-compile
Signed-off-by: Vladimir Mandic <mandic00@live.com>
2026-06-02 17:06:18 +02:00

185 lines
6.8 KiB
Python

#!/usr/bin/env python3
import io
import os
import sys
import ast
import logging
import contextlib
import py_compile
import importlib.util
import importlib.machinery
from pathlib import Path
includes = [
{ 'folder': '.', 'recursive': False, 'includes': True },
{ 'folder': 'pipelines', 'recursive': True, 'includes': True },
{ 'folder': 'modules', 'recursive': True, 'includes': False },
{ 'folder': 'scripts', 'recursive': True, 'local': True, 'includes': False },
]
excludes = [
'node_modules',
'__pycache__',
]
ignores = [
'torch_directml',
'intel_extension_for_pytorch',
'torch_xla.core.xla_model',
'flash_attn',
'flash_attn_interface',
'openai',
'rembg',
'controlnet_aux',
'image_gen_aux',
'torchsde',
'ligo.segments',
'torchdiffeq',
'insightface',
'pynvml',
]
output = '/tmp/pycompile'
root = Path('.')
def test_compile(folder: str, recursive: bool):
stats = { 'ok': [], 'failed': [], 'errors': [] }
for entry in os.scandir(folder):
if not any(exclude in entry.path for exclude in excludes) and not entry.name.startswith('.'):
if entry.is_file() and entry.name.endswith('.py'):
try:
cfile = os.path.join(output, os.path.relpath(entry.path, start='.')) + 'c'
py_compile.compile(entry.path, cfile=cfile, doraise=True)
stats['ok'].append(entry.path)
except Exception as e:
print(f'fail: file={entry.path} error={e}')
stats['failed'].append(entry.path)
stats['errors'].append(str(e))
elif entry.is_dir() and recursive:
nested_stats = test_compile(entry.path, recursive)
stats['ok'].extend(nested_stats['ok'])
stats['failed'].extend(nested_stats['failed'])
stats['errors'].extend(nested_stats['errors'])
if len(stats["ok"]) > 0 or len(stats["failed"]) > 0:
print(f'Compile: folder={folder} ok={len(stats["ok"])} failed={len(stats["failed"])}')
return stats
def list_imports(path: Path):
imports = []
tree = ast.parse(path.read_text(encoding='utf-8'), filename=str(path))
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
imports.append((alias.name, 0, path))
elif isinstance(node, ast.ImportFrom):
module = node.module
if node.level > 0:
if module:
imports.append((module, node.level, path))
else:
for alias in node.names:
if alias.name != '*':
imports.append((alias.name, node.level, path))
elif module:
imports.append((module, 0, path))
return imports
def find_import(module: str | None, level: int, path: Path):
package_parts = path.relative_to(root).with_suffix('').parts
package_parts = package_parts[:-1]
if level > len(package_parts) + 1:
return None
if level == 0:
base = list(package_parts)
else:
base = list(package_parts[: -level + 1]) if level > 1 else list(package_parts)
if module:
base.extend(module.split('.'))
return '.'.join(base) if base else None
def local_import(path: Path) -> bool: # modules that modify sys.path to allow local imports
text = path.read_text(encoding='utf-8')
return 'sys.path.append' in text or 'sys.path.insert' in text
def install_import(path: Path, module_name: str): # modules that install packages at runtime and import them
text = path.read_text(encoding='utf-8')
if f'install("{module_name}' in text or f'install(\'{module_name}' in text:
return True
return False
def test_import(module: str | None, level: int, path: Path, local: bool = False):
module_name = find_import(module, level, path) if level > 0 else module
if not module_name:
return True
old_disable = logging.root.manager.disable
error = None
try:
logging.disable(logging.CRITICAL)
if install_import(path, module_name):
return True
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
try:
spec = importlib.util.find_spec(module_name)
except ModuleNotFoundError as e:
spec = None
error = e
if spec is not None:
return True
if local or local_import(path):
for extra in [str(path.parent.parent), str(path.parent)]:
spec = importlib.machinery.PathFinder.find_spec(module_name, [extra] + sys.path)
if spec is not None:
return True
if error is not None and any(ignore in str(error) for ignore in ignores):
return True
return False
except Exception as e:
if any(ignore in str(e) for ignore in ignores):
return True
return False
finally:
logging.disable(old_disable)
def verify_imports(folder: str, recursive: bool, local: bool = False):
stats = { 'ok': [], 'failed': [] }
for entry in os.scandir(folder):
if not any(exclude in entry.path for exclude in excludes) and not entry.name.startswith('.'):
if entry.is_file() and entry.name.endswith('.py'):
file_path = Path(entry.path)
has_failure = False
for module, level, _path in list_imports(file_path):
if module in ignores:
continue
if not test_import(module, level, file_path, local):
stats['failed'].append(f'{entry.path}: module={module} level={level}')
has_failure = True
if not has_failure:
stats['ok'].append(entry.path)
elif entry.is_dir() and recursive:
nested_stats = verify_imports(entry.path, recursive)
stats['ok'].extend(nested_stats['ok'])
stats['failed'].extend(nested_stats['failed'])
if len(stats['ok']) > 0 or len(stats['failed']) > 0:
print(f'Imports: folder={folder} ok={len(stats["ok"])} failed={len(stats["failed"])}')
if len(stats['failed']) > 0:
for fail in stats['failed']:
print(f' {fail}')
return stats
if __name__ == '__main__':
os.makedirs(output, exist_ok=True)
sys.path.insert(0, str(root))
sys.path.insert(0, str(root / 'modules' / 'control'))
for item in includes:
print(f"Test {item['folder']}")
test_compile(item['folder'], item['recursive'])
if item.get('includes', False):
verify_imports(item['folder'], item.get('recursive', False), item.get('local', False))