Skip to content

Commit

Permalink
Mark tests a xfail
Browse files Browse the repository at this point in the history
  • Loading branch information
amickan committed Dec 20, 2024
1 parent 084f414 commit f0b736b
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 83 deletions.
1 change: 0 additions & 1 deletion app/grandchallenge/algorithms/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ def validate(self, data):

if Job.objects.get_jobs_with_same_inputs(
inputs=self.inputs,
interface=None, # TODO fix this in separate PR
algorithm_image=data["algorithm_image"],
algorithm_model=data["algorithm_model"],
):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,59 +36,64 @@
{% block content %}
<h2>Try-out Algorithm</h2>

{{ algorithm.job_create_page_markdown|md2html }}
{% if not algorithm.interfaces.all %}
<p>Your algorithm does not have any interfaces yet. You need to define at least one interface (i.e. input - output combination) before you can try it out.</p>
<p>To define an interface, navigate <a href="{% url 'algorithms:interface-list' slug=algorithm.slug %}">here</a>.</p>
{% else %}
{{ algorithm.job_create_page_markdown|md2html }}

{% get_obj_perms request.user for algorithm as "algorithm_perms" %}
{% get_obj_perms request.user for algorithm as "algorithm_perms" %}

{% if not algorithm.active_image %}
<p>
This algorithm is not ready to be used.
{% if 'change_algorithm' in algorithm_perms %}
Please upload a valid container image for this algorithm.
{% endif %}
</p>
{% elif form.jobs_limit < 1 %}
<p>
You have run out of credits to try this algorithm.
You can request more credits by sending an e-mail to
<a href="{{ 'mailto:[email protected]'|random_encode|safe }}" class="text-radboud">
[email protected]</a>.
</p>
{% else %}
<p>
Select the data that you would like to run the algorithm on.
</p>
<p>
{% if 'change_algorithm' in algorithm_perms %}
As an editor for this algorithm you can test and debug your algorithm in total {{ editors_job_limit }} times per unique algorithm image.
You share these credits with all other editors of this algorithm.
Once you have reached the limit, any extra jobs will be deducted from your personal algorithm credits,
of which you get {{ request.user.user_credit.credits }} per month.
{% else %}
You receive {{ request.user.user_credit.credits }} credits per month.
{% endif %}
Using this algorithm requires {{ algorithm.credits_per_job }}
credit{{ algorithm.credits_per_job|pluralize }} per job.
You can currently create up to {{ form.jobs_limit }} job{{ form.jobs_limit|pluralize }} for this algorithm.
</p>
{% if not algorithm.active_image %}
<p>
This algorithm is not ready to be used.
{% if 'change_algorithm' in algorithm_perms %}
Please upload a valid container image for this algorithm.
{% endif %}
</p>
{% elif form.jobs_limit < 1 %}
<p>
You have run out of credits to try this algorithm.
You can request more credits by sending an e-mail to
<a href="{{ 'mailto:[email protected]'|random_encode|safe }}" class="text-radboud">
[email protected]</a>.
</p>
{% else %}
<p>
Select the data that you would like to run the algorithm on.
</p>
<p>
{% if 'change_algorithm' in algorithm_perms %}
As an editor for this algorithm you can test and debug your algorithm in total {{ editors_job_limit }} times per unique algorithm image.
You share these credits with all other editors of this algorithm.
Once you have reached the limit, any extra jobs will be deducted from your personal algorithm credits,
of which you get {{ request.user.user_credit.credits }} per month.
{% else %}
You receive {{ request.user.user_credit.credits }} credits per month.
{% endif %}
Using this algorithm requires {{ algorithm.credits_per_job }}
credit{{ algorithm.credits_per_job|pluralize }} per job.
You can currently create up to {{ form.jobs_limit }} job{{ form.jobs_limit|pluralize }} for this algorithm.
</p>

<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="save" value="Submit" class="btn btn-primary" id="submit-id-save">
</form>
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="save" value="Submit" class="btn btn-primary" id="submit-id-save">
</form>

<p>
By running this algorithm you agree to the
<a href="{% url 'policies:detail' slug='terms-of-service' %}"> General
Terms of Service</a>{% if algorithm.additional_terms_markdown %},
as well as this algorithm's specific Terms of Service:
{% else %}.
{% endif %}
</p>
<p>
By running this algorithm you agree to the
<a href="{% url 'policies:detail' slug='terms-of-service' %}"> General
Terms of Service</a>{% if algorithm.additional_terms_markdown %},
as well as this algorithm's specific Terms of Service:
{% else %}.
{% endif %}
</p>

{{ algorithm.additional_terms_markdown|md2html }}
{{ algorithm.additional_terms_markdown|md2html }}

{% endif %}
{% endif %}

