Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rerun filtered environments button #179

Merged
merged 26 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
602abc8
Remove unnecessary artefactId dependency
omar-selo May 8, 2024
1cca514
Add dummy RerunFilteredEnvironmentsButton
omar-selo May 8, 2024
f878e2a
Add artefact test executions rerun filter by environment name
omar-selo May 8, 2024
9939cd6
Revert "Add dummy RerunFilteredEnvironmentsButton"
omar-selo May 8, 2024
2013b05
Revert "Add artefact test executions rerun filter by environment name"
omar-selo May 8, 2024
6769202
Revert "Revert "Add dummy RerunFilteredEnvironmentsButton""
omar-selo May 8, 2024
905ba9a
Revert "Revert "Add artefact test executions rerun filter by environm…
omar-selo May 8, 2024
64acc7f
extend test executions rerun endpoint to rerun multiple
omar-selo May 9, 2024
d09742f
remove artefact rerun endpoint as it's not necessary
omar-selo May 9, 2024
497db31
fix seed script
omar-selo May 9, 2024
021bcc6
update test executions rerun endpoint
omar-selo May 9, 2024
1a083a3
remove unused imports
omar-selo May 9, 2024
8a5c80b
fix tests
omar-selo May 9, 2024
0537f25
Show filtered test executions confirmation dialog
omar-selo May 9, 2024
c246e1d
Remove filteredTestExecutionIds provider
omar-selo May 9, 2024
8d32e1c
Fix bug
omar-selo May 9, 2024
91382be
Submit rerun request for filtered test executions
omar-selo May 9, 2024
c3d1e9f
Some code cleanup
omar-selo May 9, 2024
a9a8824
Add required trailing comma
omar-selo May 9, 2024
b89d71b
Remove unused model
omar-selo May 10, 2024
e610c23
fix test case after rebase
omar-selo May 10, 2024
3155bdd
Deal properly with multiple rerun requests
omar-selo May 10, 2024
9cbc78a
Ignore useless ruff rule
omar-selo May 10, 2024
996a25e
fix black formatting
omar-selo May 10, 2024
5618a1e
Use consistent wording as Environment
omar-selo May 10, 2024
23dbd65
Only mark successful rerun requests
omar-selo May 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading