fa736a36ab
- Mvc/Form/Security/Expression 관련 템플릿 및 테스트 구현 - 의존성 검증 및 업그레이드 스크립트 추가 (Thymeleaf 3.1.5 적용)
114 lines
5.2 KiB
Python
114 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare resolved dependency TSVs and inspect both WARs; exits nonzero on regression."""
|
|
import argparse
|
|
import hashlib
|
|
import io
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import zipfile
|
|
|
|
|
|
EXPECTED_CHANGES = {
|
|
"org.thymeleaf:thymeleaf": ("3.1.4.RELEASE", "3.1.5.RELEASE"),
|
|
"org.thymeleaf:thymeleaf-spring5": ("3.1.4.RELEASE", "3.1.5.RELEASE"),
|
|
"ognl:ognl": ("3.3.4", "3.3.5"),
|
|
}
|
|
EXPECTED_CLASSES = {
|
|
"org/thymeleaf/TemplateEngine.class": "thymeleaf-3.1.5.RELEASE.jar",
|
|
"org/thymeleaf/spring5/SpringTemplateEngine.class": "thymeleaf-spring5-3.1.5.RELEASE.jar",
|
|
"ognl/Ognl.class": "ognl-3.3.5.jar",
|
|
}
|
|
LOCAL_PREFIXES = (
|
|
"spring-boot-devtools", "spring-boot-starter-actuator", "spring-boot-actuator",
|
|
"micrometer-", "spring-boot-admin-", "tomcat-embed-websocket-",
|
|
)
|
|
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise ValueError(message)
|
|
|
|
|
|
def dependencies(path):
|
|
result = {}
|
|
for line in path.read_text().splitlines():
|
|
module, version, filename = line.split("\t")
|
|
entries = result.setdefault(module, [])
|
|
require((version, filename) not in entries, "Duplicate artifact: " + filename)
|
|
require(not entries or entries[0][0] == version, "Multiple versions: " + module)
|
|
entries.append((version, filename))
|
|
for entries in result.values():
|
|
entries.sort()
|
|
return result
|
|
|
|
|
|
def compare(before, after):
|
|
old, new = dependencies(before), dependencies(after)
|
|
require(old.keys() == new.keys(), "Added/removed external modules: " + str(old.keys() ^ new.keys()))
|
|
changed = {key: (old[key][0][0], new[key][0][0]) for key in old if old[key] != new[key]}
|
|
require(changed == EXPECTED_CHANGES, "Unexpected dependency changes: " + str(changed))
|
|
return {"external_modules": len(new), "changes": changed}
|
|
|
|
|
|
def inspect_war(path):
|
|
owners = {name: [] for name in EXPECTED_CLASSES}
|
|
with zipfile.ZipFile(path) as war:
|
|
names = war.namelist()
|
|
require(len(names) == len(set(names)), "Duplicate ZIP entries: " + str(path))
|
|
jars = sorted(name for name in names if name.endswith(".jar"))
|
|
basenames = [Path(name).name for name in jars]
|
|
require(len(basenames) == len(set(basenames)), "Duplicate JAR names: " + str(path))
|
|
require(not any(Path(name).name.startswith("application-local") and name.endswith(".yml")
|
|
for name in names), "Local profile packaged: " + str(path))
|
|
for name in jars:
|
|
basename = Path(name).name
|
|
require(not basename.startswith(LOCAL_PREFIXES), "Excluded library packaged: " + name)
|
|
require(not re.match(r"spring-[\w-]+-6\.", basename), "Spring 6 packaged: " + name)
|
|
with zipfile.ZipFile(io.BytesIO(war.read(name))) as jar:
|
|
classes = set(jar.namelist())
|
|
require(not any(c.startswith("jakarta/servlet/") for c in classes),
|
|
"Jakarta Servlet classes packaged: " + name)
|
|
for target in owners:
|
|
if target in classes:
|
|
owners[target].append(name)
|
|
# Java 8 compatibility of each upgraded library's entry class.
|
|
require(int.from_bytes(jar.read(target)[6:8], "big") <= 52,
|
|
"Java >8 class: " + name + "!" + target)
|
|
for target, expected in EXPECTED_CLASSES.items():
|
|
require(owners[target] == ["WEB-INF/lib/" + expected],
|
|
"Wrong/duplicate class provider: " + target + " " + str(owners[target]))
|
|
for pattern, expected in [
|
|
(r"thymeleaf-\d", "thymeleaf-3.1.5.RELEASE.jar"),
|
|
(r"thymeleaf-spring\d-", "thymeleaf-spring5-3.1.5.RELEASE.jar"),
|
|
(r"ognl-", "ognl-3.3.5.jar"),
|
|
]:
|
|
require([n for n in basenames if re.match(pattern, n)] == [expected],
|
|
"Wrong/duplicate library version for " + expected)
|
|
return {"file": path.name, "sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
"jar_count": len(jars), "class_providers": owners, "jars": jars}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--baseline", type=Path, required=True, help="Directory of baseline dependency TSVs")
|
|
parser.add_argument("--current", type=Path, required=True, help="Directory of current dependency TSVs")
|
|
parser.add_argument("--war", type=Path, action="append", required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
require(len(args.war) == 2 and len(set(args.war)) == 2, "Provide the standard WAR and bootWar")
|
|
result = {
|
|
"dependencies": {name: compare(args.baseline / (name + ".tsv"), args.current / (name + ".tsv"))
|
|
for name in ["runtimeClasspath", "testRuntimeClasspath"]},
|
|
"wars": [inspect_war(path) for path in args.war],
|
|
}
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n")
|
|
print("PASS: dependency changes limited to three modules; both WARs verified")
|
|
for war in result["wars"]:
|
|
print(war["file"], war["sha256"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|