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/13 19:06:12 UTC

(tooling-trusted-releases) branch sbp updated: Ignore releases being finalised during validation

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 6d1a0e91 Ignore releases being finalised during validation
6d1a0e91 is described below

commit 6d1a0e91fef9af5fad169cda0c97448825c5a335
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Thu Aug 13 20:04:54 2026 +0100

    Ignore releases being finalised during validation
---
 atr/validate.py                     | 23 +++++++---
 tests/unit/test_validate_on_disk.py | 87 +++++++++++++++++++++++++++++++++++++
 2 files changed, 105 insertions(+), 5 deletions(-)

diff --git a/atr/validate.py b/atr/validate.py
index 813ad067..740b5d3a 100644
--- a/atr/validate.py
+++ b/atr/validate.py
@@ -233,6 +233,15 @@ async def everything(data: db.Session) -> AsyncAnnotatedDivergences:
     committees_sorted = await data.committee(_child_committees=True).order_by(sql.Committee.key).all()
     projects_sorted = await data.project(_distribution_channels=True).order_by(sql.Project.key).all()
     releases_sorted = await data.release().order_by(sql.Release.key).all()
+    finalising_tasks = await data.task(
+        status_in=[sql.TaskStatus.QUEUED, sql.TaskStatus.ACTIVE],
+        task_type=sql.TaskType.RELEASE_FINALISE,
+    ).all()
+    finalising_releases = {
+        (task.project_key, task.version_key)
+        for task in finalising_tasks
+        if (task.project_key is not None) and (task.version_key is not None)
+    }
 
     for c in await asyncio.to_thread(committees, committees_sorted):
         yield c
@@ -240,7 +249,7 @@ async def everything(data: db.Session) -> AsyncAnnotatedDivergences:
     for p in await asyncio.to_thread(projects, projects_sorted):
         yield p
 
-    for r in await asyncio.to_thread(releases, releases_sorted):
+    for r in await asyncio.to_thread(releases, releases_sorted, finalising_releases):
         yield r
 
 
@@ -399,11 +408,12 @@ def projects(ps: Iterable[sql.Project]) -> AnnotatedDivergences:
         yield from project(p)
 
 
-def release(r: sql.Release) -> AnnotatedDivergences:
+def release(r: sql.Release, finalising: bool = False) -> AnnotatedDivergences:
     """Check that a release is valid."""
     yield from release_created(r)
     yield from release_key(r)
-    yield from release_on_disk(r)
+    if (r.phase != sql.ReleasePhase.RELEASE) or (not finalising):
+        yield from release_on_disk(r)
     yield from release_package_managers(r)
     yield from release_released(r)
     yield from release_sboms(r)
@@ -517,7 +527,10 @@ def release_vote_logic(r: sql.Release) -> Divergences:
     yield from divergences_predicate(okay, expected, actual)
 
 
-def releases(rs: Iterable[sql.Release]) -> AnnotatedDivergences:
+def releases(
+    rs: Iterable[sql.Release], finalising_releases: set[tuple[str, str]] | None = None
+) -> AnnotatedDivergences:
     """Check that the releases are valid."""
+    finalising_releases = finalising_releases or set()
     for r in rs:
-        yield from release(r)
+        yield from release(r, (r.project_key, r.version) in finalising_releases)
diff --git a/tests/unit/test_validate_on_disk.py b/tests/unit/test_validate_on_disk.py
index 079dab12..7647feef 100644
--- a/tests/unit/test_validate_on_disk.py
+++ b/tests/unit/test_validate_on_disk.py
@@ -15,16 +15,37 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import datetime
 import pathlib
 import unittest.mock as mock
+from collections.abc import AsyncIterator
 
 import pytest
+import sqlalchemy.ext.asyncio
+import sqlalchemy.pool
+import sqlmodel
 
+import atr.db as db
+import atr.models.results as results
 import atr.models.safe as safe
 import atr.models.sql as sql
 import atr.validate as validate
 
 
