Skip to content

Commit

Permalink
Add image testing backend (#244)
Browse files Browse the repository at this point in the history
* Extract TestExecutionStarter class from start_test_execution controller

* Move dependencies to controller class

* Extract methods of TestExecutionController for readability

* Remove weird try catch

* Remove unused import

* Store revision for charms too

* Simplify create artefact function

* Validate stage name on start test request

* Add image family

* Add image artefact fields

* Add functionality to store artefact image specific fields

* Make start image test more thorough

* Add some todos

* Use multiple parameter Literal

* Fix some linting issues

* Add image_url to Artefact

* CExtract assertion logic

* Test start test for all families

* Create TestStartTest class to test all families together

* Move test_requires_family_field into TestStartTest

* Add tests for required fields of image and charm start test

* Add test for reuse of objects on test start

* Update artefact uniqueness

* Move all family independent start tests into common class

* Support getting image artefacts

* Add logic to get previous test results for images

* Fix artefact versions for images

* Remove test requiring ci_link

* Add some image artefacts to seed script

* Fix tests
  • Loading branch information
omar-selo authored Jan 15, 2025
1 parent fd1bb68 commit b930f02
Show file tree
Hide file tree
Showing 23 changed files with 826 additions and 480 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test_backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- run: poetry install
- run: poetry run black --check test_observer tests migrations scripts tasks
- run: poetry run ruff test_observer tests migrations scripts tasks
- run: poetry run mypy --explicit-package-bases test_observer tests migrations scripts tasks
- run: poetry run mypy .
- run: poetry run pytest
env:
TEST_DB_URL: postgresql+pg8000://postgres:password@localhost:5432/postgres
Expand Down
10 changes: 6 additions & 4 deletions backend/migrations/env.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from logging.config import fileConfig

from sqlalchemy import engine_from_config, pool

from alembic import context
from sqlalchemy import engine_from_config, pool

from test_observer.data_access import Base

from test_observer.data_access.setup import DB_URL

# for 'autogenerate' support
Expand Down Expand Up @@ -59,7 +57,11 @@ def run_migrations_online() -> None:
)

with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
context.configure(
connection=connection,
target_metadata=target_metadata,
transaction_per_migration=True,
)

with context.begin_transaction():
context.run_migrations()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Add image family
Revision ID: 121edad6b53f
Revises: 7878a1b29384
Create Date: 2025-01-08 13:12:05.831020+00:00
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "121edad6b53f"
down_revision = "7878a1b29384"
branch_labels = None
depends_on = None


def upgrade() -> None:
add_image_family()
add_new_stages()

add_os_column()
add_release_column()
add_sha256_column()
add_owner_column()
add_image_url_column()


def add_image_family():
op.execute("ALTER TYPE familyname ADD VALUE IF NOT EXISTS 'image'")


def add_new_stages():
op.execute("ALTER TYPE stagename ADD VALUE IF NOT EXISTS 'pending'")
op.execute("ALTER TYPE stagename ADD VALUE IF NOT EXISTS 'current'")


def add_os_column():
op.add_column("artefact", sa.Column("os", sa.String(length=200)))
op.execute("UPDATE artefact SET os = '' WHERE os is NULL")
op.alter_column("artefact", "os", nullable=False)


def add_release_column():
op.add_column("artefact", sa.Column("release", sa.String(length=200)))
op.execute("UPDATE artefact SET release = '' WHERE release is NULL")
op.alter_column("artefact", "release", nullable=False)


def add_sha256_column():
op.add_column("artefact", sa.Column("sha256", sa.String(length=200)))
op.execute("UPDATE artefact SET sha256 = '' WHERE sha256 is NULL")
op.alter_column("artefact", "sha256", nullable=False)


def add_owner_column():
op.add_column("artefact", sa.Column("owner", sa.String(length=200)))
op.execute("UPDATE artefact SET owner = '' WHERE owner is NULL")
op.alter_column("artefact", "owner", nullable=False)


def add_image_url_column():
op.add_column("artefact", sa.Column("image_url", sa.String(length=200)))
op.execute("UPDATE artefact SET image_url = '' WHERE image_url is NULL")
op.alter_column("artefact", "image_url", nullable=False)


