Code Monkey home page Code Monkey logo

c-test-framework's Introduction

Simple test framework

This is a simple testing framework for C99 on Linux, intended to be used in an academic course. It uses the Check framework for unit testing, some custom Bash scripts for integration testing, and the Memcheck Valgrind-based tool for finding memory leaks.

The authors of this framework use it for a series of projects in our CS 261 course (example assignments). The projects include an introduction to C programming followed by the multi-part development of a machine code interpreter for the fictional Y86 language used in the CS:APP textbook. We have attempted to clean up and generalize the code in this repository, but you may still find references to our course in these files. If you have any questions, please contact the authors or open an issue.

Getting started

Prerequisites

You will need to install the Check framework (instructions) in order to use this framework. If you are running this on a departmental server you will probably need to contact your sysadmin to install the software. Fortunately, it is a standard package on most major Linux distributions.

Installing

Clone this repository in your home directory and begin working. If you wish to use our grading scripts you will need to set up submission folders in a particular way; see the appropriate section below for more details.

Using the framework

Basics

The project sources are in the ref folder. There are two provided projects:

  • pT-blank - A mostly-empty project with the barebones necessary for a new project. Copy this folder to start a new project from scratch.

  • p0-intro - A minimal project with a few required routines and I/O specifications. Several unit and integration tests are provided along with a project description.

To build and test both of the reference solutions, run make test in the ref folder.

To create a new project, simply copy the pT-blank folder in its entirety to a new folder and begin working. You will probably wish to change the filenames first, and with that you will need to change the includes and Makefiles appropriately. You should be able to search for "pT-blank" to find all of the necessary locations.

The build system is a simple Makefile with support for multiple modules as well as pre-compiled objects. The latter is useful when projects build on each other; you can distribute stripped object files so that students who were unable to fully complete the first project can still complete the second. Note that optimization is disabled by default to make debugging easier; this is not a framework requirement.

All of the testing code for each project is in the tests subfolder. The framework is intended to be self-contained (with only the Check dependency) and so there is some duplication between projects. See sections below for further details about how the tests work and for instructions on adding new tests.

Unit tests

Unit tests are intended to test individual routines (and their dependencies), and they are written in C using the Check framework. Often the code for these tests tends to resemble the solution so it is sometimes desirable to distribute them without the source. Thus, there are three main unit testing files: public.c, private.c, and hidden.c. The first is distributed as-is so that the students can see how the tests are written, the second is distributed as a stripped object file so that students can run the tests and see the results but they do not have access to the source code, and the third is not distributed at all, allowing for tests students neither have access to nor know about.

Tests are delimited using the START_TEST and END_TEST macros provided by Check. Testing expressions are written using Check assertion routines (documentation). Here is an example from the template project:

START_TEST (C_addabs_simple)
{
    ck_assert_int_eq (add_abs(2,3), 5);
}
END_TEST

To add new tests, create a new routine in the appropriate file delimited by the appropriate macros and give it a unique name (provided as a parameter to the START_TEST macro). You will also need to add a tcase_add_test line at the bottom of the file for your new test.

Aside: we use a test naming scheme where tests are labeled with a prefix containing the grade corresponding to the test; i.e., students must pass all D_* tests to receive a "D" grade, all of those plus all C_* tests to receive a "C" grade, etc. This is not a requirement of the testing framework itself, but the grading script does use it to automatically assign baseline grades based on test results.

There is also a testsuite.c driver, but it is very generic and does not generally need to be modified.

Integration tests

Integration tests are intended to test whole-program behavior and are written using input/output file pairs. The inputs are stored in the inputs folder and the expected outputs are stored in the expected folder. Tests are specified in the itests.include file, which contains one line for each integration test that specifies the name of the test (the output file must match this) as well as the command line intended to evoke the corresponding output. The actual testing is handled by the integration.sh Bash script, which should not need to be modified when creating a new project except to change the executable name at the top.

To add a new integration test: 1) put any necessary input (not all integration tests require an input file; e.g., if all parameters are provided on the command line) in the inputs folder, 2) put the required output in a text file in the expected folder, and 3) add a corresponding line to itests.include.

Note that there is no "code" for integration tests and thus there are no private integration tests; however, there is support for hidden integration tests that are not distributed to students. Hidden integration tests can be placed in the appropriate section in itests.include and the test name must end with _H.

Because memory management is such an important part of coding in a low-level language, the script also runs each test in Valgrind/Memcheck to check for memory leaks. If you do not have Valgrind installed or wish to skip these tests, you can comment out the offending calls in integration.sh.

Distributions

Scripts are provided in the dist folder for building project distributions. These scripts remove solution code and package up the project into a tarball. For convenient testing, it also saves a snapshot of both the distribution files and the full solution.

To mark solution code for removal, surround it with BEGIN_SOLUTION and END_SOLUTION one-line comments. If the removal of the code would result in invalid code (e.g., no return value), you can use a BOILERPLATE tag to provide alternative code for the distribution. Here is an example from the template project:

int add (int num1, int num2)
{
    // BEGIN_SOLUTION
    return num1 + num2;
    // END_SOLUTION
    // BOILERPLATE: return 0;
}

