Welcome to fact’s documentation!¶
This document presents the fact test framework which allows to test programming exercises written in the programming language C
.
The test types supported by the framework are Compilation, Input-/Output, Code structure, Grey-box and OCLint.
More details about each test type and it’s features are given further below.
The framework was developed for use with the automated programming analysis system Artemis.
Note that the framework is based on the C test template from Artemis.
Test types¶
Compilation tests check if the submitted code compiles successfully with the defined compiler call.
Input-/Output tests check if a program produces the expected output and exit code given a predefined input.
Code structure tests determine if the code meets predefined structural requirements.
Grey-box tests determine if predefined functions return the expected values and have the expected side effects. Grey box tests are written in
python
.OCLint tests determine if the analyzed code adheres to the enabled OCLint rules
Features¶
Modularity: The framework is very modular. Meaning that any test can occur with multiple different configurations in addition to all other test types. (Even though it is recommended that it is always checked that the source file compiles).
Expandability: It is fairly simple to add new test types. A new test just has to inherit from the
AbstractTest
.Simplicity: It was designed to enable easy test definition, configuration, and execution. Tests can be configured via a YAML-file.
GNU-Make: The build process is based on GNU make.
Artemis support: The framework can be used with Artemis.
Test definition file¶
All test cases of the various test types have to be registered in the test definition YAML file. For details on test definition files see Test definition file.
Workflow for Artemis¶
After a new C
programming exercise was created in Artemis. The following steps are required to create the tests.
Write a task description for students
Write a
Makefile
Write a template for the exercise
Write a solution for the exercise
Write/Configure test definitions
Register the test definitions
Create the
Tests.py
file (will be automated in future versions)Optional, but strongly suggested: Check your solution
Document structure¶
Test definition file¶
The test definition file is structured as follows:
version
states the used version of the framework.translation_unit
states the default name of the translation unit that is analyzed if not explicitly stated otherwise in an item oftests
.tests
is a list containing the registration of the various test cases. Each test case supports the following attributes:type
states the type of the test. Currently supported types are compile, io, structural, grey_box and oclint.name
states the name of the test case.requirements
is a list of test cases that need to pass before this test case can be run. Required prerequisites are listed byname
.
Subsequently, an example configuration containing possible attributes for each test type is given. After the example, each parameter contained in tests
is explained in detail.
Note
The following attributes for the type compile
, io
and grey_box
are complete while the types structural
and oclint
have more attributes described in their respective documentation sections.
1version: "0.0"
2translation_unit: main.c
3tests:
4 - type: compile
5 name: Compile
6 makefile_directory: dir # Default: '../assignment/'
7 makefile_name: Makefile # Default: Makefile
8 make_target: main # Default: see description below
9 make_timeout: 2 # Default: 5
10 sourcecode_directory: dir # Default: null
11 - type: structural
12 name: TestCodeStructure
13 translation_unit: main.c # Default: see description below
14 makefile_directory: dir # Default: '../assignment/'
15 exec_timeout: 2 # Default: 5
16 sourcecode_directory: dir # Default: null
17 - type: io
18 name: IO
19 io_test_config: main_io.txt # Default: see description below
20 c_file: main.c # Default: see description below
21 makefile_directory: dir # Default: '../assignment/'
22 makefile_name: Makefile # Default: Makefile
23 make_target: main # Default: see description below
24 make_timeout: 2 # Default: 5
25 exec_timeout: 2 # Default: 5
26 sourcecode_directory: dir # Default: null
27 requirements:
28 - Compile
29 - CodeStructure
30 - type: grey_box
31 name: GreyBox
32 makefile_directory: dir # Default: '../assignment/'
33 makefile_name: Makefile # Default: Makefile
34 make_target: main.so # Default: see description below
35 library_name: main.so # Default: see description below
36 make_timeout: 2 # Default: 5
37 exec_timeout: 2 # Default: 5
38 module_path: grey_box.py # Default: grey_box.py
39 class: GreyBoxTest # Default: GreyBoxTest
40 unit_test: true # Default: true
41 sourcecode_directory: dir # Default: null
42 - type: oclint
43 name: Linter
44 translation_unit: TODO # Default: TODO
45 sourcecode_directory: dir # Default: null
46 exec_timeout: 2 # Default: 5
Note
requirements
, like shown in line 27-29 as an example in the above configuration file, can be added for each test typeexecution of the individual tests is sequential. Keep this in mind when configuring
requirements
.adding new test types/ multiples of the same test type can be done by simply adding another section of the appropriate type.
not all test types have to be present
Test type details¶
type: compile
¶
name
: Required attribute. This parameter represents the compile test case’s name.makefile_directory
: The default value is ‘../assignment/’. This parameter represents the path to the directory that contains the Makefile.makefile_name
: The default value is Makefile. This parameter represents the Makefile’s filename. The file must be located inmakefile_directory
.make_target
: The default value is the file name (without file-extension) given in the top level’stranslation_unit
. This parameter represents the name of the rule withinmakefile_name
that is executed for this test case.make_timeout
: The default value is 5. This parameter represents the maximum number of seconds allotted for the complete execution ofmake_target
.sourcecode_directory
: The default value is null. If null, the source code is supposed to be in the same directory as the Makefile.
type: structural
¶
name
: Required attribute. This parameter represents the structural test case’s name.translation_unit
: The default value is the top level’stranslation_unit
. This parameter represents the translation unit analyzed in this test case.makefile_directory
: The default value is ‘../assignment/’. This parameter is used as fallback ifsourcecode_directory
is not set.exec_timeout
: The default value is 5. This parameter represents the maximum number of seconds allotted for the execution of the structural test.sourcecode_directory
: The default value is null. If null, the source code is supposed to be in the same directory as the Makefile.
Additional parameters that are relevant only for structural tests can be found in the dedicated Code Structure documentation.
type: io
¶
name
: Required attribute. This parameter represents the IO-test case collection’s name.io_test_config
: The default value is the file name (without file-extension) given in the top level’stranslation_unit
with the added suffix _io.txt. This parameter represents the IO-test case collection’s file name. The file contains the definitions of the individual Input-/Output test cases.c_file
: The default value is the top level’stranslation_unit
. This parameter represents the c-file in which the Substitution or the Replacement has to be made. This parameter is only needed if theio_test_config
contains Substitution and/or Replacement tests (two special cases of IO-tests).makefile_directory
: The default value is ‘../assignment/’. This parameter represents the path to the directory that contains the Makefile and the compiled executable.makefile_name
: The default value is Makefile. This parameter represents the Makefile’s filename. The file must be located inmakefile_directory
. This parameter is only needed if theio_test_config
contains Substitution and/or Replacement tests (two special cases of IO-tests).make_target
: The default value is the file name (without file-extension) given in the top level’stranslation_unit
. This parameter represents the name of the rule withinmakefile_name
that is executed for this test case. This parameter is only needed if theio_test_config
contains Substitution and/or Replacement tests (two special cases of IO-tests).make_timeout
: The default value is 5. This parameter represents the maximum number of seconds allotted for the complete execution ofmake_target
. This parameter is only needed if theio_test_config
contains Substitution and/or Replacement tests (two special cases of IO-tests).exec_timeout
: The default value is 5. This parameter represents the maximum number of seconds allotted for the complete execution of each individual test case defined inio_test_config
.sourcecode_directory
: The default value is null. If null, the source code is supposed to be in the same directory as the Makefile.
type: grey_box
¶
name
: Required attribute. This parameter represents the GreyBox-test case collection’s name.makefile_directory
: The default value is ‘../assignment/’. This parameter represents the path to the directory that contains the Makefile.makefile_name
: The default value is Makefile. This parameter represents the Makefile’s filename. The file must be located inmakefile_directory
.make_target
: The default value is the file name (without file-extension) given in the top level’stranslation_unit
with the added suffix .so. This parameter represents the name of the makefile rule used to compile the student’s code as a shared library.library_name
: The default value is the file name (without file-extension) given in the top level’stranslation_unit
with the added suffix .so. This parameter represents the name of the student submission’s code compiled as a shared library.make_timeout
: The default value is 5. This parameter represents the maximum number of seconds allotted for the execution of the GreyBox-test.exec_timeout
: The default value is 5. This parameter represents the maximum number of seconds allotted for the complete execution of all test cases defined in GreyBox-test case collection.module_path
: The default value is grey_box.py. This parameter represents the name of the python file containing the GreyBox-test case collection’s implementation.class
: The default value is GreyBoxTest. This parameter represents the name of the class that implementsGreyBoxTestRunner
.unit_test
: The default value is true. If true, the error message is function-specific. Otherwise, the error messaging is slightly adjusted for a procedural test.sourcecode_directory
: The default value is null. If null, the source code is supposed to be in the same directory as the Makefile.
type: oclint
¶
name
: Required attribute. This parameter represents the oclint-test case’s name.translation_unit
: The default value is the top level’stranslation_unit
. This parameter represents the translation unit analyzed in this test case.sourcecode_directory
: The default value is null. If null, the source code is supposed to be in the same directory as the Makefile.exec_timeout
: The default value is 5. This parameter represents the maximum number of seconds allotted for the complete execution of all test cases defined in GreyBox-test case collection.
Additional parameters that are relevant only for oclint tests can be found in the dedicated OCLint documentation.
Compilation¶
A compilation test tries to execute a GNU-Make rule given in a Makefile. If errors occur during the execution (compilation) of the target, the test is marked as failed, and the error messages generated when executing the makefile rule are returned as feedback for the student. In case no errors occur, the student gets the feedback that the test has been run successfully.
The only limitations for compilation tests are set by what the chosen compiler offers.
When using GCC as the compiler, among others, the following test cases would be possible.
For the examples, it is supposed that a ex.c
-file is the only necessary C-file.
The snippets are to be interpreted as GNU Makefile with implicit rules.
Compilation¶
A simple check if the submitted code compiles at all:
CC=gcc
CFLAGS=-std=c11
ex: ex.c
Warnings¶
Check if the submitted code compiles without warnings:
CC=gcc
CFLAGS=-Wall -Werror -Wextra -Wpedantic -std=c11
ex: ex.c
Input-/Output¶
An Input-/Output test (IO-test) may consist of multiple test cases.
For each test case, the framework runs the executable and provides it with the command-line arguments and stdin
-input and checks if the obtained exit code, stdout
-output and stderr
-output match the expected values.
If the output and error code does not match the expected values, the test is marked as failed, and the error messages generated by the framework are collected.
Once all test cases have been run, the errors are returned as feedback for the student.
In case no errors occur, the student gets the feedback that the IO-test has been run successfully.
An IO-test is defined in a domain-specific language (DSL). The DSL has been designed to write test cases concisely. Details on the DSL and example IO-test definitions are given further below. Additionally, for future versions of the framework, we plan to add the possibility to define IO-tests directly in the main test definition.
A particular case of IO-tests are replacement/substitution tests. For these kinds of tests, the framework performs a substitution of parts of the student’s code before recompiling and running an “ordinary” IO-test. These kinds of test cases can be useful to test code before input via command line parameters, or stdin processing are known to the students.
The error message¶
If an IO-test fails, the error message is divided into two sections: general overview and test case details. The details for each failed test case - depending on the adopted matching strategy (c.f. start> for details on matching strategies) - are:
Status: A summary of what was OK and what was not.
Commandline parameters: The command line parameters passed to the tested executable.
Input: The input piped to the tested executables
stdin
.Obtained output on stdout: The obtained
stdout
-output produced by the analyzed executable if it was not as expected.Expected output on stdout: The expected
stdout
-output.Hint stdout: The hint indicating the necessary changes for the
stdout
-output (shown only if the analyzedstdout
-output was not OK).Obtained output on stderr: The obtained
stderr
-output produced by the analyzed executable if it was not as expected.Expected output on stderr: The expected
stderr
-output.Hint stderr: The hint indicating the necessary changes for the
stderr
-output (shown only if the analyzedstderr
-output was not OK).Tested code: Shows the code that is compiled and then tested after a Replacement and/or Substitution was performed.
Hint: The hint to clarify what could have gone wrong, in case one was set in the IO-test case definition.
Note
If the output produced by the tested code contains characters outside of the ASCII range, the following error message is shown as part of the standard error messages Status section:
Your program generates output containing invalid characters that are outside of the ASCII
range (below 0 or above 127)!
Subsequently, two example error messages are given:
An example error message for a program that should determine if the value assigned to the int-variable
n
is divisible by seven or not and printThe number <n> is divisible by seven!
orThe number <n> is not divisible by seven!
respectively.<n>
should be replaced with the value of the variablen
. The full example, including test definition and accepted solution, can be found here (Example 2)An example where a timeout error occurred.
More details about the error messages sections are given right after the examples.
Example 1:
Error: The output of your program does not match the expected output. In total 1 test case failed:
Test case
--------------------------------------------------------------------------------------
Status
======
Correct return code! Expected: '0' Obtained: '0'
Wrong output on stdout!
Wrong output on stderr!
Commandline parameters (none)
=============================
Input (none)
============
Substitutions
=============
- In line 4 of the tested code the value 11 assigned to n has been substituted with 5.
Obtained output on stdout
=========================
the number 11 is NOT divissible by sven
Expected output on stdout
=========================
The number 5 is not divisible by seven!
Hint stdout
===========
[-t][+T]he number [11=>should change automatically if n changes!] is [-NOT][+not] divi[-s]sible by s[+e]ven[+!]
Obtained output on stderr
=========================
Error message to make the stderr paragraphs appear!
Expected output on stderr (none)
================================
Hint stderr
===========
[-Error message to make the stderr paragraphs appear!]
Tested code
===========
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int n = 5;
if (n % 7) {
printf("the number 11 is NOT divissible by sven\n");
} else {
printf("the number %d is divissible by sven\n", n);
}
fprintf(stderr, "Error message to make the stderr paragraphs appear!\n");
return EXIT_SUCCESS;
}
Hint (none)
===========
No hint available! Please, read the exercise description very carefully!
Example 2:
Error: The output of your program does not match the expected output. In total 1 test case failed:
Test case
--------------------------------------------------------------------------------------
Status
======
Timeout: The execution of your program was canceled since it did not finish after 1 seconds!
This might indicate that there is some unexpected behavior (e.g., an endless loop) or that
your program is very slow!
Commandline parameters (none)
=============================
Input (none)
============
Hint (none)
===========
No hint available! Please, read the exercise description very carefully!
Status¶
The Status-section of the test case details summarizes what went wrong and what was OK for the given test case. It is always comprised of three lines except if a timeout occurred and the test was aborted:
The first line states whether the return code generated by the analyzed program was correct or not. It also displays the expected and obtained return codes.
The second line states whether the output obtained on
stderr
was correct or not.The third line states whether the output obtained on
stdout
was correct or not.
At least one of the three lines must be a negative message. Alternatively - in case of a timeout - this section contains the following message:
Timeout: The execution of your program was canceled since it did not finish after 5 seconds!
This might indicate that there is some unexpected behavior (e.g., an endless loop) or that
your program is very slow!
One of the two aforementioned cases is always true. Otherwise, the test case would not have failed.
Commandline parameters¶
The Commandline parameters-section of the test case details lists the command line arguments passed to the given test case’s executable.
In case no command line parameters were passed to the executable (none)
is added to the section title for clarification.
Each passed command line parameter is wrapped by double quotes ("
) multiple command line parameters are divided by a whitespace character.
If, for example, a program was given the two parameters "p1"
and "p2"
and the exit code, stdout
-output or stderr
-output was not as expected, the Commandline parameters
section would contain "p1" "p2"
Note
This section is only shown if show_input
is true. For more details about show_input
and how it is used, see section Start.
Input¶
The Input-section of the test case details lists the values passed to the executables stdin
stream.
In case nothing is passed to the stdin
stream (none)
is added to the section title for clarification.
Each input value is provided in a separate line. Strings are wrapped in double quotes and can span over more than one line while still counting as one input value.
Note
This section is only shown if show_input
is true. For more details about show_input
and how it is used, see section Start.
Obtained output on stdout¶
The Obtained output on stdout-section of the test case details lists the output the executable produced on the stdout
stream if it differs from the expected output.
In case nothing was produced on the stdout
stream and some kind of output was expected (none)
is added to the section title for clarification.
Note
This section is shown by default if the produced stdout
-output is not as expected.
It can be turned off by setting the show_output
argument.
For more details about show_output
and how it is used, see section Start.
Please note that by setting show_output=false
for exact matching Obtained output on stderr is also turned off.
Expected output on stdout¶
The Expected output on stdout section of the test case details lists the output the analyzed executable is expected to produce on the stdout
stream.
In case nothing is expected on the stdout
stream (none)
is added to the section title for clarification.
This section is only available for exact matching.
Note
This section is not shown by default, but it can be turned on by setting the show_expected
argument. For more details about show_expected
and how it is used, see section start> matching="exact".
Hint stdout¶
The Hint stdout section of the test case details shows the necessary changes for the stdout
-output (shown only if the analyzed output was not OK).
This section is only available for exact matching.
Note
This section is only shown if the analyzed output was not OK. Hints for stdout
and stderr
can be disabled, setting the show_diff
argument. For more details about show_diff
and how it is used, see section start> matching="exact".
Obtained output on stderr¶
The Obtained output on stderr-section of the test case details lists the output the executable produced on the stderr
stream if it differs from the expected output.
In case nothing was produced on the stderr
stream and some kind of output was expected (none)
is added to the section title for clarification.
Note
This section is shown by default if the produced stderr
-output is not as expected.
It can be turned off by setting the show_error
argument for regex and the show_output
argument for exact matching.
For more details about show_error
and how it is used, see section start> matching="regex".
Please note that by setting show_output=false
for exact matching Obtained output on stdout is also turned off.
Expected output on stderr¶
The Expected output on stderr section of the test case details lists the output the analyzed executable is expected to produce on the stderr
stream.
In case nothing is expected on the stderr
stream (none)
is added to the section title for clarification.
This section is only available for exact matching.
Note
This section is not shown by default, but it can be turned on by setting the show_expected
argument. For more details about show_expected
and how it is used, see section start> matching="exact".
Hint stderr¶
The Hint stderr section of the test case details shows the necessary changes for the stderr
-output (shown only if the analyzed output was not OK).
This section is only available for exact matching.
Note
This section is only shown if the analyzed output was not OK. Hints for stdout
and stderr
can be disabled, setting the show_diff
argument. For more details about show_diff
and how it is used, see section start> matching="exact".
Tested code¶
The Tested Code section shows the code that is compiled and then tested after a Replacement and/or Substitution was performed.
Note
This section is only shown if a a Replacement and/or Substitution was performed.
It can be disabled by setting the show_substitution
argument. For more details about show_substitution
and how it is used, see section start> matching="exact" and start> matching="regex" .
Hint¶
The Hint section of the test case details displays a hint defined for the given test case.
If no hint was defined, (none)
is added to the section title for clarification.
Additionally, the message No hint available! Please, read the exercise description very carefully!
is added to the hint section.
Domain-Specific Language (DSL)¶
This DSL is used to define IO-test cases for this framework. The supported commands are (in order of possible occurrence):
start> (required) marks the start of a test case
r> (optional) defines a replacement (replacement of a predefined character sequence with a character sequence defined in the test definition)
s> (optional) defines a substitution (substitution of the initialization of a predefined variable with another value)
p> (optional) command line parameters. Each parameter as a string.
Statement (can be used multiple times and in arbitrary order):
i> (optional) input: value that will be piped to the executables
stdin
. All defined input values are concatenated. A\n
is added after each value.o> (optional) output: confront the details for
matching="regex"
andmatching="exact"
in their respective sections.e> (optional) error: confront the details for
matching="regex"
andmatching="exact"
in their respective sections.v> (optional) variable: defines a variable that is valid for the rest of the test case definition.
end> (required) marks the end of the test case.
More details on each instruction and its required parameters are given below.
Start start>
¶
The keyword start>
marks the beginning of an IO-test case. It supports a series of optional arguments depending on the
chosen matching strategy. The matching strategy determines how output comparison works and how the feedback is generated.
Possible values for matching
are "exact"
and "regex"
.
start> matching="exact"
¶
In case of matching="exact"
for o> and e> elements exact matching is performed.
In this case, a diff between the expected and obtained output can be shown.
Example diff output:
obtained output =
the number 11 is NOT divissible by sven
expected output =
The number 5 is not divisible by seven!
diff output =
[-t][+T]he number [11=>should change automatically if n changes!] is [-NOT][+not] divi[-s]sible by s[+e]ven[+!]
The values in []
show the necessary changes to get to the expected output. Possible operators are:
+
add characters-
remove characters->
replace characters=>
dedicated error hint/message
Additionally the following optional arguments are supported:
show_input
(default= false): If true, feedback for the students includes the command line argument(s) and the stdin-input provided to the executable. By default, command-line arguments and stdin-input are not shown in case of errors occur.show_output
(default= true): If false, feedback for the students does not include the output their program provided on stdout and stderr. By default, if the stdout-output differs from the expected stdout-output, it is shown in the feedback. The same holds for stderr-output.show_expected
(default= false): If true, feedback for the students contains the expected output as defined in the test definition. By default, the expected output on stdout and stderr is not shown in case errors occur.show_diff
(default= true): If false, feedback for the students does not contain the diff output like it is shown in the third line of the example above. By default the diff to the expected output on stdout and/or stderr is shown in case an error occurs.hint
(default= No hint available! Please, read the exercise description very carefully!): The string assigned to this argument is added to the error message provided to the student in the Hint section of the testfeedback. If no hint is defined the default message is printed.ignore_cases
(default= false): If true the comparison of expected and obtained output for stdout and stderr is done in a case insensitive manner. By default the comparison is case sensitive.rstrip
(default= false): If true trailing whitespace characters at the end of the concatenated output string are removed before comparison. By default the output string is not modified before comparison.line_rstrip
(default= false): If true trailing blanks and tabs at the end of each line are ignored.escape
(no default): The string assigned to this argument defines the character escape sequence that starts and ends code injection. Code can be injected in i>, o>, e> and p> statementsshow_substitution
(default= true): If false the feedback does not contain the code after Replacement and/or Substitution. By default this section is only present if at least one Replacement or Substitution was performed.printable_ascii
(default= false): If true the output can contain only printable ascii characters.
Examples
Minimal start>
for exact:
start> matching="exact"
Maximal start>
for exact with default values:
start> matching="exact" show_input=False show_output=True show_expected=False show_diff=True
ignore_cases=False rstrip=False line_rstrip=False show_substitution=True
printable_ascii=False
hint="No hint available! Please, read the exercise description very carefully!"
Note
escape
has no default value and is thus not mentioned in the maximal example with default values (confront Example 1 (escape) for an example on how to useescape
)This produces the same error message the minimal example produces.
start> matching="regex"
¶
In case of matching="regex"
for o> and e> elements regex matching is performed.
Additionally the following optional arguments are supported:
show_input
(default= false): If true, feedback for the students includes the command line argument(s) and the stdin-input provided to the executable. By default, command-line arguments and stdin-input are not shown in case errors occur.show_output
(default= true): If false, feedback for the students does not include the output their program provided on stdout. By default, if the stdout-output differs from the expected stdout-output, it is shown in the feedback.show_error
(default= true): If false, feedback for the students does not include the output their program provided on stderr. By default, if the stderr-output differs from the expected stderr-output it is shown in the feedback.hint
(default= No hint available! Please, read the exercise description very carefully!): The string assigned to this argument is added to the error message provided to the student in the Hint section of the testfeedback. If no hint is defined the default message is printed.escape
(no default): The string assigned to this argument defines the character escape sequence that starts and ends code injection. Code can be injected in i>, o>, e> and p> statementsshow_substitution
(default= true): If false the feedback does not contain the code after Replacement and/or Substitution. By defaultthis section is only present if at least one Replacement or Substitution was performed.printable_ascii
(default= false): If true the output can contain only printable ascii characters.
Examples
Minimal start>
for regex:
start> matching="regex"
Maximal start>
for regex with default values:
start> matching="regex" show_input=False show_output=True show_error=True show_substitution=True
printable_ascii=False
hint="No hint available! Please, read the exercise description very carefully!"
Note
escape
has no default value and is thus not mentioned in the maximal example with default values (confront Example 1 (escape) for an example on how to useescape
)This produces the same error message the minimal example produces.
Replacement r>
¶
The keyword r>
defines a replacement, which is the replacement of substrings matching a predefined regular expression with a predefined string in the submitted code file.
It is possible to define 0 or more replacements per test case. Each replacement has the following required parameters:
pattern
: all parts of the code file matching this python regex pattern are replaced with the second argument. Type: STRINGreplacement
: all character sequences matching the first argument are replaced by this character sequence. Type: STRINGhint
: character sequence that is shown in the error message should replacement fail. Type: STRINGnum_matches
: number of expected matches in the code. If thepattern
does not match the given number of times, the substitution is considered a failure, and an error message containing the hint is shown. Type: INT
Note
Before performing the replacement, comments in the code are removed.
Example
r> "\"Hello World\\n\"" "\"Hello People!\\n\"" "Please, read the exercise description very carefully! Your code should contain the character sequence '\"Hello World\n\"' exactly once!" 1
Note
The character "
has to be escaped inside a string. The same goes for the character \
Substitution s>
¶
The keyword s>
defines a substitution, which is the substitution of a variable’s initialization with a predefined value.
It is possible to define 0 or more substitutions per test case. Each substitution has the following required parameters:
variable
: all initializations of the variable matching this regex pattern are replaced with the second argument. Type: STRINGvalue
: this character sequence replaces the value the variable was initialized with. Type: STRINGhint
: character sequence that is shown in the error message should substitution fail. Type: STRINGnum_matches
: number of expected matches in the code. If the number if initializations of the givenvariable
does not match the given number of times, the substitution is considered a failure, and an error message containing the hint is shown. Type: INT
Note
Before performing the substitution, comments in the code are removed.
Example
Task: Define a variable named n
of type int
. Initialize n
with an integer value of your choice between 1 and 2000 (limits included) and print the following text where <n>
should be the value you initialized n
with.
The value of n is <n>!
Full IO-test case definition:
start> matching="exact" rstrip=True
s> "n" "100" "Please, read the exercise description very carefully! Your printf statement should not be hard coded!" 1
o> "The value of n is 100!"
end> 0
Solution 1 (accepted):
#include <stdio.h> #include <stdlib.h> int main() { int n = 12; printf("The value of n is %d!\n", n); return EXIT_SUCCESS; }
Solution 2 (not accepted):
#include <stdio.h> #include <stdlib.h> int main() { printf("The value of n is 100!\n"); return EXIT_SUCCESS; }
Resulting error message:
Error: The output of your program does not match the expected output. In total 1 test case failed: Please, read the exercise description very carefully! Your printf statement should not be hardcoded!
Solution 3 (possibly accepted):
#include <stdio.h> #include <stdlib.h> int main() { int n = 1; printf("The value of n is 100!\n"); return EXIT_SUCCESS; }
Note
Depending on the flags used for compilation, this solution may be accepted even though the variable n was never used. By adding a second test case and changing the substitution value hardcoded outputs can always be detected.
Improved testcase definition:
start> matching="exact" rstrip=True s> "n" "100" "Please, read the exercise description very carefully! Your printf statement should not be hard coded!" 1 o> "The value of n is 100!" end> 0 start> matching="exact" rstrip=True s> "n" "110" "Please, read the exercise description very carefully! Your printf statement should not be hardcoded!" 1 o> "The value of n is 110!" end> 0
Resulting error message:
Error: The output of your program does not match the expected output. In total 1 test case failed: Test case -------------------------------------------------------------------------------------- Status ====== Correct return code! Expected: '0' Obtained: '0' Wrong output on stdout! Correct output on stderr! Substitutions ============= - In line 4 of the tested code the value 1 assigned to n has been substituted with 110. Obtained output on stdout ========================= The value of n is 100! Hint stdout =========== The value of n is 1[0->1]0! Tested code =========== #include <stdio.h> #include <stdlib.h> int main() { int n = 110; printf("The value of n is 100!\n"); return EXIT_SUCCESS; } Hint (none) =========== No hint available! Please, read the exercise description very carefully!
Command-line parameters p>
¶
The keyword p>
defines the command line parameters, that is the command-line arguments given to the executable.
The parameters are a list of double-quoted strings separated by a whitespace character.
Each string corresponds to a separate parameter. The absence of p>
in an IO-test case definition means no command line parameters.
Example:
p> "content of argv[1]" "content of argv[2]"
Statement¶
Statements are the main elements of an IO-test case.
The different kinds of statements can occur multiple times in the same test case definition. The order they can appear in is arbitrary.
Please note that o> and e> statements differ slightly for matching="regex"
and matching="exact"
. More details are given in their in their respective sections.
Input i>
¶
The keyword i>
defines input that is piped to stdin
.
It expects an argument of type STRING.
All i>
strings of one test case are joined with newline characters and piped to the stdin
stream of the tested executable.
Additionally, python code can be injected in the input statement if an escape sequence has been defined beforehand as an argument of start> (escape
is available for both regex
, and exact
matching).
Example 1
The segment
i> "1"
i> "1"
i> "1"
i> "1"
would result in "1\n1\n1\n1\n"
being piped to the tested executable stdin
stream.
The same can be achieved by simply writing
i> "1\n1\n1\n1"
directly.
Example 2: The following test definition generates the current date in the format YYYY-mm-dd
as input for the program.
start> matching="exact" show_input=True escape="$$"
i> "$$import time; print(time.strftime('%Y-%m-%d'))$$"
end> 0
Output o>
¶
The keyword o>
defines output that is expected on stdout
. The o>
sections slightly differ between matching="regex"
and matching="exact"
more details on the differences in the respective sections below.
o>
for matching="regex"
¶Within the matching="regex"
context o>
expects one argument of type STRING.
This string is interpreted as a python regex.
All o>
strings of one test case are joined, the stdout
output of the tested executable is matched against the resulting regex.
Examples
The segment
o> "Hello World!?\n?"
would accept all executables that produce the string Hello World
followed by an optional exclamation point character and an optional newline character on the stdout
stream.
The same can be achieved by writing
o> "Hello "
o> "World"
o> "!?\n?"
o>
for matching="exact"
¶Within the matching="exact"
context o>
expects one argument of type STRING. Optionally, a second STRING can be provided.
The first argument is the exact character sequence that is expected as the output on stdout
.
The second - optional - argument defines a dedicated error hint or message that is shown if the first argument does not match the output exactly.
If the second argument is missing, the hint is automatically generated to showcase the necessary changes in the output.
Example
The segment
o> "The resulting number is: "
o> "5" "wrong computation!"
o> "!"
would only accept the character sequence The resulting number is: 5!
.
Let’s suppose the output produced by the analyzed program was the resullting numbr is: 11!!
then the diff feedback
would be [-t][+T]he resu[-l]lting numb[+e]r is: [11=>wrong computation!]![-!]
.
Let’s split the feedback up into three parts (the three parts of the output declaration):
[-t][+T]he resu[-l]lting numb[+e]r is:
for the first part all necessary changes are shown individually.[11=>wrong computation!]
for the second part, only the error message and no diff is shown because the additional argument was given in the second line of theo>
defined above.![-!]
for the third part, all necessary changes are shown individually.
Possible operators inside of []
are:
+
add characters-
remove characters->
replace characters=>
dedicated error hint/message
Error e>
¶
The keyword e>
defines output that is expected on stderr
. The e>
sections slightly differ between matching="regex"
and matching="exact"
.
The e>
keyword works basically the same as the o> keyword with stderr
instead of stdout
and e>
instead of o>
.
Variable v>
¶
The keyword v>
defines a variable definition for statements, i.e., from the definition point onwards, each character sequence defined in the first argument appears inside an argument of non-variable definition statements is replaced by the character sequence defined in the second argument.
It expects the following arguments:
var_name
: the character sequence serving as a variable, i.e., the character sequence that is to be replaced. Type: STRINGvalue
: each occurrence of the first argument inside an argument of a non-variable definition statement is replaced by this character sequence. Type: STRING
Example
In the following example, |
stands for zero to five blank or tab characters.
v> "|" "[ \t]{0,5}"
o> "|a: |5\n"
o> "|b: |2\n"
o> "|c: |3\n"
o> "|other: |10[\n]*"
the same statements without variable substitution would be
o> "[ \t]{0,5}a: [ \t]{0,5}5\n"
o> "[ \t]{0,5}b: [ \t]{0,5}2\n"
o> "[ \t]{0,5}c: [ \t]{0,5}3\n"
o> "[ \t]{0,5}other: [ \t]{0,5}10[\n]*"
Note
Variables in statements have been introduced to make statements more comfortable to read and less cluttered with regular expressions. The variable names have to be chosen carefully because every occurrence of the var_name
character sequence is replaced by value
!
Delimiter end>
¶
The keyword end>
marks the end of an IO-test case. Optionally, a list of one or more INT-values separated by ,
can be given.
These arguments represent the accepted exit-codes for the program.
If no INT-values are given all exit-codes are accepted.
Examples
end>
end> 0
end> 1
end> 1,2,3
Examples¶
Example 1 escape
:
IO-Test definition:
start> matching="exact" escape="$$"
o> "Date: $$import time; print(time.strftime('%Y-%m-%d'), end='')$$"
end> 0
Solution 1 (accepted):
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main(void) {
time_t t = time(NULL);
struct tm* tm;
char date[11];
tm = localtime(&t);
strftime(date, sizeof(date), "%Y-%m-%d", tm);
printf("Date: %s", date);
return EXIT_SUCCESS;
}
Solution 2 (not accepted):
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main(void) {
time_t t = time(NULL);
struct tm* tm;
char date[11];
tm = localtime(&t);
strftime(date, sizeof(date), "%Y-%m-%d", tm);
printf("%s\n", date);
return EXIT_SUCCESS;
}
Error feedback for solution 2:
Error: The output of your program does not match the expected output. In total 1 test case failed:
Test case
-----------------------------------------------------------------------------------------------
Status
======
Correct return code! Expected: '0' Obtained: '0'
Wrong output on stdout!
Correct output on stderr!
Obtained output on stdout
=========================
2021-11-12
Hint stdout
===========
[+Date: ]2021-11-12[-
]
Hint (none)
===========
No hint available! Please, read the exercise description very carefully!
Example 2:
IO-Test definition:
start> matching="exact" show_input=true show_expected=true rstrip=true
s> "n" "5" "Please, read the exercise description very carefully! Your printf statement should not be hard coded!" 1
o> "The number "
o> "5" "should change automatically if n changes!"
o> " is not divisible by seven!"
end> 0
Solution 1 (accepted):
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int n = 11;
if (n % 7) {
printf("The number %d is not divisible by seven!\n", n);
} else {
printf("The number %d is divisible by seven!\n", n);
}
return EXIT_SUCCESS;
}
Solution 2 (not accepted):
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int n = 11;
if (n % 7) {
printf("the number 11 is NOT divissible by sven\n");
} else {
printf("the number %d is divissible by sven\n", n);
}
fprintf(stderr, "Error message to make the stderr paragraphs appear!\n");
return EXIT_SUCCESS;
}
Error feedback for solution 2:
Error: The output of your program does not match the expected output. In total 1 test case failed:
Test case
--------------------------------------------------------------------------------------
Status
======
Correct return code! Expected: '0' Obtained: '0'
Wrong output on stdout!
Wrong output on stderr!
Commandline parameters (none)
=============================
Input (none)
============
Substitutions
=============
- In line 4 of the tested code the value 11 assigned to n has been substituted with 5.
Obtained output on stdout
=========================
the number 11 is NOT divissible by sven
Expected output on stdout
=========================
The number 5 is not divisible by seven!
Hint stdout
===========
[-t][+T]he number [11=>should change automatically if n changes!] is [-NOT][+not] divi[-s]sible by s[+e]ven[+!]
Obtained output on stderr
=========================
Error message to make the stderr paragraphs appear!
Expected output on stderr (none)
================================
Hint stderr
===========
[-Error message to make the stderr paragraphs appear!]
Tested code
===========
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int n = 5;
if (n % 7) {
printf("the number 11 is NOT divissible by sven\n");
} else {
printf("the number %d is divissible by sven\n", n);
}
fprintf(stderr, "Error message to make the stderr paragraphs appear!\n");
return EXIT_SUCCESS;
}
Hint (none)
===========
No hint available! Please, read the exercise description very carefully!
Code Structure¶
A code structure test analyzes the structure of a translation unit’s source code and enforces the predefined rules. Each translation unit requires its own set of rules. If the analyzed code violates at least one rule, the test is failed, and feedback on what rules have been violated is given. Otherwise, feedback that the code structure test has been passed successfully is given.
There are three different types of rules:
General rules: These rules can be defined on every level.
Global rules: These rules can only be defined at a global level.
Function rules: These rules can only be defined at the function level.
Note
If a rule is defined globally, each function is expected to abide by it, but rules on the function level supersede global definitions within their bodies.
In the following sections, the rules, their meaning, and default values are explained in detail. The remainder of this section lists the different rules for each level.
General rules:
input: allow/disallow/expect
stdin
input.output: allow/disallow/expect
stdout
output.insecure: allow/disallow/expect
stderr
output.recursion: allow/disallow/expect recursion.
disallowed_keywords: List of disallowed keywords.
expected_keywords: List of expected keywords.
allowed_function_calls: List of allowed function calls.
disallowed_function_calls: List of disallowed function calls.
expected_function_calls: List of expected function calls.
expected_variable_declarations: List of expected variable definitions.
Rules exclusive to the global definition:
required_functions: List of function prototypes defining required functions.
disallowed_includes: List of C-library names that are not allowed.
global_variables: allow/disallow global variable declarations.
compile_args: compiler arguments for clang AST-generation
functions: Key that indicates the start of the list function rule segments.
Additional rules for function level are:
function: function name
A key mechanic of code structure tests is that all keyword and function usages and whether recursive functions are used are propagated to the calling function.
General Rules¶
These rules can be used on both the global and function level. If a rule is defined on the global level, functions must abide by it as well, but functions can supersede rules defined on the global level either partially or fully. The following sections describe all general rules.
input
¶
This rule indicates whether input from stdin
is allowed, disallowed or expected.
This is achieved by allowing/disallowing or expecting the usage of at least one of the following functions:
Input Functions (click to expand)
fgetc
fgets
fgetwc
fread
fscanf
fscanf_s
fwscanf
fwscanf_s
getc
getc_unlocked
getchar
getchar_unlocked
getdelim
getline
gets
gets_s
getw
getwc
getwchar
getwline
pread
read
readv
scanf
scanf_s
sscanf
sscanf_s
swscanf
swscanf_s
vfscanf
vfscanf_s
vfwscanf
vfwscanf_s
vscanf
vscanf_s
vsscanf
vsscanf_s
vswscanf
vswscanf_s
vwscanf
vwscanf_s
wscanf
wscanf_s
If defined globally, this rule can be superseded on function level either partially, by defining allowed_function_calls, or expected_function_calls, or as a whole by redefining input
.
Note
Please note that expected is not yet implemented! Currently, expected behaves just like allowed.
Possible values:
true
: usage of at least one of the functions is expected (this is not implemented yet! currentlytrue
behaves just likenull
)null
: usage of the functions is allowed but not mandatory.false
: usage of the functions is disallowed
Example:
input: false
Default value:
If it is not present on the function level, the global settings remain unchanged.
If the keyword input
is omitted on the global level, input is allowed.
output
¶
This rule indicates whether output on stdout
is allowed, disallowed or expected.
This is achieved by allowing/disallowing or expecting the usage of at least one of the following functions:
Output Functions (click to expand)
ferror
fprintf
fprintf_s
fpurge
fputc
fputs
fputwc
fputws
fputws_l
fread
freopen
fropen
fwprintf
fwrite
perror
printf
printf_s
putc
putchar
puts
putw
putwc
putwchar
pwrite
vasprintf
vfprintf
vfprintf_s
vfwprintf
vfwprintf_s
vprintf
vprintf_s
vwprintf
vwprintf_s
wprintf
write
writev
If defined globally, this rule can be superseded on function level either partially, by defining allowed_function_calls or expected_function_calls, or as a whole by redefining output
.
Note
Please note that expected is not yet implemented! Currently, expected behaves just like allowed.
Possible values:
true
: usage of at least one of the functions is expected (this is not implemented yet! currentlytrue
behaves just likenull
)null
: usage of the functions is allowed but not mandatory.false
: usage of the functions is disallowed
Example:
output: null
Default value:
If it is not present on the function level, the global settings remain unchanged.
If the keyword output
is omitted on the global level output is allowed.
insecure
¶
This rule indicates whether the usage of insecure functions is allowed, disallowed or expected.
Insecure functions are functions that start or kill processes or threads as well as the exec
-function family.
This is achieved by allowing/disallowing or expecting the usage of at least one of the following functions:
Insecure Functions (click to expand)
fork
system
kill
killpg
execl
execle
execlp
execv
execvp
execvP
execve
execvpe
fexecve
vfork
clone
tkill
tgkill
execveat
pthread_create
thrd_create
If defined globally, this rule can be superseded on function level either partially, by defining allowed_function_calls or expected_function_calls, or as a whole by redefining insecure
.
Note
Please note that expected is not yet implemented! Currently, expected behaves just like allowed.
Possible values:
true
: usage of at least one of the functions is expected (this is not implemented yet! currentlytrue
behaves just likenull
)null
: usage of the functions is allowed but not mandatory.false
: usage of the functions is disallowed
Example:
insecure: false
Default value:
If it is not present on the function level, the global settings remain unchanged.
If the keyword insecure
is omitted on the global level, insecure functions are is allowed.
recursion
¶
This rule indicates whether recursion is allowed, disallowed, or expected.
If defined globally, this rule can be superseded on function level by redefining recursion
.
Possible values:
true
: recursion is expected. If used globally, this means that every function has to be recursive. If used on the function level, the current function has to be recursive.null
: recursion is is allowed but not mandatory.false
: recursion is disallowed.
Example:
recursion: true
Default value:
If it is not present on the function level, the global settings remain unchanged.
If the keyword recursion
is omitted on the global level, recursion is allowed.
disallowed_keywords
¶
The usage of all keywords listed here is disallowed. If defined globally, this rule can be superseded on the function level by adding globally disallowed keywords to expected_keywords.
Possible values:
A list of c-keywords that is not allowed
Example:
disallowed_keywords:
- for
- while
- do
Default value:
If it is not present on the function level, the global settings remain unchanged.
If disallowed_keywords
is omitted on the global level, all keywords are allowed.
expected_keywords
¶
The usage of all keywords listed here is expected. If defined globally, this rule can be superseded on the function level by adding globally expected keywords to disallowed_keywords.
Possible values:
A list of c-keywords that is expected
Example:
expected_keywords:
- if
- unsigned
Default value:
If it is not present on the function level, the global settings remain unchanged.
If expected_keywords
is omitted on the global level, all keywords are allowed.
allowed_function_calls
¶
The usage of all function calls of external functions listed here is allowed. If defined globally, this rule can be superseded on the function level by adding globally allowed function calls to disallowed_function_calls or expected_function_calls.
Possible values:
A list of c-library function calls that is allowed.
Example:
allowed_function_calls:
- printf
- fprintf
Default value:
If it is not present on the function level, the global settings remain unchanged.
If allowed_function_calls
is omitted on the global level, all function calls are allowed.
disallowed_function_calls
¶
The usage of all function calls of external functions listed here is disallowed. If defined globally, this rule can be superseded on the function level by adding globally disallowed function calls to allowed_function_calls or expected_function_calls.
Possible values:
A list of C-library function calls that are not allowed.
disallowed_function_calls:
- pow
Default value:
If it is not present on the function level, the global settings remain unchanged.
If disallowed_function_calls
is omitted on the global level, all function calls are allowed.
expected_function_calls
¶
The usage of all function calls of external functions listed here is expected. If defined globally, this rule can be superseded on the function level by adding globally expected function calls to allowed_function_calls or disallowed_function_calls.
Possible values:
A list of C-library function calls that are expected.
expected_function_calls:
- qsort
Default value:
If it is not present on the function level, the global settings remain unchanged.
If expected_function_calls
is omitted on the global level, all function calls are allowed.
expected_variable_declarations
¶
The variables defined in this list are expected to be defined with the same type in the analyzed code. If defined globally, this means that each function body must contain this variable definition. If defined on function level, only the function body in question must contain the variable definitions. This rule can not be superseded on the function level.
Note
We plan on adding function parameters to the analyzed code for variable declarations. This is not yet implemented.
Possible values:
The list of variable declarations that are expected.
Example:
expected_variable_declarations:
- 'long int n;'
- 'long long value;'
Default value:
If expected_variable_declarations
is omitted there are no expected variable declarations.
Global Rules¶
These rules can only be used on the global level, and it is not possible to influence them on the function level. The following sections describe all global rules.
required_functions
¶
The function prototypes defined in this list must be present in the analyzed code.
Possible values:
The list of c function prototypes that must be present
Example 1:
required_functions:
- "void foo(int);"
- "int bar(void);"
Example 2:
required_functions:
- "#include \"types.h\" \n void print_person(person_t *person);"
- "#include <stdlib.h> \nvoid my_sort(char array[], size_t length);"
Note
As shown in Example 2, non standard types and types defined with typedef can be loaded via includes. Attention: The types.h
file must reside in the same location as the test configuration fie and the Tests.py
file.
Alternatively the same required functions can be written as:
required_functions:
- |
#include "types.h"
void print_person(person_t *person);
- |
#include <stdlib.h>
void my_sort(char array[], size_t length);"
Default value:
If required_functions
is omitted, there are no functions whose implementation is required.
disallowed_includes
¶
The usage of C-library includes listed here is not allowed in the analyzed code.
Possible values:
The list of c-library includes that are disallowed
Example:
disallowed_includes:
- 'stdio.h'
Default value:
If disallowed_includes
is omitted, all c-library includes are allowed.
global_variables
¶
Indicates whether global variables can be used in the analyzed translation unit or not.
If global_variables
is true, the usage of global variables is allowed.
If global_variables
is false, the usage of global variables is not allowed.
Example:
global_variables: false
Default value:
If global_variables
is omitted, the usage of global variables is not allowed.
compile_args
¶
This keyword can be used to define an array containing additional command line arguments that should be passed to clang when it is parsing the syntax tree. For example they can be used to specify include paths or warnings.
Example:
compile_args:
- "-I/path/to/include"
- "-Wall"
functions
¶
This key indicates the start of the function rules list. It is required if rules for at least one function are defined. Otherwise, it is disallowed.
Function Rules¶
These rules can only be used on function level. The following sections describe all function rules.
Note
If function f1
calls function f2
then function f2
must also abide to all rules for function f1
because all checks are propagated backward along the call graph.
function
¶
This key indicates the name of the function to which the following rules are applied. This key can only be used in the functions list.
Possible values:
The name of the function
Example:
functions:
- function: print_something
output: true
- function: main
output: true
Default value:
The function
key can not be omitted if rules for a given function are to be added.
Example code structure test definitions:¶
In this section, a few examples for code structure test definitions are given.
Note
For security reasons, insecure: false
is included in each of the following test cases. Omit insecure: false
at your own risk.
Example 1 (incorrect): Output via printf is only allowed in function print_hello_world
but print_hello_world
is called in the function main
1version: "0.0"
2translation_unit: hello_world.c
3tests:
4 - type: structural
5 name: Structural
6 insecure: false
7 output: false
8 required_functions:
9 - "void print_hello_world(void);"
10 functions:
11 - function: print_hello_world
12 expected_function_calls:
13 - printf
14 - function: main
15 expected_function_calls:
16 - print_hello_world
Lets suppose the function print_hello_world
calls the printf
function. The printf
-call triggers an infringement of the global rule output: false
because the printf call is propagated backward along the call-graph from print_hello_world
to main
.
Example 1 (correct): Output is only allowed in functions print_hello_world
and main
1version: "0.0"
2translation_unit: hello_world.c
3tests:
4 - type: structural
5 name: Structural
6 insecure: false
7 output: false
8 required_functions:
9 - "void print_hello_world(void);"
10 functions:
11 - function: print_hello_world
12 expected_function_calls:
13 - printf
14 - function: main
15 expected_function_calls:
16 - print_hello_world
17 - printf
Lets suppose the function print_hello_world
calls the printf
function. In this case the printf
-call does not trigger an infringement of the global rule output: false
because the rule has been overridden for all functions along the call-graph, in this specific case print_hello_world
and main
.
Grey Box¶
A grey box test analyzes the return type and side effects of functions.
Output on stdout
and stderr
can currently not be monitored.
The only functionality for output monitoring is implemented in Input-/Output tests.
This is done by building a shared library file and calling the C
-function directly from within the python code.
Compilation of the shared library file is done by the framework. The grey box test definition is an implementation of the abstract class GreyBoxTestRunner
Note
If the execution of the tested function ends unexpectedly a dedicated message is added to the test feedback.
This message can be customized by overriding the function exit_failure_message
.
The default message for the error signal 8 or 11 is:
Error occurred during execution! Exit code: 8 This exit code might indicate that a fatal arithmetic error occurred (e.g., division by zero).
Error occurred during execution! Exit code: 11 This exit code might indicate that your program tries to read and write outside the memory that is allocated for it.
For other exit codes, by default, only “Error occurred during execution! Exit code: ” followed by the respective exit code is printed.
Furthermore, the string produced by function_call_details
is appended in any case.
Note
If during the execution of the tested function a timeout occurs a dedicated message to indicate the timeout is produced.
The message starts with the following general description:
Timeout: The execution of your program was canceled since it did not finish after x
seconds! This might indicate that there is some unexpected behavior (e.g., an endless loop) or that your program is very slow!
Last executed test:
x
corresponds to the value configured in the Test definition file.
Furthermore, the string produced by function_call_details
is appended.
Note
For convenience sake - to save the trouble of checking the exported symbols of the shared library - we recommend adding all functions that will be tested within the grey box test as required_functions for the structural test and make the structural test a requirement of the grey box test (see Test definition file for requirements).
Example - Add¶
In the following, a task description of a simple assignment is given. Additionally, a potential solution and the workflow to create a grey box test for this task are provided.
Note
For this example, the following file structure is assumed.
assignment/
add.c
Makefile
test/
add.yml
add_grey_box.py
Tests.py
Task¶
Implement a function with the signature int add(int a, int b)
that adds the two integers a
and b
and returns the result.
Test the function add
in main
!
Solution¶
add.c
:
#include <stdio.h>
#include <stdlib.h>
int add(int a, int b) {
return a + b;
}
int main(void) {
int a = 5;
int b = 6;
printf("%d + %d = %d\n", a, b, add(5, 6));
return EXIT_SUCCESS;
}
Makefile
:
CC=gcc
CFLAGS=-Wall -Werror -Wextra -Wpedantic -std=c11
.PHONY: add.so clean
add: add.c
add.so: CFLAGS+=-fPIC -shared
add.so: add.c
$(CC) $(CFLAGS) $^ -o $@
clean:
rm add add.so
Test¶
Add a configuration file
add.yml
version: "0.0"
translation_unit: add.c
tests:
- type: compile
name: TestCompile
- type: structural
name: TestCodeStructure
requirements:
- TestCompile
required_functions:
- 'int add(int a, int b);'
- type: grey_box
name: TestGreyBox
requirements:
- TestCompile
- TestCodeStructure
module_path: add_grey_box.py
class: GreyBoxTest
unit_test: true
Add a grey_box test file
add_grey_box.py
import ctypes
from typing import Optional
from fact.c_util import test_case, create_error_hint
from fact.test_cases import GreyBoxTestRunner
class GreyBoxTest(GreyBoxTestRunner):
def __init__(self, library_path: str) -> None:
super().__init__(library_path)
self.add = self.library.add
self.add.argtypes = [ctypes.c_int, ctypes.c_int]
self.add.restype = ctypes.c_int
def run(self) -> None:
self.test_cases_add()
def test_cases_add(self) -> None:
self.test_add(0, 0, show_expected=True, show_hint=True)
self.test_add(1, -1, show_expected=True, show_hint=True)
self.test_add(-1, 1, show_expected=True, show_hint=True)
self.test_add(-1, -1, show_expected=True, show_hint=True)
self.test_add(1, 5, show_expected=True, show_hint=True)
self.test_add(-32767, 32767, show_expected=True, show_hint=True)
self.test_add(23, 52)
self.test_add(-23, -52)
@test_case('add')
def test_add(self, a, b, show_expected=False, show_hint=False) -> Optional[str]:
p_res = a + b
c_res = self.add(a, b)
if c_res == p_res:
return None
hint = None
if show_hint:
hint = f"Did you try 'a = {a}' and 'b = {b}'?"
return create_error_hint(c_res, p_res, show_expected, hint)
Add
Tests.py
file
from fact.tester import Tester
tester = Tester.from_config('add.yml')
tester.run()
tester.export_result()
Run
Tests.py
to execute the tests
OCLint¶
An oclint test analyzes the translation unit’s source code and enforces the predefined rules. Each translation unit requires its own set of rules. If the analyzed code violates at least one rule, the test is failed, and feedback on what rules have been violated is given. Otherwise, feedback that the oclint test has been passed successfully is given. The tool OCLint is used to perform the analysis for this test case.
In addition to the configuration options presented in Test definition file the following attributes are available:
suppress_line: allow/disallow comments to suppress the analysis of the commented line.
suppress_range: allow/disallow annotations to suppress the analysis.
disable_rules: use all rules except for the rules given in this array.
apply_rules: only use the rules given in this array.
Note
For the oclint analysis the NDEBUG
flag is always set during OCLint’s the integrated compilation step in order to disable the analysis of assert
statements.
Attribute details¶
Details on the additional configuration options for oclint tests are given in the following sections.
suppress_line
¶
This rule indicates whether students are allowed to suppress OCLint warnings using the //!OCLint
comment.
Possible values:
true
: the suppression of OCLint warnings using the//!OCLint
comment is allowed.false
(default): the suppression of OCLint warnings using the//!OCLint
comment is not allowed.
Example:
suppress_line: true
If suppression of warnings is allowed all warnings generated by a line of code can be suppressed as follows:
void a() {
int unusedLocalVariable; //!OCLint
}
See !OCLint Comment in the OCLint documentation for further details and examples.
suppress_range
¶
This rule indicates whether students are allowed to suppress OCLint rules using annotations.
Possible values:
true
: the suppression of OCLint rules using annotations is allowed.false
(default): the suppression of OCLint rules using annotations is not allowed.
Example:
suppress_range: true
If suppression of rules is allowed all rules in the annotated scope are suppressed as follows:
__attribute__((annotate("oclint:suppress")))
See Annotations in the OCLint documentation for further details and examples.
disable_rules
¶
Array indicating the OCLint rules that should not be applied to the analyzed translation unit.
Possible values:
All rules defined in the Rule Index of the OCLint documentation. The rules have to be written as given in the referenced rule index.
Example:
disable_rules:
- ShortVariableName
- UselessParentheses
apply_rules
¶
Array indicating the OCLint rules that be applied to the analyzed translation unit.
Possible values:
All rules defined in the Rule Index of the OCLint documentation. The rules have to be written as given in the referenced rule index.
Example:
apply_rules:
- GotoStatement
- EmptyElseBlock
- LongVariableName
- LongLine
Example code structure test definitions:¶
In this section, a few examples for oclint test definitions are given.
Example 1: select all applied rules by hand.
1version: "0.0"
2translation_unit: hello_world.c
3tests:
4 - type: oclint
5 name: Linter
6 apply_rules:
7 - GotoStatement
8 - EmptyElseBlock
9 - LongVariableName
10 - LongLine
Example 2: select all applied rules by hand and allow students to disable rules.
version: "0.0"
translation_unit: hello_world.c
tests:
- type: oclint
name: Linter
suppress_line: true
suppress_range: true
apply_rules:
- GotoStatement
- EmptyElseBlock
- LongVariableName
- LongLine
Example 3: disable selected rules and allow students to disable rules with line comments only.
version: "0.0"
translation_unit: hello_world.c
tests:
- type: oclint
name: Linter
suppress_line: true
disable_rules:
- BitwiseOperatorInConditional
- MissingBreakInSwitchStatement
- UselessParentheses
- HighCyclomaticComplexity
- HighNPathComplexity
- HighNcssMethod
- ShortVariableName
- EmptyIfStatement
- MissingDefaultStatement
- UselessParentheses
fact package¶
Submodules¶
fact.c_util module¶
Utility functions and decorators for grey box testing using python.
- class fact.c_util.CaptureStream(stream: TextIO, stream_name: str)[source]¶
Bases:
object
Used to capture output on a stream. Please ensure that all data is written on the captured stream before capturing is stopped (e.g., flush the stream).
Note that this class is based on https://stackoverflow.com/a/29834357.
- captured_text: str¶
- get_data() str [source]¶
Returns the captured data
- Returns
The captured data or empty string, if no data was captured.
- new_fileno: int¶
- original_fileno: int¶
- original_stream: TextIO¶
- pipe_in: int¶
- pipe_out: int¶
- stream_name: str¶
- exception fact.c_util.GreyBoxTimeoutError(function_call_details: str, *args: object)[source]¶
Bases:
TimeoutError
Raised when a timeout expired during a grey box test.
- function_call_details: str¶
- exception fact.c_util.NonAsciiCharacter(output: str, stream: str, *args: object)[source]¶
Bases:
Exception
Raised when the captured output contains non ascii characters. All non printable ascii characters in output are replaced with the special character �.
- error_message_students(function_name: str) str [source]¶
Returns the default error message if a captured output contains non ascii characters.
- Parameters
function_name – Name of the output generating function
- Returns
The error message
- output: str¶
- stream: str¶
- exception fact.c_util.NonPrintableAsciiCharacter(output: str, stream: str, *args: object)[source]¶
Bases:
Exception
Raised when the captured output contains non printable ascii characters. All non printable ascii characters in output are replaced with the special character �.
- error_message_students(function_name: str) str [source]¶
Returns the default error message if a captured output contains non printable ascii characters.
- Parameters
function_name – Name of the output generating function
- Returns
The error message
- output: str¶
- stream: str¶
- fact.c_util.c_array_to_string(array: List[Any])[source]¶
Returns a string representation of the values in the array. A brace-enclosed list is used to represent the values.
- Parameters
array – The array
- Returns
The string representation of the array
- fact.c_util.c_char_array_to_string(array: List[Any], null_terminated: bool = False) str [source]¶
Returns a string representation of the characters in the array. A brace-enclosed list is used to represent the characters. The characters are shown as single-byte integer character constants (e.g.,
'a'
). Non printable ascii characters are replaced with'�'
.- Parameters
array – The array
null_terminated – Should a null termination be added?
- Returns
The string representation of the array
- fact.c_util.c_pointer(pointer) str [source]¶
Returns a hexadecimal string representation of a C-pointer.
- Parameters
pointer – The pointer
- Returns
The string representation
- fact.c_util.c_pointer_array_to_string(array: List[Any])[source]¶
Returns a string representation of the pointers in the array. A brace-enclosed list is used to represent the values.
- Parameters
array – The array
- Returns
The string representation of the array
- fact.c_util.char_arr_c(length: int)[source]¶
Creates a ctypes char-array with the given length.
- Parameters
length – The length of the array
- Returns
The char-array
- fact.c_util.char_arr_c2p(array) List[Any] [source]¶
Creates a list based on the values from a ctypes char-array.
- Parameters
array – The ctypes array comprising the elements which should be copied to the new list.
- Returns
The list
- fact.c_util.char_arr_p2c(array: str)[source]¶
Creates a ctypes char-array with the appropriate length. The provided string is used to fill the created array. Note that the created array is null terminated!
- Parameters
array – The string comprising the elements of the new array.
- Returns
The char-array
- fact.c_util.create_error_hint(actual=None, expected=None, show_expected=False, hint=None) str [source]¶
Creates an error hint.
- Parameters
actual – The actual result
expected – The expected result
show_expected – Should the actual and expected results be shown?
hint – An additional hint
- Returns
The error hint
- fact.c_util.int_arr_c(length)[source]¶
Creates a ctypes int-array with the given length.
- Parameters
length – The length of the array
- Returns
The int-array
- fact.c_util.int_arr_c2p(array) List[Any] [source]¶
Creates a list based on the values from a ctypes array.
- Parameters
array – The ctypes array comprising the elements which should be copied to the new list.
- Returns
The list
- fact.c_util.int_arr_p2c(array: List[int])[source]¶
Creates a ctypes int-array with the appropriate length. The provided array is used to fill the created array.
- Parameters
array – The array comprising the elements which should be copied to the new array.
- Returns
The int-array
fact.io module¶
Parsing of the DSL for input-output tests
- class fact.io.IOParser(input_text: str)[source]¶
Bases:
object
Parses an input-output test definition using the DSL
- tests: List[fact.io.IOTestConfig]¶
- class fact.io.IOReplacement(parent, pattern: str, replace: str, hint: str, num_matches: int)[source]¶
Bases:
object
Configuration of a variable substitution where the value of a variable is replaced.
- hint: str¶
- num_matches: int¶
- pattern: str¶
- replace: str¶
- exception fact.io.IOScriptExecutionError[source]¶
Bases:
Exception
Raised when a script in a IO-Test does not terminate successfully.
- class fact.io.IOSubstitution(parent, variable: str, value: str, hint: str, num_matches: int)[source]¶
Bases:
object
Configuration of a variable substitution where the value of a variable is replaced.
- hint: str¶
- num_matches: int¶
- value: str¶
- variable: str¶
- class fact.io.IOTestConfig(stdin: List[str], args: List[str], return_code: List[int], replacements: List[fact.io.IOReplacement], substitutions: List[fact.io.IOSubstitution], settings: fact.io.IOTestSettings)[source]¶
Bases:
abc.ABC
An abstract io test configuration that each io test configuration has to inherit from
- arguments: List[str]¶
- check_return_value(obtained_return_value: int) bool [source]¶
Returns whether the obtained return value matches any of the expected return codes
- Parameters
obtained_return_value – The obtained return_value
- Returns
True, return value matches any of the expected return codes
- abstract check_stderr(obtained: str) bool [source]¶
Returns whether the obtained output matches the expected output on stderr
- Parameters
obtained – The obtained output on stderr
- Returns
True, if the obtained output matches the expected output
- abstract check_stdout(obtained: str) bool [source]¶
Returns whether the obtained output matches the expected output on stdout
- Parameters
obtained – The obtained output on stdout
- Returns
True, if the obtained output matches the expected output
- modifies_code() bool [source]¶
Returns whether the test configuration changes the source because of replacement or substitution.
- Returns
True, if the code is modified.
- replacements: List[fact.io.IOReplacement]¶
- return_code: List[int]¶
- settings: fact.io.IOTestSettings¶
- stdin: str¶
- substitutions: List[fact.io.IOSubstitution]¶
- class fact.io.IOTestConfigExact(stdin: List[str], stdout: List[Tuple[str, str]], stderr: List[Tuple[str, str]], args: List[str], return_code: List[int], replacements: List[fact.io.IOReplacement], substitutions: List[fact.io.IOSubstitution], settings: fact.io.IOTestSettingsExact)[source]¶
Bases:
fact.io.IOTestConfig
Configuration of an input-output test with regex matching
- check_stderr(obtained: str) bool [source]¶
Returns whether the obtained output matches the expected output on stderr
- Parameters
obtained – The obtained output on stderr
- Returns
True, if the obtained output matches the expected output
- check_stdout(obtained: str) bool [source]¶
Returns whether the obtained output matches the expected output on stdout
- Parameters
obtained – The obtained output on stdout
- Returns
True, if the obtained output matches the expected output
- settings: fact.io.IOTestSettingsExact¶
- stderr: List[Tuple[str, str]]¶
- stderr_mod: str¶
- stdout: List[Tuple[str, str]]¶
- stdout_mod: str¶
- class fact.io.IOTestConfigRegex(stdin: List[str], stdout: List[str], stderr: List[str], args: List[str], return_code: List[int], replacements: List[fact.io.IOReplacement], substitutions: List[fact.io.IOSubstitution], settings: fact.io.IOTestSettingsRegex)[source]¶
Bases:
fact.io.IOTestConfig
Configuration of an input-output test with regex matching
- check_stderr(obtained: str) bool [source]¶
Returns whether the obtained output matches the expected output on stderr
- Parameters
obtained – The obtained output on stderr
- Returns
True, if the obtained output matches the expected output
- check_stdout(obtained: str) bool [source]¶
Returns whether the obtained output matches the expected output on stdout
- Parameters
obtained – The obtained output on stdout
- Returns
True, if the obtained output matches the expected output
- settings: fact.io.IOTestSettingsRegex¶
- stderr: str¶
- stdout: str¶
- class fact.io.IOTestResults(test: fact.io.IOTestConfig, output: Optional[subprocess.CompletedProcess], substitution_description: List[str])[source]¶
Bases:
abc.ABC
An abstract io test result that each io test result has to inherit from
- ascii_msg(output: str, stream: str)[source]¶
Returns the error message if the output contains non printable ascii characters. All non printable ascii characters in output are replaced with a special character (�).
- Parameters
output – Erroneous output
stream – Stream on which the erroneous output occurred
- Returns
The error message
- abstract error_msg(source_file: pathlib.Path) str [source]¶
Returns the error message for the io test
- Parameters
source_file – The path to source_file
- Returns
Error message
- is_successful() bool [source]¶
Checks whether exit code, stdout and stderr are as expected.
- Returns
True, if the test was successful
- no_hint_msg = 'No hint available! Please, read the exercise description very carefully!'¶
- substitution_description: List[str]¶
- test: fact.io.IOTestConfig¶
- test_return_value: bool¶
- test_stderr: bool¶
- test_stdout: bool¶
- class fact.io.IOTestResultsExact(test: fact.io.IOTestConfigExact, output: Optional[subprocess.CompletedProcess], substitution_description: List[str])[source]¶
Bases:
fact.io.IOTestResults
Test result of an io-test using exact matching.
- class fact.io.IOTestResultsRegex(test: fact.io.IOTestConfigRegex, output: Optional[subprocess.CompletedProcess], substitution_description: List[str])[source]¶
Bases:
fact.io.IOTestResults
Test result of an io-test using regex matching.
- class fact.io.IOTestSettings[source]¶
Bases:
abc.ABC
An abstract io test setting that every io test setting has to inherit from.
- escape_sequence: Optional[str] = None¶
- hint: Optional[str] = None¶
A hint which may help students if they encounter an error in this test
- printable_ascii: bool = False¶
The escape fraction. Code between two escape parts is executed. The code and the two escape parts are replaced with the resulting output on stdout.
- show_input: bool = False¶
Should the test input be shown in the error message?
- show_output: bool = True¶
Should the obtained output be shown?
- show_substitution: bool = True¶
Should the substituted code be shown in the error message?
- class fact.io.IOTestSettingsExact[source]¶
Bases:
fact.io.IOTestSettings
Settings of an io test with exact matching
- ignore_cases: bool = False¶
Should the cases be ignored?
- line_rstrip: bool = False¶
Should all whitespaces & tabs at the end of each line be ignored?
- rstrip: bool = False¶
Should all whitespace characters at the end be ignored?
- show_diff: bool = True¶
Should a diff of the expected and obtained outputs be shown?
- show_expected: bool = False¶
Should the expected output be shown?
- class fact.io.IOTestSettingsRegex[source]¶
Bases:
fact.io.IOTestSettings
Settings of an io test with regex matching
- show_error: bool = True¶
Should the obtained output on stderr be shown?
fact.test_cases module¶
This module contains the different test cases which can be used for this C testing framework. Note that each test case must inherit from AbstractTest!
- class fact.test_cases.AbstractTest(test_name: str, sourcecode_runner: fact.test_cases.SourcecodeRunner, requirements: Optional[List[str]] = None)[source]¶
Bases:
abc.ABC
A abstract test that every test has to inherit from.
- requirements: List[str]¶
- sourcecode_runner: fact.test_cases.SourcecodeRunner¶
- start(case)[source]¶
Starts the test run.
- Parameters
case – The test case where this test should get added to.
- Returns
None
- test_name: str¶
- class fact.test_cases.AbstractTimeoutTest(test_name: str, sourcecode_runner: fact.test_cases.SourcecodeRunner, requirements: Optional[List[str]] = None)[source]¶
Bases:
fact.test_cases.AbstractTest
Abstract test for test cases that are not run via the subprocess module
- requirements: List[str]¶
- sourcecode_runner: fact.test_cases.SourcecodeRunner¶
- test_name: str¶
- class fact.test_cases.CTest(makefile_directory, shared_lib_test: str, shared_lib_student, c_array_size: int = 8192)[source]¶
Bases:
object
Loads a shared library which executes tests.
The execution, verification, and error reporting is left to the user. The shared library has to implement at least four function:
void *fact_init(char *lib_name): Initializes the test run and loads the shared library compiled from the student solution
int fact_tests(void *ptr): Tests the solution
int fact_errors(void *ptr, char *error, size_t max_error_len): Reports errors
int fact_free(void *ptr): Frees resources
- c_array_size: int¶
- makefile_directory: pathlib.Path¶
- run_tests() Optional[str] [source]¶
Executes the shared library to test the solution.
- Returns
An error message, if an error occurs.
- exception fact.test_cases.ConfigurationError[source]¶
Bases:
Exception
Raised when a test configuration is erroneous.
- class fact.test_cases.GreyBoxTestRunner(library_path: str)[source]¶
Bases:
abc.ABC
Loads a shared library and executes tests implemented in the function run.
Note that each grey box test must inherit from GreyBoxTestRunner! The execution, verification, and error reporting is left to the user.
- add_error(function_name: str, hint: Optional[str] = None) None [source]¶
Adds an error. Call this method to report an error in the grey box test
- Parameters
function_name – The name of the erroneous function
hint – A hint that should be shown as part of the error message
- Returns
None
- errors: Dict[str, List[str]]¶
- exit_failure_message(function_name: str, exitcode: int, args: List[any])[source]¶
Adds an error message when the execution of the tested function does not terminate successfully.
- Parameters
function_name – The name of the tested function
exitcode – The exitcode of the process executing the test function
args – The arguments passed to the tested function
- Returns
None
- abstract function_call_details(function_name: str, args: List[any]) str [source]¶
Returns a description of the function call for the tested function.
- Parameters
function_name – The name of the tested function
args – The arguments passed to the tested function
- Returns
The description of the function call
- library_path: str¶
- class fact.test_cases.Replacement(lineno: int, old_value: str)[source]¶
Bases:
object
Represents a replacement of a literal assigned to a variable.
- class fact.test_cases.SourcecodeRunner(make_target: str, makefile_directory: Union[str, pathlib.Path], makefile_name: str = 'Makefile', sourcecode_directory: Optional[str] = None, make_timeout_sec: Optional[int] = None, exec_timeout_sec: Optional[int] = None)[source]¶
Bases:
object
Runner for building and executing C programs.
- build_executable(target: Optional[str] = None) subprocess.CompletedProcess [source]¶
Compiles the C program
- Parameters
target – Target name of the Makefile rule to be used
- Returns
Output
- Raises
subprocess.CalledProcessError – If the process exits with a non-zero exit code.
- exec_timeout_sec: int¶
- classmethod from_config(test_config: Dict[str, Any], default_make_target: Optional[str] = None)[source]¶
Configure a source code runner with a given yaml-file.
- Parameters
test_config – File name of the yaml-file containing the configuration
default_make_target – The default target for the make file. Used as a fallback if no target is specified in the configuration.
- make_target: str¶
- make_timeout_sec: int¶
- makefile_directory: Union[str, pathlib.Path]¶
- makefile_name: str¶
- run_executable(arguments: List[str], stdin: str) subprocess.CompletedProcess [source]¶
Executes the C program with the provided arguments and input on stdin
- Parameters
arguments – Commandline arguments
stdin – Input on stdin
- Returns
Output
- Raises
subprocess.CalledProcessError – If the process exits with a non-zero exit code.
- sourcecode_directory: str¶
- status_msg_timeout() str [source]¶
Returns a status message describing the make and execution timeout of this test runner.
- Returns
The status message
- exception fact.test_cases.SubstitutionException(errors)[source]¶
Bases:
Exception
Raised when a substitution fails.
- errors: List[str]¶
- class fact.test_cases.TestCodeStructure(test_name: str, translation_unit: str, config: Dict[str, Any], sourcecode_runner: fact.test_cases.SourcecodeRunner, requirements: Optional[List[str]] = None)[source]¶
Bases:
fact.test_cases.AbstractTimeoutTest
Test runner for structural tests. Analyzes the code structure of a program and checks whether the structure satisfies the requirement.
- config: Dict[str, Any]¶
- classmethod from_config(test_config: Dict[str, Any])[source]¶
Configure a test runner for structural tests with a given yaml-file.
- Parameters
test_config – File name of the yaml-file containing test configuration
- translation_unit: str¶
- class fact.test_cases.TestCompile(test_name: str, sourcecode_runner: fact.test_cases.SourcecodeRunner, requirements: Optional[List[str]] = None)[source]¶
Bases:
fact.test_cases.AbstractTest
Test runner for compilation tests. Compiles the C source code and check if the compilation was successful.
- classmethod from_config(test_config: Dict[str, Any])[source]¶
Configure a test runner for compilation tests with a given yaml-file.
- Parameters
test_config – File name of the yaml-file containing test configuration
- requirements: List[str]¶
- sourcecode_runner: fact.test_cases.SourcecodeRunner¶
- test_name: str¶
- class fact.test_cases.TestGreyBox(test_name: str, library_name: str, test_case, sourcecode_runner: fact.test_cases.SourcecodeRunner, requirements: Optional[List[str]] = None, unit_test: bool = True, max_errors: int = 0)[source]¶
Bases:
fact.test_cases.AbstractTimeoutTest
Test runner for grey-box tests. Executes tests defined in a GreyBoxTestRunner.
- errors: Dict[str, List[str]]¶
- classmethod from_config(test_config: Dict[str, Any])[source]¶
Configure a test runner for python grey-box tests with a given yaml-file.
- Parameters
test_config – File name of the yaml-file containing test configuration
- library_name: str¶
- max_errors: int¶
- unit_test: bool¶
- class fact.test_cases.TestGreyBoxC(test_name: str, make_target_test: str, library_name_student: str, library_name_test: str, sourcecode_runner: fact.test_cases.SourcecodeRunner, requirements: Optional[List[str]] = None, max_error_len: int = 8192)[source]¶
Bases:
fact.test_cases.AbstractTimeoutTest
Test runner for grey-box tests written in C.
- classmethod from_config(test_config: Dict[str, Any])[source]¶
Configure a test runner for C/C++ grey-box tests with a given yaml-file.
- Parameters
test_config – File name of the yaml-file containing test configuration
- library_name_student: str¶
- library_name_test: str¶
- make_target_test: str¶
- max_error_len: int¶
- class fact.test_cases.TestIO(test_name: str, c_file: str, filename_io_test: str, sourcecode_runner: fact.test_cases.SourcecodeRunner, requirements: Optional[List[str]] = None)[source]¶
Bases:
fact.test_cases.AbstractTest
Test runner for input-output tests. For IO-tests the following steps are performed:
If substitution is present, apply the substitution to the source code
Recompile if necessary
Run program with the defined commandline arguments and the input on stdin
Compare the exit code and match the regexes for stdout and stderr
Report results if an error occurred
- c_file_path: pathlib.Path¶
- execute_io_test(test: fact.io.IOTestConfig, substitution_description: List[str]) Optional[str] [source]¶
Executes an io test
- Parameters
test – Test case
substitution_description – Description of substitutions.
- Returns
An error if the test case failed
- filename_io_test: str¶
- classmethod from_config(test_config: Dict[str, Any])[source]¶
Configure a test runner for input-output tests with a given yaml-file.
- Parameters
test_config – File name of the yaml-file containing test configuration
- original_content: str¶
- remove_comments() Optional[str] [source]¶
Removes all comments in the source code.
- Returns
An error message, if an error occurs.
- replace()[source]¶
Manages insertion of the replacement and recovery of the original code.
- Returns
None
- replacement_content: Optional[str]¶
- valid_replacement(test: fact.io.IOTestConfig) List[str] [source]¶
Check if the number of replacements matches with the definition and applies the replacement if no error was found
- Parameters
test – Test case
- Returns
List of error messages
- valid_substitution(test: fact.io.IOTestConfig) List[str] [source]¶
Check if the number of substitutions matches with the definition and applies the substitution if no error was found
- Parameters
test – Test case
- Returns
List of error messages
- class fact.test_cases.TestOclint(test_name: str, translation_unit: str, sourcecode_runner: fact.test_cases.SourcecodeRunner, requirements: Optional[List[str]] = None, suppress_line: bool = False, suppress_range: bool = False, disable_rules: Optional[List[str]] = None, apply_rules: Optional[List[str]] = None)[source]¶
Bases:
fact.test_cases.AbstractTest
Test runner for static code analysis using OCLint.
- apply_rules: List[str]¶
- disable_rules: List[str]¶
- classmethod from_config(test_config: Dict[str, Any])[source]¶
Configure a test runner for compilation tests with a given yaml-file.
- Parameters
test_config – File name of the yaml-file containing test configuration
- suppress_line: bool¶
- suppress_range: bool¶
- translation_unit: str¶
- class fact.test_cases.TestStatus(value)[source]¶
Bases:
enum.Enum
Models the four states - skipped, error, failure, and success - of a test.
- ERROR = 'error'¶
- FAILURE = 'failure'¶
- SKIPPED = 'skipped'¶
- SUCCESS = 'success'¶
- fact.test_cases.apply_substitution(variable: str, value: str, text: str) Tuple[str, List[fact.test_cases.Replacement]] [source]¶
Searches in the string for matches. If a match was found, the original value of the variable initialization is substituted accordingly.
- Can be used to substitute all scalar basic types (e.g.,
bool
,char
,short
, int
,long
,float
,double
).
- Parameters
variable – variable name
value – new value of the variable
text – string searched for matches. If a match was found, the original value of the variable initialization is substituted accordingly
- Returns
String after substitution and list of replacements
- Can be used to substitute all scalar basic types (e.g.,
fact.tester module¶
Infrastructure for test case registration and reporting.
- exception fact.tester.NotExecutedError[source]¶
Bases:
Exception
Raised if the results are checked before they are computed.
- class fact.tester.TestCase(name: str)[source]¶
Bases:
object
A single test case (e.g., test compilation or IO).
- message: str¶
- name: str¶
- result: fact.test_cases.TestStatus¶
- stderr: str¶
- stdout: str¶
- tester_output: str¶
- time: datetime.timedelta¶
- to_xml(suite: xml.etree.ElementTree.Element, max_chars_per_output: int = 16384) None [source]¶
Adds the results of the test to a XML test suite
- Parameters
suite – XML representation of the test suite
max_chars_per_output – Maximal numbers of characters for stdout and stderr of the test output
- Returns
- class fact.tester.TestSuite(name: str)[source]¶
Bases:
object
Test suite comprising multiple test cases.
- add_case(case: fact.tester.TestCase) None [source]¶
Adds a test case to the test suite.
- Parameters
case – Test case to add
- Returns
None
- cases: Dict[str, fact.tester.TestCase]¶
- errors: int¶
- failures: int¶
- get_test_cases() Dict[str, fact.tester.TestCase] [source]¶
Returns the test cases :return: Dict of str -> TestCase: Maps the test case name to a TestCase
- name: str¶
- skipped: int¶
- successful: int¶
- tests: int¶
- time: datetime.timedelta¶
- class fact.tester.Tester(name: str = 'FACT-0.0.5', logging_level=10)[source]¶
Bases:
object
Test runner used to add, execute tests and export the test results.
- add_test(test: Any) None [source]¶
Adds a new test that will be run once “run()” is invoked.
- Raises
NameError – If the test_name of the provided test has already been added.
- Parameters
test – Test to add
- Returns
None
- executed: bool¶
- export_result(output_path: str = '../test-reports/tests-results.xml') None [source]¶
Exports the test results into a JUnit format and stores it at the given output_path. The JUnit format is based on 1.
- Parameters
output_path – Path used to store the export
- Returns
None
- classmethod from_config(config_file: Union[str, pathlib.Path], name: str = 'FACT-0.0.5', logging_level=10)[source]¶
Configure a tester with a given yaml-file.
- Parameters
config_file – The yml-file
name – The name of the tester
logging_level – Debugging level used for printing on stdout
- Returns
The Tester
- classmethod from_dict(conf_dict: Dict[str, Any], makefile_directory: Optional[str] = None, sourcecode_directory: Optional[str] = None, name: str = 'FACT-0.0.5', logging_level=10)[source]¶
Configure a tester with a given dict.
- Parameters
conf_dict – Dict containing the test configuration
makefile_directory – The directory in which the Makefile resides
sourcecode_directory – The directory in which the translation unit resides
name – The name of the tester
logging_level – Debugging level used for printing on stdout
- Returns
The Tester
- name: str¶
- successful() Optional[bool] [source]¶
Returns whether all tests were executed successfully.
- Returns
True, if all tests could be executed successfully. If the tests were not yet executed None is returned.
- suite: fact.tester.TestSuite¶
- tests: Dict[str, Any]¶
Module contents¶
Test framework for C-programs. The framework implements compilation tests, input-output tests, structural tests and grey box tests.
It is assumed that libclang from clang 11.0 is installed (https://apt.llvm.org/).