def downgrade() -> None:
op.execute("DELETE FROM artefact WHERE family = 'image'")
op.drop_column("artefact", "owner")
op.drop_column("artefact", "sha256")
op.drop_column("artefact", "release")
op.drop_column("artefact", "os")
op.drop_column("artefact", "image_url")

remove_image_family_enum_value()
remove_added_stage_enum_values()


def remove_image_family_enum_value():
op.execute("ALTER TYPE familyname RENAME TO familyname_old")
op.execute("CREATE TYPE familyname AS " "ENUM('snap', 'deb', 'charm')")
op.execute(
"ALTER TABLE artefact ALTER COLUMN family TYPE "
"familyname USING family::text::familyname"
)
op.execute("DROP TYPE familyname_old")


def remove_added_stage_enum_values():
op.execute("ALTER TYPE stagename RENAME TO stagename_old")
op.execute(
"CREATE TYPE stagename AS "
"ENUM('edge', 'beta', 'candidate', 'stable', 'proposed', 'updates')"
)
op.execute(
"ALTER TABLE artefact ALTER COLUMN stage TYPE "
"stagename USING stage::text::stagename"
)
op.execute("DROP TYPE stagename_old")
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Update artefact unique constraint
Revision ID: 2be627e68efd
Revises: 121edad6b53f
Create Date: 2025-01-14 09:34:28.190863+00:00
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "2be627e68efd"
down_revision = "121edad6b53f"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.create_index(
"unique_charm",
"artefact",
["name", "version", "track"],
unique=True,
postgresql_where=sa.text("family = 'charm'"),
)
op.create_index(
"unique_image",
"artefact",
["sha256"],
unique=True,
postgresql_where=sa.text("family = 'image'"),
)

op.drop_index("unique_snap", "artefact")
op.create_index(
"unique_snap",
"artefact",
["name", "version", "track"],
unique=True,
postgresql_where=sa.text("family = 'snap'"),
)

op.drop_index("unique_deb", "artefact")
op.create_index(
"unique_deb",
"artefact",
["name", "version", "series", "repo"],
unique=True,
postgresql_where=sa.text("family = 'deb'"),
)


def downgrade() -> None:
op.drop_index("unique_deb", "artefact")
op.create_index(
"unique_deb",
"artefact",
["name", "version", "series", "repo"],
unique=True,
postgresql_where=sa.text("series != '' AND repo != ''"),
)

op.drop_index("unique_snap", "artefact")
op.create_index(
"unique_snap",
"artefact",
["name", "version", "track"],
unique=True,
postgresql_where=sa.text("track != ''"),
)

op.drop_index("unique_image", table_name="artefact")
op.drop_index("unique_charm", table_name="artefact")
4 changes: 4 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ select = [
]
ignore = ["ANN201", "ANN003", "N999", "ANN101", "ANN204", "N818", "B008"]

[tool.mypy]
exclude = "charm/*"
explicit_package_bases = true

[[tool.mypy.overrides]]
module = "celery.*"
ignore_missing_imports = true
Expand Down
48 changes: 47 additions & 1 deletion backend/scripts/seed_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python

# ruff: noqa: E501 Line too long
# ruff: noqa

