Skip to content

Commit 8db9106

Browse files
authored
Add input argument piping (#15)
1 parent c396714 commit 8db9106

File tree

12 files changed

+110
-26
lines changed

12 files changed

+110
-26
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The idea behind the script is the following:
99

1010
It is expected that the submitted homework will follow the folder structure specified in the `homework.yml` file.
1111

12-
## Core funcionality ##
12+
## Core functionality ##
1313

1414
### Run different tests ###
1515
For now we support running tests for code written in different languages:

homework_checker/core/schema_manager.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ def __init__(self: SchemaManager, file_name: Path):
3333

3434
test_schema = {
3535
Tags.NAME_TAG: str,
36-
Optional(Tags.INPUT_TAG): str,
36+
Optional(Tags.INPUT_ARGS_TAG): str,
3737
Optional(Tags.INJECT_FOLDER_TAG): [injection_schema],
3838
Optional(Tags.RUN_GTESTS_TAG, default=False): bool,
3939
Optional(Tags.EXPECTED_OUTPUT_TAG): Or(str, float, int),
40+
Optional(Tags.INPUT_PIPE_TAG, default=""): str,
41+
Optional(Tags.OUTPUT_PIPE_TAG, default=""): str,
4042
Optional(Tags.TIMEOUT_TAG, default=60): float,
4143
}
4244

@@ -47,9 +49,10 @@ def __init__(self: SchemaManager, file_name: Path):
4749
Optional(Tags.OUTPUT_TYPE_TAG, default=OutputTags.STRING): Or(
4850
OutputTags.STRING, OutputTags.NUMBER
4951
),
50-
Optional(Tags.COMPILER_FLAGS_TAG, default="-Wall"): str,
52+
Optional(
53+
Tags.COMPILER_FLAGS_TAG, default="-std=c++17 -Wall -Wpedantic -Wextra"
54+
): str,
5155
Optional(Tags.BINARY_NAME_TAG, default="main"): str,
52-
Optional(Tags.PIPE_TAG, default=""): str,
5356
Optional(Tags.BUILD_TYPE_TAG, default=BuildTags.CMAKE): Or(
5457
BuildTags.CMAKE, BuildTags.SIMPLE
5558
),

homework_checker/core/schema_tags.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ class Tags:
1616
INJECT_DESTINATION_TAG = "destination"
1717
INJECT_FOLDER_TAG = "inject_folders"
1818
INJECT_SOURCE_TAG = "source"
19-
INPUT_TAG = "input_args"
19+
INPUT_ARGS_TAG = "input_args"
20+
INPUT_PIPE_TAG = "input_pipe_args"
2021
LANGUAGE_TAG = "language"
2122
NAME_TAG = "name"
23+
OUTPUT_PIPE_TAG = "output_pipe_args"
2224
OUTPUT_TYPE_TAG = "output_type"
23-
PIPE_TAG = "pipe_through"
2425
RUN_GTESTS_TAG = "run_google_tests"
2526
TASKS_TAG = "tasks"
2627
TESTS_TAG = "tests"

homework_checker/core/tasks.py

+31-12
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,11 @@ def __init__(
7474
self._output_type = task_node[Tags.OUTPUT_TYPE_TAG]
7575
self._student_task_folder = student_task_folder
7676
self._binary_name = task_node[Tags.BINARY_NAME_TAG]
77-
self._pipe_through = task_node[Tags.PIPE_TAG]
7877
self._build_timeout = task_node[Tags.BUILD_TIMEOUT_TAG]
78+
79+
self._test_nodes = []
7980
if Tags.TESTS_TAG in task_node:
8081
self._test_nodes = task_node[Tags.TESTS_TAG]
81-
else:
82-
self._test_nodes = [] # Sometimes we don't have tests.
8382
self.__test_counter = 0
8483

8584
def __with_number_prefix(self: Task, test_name: str) -> str:
@@ -187,7 +186,7 @@ class CppTask(Task):
187186

188187
CMAKE_BUILD_CMD = "cmake .. && make -j2"
189188
REMAKE_AND_TEST = "make clean && rm -r * && cmake .. && make -j2 && ctest -VV"
190-
BUILD_CMD_SIMPLE = "clang++ -std=c++14 -o {binary} {compiler_flags} {binary}.cpp"
189+
BUILD_CMD_SIMPLE = "clang++ {compiler_flags} -o {binary} {binary}.cpp"
191190

192191
def __init__(self: CppTask, task_node: dict, root_folder: Path, job_file: Path):
193192
"""Initialize the C++ Task."""
@@ -247,17 +246,28 @@ def _run_test(self: CppTask, test_node: dict, executable_folder: Path):
247246
cwd=executable_folder,
248247
timeout=test_node[Tags.TIMEOUT_TAG],
249248
)
249+
250+
output_pipe_args = None
251+
if Tags.OUTPUT_PIPE_TAG in test_node:
252+
output_pipe_args = test_node[Tags.OUTPUT_PIPE_TAG]
253+
input_pipe_args = None
254+
if Tags.INPUT_PIPE_TAG in test_node:
255+
input_pipe_args = test_node[Tags.INPUT_PIPE_TAG]
256+
250257
input_str = ""
251-
if Tags.INPUT_TAG in test_node:
252-
input_str = test_node[Tags.INPUT_TAG]
258+
if Tags.INPUT_ARGS_TAG in test_node:
259+
input_str = test_node[Tags.INPUT_ARGS_TAG]
253260
run_cmd = "./{binary_name} {args}".format(
254261
binary_name=self._binary_name, args=input_str
255262
)
256-
if self._pipe_through:
257-
run_cmd += " " + self._pipe_through
263+
if input_pipe_args:
264+
run_cmd = input_pipe_args + " | " + run_cmd
265+
if output_pipe_args:
266+
run_cmd += " | " + output_pipe_args
258267
run_result = tools.run_command(
259268
run_cmd, cwd=executable_folder, timeout=test_node[Tags.TIMEOUT_TAG]
260269
)
270+
261271
if not run_result.succeeded():
262272
return run_result
263273
# TODO(igor): do I need explicit error here?
@@ -294,12 +304,21 @@ def _code_style_errors(self: BashTask):
294304
def _run_test(
295305
self: BashTask, test_node: dict, executable_folder: Path
296306
) -> tools.CmdResult:
307+
output_pipe_args = None
308+
if Tags.OUTPUT_PIPE_TAG in test_node:
309+
output_pipe_args = test_node[Tags.OUTPUT_PIPE_TAG]
310+
input_pipe_args = None
311+
if Tags.INPUT_PIPE_TAG in test_node:
312+
input_pipe_args = test_node[Tags.INPUT_PIPE_TAG]
313+
297314
input_str = ""
298-
if Tags.INPUT_TAG in test_node:
299-
input_str = test_node[Tags.INPUT_TAG]
315+
if Tags.INPUT_ARGS_TAG in test_node:
316+
input_str = test_node[Tags.INPUT_ARGS_TAG]
300317
run_cmd = BashTask.RUN_CMD.format(binary_name=self._binary_name, args=input_str)
301-
if self._pipe_through:
302-
run_cmd += " " + self._pipe_through
318+
if input_pipe_args:
319+
run_cmd = input_pipe_args + " | " + run_cmd
320+
if output_pipe_args:
321+
run_cmd += " | " + output_pipe_args
303322
run_result = tools.run_command(
304323
run_cmd, cwd=executable_folder, timeout=test_node[Tags.TIMEOUT_TAG]
305324
)

homework_checker/core/tests/data/homework/example_job.yml

+23-3
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ homeworks:
99
folder: task_1 # Name of the folder containing the Task.
1010
output_type: string # We expect a string as an output.
1111
binary_name: main
12-
tests: # An Task can have multiple tests.
12+
compiler_flags: "-std=c++17"
13+
tests: # A Task can have multiple tests.
1314
- name: String output test
1415
expected_output: > # this wraps into a long string, no line breaks.
1516
This is a long test output that we expect to be produced by the
16-
code. We will compare the ouput to this EXACTLY.
17+
code. We will compare the output to this EXACTLY.
1718
- name: Input output test
1819
input_args: Some string
1920
expected_output: >
@@ -24,11 +25,13 @@ homeworks:
2425
- name: Build failure task # This one should not build, no need for tests
2526
language: cpp
2627
folder: task_2
28+
compiler_flags: "-std=c++17"
2729
output_type: string
2830
- name: CMake build arithmetics task
2931
language: cpp
3032
folder: task_3
3133
output_type: number
34+
compiler_flags: "-std=c++17"
3235
binary_name: sum_numbers
3336
tests:
3437
- name: Test integer arithmetics
@@ -50,6 +53,18 @@ homeworks:
5053
test_me.sh
5154
- name: Test wrong output
5255
expected_output: Different output that doesn't match generated one
56+
- name: Test input piping
57+
language: cpp
58+
folder: task_5
59+
compiler_flags: "-std=c++17"
60+
output_type: string
61+
tests:
62+
- name: Test input piping
63+
input_pipe_args: echo hello world
64+
expected_output: |
65+
Input string:
66+
Input another string:
67+
hello_world
5368
5469
- name: "Homework where things go wrong"
5570
folder: "homework_2"
@@ -59,6 +74,7 @@ homeworks:
5974
language: cpp
6075
folder: task_1
6176
build_type: simple
77+
compiler_flags: "-std=c++17"
6278
output_type: number
6379
tests:
6480
# Should fail as the binary returns a string
@@ -67,6 +83,7 @@ homeworks:
6783
- name: While loop task
6884
language: cpp
6985
folder: task_2
86+
compiler_flags: "-std=c++17"
7087
build_type: simple
7188
tests:
7289
- name: Test timeout # Should fail because of the timeout.
@@ -76,6 +93,7 @@ homeworks:
7693
language: cpp
7794
folder: task_3
7895
build_type: simple
96+
compiler_flags: "-std=c++17"
7997
output_type: number
8098
tests:
8199
- name: Test 1
@@ -86,6 +104,7 @@ homeworks:
86104
tasks:
87105
- name: Google Tests
88106
language: cpp
107+
compiler_flags: "-std=c++17"
89108
folder: cpptests
90109
tests:
91110
- name: Just build
@@ -104,14 +123,14 @@ homeworks:
104123
language: bash
105124
folder: bashtests
106125
binary_name: ls_me
107-
pipe_through: "| head -n 2"
108126
tests:
109127
- name: ls
110128
inject_folders: # Just multiple folders.
111129
- source: solutions/fail
112130
destination: fail
113131
- source: solutions/pass
114132
destination: pass
133+
output_pipe_args: "head -n 2"
115134
expected_output: | # This maintains whitespaces.
116135
fail
117136
ls_me.sh
@@ -124,6 +143,7 @@ homeworks:
124143
folder: task_1
125144
build_type: simple
126145
output_type: number
146+
compiler_flags: "-std=c++17"
127147
tests:
128148
- name: Irrelevant
129149
expected_output: 4

homework_checker/core/tests/data/homework/homework_1/task_1/main.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ int main(int argc, char const *argv[]) {
44
if (argc == 1) {
55
fprintf(stdout,
66
"This is a long test output that we expect to be produced by "
7-
"the code. We will compare the ouput to this EXACTLY.\n");
7+
"the code. We will compare the output to this EXACTLY.\n");
88
} else {
99
fprintf(stdout, "%s %s output\n", argv[1], argv[2]);
1010
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
cmake_minimum_required(VERSION 3.0.0)
2+
project(test_cpp_project)
3+
4+
add_executable(main main.cpp)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <iostream>
2+
3+
int main() {
4+
std::string smth, smth_else;
5+
std::cout << "Input string:" << std::endl;
6+
std::cin >> smth;
7+
std::cout << "Input another string:" << std::endl;
8+
std::cin >> smth_else;
9+
std::cout << smth << "_" << smth_else;
10+
}

homework_checker/core/tests/test_task.py

+24
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,30 @@ def test_check_cmake_arithmetics_task(self: "TestTask"):
129129
self.assertTrue(results["Test integer arithmetics"].succeeded())
130130
self.assertFalse(results["Test float arithmetics"].succeeded())
131131

132+
def test_check_input_args_piping(self: "TestTask"):
133+
"""Check that we can pipe arguments into a binary."""
134+
135+
homework_name, task = self.__get_homework_name_and_task(
136+
homework_index=0, task_index=4
137+
)
138+
self.assertEqual(homework_name, "Sample homework")
139+
self.assertEqual(task.name, "Test input piping")
140+
results = task.check()
141+
results = TestTask.__sanitize_results(results)
142+
expected_number_of_build_outputs = 1
143+
expected_number_of_test_outputs = 1
144+
expected_number_of_code_style_outputs = 0
145+
self.assertEqual(
146+
len(results),
147+
expected_number_of_build_outputs
148+
+ expected_number_of_test_outputs
149+
+ expected_number_of_code_style_outputs,
150+
"Wrong results: {}".format(results),
151+
)
152+
self.assertTrue(results[BUILD_SUCCESS_TAG].succeeded())
153+
print(results["Test input piping"].stderr)
154+
self.assertTrue(results["Test input piping"].succeeded())
155+
132156
def test_check_bash_task(self: "TestTask"):
133157
"""Check a simple cmake build on arithmetics example."""
134158

homework_checker/core/tools.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55
from os import environ
66
from subprocess import Popen, TimeoutExpired, CalledProcessError, CompletedProcess
7+
from sys import stdin
78
from typing import Union, List, Optional, Mapping, Any, Tuple
89
from pathlib import Path
910
import tempfile
@@ -219,6 +220,7 @@ def run_command(
219220
command,
220221
stdout=subprocess.PIPE,
221222
stderr=subprocess.PIPE,
223+
stdin=subprocess.PIPE,
222224
shell=shell,
223225
cwd=str(cwd),
224226
env=env,
@@ -248,7 +250,7 @@ def __run_subprocess(
248250
str_input: str = None,
249251
timeout: float = None,
250252
check: bool = False,
251-
**kwargs
253+
**kwargs,
252254
) -> subprocess.CompletedProcess:
253255
"""Run a command as a subprocess.
254256

schema/schema.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@ homeworks:
1010
name: String value
1111
~[optional]~ binary_name: String value
1212
~[optional]~ build_timeout: Float value
13-
~[optional]~ build_type: Any of ['cmake', 'simple']
1413
~[optional]~ compiler_flags: String value
14+
~[optional]~ build_type: Any of ['cmake', 'simple']
1515
~[optional]~ output_type: Any of ['string', 'number']
16-
~[optional]~ pipe_through: String value
1716
~[optional]~ tests:
1817
- name: String value
1918
~[optional]~ expected_output: Any of ['String value', 'Float value', 'Int value']
2019
~[optional]~ inject_folders:
2120
- destination: String value
2221
source: String value
2322
~[optional]~ input_args: String value
23+
~[optional]~ input_pipe_args: String value
24+
~[optional]~ output_pipe_args: String value
2425
~[optional]~ run_google_tests: Boolean value
2526
~[optional]~ timeout: Float value

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from setuptools import find_packages
77
from setuptools.command.install import install
88

9-
VERSION_STRING = "1.0.3"
9+
VERSION_STRING = "1.0.4"
1010

1111
PACKAGE_NAME = "homework_checker"
1212

0 commit comments

Comments
 (0)