{% endblock %}
1 change: 1 addition & 0 deletions app/tests/algorithms_tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def test_job_list_view_num_queries(
assert len(response.json()["results"]) == num_jobs


@pytest.mark.xfail(reason="Still to be addressed for optional inputs pitch")
@pytest.mark.django_db
class TestJobCreationThroughAPI:

Expand Down
3 changes: 2 additions & 1 deletion app/tests/algorithms_tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1535,7 +1535,8 @@ def test_has_default_interface():
io1, io2 = AlgorithmInterfaceFactory.create_batch(2)

alg1.interfaces.add(io1, through_defaults={"is_default": True})
alg2.interfaces.add(io2)
alg1.interfaces.add(io2)
alg2.interfaces.add(io1)

assert alg1.default_interface == io1
assert not alg2.default_interface
Expand Down
3 changes: 3 additions & 0 deletions app/tests/algorithms_tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ def test_job_permissions_from_template(self, client):
algorithm_image=algorithm_image, job=job, user=user
)

@pytest.mark.xfail(
reason="Still to be addressed for optional inputs pitch"
)
def test_job_permissions_from_api(self, rf):
# setup
user = UserFactory()
Expand Down
4 changes: 4 additions & 0 deletions app/tests/algorithms_tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def test_algorithm_relations_on_job_serializer(rf):
)


