diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 9eb9a80fcd..e8a1738899 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -144,7 +144,7 @@ row would otherwise be assigned. Two spellings are accepted: | Form | Example | |---|---| | ASCII | `web-app-nextcloud#0,2@swarm+tor%debian/zfs:arm64` | -| the job label, pasted back out of the failed run | `馃惓馃馃寑馃馃饟彺饟彌路Nextcloud#0` | +| the job label, pasted back out of the failed run | `馃惓馃馃寑馃馃饟彺饟彌Nextcloud#0` | | Separator | Axis | Values | |---|---|---| diff --git a/tests/unit/python/cli/administration/deploy/ci/test_runs.py b/tests/unit/python/cli/administration/deploy/ci/test_runs.py index 76fce260f2..2d0763a123 100644 --- a/tests/unit/python/cli/administration/deploy/ci/test_runs.py +++ b/tests/unit/python/cli/administration/deploy/ci/test_runs.py @@ -429,7 +429,7 @@ class TestConfigFromTitle(unittest.TestCase): def test_a_truncated_priority_entry_aborts_instead_of_passing_through(self) -> None: with self.assertRaises(SystemExit): - runs.untriggered_priority("web-app-a 饟彺饟彌路Funkwha...", {}) + runs.untriggered_priority("web-app-a 饟彺饟彌Funkwha...", {}) def test_the_network_mode_is_carried_over_from_the_job_log(self) -> None: title = render({"mode": "swarm"}) diff --git a/tests/unit/python/utils/roles/test_display.py b/tests/unit/python/utils/roles/test_display.py index 07ed0a7cc5..2c1f099e11 100644 --- a/tests/unit/python/utils/roles/test_display.py +++ b/tests/unit/python/utils/roles/test_display.py @@ -12,7 +12,7 @@ class TestRoleDisplayName(unittest.TestCase): def test_encode_runs_the_category_labels_together_before_the_heading(self): self.assertEqual( - f"{''.join(self.codec.category_labels(APP))}路{self.codec.title(APP)}", + f"{''.join(self.codec.category_labels(APP))}{self.codec.title(APP)}", self.codec.encode(APP), ) @@ -22,13 +22,12 @@ class TestRoleDisplayName(unittest.TestCase): len(self.codec.title(long_heading)), len(self.codec.id_fragment(long_heading)), ) - self.assertTrue( - self.codec.encode(long_heading).endswith( - f"路{self.codec.id_fragment(long_heading)}" - ), + self.assertEqual( + "".join(self.codec.category_labels(long_heading)) + + self.codec.id_fragment(long_heading), self.codec.encode(long_heading), ) - self.assertEqual("饟墣饟寑饟寜路compose", self.codec.encode("sys-ctl-alm-compose")) + self.assertEqual("饟墣饟寑饟寜compose", self.codec.encode("sys-ctl-alm-compose")) def test_the_id_section_drops_what_the_categories_already_say(self): self.assertEqual("nextcloud", self.codec.id_fragment(APP)) @@ -43,6 +42,13 @@ class TestRoleDisplayName(unittest.TestCase): for variant in ("", "0", "0,1"): self.assertEqual(APP, self.codec.decode(self.codec.encode(APP, variant))) + def test_decode_still_reads_the_dotted_name_of_older_runs(self): + glyphs = "".join(self.codec.category_labels(APP)) + dotted = self.codec.encode(APP).replace(glyphs, f"{glyphs}路", 1) + self.assertNotEqual(dotted, self.codec.encode(APP)) + self.assertEqual(APP, self.codec.decode(dotted)) + self.assertEqual(APP, self.codec.decode(f"{dotted}#2")) + def test_decode_passes_a_role_id_through(self): self.assertEqual(APP, self.codec.decode(APP)) self.assertEqual(APP, self.codec.decode(f"{APP}#0,1")) diff --git a/utils/github/variant/selection.py b/utils/github/variant/selection.py index 22e8ec3cbe..3d142a3dd5 100644 --- a/utils/github/variant/selection.py +++ b/utils/github/variant/selection.py @@ -12,7 +12,7 @@ the architecture on both lines. Two spellings are accepted, so an operator can either type the token or paste back the job title of the run they want repeated: -* the job label CI emits (``馃悵馃馃殗馃寑馃馃饟彺饟彌路Nextcloud#2``) -- the glyphs carry +* the job label CI emits (``馃悵馃馃殗馃寑馃馃饟彺饟彌Nextcloud#2``) -- the glyphs carry mode, network mode, mesh state, distro, filesystem and architecture, the ``#`` shard the variants; * an ASCII form (``web-app-nextcloud#0,2@swarm+tor%debian/zfs:arm64&vpn``). diff --git a/utils/roles/display.py b/utils/roles/display.py index a27f12e0c5..271f38fe02 100644 --- a/utils/roles/display.py +++ b/utils/roles/display.py @@ -2,10 +2,10 @@ A role id is the machine handle (``web-app-nextcloud``); the display name is what a human reads off a GitHub Actions job or run title -(``饟彺饟彌路Nextcloud``). The category path renders as the ``glyph`` labels -``meta/categories.yml`` declares, run together into one block, then a ``路``, -then the role's own ``README.md`` heading or the part of the id the categories -do not already carry. +(``饟彺饟彌Nextcloud``). The category path renders as the ``glyph`` labels +``meta/categories.yml`` declares, run together into one block, directly +followed by the role's own ``README.md`` heading or the part of the id the +categories do not already carry. The name never holds a space: the ``whitelist`` and ``priority`` workflow inputs are space-separated lists, so a display name with a space in it would @@ -128,25 +128,37 @@ class RoleDisplayName: app_id: role id, e.g. ``'web-app-nextcloud'``. variant: variant CSV the job covers, e.g. ``'0,1'`` (``''``: none). """ - name = app_id - if (self.roles_dir / app_id).is_dir(): - fragment = self.id_fragment(app_id) - title = self.title(app_id) - if len(title) <= len(fragment): - fragment = title - name = SEPARATOR.join( - part - for part in ("".join(self.category_labels(app_id)), fragment) - if part - ) + name = self._name(app_id, "") return f"{name}{VARIANT_SEPARATOR}{variant}" if variant else name + def _name(self, app_id: str, joiner: str) -> str: + """The display name of *app_id* without a variant suffix. + + Args: + app_id: role id, e.g. ``'web-app-nextcloud'``. + joiner: what stands between the glyph block and the fragment; + ``''`` today, :data:`SEPARATOR` in names of older runs. + """ + if not (self.roles_dir / app_id).is_dir(): + return app_id + fragment = self.id_fragment(app_id) + title = self.title(app_id) + if len(title) <= len(fragment): + fragment = title + return joiner.join( + part for part in ("".join(self.category_labels(app_id)), fragment) if part + ) + def _registry(self) -> dict[str, str]: if self._by_display is None: - self._by_display = { - self.encode(path.name): path.name + roles = [ + path.name for path in sorted(self.roles_dir.iterdir()) if (path / "README.md").is_file() + ] + self._by_display = { + **{self._name(app_id, SEPARATOR): app_id for app_id in roles}, + **{self._name(app_id, ""): app_id for app_id in roles}, } return self._by_display