In the distribution, this will be converted to:

int add (int num1, int num2)
{
    return 0;
}

To create distribution files for a new project, copy rebuild-pT.sh in the dist folder and make any appropriate changes (file names, new files, etc.). Make sure you use the provided "cleanup" function to copy any files that contain source code. If you are providing prebuilt object files containing solutions to a previous project, make sure you strip -S them after copying them into the distribution folder.

We recommend double-checking the distribution files every time you rebuild them to ensure you are not accidentally distributing solution code because of some framework misconfiguration.

Grading

A sample grading script has been provided in /grading/grade.sh. To use this folder, you will need to collect student submissions using a specific file structure. There must be a root submissions folder with subfolders for each student (we use the students' login IDs as subfolder names). Each student subfolder should then have a subfolder for each project containing the actual submission files. Each of these subsubfolders must have a unique project identifier (e.g., 'p0', 'p1', etc.).

You must also create a project.include file (in the current folder wherever you wish to run the tests, but we recommend it NOT be in the submissio folder) containing filename and path information specific to your project. See the template file or p0 file for examples of what this file should look like. You will need to change all of the files and paths to match your setup:

  • TAG is the unique project identifier.
  • EXE is the name of the compiled binary.
  • FILES is a list of submission files to be copied into the results folder.
  • REFFILES is a list of reference testing files to be copied into the results folder (this is to prevent the student from altering the test files).
  • REF is the path to the project reference files.
  • SUBMIT is the path to the root student submission folder, which should contain student subfolders and project subsubfolders as described above.
  • RESULTS is the path to a folder where all of the results can be copied.

When the script is run, it copies all of the student submissions along with testing files into subfolders in RESULTS. It then builds and runs all tests on all submissions, printing a summary of the results to standard output.

Docker containers

The framework has been designed to be platform-independent and has been tested on a variety of Linux platforms as well as macOS High Sierra and Windows 10 (using Cygwin). In addition, we have provided Docker files so that you can run this framework on non-supported platforms. See the README.md for more info.

Contributing

If you wish to contribute, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository. We may decide to incorporate your contributions in our project or we may suggest that you maintain them as a fork.

Authors

The primary developer (and current maintainer) of this project is Mike Lam, and the code was based on an older framework written by Michael Kirkpatrick. Both authors are faculty at James Madison University.

License

This project is licensed under the MIT License - see the LICENSE.txt file for details.

c-test-framework's People

Contributors

kylelaker avatar lam2mo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

c-test-framework's Issues

Re-test on Windows

This framework hasn't been tested on Windows in the past year. It would be good to re-verify that everything is working. In the past we have tested exclusively with Cygwin, but other Windows-based platforms include MinGW and the WSL.

Parallelize testing

If multiple cores are available, improve the grading process so that multiple students are graded in parallel. Because this is naturally parallel there shouldn't be any synchronization issues as long as the file system can keep up.

Bonus points: make it SLURM or MOAB-aware for distributed testing on a cluster.

p0 reference solution does not work on ARM64

The reference solution (and some of the sample submissions) for p0 do not work on ARM64. The issue seems to be in the following section of code in main.c:

    char c;
    while ((c = getopt(argc, argv, "gf:c:")) != -1) {
        switch (c) {

            case 'g': goodbye = true; hello = false;                    break;
            case 'f':    fact = true; hello = false; fact_num = optarg; break;
            case 'c':     cat = true; hello = false;   cat_fn = optarg; break;

            default:
                printf("Invalid argument.\n");
                return EXIT_FAILURE;
        }
    }

No matter what command line arguments are passed to intro, it always prints "Invalid argument."

The issue seems to be that after the call to getopt(3), c holds the value 255. While this should be the same as -1, the comparison seems to evaluate to true, so the loop body executes. The default branch then matches and the program terminates.

Since getopt(3) returns an int, I am curious if it is getting truncated to sizeof(char) for the assignment and then c is zero-extended during the comparison. Doing a zero-extension would be odd, but it's the only explanation I've been able to think of. The output of gcc -S -g -O0 main.c is attached as `main.txt (GitHub apparently really cared about the file extension being one of the supported ones). I don't know ARM assembly well-enough to know what's happening.

Changing the declaration of c to int c seems to fix the issue.
Interestingly, so does

while ((c = getopt(argc, argv, "gf:c:")) != (char) -1) {

I am happy to submit a PR to fix this. Are there any places other than the reference solution and the sample student submissions for p0 that would need to be adjusted?

Integrate with Autolab

A lot of faculty in our department are moving Autolab as a testing platform. My understanding is that it would require minimal changes to this framework to make it compatible with that platform, which would improve the submission experience for students and also make testing more secure.

Update and clean up Docker files

The Docker files were written a year ago and haven't been touched or tested since then. They are also still very much CS261-specific. They need to be cleaned up and generalized to match the rest of the files in the repository.

Add hidden tests

Currently there is no easy way to have hidden tests (e.g., students do not have access to them), which could be an obstacle to using this framework in an upper-level course where we may want to encourage test writing by withholding some of the tests in the distribution.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.