htranx commited on
Commit
a02bbc5
·
1 Parent(s): 8e8d312

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)

Files changed (2) hide show
  1. app.py +19 -4
  2. 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. Use **Validate format (no submit)** to check your fields without spending one.
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. **Format is checked "
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)