Tutorial for creating a course from custom repository

Creating a course

TMC exercises are stored in a git repository. Although we'll cover the basics here, we highly recommend you learn to use git properly, especially if you collaborate with other course instructors. We chose it because it's a supremely useful tool that saves time and frustration even when working alone.

Let's log in as a teacher of some organization.

(screenshot)

Now we can create a new course for our organization.

(screenshot)

From this page we select to create custom course.

(screenshot)

We'll name our course "programming101", and we'll also have to give the address of the git repository where we'll store the exercises. Also we have to define branch. Master branch is good for us.

You need to give a remote GIT repository address. If you have an existing repository, say on GitHub, enter its read-only URL as the remote repo. In this example we'll pretend the remote repo is at file:///home/travis/tmc-example-repo.

(screenshot)

For rest of the creation phases, just click continue. After last phase the course is created successfully and you should see course's main page.

(screenshot)

Now we'll need to add some exercises. To do that, we'll need to clone the remote repository, make our changes and push them back, as usual with git.

Starting from a template

We start by cloning the course template (not to be confused with course templates that exist in TMC webapp), which has some useful scripts. We'll name the template repo as the 'template' remote, since won't be pushing back to the template. The actual course repo will be the 'origin' remote.

$ git clone --origin template git://github.com/testmycode/tmc-course-template.git tmc-courses/prog101
Cloning into 'tmc-courses/prog101'...

$ cd tmc-courses/prog101
$ ls
private
scripts

$ git remote add origin file:///home/travis/tmc-example-repo

Now we'll push everything we got from the template into 'origin'. The -u flag makes the master branch track the master branch in 'origin'. This means that whenever we commit to master and push, it'll push to 'origin' by default (and not to 'template').

$ git push -u origin master
To file:///home/travis/tmc-example-repo
d030836..bcef7a5 master -> master
Branch master set up to track remote branch master from origin.

An exercise is just a NetBeans project with some unit tests and at least the JUnit and edu-test-utils libraries included. We pulled a handy template exercise into private/template_project from the course template.

$ cd private/template_project; find .
.
./src
./src/.gitkeep
./lib
./lib/edu-test-utils-0.3.2.jar
./lib/junit-4.10.jar
./.gitignore
./test
./test/.gitkeep
./build.xml
./manifest.mf
./nbproject
./nbproject/project.xml
./nbproject/genfiles.properties
./nbproject/build-impl.xml
./nbproject/project.properties
./nbproject/private
./nbproject/private/config.properties
./nbproject/private/private.properties

It's not advised to copy the template directly but instead use scripts/create-project as it automatically also renames the exercise in all relevant NetBeans settings files.

$ scripts/create-project factorial
Copying from private/template_project to factorial
Replacing occurrences of template_project with factorial
in factorial/nbproject/project.properties
in factorial/nbproject/build-impl.xml
in factorial/nbproject/project.xml
in factorial/build.xml

Done!

$ ls
factorial
private
README
scripts

Writing the exercise

Now that we have a blank project, we can finally write our test cases and stub class. Open the project in NetBeans and write the following files.

factorial/src/Factorial.java
public class Factorial {
    public static long factorial(long n) {
        // BEGIN SOLUTION
        long result = 1;
        for (long i = 2; i <= n; ++i) {
            result *= i;
        }
        return result;
        // END SOLUTION
        // STUB: return 0; // Please write your code here
    }
}

factorial/test/FactorialTest.java
import org.junit.Test;
import static org.junit.Assert.*;

public class FactorialTest {

    @Test
    public void miscellaneousFactorials() {
        assertEquals(2, Factorial.factorial(2));
        assertEquals(6, Factorial.factorial(3));
        assertEquals(24, Factorial.factorial(4));
        assertEquals(3628800, Factorial.factorial(10));
    }

    @Test
    public void factorialOfOneIsOne() {
        assertEquals(1, Factorial.factorial(1));
    }

    @Test
    public void factorialOfZeroIsOne() {
        assertEquals(1, Factorial.factorial(0));
    }    
}

We marked solution code we don't want the student to see with special comments. TMC will automatically make two versions of each exercise: a stub version that will be given to students and a solution version that will be shown as the model solution.

The stub version has the parts in begin/end solution tags removed and lines starting with // STUB: uncommented. The stub comments are mainly useful to avoid the missing return statement compilation error. The solution version is effectively the same as the original but has stub and solution comments removed.