from datetime import date, timedelta
from textwrap import dedent
Expand All @@ -21,6 +21,7 @@
EndTestExecutionRequest,
StartCharmTestExecutionRequest,
StartDebTestExecutionRequest,
StartImageTestExecutionRequest,
StartSnapTestExecutionRequest,
)
from test_observer.data_access.models import Artefact
Expand Down Expand Up @@ -265,6 +266,51 @@
ci_link="http://example13",
test_plan="com.canonical.solutions-qa::tbd",
),
StartImageTestExecutionRequest(
name="noble-live-desktop-amd64",
os="ubuntu",
release="noble",
arch="amd64",
version="20240827",
sha256="e71fb5681e63330445eec6fc3fe043f365289c2e595e3ceeac08fbeccfb9a957",
owner="foundations",
image_url=HttpUrl(
"https://cdimage.ubuntu.com/noble/daily-live/20240827/noble-desktop-amd64.iso"
),
execution_stage=StageName.pending,
test_plan="image test plan",
environment="xps",
),
StartImageTestExecutionRequest(
name="noble-live-desktop-amd64",
os="ubuntu",
release="noble",
arch="amd64",
version="20240827",
sha256="e71fb5681e63330445eec6fc3fe043f365289c2e595e3ceeac08fbeccfb9a957",
owner="foundations",
image_url=HttpUrl(
"https://cdimage.ubuntu.com/noble/daily-live/20240827/noble-desktop-amd64.iso"
),
execution_stage=StageName.pending,
test_plan="desktop image test plan",
environment="xps",
),
StartImageTestExecutionRequest(
name="ubuntu-core-20-arm64-raspi",
os="ubuntu-core",
release="20",
arch="amd64+raspi",
version="20221025.4",
sha256="e94418aa109cf5886a50e828e98ac68361ea7e3ca1ab4aed2bbddc0a299b334f",
owner="snapd",
image_url=HttpUrl(
"https://cdimage.ubuntu.com/ubuntu-core/20/stable/20221025.4/ubuntu-core-20-arm64+raspi.img.xz"
),
execution_stage=StageName.pending,
test_plan="core image test plan",
environment="rpi3",
),
]

END_TEST_EXECUTION_REQUESTS = [
Expand Down
2 changes: 2 additions & 0 deletions backend/test_observer/controllers/artefacts/artefacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,7 @@ def get_artefact_versions(
.where(Artefact.track == artefact.track)
.where(Artefact.series == artefact.series)
.where(Artefact.repo == artefact.repo)
.where(Artefact.os == artefact.os)
.where(Artefact.series == artefact.series)
.order_by(Artefact.id.desc())
)
5 changes: 5 additions & 0 deletions backend/test_observer/controllers/artefacts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class ArtefactDTO(BaseModel):
store: str
series: str
repo: str
os: str
release: str
owner: str
sha256: str
image_url: str
stage: str
status: ArtefactStatus
assignee: UserDTO | None
Expand Down
2 changes: 2 additions & 0 deletions backend/test_observer/controllers/reports/test_executions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
Artefact.track,
Artefact.series,
Artefact.repo,
Artefact.os,
Artefact.series,
TestExecution.id,
TestExecution.status,
TestExecution.ci_link,
Expand Down
2 changes: 2 additions & 0 deletions backend/test_observer/controllers/reports/test_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
Artefact.track,
Artefact.series,
Artefact.repo,
Artefact.os,
Artefact.series,
Artefact.created_at,
TestExecution.id,
TestExecution.status,
Expand Down
25 changes: 3 additions & 22 deletions backend/test_observer/controllers/test_executions/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from collections import defaultdict

from sqlalchemy import delete, desc, func, or_, over, select
from sqlalchemy.orm import Session, selectinload
from sqlalchemy.orm import Session

from test_observer.common.constants import PREVIOUS_TEST_RESULT_COUNT
from test_observer.controllers.test_executions.models import PreviousTestResult
Expand All @@ -30,27 +30,6 @@
)


def delete_related_rerun_requests(
db: Session, artefact_build_id: int, environment_id: int, test_plan: str
):
related_test_execution_runs = db.scalars(
select(TestExecution)
.where(
TestExecution.artefact_build_id == artefact_build_id,
TestExecution.environment_id == environment_id,
TestExecution.test_plan == test_plan,
)
.options(selectinload(TestExecution.rerun_request))
)

for te in related_test_execution_runs:
rerun_request = te.rerun_request
if rerun_request:
db.delete(rerun_request)

db.commit()


def delete_previous_results(
db: Session,
test_execution: TestExecution,
Expand Down Expand Up @@ -111,6 +90,8 @@ def get_previous_test_results(
Artefact.series == test_execution.artefact_build.artefact.series,
Artefact.repo == test_execution.artefact_build.artefact.repo,
Artefact.track == test_execution.artefact_build.artefact.track,
Artefact.os == test_execution.artefact_build.artefact.os,
Artefact.series == test_execution.artefact_build.artefact.series,
Artefact.id <= test_execution.artefact_build.artefact_id,
or_(
TestExecution.id < test_execution.id,
Expand Down
Loading

0 comments on commit b930f02

Please sign in to comment.