tell the user the submit button is working, and stop claiming a format check (#15)
Browse files- tell the user the submit button is working, and stop claiming a format check (34c4bfc4f1e6082f070348534e53df3a7dae5c31)
- app.py +19 -4
- tests/test_val_phase_closed.py +70 -0
app.py
CHANGED
|
@@ -621,7 +621,7 @@ stronger, upload both and submit only the one you want scored.
|
|
| 621 |
|
| 622 |
We evaluate the exact image recorded at registration. A later push to the same repository
|
| 623 |
does **not** change a submission already made — submit again if you want the newer image
|
| 624 |
-
evaluated.
|
| 625 |
|
| 626 |
### After you submit
|
| 627 |
|
|
@@ -879,7 +879,19 @@ def test_submit_tab():
|
|
| 879 |
interactive=config.TEST_PHASE_OPEN)
|
| 880 |
out = gr.Markdown()
|
| 881 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 882 |
btn.click(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 883 |
do_test_submit,
|
| 884 |
inputs=[
|
| 885 |
reg_id, model_name, license_str, open_weight,
|
|
@@ -889,6 +901,11 @@ def test_submit_tab():
|
|
| 889 |
# Serialize so the per-track cap check cannot be raced by one team firing
|
| 890 |
# two registrations at once. The worker enforces the cap again anyway.
|
| 891 |
concurrency_limit=1,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 892 |
)
|
| 893 |
|
| 894 |
|
|
@@ -989,9 +1006,7 @@ def _subs_view(profile: gr.OAuthProfile | None):
|
|
| 989 |
def my_submissions_tab():
|
| 990 |
gr.Markdown("## My Submissions")
|
| 991 |
gr.Markdown(
|
| 992 |
-
"Track your own submissions through the pipeline. **
|
| 993 |
-
"instantly when you submit**, so a *Pending* entry has already passed "
|
| 994 |
-
"format validation and is just awaiting scoring; only *Scored* entries "
|
| 995 |
"appear on the leaderboard."
|
| 996 |
)
|
| 997 |
login = gr.LoginButton(scale=0)
|
|
|
|
| 621 |
|
| 622 |
We evaluate the exact image recorded at registration. A later push to the same repository
|
| 623 |
does **not** change a submission already made — submit again if you want the newer image
|
| 624 |
+
evaluated.
|
| 625 |
|
| 626 |
### After you submit
|
| 627 |
|
|
|
|
| 879 |
interactive=config.TEST_PHASE_OPEN)
|
| 880 |
out = gr.Markdown()
|
| 881 |
|
| 882 |
+
# Three steps, not one. The call takes about 20 seconds and said nothing
|
| 883 |
+
# while it ran, so a second click looked like the reasonable thing to
|
| 884 |
+
# do: two identical submissions landed 8 seconds apart on 2026-08-08.
|
| 885 |
+
# Disabling the button is the half that prevents it; the message is the
|
| 886 |
+
# half that stops the participant wanting to.
|
| 887 |
btn.click(
|
| 888 |
+
lambda: (
|
| 889 |
+
gr.update(value="Submitting...", interactive=False),
|
| 890 |
+
"⏳ Submitting. This takes up to a minute, please do not click "
|
| 891 |
+
"again or reload.",
|
| 892 |
+
),
|
| 893 |
+
outputs=[btn, out],
|
| 894 |
+
).then(
|
| 895 |
do_test_submit,
|
| 896 |
inputs=[
|
| 897 |
reg_id, model_name, license_str, open_weight,
|
|
|
|
| 901 |
# Serialize so the per-track cap check cannot be raced by one team firing
|
| 902 |
# two registrations at once. The worker enforces the cap again anyway.
|
| 903 |
concurrency_limit=1,
|
| 904 |
+
).then(
|
| 905 |
+
# Re-enabled whether the submission succeeded or failed: a refusal a
|
| 906 |
+
# team cannot retry is worse than the refusal itself.
|
| 907 |
+
lambda: gr.update(value="Submit for evaluation", interactive=True),
|
| 908 |
+
outputs=btn,
|
| 909 |
)
|
| 910 |
|
| 911 |
|
|
|
|
| 1006 |
def my_submissions_tab():
|
| 1007 |
gr.Markdown("## My Submissions")
|
| 1008 |
gr.Markdown(
|
| 1009 |
+
"Track your own submissions through the pipeline. Only *Scored* entries "
|
|
|
|
|
|
|
| 1010 |
"appear on the leaderboard."
|
| 1011 |
)
|
| 1012 |
login = gr.LoginButton(scale=0)
|
tests/test_val_phase_closed.py
CHANGED
|
@@ -389,3 +389,73 @@ class CredentialsPanelShapeTest(unittest.TestCase):
|
|
| 389 |
def test_the_secret_is_shown_once_and_only_in_the_login_block(self) -> None:
|
| 390 |
body = self.panel()
|
| 391 |
self.assertEqual(body.count("not-a-real-secret"), 2, "table row plus export")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
def test_the_secret_is_shown_once_and_only_in_the_login_block(self) -> None:
|
| 390 |
body = self.panel()
|
| 391 |
self.assertEqual(body.count("not-a-real-secret"), 2, "table row plus export")
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
class SubmitButtonFeedbackTest(unittest.TestCase):
|
| 395 |
+
"""The submit button must say something before the 20-second call returns.
|
| 396 |
+
|
| 397 |
+
Silence reads as "the click did not register", so the reasonable next move
|
| 398 |
+
is to click again. That is what produced two identical test submissions 8
|
| 399 |
+
seconds apart on 2026-08-08, and a FAILED submission consumes one of a
|
| 400 |
+
team's three slots.
|
| 401 |
+
|
| 402 |
+
Read as source: app.py needs gradio, which is not installed everywhere the
|
| 403 |
+
tests run, and a test that skips itself on a missing import reports nothing
|
| 404 |
+
while looking green.
|
| 405 |
+
"""
|
| 406 |
+
|
| 407 |
+
def setUp(self) -> None:
|
| 408 |
+
path = os.path.join(
|
| 409 |
+
os.path.dirname(os.path.abspath(__file__)), os.pardir, "app.py"
|
| 410 |
+
)
|
| 411 |
+
with open(path) as fh:
|
| 412 |
+
self.src = fh.read()
|
| 413 |
+
block = self.src[self.src.index("def test_submit_tab("):]
|
| 414 |
+
self.block = block[: block.index("\ndef ", 1)]
|
| 415 |
+
|
| 416 |
+
def test_the_click_chain_gives_immediate_feedback(self) -> None:
|
| 417 |
+
self.assertIn("Submitting...", self.block)
|
| 418 |
+
self.assertIn(".then(", self.block, "feedback then work, not one handler")
|
| 419 |
+
|
| 420 |
+
def test_the_button_is_disabled_while_it_runs(self) -> None:
|
| 421 |
+
i = self.block.index("Submitting...")
|
| 422 |
+
self.assertIn("interactive=False", self.block[i - 200 : i + 200])
|
| 423 |
+
|
| 424 |
+
def test_it_is_re_enabled_afterwards(self) -> None:
|
| 425 |
+
"""Including after a refusal: a team must be able to fix and retry."""
|
| 426 |
+
tail = self.block[self.block.rindex(".then(") :]
|
| 427 |
+
self.assertIn("interactive=True", tail)
|
| 428 |
+
self.assertIn("Submit for evaluation", tail)
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
class NoFormatCheckClaimedForTheTestPhaseTest(unittest.TestCase):
|
| 432 |
+
"""Nothing format-checks a test submission, so nothing may say it does.
|
| 433 |
+
|
| 434 |
+
A test submission is an image digest, not a predictions file. Claiming a
|
| 435 |
+
check that does not run tells a team its submission is further along than it
|
| 436 |
+
is, and right now no worker processes test requests at all.
|
| 437 |
+
"""
|
| 438 |
+
|
| 439 |
+
def setUp(self) -> None:
|
| 440 |
+
path = os.path.join(
|
| 441 |
+
os.path.dirname(os.path.abspath(__file__)), os.pardir, "app.py"
|
| 442 |
+
)
|
| 443 |
+
with open(path) as fh:
|
| 444 |
+
self.src = fh.read()
|
| 445 |
+
|
| 446 |
+
def test_my_submissions_does_not_claim_a_format_check(self) -> None:
|
| 447 |
+
block = self.src[self.src.index("def my_submissions_tab("):]
|
| 448 |
+
# The function may be the last one in the file, so slicing to the next
|
| 449 |
+
# `def` has to tolerate there not being one. Getting this wrong makes
|
| 450 |
+
# the test error rather than fail, which reads as a broken test rather
|
| 451 |
+
# than as a broken claim.
|
| 452 |
+
nxt = block.find("\ndef ", 1)
|
| 453 |
+
if nxt != -1:
|
| 454 |
+
block = block[:nxt]
|
| 455 |
+
self.assertNotIn("Format is checked", block)
|
| 456 |
+
self.assertNotIn("passed format validation", block)
|
| 457 |
+
|
| 458 |
+
def test_the_test_guide_does_not_offer_a_control_it_lacks(self) -> None:
|
| 459 |
+
guide = self.src[self.src.index("_TEST_GUIDE_MD = "):]
|
| 460 |
+
guide = guide[: guide.index('"""', guide.index('"""') + 3)]
|
| 461 |
+
self.assertNotIn("Validate format", guide)
|