You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/08/11 13:44:00 UTC

(tooling-trusted-releases) branch sbp updated: Update release validation to not use the finished directory

This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new 56188426 Update release validation to not use the finished directory
56188426 is described below

commit 5618842662297b86e8e3d6fabab3d7e1d1e26405
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Tue Aug 11 14:43:29 2026 +0100

    Update release validation to not use the finished directory
---
 atr/admin/__init__.py                | 17 +++++++---
 atr/validate.py                      |  5 +++
 tests/unit/test_admin_consistency.py | 49 +++++++++++++++++++++++++++
 tests/unit/test_validate_on_disk.py  | 65 ++++++++++++++++++++++++++++++++++++
 4 files changed, 131 insertions(+), 5 deletions(-)

diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 37f08f62..3f31439f 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -27,7 +27,7 @@ import pathlib
 import statistics
 import sys
 import time
-from collections.abc import Callable, Mapping
+from collections.abc import Callable, Mapping, Sequence
 from typing import Annotated, Any, Final, Literal, NamedTuple
 
 import aiofiles.os
@@ -1364,10 +1364,7 @@ async def consistency(_session: web.Committer, _consistency: Literal["consistenc
     # Get all releases from the database
     async with db.session() as data:
         releases = await data.release().all()
-    database_dirs = []
-    for release in releases:
-        path = paths.release_directory_version(release)
-        database_dirs.append(str(path))
+    database_dirs = await _consistency_database_dirs(releases)
     if len(set(database_dirs)) != len(database_dirs):
         raise base.ASFQuartException("Duplicate release directories in database", errorcode=500)
 
@@ -2690,6 +2687,16 @@ def _format_exception_location(exc: BaseException) -> str:
     return f"{type(exc).__name__} at {filename}:{lineno} in {func}: {exc}"
 
 
+async def _consistency_database_dirs(releases: Sequence[sql.Release]) -> list[str]:
+    database_dirs: list[str] = []
+    for release in releases:
+        path = paths.release_directory_version(release)
+        if (release.phase == sql.ReleasePhase.RELEASE) and (not await aiofiles.os.path.isdir(path)):
+            continue
+        database_dirs.append(str(path))
+    return database_dirs
+
+
 async def _get_filesystem_dirs() -> list[str]:
     filesystem_dirs = []
     await _get_filesystem_dirs_finished(filesystem_dirs)
diff --git a/atr/validate.py b/atr/validate.py
index 6c09c80b..813ad067 100644
--- a/atr/validate.py
+++ b/atr/validate.py
@@ -452,6 +452,11 @@ def release_key(r: sql.Release) -> Divergences:
 @release_components("Release")
 def release_on_disk(r: sql.Release) -> Divergences:
     """Check that the release is on disk."""
+    if r.phase == sql.ReleasePhase.RELEASE:
+        unfinished = paths.get_unfinished_dir() / r.project_key / r.version
+        expected = "no unfinished directory to remain after release"
+        yield from divergences_predicate(lambda p: not p.path.exists(), expected, unfinished)
+        return
     path = paths.release_directory(r)
 
     def okay(p: safe.StatePath) -> bool:
diff --git a/tests/unit/test_admin_consistency.py b/tests/unit/test_admin_consistency.py
new file mode 100644
index 00000000..d49fbccf
--- /dev/null
+++ b/tests/unit/test_admin_consistency.py
@@ -0,0 +1,49 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pathlib
+import unittest.mock as mock
+
+import pytest
+
+import atr.admin as admin
+import atr.config as config
+import atr.models.sql as sql
+
+
+def release(phase: sql.ReleasePhase, project_key: str, version: str) -> mock.MagicMock:
+    result = mock.MagicMock()
+    result.phase = phase
+    result.project_key = project_key
+    result.version = version
+    return result
+
+
+async def test_consistency_database_dirs(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.setattr(config.get(), "FINISHED_STORAGE_DIR", str(tmp_path / "finished"), raising=False)
+    monkeypatch.setattr(config.get(), "UNFINISHED_STORAGE_DIR", str(tmp_path / "unfinished"), raising=False)
+    (tmp_path / "finished" / "old" / "1.0").mkdir(parents=True)
+    old_world = release(sql.ReleasePhase.RELEASE, "old", "1.0")
+    new_world = release(sql.ReleasePhase.RELEASE, "newer", "2.0")
+    draft = release(sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT, "draft", "3.0")
+
+    dirs = await admin._consistency_database_dirs([old_world, new_world, draft])
+
+    assert dirs == [
+        str(tmp_path / "finished" / "old" / "1.0"),
+        str(tmp_path / "unfinished" / "draft" / "3.0"),
+    ]
diff --git a/tests/unit/test_validate_on_disk.py b/tests/unit/test_validate_on_disk.py
new file mode 100644
index 00000000..079dab12
--- /dev/null
+++ b/tests/unit/test_validate_on_disk.py
@@ -0,0 +1,65 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pathlib
+import unittest.mock as mock
+
+import pytest
+
+import atr.models.safe as safe
+import atr.models.sql as sql
+import atr.validate as validate
+
+
+def release(phase: sql.ReleasePhase) -> mock.MagicMock:
+    result = mock.MagicMock()
+    result.phase = phase
+    result.key = "proj-1.0"
+    result.project_key = "proj"
+    result.version = "1.0"
+    return result
+
+
+def test_release_on_disk_flags_lingering_unfinished_directory(
+    tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+    (tmp_path / "unfinished" / "proj" / "1.0").mkdir(parents=True)
+    monkeypatch.setattr(validate.paths, "get_unfinished_dir", lambda: safe.StatePath(tmp_path / "unfinished"))
+
+    divergences = list(validate.release_on_disk(release(sql.ReleasePhase.RELEASE)))
+
+    assert len(divergences) == 1
+
+
+def test_release_on_disk_flags_missing_draft_directory(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.setattr(
+        validate.paths, "release_directory", lambda _release: safe.StatePath(tmp_path / "unfinished" / "proj" / "1.0")
+    )
+
+    divergences = list(validate.release_on_disk(release(sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT)))
+
+    assert len(divergences) == 1
+
+
+def test_release_on_disk_passes_released_without_directories(
+    tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+    monkeypatch.setattr(validate.paths, "get_unfinished_dir", lambda: safe.StatePath(tmp_path / "unfinished"))
+
+    divergences = list(validate.release_on_disk(release(sql.ReleasePhase.RELEASE)))
+
+    assert divergences == []


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org