How to create an assignment

Tutorial on assignment creation.

We will describe how to create a very simple assignment with dojo.

Here we will build a Hello world! in the C programming language. We will describe how one must modify the default template to have an exercise.

The exercise will be to fill a function to return the Hello world! string. The output of the function will be printed on the standard output. The structure will be provided to the students as well as a very simple Makefile to compile and run the code.

The success or failure of this assignment will be tested by comparing the output of our program with an the expected output (the famous Hello world!).

To build this exercise we need to perform several steps that will be described below in great details.

Empty assignment creation

First create a new assignment with the command

dojo assignment create --name c_hello_world
Please wait while we verify and retrieve data...
ℹ Checking Dojo session: 
    ✔ The session is valid
        ✔ Teaching staff permissions
ℹ Checking Gitlab token: 
    ✔ Read access
    ✔ Write access
✔ Assignment name "c_hello_world" is available
Please wait while we are creating the assignment...
✔ Assignment successfully created
    ℹ Name: c_hello_world
    ℹ Web URL: https://gitedu.hesge.ch/dojo/assignment/c_hello_world
    ℹ HTTP Repo: https://gitedu.hesge.ch/dojo/assignment/c_hello_world.git
    ℹ SSH Repo: ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git

Clone the assignment locally

The assignment is nothing more than a git repository. We just clone it and start modifying it

git clone ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git
Cloning into 'c_hello_world'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 18 (delta 3), reused 18 (delta 3), pack-reused 0
Receiving objects: 100% (18/18), 4.37 KiB | 4.37 MiB/s, done.
Resolving deltas: 100% (3/3), done.

The c_hello_world assignment has now the following structure

tree c_hello_world/
c_hello_world/
├── docker-compose.yml
├── Dockerfile
├── dojo.assignment
└── README.md

Build the development environment

In order to execute a C program we need a working compiler and make. In order to produce a special result file to be parsed by the dojo tool to produce nicely formatted output we will use jq, a command-line json creation/edition/reding tool. Our Dockerfile contains

FROM ubuntu:latest
RUN apt update && apt install gcc make jq -y
COPY . /
ENTRYPOINT ["./run.sh"]

We see that we start by installing the required packages on top of the latest Ubuntu image. We then proceed to copy the complete assignment into the docker container, finall we run a yet to be created run.sh script.

We can test our Dockerfile by running the command

docker compose run --build hello_world
[+] Building 20.5s (8/8) FINISHED                                 
 => [hello_world internal] load build definition from Dockerfile
 => => transferring dockerfile: 134B
 => [hello_world internal] load .dockerignore
 => => transferring context: 2B
 => [hello_world internal] load metadata for docker.io/library/ubuntu:latest
 => CACHED [hello_world 1/3] FROM docker.io/library/ubuntu:latest@sha256:ec050c32e4a6085b423d36ecd025c0d3ff00c38ab93a3d71a460ff1c44fa6d77
 => [hello_world internal] load build context
 => => transferring context: 39.87kB
 => [hello_world 2/3] RUN apt update && apt install gcc make jq -y
 => [hello_world 3/3] COPY . /
 => [hello_world] exporting to image
 => => exporting layers
 => => writing image sha256:4b561113c7123da08206a2cf2642cb4f331670fe44350646437eaa78e44aff3a
 => => naming to docker.io/library/c_hello_world-hello_world
ERRO[0021] error waiting for container:                                   
Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "./run.sh": stat ./run.sh: no such file or directory: unknown

