CodeGrade Help
Go to websiteAPI docsContact us
  • 🏠CodeGrade Help Center
  • ❓FAQ
    • Using CodeGrade in Safari
    • Using sections
    • Configuring roles of members
    • Archiving your course
    • Adding new members
    • Releasing grades and feedback
    • Exporting Submissions
    • Choosing a grading scale
    • Creating course-wide snippets
    • Course Gradebook
    • Allowing students to hand in after the deadline
  • Use built-in content
    • 👥Community Library
    • 🐍Introduction to Python Curriculum
  • Create your own assignments
    • 1️⃣Build your assignment
      • ➕Create assignment
        • ➕Standalone
        • ➕In Blackboard
        • ➕In Brightspace
        • ➕In Canvas
        • ➕In Moodle
        • ➕In Sakai
        • ➕In Open edX
        • 💾Importing a previous assignment
      • ⚙️General settings
      • 📋Add Rubric
      • 🤖Add Automatic Grading
      • 🧑‍🎓Student View
    • 2️⃣Grade your assignment
      • ✏️Give Feedback
      • 💯Give a Grade
    • 3️⃣Analyze your assignment
      • 🕵️‍♂️Detect Plagiarism
      • 📊Analytics
      • 🎓View and export grades
    • *️⃣Other features
      • AI Assistant
      • 👥Peer Feedback
      • 🫂Group Assignments
      • 🙋Collaborative Grading
      • 🕶️Anonymous Grading
      • 🛡️Manage Course Permissions
      • 📬Hand In Requirements
  • Automatic Grading Guides
    • 🏗️AutoTest V2 Blocks
    • ✏️Quizzes
      • ❓Multiple Choice Question
      • ❓Select All Question
      • 💻Coding Question
    • 🐍Python
      • 🐍Create your first Python assignment
      • 🐍Advanced Python autograding
    • ☕Java
      • ☕Create your first Java assignment
      • ☕Advanced Java autograding
    • 📘Jupyter Notebook
    • 🐬MySQL
    • 🌐Web Development
      • 🌐Web Development with Selenium
      • 🎨Automatically grading CSS
    • 🟨JavaScript
      • 🟨Create your first JavaScript assignment
      • 🟨Advanced JavaScript autograding
    • 📊R
    • ©️C
      • ©️Create your first C assignment
      • ©️Advanced C Autograding
    • 🖥️C#
      • 🖥️Create your first C# assignment
      • 🖥️Advanced C# autograding
    • ➕C++
      • ➕Create your first C++ assignment
      • ➕Advanced C++ autograding
    • 🐘PHP
      • 🐘Create your first PHP assignment
      • 🐘Advanced PHP autograding
    • 🏗️Code Structure Tests with Semgrep
  • For students
    • 🚀Getting started
      • 🚀Getting started in CodeGrade
      • 🚀Getting started in Blackboard
      • 🚀Getting started in Brightspace
      • 🚀Getting started in Canvas
      • 🚀Getting started in Moodle
      • 🤷‍♂️I forgot my CodeGrade username / password
    • 🧬Advanced Features
      • 👥Handing in with a group
      • 📥Handing in using Git
      • 📝Giving Peer Feedback
      • 🏆Doing a Final Exam
      • ❓Asking Questions
      • 💳Enrolling in a paid course
      • 🎟️Using a coupon to enroll in a course
      • ⏪Refunding a paid course
  • 📘APIv2: Typescript
  • 🐍APIv1: Python
  • 🤖APIv1: Docs
  • 🌐Our Website
  • ✉️Contact us
Powered by GitBook
On this page
  • Check Unit Tests
  • "cg junitxml" command
  • Instructions
  • Clang-tidy
  • "cg comments" command
  • Instructions
  1. Automatic Grading Guides
  2. C

Advanced C Autograding

Discover the advanced autograding options for C assignments

PreviousCreate your first C assignmentNextC#

Last updated 11 months ago

In this guide, we explore the advanced grading options available for C assignments. For more information about setting up a C assignment from scratch, see:

Check Unit Tests

is an industry-standard unit testing framework for C. It is particularly useful for grading assignments that require students to write functions and classes. Unit testing with check offers several advantages over conventional IO testing including the ability to use assertions, parametrize test cases, and provide better feedback for students. Consider the example submission below:

bubble_sort.h
#ifndef BUBBLESORT_H
#define BUBBLESORT_H

void bubbleSort(int arr[], int n);

#endif // BUBBLESORT_H
bubble_sort.c
#include "bubble_sort.h"

