|
6 | 6 | import python_on_whales |
7 | 7 |
|
8 | 8 | from posit_bakery.config.dependencies import PythonDependencyVersions, RDependencyVersions |
| 9 | +from posit_bakery.config.image.parsed_version import ParsedVersion |
9 | 10 | from posit_bakery.config.tag import default_tag_patterns, TagPatternFilter |
10 | 11 | from posit_bakery.const import OCI_LABEL_PREFIX, POSIT_LABEL_PREFIX |
11 | 12 | from posit_bakery.image.image_metadata import BuildMetadata |
@@ -1037,3 +1038,113 @@ def test_get_merge_sources_single_platform(self, basic_standard_image_target): |
1037 | 1038 | sources = basic_standard_image_target.get_merge_sources() |
1038 | 1039 |
|
1039 | 1040 | assert sources == ["image@sha256:digest"] |
| 1041 | + |
| 1042 | + |
| 1043 | +class TestPushSortKey: |
| 1044 | + """Tests for ImageTarget.push_sort_key — see ordered-push design spec.""" |
| 1045 | + |
| 1046 | + @staticmethod |
| 1047 | + def _make( |
| 1048 | + *, |
| 1049 | + image_name="connect", |
| 1050 | + is_latest=False, |
| 1051 | + version_name="2026.03.0", |
| 1052 | + is_primary_os=True, |
| 1053 | + is_primary_variant=True, |
| 1054 | + variant_name="Standard", |
| 1055 | + os_name="Ubuntu 24.04", |
| 1056 | + is_matrix=False, |
| 1057 | + ): |
| 1058 | + """Build a MagicMock(spec=ImageTarget) with the inputs push_sort_key reads.""" |
| 1059 | + target = MagicMock(spec=ImageTarget) |
| 1060 | + target.image_name = image_name |
| 1061 | + target.is_latest = is_latest |
| 1062 | + target.is_primary_os = is_primary_os |
| 1063 | + target.is_primary_variant = is_primary_variant |
| 1064 | + |
| 1065 | + image_version = MagicMock() |
| 1066 | + image_version.name = version_name |
| 1067 | + image_version.parsed_version = None if is_matrix else ParsedVersion.parse(version_name) |
| 1068 | + target.image_version = image_version |
| 1069 | + |
| 1070 | + variant = MagicMock() |
| 1071 | + variant.name = variant_name |
| 1072 | + target.image_variant = variant |
| 1073 | + |
| 1074 | + os_obj = MagicMock() |
| 1075 | + os_obj.name = os_name |
| 1076 | + target.image_os = os_obj |
| 1077 | + |
| 1078 | + # Bind the real property — push_sort_key reads only these attributes, |
| 1079 | + # so we can borrow ImageTarget's actual implementation. |
| 1080 | + target.push_sort_key = ImageTarget.push_sort_key.fget(target) |
| 1081 | + return target |
| 1082 | + |
| 1083 | + def test_latest_sorts_last(self): |
| 1084 | + """Within one image+version, the is_latest=True target sorts after is_latest=False.""" |
| 1085 | + non_latest = self._make(is_latest=False, version_name="2026.04.0") |
| 1086 | + latest = self._make(is_latest=True, version_name="2026.04.0") |
| 1087 | + assert sorted([latest, non_latest], key=lambda t: t.push_sort_key) == [non_latest, latest] |
| 1088 | + |
| 1089 | + def test_within_version_primary_sorts_last(self): |
| 1090 | + """Within one version, primary_score 0 < 1 < 2 — most-primary pushed last.""" |
| 1091 | + non_primary = self._make( |
| 1092 | + is_primary_os=False, is_primary_variant=False, os_name="Ubuntu 22.04", variant_name="Minimal" |
| 1093 | + ) |
| 1094 | + partial_primary = self._make( |
| 1095 | + is_primary_os=True, is_primary_variant=False, os_name="Ubuntu 24.04", variant_name="Minimal" |
| 1096 | + ) |
| 1097 | + full_primary = self._make( |
| 1098 | + is_primary_os=True, is_primary_variant=True, os_name="Ubuntu 24.04", variant_name="Standard" |
| 1099 | + ) |
| 1100 | + ordered = sorted( |
| 1101 | + [full_primary, non_primary, partial_primary], |
| 1102 | + key=lambda t: t.push_sort_key, |
| 1103 | + ) |
| 1104 | + assert ordered == [non_primary, partial_primary, full_primary] |
| 1105 | + |
| 1106 | + def test_older_version_sorts_first(self): |
| 1107 | + """Two non-latest stable targets — older (2026.03.0) sorts before newer (2026.04.0).""" |
| 1108 | + old = self._make(version_name="2026.03.0") |
| 1109 | + new = self._make(version_name="2026.04.0") |
| 1110 | + assert sorted([new, old], key=lambda t: t.push_sort_key) == [old, new] |
| 1111 | + |
| 1112 | + def test_dev_prerelease_orders_correctly(self): |
| 1113 | + """Locks in ParsedVersion semver §11: daily < dev < release at same release tuple.""" |
| 1114 | + daily = self._make(version_name="2026.04.0-daily+92") |
| 1115 | + dev = self._make(version_name="2026.04.0-dev+485-gdb8245deea") |
| 1116 | + release = self._make(version_name="2026.04.0") |
| 1117 | + assert sorted([release, dev, daily], key=lambda t: t.push_sort_key) == [daily, dev, release] |
| 1118 | + |
| 1119 | + def test_matrix_non_latest_rows_use_name_tiebreaker(self): |
| 1120 | + """Non-latest matrix rows collapse to MIN on version key; deterministic via version.name lex.""" |
| 1121 | + a = self._make(is_matrix=True, version_name="R4.3-python3.11") |
| 1122 | + b = self._make(is_matrix=True, version_name="R4.3-python3.12") |
| 1123 | + c = self._make(is_matrix=True, version_name="R4.4-python3.11") |
| 1124 | + ordered = sorted([c, a, b], key=lambda t: t.push_sort_key) |
| 1125 | + assert [t.image_version.name for t in ordered] == [ |
| 1126 | + "R4.3-python3.11", |
| 1127 | + "R4.3-python3.12", |
| 1128 | + "R4.4-python3.11", |
| 1129 | + ] |
| 1130 | + |
| 1131 | + def test_matrix_latest_row_sorts_last(self): |
| 1132 | + """The is_latest=True matrix row sorts after all non-latest matrix rows.""" |
| 1133 | + non_latest = self._make(is_matrix=True, version_name="R4.3-python3.11", is_latest=False) |
| 1134 | + latest = self._make(is_matrix=True, version_name="R4.4-python3.12", is_latest=True) |
| 1135 | + assert sorted([latest, non_latest], key=lambda t: t.push_sort_key) == [non_latest, latest] |
| 1136 | + |
| 1137 | + def test_multi_image_grouped(self): |
| 1138 | + """Two image_names in one list — output fully grouped, no interleaving.""" |
| 1139 | + connect_old = self._make(image_name="connect", version_name="2026.03.0") |
| 1140 | + connect_new = self._make(image_name="connect", version_name="2026.04.0", is_latest=True) |
| 1141 | + content_a = self._make(image_name="connect-content", is_matrix=True, version_name="R4.3-python3.11") |
| 1142 | + content_b = self._make( |
| 1143 | + image_name="connect-content", is_matrix=True, version_name="R4.4-python3.12", is_latest=True |
| 1144 | + ) |
| 1145 | + ordered = sorted( |
| 1146 | + [content_b, connect_old, content_a, connect_new], |
| 1147 | + key=lambda t: t.push_sort_key, |
| 1148 | + ) |
| 1149 | + names = [t.image_name for t in ordered] |
| 1150 | + assert names == ["connect", "connect", "connect-content", "connect-content"] |
0 commit comments