The tmc-netbeans-author plugin is recommended as it provides syntax highlighting for all these comments.

Let's commit and push our changes.

$ git status
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# factorial/
#
nothing added to commit but untracked files present (use "git add" to track)

$ git add --verbose factorial
add 'factorial/.gitignore'
add 'factorial/build.xml'
add 'factorial/lib/edu-test-utils-0.3.2.jar'
add 'factorial/lib/junit-4.10.jar'
add 'factorial/manifest.mf'
add 'factorial/nbproject/build-impl.xml'
add 'factorial/nbproject/genfiles.properties'
add 'factorial/nbproject/project.properties'
add 'factorial/nbproject/project.xml'
add 'factorial/src/.gitkeep'
add 'factorial/src/Factorial.java'
add 'factorial/test/.gitkeep'
add 'factorial/test/FactorialTest.java'

$ git commit -m "Added factorial exercise."
[master b408f02] Added factorial exercise.
13 files changed, 1269 insertions(+)
create mode 100644 factorial/.gitignore
create mode 100644 factorial/build.xml
create mode 100644 factorial/lib/edu-test-utils-0.3.2.jar
create mode 100644 factorial/lib/junit-4.10.jar
create mode 100644 factorial/manifest.mf
create mode 100644 factorial/nbproject/build-impl.xml
create mode 100644 factorial/nbproject/genfiles.properties
create mode 100644 factorial/nbproject/project.properties
create mode 100644 factorial/nbproject/project.xml
create mode 100644 factorial/src/.gitkeep
create mode 100644 factorial/src/Factorial.java
create mode 100644 factorial/test/.gitkeep
create mode 100644 factorial/test/FactorialTest.java

$ git push
warning: push.default is unset; its implicit value is changing in
Git 2.0 from 'matching' to 'simple'. To squelch this message
and maintain the current behavior after the default changes, use:

git config --global push.default matching

To squelch this message and adopt the new behavior now, use:

git config --global push.default simple

See 'git help config' and search for 'push.default' for further information.
(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode
'current' instead of 'simple' if you sometimes use older versions of Git)

To file:///home/travis/tmc-example-repo
bcef7a5..b408f02 master -> master

The webapp won't react to changes in the repository before you 'refresh' the course. This may be inconvenient, but it also somewhat protects against mistakes.

(screenshot)

The system now reloads all exercises from the repository and rebuilds its internal caches. Any submissions made to previous exercises will survive this process, even if the exercise is deleted in the push.

(screenshot)

The exercise should now be available for download and accept submissions either from the webapp or from the NetBeans plugin.

Personalized course progression

Notice that course progression can now be configured from the webapp. Configuration from the repository is only loaded initially, and will not change after subsequent refreshes.

TMC can be used to offer courses that may be started at any time. To avoid overwhelming the student and to provide a bit of a reward system, TMC may be configured to unlock and provide for download a set of new exercises only after the student has completed a certain amount of previous exercises. In addition, TMC supports imposing a personal deadline such as "you must complete this exercise withing one week of unlocking it".

Let's assume we have the following exercise directories. As per TMC's naming conventions, these define the exercises named part01-first and so forth.

To define when an exercise is unlocked, one must write a metadata.yml for the exercise or its parent directory. Let's write the following into part02/metadata.yml as an example. It will define that the exercises under part02 will only become visible after the student has gotten 80% of all possible points under part01, including all possible points from part01-first. It also says that the exercise shall not unlock before the 12th of June.

unlocked_after:
  - 80% of part01
  - 100% of part01-first
  - 2012-06-12

Now the IDE plugin will offer part02's exercises for download as soon as all the unlock conditions are met. Before that, the student will not see these exercises anywhere in TMC.

TMC remembers when an exercise was first unlocked for a student. This makes it possible to set personalized deadlines. For instance, the following metadata.yml definition sets the deadline for the exercise(s) it applies to to be one week after the student has unlocked it, but no later than the 12th of July.

deadline:
  - unlock + 1 week
  - 2012-07-12

If an unlock + ... deadline definition is present, exercises will not unlock automatically. Instead, the IDE plugin prompts the user to unlock and download the next set of exercises. The user may only unlock all possible unlockable exercises at once, not one at a time. Students that do not wish to use an IDE may unlock exercises through the web application.