@pytest.mark.xfail(reason="Still to be addressed for optional inputs pitch")
@pytest.mark.django_db
@pytest.mark.parametrize(
"title, "
Expand Down Expand Up @@ -183,6 +184,7 @@ def test_algorithm_job_post_serializer_validations(
assert len(job.inputs.all()) == 2


@pytest.mark.xfail(reason="Still to be addressed for optional inputs pitch")
@pytest.mark.django_db
def test_algorithm_job_post_serializer_create(
rf, settings, django_capture_on_commit_callbacks
Expand Down Expand Up @@ -240,6 +242,7 @@ def test_algorithm_job_post_serializer_create(
assert len(job.inputs.all()) == 3


@pytest.mark.xfail(reason="Still to be addressed for optional inputs pitch")
@pytest.mark.django_db
class TestJobCreateLimits:
def test_form_invalid_without_enough_credits(self, rf, settings):
Expand Down Expand Up @@ -380,6 +383,7 @@ def test_reformat_inputs(rf):
)


@pytest.mark.xfail(reason="Still to be addressed for optional inputs pitch")
@pytest.mark.django_db
def test_algorithm_post_serializer_image_and_time_limit_fixed(rf):
request = rf.get("/foo")
Expand Down
107 changes: 74 additions & 33 deletions app/tests/algorithms_tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from actstream.models import Follow
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.base import ContentFile
from guardian.shortcuts import assign_perm

from grandchallenge.algorithms.models import Job
from grandchallenge.algorithms.tasks import (
Expand Down Expand Up @@ -37,6 +38,7 @@
ComponentInterfaceFactory,
ComponentInterfaceValueFactory,
)
from tests.conftest import get_interface_form_data
from tests.factories import (
GroupFactory,
ImageFactory,
Expand All @@ -45,6 +47,7 @@
UserFactory,
)
from tests.utils import get_view_for_user, recurse_callbacks
from tests.verification_tests.factories import VerificationFactory


@pytest.mark.django_db
Expand Down Expand Up @@ -187,7 +190,7 @@ def test_jobs_workflow(django_capture_on_commit_callbacks):
@pytest.mark.flaky(reruns=3)
@pytest.mark.django_db
def test_algorithm(
algorithm_image, settings, django_capture_on_commit_callbacks
algorithm_image, client, settings, django_capture_on_commit_callbacks
):
# Override the celery settings
settings.task_eager_propagates = (True,)
Expand All @@ -202,6 +205,10 @@ def test_algorithm(
with open(algorithm_image, "rb") as f:
ai.image.save(algorithm_image, ContentFile(f.read()))

user = UserFactory()
ai.algorithm.add_editor(user)
VerificationFactory(user=user, is_verified=True)

recurse_callbacks(
callbacks=callbacks,
django_capture_on_commit_callbacks=django_capture_on_commit_callbacks,
Expand All @@ -212,6 +219,7 @@ def test_algorithm(
image_file = ImageFileFactory(
file__from_path=Path(__file__).parent / "resources" / "input_file.tif"
)
assign_perm("cases.view_image", user, image_file.image)

input_interface = ComponentInterface.objects.get(
slug="generic-medical-image"
Expand All @@ -228,17 +236,23 @@ def test_algorithm(
interface, through_defaults={"is_default": True}
)

civ = ComponentInterfaceValueFactory(
image=image_file.image, interface=input_interface, file=None
)

with django_capture_on_commit_callbacks() as callbacks:
create_algorithm_jobs(
algorithm_image=ai,
civ_sets=[{civ}],
time_limit=ai.algorithm.time_limit,
requires_gpu_type=ai.algorithm.job_requires_gpu_type,
requires_memory_gb=ai.algorithm.job_requires_memory_gb,
get_view_for_user(
viewname="algorithms:job-create",
client=client,
method=client.post,
user=user,
reverse_kwargs={
"slug": ai.algorithm.slug,
"interface": interface.pk,
},
follow=True,
data={
**get_interface_form_data(
interface_slug=input_interface.slug,
data=image_file.image.pk,
),
},
)

recurse_callbacks(
Expand Down Expand Up @@ -279,22 +293,37 @@ def test_algorithm(
slug="detection-json-file",
kind=ComponentInterface.Kind.ANY,
)
ai.algorithm.outputs.add(detection_interface)
ai.save()
interface2 = AlgorithmInterfaceFactory(
inputs=[input_interface],
outputs=[
json_result_interface,
heatmap_interface,
detection_interface,
],
)
ai.algorithm.interfaces.add(interface2)
image_file = ImageFileFactory(
file__from_path=Path(__file__).parent / "resources" / "input_file.tif"
)
civ = ComponentInterfaceValueFactory(
image=image_file.image, interface=input_interface, file=None
)
assign_perm("cases.view_image", user, image_file.image)

with django_capture_on_commit_callbacks() as callbacks:
create_algorithm_jobs(
algorithm_image=ai,
civ_sets=[{civ}],
time_limit=ai.algorithm.time_limit,
requires_gpu_type=ai.algorithm.job_requires_gpu_type,
requires_memory_gb=ai.algorithm.job_requires_memory_gb,
get_view_for_user(
viewname="algorithms:job-create",
client=client,
method=client.post,
user=user,
reverse_kwargs={
"slug": ai.algorithm.slug,
"interface": interface2.pk,
},
follow=True,
data={
**get_interface_form_data(
interface_slug=input_interface.slug,
data=image_file.image.pk,
),
},
)

recurse_callbacks(
Expand All @@ -318,7 +347,7 @@ def test_algorithm(

@pytest.mark.django_db
def test_algorithm_with_invalid_output(
algorithm_image, settings, django_capture_on_commit_callbacks
algorithm_image, client, settings, django_capture_on_commit_callbacks
):
# Override the celery settings
settings.task_eager_propagates = (True,)
Expand All @@ -333,6 +362,10 @@ def test_algorithm_with_invalid_output(
with open(algorithm_image, "rb") as f:
ai.image.save(algorithm_image, ContentFile(f.read()))

user = UserFactory()
ai.algorithm.add_editor(user)
VerificationFactory(user=user, is_verified=True)

recurse_callbacks(
callbacks=callbacks,
django_capture_on_commit_callbacks=django_capture_on_commit_callbacks,
Expand All @@ -359,19 +392,27 @@ def test_algorithm_with_invalid_output(
image_file = ImageFileFactory(
file__from_path=Path(__file__).parent / "resources" / "input_file.tif"
)

civ = ComponentInterfaceValueFactory(
image=image_file.image, interface=input_interface, file=None
)
assign_perm("cases.view_image", user, image_file.image)

with django_capture_on_commit_callbacks() as callbacks:
create_algorithm_jobs(
algorithm_image=ai,
civ_sets=[{civ}],
time_limit=ai.algorithm.time_limit,
requires_gpu_type=ai.algorithm.job_requires_gpu_type,
requires_memory_gb=ai.algorithm.job_requires_memory_gb,
get_view_for_user(
viewname="algorithms:job-create",
client=client,
method=client.post,
user=user,
reverse_kwargs={
"slug": ai.algorithm.slug,
"interface": interface.pk,
},
follow=True,
data={
**get_interface_form_data(
interface_slug=input_interface.slug,
data=image_file.image.pk,
),
},
)

recurse_callbacks(
callbacks=callbacks,
django_capture_on_commit_callbacks=django_capture_on_commit_callbacks,
Expand Down
4 changes: 3 additions & 1 deletion app/tests/algorithms_tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,7 @@ def test_job_gpu_type_set(client, settings):
assert algorithm.credits_per_job == 190


@pytest.mark.xfail(reason="Still to be addressed for optional inputs pitch")
@pytest.mark.django_db
def test_job_gpu_type_set_with_api(client, settings):
settings.COMPONENTS_DEFAULT_BACKEND = "grandchallenge.components.backends.amazon_sagemaker_training.AmazonSageMakerTrainingExecutor"
Expand Down Expand Up @@ -2167,7 +2168,8 @@ def test_interface_select_for_job_view_permission(client):
inputs=[ComponentInterfaceFactory()],
outputs=[ComponentInterfaceFactory()],
)
alg.interfaces.set([interface1, interface2])
alg.interfaces.add(interface1, through_defaults={"is_default": True})
alg.interfaces.add(interface2)

response = get_view_for_user(
viewname="algorithms:job-interface-select",
Expand Down

0 comments on commit f0b736b

Please sign in to comment.