We see an error at the end of the command which is perfectly normal. We did not create the run.sh file et (let's leave that for later).

We then need to focus on the docker-compose.yml file which will take care of all the hard work needed if complex workflows are required. Here it is as simple as possible and should contain

services:
    hello_world:
        container_name: hello_world
        build:
            context: ./
            dockerfile: Dockerfile
        volumes:
            - hello_world_volume:/result # <hello_world_volume> must be the same as below but
              # the name may be arbitrary. The volume is optional but 
            # you can provide it for show details of the execution like
            # tests passed or not or simply any log file you think that 
            # can be useful for the students. If it's present, this volume 
            # must be present in the dojo_assignment.json file under the field
            # "result": {
            #   "volume": "hello_world_volume", 
            #    ...
            # }
volumes:
    hello_world_volume:

In this file, we see the definition of a hello_world_volume this is an arbitrary name and can be changed but it must be coherent in this file and in the dojo_assignment.json file (more on this later configuration file in The dojo configuration file). This (optional) volume is responsible of mounting /result/ directory which will contain all the output generated by our assignment (again the name can be changed). In particular in this director he will be required to create a dojo_assignment.json file that contains at least if the assignment was successfully performed (more on that at the end of the Creating the assignment files section).

The dojo configuration file

The dojo_assignment.json file contains the general configuration of the assignment. The configuration parameters are:

  • the dojoAssignmentVersion which define the version of the dojo assignment file (actually only version 1 is available),
  • the version of you assignment,
  • the immutable field which are files or directories that will be overwritten when the compilation pipeline is run ( even if the student modifies these files the modifications will not be taken into account). Each immutable is defined by:
    • path (required): the path of the immutable file or directory,
    • description (optional): provides a description of the immutable for the students or Dojo interface,
    • isDirectory (optional): true if the immutable is a directory, false otherwise (default),
  • the results which provide information to the Dojo for finding results of the execution. The result field is defined by:
    • container (required): the name of the service in the docker compose file that will be run (his dependencies will be run automatically). In our case it is hello_world,
    • volume (optional): this field (hello_world_volume) corresponds to the volumes field in the docker-compose.yml file.

In its default form dojo_assignment.json file contains

{
    "dojoAssignmentVersion": 1,
    "version"              : 1,
    "immutable"            : [
        {
            "description": "Dockerfile of the unique container",
            "path"       : "Dockerfile",
            "isDirectory": false
        }
    ],
    "result"               : {
        "container": "hello_world",
        "volume"   : "hello_world_volume"
    }
}

This file will be completed in The immutable files section with the files of our assignment that will be created in the next section.

Creating the assignment files

For this assignment we will create the following files with the following content:

  • src/Makefile
CC:=gcc
CFLAGS:=-Wall -Wextra -Wpedantic -fsanitize=address  -Werror -g
LDFLAGS:=-fsanitize=address

hello_world: hello_world.o function.o
  gcc -o $@ $^ $(LDFLAGS)

hello_world.o: function.h

function.o: function.h

run: hello_world
  ./hello_world

clean: 
  rm -f *.o hello_world
  • src/hello_world.c
#include <stdio.h>
#include <stdlib.h>
#include "function.h"

int main()
{
    printf("%s", hello_world());
    return EXIT_SUCCESS;
}
  • src/function.h
#ifndef _FUNCTION_H_
#define _FUNCTION_H_

char *hello_world();

#endif
  • src/function.c
#include "function.h"

char *hello_world() {
    // TODO: Replace 0 here to return "Hello world!"
    return 0;
}
  • src/expected_output.txt
Hello world!

These files will be used to create an executable that will be run by the custom execution script, run.sh, see the Dockerfile in the Build the environment section.

All these files have arbitrary names and it's completely up to the teacher to make a coherent exercise.

The only missing file is run.sh (do not forget to make it executable, chmod +x run.sh) that contains the following code

#!/bin/bash

echo "Starting tests."

GLOBAL_SUCCESS=false

make -C src clean -s
make -C src -s
if [ $? -eq 0 ]; then
    make run -C src > src/output.txt -s
    if [ $? -eq 0 ]; then
        diff --color src/output.txt src/expected_output.txt > result/diff_output.txt
        if [ $? -ne 0 ]; then
            echo "Output is wrong:";
            cat result/diff_output.txt
            exit(1);
        else
            echo "All tests were a complete success"
            GLOBAL_SUCCESS=true
            exit(0);
        fi
        
    else
        echo "Execution failed";
        exit(2);
    fi
else
    echo "Compilation failed."
    exit(3);
fi

We use the exit code to say to Dojo if the exercise was a success (exit code 0) or a failure (all other exit codes). Here one can see the creation of two different files that are located in the result directory:

  • result/results.json
  • result/diff_output.txt The results.json is mandatory to be created and must at least contain the success field must be true or false and determines whether the assignment is a success or a failure. The other files present in the result folder can be retrieved by the students.

In the result directory you can provide the optional result.json file that can contain more details about the tests:

successfulTests?: number;
failedTests?: number;

successfulTestsList?: Array<string>;
failedTestsList?: Array<string>;
  • the successfulTests which provide the number of successfully passed tests,
  • the failedTests which provide the number of failed tests,
  • the successfulTestsList which provide the list (of string) of successfully passed tests,
  • the failedTestsList which provide the list (of string) of failed tests,

To test if everything works according to plan, one can again use the command

docker compose run --build hello_world
[+] Building 1.8s (8/8) FINISHED                                                                                    
 => [hello_world internal] load build definition from Dockerfile
 => => transferring dockerfile: 134B
 => [hello_world internal] load .dockerignore
 => => transferring context: 2B
 => [hello_world internal] load metadata for docker.io/library/ubuntu:latest
 => [hello_world 1/3] FROM docker.io/library/ubuntu:latest@sha256:ec050c32e4a6085b423d36ecd025c0d3ff00c38ab93
 => [hello_world internal] load build context
 => => transferring context: 3.23kB
 => CACHED [hello_world 2/3] RUN apt update && apt install gcc make jq -y
 => [hello_world 3/3] COPY . /
 => [hello_world] exporting to image
 => => exporting layers
 => => writing image sha256:5499aa3aa0b2f2021584dbf46b4b05ab83dc0a5ad4d6c81a3466c56673c3f562
 => => naming to docker.io/library/c_hello_world-hello_world
Starting tests.
Output is wrong:
1c1
< (null)
\ No newline at end of file
---
> Hello world!
\ No newline at end of file

where we see that the execution fails. This will be improved in the future and the actual execution as expected to be run by the students will be added.

The immutable files

There is only one file that should be modified by the students in this example: the function.c file. Therefore we can safely add all the created files in the src except src/function.c which is precisely the file that the student must modify, as well as the run.sh file. The dojo_assignment.json file becomes

{
    "dojoAssignmentVersion": 1,
    "version"              : 1,
    "immutable"            : [
        {
            "description": "Dockerfile of the unique container",
            "path"       : "Dockerfile",
            "isDirectory": false
        },
        {
            "description": "The entry point of the Dockerfile",
            "path"       : "run.sh",
            "isDirectory": false
        },
        {
            "description": "The makefile for compilation/execution purposes",
            "path"       : "src/Makefile",
            "isDirectory": false
        },
        {
            "description": "Entry point of the code",
            "path"       : "src/hello_world.c",
            "isDirectory": false
        },
        {
            "description": "The header file fot the program's assignment",
            "path"       : "src/function.h",
            "isDirectory": false
        },
        {
            "description": "The expected output file for comparing with the actual output",
            "path"       : "src/expected_output.txt",
            "isDirectory": false
        }
    ],
    "result"               : {
        "container": "hello_world",
        "volume"   : "hello_world_volume"
    }
}

The README.md file

The README.md file is used to provide informations on the assignment and the way the teacher wants students to accomplish the assignment and its content is completely free. It is recommended to use the Markdown syntax as the file extension suggests.

In this assignment the README.md file reads

# Hello world!

C'est le premier vrai exercice jamais créé sur le Dojo!

Il sert de tutoriel pour créer un exercice simple en C.

## But

Le but est de faire afficher "Hello world!" à notre programme en C.

Pour ce faire, il faut modifier le fichier `src/function.c` sous la ligne annotée avec `TODO`. Bonne chance!

Validate the assignment

Now that the assignment is ready, we must validate it. You can do it first locally with the command

dojo assignment check
Please wait while we are checking requirements...
    ✔ Docker daemon is running
    ✔ All required files exists
Please wait while we are validating dojo_assignment.json file...
    ✔ dojo_assignment.json file schema is valid
    ✔ Immutable files are valid
Please wait while we are validating docker compose file...
    ✔ Docker compose file structure is valid
    ✔ Docker compose file content is valid
Please wait while we are validating dockerfiles...
    ✔ Docker compose file content is valid
Please wait while we are running the assignment...
    ✔ Docker Compose file run successfully
    ✔ Linked services logs acquired
    ✔ Containers stopped and removed

   ┏━━━━━━━━━━━━━━━━━ Results ━━━━━━━━━━━━━━━━━┓
   ┃                                           ┃
   ┃   Global result : ✅ Success              ┃
   ┃                                           ┃
   ┃   The assignment is ready to be pushed.   ┃
   ┃                                           ┃
   ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Publish the work

Now that the assignment is ready, we must publish it. First add/commit/push all the files needed for your exercise. In this case, it should be:

c_hello_world/
├── docker-compose.yml
├── Dockerfile
├── dojo_assignment.json
├── README.md
├── run.sh
└── src
    ├── expected_output.txt
    ├── function.c
    ├── function.h
    ├── hello_world.c
    └── Makefile

Then one must publish the assignment for the students to be able to perform to get the exercise. A pipeline will be automatically run to check that the assignment is valid (the same as the previous state).

Wait for the successfully completion of this pipeline and then you are ready to publish the assignment with the command:

dojo assignment publish c_hello_world
? Are you sure you want to publish this assignment? Yes
Please wait while we verify and retrieve data...
ℹ Checking Dojo session: 
    ✔ The session is valid
ℹ Checking assignment:
    ℹ c_hello_world
        ✔ The assignment exists
        ✔ You are in the staff of this assignment
Please wait while we publish the assignment...
✔ Assignment c_hello_world successfully published

The assignment is now ready to be performed by students!