+@pytest.fixture
+async def sqlite_sessionmaker() -> AsyncIterator[sqlalchemy.ext.asyncio.async_sessionmaker[db.Session]]:
+    engine = sqlalchemy.ext.asyncio.create_async_engine(
+        "sqlite+aiosqlite:///:memory:",
+        connect_args={"check_same_thread": False},
+        poolclass=sqlalchemy.pool.StaticPool,
+    )
+    async with engine.begin() as conn:
+        await conn.run_sync(sqlmodel.SQLModel.metadata.create_all)
+    sessionmaker = sqlalchemy.ext.asyncio.async_sessionmaker(bind=engine, class_=db.Session, expire_on_commit=False)
+    yield sessionmaker
+    await engine.dispose()
+
+
 def release(phase: sql.ReleasePhase) -> mock.MagicMock:
     result = mock.MagicMock()
     result.phase = phase
@@ -63,3 +84,69 @@ def test_release_on_disk_passes_released_without_directories(
     divergences = list(validate.release_on_disk(release(sql.ReleasePhase.RELEASE)))
 
     assert divergences == []
+
+
+@pytest.mark.parametrize(
+    ("status", "task_type", "task_version", "expected_divergences"),
+    [
+        (sql.TaskStatus.QUEUED, sql.TaskType.RELEASE_FINALISE, "1.0", 0),
+        (sql.TaskStatus.ACTIVE, sql.TaskType.RELEASE_FINALISE, "1.0", 0),
+        (sql.TaskStatus.ACTIVE, sql.TaskType.RELEASE_FINALISE, "2.0", 1),
+        (sql.TaskStatus.ACTIVE, sql.TaskType.SVN_PUBLISH, "1.0", 1),
+        (sql.TaskStatus.COMPLETED, sql.TaskType.RELEASE_FINALISE, "1.0", 1),
+        (sql.TaskStatus.FAILED, sql.TaskType.RELEASE_FINALISE, "1.0", 1),
+        (sql.TaskStatus.BROKEN, sql.TaskType.RELEASE_FINALISE, "1.0", 1),
+    ],
+)
+async def test_release_on_disk_accounts_for_finalisation_task_status(
+    sqlite_sessionmaker,
+    tmp_path: pathlib.Path,
+    monkeypatch: pytest.MonkeyPatch,
+    status: sql.TaskStatus,
+    task_type: sql.TaskType,
+    task_version: str,
+    expected_divergences: int,
+) -> None:
+    unfinished = tmp_path / "unfinished"
+    (unfinished / "proj" / "1.0").mkdir(parents=True)
+    monkeypatch.setattr(validate.paths, "get_unfinished_dir", lambda: safe.StatePath(unfinished))
+    created = datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC)
+    async with sqlite_sessionmaker() as data:
+        data.add(sql.Committee(key="proj", name="Proj"))
+        data.add(sql.Project(key="proj", name="Apache Proj", committee_key="proj", created=created))
+        await data.commit()
+        data.add(
+            sql.Release(
+                phase=sql.ReleasePhase.RELEASE,
+                created=created,
+                project_key="proj",
+                version="1.0",
+            )
+        )
+        data.add(
+            sql.Task(
+                status=status,
+                task_type=task_type,
+                task_args={},
+                asf_uid="user",
+                started=created if status == sql.TaskStatus.ACTIVE else None,
+                pid=1 if status == sql.TaskStatus.ACTIVE else None,
+                completed=created
+                if status in (sql.TaskStatus.COMPLETED, sql.TaskStatus.FAILED, sql.TaskStatus.BROKEN)
+                else None,
+                result=results.ReleaseFinalise(kind="release_finalise", audit_events=1, message="Finalised")
+                if status == sql.TaskStatus.COMPLETED
+                else None,
+                error="Failed" if status in (sql.TaskStatus.FAILED, sql.TaskStatus.BROKEN) else None,
+                project_key="proj",
+                version_key=task_version,
+                revision_number="00001",
+            )
+        )
+        await data.commit()
+
+        divergences = [
+            divergence async for divergence in validate.everything(data) if divergence.validator == "release_on_disk"
+        ]
+
+    assert len(divergences) == expected_divergences


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