The full syntax of unlock conditions and deadlines is documented in the metadata.yml reference.

Reference

Special comments

Solution code can be demarcated with special comments so it will be removed from the version the student gets (the "stub" version).

public class Example {
    public int sampleMethod(int a, int b) {
        // BEGIN SOLUTION
        // The student will not see this implementation nor this comment
        // before the model solution is published.
        return a * (b + 3);
        // END SOLUTION
        // STUB: return 0;
    }
}
// SOLUTION FILE
// This file will not be present in the exercise given to the student
// but will be part of the model solution.
public class AnotherExample {
    // ...
}

Analogous XML comments will work for .xml and .jsp files:

The tmc-netbeans-author plugin is recommended as it provides syntax highlighting for the special comments (though XML highlighting is not yet supported).

Configuration files

TMC can be configured by placing certain files in certain directories. There are four kinds of configuration files as listed below, but mostly you only need metadata.yml files.

metadata.yml

metadata.yml may be placed in an exercise directory or any other directory, which will cause it to be applied to all exercises below that directory. You may also specify defaults in a parent directory's metadata.yml and override them selectively in subdirectories.

metadata.yml may contain directives as in the following example:

hidden: false
unlocked_after: 80% of exercise1
deadline: 2010-02-15
publish_time: 2010-02-01
returnable: false
gdocs_sheet: week1
solution_visible_after: 2010-02-15
runtime_options: ["-Xss8M"]
valgrind_strategy: fail
code_review_requests_enabled: false

The unlocked_after and deadline directives may take one or more conditions, the forms of which are shown in the following example.

unlocked_after:
  - exercise foo
  - point foo
  - points foo bar
  - 50% of some-exercise
  - 50% of some-exercise-group
  - 3 points in some-exercise
  - 3 points in some-exercise-group
  - 1 exercise in some-exercise-group
  - 2012-06-12
deadline:
  - unlock + 1 day
  - 2012-07-12

If there are multiple unlock conditions, then all of them must be met for the exercise to be unlocked. If there are multiple deadline definitions, then the earliest prevails.

The full syntax of an unlocked_after condition is as follows.

The full syntax of a deadline setting is as follows.

.tmcproject.yml

An exercise directory may contain a .tmcproject.yml. It currently supports one setting, extra_student_files, which is a list of (relative paths to) files to be treated roughly as if they were in src/. It can be used e.g. to mark test files as meant to be implemented by students.

Example:

extra_student_files:
  - test/StudentTest.java

This causes test/StudentTest.java to be shown as part of the solution. Also, the NetBeans plugin would not overwrite the file when the exercise is updated.

.tmcrc

A shell script named .tmcrc may be put in a project directory. It is run (actually, sourced) in the sandbox just before the test suite is run. Use it e.g. to start Xvfb for GUI tests or set environment variables. Note that it is NOT run on the student's machine.

course_options.yml

course_options.yml has to be in the root directory of the repository and may contain directives as in the following example:

  hidden: true
  hide_after: 2010-02-15
  hidden_if_registered_after: 2010-01-01
  locked_exercise_points_visible: false
  formal_name: "Object Oriented Programming – part 1"
  certificate_downloadable: true
  certificate_unlock_spec: 80% of week1
  

If there are multiple unlock conditions, then all of them must be met for the exercise to be unlocked.

The full syntax of an certificate_unlocked_spec condition is as follows.

.tmcnosubmit & .tmcnosnapshot

If .tmcnosubmit is placed in a directory, that directory's contents (recursively) will never be submitted to the server nor to snapshots. The file .tmcnosnapshot does the same but only for snapshots. Note that these files are not necessary for directories like lib/ and test/, which are ordinarily not submitted anyway.

Course-specific configuration

It is sometimes useful to keep multiple courses in the same repository and have the courses only differ in configuration. This can be accomplished by specifying course-specific configuration in a 'courses' section, like this:

courses:
  my-course-no-deadline:
    deadline: null
deadline: 2010-02-15

The 'courses' section works in both metadata.yml and course_options.yml files.

A course-specific option always overrides a general option in the same file.

Subdirectory options are applied after course-specific options in the parent directory have been applied. That means that if parent-dir/metadata.yml specifies a course-specific deadline, parent-dir/subdir/metadata.yml can still override it with a general non-course-specific setting.

ProTips