diff --git a/algorithmic/problems/23/check.cpp b/algorithmic/problems/23/check.cpp index 49d9140b9..fd4a73fb2 100644 --- a/algorithmic/problems/23/check.cpp +++ b/algorithmic/problems/23/check.cpp @@ -351,6 +351,8 @@ namespace test_gen{ } + if (op<0 || op>=(int)vf.size()) + quitf(_fail, "Tid %d selects no test set. Please fix the data !!!",Tid); (vf[op])(); } @@ -368,7 +370,6 @@ namespace test_gen{ } void check_Tid(){ test_gen::Tid=inf.readInteger(); - if (test_gen::Tid%100==0) quitf(_ok,"Test 0."); } void read_test(){ test_gen::gao(); @@ -387,6 +388,10 @@ void read_test(){ */ } void validate_test(){ + // Every test must run the program. Test set 0 is empty, and a Tid with + // Tid%100==0 used to be accepted without running it (#205). + if (Tcase==0) + quitf(_fail, "Tid %d generates no test strings. Please fix the data !!!",test_gen::Tid); pattern tp1("[abcS]{3,1000}"); pattern tp2("[abc]{1,1000}S[abc]{1,1000}"); for (int T=1;T<=Tcase;T++){ diff --git a/algorithmic/problems/23/statement.txt b/algorithmic/problems/23/statement.txt index 122510f0b..c8d983452 100644 --- a/algorithmic/problems/23/statement.txt +++ b/algorithmic/problems/23/statement.txt @@ -321,8 +321,6 @@ b=1 ## Scoring -For Tid divisible by 100, the checker validates the submitted program syntax and then accepts it for 100 points without executing generated test strings. No currently released test case uses Tid divisible by 100. The execution-based rules below apply to the other test identifiers. - Let x be E, the number of executed instructions in a test string. C=max(2*l*l,50), where l is the initial string length. The execution limit remains 2*C; program-length and correctness requirements still apply. The physical reference (the 60-point level) is a=C. Define the lower-band continuation: L(x) = 60*max(0,(2*C-x)/C). diff --git a/algorithmic/tests/test_problem23.py b/algorithmic/tests/test_problem23.py new file mode 100644 index 000000000..f9387d164 --- /dev/null +++ b/algorithmic/tests/test_problem23.py @@ -0,0 +1,58 @@ +"""Every test must run the submitted A=B program; Tid % 100 == 0 used to be accepted unchecked (#205).""" +from pathlib import Path +import re +import subprocess +import pytest + +ROOT = Path(__file__).resolve().parents[2] +DATA = ROOT / 'algorithmic/problems/23/testdata' +RELEASED = sorted(int(p.stem) for p in DATA.glob('*.in')) +# Every N.ans holds the same correct A=B program for the substring task. +REFERENCE = (DATA / '1.ans').read_text() +EMPTY, ONE_RULE = '', 'a=b\n' + + +@pytest.fixture(scope='module') +def checker(tmp_path_factory): + executable = tmp_path_factory.mktemp('checker23') / 'checker' + subprocess.run(['g++', '-std=gnu++17', '-O2', '-I' + str(ROOT / 'algorithmic/judge/include'), + str(ROOT / 'algorithmic/problems/23/check.cpp'), '-o', str(executable)], check=True) + return executable + + +def check(checker, tmp_path, data, program): + inf, out = tmp_path / 'in', tmp_path / 'out' + inf.write_text(data) + out.write_text(program) + return subprocess.run([str(checker), str(inf), str(out), str(DATA / '1.ans')], + text=True, capture_output=True, timeout=30) + + +# Tid % 100 picks the test set, and set 0 is empty. The ids at the pinned commit +# c7ec558 were 0, 100 and 200, and each accepted any parseable program as "ok Test 0.". +@pytest.mark.parametrize('tid', [0, 100, 200, 2000000000]) +@pytest.mark.parametrize('program', [EMPTY, ONE_RULE, REFERENCE], ids=['empty', 'one-rule', 'reference']) +def test_tid_divisible_by_100_is_never_accepted(checker, tmp_path, tid, program): + result = check(checker, tmp_path, '%d\n' % tid, program) + assert result.returncode == 3, result.stderr + assert 'generates no test strings' in result.stderr + + +# 17 to 20 indexed past the 17 generators and crashed; 21 to 99 fell back to the +# empty set 0 and scored nan. +@pytest.mark.parametrize('tid', [17, 20, 21, 50, 1234567899]) +def test_tid_without_a_test_set_is_a_data_error(checker, tmp_path, tid): + result = check(checker, tmp_path, '%d\n' % tid, REFERENCE) + assert result.returncode == 3, result.stderr + assert result.stderr.startswith('FAIL Tid %d ' % tid), result.stderr + + +@pytest.mark.parametrize('test', RELEASED) +def test_released_tests_run_the_program(checker, tmp_path, test): + data = (DATA / ('%d.in' % test)).read_text() + for program in (EMPTY, ONE_RULE): + result = check(checker, tmp_path, data, program) + assert result.returncode == 1 and 'WA on Test case' in result.stderr, result.stderr + result = check(checker, tmp_path, data, REFERENCE) + assert result.returncode == 7, result.stderr + assert 0 < float(re.search(r'Ratio: ([0-9.]+)', result.stderr).group(1)) <= 1