Skip to content

Commit

Permalink
Rerun filtered environments button (#179)
Browse files Browse the repository at this point in the history
* Remove unnecessary artefactId dependency

* Add dummy RerunFilteredEnvironmentsButton

* Add artefact test executions rerun filter by environment name

* Revert "Add dummy RerunFilteredEnvironmentsButton"

This reverts commit 4df7665.

* Revert "Add artefact test executions rerun filter by environment name"

This reverts commit 4804771.

* Revert "Revert "Add dummy RerunFilteredEnvironmentsButton""

This reverts commit b25cf36.

* Revert "Revert "Add artefact test executions rerun filter by environment name""

This reverts commit b72ed72.

* extend test executions rerun endpoint to rerun multiple

* remove artefact rerun endpoint as it's not necessary

* fix seed script

* update test executions rerun endpoint

* remove unused imports

* fix tests

* Show filtered test executions confirmation dialog

* Remove filteredTestExecutionIds provider

* Fix bug

* Submit rerun request for filtered test executions

* Some code cleanup

* Add required trailing comma

* Remove unused model

* fix test case after rebase

* Deal properly with multiple rerun requests

* Ignore useless ruff rule

* fix black formatting

* Use consistent wording as Environment

* Only mark successful rerun requests
  • Loading branch information
omar-selo committed May 10, 2024
1 parent ff38d48 commit 76c0150
Show file tree
Hide file tree
Showing 17 changed files with 311 additions and 231 deletions.
2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ select = [
"PLE",
"TID252",
]
ignore = ["ANN201", "ANN003", "N999", "ANN101", "ANN204"]
ignore = ["ANN201", "ANN003", "N999", "ANN101", "ANN204", "N818"]

[tool.ruff.flake8-bugbear]
extend-immutable-calls = [
Expand Down
10 changes: 5 additions & 5 deletions backend/scripts/seed_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,11 @@ def seed_data(client: TestClient | requests.Session, session: Session | None = N
def _rerun_some_test_executions(
client: TestClient | requests.Session, test_executions: list[dict]
) -> None:
for te in test_executions[::2]:
client.post(
RERUN_TEST_EXECUTION_URL,
json={"test_execution_id": te["id"]},
).raise_for_status()
te_ids = [te["id"] for te in test_executions[::2]]
client.post(
RERUN_TEST_EXECUTION_URL,
json={"test_execution_ids": te_ids},
).raise_for_status()


def _add_bugurl_and_duedate(session: Session) -> None:
Expand Down
27 changes: 1 addition & 26 deletions backend/test_observer/controllers/artefacts/artefacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@
Artefact,
ArtefactBuild,
TestExecution,
TestExecutionRerunRequest,
)
from test_observer.data_access.models_enums import ArtefactStatus, FamilyName
from test_observer.data_access.repository import get_artefacts_by_family, get_or_create
from test_observer.data_access.repository import get_artefacts_by_family
from test_observer.data_access.setup import get_db

from .logic import (
Expand All @@ -39,7 +38,6 @@
ArtefactBuildDTO,
ArtefactDTO,
ArtefactPatch,
RerunArtefactTestExecutionsRequest,
)

router = APIRouter(tags=["artefacts"])
Expand Down Expand Up @@ -146,26 +144,3 @@ def get_artefact_builds(artefact_id: int, db: Session = Depends(get_db)):
)

return latest_builds


@router.post("/{artefact_id}/reruns")
def rerun_artefact_test_executions(
request: RerunArtefactTestExecutionsRequest | None = None,
artefact: Artefact = Depends(_get_artefact_from_db),
db: Session = Depends(get_db),
):
latest_builds = db.scalars(
queries.latest_artefact_builds.where(ArtefactBuild.artefact_id == artefact.id)
)
test_executions = (te for ab in latest_builds for te in ab.test_executions)

if request:
if status := request.test_execution_status:
test_executions = (te for te in test_executions if te.status == status)
if (decision := request.test_execution_review_decision) is not None:
test_executions = (
te for te in test_executions if set(te.review_decision) == decision
)

for te in test_executions:
get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id})
5 changes: 0 additions & 5 deletions backend/test_observer/controllers/artefacts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,3 @@ class ArtefactBuildDTO(BaseModel):

class ArtefactPatch(BaseModel):
status: ArtefactStatus


class RerunArtefactTestExecutionsRequest(BaseModel):
test_execution_status: TestExecutionStatus | None = None
test_execution_review_decision: set[TestExecutionReviewDecision] | None = None
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class TestResultDTO(BaseModel):


class RerunRequest(BaseModel):
test_execution_id: int
test_execution_ids: set[int]


class PendingRerun(BaseModel):
Expand Down
41 changes: 34 additions & 7 deletions backend/test_observer/controllers/test_executions/reruns.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException
import contextlib

