Skip to content
Snippets Groups Projects

Adds a first version of the wiki documentation

Merged orestis.malaspin requested to merge add_wiki_doc into main
1 file
+ 414
0
Compare changes
  • Side-by-side
  • Inline
+ 414
0
 
# How to create an assignment
 
 
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
 
```bash
 
$ dojo assignment create --name c_hello_world
 
```
 
```console
 
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
 
```bash
 
$ git clone ssh://git@ssh.hesge.ch:10572/dojo/assignment/c_hello_world.git
 
```
 
```console
 
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
 
```bash
 
$ tree c_hello_world/
 
```
 
```console
 
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
 
```dockerfile
 
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
 
```bash
 
$ docker compose run --build hello_world
 
```
 
```console
 
[+] 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
 
```yml
 
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. 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](#the-dojo-configuration-file)).
 
This 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 e 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](#creating-the-assignment-files) section).
 
 
## The dojo configuration file
 
 
The `dojo_assignment.json` file contains the general configuration of the assignment.
 
The important configuration parameters are:
 
- the *immutable files* which are files 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),
 
- the *results* which is the volume corresponding to the `docker-compose.yml` file.
 
 
In its default form `dojo_assignment.json` file contains
 
```json
 
{
 
"dojoAssignmentVersion": 1,
 
"version": 1,
 
"immutable": [
 
{
 
"description": "Dockerfile of the unique container",
 
"path": "Dockerfile",
 
"isDirectory": false
 
}
 
],
 
"result": {
 
"container": "hello_world",
 
"volume": "hello_world_volume"
 
}
 
}
 
```
 
Here we see only one immutable file which is the `Dockerfile` (the `isDirectory` field is `false` but it is possible to make
 
complete directories immutable) and we see that:
 
* the value of the `container` field (`hello_world`) corresponds to
 
the value of the `container_name` field in the `docker-compose.yml` file,
 
* the value of the `volume` field (`hello_world_volume`) corresponds to the `volumes` field in the `docker-compose.yml` file.
 
 
This file will be completed in [The immutable files](#the-immutable-files) sectino with the files of our assignment that will be
 
created in the [next section](#creating-the-assignment-files).
 
 
## Creating the assignment files
 
 
For this assignment we will create the following files with the following content:
 
 
- `src/Makefile`
 
```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`
 
```c
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include "function.h"
 
 
int main()
 
{
 
printf("%s", hello_world());
 
return EXIT_SUCCESS;
 
}
 
```
 
- `src/function.h`
 
```c
 
#ifndef _FUNCTION_H_
 
#define _FUNCTION_H_
 
 
char *hello_world();
 
 
#endif
 
```
 
- `src/function.c`
 
```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](#build-the-development-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
 
```bash
 
#!/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
 
else
 
echo "All tests were a complete success"
 
GLOBAL_SUCCESS=true
 
fi
 
 
else
 
echo "Execution failed";
 
fi
 
else
 
echo "Compilation failed."
 
fi
 
 
jq --null-input --arg success $GLOBAL_SUCCESS \
 
'{"success": $success | test("true")}' > /result/results.json
 
```
 
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.
 
 
To test if everything works according to plan, one can again use the command
 
```bash
 
$ docker compose run --build hello_world
 
```
 
```console
 
[+] 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
 
```json
 
{
 
"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
 
```markdown
 
# 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!
 
```
 
 
## 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:
 
```console
 
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.
 
 
```bash
 
$ dojo assignment publish c_hello_world
 
```
 
```console
 
? 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!
Loading