Skip to content

Commit 06a10b5

Browse files
committed
test(76471): add unit tests for validate_java_version_consistency.py and CI workflow
1 parent f435846 commit 06a10b5

3 files changed

Lines changed: 308 additions & 0 deletions

File tree

.github/workflows/ci-cd-java.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
clean: 'true'
4444
fetch-depth: 2
4545

46+
# Required since custom scripts from /scripts are being used
4647
- name: Resolve shared workflow ref
4748
run: |
4849
set -euo pipefail

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: ci.yml
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
push:
9+
branches:
10+
- main
11+
12+
jobs:
13+
test-scripts:
14+
name: Test Python scripts
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- name: Setup Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: '3.x'
24+
25+
- name: Install pytest
26+
run: pip install pytest
27+
28+
- name: Run script tests
29+
run: pytest scripts/ -v
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import pytest
2+
3+
from validate_java_version_consistency import (
4+
extract_java_version_from_docker_tag,
5+
normalize_java_version,
6+
parse_docker_java_version,
7+
parse_gradle_java_version,
8+
resolve_args,
9+
)
10+
11+
12+
# ---------------------------------------------------------------------------
13+
# normalize_java_version
14+
# ---------------------------------------------------------------------------
15+
16+
class TestNormalizeJavaVersion:
17+
def test_plain_number(self):
18+
assert normalize_java_version("25") == "25"
19+
20+
def test_strips_whitespace(self):
21+
assert normalize_java_version(" 17 ") == "17"
22+
23+
def test_strips_single_quotes(self):
24+
assert normalize_java_version("'21'") == "21"
25+
26+
def test_strips_double_quotes(self):
27+
assert normalize_java_version('"11"') == "11"
28+
29+
def test_legacy_1x_format_java_8(self):
30+
assert normalize_java_version("1.8") == "8"
31+
32+
def test_legacy_1x_format_java_11(self):
33+
assert normalize_java_version("1.11") == "11"
34+
35+
def test_gradle_java_version_constant(self):
36+
assert normalize_java_version("JavaVersion.VERSION_25") == "25"
37+
38+
def test_gradle_java_version_constant_11(self):
39+
assert normalize_java_version("JavaVersion.VERSION_11") == "11"
40+
41+
def test_leading_zeros_stripped(self):
42+
# int() conversion removes leading zeros
43+
assert normalize_java_version("011") == "11"
44+
45+
# --- sad paths ---
46+
47+
def test_none_input(self):
48+
assert normalize_java_version(None) is None
49+
50+
def test_empty_string(self):
51+
assert normalize_java_version("") is None
52+
53+
def test_only_whitespace(self):
54+
assert normalize_java_version(" ") is None
55+
56+
def test_unexpanded_placeholder(self):
57+
assert normalize_java_version("${java.version}") is None
58+
59+
def test_no_digits(self):
60+
assert normalize_java_version("abc") is None
61+
62+
63+
# ---------------------------------------------------------------------------
64+
# extract_java_version_from_docker_tag
65+
# ---------------------------------------------------------------------------
66+
67+
class TestExtractJavaVersionFromDockerTag:
68+
def test_plain_number(self):
69+
assert extract_java_version_from_docker_tag("25") == "25"
70+
71+
def test_hsl_base_image_jre_tag(self):
72+
assert extract_java_version_from_docker_tag("1.0.2-25-java-jre") == "25"
73+
74+
def test_hsl_base_image_jdk_tag(self):
75+
assert extract_java_version_from_docker_tag("1.0.2-25-java-jdk") == "25"
76+
77+
def test_version_dash_jdk(self):
78+
assert extract_java_version_from_docker_tag("25-jdk") == "25"
79+
80+
def test_version_dash_jre(self):
81+
assert extract_java_version_from_docker_tag("25-jre") == "25"
82+
83+
def test_java_dash_version(self):
84+
assert extract_java_version_from_docker_tag("java-11") == "11"
85+
86+
def test_case_insensitive_jdk(self):
87+
assert extract_java_version_from_docker_tag("17-JDK") == "17"
88+
89+
# --- sad paths ---
90+
91+
def test_latest_tag(self):
92+
assert extract_java_version_from_docker_tag("latest") is None
93+
94+
def test_empty_tag(self):
95+
assert extract_java_version_from_docker_tag("") is None
96+
97+
def test_no_java_version_hint(self):
98+
assert extract_java_version_from_docker_tag("ubuntu") is None
99+
100+
101+
# ---------------------------------------------------------------------------
102+
# resolve_args
103+
# ---------------------------------------------------------------------------
104+
105+
class TestResolveArgs:
106+
def test_curly_brace_syntax(self):
107+
assert resolve_args("${BASE}", {"BASE": "ubuntu:22.04"}) == "ubuntu:22.04"
108+
109+
def test_dollar_word_syntax(self):
110+
assert resolve_args("$VERSION", {"VERSION": "1.0"}) == "1.0"
111+
112+
def test_embedded_variable(self):
113+
assert resolve_args("prefix-${TAG}-suffix", {"TAG": "foo"}) == "prefix-foo-suffix"
114+
115+
def test_multiple_variables(self):
116+
assert resolve_args("${A}/${B}", {"A": "x", "B": "y"}) == "x/y"
117+
118+
def test_no_variables(self):
119+
assert resolve_args("plain-string", {}) == "plain-string"
120+
121+
# --- sad paths ---
122+
123+
def test_missing_curly_brace_variable_left_intact(self):
124+
assert resolve_args("${MISSING}", {}) == "${MISSING}"
125+
126+
def test_missing_dollar_word_variable_left_intact(self):
127+
assert resolve_args("$MISSING", {}) == "$MISSING"
128+
129+
def test_empty_string(self):
130+
assert resolve_args("", {}) == ""
131+
132+
133+
# ---------------------------------------------------------------------------
134+
# parse_docker_java_version (uses tmp_path fixture)
135+
# ---------------------------------------------------------------------------
136+
137+
class TestParseDockerJavaVersion:
138+
def test_simple_jre_tag(self, tmp_path):
139+
dockerfile = tmp_path / "Dockerfile"
140+
dockerfile.write_text(
141+
"FROM hsldevcom/infodevops-docker-base-images:1.0.2-25-java-jre\n"
142+
)
143+
version, image_ref = parse_docker_java_version(str(dockerfile))
144+
assert version == "25"
145+
assert "1.0.2-25-java-jre" in image_ref
146+
147+
def test_arg_substitution(self, tmp_path):
148+
dockerfile = tmp_path / "Dockerfile"
149+
dockerfile.write_text(
150+
"ARG BASE_TAG=1.0.2-25-java-jre\n"
151+
"FROM hsldevcom/infodevops-docker-base-images:${BASE_TAG}\n"
152+
)
153+
version, _ = parse_docker_java_version(str(dockerfile))
154+
assert version == "25"
155+
156+
def test_multistage_uses_last_non_scratch(self, tmp_path):
157+
dockerfile = tmp_path / "Dockerfile"
158+
dockerfile.write_text(
159+
"FROM hsldevcom/infodevops-docker-base-images:1.0.2-25-java-jdk AS build\n"
160+
"FROM scratch\n"
161+
"FROM hsldevcom/infodevops-docker-base-images:1.0.2-25-java-jre\n"
162+
)
163+
version, image_ref = parse_docker_java_version(str(dockerfile))
164+
assert version == "25"
165+
assert "java-jre" in image_ref
166+
167+
def test_scratch_is_skipped_when_real_image_exists(self, tmp_path):
168+
dockerfile = tmp_path / "Dockerfile"
169+
dockerfile.write_text(
170+
"FROM hsldevcom/infodevops-docker-base-images:1.0.2-17-java-jre AS base\n"
171+
"FROM scratch\n"
172+
)
173+
# scratch is not the last non-scratch image; base is
174+
version, _ = parse_docker_java_version(str(dockerfile))
175+
assert version == "17"
176+
177+
def test_comments_are_ignored(self, tmp_path):
178+
dockerfile = tmp_path / "Dockerfile"
179+
dockerfile.write_text(
180+
"# syntax=docker/dockerfile:1\n"
181+
"# This is a comment\n"
182+
"FROM hsldevcom/infodevops-docker-base-images:1.0.2-11-java-jre\n"
183+
)
184+
version, _ = parse_docker_java_version(str(dockerfile))
185+
assert version == "11"
186+
187+
def test_image_with_digest_strips_digest(self, tmp_path):
188+
dockerfile = tmp_path / "Dockerfile"
189+
dockerfile.write_text(
190+
"FROM hsldevcom/infodevops-docker-base-images:1.0.2-25-java-jre@sha256:abc123\n"
191+
)
192+
version, _ = parse_docker_java_version(str(dockerfile))
193+
assert version == "25"
194+
195+
# --- sad paths ---
196+
197+
def test_no_from_instruction_raises(self, tmp_path):
198+
dockerfile = tmp_path / "Dockerfile"
199+
dockerfile.write_text("RUN echo hello\n")
200+
with pytest.raises(RuntimeError, match="Could not find a runtime image"):
201+
parse_docker_java_version(str(dockerfile))
202+
203+
def test_only_scratch_raises(self, tmp_path):
204+
dockerfile = tmp_path / "Dockerfile"
205+
dockerfile.write_text("FROM scratch\n")
206+
with pytest.raises(RuntimeError, match="Could not find a runtime image"):
207+
parse_docker_java_version(str(dockerfile))
208+
209+
def test_image_without_tag_raises(self, tmp_path):
210+
dockerfile = tmp_path / "Dockerfile"
211+
dockerfile.write_text("FROM ubuntu\n")
212+
with pytest.raises(RuntimeError, match="Could not determine the Java tag"):
213+
parse_docker_java_version(str(dockerfile))
214+
215+
def test_unrecognizable_java_version_in_tag_raises(self, tmp_path):
216+
dockerfile = tmp_path / "Dockerfile"
217+
dockerfile.write_text("FROM ubuntu:latest\n")
218+
with pytest.raises(RuntimeError, match="Could not determine the Java version"):
219+
parse_docker_java_version(str(dockerfile))
220+
221+
222+
# ---------------------------------------------------------------------------
223+
# parse_gradle_java_version (uses tmp_path fixture)
224+
# ---------------------------------------------------------------------------
225+
226+
class TestParseGradleJavaVersion:
227+
def test_jvm_target_string(self, tmp_path):
228+
gradle = tmp_path / "build.gradle.kts"
229+
gradle.write_text('compileKotlin { kotlinOptions { jvmTarget = "25" } }\n')
230+
version, source = parse_gradle_java_version(str(gradle))
231+
assert version == "25"
232+
assert "25" in source
233+
234+
def test_jvm_target_java_version_constant(self, tmp_path):
235+
gradle = tmp_path / "build.gradle.kts"
236+
gradle.write_text("tasks.withType<KotlinCompile> { jvmTarget = JavaVersion.VERSION_17 }\n")
237+
version, _ = parse_gradle_java_version(str(gradle))
238+
assert version == "17"
239+
240+
def test_language_version_set(self, tmp_path):
241+
gradle = tmp_path / "build.gradle.kts"
242+
gradle.write_text(
243+
"java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } }\n"
244+
)
245+
version, _ = parse_gradle_java_version(str(gradle))
246+
assert version == "21"
247+
248+
def test_source_compatibility(self, tmp_path):
249+
gradle = tmp_path / "build.gradle"
250+
gradle.write_text("sourceCompatibility = JavaVersion.VERSION_11\n")
251+
version, _ = parse_gradle_java_version(str(gradle))
252+
assert version == "11"
253+
254+
def test_target_compatibility(self, tmp_path):
255+
gradle = tmp_path / "build.gradle"
256+
gradle.write_text("targetCompatibility = JavaVersion.VERSION_11\n")
257+
version, _ = parse_gradle_java_version(str(gradle))
258+
assert version == "11"
259+
260+
def test_source_name_in_reported_source(self, tmp_path):
261+
gradle = tmp_path / "build.gradle.kts"
262+
gradle.write_text('compileKotlin { kotlinOptions { jvmTarget = "25" } }\n')
263+
_, source = parse_gradle_java_version(str(gradle))
264+
assert "build.gradle.kts" in source
265+
266+
# --- sad paths ---
267+
268+
def test_no_matching_pattern_raises(self, tmp_path):
269+
gradle = tmp_path / "build.gradle.kts"
270+
gradle.write_text("plugins { kotlin(\"jvm\") }\n")
271+
with pytest.raises(RuntimeError, match="Could not determine the Java version"):
272+
parse_gradle_java_version(str(gradle))
273+
274+
def test_empty_file_raises(self, tmp_path):
275+
gradle = tmp_path / "build.gradle.kts"
276+
gradle.write_text("")
277+
with pytest.raises(RuntimeError, match="Could not determine the Java version"):
278+
parse_gradle_java_version(str(gradle))

0 commit comments

Comments
 (0)