diff --git a/kattcmd/commands/run.py b/kattcmd/commands/run.py index bd17f58a7039c4d83186b42bc57ee925bdf59459..9ba51dadc3f21d85d44271b347dfdc546bda3e6e 100644 --- a/kattcmd/commands/run.py +++ b/kattcmd/commands/run.py @@ -4,7 +4,7 @@ import click def RunPythonAgainstTests(bus, inputs, problemname): - '''Run python program against given inputs and return subprocesses/errors.''' + '''Run python program against given inputs and return output/errors.''' home = bus.call('kattcmd:find-root', bus) executable = os.path.join(home, 'build', problemname, problemname + '.py') @@ -39,6 +39,41 @@ def RunPythonAgainstTests(bus, inputs, problemname): return processes +def RunCppAgainstTests(bus, inputs, problemname): + '''Run c++ against given inputs and return outpu/errors.''' + home = bus.call('kattcmd:find-root', bus) + executable = os.path.join(home, 'build', problemname, problemname) + + if not os.path.exists(executable): + bus.call('kattcmd:run:no-executable', problemname, 'cpp') + return + + timeout = bus.call('kattcmd:config:load-user', bus, 'default-timeout') + timeout = timeout or bus.call('kattcmd:config:load-repo', bus, 'default-timeout') + timeout = int(timeout or '10') + + def RunSingleInput(inputfile): + abspath = os.path.abspath(inputfile) + command = '{} < {}'.format(os.path.abspath(executable), abspath) + try: + output = subprocess.check_output( + command, + timeout=timeout, + shell=True + ) + if isinstance(output, bytes): + output = output.decode('utf-8') + return output + except subprocess.TimeoutExpired as e: + return e + except subprocess.CalledProcessError as e: + return e + + processes = [RunSingleInput(inputfile) for inputfile in inputs] + bus.call('kattcmd:run:executed', problemname, 'cpp', inputs, processes) + return processes + + def Init(bus): def OnNoExecutable(problemname, executable_type): @@ -48,6 +83,8 @@ def Init(bus): '''Called when a problem has been executed and finished.''' bus.provide('kattcmd:run:python', RunPythonAgainstTests) + bus.provide('kattcmd:run:cpp', RunCppAgainstTests) bus.provide('kattcmd:run:no-executable', OnNoExecutable) bus.provide('kattcmd:run:executed', OnExecuted) + diff --git a/kattcmd/commands/submit.py b/kattcmd/commands/submit.py index ed99c051474d7381edf1e45933cb0b0d5e6e23c3..1970522123ca54b02c46005aaba740649e613c79 100644 --- a/kattcmd/commands/submit.py +++ b/kattcmd/commands/submit.py @@ -100,6 +100,30 @@ def SubmitPythonProblem(bus, problemname): bus.call('kattcmd:submit:submitted', submit_response) return submit_response + +def SubmitCppProblem(bus, problemname): + '''Submits a cpp problem to kattis.''' + + response = _Login(bus) + if response.status_code != 200: + bus.call('kattcmd:submit:bad-login-response', response) + return + + extensions = ['.cc', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.hxx'] + files = _GetFilesWithExtensions(bus, problemname, extensions) + language = 'C++' + submiturl = _GetUserConfig(bus).get('kattis', 'submissionurl') + submit_response = _Submit(submiturl, response.cookies, language, + problemname, files, problemname) + + if submit_response.status_code != 200: + bus.call('kattcmd:submit:bad-submit-response', submit_response) + return + + bus.call('kattcmd:submit:submitted', submit_response) + return submit_response + + def Init(bus): def OnBadLoginResponse(response): @@ -115,6 +139,7 @@ def Init(bus): bus.provide('kattcmd:submit:bad-login-response', OnBadLoginResponse) bus.provide('kattcmd:submit:bad-submit-response', OnBadSubmitResponse) bus.provide('kattcmd:submit:submitted', OnSubmitted) + bus.provide('kattcmd:submit:cpp', SubmitCppProblem) def CLI(bus, parent): @@ -141,25 +166,32 @@ def CLI(bus, parent): problemurl = '{}/submissions/{}'.format(host, ids[0]) click.launch(problemurl) + def OnNoFiles(problemname): + click.secho('Could not find any files for "{}"!'.format(problemname), fg='red') + click.echo('Exiting') + exit(1) @parent.command() @click.argument('name') - @click.argument('language', default='python', type=click.Choice(['python', 'cpp'])) - def submit(name, language): + def submit(name): '''Submits a problem to kattis. When you have implemented and tested a problem this command will upload it to kattis and then open the submission in your - browser. The language defaults to python and you must specify - which language your solution uses. Currently only supporting - 'python' and 'cpp' (as in C++). + browser. It will look for the files you modified most recently + and use the language that is written in to determine what to + files to upload. ''' bus.listen('kattcmd:submit:bad-login-response', OnBadLoginResponse) bus.listen('kattcmd:submit:bad-submit-response', OnBadSubmitResponse) bus.listen('kattcmd:submit:submitted', OnSubmitted) + bus.listen('kattcmd:latest:no-files', OnNoFiles) + language, _ = bus.call('kattcmd:latest', bus, name) if language == 'python': bus.call('kattcmd:submit:python', bus, name) + elif language == 'cpp': + bus.call('kattcmd:submit:cpp', bus, name) else: click.secho('Unsupported language!', fg='red') diff --git a/kattcmd/commands/test.py b/kattcmd/commands/test.py index 1af5b7b3827954938dd9906e8f87d5483a7907fe..48f1c6d91aa31d8bf496d6e9d2163981980650cb 100644 --- a/kattcmd/commands/test.py +++ b/kattcmd/commands/test.py @@ -75,6 +75,10 @@ def CLI(bus, parent): click.secho('Could not manage to find an executable for {}. Was looking for a {} file'.format(name, type), fg='red') exit(1) + def OnNoFiles(problemname): + click.secho('Could not find any files for problem "{}"'.format(problemname), fg='red') + click.echo('Exiting') + exit(1) @parent.command() @click.argument('name') @@ -95,6 +99,7 @@ def CLI(bus, parent): bus.listen('kattcmd:compile:cpp-compiled', OnCppCompiled) bus.listen('kattcmd:compile:cpp-compile-failed', OnCppFailed) bus.listen('kattcmd:run:no-executable', OnNoExecutable) + bus.listen('kattcmd:latest:no-files', OnNoFiles) # Compile name under most appropriate language root = bus.call('kattcmd:find-root', bus) @@ -103,23 +108,18 @@ def CLI(bus, parent): click.secho('Could not find problem with name: {}'.format(name), fg='red') return - cpp_endings = ['.cpp', '.cxx', '.cc'] - directory = os.path.join(root, 'kattis', name) - items = os.listdir(directory) - if '{}.py'.format(name) in items: - files = bus.call('kattcmd:compile:python', bus, name) - topic = 'kattcmd:run:python' - args = [name] - - elif any('{}{}'.format(name, ext) in items for ext in cpp_endings): - binary = bus.call('kattcmd:compile:cpp', bus, name) - binary = os.path.relpath(binary) - click.secho('Not implemented yet, but binary is compiled and at {}'.format(binary)) - return + # TODO: below should use kattcmd:latest + language, _ = bus.call('kattcmd:latest', bus, name) + topic_mapping = { + 'python': 'kattcmd:run:python', + 'cpp': 'kattcmd:run:cpp' + } - else: - click.secho('Could not find main program to be either C++ or Python') + if not language in topic_mapping: + click.secho('Cannot run for language: {}'.format(language), fg='red') return + + topic = topic_mapping[language] items = [os.path.join(directory, item) for item in items] # Find all test files diff --git a/test/test_run.py b/test/test_run.py index 7979cff1bf4b168829b068ced03efd04a04ab5cd..41cac7ec5621c8d4a8048ce857e60b81aa157584 100644 --- a/test/test_run.py +++ b/test/test_run.py @@ -10,6 +10,17 @@ a, b = map(int, sys.stdin.readline().split()) print(b) """ +_CarrotsSolutionCpp = """ +#include <iostream> +using namespace std; +int main() { + int a; + cin >> a >> a; + cout << a << endl; + return 0; +} +""" + @WithCustomCWD @WithMostModules def test_PythonRun(bus): @@ -42,3 +53,39 @@ def test_PythonRun(bus): assert isinstance(output, str) assert output.strip() == "1" + +@WithCustomCWD +@WithMostModules +def test_CppRun(bus): + problemname = 'carrots' + calls = [ + ('kattcmd:init', 'kattcmd:init:directory-created'), + ('kattcmd:open', 'kattcmd:open:problem-opened', [problemname]) + ] + assert all(checker.yay for _, checker in ExecuteInOrder(bus, calls)) + + home = os.environ['HOME'] + source_path = os.path.join(home, 'kattis', problemname, problemname + '.cpp') + with open(source_path, 'w') as f: + f.write(_CarrotsSolutionCpp) + + calls = [ + ('kattcmd:compile:cpp', 'kattcmd:compile:cpp-compiled', [problemname]) + ] + assert all(checker.yay for _, checker in ExecuteInOrder(bus, calls)) + + home = os.environ['HOME'] + + inputfname = os.path.join(home, 'test1.in') + with open(inputfname, 'w') as f: + f.write("2 1") + + checker = CallChecker() + bus.listen('kattcmd:run:executed', checker) + outputs = bus.call('kattcmd:run:cpp', bus, [inputfname], problemname) + assert checker.yay + + assert len(outputs) == 1 + output = outputs[0] + assert isinstance(output, str) + assert output.strip() == "1" diff --git a/test/test_submit.py b/test/test_submit.py index b54096541f5cd8b41eddd0f775072723395d2ed0..c1c528cde12d8d2fea2d26b2dd731cf71715a61c 100644 --- a/test/test_submit.py +++ b/test/test_submit.py @@ -10,6 +10,17 @@ a, b = map(int, sys.stdin.readline().split()) print(b) """ +_CarrotsSolutionCpp = """ +#include <iostream> +using namespace std; +int main() { + int a; + cin >> a >> a; + cout << a << endl; + return 0; +} +""" + def ShouldSkipSubmit(): expected_envs = [ 'KATTIS_TOKEN', @@ -83,3 +94,40 @@ def test_SubmitCarrotsWithPython(bus): assert result.text and isinstance(result.text, str) assert 'Submission ID:' in result.text + +@pytest.mark.submission +@pytest.mark.skipif(ShouldSkipSubmit(), reason='No environment variables for running submit') +@WithCustomCWD +@WithMostModules +def test_SubmitCarrotsWithCpp(bus): + SetupKattisRC() + problemname = 'carrots' + solution = _CarrotsSolutionCpp + + # Init and open carrots problem + calls = [ + ('kattcmd:init', 'kattcmd:init:directory-created'), + ('kattcmd:open', 'kattcmd:open:problem-opened', [problemname]) + ] + assert all(checker.yay for _, checker in ExecuteInOrder(bus, calls)) + + # Create solution + home = bus.call('kattcmd:find-root', bus) + target = os.path.join(home, 'kattis', problemname, problemname + '.cpp') + with open(target, 'w') as f: + f.write(solution) + + # Submit + calls = [ + ('kattcmd:submit:cpp', 'kattcmd:submit:submitted', [problemname]) + ] + + results = list(ExecuteInOrder(bus, calls)) + assert len(results) == 1 + + # Check call and that the text received from kattis is as expected + result, checker = results[0] + assert checker.yay + assert result.text and isinstance(result.text, str) + assert 'Submission ID:' in result.text +