87 lines
2.6 KiB
Python
87 lines
2.6 KiB
Python
"""Uji V31: tiap image stack mandiri — daftar COPY memuat semua impor intra-proyek.
|
|
|
|
`./venv/bin/python test_images.py` harus exit 0. Memindai `Dockerfile`,
|
|
`Dockerfile.scan`, `Dockerfile.dist`, `Dockerfile.claim`: untuk tiap modul .py
|
|
yang di-COPY, kumpulkan impor intra-proyek (top-level `import X` / `from X
|
|
import`) lalu pastikan semuanya ada di daftar COPY image itu. Menangkap kelas
|
|
bug B4: modul yang diimpor modul yang di-COPY tapi ⊥ ikut disalin →
|
|
ModuleNotFoundError di container (image ⊥ mandiri).
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
PROJECT = {'config', 'db', 'get_voters', 'dashboard', 'gunicorn_conf',
|
|
'scan_loop', 'distribute', 'distribute_loop', 'telegram',
|
|
'claim', 'claim_loop'}
|
|
|
|
IMAGES = {
|
|
'web': 'Dockerfile',
|
|
'scan': 'Dockerfile.scan',
|
|
'dist': 'Dockerfile.dist',
|
|
'claim': 'Dockerfile.claim',
|
|
}
|
|
|
|
|
|
def copied_modules(dfile):
|
|
"""Modul .py yang di-COPY oleh `COPY <files...> ./`."""
|
|
mods = set()
|
|
with open(os.path.join(ROOT, dfile)) as fh:
|
|
for line in fh:
|
|
if not line.startswith('COPY '):
|
|
continue
|
|
tokens = line.split()
|
|
if tokens[-1] != './':
|
|
continue
|
|
for tok in tokens[1:-1]:
|
|
if tok.endswith('.py'):
|
|
mods.add(tok[:-3])
|
|
return mods
|
|
|
|
|
|
def project_imports(mod):
|
|
"""Impor intra-proyek dari sumber modul `mod`."""
|
|
imports = set()
|
|
path = os.path.join(ROOT, mod + '.py')
|
|
if not os.path.exists(path):
|
|
return imports
|
|
with open(path) as fh:
|
|
src = fh.read()
|
|
for m in re.finditer(r'^\s*import\s+([\w.]+)', src, re.M):
|
|
name = m.group(1).split('.')[0]
|
|
if name in PROJECT:
|
|
imports.add(name)
|
|
for m in re.finditer(r'^\s*from\s+([\w.]+)\s+import', src, re.M):
|
|
name = m.group(1).split('.')[0]
|
|
if name in PROJECT:
|
|
imports.add(name)
|
|
return imports
|
|
|
|
|
|
def check(tag, cond):
|
|
if not cond:
|
|
raise AssertionError(f'GAGAL: {tag}')
|
|
print(f'ok: {tag}')
|
|
|
|
|
|
def main():
|
|
for image, dfile in IMAGES.items():
|
|
copied = copied_modules(dfile)
|
|
check(f'V31 {image}: ada modul .py di COPY',
|
|
bool(copied) and 'config' in copied)
|
|
for mod in sorted(copied):
|
|
missing = project_imports(mod) - copied
|
|
if missing:
|
|
raise AssertionError(
|
|
f'GAGAL: V31 {image}: {mod} impor {sorted(missing)} '
|
|
f'yang ⊥ di-COPY')
|
|
print(f'ok: V31 {image}: {mod} impor ⊆ COPY')
|
|
print('\nSEMUA UJI PASS')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|