void bubble_sort(int arr[], int n) {
    int i, j, temp;
    for (i = 0; i < n-1; i++) {
        for (j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                // Swap arr[j] and arr[j+1]
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

In this example, students have been tasked with creating the bubble_sort() function. We can create robust test cases to check this submission using assertions from the check unit testing framework.

unit_test.c
#include <check.h>
#include "bubbleSort.h"

START_TEST(test_bubbleSort_basic) {
    int arr[] = {5, 3, 8, 4, 2};
    int expected[] = {2, 3, 4, 5, 8};
    int n = 5;

    bubbleSort(arr, n);

    for (int i = 0; i < n; i++) {
        ck_assert_int_eq(arr[i], expected[i]);
    }
}
END_TEST

START_TEST(test_bubbleSort_sorted) {
    int arr[] = {1, 2, 3, 4, 5};
    int expected[] = {1, 2, 3, 4, 5};
    int n = 5;

    bubbleSort(arr, n);

    for (int i = 0; i < n; i++) {
        ck_assert_int_eq(arr[i], expected[i]);
    }
}
END_TEST

START_TEST(test_bubbleSort_reverse) {
    int arr[] = {9, 7, 5, 3, 1};
    int expected[] = {1, 3, 5, 7, 9};
    int n = 5;

    bubbleSort(arr, n);

    for (int i = 0; i < n; i++) {
        ck_assert_int_eq(arr[i], expected[i]);
    }
}
END_TEST

START_TEST(test_bubbleSort_duplicates) {
    int arr[] = {4, 1, 3, 4, 2};
    int expected[] = {1, 2, 3, 4, 4};
    int n = 5;

    bubbleSort(arr, n);

    for (int i = 0; i < n; i++) {
        ck_assert_int_eq(arr[i], expected[i]);
    }
}
END_TEST

Suite* bubbleSort_suite(void) {
    Suite *s;
    TCase *tc_core;

    s = suite_create("BubbleSort");

    /* Core test case */
    tc_core = tcase_create("Core");

    tcase_add_test(tc_core, test_bubbleSort_basic);
    tcase_add_test(tc_core, test_bubbleSort_sorted);
    tcase_add_test(tc_core, test_bubbleSort_reverse);
    tcase_add_test(tc_core, test_bubbleSort_duplicates);
    suite_add_tcase(s, tc_core);

    return s;
}

int main(void) {
    int number_failed;
    Suite *s;
    SRunner *sr;

    s = bubbleSort_suite();
    sr = srunner_create(s);

    srunner_set_xml(sr, "report.xml");
    srunner_run_all(sr, CK_NORMAL);
    number_failed = srunner_ntests_failed(sr);
    srunner_free(sr);
    return (number_failed == 0) ? 0 : 1;
}

"cg junitxml" command

We could simply compile and run the test cases to show students a simple pass or fail depending on the exit code of the tests. However, we wouldn't be able to award partial marks for each test case unless we parsed the results of the unit tests. To accomplish that, we can use the cg junitxml command. This command allows us to parse the number of test cases passed and the feedback from failed test cases from any unit test report written in the JUnit XML format.

Running the command cg junitxml --help shows the following information:

This is a parser for generic unit test coverage reports in JUnit XML format.

The parser can take in input multiple JUnit XML files. The final reported score
is an aggregate of all the reports. Skipped test cases do not count towards the
score.

Example use:
    Input to the command (in a file called coverage.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<testsuites id="Calculator suite" name="empty_name" tests="2" failures="1" time="0.001">
  <testsuite id="uuid1" name="Addition and Multiplication" tests="2" failures="1" time="0.001">
    <testcase id="uuid2" name="[1] Check whether the addition function returns the expected result." time="0.001"></testcase>
    <testcase id="uuid2" name="[2] Check whether the multiply function returns the expected result." time="0.001">
      <failure message="expected: 6.0 but was: 5.0" type="ERROR">
ERROR: Expected: 6.0 but was: 5.0
Category: Checking returns - Multiplication
File: /home/codegrade/student/calculator.py
Line: 2
      </failure>
    </testcase>
  </testsuite>
</testsuites>

    You would then call:
cg junitxml coverage.xml

Usage:
  cg junitxml XMLS... [flags]

Flags:
  -h, --help              help for junitxml
      --no-parse-weight   Don't try to parse the weight from titles.
      --no-score          Don't calculate and output the final score based on the results of the input data.
  -o, --output string     The output file to to use. If this is a number it will use that file descriptor. (default "3")

The cg junitxml command works well in combination with the Custom Test block by displaying the results of the parsed JUnit XML report beautifully, making it easy to read and interpret, as shown in the image below.

Unfortunately, the check framework doesn't have a method for automatically outputting JUnit XML. Instead we can use the srunner_set_xml() command to generate an XML report (in check's own format) and use the following script to convert that into the JUnit XML format.

junit_xml.py
#!/usr/bin/env python3
# pylint: disable=missing-module-docstring,missing-function-docstring

import os
import sys
import typing as t
import subprocess
import xml.etree.ElementTree as ET



def _find_and_get_text(el: ET.Element, to_find: str) -> str:
    found = el.find(to_find)
    assert found is not None, (
        'Expected to find %s in %s, but could not find it' % (to_find, el.tag)
    )
    return found.text or ''


def parse_case(el: ET.Element) -> ET.Element:
    case = ET.Element('testcase')
    case.set('classname', _find_and_get_text(el, 'description'))
    case.set('name', _find_and_get_text(el, 'id'))
    # Sometimes the 'duration' attr is -1.
    case.set('time', str(max(0, float(_find_and_get_text(el, 'duration')))))

    state = el.get('result')
    if state is not None and state != 'success':
        message = ET.Element(state)
        message.text = _find_and_get_text(el, 'message')
        case.append(message)

    return case


def parse_suite(el: ET.Element) -> ET.Element:
    cases = [parse_case(c) for c in el.findall('test')]

    suite = ET.Element('testsuite')
    suite.set('name', _find_and_get_text(el, 'title'))
    suite.set('tests', str(len(cases)))
    suite.set(
        'failures',
        str(sum(1 for c in cases if c.find('failure') is not None))
    )
    suite.set(
        'errors', str(sum(1 for c in cases if c.find('error') is not None))
    )
    suite.set('time', str(sum(float(c.get('time') or '0') for c in cases)))
    suite.extend(cases)

    return suite


def parse_tree(el: ET.Element) -> ET.ElementTree:
    assert el.tag == 'testsuites'

    suites = [parse_suite(s) for s in el.findall('suite')]

    tree = ET.Element('testsuites')
    tree.set('time', _find_and_get_text(el, 'duration'))
    tree.set('tests', str(sum(int(s.get('tests') or '0') for s in suites)))
    tree.set(
        'failures', str(sum(int(s.get('failures') or '0') for s in suites))
    )
    tree.set('errors', str(sum(int(s.get('errors') or '0') for s in suites)))
    tree.extend(suites)

    return ET.ElementTree(tree)


def run(
    test: t.List[str]
) -> t.NoReturn:
    """Run a Check suite and parse its results
    """
    status = subprocess.run(test)

    # Remove namespaces in tag names.
    tree = ET.iterparse("report.xml")
    for _, el in tree:
        _, _, el.tag = el.tag.rpartition('}')

    root = tree.root  # type: ignore
    parse_tree(root).write("report.xml")
    sys.exit(status)

if __name__ == '__main__':
    run(sys.argv[1:])

Instructions

  1. In the AutoTest settings, navigate to the Setup tab.

  2. Add an Install GCC block to your setup configuration.

  3. Add a Script block to your setup configuration and install check with the following command.

    sudo apt install check
  4. In the AutoTest settings, navigate to the Tests tab.

  5. Add an Upload Files block. Upload bubble_sort.h, unit_test.c, and junit_xml.py.

  6. Add a Script block. Move all the uploaded files to the student's directory and compile bubble_sort.c with the following commands.

    mv $UPLOADED_FILES/* .
    gcc -o unit_test unit_test.c bubbleSort.c -lcheck -lm -lpthread -lrt -lsubunit
  7. Add a Connect Rubric block and a Custom Test block. Nest the Custom Test block within the Connect Rubric block. Run the unit tests and parse the results with the following commands.

    python3 check.py ./unit_test
    cg junitxml junit.xml
  8. Build and publish your snapshot.

Clang-tidy

"cg comments" command

Simply running clang-tidy would allow us to produce the default command line output. However, this wouldn't be particularly useful for students as they would have to spend time interpreting the output and would have to switch back and forth between the AutoTest output and their code. Instead, we can use the cg comments command to parse the output of clang-tidy and write the comments directly onto our students' code. The cg comments command will highlight each target line according to the severity of the comment and can be read by hovering over the line number with the mouse cursor. The comments are also placed on students' code in the editor, making it a powerful combination.

Instructions

  1. In the AutoTest settings, navigate to the Setup tab.

  2. Add a Script block. Install clang-tidy using the following command.

    sudo apt update
    sudo apt install clang-tidy
  3. In the AutoTest settings, navigate to the Tests tab.

  4. Add a Connect Rubric block and a Custom Test block. Nest the Custom Test block in the Connect Rubric block. Run clang-tidy and parse the output using the following commands.

    clang-tidy bubble_sort.c --quiet --checks=* -- | cg comments \
        '^(?P<file>[^:]*):(?P<line>[\d]+):(?P<column>[\d]+): (?P<severity>\s*\S*): (?P<message>.*)$' \
         --origin clang-tidy \
         --ignore-regex '^\s*$|^[^/].*$'
  5. Build and Publish your snapshot.

is an industry-standard static analysis tool for C, C++, and Objective-C that allows you to diagnose and fix code style violations, interface misuse, and bugs. It is a useful tool for enforcing code styling best practices for beginner programmers.

©️
©️
©️Create your first C assignment
Check
Clang-tidy