Skip to content

Commit 85a6ec0

Browse files
Add inwer command (#34)
* Add inwer command * Small changes in run command * Add group number in inwer command * Create a struct with data for printing view * Fix `run` tests * Parse verification results * Fix tests for run command * Add tests for `package_util` * Add package for verifying `iwner` command * Refactor ins and outs generation in tests * Add second inwer for test package * Refactor package_util * Fix problems with `run` command tests * Add tests for `inwer` command * Fix tests on macOS * Fix typos Co-authored-by: Tomasz Nowak <[email protected]> * Use ???inwer.* by default * Add function for compiling files and use it in inwer_util * Add comments to TableData * Update function name for TestResult * Add description for extract_test_no function and use it in TestResult constructor * Different compiler's help messages depending on system * Add more tests for package_util.extract_test_no * Rename inwer argument to inwer_path * Replace os.system("head ...") with Python function * Allow running multiple inwers in parallel * Fix printing of compilation log * Fix error messages * Fix for running `inwer` in parallel * Fix for running `inwer` in parallel * Add missing compilation flags * Refactor help messages * Rename extract_test_no to extract_test_id * Apply suggestions from code review Co-authored-by: Tomasz Nowak <[email protected]> * Fix tests * Revert passing test name to inwer * Fix tests * Fix tests * Add support for inwers that use assert * Add tests for asserting inwer * Fix tests on macOS * Update readme with inwer information * Apply suggestions from code review Co-authored-by: Tomasz Nowak <[email protected]> * Bump version for release --------- Co-authored-by: Tomasz Nowak <[email protected]>
1 parent 074fab6 commit 85a6ec0

31 files changed

+910
-165
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ The availabe commands (see `sinol-make --help`) are:
5454
of CPUs. Measures the solutions' time with oiejq, unless specified otherwise. After running the solutions, it
5555
compares the solutions' scores with the ones saved in config.yml.
5656
Run `sinol-make run --help` to see available flags.
57+
- `sinol-make inwer` -- Verifies whether input files are correct using your "inwer.cpp" program. You can specify what inwer
58+
program to use, what tests to check and how many CPUs to use. Run `sinol-make inwer --help` to see available flags.
5759

5860
### Reporting bugs and contributing code
5961

6062
- Want to report a bug or request a feature? [Open an issue](https://github.com/sio2project/sinol-make/issues).
6163
- Want to help us build `sinol-make`? Create a Pull Request and we will gladly review it.
62-

src/sinol_make/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from sinol_make import util
55

6-
__version__ = "1.1.2"
6+
__version__ = "1.2.0"
77

88
def configure_parsers():
99
parser = argparse.ArgumentParser(
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import subprocess
2+
import argparse
3+
import os
4+
import multiprocessing as mp
5+
6+
from sinol_make import util
7+
from sinol_make.commands.inwer.structs import TestResult, InwerExecution, VerificationResult, TableData
8+
from sinol_make.helpers import package_util, compile
9+
from sinol_make.helpers.parsers import add_compilation_arguments
10+
from sinol_make.interfaces.BaseCommand import BaseCommand
11+
from sinol_make.commands.inwer import inwer_util
12+
13+
14+
class Command(BaseCommand):
15+
"""
16+
Class for "inwer" command.
17+
"""
18+
19+
def get_name(self):
20+
return "inwer"
21+
22+
def configure_subparser(self, subparser: argparse.ArgumentParser):
23+
parser = subparser.add_parser(
24+
self.get_name(),
25+
help='Verify if input files are correct',
26+
description='Verify if input files are correct using inwer program '
27+
'(for example prog/abcinwer.cpp for abc task). You can also '
28+
'specify your inwer source file which will be used.'
29+
)
30+
31+
parser.add_argument('inwer_path', type=str, nargs='?',
32+
help='path to inwer source file, for example prog/abcinwer.cpp')
33+
parser.add_argument('--tests', type=str, nargs='+',
34+
help='test to verify, for example in/abc{0,1}*')
35+
parser.add_argument('--cpus', type=int,
36+
help=f'number of cpus to use, by default {mp.cpu_count()} (all available)')
37+
add_compilation_arguments(parser)
38+
39+
def compile_inwer(self, args: argparse.Namespace):
40+
self.inwer_executable, compile_log_path = inwer_util.compile_inwer(self.inwer, args)
41+
if self.inwer_executable is None:
42+
print(util.error('Compilation failed.'))
43+
compile.print_compile_log(compile_log_path)
44+
exit(1)
45+
else:
46+
print(util.info('Compilation successful.'))
47+
48+
@staticmethod
49+
def verify_test(execution: InwerExecution) -> VerificationResult:
50+
"""
51+
Verifies a test and returns the result of inwer on this test.
52+
"""
53+
output_dir = os.path.join(os.getcwd(), 'cache', 'executions', execution.test_name)
54+
os.makedirs(output_dir, exist_ok=True)
55+
56+
command = f'{execution.inwer_exe_path} < {execution.test_path}'
57+
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
58+
process.wait()
59+
exit_code = process.returncode
60+
out, _ = process.communicate()
61+
62+
return VerificationResult(
63+
execution.test_path,
64+
exit_code == 0,
65+
out.decode('utf-8')
66+
)
67+
68+
def verify_and_print_table(self) -> dict[str, TestResult]:
69+
results = {}
70+
sorted_tests = sorted(self.tests, key=lambda x: x[0])
71+
executions: list[InwerExecution] = []
72+
for test in sorted_tests:
73+
results[test] = TestResult(test)
74+
executions.append(InwerExecution(test, results[test].test_name, self.inwer_executable))
75+
76+
table_data = TableData(results, 0)
77+
print('Verifying tests...\n\n')
78+
with mp.Pool(self.cpus) as pool:
79+
for i, result in enumerate(pool.imap(self.verify_test, executions)):
80+
table_data.results[result.test_path].set_results(result.valid, result.output)
81+
inwer_util.print_view(table_data)
82+
83+
return results
84+
85+
def run(self, args: argparse.Namespace):
86+
if not util.check_if_project():
87+
util.exit_with_error('You are not in a project directory (couldn\'t find config.yml in current directory).')
88+
89+
self.task_id = package_util.get_task_id()
90+
self.inwer = inwer_util.get_inwer_path(self.task_id, args.inwer_path)
91+
if self.inwer is None:
92+
if args.inwer_path is None:
93+
util.exit_with_error('No inwer found in `prog/` directory.')
94+
else:
95+
util.exit_with_error(f'Inwer "{args.inwer_path}" not found.')
96+
relative_path = os.path.relpath(self.inwer, os.getcwd())
97+
print(f'Verifying with inwer {util.bold(relative_path)}')
98+
99+
self.cpus = args.cpus or mp.cpu_count()
100+
self.tests = package_util.get_tests(args.tests)
101+
102+
if len(self.tests) == 0:
103+
util.exit_with_error('No tests found.')
104+
else:
105+
print('Verifying tests: ' + util.bold(', '.join(self.tests)))
106+
107+
self.compile_inwer(args)
108+
results: dict[str, TestResult] = self.verify_and_print_table()
109+
print('')
110+
111+
failed_tests = []
112+
for result in results.values():
113+
if not result.valid:
114+
failed_tests.append(result.test_name)
115+
116+
if len(failed_tests) > 0:
117+
util.exit_with_error(f'Verification failed for tests: {", ".join(failed_tests)}')
118+
else:
119+
print(util.info('Verification successful.'))
120+
exit(0)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import glob
2+
import os
3+
import sys
4+
5+
import argparse
6+
7+
from sinol_make import util
8+
from sinol_make.commands.inwer import TestResult, TableData
9+
from sinol_make.helpers import compile, package_util
10+
from sinol_make.helpers import compiler
11+
from sinol_make.interfaces.Errors import CompilationError
12+
13+
14+
def get_inwer_path(task_id: str, path = None) -> str or None:
15+
"""
16+
Returns path to inwer executable for given task or None if no inwer was found.
17+
"""
18+
if path is None:
19+
inwers = glob.glob(os.path.join(os.getcwd(), 'prog', f'{task_id}inwer.*'))
20+
if len(inwers) == 0:
21+
return None
22+
return inwers[0]
23+
else:
24+
inwer = os.path.join(os.getcwd(), path)
25+
if os.path.exists(inwer):
26+
return inwer
27+
return None
28+
29+
30+
def compile_inwer(inwer_path: str, args: argparse.Namespace):
31+
"""
32+
Compiles inwer and returns path to compiled executable and path to compile log.
33+
"""
34+
compilers = compiler.verify_compilers(args, [inwer_path])
35+
return compile.compile_file(inwer_path, package_util.get_executable(inwer_path), compilers)
36+
37+
38+
def print_view(table_data: TableData):
39+
"""
40+
Prints current results of test verification.
41+
"""
42+
43+
sys.stdout.write(f'\033[{table_data.previous_vertical_height}A')
44+
table_data.previous_vertical_height = 2
45+
46+
results = table_data.results
47+
column_lengths = [0, len('Group') + 1, len('Status') + 1, 0]
48+
sorted_test_paths = []
49+
for result in results.values():
50+
column_lengths[0] = max(column_lengths[0], len(result.test_name) + 1)
51+
column_lengths[1] = max(column_lengths[1], len(result.test_group) + 1)
52+
sorted_test_paths.append(result.test_path)
53+
sorted_test_paths.sort()
54+
55+
try:
56+
terminal_width = os.get_terminal_size().columns
57+
except OSError:
58+
terminal_width = 80
59+
60+
column_lengths[3] = max(10, terminal_width - 20 - column_lengths[0] - column_lengths[1] - column_lengths[2] - 9) # 9 is for " | " between columns
61+
62+
print("Test".ljust(column_lengths[0]) + " | " + "Group".ljust(column_lengths[1] - 1) + " | " + "Status" + " | " + "Output".ljust(column_lengths[3]))
63+
print("-" * (column_lengths[0] + 1) + "+" + "-" * (column_lengths[1] + 1) + "+" +
64+
"-" * (column_lengths[2] + 1) + "+" + "-" * (column_lengths[3] + 1))
65+
66+
for test_path in sorted_test_paths:
67+
result = results[test_path]
68+
print(result.test_name.ljust(column_lengths[0]) + " | ", end='')
69+
print(result.test_group.ljust(column_lengths[1] - 1) + " | ", end='')
70+
71+
if result.verified:
72+
if result.valid:
73+
print(util.info("OK".ljust(column_lengths[2] - 1)), end='')
74+
else:
75+
print(util.error("ERROR".ljust(column_lengths[2] - 1)), end='')
76+
else:
77+
print(util.warning("...".ljust(column_lengths[2] - 1)), end='')
78+
print(" | ", end='')
79+
80+
output = []
81+
if result.verified:
82+
split_output = result.output.split('\n')
83+
for line in split_output:
84+
output += [line[i:i + column_lengths[3]] for i in range(0, len(line), column_lengths[3])]
85+
else:
86+
output.append("")
87+
88+
print(output[0].ljust(column_lengths[3]))
89+
table_data.previous_vertical_height += 1
90+
output.pop(0)
91+
92+
for line in output:
93+
print(" " * (column_lengths[0]) + " | " + " " * (column_lengths[1] - 1) + " | " +
94+
" " * (column_lengths[2] - 1) + " | " + line.ljust(column_lengths[3]))
95+
table_data.previous_vertical_height += 1
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
from dataclasses import dataclass
3+
4+
from sinol_make.helpers import package_util
5+
6+
7+
@dataclass
8+
class TestResult:
9+
test_path: str
10+
test_name: str
11+
test_group: str
12+
verified: bool
13+
valid: bool
14+
output: str
15+
16+
def __init__(self, test_path):
17+
self.test_path = test_path
18+
self.test_name = os.path.split(test_path)[-1]
19+
self.test_group = package_util.extract_test_id(self.test_path)
20+
21+
self.verified = False
22+
self.valid = False
23+
self.output = ""
24+
25+
def set_results(self, valid, output):
26+
self.verified = True
27+
self.valid = valid
28+
self.output = output
29+
30+
@dataclass
31+
class TableData:
32+
"""
33+
Data used for printing table with verification results.
34+
"""
35+
36+
# Dictionary with test path as key and verification result as value.
37+
results: dict[str, TestResult]
38+
39+
# Previous vertical height of table, used for moving cursor up.
40+
previous_vertical_height: int
41+
42+
@dataclass
43+
class InwerExecution:
44+
test_path: str
45+
test_name: str
46+
inwer_exe_path: str
47+
48+
@dataclass
49+
class VerificationResult:
50+
test_path: str
51+
valid: bool
52+
output: str

0 commit comments

Comments
 (0)