Thymeleaf 호환성 테스트 추가:
- Mvc/Form/Security/Expression 관련 템플릿 및 테스트 구현 - 의존성 검증 및 업그레이드 스크립트 추가 (Thymeleaf 3.1.5 적용)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
// ./gradlew -I gradle/thymeleaf-verification.init.gradle :thymeleafCompatibilityDependencies
|
||||
// Read-only dependency resolution; does not override versions or the normal build.
|
||||
gradle.projectsEvaluated {
|
||||
def portal = gradle.rootProject
|
||||
portal.tasks.register('thymeleafCompatibilityDependencies') {
|
||||
doLast {
|
||||
def output = new File(portal.buildDir, 'reports/thymeleaf-compatibility')
|
||||
output.mkdirs()
|
||||
['runtimeClasspath', 'testRuntimeClasspath'].each { name ->
|
||||
def artifacts = portal.configurations.getByName(name).resolvedConfiguration.resolvedArtifacts
|
||||
def rows = artifacts.findAll {
|
||||
it.id.componentIdentifier instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||
}.collect {
|
||||
"${it.moduleVersion.id.group}:${it.name}\t${it.moduleVersion.id.version}\t${it.file.name}"
|
||||
}.sort()
|
||||
new File(output, "${name}.tsv").text = rows.join('\n') + '\n'
|
||||
}
|
||||
println "Dependency evidence: ${output}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user