computer-playbook/tests/integration/test_variable_usage.py

98 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()