Skip to content

Commit

Permalink
Merge pull request #227 from sio2project/parallel-test-content-ver
Browse files Browse the repository at this point in the history
Validate tests in parallel
  • Loading branch information
MasloMaslane authored Apr 5, 2024
2 parents 673aa90 + bd31b1b commit e842c4d
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 22 deletions.
6 changes: 2 additions & 4 deletions src/sinol_make/commands/ingen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def configure_subparser(self, subparser):
help='path to ingen source file, for example prog/abcingen.cpp')
parser.add_argument('-n', '--no-validate', default=False, action='store_true',
help='do not validate test contents')
parsers.add_cpus_argument(parser, 'number of cpus used for validating tests')
parser.add_argument('-f', '--fsanitize', default=False, action='store_true',
help='Use -fsanitize=address,undefined for compilation. Warning: this may fail on some '
'systems. Tof fix this, run `sudo sysctl vm.mmap_rnd_bits = 28`.')
Expand Down Expand Up @@ -74,8 +75,5 @@ def run(self, args: argparse.Namespace):
f.write("\n".join(glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in"))))

if not self.args.no_validate:
print(util.info('Validating input test contents.'))
tests = sorted(glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in")))
for test in tests:
package_util.validate_test(test)
print(util.info('Input test contents are valid!'))
package_util.validate_tests(tests, self.args.cpus, 'input')
5 changes: 1 addition & 4 deletions src/sinol_make/commands/outgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,4 @@ def run(self, args: argparse.Namespace):
yaml.dump(md5_sums, f)

if not self.args.no_validate:
print(util.info('Validating output test contents.'))
for test in sorted(outputs_to_generate):
package_util.validate_test(test)
print(util.info('Output test contents are valid!'))
package_util.validate_tests(sorted(outputs_to_generate), self.args.cpus, 'outputs')
39 changes: 31 additions & 8 deletions src/sinol_make/helpers/package_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import yaml
import glob
import fnmatch
import multiprocessing as mp
from enum import Enum
from typing import List, Union, Dict, Any
from typing import List, Union, Dict, Any, Tuple

from sinol_make.helpers.func_cache import cache_result
from sinol_make import util
Expand Down Expand Up @@ -355,11 +356,12 @@ def save_contest_type_to_cache(contest_type):
contest_type_file.write(contest_type)


def validate_test(test_path: str):
def validate_test(test_path: str) -> Tuple[bool, str]:
"""
Check if test doesn't contain leading/trailing whitespaces,
has only one space between tokens and ends with newline.
Exits with error if any of the conditions is not met.
:return: Tuple of two values: True if test is valid, error message otherwise.
"""
basename = os.path.basename(test_path)
num_empty = 0
Expand All @@ -368,24 +370,45 @@ def validate_test(test_path: str):
for i, line in enumerate(lines):
line = line.decode('utf-8')
if len(line) > 0 and line[0] == ' ':
util.exit_with_error(f'Leading whitespace in {basename}:{i + 1}')
return False, util.error(f'Leading whitespace in {basename}:{i + 1}')
if len(line) > 0 and (line[-2:] == '\r\n' or line[-2:] == '\n\r' or line[-1] == '\r'):
util.exit_with_error(f'Carriage return at the end of {basename}:{i + 1}')
return False, util.error(f'Carriage return at the end of {basename}:{i + 1}')
if len(line) > 0 and line[-1] != '\n':
util.exit_with_error(f'No newline at the end of {basename}')
return False, util.error(f'No newline at the end of {basename}')
if line == '\n' or line == '':
num_empty += 1
continue
elif i == len(lines) - 1:
num_empty = 0
if line[-2] == ' ':
util.exit_with_error(f'Trailing whitespace in {basename}:{i + 1}')
return False, util.error(f'Trailing whitespace in {basename}:{i + 1}')
for j in range(len(line) - 1):
if line[j] == ' ' and line[j + 1] == ' ':
util.exit_with_error(f'Tokens not separated by one space in {basename}:{i + 1}')
return False, util.error(f'Tokens not separated by one space in {basename}:{i + 1}')

if num_empty != 0:
util.exit_with_error(f'Exactly one empty line expected in {basename}')
return False, util.error(f'Exactly one empty line expected in {basename}')

return True, ''


def validate_tests(tests: List[str], cpus: int, type: str = 'input'):
"""
Validate all tests in parallel.
"""
if not tests:
return
print(f'Validating {type} test contents.')
num_tests = len(tests)
finished = 0
with mp.Pool(cpus) as pool:
for valid, message in pool.imap(validate_test, tests):
if not valid:
util.exit_with_error(message)
finished += 1
print(f'Validated {finished}/{num_tests} tests', end='\r')
print()
print(util.info(f'All {type} tests are valid!'))


def get_all_inputs(task_id):
Expand Down
9 changes: 3 additions & 6 deletions tests/commands/gen/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,6 @@ def test_validate_tests(create_package, capsys):
]

for test, error in tests:
with pytest.raises(SystemExit) as e:
package_util.validate_test(os.path.join(package_path, "in", test))
assert e.type == SystemExit
assert e.value.code == 1
captured = capsys.readouterr()
assert error in captured.out, f"Expected error not found in output: {captured.out}"
valid, msg = package_util.validate_test(os.path.join(package_path, "in", test))
assert not valid
assert error in msg

0 comments on commit e842c4d

Please sign in to comment.