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