from fastapi import APIRouter, Depends, HTTPException, Response, status
from sqlalchemy import delete, select
from sqlalchemy.orm import Session, joinedload

Expand All @@ -17,14 +19,35 @@
router = APIRouter()


@router.post("/reruns")
def create_a_rerun_request(request: RerunRequest, db: Session = Depends(get_db)):
te = db.get(TestExecution, request.test_execution_id)
@router.post("/reruns", response_model=list[PendingRerun])
def create_rerun_requests(
request: RerunRequest, response: Response, db: Session = Depends(get_db)
):
rerun_requests = []
for test_execution_id in request.test_execution_ids:
with contextlib.suppress(_TestExecutionNotFound):
rerun_requests.append(_create_rerun_request(test_execution_id, db))

if not rerun_requests:
raise HTTPException(
status.HTTP_404_NOT_FOUND,
"Didn't find test executions with provided ids",
)

if len(rerun_requests) != len(request.test_execution_ids):
response.status_code = status.HTTP_207_MULTI_STATUS

return rerun_requests


def _create_rerun_request(
test_execution_id: int, db: Session
) -> TestExecutionRerunRequest:
te = db.get(TestExecution, test_execution_id)
if not te:
msg = f"No test execution with id {request.test_execution_id} found"
raise HTTPException(status_code=404, detail=msg)
raise _TestExecutionNotFound

get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id})
return get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id})


@router.get("/reruns", response_model=list[PendingRerun])
Expand All @@ -47,3 +70,7 @@ def delete_rerun_requests(request: DeleteReruns, db: Session = Depends(get_db)):
TestExecutionRerunRequest.test_execution_id.in_(request.test_execution_ids)
)
)


class _TestExecutionNotFound(ValueError):
...
93 changes: 0 additions & 93 deletions backend/tests/controllers/artefacts/test_artefacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from test_observer.data_access.models_enums import (
ArtefactStatus,
TestExecutionReviewDecision,
TestExecutionStatus,
)
from tests.data_generator import DataGenerator

Expand Down Expand Up @@ -343,95 +342,3 @@ def test_artefact_signoff_ignore_old_build_on_reject(
)

assert response.status_code == 400


def test_rerun_all_artefact_test_executions(
test_client: TestClient, test_execution: TestExecution
):
artefact_id = test_execution.artefact_build.artefact_id

response = test_client.post(f"/v1/artefacts/{artefact_id}/reruns")

assert response.status_code == 200
assert test_execution.rerun_request


def test_rerun_skips_test_executions_of_old_builds(
test_client: TestClient, generator: DataGenerator
):
a = generator.gen_artefact("candidate")
ab1 = generator.gen_artefact_build(a, revision=1)
ab2 = generator.gen_artefact_build(a, revision=2)
e = generator.gen_environment()
te1 = generator.gen_test_execution(ab1, e)
te2 = generator.gen_test_execution(ab2, e)

response = test_client.post(f"/v1/artefacts/{a.id}/reruns")

assert response.status_code == 200
assert te1.rerun_request is None
assert te2.rerun_request


def test_rerun_failed_artefact_test_executions(
test_client: TestClient, generator: DataGenerator
):
a = generator.gen_artefact("candidate")
ab = generator.gen_artefact_build(a)
e1 = generator.gen_environment(name="laptop")
e2 = generator.gen_environment(name="server")
te1 = generator.gen_test_execution(ab, e1)
te2 = generator.gen_test_execution(ab, e2, status=TestExecutionStatus.FAILED)

response = test_client.post(
f"/v1/artefacts/{a.id}/reruns",
json={"test_execution_status": TestExecutionStatus.FAILED},
)

assert response.status_code == 200
assert te1.rerun_request is None
assert te2.rerun_request


def test_rerun_undecided_artefact_test_executions(
test_client: TestClient, generator: DataGenerator
):
a = generator.gen_artefact("candidate")
ab = generator.gen_artefact_build(a)
e1 = generator.gen_environment(name="laptop")
e2 = generator.gen_environment(name="server")
te1 = generator.gen_test_execution(
ab, e1, review_decision=[TestExecutionReviewDecision.APPROVED_ALL_TESTS_PASS]
)
te2 = generator.gen_test_execution(ab, e2, review_decision=[])

response = test_client.post(
f"/v1/artefacts/{a.id}/reruns",
json={"test_execution_review_decision": []},
)

assert response.status_code == 200
assert te1.rerun_request is None
assert te2.rerun_request


def test_rerun_filters_ignore_review_decisions_order(
test_client: TestClient, test_execution: TestExecution
):
test_execution.review_decision = [
TestExecutionReviewDecision.APPROVED_INCONSISTENT_TEST,
TestExecutionReviewDecision.APPROVED_FAULTY_HARDWARE,
]

