mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-07-18 06:24:25 +02:00
98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
import unittest
|
||
import os
|
||
import yaml
|
||
from glob import glob
|
||
import re
|
||
|
||
class TestTopLevelVariableUsage(unittest.TestCase):
|
||
def setUp(self):
|
||
self.project_root = os.path.abspath(
|
||
os.path.join(os.path.dirname(__file__), '../../')
|
||
)
|
||
# Braces werden von glob nicht unterstützt – also einzeln sammeln:
|
||
self.roles_vars_paths = (
|
||
glob(os.path.join(self.project_root, 'roles/*/vars/main.yml')) +
|
||
glob(os.path.join(self.project_root, 'roles/*/defaults/main.yml'))
|
||
)
|
||
self.group_vars_paths = glob(
|
||
os.path.join(self.project_root, 'group_vars/all/*.yml')
|
||
)
|
||
self.all_variable_files = self.roles_vars_paths + self.group_vars_paths
|
||
self.valid_extensions = {
|
||
'.yml', '.yaml', '.j2', '.py', '.sh', '.conf',
|
||
'.env', '.xml', '.html', '.txt'
|
||
}
|
||
|
||
def get_top_level_keys(self, file_path):
|
||
with open(file_path, 'r') as f:
|
||
try:
|
||
data = yaml.safe_load(f)
|
||
if isinstance(data, dict):
|
||
return list(data.keys())
|
||
except yaml.YAMLError:
|
||
pass
|
||
return []
|
||
|
||
def find_declaration_line(self, file_path, varname):
|
||
"""
|
||
Findet die Zeilennummer (1-basiert), in der der Top-Level-Key wirklich deklariert wird.
|
||
"""
|
||
pattern = re.compile(rf"^\s*{re.escape(varname)}\s*:")
|
||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||
for i, line in enumerate(f, 1):
|
||
if pattern.match(line) and not line.lstrip().startswith('#'):
|
||
return i
|
||
return None
|
||
|
||
def find_usage_in_project(self, varname, definition_path):
|
||
"""
|
||
Sucht im gesamten Projekt nach varname, überspringt dabei
|
||
nur die eine Deklarationszeile in definition_path.
|
||
"""
|
||
decl_line = self.find_declaration_line(definition_path, varname)
|
||
|
||
for root, _, files in os.walk(self.project_root):
|
||
for fn in files:
|
||
path = os.path.join(root, fn)
|
||
ext = os.path.splitext(path)[1]
|
||
if ext not in self.valid_extensions:
|
||
continue
|
||
try:
|
||
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
||
for i, line in enumerate(f, 1):
|
||
if (path == definition_path and
|
||
decl_line is not None and
|
||
i == decl_line):
|
||
# genau die Deklarationszeile überspringen
|
||
continue
|
||
if varname in line:
|
||
return True
|
||
except Exception:
|
||
continue
|
||
return False
|
||
|
||
def test_top_level_variable_usage(self):
|
||
"""
|
||
Stellt sicher, dass jede Top-Level-Variable in roles/*/{vars,defaults}/main.yml
|
||
und group_vars/all/*.yml irgendwo im Projekt (außer in ihrer eigenen
|
||
Deklarationszeile) verwendet wird.
|
||
"""
|
||
unused = []
|
||
for varfile in self.all_variable_files:
|
||
keys = self.get_top_level_keys(varfile)
|
||
for key in keys:
|
||
if not self.find_usage_in_project(key, varfile):
|
||
unused.append((varfile, key))
|
||
|
||
if unused:
|
||
msg = "\n".join(
|
||
f"{path}: unused top-level key '{key}'"
|
||
for path, key in unused
|
||
)
|
||
self.fail(
|
||
"The following top-level variables are defined but never used:\n" + msg
|
||
)
|
||
|
||
if __name__ == '__main__':
|
||
unittest.main()
|