response = test_client.post(
f"/v1/artefacts/{test_execution.artefact_build.artefact_id}/reruns",
json={
"test_execution_review_decision": [
TestExecutionReviewDecision.APPROVED_FAULTY_HARDWARE,
TestExecutionReviewDecision.APPROVED_INCONSISTENT_TEST,
]
},
)

assert response.status_code == 200
assert test_execution.rerun_request
62 changes: 52 additions & 10 deletions backend/tests/controllers/test_executions/test_reruns.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,29 @@ def test_post_no_data_returns_422(post: Post):


def test_post_invalid_id_returns_404_with_message(post: Post):
response = post({"test_execution_id": 1})
response = post({"test_execution_ids": [1]})
assert response.status_code == 404
assert response.json()["detail"] == "No test execution with id 1 found"
assert response.json()["detail"] == "Didn't find test executions with provided ids"


def test_valid_post_returns_200(post: Post, test_execution: TestExecution):
assert post({"test_execution_id": test_execution.id}).status_code == 200
def test_valid_post(post: Post, test_execution: TestExecution):
test_execution.ci_link = "ci.link"
response = post({"test_execution_ids": [test_execution.id]})

assert response.status_code == 200
assert response.json() == [
{
"test_execution_id": test_execution.id,
"ci_link": test_execution.ci_link,
"family": test_execution.artefact_build.artefact.stage.family.name,
}
]


def test_post_with_valid_and_invalid_ids(post: Post, test_execution: TestExecution):
test_execution.ci_link = "ci.link"
response = post({"test_execution_ids": [test_execution.id, test_execution.id + 1]})
assert response.status_code == 207


def test_get_returns_200_with_empty_list(get: Get):
Expand All @@ -63,7 +79,7 @@ def test_get_returns_200_with_empty_list(get: Get):
def test_get_after_one_post(get: Get, post: Post, test_execution: TestExecution):
test_execution.ci_link = "ci.link"

post({"test_execution_id": test_execution.id})
post({"test_execution_ids": [test_execution.id]})

assert get().json() == [
{
Expand All @@ -79,8 +95,8 @@ def test_get_after_two_identical_posts(
):
test_execution.ci_link = "ci.link"

post({"test_execution_id": test_execution.id})
post({"test_execution_id": test_execution.id})
post({"test_execution_ids": [test_execution.id]})
post({"test_execution_ids": [test_execution.id]})

assert get().json() == [
{
Expand All @@ -100,8 +116,34 @@ def test_get_after_two_different_posts(
e2 = generator.gen_environment("desktop")
te2 = generator.gen_test_execution(te1.artefact_build, e2, ci_link="ci2.link")

post({"test_execution_id": te1.id})
post({"test_execution_id": te2.id})
post({"test_execution_ids": [te1.id]})
post({"test_execution_ids": [te2.id]})

assert get().json() == [
{
"test_execution_id": te1.id,
"ci_link": te1.ci_link,
"family": te1.artefact_build.artefact.stage.family.name,
},
{
"test_execution_id": te2.id,
"ci_link": te2.ci_link,
"family": te2.artefact_build.artefact.stage.family.name,
},
]


def test_get_after_post_with_two_test_execution_ids(
get: Get, post: Post, generator: DataGenerator
):
a = generator.gen_artefact("beta")
ab = generator.gen_artefact_build(a)
e1 = generator.gen_environment("e1")
e2 = generator.gen_environment("e2")
te1 = generator.gen_test_execution(ab, e1, ci_link="ci1.link")
te2 = generator.gen_test_execution(ab, e2, ci_link="ci2.link")

post({"test_execution_ids": [te1.id, te2.id]})

assert get().json() == [
{
Expand All @@ -121,7 +163,7 @@ def test_post_delete_get(
get: Get, post: Post, delete: Delete, test_execution: TestExecution
):
test_execution.ci_link = "ci.link"
post({"test_execution_id": test_execution.id})
post({"test_execution_ids": [test_execution.id]})
response = delete({"test_execution_ids": [test_execution.id]})

assert response.status_code == 200
Expand Down
15 changes: 15 additions & 0 deletions frontend/lib/models/rerun_request.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';

part 'rerun_request.freezed.dart';
part 'rerun_request.g.dart';

@freezed
class RerunRequest with _$RerunRequest {
const factory RerunRequest({
@JsonKey(name: 'test_execution_id') required int testExecutionId,
@JsonKey(name: 'ci_link') required String ciLink,
}) = _RerunRequest;

factory RerunRequest.fromJson(Map<String, Object?> json) =>
_$RerunRequestFromJson(json);
}
Loading

0 comments on commit 76c0150

Please sign in to comment.