diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..dc3b136482a1f385b4023be360543f3d1e874bfc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+LSINF1252/
+venv/
\ No newline at end of file
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/__pycache__/task_common.cpython-38.pyc b/__pycache__/task_common.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ad8ee7ac5894589d3e36a4ce2c718dd76fb65eaa
Binary files /dev/null and b/__pycache__/task_common.cpython-38.pyc differ
diff --git a/run_tests.py b/run_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..788263b68786f680f3c3bbd3f457b23341f0e41b
--- /dev/null
+++ b/run_tests.py
@@ -0,0 +1,11 @@
+import unittest
+from tests.test_task_data import TaskDataTestCase
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(TaskDataTestCase('test_task_dir_to_TaskData'))
+    return suite
+
+if __name__ == '__main__':
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
\ No newline at end of file
diff --git a/task_common.py b/task_common.py
index 49a156fd7b65c65c021f757e68501759081194b3..4859cfcb44663fe1322f4c996b0f1cf71e83fd96 100644
--- a/task_common.py
+++ b/task_common.py
@@ -2,13 +2,13 @@ from typing import Optional, List, Callable, Any
 from pathlib import Path
 import yaml
 import os
-import subprocess, shlex, re, os, yaml
-from inginious import feedback, rst, input
 import logging
 import subprocess, shlex, re, os, yaml
-LOG_FMT = '%(asctime)s %(message)s'
-logging.basicConfig(format=FORMAT)
+from dataclasses import dataclass
+from itertools import chain
+logging.basicConfig()
 logger = logging.getLogger('JudgeAPI')
+logger.setLevel('DEBUG')
 """
 Task common, the file defining the API that will be used for the C judge system.
 
@@ -56,29 +56,28 @@ def task_dir_validate(task_dir_path: Path) -> bool:
         return False
     #list task files
     dir_content = os.listdir(task_dir_path)
-    dir_files = {str(f).lower():f for f in dir_content if os.path.isfile(f)}
-    dir_subdirs = {str(d).lower():d for d in dir_content if os.path.isdir(d)}
+    dir_files = {str(f).lower():f for f in dir_content if os.path.isfile(os.path.join(task_dir_path, f))}
+    dir_subdirs = {str(d).lower():d for d in dir_content if os.path.isdir(os.path.join(task_dir_path, d))}
 
     #check for mandatory files and subdirectories
     if "task.yaml" not in dir_files and "task.yml" not in dir_files:
-        logger.debug(f"Could not find task.yaml in {str(task_dir_path)}")
+        logger.warning(f"Could not find task.yaml in {str(task_dir_path)}")
         return False
     
     if "run" not in dir_files:
-        logger.debug(f"Could not find run file in {task_dir_path}")
+        logger.warning(f"Could not find run file in {task_dir_path}")
         return False
     
     if "student" not in dir_subdirs:
-        logger.debug(f"Could not find student folder in {task_dir_path}. Maybe the tasks are too simple for using this API ?")
+        logger.warning(f"Could not find student folder in {task_dir_path}. Maybe the tasks are too simple for using this API ?")
         return False
     
     #check the content of the student directory
-
     student_dir = os.path.join(task_dir_path, dir_subdirs['student'])
-    student_dir_content_splitted = [str(c).to_lower().split('.') for c in os.listdir(student_dir)]
-    student_dir_content_ext = [splitted[1] for c in student_dir_content_splitted if len(splitted) > 1]
-    if 'tpl' not in student_dir_content_ext:
-        logger.debug(f"Could not find template file in {str(student_dir)}")
+    student_dir_content_splitted = [str(c).lower().split('.') for c in os.listdir(student_dir)]
+    student_dir_content_ext = [splitted[1:] for splitted in student_dir_content_splitted if len(splitted) > 1]
+    if 'tpl' not in chain(*student_dir_content_ext):
+        logger.warning(f"Could not find template file in {str(student_dir)}")
         return False
 
     logger.debug(f"Validated task directory {str(task_dir_path)}")
@@ -123,18 +122,20 @@ def task_dir_to_TaskData(task_dir_path: Path, build_script: Path=None, lib_dirs:
     
     #discover files and folders
     task_dir_content = {str(content).lower():content for content in os.listdir(task_dir_path)}
-    task_dir_files = {name:path for name, path in task_dir_content.items() if os.path.isfile(path)}
-    task_dir_subdirs = {name:path for name, path in task_dir_content.items() if os.path.isdir(s)(path)}
+    task_dir_files = {name:path for name, path in task_dir_content.items() if os.path.isfile(os.path.join(task_dir_path, path))}
+    task_dir_subdirs = {name:path for name, path in task_dir_content.items() if os.path.isdir(os.path.join(task_dir_path, path))}
 
     #Guess the yaml extension to the task file and load it into a dict
     task_file = task_dir_files['task.yaml'] if 'task.yaml' in task_dir_files else task_dir_files['task.yml']
-    with open(task_file, 'r') as f:
-        TaskData_init_kwargs['task'] = yaml.load(f)
+    with open(os.path.join(task_dir_path, task_file), 'r') as f:
+        TaskData_init_kwargs['task'] = yaml.safe_load(f)
 
+    student_dir_path = os.path.join(task_dir_path, task_dir_content['student'])
+    
     #discover files and folders of the student directory
-    student_dir_content = {str(content).lower():content for content in os.listdir(os.path.join(task_dir_path, task_dir_content['student']))}
-    student_dir_files = {name:path for name, path in student_dir_content.items() if os.path.isfile(path)}
-    student_dir_subdirs = {name:path for name, path in student_dir_content.items() if os.path.isdir(s)(path)}
+    student_dir_content = {str(content).lower():content for content in os.listdir(student_dir_path)}
+    student_dir_files = {name:path for name, path in student_dir_content.items() if os.path.isfile(os.path.join(student_dir_path, path))}
+    student_dir_subdirs = {name:path for name, path in student_dir_content.items() if os.path.isfile(os.path.join(student_dir_path, path))}
 
     #retrieve the template file and the annex files
     annex = []
@@ -155,12 +156,12 @@ def task_dir_to_TaskData(task_dir_path: Path, build_script: Path=None, lib_dirs:
     return TaskData(**TaskData_init_kwargs)
 
 
-def student_code_generate(task: TaskData):
+def student_code_generate(task: TaskData, generator: Callable[[str, str], None]):
     filename = task.template.name
     filename_components = filename.split(".")
     extension = filename_components[1] if len(filename_components) > 2 else ""
     output_name = f"{filename_components[0]}{extension}"
-    input.parse_template(filename, output_name)
+    generator(filename, output_name)
     task.student_code = output_name
     logger.debug(f"Generated student code: {output_name}")
 
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/__pycache__/__init__.cpython-38.pyc b/tests/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f908614706e23bedbec580533cbd7c8575d1e496
Binary files /dev/null and b/tests/__pycache__/__init__.cpython-38.pyc differ
diff --git a/tests/__pycache__/test_task_data.cpython-38.pyc b/tests/__pycache__/test_task_data.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..21f1a7a49717f502c4d89f3213595c6441483bb1
Binary files /dev/null and b/tests/__pycache__/test_task_data.cpython-38.pyc differ
diff --git a/tests/data/tasks/strcpy/run b/tests/data/tasks/strcpy/run
new file mode 100644
index 0000000000000000000000000000000000000000..091a7605cbcb1b0ade49704c19fc9802b8d16010
--- /dev/null
+++ b/tests/data/tasks/strcpy/run
@@ -0,0 +1,145 @@
+#!/bin/python3
+
+# Script d'interface entre INGInious et des tests unitaires écrits à l'aide de CUnit
+# Auteurs : Mathieu Xhonneux, Anthony Gégo
+# Licence : GPLv3
+
+import subprocess, shlex, re, os, yaml
+from inginious import feedback, rst, input
+
+# Switch working directory to student/
+os.chdir("student")
+
+# Fetch and save the student code into a file for compilation
+input.parse_template("student_code.c.tpl", "student_code.c")
+
+# Compilation
+p = subprocess.Popen(shlex.split("make"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
+make_output = p.communicate()[0].decode('utf-8')
+# If compilation failed, exit with "failed" result
+if p.returncode:
+    feedback.set_tag("not_compile", True)
+    feedback.set_global_result("failed")
+    feedback.set_global_feedback("La compilation de votre code a échoué. Voici le message de sortie de la commande ``make`` :")
+    feedback.set_global_feedback(rst.get_codeblock('', make_output), True)
+    exit(0)
+else:
+    # Cppcheck
+    p = subprocess.Popen(shlex.split("make check"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
+    cppcheck_output = p.communicate()[0].decode('utf-8')
+    if p.returncode:
+        feedback.set_tag("cppcheck", True)
+        feedback.set_global_result("failed")
+        feedback.set_global_feedback("La compilation de votre code avec ``cppcheck`` a échoué. Voici le message de sortie de la commande ``make check`` :")
+        feedback.set_global_feedback(rst.get_codeblock('', cppcheck_output), True)
+        exit(0)
+    else:
+        feedback.set_global_result("success")
+        feedback.set_global_feedback("- Votre code compile.\n")
+
+# Parse banned functions
+try:
+    banned_funcs = re.findall("BAN_FUNCS\(([a-zA-Z0-9_, ]*)\)", open('tests.c').read())[-1].replace(" ", "").split(",")
+    banned_funcs = list(filter(None, banned_funcs))
+except IndexError:
+    banned_funcs = []
+
+if banned_funcs:
+    p = subprocess.Popen(shlex.split("readelf -s student_code.o"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
+    readelf_output = p.communicate()[0].decode('utf-8')
+    for func in banned_funcs:
+        if re.search("UND {}\n".format(func), readelf_output):
+            feedback.set_tag("banned_funcs", True)
+            feedback.set_global_result("failed")
+            feedback.set_global_feedback("Vous utilisez la fonction {}, qui n'est pas autorisée.".format(func))
+            exit(0)
+
+
+# Remove source files
+subprocess.run("rm -rf *.c *.tpl *.h *.o", shell=True)
+
+LANG = input.get_input('@lang')
+
+# Run the code in a parallel container
+p = subprocess.Popen(shlex.split("run_student --time 20 --hard-time 60 ./tests LANGUAGE={}".format(LANG)), stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
+o, e = p.communicate()
+print(o.decode("utf-8"))
+# If run failed, exit with "failed" result
+if p.returncode:
+    feedback.set_global_result("failed")
+    if p.returncode == 256-8:
+        montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur. Le signal SIGFPE a été envoyé : *Floating Point Exception*.")
+        feedback.set_tag("sigfpe", True)
+    elif p.returncode == 256-11:
+        montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur. Le signal SIGSEGV a été envoyé : *Segmentation Fault*.")
+    elif p.returncode == 252:
+        montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a tenté d'allouer plus de mémoire que disponible.")
+        feedback.set_tag("memory", True)
+    elif p.returncode == 253:
+        montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a pris trop de temps pour s'exécuter.")
+    else:
+        montest_output = rst.get_admonition("warning", "**Erreur d'exécution**", "Votre code a produit une erreur.")
+    feedback.set_global_feedback(rst.indent_block(2, montest_output, " "), True)
+    exit(0)
+#elif run_output:   
+#    feedback.set_global_feedback("- Sortie de votre méthode de test:\n" + rst.indent_block(2, rst.get_codeblock('', run_output), " "), True)
+
+# Comment to run the tests
+#feedback.set_global_feedback("- **Cette note n'est pas finale.** Une série de tests sera exécutée sur votre code après l'examen.\n", True)
+#exit(0)
+
+# Fetch CUnit test results
+results_raw = [r.split('#') for r in open('results.txt').read().splitlines()]
+results = [{'pid':r[0], 'code':r[1], 'desc':r[2], 'weight':int(r[3]), 'tags': r[4].split(","), 'info_msgs':r[5:]} for r in results_raw]
+
+
+# Produce feedback
+if all([r['code'] == 'SUCCESS' for r in results]):
+    feedback.set_global_feedback("\n- Votre code a passé tous les tests.", True)
+else:
+    feedback.set_global_feedback("\n- Il y a des erreurs dans votre solution.", True)
+
+score = 0
+total = 0
+tests_result = {}
+
+for test in results:
+    total += test['weight']
+    for tag in test['tags']:
+        if tag != "":
+            feedback.set_tag(tag, True)
+    if test['code'] == 'SUCCESS':
+        score += test['weight']
+        feedback.set_problem_feedback("* {desc}\n\n  => réussi ({weight}/{weight}) pts)\n\n".format(**test)+("  Info: {}\n\n".format(" — ".join(test['info_msgs'])) if test['info_msgs'] else '\n'),
+                test['pid'], True)
+        tests_result[test['pid']] = True if tests_result.get(test['pid'], True) else False
+    else:
+        feedback.set_problem_feedback("* {desc}\n\n  => échoué (0/{weight}) pts)\n\n".format(**test)+("  Info: {}\n\n".format(" — ".join(test['info_msgs'])) if test['info_msgs'] else '\n'),
+                test['pid'], True)
+        tests_result[test['pid']] = False
+        
+for pid, result in tests_result.items():
+    if result:
+        feedback.set_problem_result("success", pid)
+    else:
+        feedback.set_problem_result("failed", pid)
+
+with open("../task.yaml", 'r') as stream:
+    problems = yaml.load(stream)['problems']
+    
+    for name, meta in problems.items():
+        if meta['type'] == 'match':
+            answer = input.get_input(name)
+            if answer == meta['answer']:
+                feedback.set_problem_result("success", name)
+                feedback.set_problem_feedback("Votre réponse est correcte. (1/1 pts)", name, True)
+                score += 1
+            else:
+                feedback.set_problem_result("failed", name)
+                feedback.set_problem_feedback("Votre réponse est incorrecte. (0/1 pts)", name, True)
+
+            total += 1
+
+score = 100*score/(total if not total == 0 else 1)
+feedback.set_grade(score)
+feedback.set_global_result("success" if score >= 50 else "failed")
diff --git a/tests/data/tasks/strcpy/solutions/sol.c b/tests/data/tasks/strcpy/solutions/sol.c
new file mode 100644
index 0000000000000000000000000000000000000000..c86c537c1d3e82d7c3e65649095d98a80416e88c
--- /dev/null
+++ b/tests/data/tasks/strcpy/solutions/sol.c
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+
+char *buf_strcpy(const char *src){
+  int len = strlen(src) + 1, i;
+  char * ret = (char*) malloc(sizeof(char)*len);
+  if (!ret)
+    return NULL;
+
+  for(i = 0; i < len; i++)
+    *(ret+i) = *(src+i);
+
+  return ret;
+}
diff --git a/tests/data/tasks/strcpy/student/CTester/CTester.c b/tests/data/tasks/strcpy/student/CTester/CTester.c
new file mode 100644
index 0000000000000000000000000000000000000000..f4a85fae91e551cd602eb09544575dcb86e95fd6
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/CTester.c
@@ -0,0 +1,353 @@
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/time.h>
+
+#include <CUnit/CUnit.h>
+#include <CUnit/Basic.h>
+#include <CUnit/Automated.h>
+
+#include <libintl.h>
+#include <locale.h>
+#define _(STRING) gettext(STRING)
+#include <dlfcn.h>
+#include <malloc.h>
+
+#include "wrap.h"
+
+#define TAGS_NB_MAX 20
+#define TAGS_LEN_MAX 30
+
+extern bool wrap_monitoring;
+extern struct wrap_stats_t stats;
+extern struct wrap_monitor_t monitored;
+extern struct wrap_fail_t failures;
+extern struct wrap_log_t logs;
+
+extern sigjmp_buf segv_jmp;
+
+int true_stderr;
+int true_stdout;
+int pipe_stderr[2], usr_pipe_stderr[2];
+int pipe_stdout[2], usr_pipe_stdout[2];
+extern int stdout_cpy, stderr_cpy;
+struct itimerval it_val;
+
+CU_pSuite pSuite = NULL;
+
+
+struct info_msg {
+    char *msg;
+    struct info_msg *next;
+};
+
+struct __test_metadata {
+    struct info_msg *fifo_in;
+    struct info_msg *fifo_out;
+    char problem[140];
+    char descr[250];
+    unsigned int weight;
+    unsigned char nb_tags;
+    char tags[TAGS_NB_MAX][TAGS_LEN_MAX];
+    int err;
+} test_metadata;
+
+
+void set_test_metadata(char *problem, char *descr, unsigned int weight)
+{
+    test_metadata.weight = weight;
+    strncpy(test_metadata.problem, problem, sizeof(test_metadata.problem));
+    strncpy(test_metadata.descr, descr, sizeof(test_metadata.descr));
+}
+
+void push_info_msg(char *msg)
+{
+    if (strstr(msg, "#") != NULL || strstr(msg, "\n") != NULL) {
+        test_metadata.err = EINVAL;
+        return;
+    }
+
+    struct info_msg *item = malloc(sizeof(struct info_msg));
+    if (item == NULL)
+        test_metadata.err = ENOMEM;
+
+    item->next = NULL;
+    item->msg = malloc(strlen(msg) + 1);
+    if (item->msg == NULL)
+        test_metadata.err = ENOMEM;
+
+    strcpy(item->msg, msg);
+    if (test_metadata.fifo_in == NULL && test_metadata.fifo_out == NULL) {
+        test_metadata.fifo_in = item;
+        test_metadata.fifo_out = item;
+    } else {
+        test_metadata.fifo_out->next = item;
+        test_metadata.fifo_out = item;
+    }
+}
+
+void set_tag(char *tag)
+{
+    int i=0;
+    while (tag[i] != '\0' && i < TAGS_LEN_MAX) {
+        if (!isalnum(tag[i]) && tag[i] != '-' && tag[i] != '_')
+            return;
+        i++;
+    }
+
+    if (test_metadata.nb_tags < TAGS_NB_MAX)
+        strncpy(test_metadata.tags[test_metadata.nb_tags++], tag, TAGS_LEN_MAX);
+}
+
+void segv_handler(int sig, siginfo_t *unused, void *unused2) {
+    wrap_monitoring = false;
+    push_info_msg(_("Your code produced a segfault."));
+    set_tag("sigsegv");
+    wrap_monitoring = true;
+    siglongjmp(segv_jmp, 1);
+}
+
+void alarm_handler(int sig, siginfo_t *unused, void *unused2)
+{
+    wrap_monitoring = false;
+    push_info_msg(_("Your code exceeded the maximal allowed execution time."));
+    set_tag("timeout");
+    wrap_monitoring = true;
+    siglongjmp(segv_jmp, 1);
+}
+
+
+int sandbox_begin()
+{
+    // Start timer
+    it_val.it_value.tv_sec = 2;
+    it_val.it_value.tv_usec = 0;
+    it_val.it_interval.tv_sec = 0;
+    it_val.it_interval.tv_usec = 0;
+    setitimer(ITIMER_REAL, &it_val, NULL);
+
+    // Intercepting stdout and stderr
+    dup2(pipe_stdout[1], STDOUT_FILENO);
+    dup2(pipe_stderr[1], STDERR_FILENO);
+    // Emptying the user pipes
+    char buf[BUFSIZ];
+    int n;
+    while ((n = read(usr_pipe_stdout[0], buf, BUFSIZ)) > 0);
+    while ((n = read(usr_pipe_stderr[0], buf, BUFSIZ)) > 0);
+
+    wrap_monitoring = true;
+    return 0;
+}
+
+void sandbox_fail()
+{
+    CU_FAIL("Segfault or timeout");
+}
+
+void sandbox_end()
+{
+    wrap_monitoring = false;
+
+    // Remapping stderr to the orignal one ...
+    dup2(true_stdout, STDOUT_FILENO); // TODO
+    dup2(true_stderr, STDERR_FILENO);
+
+    // ... and looking for a double free warning
+    char buf[BUFSIZ];
+    int n;
+    while ((n = read(pipe_stdout[0], buf, BUFSIZ)) > 0) {
+        write(usr_pipe_stdout[1], buf, n);
+        write(STDOUT_FILENO, buf, n);
+    }
+
+
+    while ((n = read(pipe_stderr[0], buf, BUFSIZ)) > 0) {
+        if (strstr(buf, "double free or corruption") != NULL) {
+            CU_FAIL("Double free or corruption");
+            push_info_msg(_("Your code produced a double free."));
+            set_tag("double_free");
+        }
+        write(usr_pipe_stderr[1], buf, n);
+        write(STDERR_FILENO, buf, n);
+    }
+
+
+    it_val.it_value.tv_sec = 0;
+    it_val.it_value.tv_usec = 0;
+    it_val.it_interval.tv_sec = 0;
+    it_val.it_interval.tv_usec = 0;
+    setitimer(ITIMER_REAL, &it_val, NULL);
+}
+
+
+int init_suite1(void)
+{
+    return 0;
+}
+
+int clean_suite1(void)
+{
+    return 0;
+}
+
+void start_test()
+{
+    bzero(&test_metadata,sizeof(test_metadata));
+    bzero(&stats,sizeof(stats));
+    bzero(&failures,sizeof(failures));
+    bzero(&monitored,sizeof(monitored));
+    bzero(&logs,sizeof(logs));
+}
+
+int __real_exit(int status);
+int __wrap_exit(int status){
+    return status;
+}
+
+int run_tests(int argc, char *argv[], void *tests[], int nb_tests) {
+    for (int i=1; i < argc; i++) {
+        if (!strncmp(argv[i], "LANGUAGE=", 9))
+                putenv(argv[i]);
+    }
+    setlocale (LC_ALL, "");
+    bindtextdomain("tests", getenv("PWD"));
+    bind_textdomain_codeset("messages", "UTF-8");
+    textdomain("tests");
+
+    mallopt(M_PERTURB, 142); // newly allocated memory with malloc will be set to ~142
+
+    // Code for detecting properly double free errors
+    mallopt(M_CHECK_ACTION, 1); // don't abort if double free
+    true_stderr = dup(STDERR_FILENO); // preparing a non-blocking pipe for stderr
+    true_stdout = dup(STDOUT_FILENO); // preparing a non-blocking pipe for stderr
+
+    int *pipes[] = {pipe_stderr, pipe_stdout, usr_pipe_stdout, usr_pipe_stderr};
+    for(int i=0; i < 4; i++) { // Configuring pipes to be non-blocking
+        pipe(pipes[i]);
+        int flags = fcntl(pipes[i][0], F_GETFL, 0);
+        fcntl(pipes[i][0], F_SETFL, flags | O_NONBLOCK);
+    }
+    stdout_cpy = usr_pipe_stdout[0];
+    stderr_cpy = usr_pipe_stderr[0];
+
+    putenv("LIBC_FATAL_STDERR_=2"); // needed otherwise libc doesn't print to program's stderr
+
+    /* make sure that we catch segmentation faults */
+    struct sigaction sa;
+
+    memset(&sa, 0, sizeof(sigaction));
+    sigemptyset(&sa.sa_mask);
+    static char stack[SIGSTKSZ];
+    stack_t ss = {
+        .ss_size = SIGSTKSZ,
+        .ss_sp = stack,
+    };
+
+    sa.sa_flags     = SA_NODEFER|SA_ONSTACK|SA_RESTART;
+    sa.sa_sigaction = segv_handler;
+    sigaltstack(&ss, 0);
+    sigfillset(&sa.sa_mask);
+    int ret = sigaction(SIGSEGV, &sa, NULL);
+    if (ret)
+        return ret;
+    sa.sa_sigaction = alarm_handler;
+    ret = sigaction(SIGALRM, &sa, NULL);
+    if (ret)
+        return ret;
+
+
+    /* Output file containing succeeded / failed tests */
+    FILE* f_out = fopen("results.txt", "w");
+    if (!f_out)
+        return -ENOENT;
+
+
+    /* initialize the CUnit test registry */
+    if (CUE_SUCCESS != CU_initialize_registry())
+        return CU_get_error();
+
+    /* add a suite to the registry */
+    pSuite = CU_add_suite("Suite_1", init_suite1, clean_suite1);
+    if (NULL == pSuite) {
+        CU_cleanup_registry();
+        return CU_get_error();
+    }
+
+    for (int i=0; i < nb_tests; i++) {
+        Dl_info  DlInfo;
+        if (dladdr(tests[i], &DlInfo) == 0)
+            return -EFAULT;
+
+        CU_pTest pTest;
+        if ((pTest = CU_add_test(pSuite, DlInfo.dli_sname, tests[i])) == NULL) {
+                CU_cleanup_registry();
+                return CU_get_error();
+        }
+
+        printf("\n==== Results for test %s : ====\n", DlInfo.dli_sname);
+
+        start_test();
+
+        if (CU_basic_run_test(pSuite,pTest) != CUE_SUCCESS)
+            return CU_get_error();
+
+        if (test_metadata.err)
+            return test_metadata.err;
+
+        int nb = CU_get_number_of_tests_failed();
+        if (nb > 0)
+            ret = fprintf(f_out, "%s#FAIL#%s#%d#", test_metadata.problem,
+                    test_metadata.descr, test_metadata.weight);
+
+        else
+            ret = fprintf(f_out, "%s#SUCCESS#%s#%d#", test_metadata.problem,
+                    test_metadata.descr, test_metadata.weight);
+        if (ret < 0)
+            return ret;
+
+        for(int i=0; i < test_metadata.nb_tags; i++) {
+            ret = fprintf(f_out, "%s", test_metadata.tags[i]);
+            if (ret < 0)
+                return ret;
+
+            if (i != test_metadata.nb_tags - 1) {
+                ret = fprintf(f_out, ",");
+                if (ret < 0)
+                    return ret;
+            }
+        }
+
+
+        while (test_metadata.fifo_in != NULL) {
+            struct info_msg *head = test_metadata.fifo_in;
+            ret = fprintf(f_out, "#%s", head->msg);
+
+            if (head->msg != NULL)
+                free(head->msg);
+            test_metadata.fifo_in = head->next;
+            free(head);
+
+            if (ret < 0)
+                return ret;
+        }
+
+        test_metadata.fifo_out = NULL;
+        ret = fprintf(f_out, "\n");
+        if (ret < 0)
+            return ret;
+
+    }
+
+    fclose(f_out);
+
+    /* Run all tests using the CUnit Basic interface */
+    //CU_basic_run_tests();
+    //CU_automated_run_tests();
+    CU_cleanup_registry();
+    return CU_get_error();
+}
diff --git a/tests/data/tasks/strcpy/student/CTester/CTester.h b/tests/data/tasks/strcpy/student/CTester/CTester.h
new file mode 100644
index 0000000000000000000000000000000000000000..4448aded8abc4f241f189a1a617df2ba28aecd78
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/CTester.h
@@ -0,0 +1,43 @@
+#include <sys/mman.h>
+#include <CUnit/CUnit.h>
+#include <setjmp.h>
+
+#include "wrap.h"
+#include "trap.h"
+
+#include <libintl.h>
+#include <locale.h>
+#define _(STRING) gettext(STRING)
+
+#define RUN(...) void *ptr_tests[] = {__VA_ARGS__}; return run_tests(argc, argv, ptr_tests, sizeof(ptr_tests)/sizeof(void*))
+#define BAN_FUNCS(...) 
+#define SANDBOX_BEGIN sandbox_begin(); if(sigsetjmp(segv_jmp,1) == 0) { (void)0
+#define SANDBOX_END } else { \
+                             sandbox_fail(); \
+                           } \
+                           sandbox_end()
+
+
+// Hidden by macros
+int run_tests(int argc, char *argv[], void *tests[], int nb_tests);
+int sandbox_begin();
+void sandbox_fail();
+void sandbox_end();
+
+// To use inside tests
+void set_test_metadata(char *problem, char *descr, unsigned int weight);
+void push_info_msg(char *msg);
+void set_tag(char *tag);
+
+
+// Set to true to enable monitoring features
+bool wrap_monitoring = false;
+
+struct wrap_stats_t stats;
+struct wrap_monitor_t monitored;
+struct wrap_fail_t failures;
+struct wrap_log_t logs;
+
+int stdout_cpy, stderr_cpy;
+
+sigjmp_buf segv_jmp;
diff --git a/tests/data/tasks/strcpy/student/CTester/trap.c b/tests/data/tasks/strcpy/student/CTester/trap.c
new file mode 100644
index 0000000000000000000000000000000000000000..2b36c07dc2da24dc15521999d650a76bf8f099bc
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/trap.c
@@ -0,0 +1,37 @@
+#include <sys/mman.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "trap.h"
+
+void *trap_buffer(size_t size, int type, int flags, void *data)
+{
+    int nb_pages = (size / getpagesize()) + 2;
+    void *ptr = mmap(NULL, getpagesize() * nb_pages, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+    if (ptr == MAP_FAILED) {
+        return NULL;
+    }
+
+    void *buf_start;
+    if (type == TRAP_LEFT) {
+        buf_start = ptr + getpagesize();
+        mprotect(ptr, getpagesize(), PROT_NONE);
+    } else if (type == TRAP_RIGHT) {
+        buf_start = ptr + (nb_pages - 1) * getpagesize() - size;
+        mprotect(ptr + (nb_pages - 1)*getpagesize(), getpagesize(), PROT_NONE);
+    } else {
+        return NULL;
+    }
+
+    if (data != NULL)
+        memcpy(buf_start, data, size);
+
+    mprotect(buf_start, size, flags);
+
+    return buf_start;
+}
+
+int free_trap(void *ptr, size_t size)
+{
+    return munmap(ptr, size);
+}
diff --git a/tests/data/tasks/strcpy/student/CTester/trap.h b/tests/data/tasks/strcpy/student/CTester/trap.h
new file mode 100644
index 0000000000000000000000000000000000000000..fa29af4944997e544703905217c8694b9ae004be
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/trap.h
@@ -0,0 +1,8 @@
+enum {
+    TRAP_LEFT,
+    TRAP_RIGHT
+};
+
+
+void *trap_buffer(size_t size, int type, int flags, void *data);
+int free_trap(void *ptr, size_t size);
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap.h b/tests/data/tasks/strcpy/student/CTester/wrap.h
new file mode 100644
index 0000000000000000000000000000000000000000..e62430afad1929c33a361f372cf4d167d1e65569
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap.h
@@ -0,0 +1,163 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <errno.h>
+
+// headers for all stats
+
+#include "wrap_getpid.h"
+#include "wrap_file.h"
+#include "wrap_malloc.h"
+#include "wrap_mutex.h"
+#include "wrap_sleep.h"
+
+// Basic structures for system call wrapper
+// verifies whether the system call needs to be monitored. Each
+// supported system call must be referenced here
+
+// flag must be set to true if the system call must be monitored
+
+struct wrap_monitor_t {
+  bool getpid;
+  bool open;
+  bool creat;
+  bool close;
+  bool read;
+  bool write;
+  bool stat;
+  bool fstat;
+  bool lseek;
+  bool free;
+  bool malloc;
+  bool calloc;
+  bool realloc;
+  bool pthread_mutex_lock;
+  bool pthread_mutex_trylock;
+  bool pthread_mutex_unlock;
+  bool pthread_mutex_init;
+  bool pthread_mutex_destroy;
+  bool sleep;
+};
+
+#define MAX_LOG 1000
+
+// log for specific system calls
+struct malloc_t {
+    int n;
+    struct malloc_elem_t log[MAX_LOG];
+};
+
+struct wrap_log_t {
+  struct malloc_t malloc;
+} ;
+
+
+// failures are defined as a bitmask. If set to zero, the system call
+// never fails. Otherwise, the fail information is shifted to the right
+// and the next system call will fail if its low order bit is set
+// we assume that there are no more than 32 system calls that need
+// fail. This should be sufficient for most inginious exercices.
+// If not, the idea can be extended to support more bits.
+
+#define FAIL_ALWAYS 0b11111111111111111111111111111111
+#define FAIL_FIRST  0b00000000000000000000000000000001 
+#define FAIL_TWICE  0b00000000000000000000000000000011 
+#define FAIL_NEVER  0b00000000000000000000000000000000
+#define FAIL_SECOND 0b00000000000000000000000000000010 
+#define FAIL_THIRD  0b00000000000000000000000000000100
+
+#define FAIL(v) (((v & 0b00000000000000000000000000000001) == 0b00000000000000000000000000000001) )
+#define NEXT(v) (v==FAIL_ALWAYS ? FAIL_ALWAYS : v >> 1)
+struct wrap_fail_t {
+  // bool getpid - this call cannot fail
+  uint32_t open;  // indicates whether next open will fail
+  int open_ret;   // return value if open fails
+  int open_errno; // errno value set if open fails
+
+  uint32_t creat;
+  int creat_ret;
+  int creat_errno;
+
+  uint32_t close;
+  int close_ret;
+  int close_errno;
+
+  uint32_t read;
+  int read_ret;
+  int read_errno;
+
+  uint32_t write;
+  int write_ret;
+  int write_errno;
+
+  uint32_t stat;
+  int stat_ret;
+  int stat_errno;
+
+  uint32_t fstat;
+  int fstat_ret;
+  int fstat_errno;
+
+  uint32_t lseek;
+  int lseek_ret;
+  int lseek_errno;
+
+  uint32_t malloc;
+  void *malloc_ret;
+
+  uint32_t calloc;
+  void *calloc_ret;
+  
+  uint32_t realloc;
+  void *realloc_ret;
+
+  uint32_t free;
+
+  uint32_t pthread_mutex_lock;
+  int pthread_mutex_lock_ret;
+  int pthread_mutex_lock_errno;
+
+  uint32_t pthread_mutex_trylock;
+  int pthread_mutex_trylock_ret;
+  int pthread_mutex_trylock_errno;
+
+  uint32_t pthread_mutex_unlock;
+  int pthread_mutex_unlock_ret;
+  int pthread_mutex_unlock_errno;
+
+  uint32_t pthread_mutex_init;
+  int pthread_mutex_init_ret;
+  int pthread_mutex_init_errno;
+
+  uint32_t pthread_mutex_destroy;
+  int pthread_mutex_destroy_ret;
+  int pthread_mutex_destroy_errno;
+
+  uint32_t sleep;
+  unsigned int sleep_ret;
+
+} ;
+
+
+struct wrap_stats_t {
+  struct stats_getpid_t getpid;
+  struct stats_open_t open;
+  struct stats_creat_t creat;
+  struct stats_close_t close;
+  struct stats_read_t read;
+  struct stats_write_t write;
+  struct stats_stat_t stat;
+  struct stats_fstat_t fstat;
+  struct stats_lseek_t lseek;
+  struct stats_malloc_t malloc;
+  struct stats_calloc_t calloc;
+  struct stats_memory_t memory;
+  struct stats_free_t free;
+  struct stats_realloc_t realloc;
+  struct stats_pthread_mutex_lock_t pthread_mutex_lock;
+  struct stats_pthread_mutex_trylock_t pthread_mutex_trylock;
+  struct stats_pthread_mutex_unlock_t pthread_mutex_unlock;
+  struct stats_pthread_mutex_unlock_t pthread_mutex_init;
+  struct stats_pthread_mutex_unlock_t pthread_mutex_destroy;
+  struct stats_sleep_t sleep;
+};
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_file.c b/tests/data/tasks/strcpy/student/CTester/wrap_file.c
new file mode 100644
index 0000000000000000000000000000000000000000..122a817688d7942ca0186e6f513599b764ac8f07
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_file.c
@@ -0,0 +1,239 @@
+// wrapper for the file operations, open, read, write
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "wrap.h"
+
+int __real_open(const char *pathname, int flags, mode_t mode);
+int __real_creat(const char *pathname, mode_t mode);
+int __real_close(int fd);
+ssize_t __real_read(int fd, void *buf, size_t count);
+ssize_t __real_write(int fd, const void *buf, size_t count);
+int __real_stat(const char* path, struct stat *buf);
+int __real_fstat(int fd, struct stat *buf);
+int __real_lstat(const char* path, struct stat *buf);
+ssize_t __real_pread(int fd, void *buf, size_t count, off_t offset);
+ssize_t __real_pwrite(int fd, const void *buf, size_t count, off_t offset);
+off_t __real_lseek(int fd, off_t offset, int whence);
+
+extern bool wrap_monitoring;
+extern struct wrap_stats_t stats;
+extern struct wrap_monitor_t monitored;
+extern struct wrap_fail_t failures;
+extern struct wrap_log_t logs;
+
+int __wrap_open(char *pathname, int flags, mode_t mode) {
+
+  if(!wrap_monitoring || !monitored.open) {
+    return __real_open(pathname,flags,mode); 
+  }
+  stats.open.called++;
+  stats.open.last_params.pathname=pathname;
+  stats.open.last_params.flags=flags;
+  stats.open.last_params.mode=mode;
+  
+  if (FAIL(failures.open)) {
+    failures.open=NEXT(failures.open);
+    errno=failures.open_errno;
+    stats.open.last_return=failures.open_ret;
+    return failures.open_ret;
+  }
+  failures.open=NEXT(failures.open);
+  // did not fail
+  int ret=__real_open(pathname, flags, mode);
+  stats.open.last_return=ret;
+  return ret;
+
+}
+
+int __wrap_creat(char *pathname, mode_t mode) {
+
+
+  if(!wrap_monitoring || !monitored.creat) {
+    return __real_creat(pathname,mode); 
+  }
+  stats.creat.called++;
+  stats.creat.last_params.pathname=pathname;
+  stats.creat.last_params.mode=mode;
+  
+  if (FAIL(failures.creat)) {
+    failures.creat=NEXT(failures.creat);
+    errno=failures.creat_errno;
+    stats.creat.last_return=failures.creat_ret;
+    return failures.creat_ret;
+  }
+  failures.creat=NEXT(failures.creat);
+  // did not fail
+  int ret=__real_creat(pathname, mode);
+  stats.creat.last_return=ret;
+  return ret;
+
+}
+
+int __wrap_close(int fd){
+
+  if(!wrap_monitoring || !monitored.close) {
+    return __real_close(fd); 
+  }
+  stats.close.called++;
+  stats.close.last_params.fd=fd;
+  
+  if (FAIL(failures.close)) {
+    failures.close=NEXT(failures.close);
+    errno=failures.close_errno;
+    stats.close.last_return=failures.close_ret;
+    return failures.close_ret;
+  }
+  failures.close=NEXT(failures.close);
+  // did not fail
+  int ret=__real_close(fd);
+  stats.close.last_return=ret;
+  return ret;
+
+}
+
+int __wrap_read(int fd, void *buf, size_t count){
+
+  if(!wrap_monitoring || !monitored.read) {
+    return __real_read(fd,buf,count); 
+  }
+  stats.read.called++;
+  stats.read.last_params.fd=fd;
+  stats.read.last_params.buf=buf;
+  stats.read.last_params.count=count;
+  
+  if (FAIL(failures.read)) {
+    failures.read=NEXT(failures.read);
+    errno=failures.read_errno;
+    stats.read.last_return=failures.read_ret;
+    return failures.read_ret;
+  }
+  failures.read=NEXT(failures.read);
+  // did not fail
+  int ret=__real_read(fd,buf,count);
+  stats.read.last_return=ret;
+  return ret;
+
+}
+
+
+int __wrap_write(int fd, void *buf, size_t count){
+
+  if(!wrap_monitoring || !monitored.write) {
+    return __real_write(fd,buf,count); 
+  }
+  stats.write.called++;
+  stats.write.last_params.fd=fd;
+  stats.write.last_params.buf=buf;
+  stats.write.last_params.count=count;
+  
+  if (FAIL(failures.write)) {
+    failures.write=NEXT(failures.write);
+    errno=failures.write_errno;
+    stats.write.last_return=failures.write_ret;
+    return failures.write_ret;
+  }
+  failures.write=NEXT(failures.write);
+  // did not fail
+  int ret=__real_write(fd,buf,count);
+  stats.write.last_return=ret;
+  return ret;
+
+}
+
+int __wrap_stat(char *path, struct stat *buf) {
+  
+  if(!wrap_monitoring || !monitored.stat) {
+return __real_stat(path,buf); 
+  }
+  stats.stat.called++;
+  stats.stat.last_params.path=path;
+  stats.stat.last_params.buf=buf;
+  
+  if (FAIL(failures.stat)) {
+    failures.stat=NEXT(failures.stat);
+    errno=failures.stat_errno;
+    stats.stat.last_return=failures.stat_ret;
+    return failures.stat_ret;
+  }
+  failures.stat=NEXT(failures.stat);
+  // did not fail
+  int ret=__real_stat(path,buf);
+  stats.stat.returned_stat.st_dev=buf->st_dev;
+  stats.stat.returned_stat.st_ino=buf->st_ino;
+  stats.stat.returned_stat.st_mode=buf->st_mode;
+  stats.stat.returned_stat.st_nlink=buf->st_nlink;
+  stats.stat.returned_stat.st_uid=buf->st_uid;
+  stats.stat.returned_stat.st_gid=buf->st_gid;
+  stats.stat.returned_stat.st_rdev=buf->st_rdev;
+  stats.stat.returned_stat.st_size=buf->st_size;
+  stats.stat.returned_stat.st_blksize=buf->st_blksize;
+  stats.stat.returned_stat.st_blocks=buf->st_blocks;
+  stats.stat.returned_stat.st_atime=buf->st_atime;
+  stats.stat.returned_stat.st_mtime=buf->st_mtime;
+  stats.stat.returned_stat.st_ctime=buf->st_ctime;
+  stats.stat.last_return=ret;
+  return ret;
+
+}
+
+int __wrap_fstat(int fd, struct stat *buf) {
+
+  if(!wrap_monitoring || !monitored.fstat) {
+    return __real_fstat(fd,buf);
+  }
+  stats.fstat.called++;
+  stats.fstat.last_params.fd=fd;
+  stats.fstat.last_params.buf=buf;
+  
+  if (FAIL(failures.fstat)) {
+    failures.fstat=NEXT(failures.fstat);
+    errno=failures.fstat_errno;
+    return failures.fstat_ret;
+  }
+  failures.fstat=NEXT(failures.fstat);
+  // did not fail
+  int ret=__real_fstat(fd,buf);
+  stats.fstat.returned_stat.st_dev=buf->st_dev;
+  stats.fstat.returned_stat.st_ino=buf->st_ino;
+  stats.fstat.returned_stat.st_mode=buf->st_mode;
+  stats.fstat.returned_stat.st_nlink=buf->st_nlink;
+  stats.fstat.returned_stat.st_uid=buf->st_uid;
+  stats.fstat.returned_stat.st_gid=buf->st_gid;
+  stats.fstat.returned_stat.st_rdev=buf->st_rdev;
+  stats.fstat.returned_stat.st_size=buf->st_size;
+  stats.fstat.returned_stat.st_blksize=buf->st_blksize;
+  stats.fstat.returned_stat.st_blocks=buf->st_blocks;
+  stats.fstat.returned_stat.st_atime=buf->st_atime;
+  stats.fstat.returned_stat.st_mtime=buf->st_mtime;
+  stats.fstat.returned_stat.st_ctime=buf->st_ctime;
+
+  stats.fstat.last_return=ret;
+  return ret;
+
+}
+
+off_t __wrap_lseek(int fd, off_t offset, int whence) {
+  
+  if(!wrap_monitoring || !monitored.lseek) {
+    return __real_lseek(fd,offset,whence);
+  }
+  stats.lseek.called++;
+  stats.lseek.last_params.fd=fd;
+  stats.lseek.last_params.offset=offset;
+  stats.lseek.last_params.whence=whence;
+
+  if (FAIL(failures.lseek)) {
+    failures.lseek=NEXT(failures.lseek);
+    errno=failures.lseek_errno;
+    return failures.lseek_ret;
+  }
+  failures.lseek=NEXT(failures.lseek);
+  // did not fail
+  off_t ret=__real_lseek(fd,offset,whence);
+  stats.lseek.last_return=ret;
+  return ret;
+}
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_file.h b/tests/data/tasks/strcpy/student/CTester/wrap_file.h
new file mode 100644
index 0000000000000000000000000000000000000000..7cae21f43b6a51b50b6c8a00db2250b93e61a3b5
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_file.h
@@ -0,0 +1,120 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+// basic structure to record the parameters of the last open call
+
+
+struct params_open_t {
+  char *pathname;
+  int flags;
+  mode_t mode;
+}; 
+
+// basic statistics for the utilisation of the open system call
+
+struct stats_open_t {
+  int called;  // number of times the open system call has been issued
+  struct params_open_t last_params; // parameters for the last call issued
+  int last_return;   // return value of the last open call issued
+};
+
+
+struct params_creat_t {
+  char *pathname;
+  mode_t mode;
+}; 
+
+// basic statistics for the utilisation of the creat system call
+
+struct stats_creat_t {
+  int called;  // number of times the open system call has been issued
+  struct params_creat_t last_params; // parameters for the last call issued
+  int last_return;   // return value of the last open call issued
+};
+
+
+struct params_close_t {
+  int fd;
+}; 
+
+// basic statistics for the utilisation of the close system call
+
+struct stats_close_t {
+  int called;  // number of times the open system call has been issued
+  struct params_close_t last_params; // parameters for the last call issued
+  int last_return;   // return value of the last open call issued
+};
+
+struct params_read_t {
+  int fd;
+  void *buf;
+  ssize_t count;
+}; 
+
+// basic statistics for the utilisation of the read system call
+
+struct stats_read_t {
+  int called;  // number of times the read system call has been issued
+  struct params_read_t last_params; // parameters for the last call issued
+  int last_return;   // return value of the last read call issued
+};
+
+struct params_write_t {
+  int fd;
+  void *buf;
+  ssize_t count;
+}; 
+
+// basic statistics for the utilisation of the write system call
+
+struct stats_write_t {
+  int called;  // number of times the write system call has been issued
+  struct params_read_t last_params; // parameters for the last call issued
+  int last_return;   // return value of the last read call issued
+};
+
+struct params_stat_t {
+  char *path;
+  struct stat *buf;
+}; 
+
+// basic statistics for the utilisation of the stat system call
+
+struct stats_stat_t {
+  int called;  // number of times the write system call has been issued
+  struct params_stat_t last_params; // parameters for the last call issued
+  int last_return;   // return value of the last read call issued
+  struct stat returned_stat; // last returned stat structure
+};
+
+
+struct params_fstat_t {
+  int fd;
+  struct stat *buf;
+}; 
+
+// basic statistics for the utilisation of the fstat system call
+
+struct stats_fstat_t {
+  int called;  // number of times the write system call has been issued
+  struct params_fstat_t last_params; // parameters for the last call issued
+  int last_return;   // return value of the last read call issued
+  struct stat returned_stat; // last returned stat structure
+};
+
+
+struct params_lseek_t {
+  int fd;
+  off_t offset;
+  int whence;
+}; 
+
+// basic statistics for the utilisation of the fstat system call
+
+struct stats_lseek_t {
+  int called;  // number of times the lseek system call has been issued
+  struct params_lseek_t last_params; // parameters for the last call issued
+  int last_return;   // return value of the last lseek call issued
+};
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_getpid.c b/tests/data/tasks/strcpy/student/CTester/wrap_getpid.c
new file mode 100644
index 0000000000000000000000000000000000000000..ff2e52595eb4811da850b6c6a5c975f150c0ddaa
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_getpid.c
@@ -0,0 +1,42 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include "wrap.h" // system call wrapper
+
+// pid_t getpid(void);
+
+pid_t __real_getpid(void);
+
+extern bool wrap_monitoring;
+extern struct wrap_stats_t stats;
+extern struct wrap_monitor_t monitored;
+extern struct wrap_fail_t failures;
+
+
+void init_getpid() {
+  // nothing to do
+}
+
+void clean_getpid() {
+  // nothing to do
+}
+
+void resetstats_getpid() {
+  stats.getpid.called=0;
+  stats.getpid.last_return=0;
+}
+
+pid_t __wrap_getpid(void) {
+  if(!wrap_monitoring || !monitored.getpid) {
+    return __real_getpid();
+  }
+  // being monitored
+
+  stats.getpid.called++;
+  pid_t ret=__real_getpid();
+  stats.getpid.last_return=ret;
+  return ret;
+
+}
+
+
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_getpid.h b/tests/data/tasks/strcpy/student/CTester/wrap_getpid.h
new file mode 100644
index 0000000000000000000000000000000000000000..2c1905c9e304ddbade51f9c06b52653ca63e4b83
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_getpid.h
@@ -0,0 +1,16 @@
+// never remove statistics from this structure, they could be
+// used by existing exercices. You might add some additional information
+// if it can help to validate some exercices
+#include <sys/types.h>
+#include <unistd.h>
+
+struct stats_getpid_t {
+  int called;           // number of times the system call has been called
+  pid_t last_return;    // last return value for getpid
+
+};
+
+void init_getpid();
+void clean_getpid();
+void resetstats_getpid();
+
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_malloc.c b/tests/data/tasks/strcpy/student/CTester/wrap_malloc.c
new file mode 100644
index 0000000000000000000000000000000000000000..eb345d19124036d1d897020db3b38b3f21b8da4d
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_malloc.c
@@ -0,0 +1,206 @@
+/* 
+ * Wrapper for malloc, free and calloc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include  "wrap.h"
+
+#include <libintl.h> 
+#include <locale.h> 
+#define _(STRING) gettext(STRING)
+
+#define MSG_SIZE 1000
+char msg[MSG_SIZE];
+
+
+void * __real_malloc(size_t s);
+void * __real_calloc(size_t nmemb, size_t s);
+void __real_free(void *);
+void * __real_realloc(void *ptr, size_t size);
+
+
+extern bool wrap_monitoring;
+extern struct wrap_stats_t stats;
+extern struct wrap_monitor_t monitored;
+extern struct wrap_fail_t failures;
+extern struct wrap_log_t logs;
+
+//
+// keeps only MAX_LOG in memory
+//
+void log_malloc(void *ptr, size_t size) {
+  
+  if(ptr!=NULL && logs.malloc.n < MAX_LOG) {
+    logs.malloc.log[logs.malloc.n].size=size;
+    logs.malloc.log[logs.malloc.n].ptr=ptr;
+    logs.malloc.n++;
+  }
+}
+
+void update_realloc_block(void *ptr, size_t newsize) {
+   for(int i=0;i<MAX_LOG;i++) {
+     if(logs.malloc.log[i].ptr==ptr) {
+      logs.malloc.log[i].size=newsize;
+      return;
+     } 
+  }
+   return ;
+}
+
+size_t find_size_malloc(void *ptr) {
+  for(int i=0;i<MAX_LOG;i++) {
+    if(logs.malloc.log[i].ptr==ptr) 
+      return logs.malloc.log[i].size;
+  }
+  return -1;
+}
+
+
+void * __wrap_malloc(size_t size) {
+  if(!wrap_monitoring || !monitored.malloc) {
+    return __real_malloc(size);
+  }
+  stats.malloc.called++;
+  stats.malloc.last_params.size=size;
+  if(FAIL(failures.malloc)) {
+    failures.malloc=NEXT(failures.malloc);
+    return failures.malloc_ret;
+  }
+  stats.memory.used+=size;
+  failures.malloc=NEXT(failures.malloc);    
+  void *ptr=__real_malloc(size);
+  stats.malloc.last_return=ptr;
+  log_malloc(ptr,size);
+  return ptr;
+}
+
+void * __wrap_realloc(void *ptr, size_t size) {
+  if(!wrap_monitoring || !monitored.realloc) {
+    return __real_realloc(ptr, size);
+  }
+  stats.realloc.called++;
+  stats.realloc.last_params.size=size;
+  if(FAIL(failures.realloc)) {
+    failures.realloc=NEXT(failures.realloc);
+    return failures.realloc_ret;
+  }
+  failures.realloc=NEXT(failures.realloc);    
+  int old_size=find_size_malloc(ptr);
+  void *r_ptr=__real_realloc(ptr,size);
+  stats.realloc.last_return=r_ptr;
+  if(ptr!=NULL) {
+      stats.memory.used+=size-old_size;
+      update_realloc_block(ptr,size);
+  }
+  return r_ptr;
+}
+
+
+void * __wrap_calloc(size_t nmemb, size_t size) {
+  if(!wrap_monitoring || !monitored.calloc) {
+    return __real_calloc(nmemb, size);
+  }
+  stats.calloc.called++;
+  stats.calloc.last_params.size=size;
+  stats.calloc.last_params.nmemb=nmemb;
+
+  if(FAIL(failures.calloc)) {
+    failures.calloc=NEXT(failures.calloc);
+    return failures.calloc_ret;
+  }
+  stats.memory.used+=nmemb*size;
+  failures.calloc=NEXT(failures.calloc);
+    
+  void *ptr=__real_calloc(nmemb,size);
+  stats.calloc.last_return=ptr;
+  log_malloc(ptr,nmemb*size);
+  return ptr;
+}
+
+int malloc_free_ptr(void *ptr) {
+
+  for(int i=0;i<MAX_LOG;i++) {
+    if(logs.malloc.log[i].ptr==ptr) {
+      int size=logs.malloc.log[i].size;
+      logs.malloc.log[i].size=-1;
+      logs.malloc.log[i].ptr=NULL;
+      return size;
+    }
+  }
+  return 0;
+}
+
+void __wrap_free(void *ptr) {
+  if(!wrap_monitoring || !monitored.free) {
+    return __real_free(ptr);
+  }
+  stats.free.called++;
+  stats.free.last_params.ptr=ptr;
+  if(ptr!=NULL) {
+    stats.memory.used-=malloc_free_ptr(ptr);
+
+    if (FAIL(failures.free))
+      failures.free=NEXT(failures.free);
+    else
+      __real_free(ptr);
+  }
+}
+
+
+/*
+void * find_ptr_malloc(size_t size){
+  for(int i=0;i<MAX_LOG;i++) {
+    if(logs.malloc.log[i].size==size) 
+      return logs.malloc.log[i].ptr;
+  }
+  return NULL;
+}
+
+void malloc_log_init(struct malloc_t *l) {
+  for(int i=0;i<MAX_LOG;i++) {
+    l->log[i].size=-1;
+    l->log[i].ptr=NULL;
+  }
+  l->n=0;
+
+}
+
+*/
+int  malloc_allocated() {
+  int tot=0;
+  for(int i=0;i<MAX_LOG;i++) {
+    if(logs.malloc.log[i].ptr!=NULL) {
+      tot+=(int) logs.malloc.log[i].size;
+    }
+  }
+  return tot;
+}
+
+/*
+ * returns true if the address has been managed by malloc, false
+ * otherwise (also false if address has been freed)
+ */
+int malloced(void *addr) {
+  for(int i=0;i<logs.malloc.n;i++) {
+    if(logs.malloc.log[i].ptr<=addr && 
+       (logs.malloc.log[i].ptr+ logs.malloc.log[i].size)>=addr) {
+      return true;
+    }
+  }
+  return false;
+
+}
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_malloc.h b/tests/data/tasks/strcpy/student/CTester/wrap_malloc.h
new file mode 100644
index 0000000000000000000000000000000000000000..dcc7a8c6fc83e38ed246fa342820c2003529ef40
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_malloc.h
@@ -0,0 +1,100 @@
+/* 
+ * Wrapper for malloc, free and calloc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+// log for malloc operations
+
+
+struct malloc_elem_t {
+  size_t size;
+  void *ptr;
+};
+
+
+// basic structure to record the parameters of the last malloc call
+
+struct params_malloc_t {
+       size_t size;           
+}; 
+
+// basic statistics for the utilisation of the malloc call
+
+struct stats_malloc_t {
+  int called;  // number of times the malloc call has been issued
+  struct params_malloc_t last_params; // parameters for the last call issued
+  void *last_return;   // return value of the last malloc call issued
+};
+
+struct stats_memory_t {
+  int used;  // Total number of bytes allocated
+};
+
+// basic structure to record the parameters of the last malloc call
+
+struct params_calloc_t {
+       size_t nmemb;
+       size_t size;           
+}; 
+
+// basic statistics for the utilisation of the calloc call
+
+struct stats_calloc_t {
+  int called;  // number of times the malloc call has been issued
+  struct params_calloc_t last_params; // parameters for the last call issued
+  void *last_return;   // return value of the last malloc call issued
+};
+
+
+// basic structure to record the parameters of the last free call
+
+struct params_free_t {
+   void *ptr;
+}; 
+
+// basic statistics for the utilisation of the free call
+
+struct stats_free_t {
+  int called;  // number of times the free call has been issued
+  struct params_free_t last_params; // parameters for the last call issued
+};
+
+
+// basic structure to record the parameters of the last realloc call
+
+struct params_realloc_t {
+  void *ptr;
+  size_t size;           
+}; 
+
+// basic statistics for the utilisation of the realloc call
+
+struct stats_realloc_t {
+  int called;  // number of times the malloc call has been issued
+  struct params_realloc_t last_params; // parameters for the last call issued
+  void  *last_return;   // return value of the last realloc call issued
+};
+
+
+// function prototypes
+
+//void malloc_log_init(struct malloc_t *l);
+void malloc_log(void *ptr, size_t size);
+
+// true if memory was allocated by malloc, false otherwise
+int malloced(void *addr);
+// total amount of memory allocated by malloc
+int  malloc_allocated();
\ No newline at end of file
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_mutex.c b/tests/data/tasks/strcpy/student/CTester/wrap_mutex.c
new file mode 100644
index 0000000000000000000000000000000000000000..7b19c1768a114fe970828e73ac4dbc753607154f
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_mutex.c
@@ -0,0 +1,117 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include "wrap.h" // system call wrapper
+#include <pthread.h>
+
+
+//int pthread_mutex_lock(pthread_mutex_t *mutex);
+//int pthread_mutex_trylock(pthread_mutex_t *mutex);
+//int pthread_mutex_unlock(pthread_mutex_t *mutex); 
+
+
+int __real_pthread_mutex_lock(pthread_mutex_t *mutex);
+int __real_pthread_mutex_trylock(pthread_mutex_t *mutex);
+int __real_pthread_mutex_unlock(pthread_mutex_t *mutex); 
+int __real_pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
+int __real_pthread_mutex_destroy(pthread_mutex_t *mutex);
+
+extern bool wrap_monitoring;
+extern struct wrap_stats_t stats;
+extern struct wrap_monitor_t monitored;
+extern struct wrap_fail_t failures;
+
+
+void init_mutex() {
+  // nothing to do
+}
+
+void clean_mutex() {
+  // nothing to do
+}
+
+void resetstats_mutex() {
+
+  stats.pthread_mutex_lock.called=0;
+  stats.pthread_mutex_lock.last_return=0;
+  stats.pthread_mutex_trylock.called=0;
+  stats.pthread_mutex_trylock.last_return=0;
+  stats.pthread_mutex_unlock.called=0;
+  stats.pthread_mutex_unlock.last_return=0;
+  stats.pthread_mutex_init.called=0;
+  stats.pthread_mutex_init.last_return=0;
+  stats.pthread_mutex_destroy.called=0;
+  stats.pthread_mutex_destroy.last_return=0;
+}
+
+int __wrap_pthread_mutex_destroy(pthread_mutex_t *mutex) {
+  if(!wrap_monitoring || !monitored.pthread_mutex_destroy) {
+    return __real_pthread_mutex_destroy(mutex);
+  }
+  // being monitored
+
+  stats.pthread_mutex_destroy.called++;
+  int ret=__real_pthread_mutex_destroy(mutex);
+  stats.pthread_mutex_destroy.last_arg=mutex;
+  stats.pthread_mutex_destroy.last_return=ret;
+  return ret;
+}
+
+
+int __wrap_pthread_mutex_init(pthread_mutex_t *restrict mutex,
+                       const pthread_mutexattr_t *restrict attr) {
+  if(!wrap_monitoring || !monitored.pthread_mutex_init) {
+    return __real_pthread_mutex_init(mutex,attr);
+  }
+  // being monitored
+
+  stats.pthread_mutex_init.called++;
+  int ret=__real_pthread_mutex_init(mutex,attr);
+  stats.pthread_mutex_init.last_arg=mutex;
+  stats.pthread_mutex_init.last_return=ret;
+  return ret;
+
+}
+
+
+pid_t __wrap_pthread_mutex_lock(pthread_mutex_t *mutex) {
+  if(!wrap_monitoring || !monitored.pthread_mutex_lock) {
+    return __real_pthread_mutex_lock(mutex);
+  }
+  // being monitored
+
+  stats.pthread_mutex_lock.called++;
+  int ret=__real_pthread_mutex_lock(mutex);
+  stats.pthread_mutex_lock.last_arg=mutex;
+  stats.pthread_mutex_lock.last_return=ret;
+  return ret;
+
+}
+
+pid_t __wrap_pthread_mutex_trylock(pthread_mutex_t *mutex) {
+  if(!wrap_monitoring || !monitored.pthread_mutex_trylock) {
+    return __real_pthread_mutex_trylock(mutex);
+  }
+  // being monitored
+
+  stats.pthread_mutex_trylock.called++;
+  int ret=__real_pthread_mutex_trylock(mutex);
+  stats.pthread_mutex_trylock.last_arg=mutex;
+  stats.pthread_mutex_trylock.last_return=ret;
+  return ret;
+
+}
+
+pid_t __wrap_pthread_mutex_unlock(pthread_mutex_t *mutex) {
+  if(!wrap_monitoring || !monitored.pthread_mutex_unlock) {
+    return __real_pthread_mutex_unlock(mutex);
+  }
+  // being monitored
+
+  stats.pthread_mutex_unlock.called++;
+  int ret=__real_pthread_mutex_unlock(mutex);
+  stats.pthread_mutex_unlock.last_arg=mutex;
+  stats.pthread_mutex_unlock.last_return=ret;
+  return ret;
+
+}
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_mutex.h b/tests/data/tasks/strcpy/student/CTester/wrap_mutex.h
new file mode 100644
index 0000000000000000000000000000000000000000..9a1e5eb92cfacd31350e79ba4cc01ff86ed52070
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_mutex.h
@@ -0,0 +1,62 @@
+// never remove statistics from this structure, they could be
+// used by existing exercices. You might add some additional information
+// if it can help to validate some exercices
+#include <sys/types.h>
+#include <unistd.h>
+#include <pthread.h>
+
+struct stats_pthread_mutex_lock_t {
+  int called;           // number of times the system call has been called
+  pid_t last_return;    // last return value 
+  pthread_mutex_t *last_arg; // last mutex passed as argument
+
+};
+
+void init_pthread_mutex_lock();
+void clean_pthread_mutex_lock();
+void resetstats_pthread_mutex_lock();
+
+struct stats_pthread_mutex_trylock_t {
+  int called;           // number of times the system call has been called
+  pid_t last_return;    // last return value 
+  pthread_mutex_t *last_arg; // last mutex passed as argument
+};
+
+void init_pthread_mutex_trylock();
+void clean_pthread_mutex_trylock();
+void resetstats_pthread_mutex_trylock();
+
+
+struct stats_pthread_mutex_unlock_t {
+  int called;           // number of times the system call has been called
+  pid_t last_return;    // last return value 
+  pthread_mutex_t *last_arg; // last mutex passed as argument
+
+};
+
+void init_pthread_mutex_unlock();
+void clean_pthread_mutex_unlock();
+void resetstats_pthread_mutex_unlock();
+
+struct stats_pthread_mutex_init_t {
+  int called;           // number of times the system call has been called
+  pid_t last_return;    // last return value 
+  pthread_mutex_t *last_arg; // last mutex passed as argument
+
+};
+
+void init_pthread_mutex_init();
+void clean_pthread_mutex_init();
+void resetstats_pthread_mutex_init();
+
+struct stats_pthread_mutex_destroy_t {
+  int called;           // number of times the system call has been called
+  pid_t last_return;    // last return value 
+  pthread_mutex_t *last_arg; // last mutex passed as argument
+
+};
+
+void init_pthread_mutex_destroy();
+void clean_pthread_mutex_destroy();
+void resetstats_pthread_mutex_destroy();
+
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_sleep.c b/tests/data/tasks/strcpy/student/CTester/wrap_sleep.c
new file mode 100644
index 0000000000000000000000000000000000000000..b74a2a6c387a729b624c13b85a7f52989224d6b8
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_sleep.c
@@ -0,0 +1,51 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include "wrap.h" // system call wrapper
+
+// unsigned int sleep(unsigned int seconds);
+
+unsigned int __real_sleep(unsigned int);
+
+extern bool wrap_monitoring;
+extern struct wrap_stats_t stats;
+extern struct wrap_monitor_t monitored;
+extern struct wrap_fail_t failures;
+
+
+void init_sleep() {
+  // nothing to do
+}
+
+void clean_sleep() {
+  // nothing to do
+}
+
+void resetstats_sleep() {
+  stats.sleep.called=0;
+  stats.sleep.last_return=0;
+  stats.sleep.last_arg=0;
+}
+
+unsigned int __wrap_sleep(unsigned int time) {
+  if(!wrap_monitoring || !monitored.sleep) {
+    return __real_sleep(time);
+  }
+
+  stats.sleep.called++;
+  stats.sleep.last_arg = time;
+  // being monitored
+  if (FAIL(failures.sleep)) {
+    failures.sleep=NEXT(failures.sleep);
+    stats.sleep.last_return=failures.sleep_ret;
+    return failures.sleep_ret;
+  }
+  failures.sleep=NEXT(failures.sleep);
+  // did not fail
+
+  unsigned int ret=__real_sleep(time);
+  stats.sleep.last_return=ret;
+  return ret;
+}
+
+
diff --git a/tests/data/tasks/strcpy/student/CTester/wrap_sleep.h b/tests/data/tasks/strcpy/student/CTester/wrap_sleep.h
new file mode 100644
index 0000000000000000000000000000000000000000..6d0a488736aac6b1b97c25a3786d23ad959dd54e
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/CTester/wrap_sleep.h
@@ -0,0 +1,16 @@
+// never remove statistics from this structure, they could be
+// used by existing exercices. You might add some additional information
+// if it can help to validate some exercices
+#include <sys/types.h>
+#include <unistd.h>
+
+struct stats_sleep_t {
+  int called;           // number of times the system call has been called
+  unsigned int last_return;    // last return value for sleep
+  unsigned int last_arg;    // last return value for sleep
+};
+
+void init_sleep();
+void clean_sleep();
+void resetstats_sleep();
+
diff --git a/tests/data/tasks/strcpy/student/Makefile b/tests/data/tasks/strcpy/student/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..52bdded09c4e4ab1bc2e5ecf7122f3319c16b536
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/Makefile
@@ -0,0 +1,41 @@
+# MakeFile for the tasks using Chestier
+CC=gcc
+CPP=cppcheck
+EXEC=tests
+CPPFLAGS=--error-exitcode=1
+LDFLAGS=-lcunit -lm -lpthread -ldl -rdynamic
+SRC=$(wildcard *.c) CTester/wrap_mutex.c CTester/wrap_malloc.c CTester/wrap_file.c CTester/wrap_sleep.c CTester/CTester.c CTester/trap.c
+OBJ=$(SRC:.c=.o)
+CFLAGS=-Wall -Werror -DC99 -std=gnu99 -ICTester
+WRAP=-Wl,-wrap=pthread_mutex_lock -Wl,-wrap=pthread_mutex_unlock -Wl,-wrap=pthread_mutex_trylock -Wl,-wrap=pthread_mutex_init -Wl,-wrap=pthread_mutex_destroy -Wl,-wrap=malloc -Wl,-wrap=free -Wl,-wrap=realloc -Wl,-wrap=calloc -Wl,-wrap=open -Wl,-wrap=creat -Wl,-wrap=close -Wl,-wrap=read -Wl,-wrap=write -Wl,-wrap=stat -Wl,-wrap=fstat -Wl,-wrap=lseek -Wl,-wrap=exit -Wl,-wrap=sleep
+
+all: $(EXEC)
+
+%.o: %.c
+	$(CC) $(CFLAGS) -c -o $@ $< 
+
+$(EXEC): $(OBJ)
+	$(CC) $(WRAP) -o $@ $(OBJ) $(LDFLAGS)
+
+create-po:
+	mkdir -p po/fr/
+	xgettext --keyword=_ --language=C --add-comments --sort-output --from-code=UTF-8 -o po/tests.pot $(SRC)
+	msginit --input=po/tests.pot --locale=fr_BE.utf8 --output=po/fr/tests.po
+
+update-po:
+	xgettext --keyword=_ --language=C --add-comments --sort-output --from-code=UTF-8 -o po/tests.pot $(SRC)
+	msgmerge --update po/fr/tests.po po/tests.pot
+
+compile-mo:
+	msgfmt --no-hash --output-file=po/fr/tests.mo po/fr/tests.po
+	mkdir -p fr/LC_MESSAGES/
+	cp po/fr/tests.mo fr/LC_MESSAGES/tests.mo
+    
+check: $(SRC)
+	$(CPP) $(CPPFLAGS) $<
+
+clean:
+	rm -f $(EXEC) $(OBJ)
+
+.PHONY: tests
+
diff --git a/tests/data/tasks/strcpy/student/fr/LC_MESSAGES/tests.mo b/tests/data/tasks/strcpy/student/fr/LC_MESSAGES/tests.mo
new file mode 100644
index 0000000000000000000000000000000000000000..cc8533c9066467d118deb3fe66a0db0e498e7286
Binary files /dev/null and b/tests/data/tasks/strcpy/student/fr/LC_MESSAGES/tests.mo differ
diff --git a/tests/data/tasks/strcpy/student/student_code.c.tpl b/tests/data/tasks/strcpy/student/student_code.c.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..ed4fc2d74b159b043438dd41f27ac3779ccff53b
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/student_code.c.tpl
@@ -0,0 +1,8 @@
+#include<stdio.h>
+#include<stdlib.h>
+
+#include "student_code.h"
+
+char *buf_strcpy(const char *src) {
+  @@strcpy_impl@@
+}
diff --git a/tests/data/tasks/strcpy/student/student_code.h b/tests/data/tasks/strcpy/student/student_code.h
new file mode 100644
index 0000000000000000000000000000000000000000..649c060d753b22176606b0a207aba72c9deca08f
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/student_code.h
@@ -0,0 +1,5 @@
+#include<string.h>
+#include<stdlib.h>
+
+char *buf_strcpy(const char *src);
+  
diff --git a/tests/data/tasks/strcpy/student/tests.c b/tests/data/tasks/strcpy/student/tests.c
new file mode 100644
index 0000000000000000000000000000000000000000..1613b608eb194bc8663f2f70ded143d3ae195a6b
--- /dev/null
+++ b/tests/data/tasks/strcpy/student/tests.c
@@ -0,0 +1,94 @@
+/*
+ * TODO : Check if it works well
+ */
+
+// CTester template
+
+#include <stdlib.h>
+#include "student_code.h"
+#include "CTester/CTester.h"
+
+void test_strcpy_return() {
+    set_test_metadata("strcpy_impl", _("Check if the string is correctly put in memory"), 1);
+
+    char *ret = NULL;
+
+    const char *stack_src = "Chaine de char de test un peu courte mais pas trop quand meme";
+    char *src = (char *)malloc(strlen(stack_src)+1);
+    if (src == NULL)
+        CU_FAIL("no mem");
+    strcpy(src, stack_src);
+
+    monitored.malloc = true;
+    monitored.calloc = true;
+
+    SANDBOX_BEGIN;
+    ret = buf_strcpy(src);
+    SANDBOX_END;
+
+
+    // check if only one call to malloc
+    int ms = stats.malloc.called;
+    int cs = stats.calloc.called;
+    CU_ASSERT_EQUAL(ms, 1);
+    if (ms > 1){
+        push_info_msg(_("You used more than one call to malloc"));
+        return;
+    }
+
+    // check if new element is malloced
+    int mal = malloced((void*) ret);
+    CU_ASSERT_TRUE(mal);
+    // if malloced, check the value, else not because it produces buffer overflow due to CUNIT
+    if (mal && ms) {
+        if(stats.malloc.last_params.size != strlen(src)+1) {
+            CU_FAIL("wrong malloc size");
+            push_info_msg(_("The allocated memory doesn't the correct size."));
+            set_tag("malloc_fail");
+            return;
+        }
+        if (strncmp(ret, src, strlen(src) + 1) != 0){
+            CU_FAIL("wrong string");
+        }
+        free(ret);
+    }
+    if(!mal){
+        push_info_msg(_("The returned pointer is not malloced"));
+        set_tag("malloc_fail");
+    }
+    if(cs){
+        CU_FAIL();
+        set_tag("malloc_fail");
+        push_info_msg(_("You should use malloc for this task. Calloc could also work but it's not efficient to use it here since we initialise the memory just after we allocate it"));
+    }
+}
+
+void test_strcpy_nomem() {
+    set_test_metadata("strcpy_impl", _("Check the behavior of the function when the call to malloc fails"), 1);
+
+    char *ret = NULL;
+    char *src = "Chaine de char de test un peu courte mais pas trop quand meme";
+
+    monitored.malloc = true;
+    failures.malloc = FAIL_ALWAYS;
+    failures.malloc_ret = NULL;
+
+    SANDBOX_BEGIN;
+    ret = buf_strcpy(src);
+    SANDBOX_END;
+
+    CU_ASSERT_PTR_NULL(ret);
+    if (ret){
+        push_info_msg(_("The return value of your implementation is wrong"));
+        set_tag("malloc_fail");
+    }
+
+    free(ret);
+
+}
+
+int main(int argc,char** argv)
+{
+    BAN_FUNCS(memcpy, memccpy);
+    RUN(test_strcpy_return, test_strcpy_nomem);
+}
diff --git a/tests/data/tasks/strcpy/task.yaml b/tests/data/tasks/strcpy/task.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b11b0a6b4742ae351313db4c18996313485e36f8
--- /dev/null
+++ b/tests/data/tasks/strcpy/task.yaml
@@ -0,0 +1,50 @@
+accessible: true
+author: Nicolas Rybowski
+context: 'The classic function ``char *strcpy(char *destination, const char *source);``
+  `strcpy(3) <https://linux.die.net/man/3/strcpy>`_ needs a destination buffer where
+  the source string is copied. We ask you to code a function which allocates a buffer
+  itself, and then performs the copy.
+
+
+  The use of copy functions as ``memcpy`` is not allowed.
+
+
+  *Hint* : use `malloc(3) <https://sites.uclouvain.be/SystInfo/manpages/man3/malloc.3.html>`_'
+environment: cpp
+evaluate: best
+groups: false
+input_random: '0'
+limits: {memory: '100', output: '2', time: '30'}
+name: '[S3] Improved strcpy '
+network_grading: false
+order: 34
+problems:
+  strcpy_impl: {default: '', header: "Write the body of the function *buf_strcpy*.\n\
+      \n.. code-block:: c\n\n    /*\n    * Creates a buffer that has the same size\
+      \ as src, and copies the content of src to this buffer.\n    *\n    * @src:\
+      \ string to be copied\n    * @return: return pointer. if src == NULL or in case\
+      \ of error, return NULL\n    *\n    * Remember that strings are terminated with\
+      \ '\\0' and that strlen(\"abc\") returns 3 even if 4 bytes are required to store\
+      \ this string.\n    */\n    char *buf_strcpy(const char *src) {\n", language: c,
+    name: buf_strcpy, type: code}
+stored_submissions: 0
+submission_limit: {amount: -1, period: -1}
+tags:
+  '0': {description: Your code exceeds the maximum allowed time., id: timeout, name: Timeout,
+    type: 1, visible: true}
+  '1': {description: '', id: sigsegv, name: Segmentation Fault, type: 1, visible: true}
+  '10': {description: Your code does not compile with cppcheck, id: cppcheck, name: Cppcheck
+      fails, type: 1, visible: true}
+  '2': {description: You code does not compile., id: not_compile, name: Not compile,
+    type: 1, visible: true}
+  '3': {description: '', id: memory, name: Memory Exceeded, type: 1, visible: true}
+  '4': {description: '', id: sigfpe, name: Floating Point Exception, type: 1, visible: true}
+  '5': {description: Your code produced a double free., id: double_free, name: Double
+      free, type: 1, visible: true}
+  '6': {description: You use some banned functions., id: banned_funcs, name: Banned
+      functions, type: 1, visible: true}
+  '7': {description: You do not manage the case where malloc() fails., id: malloc_fail,
+    name: Malloc fail, type: 1, visible: true}
+  '8': {description: '', id: '', name: S3, type: 2, visible: true}
+  '9': {description: Usage of malloc(), id: '', name: Malloc, type: 2, visible: true}
+weight: 1.0
diff --git a/tests/data/tasks/strcpy/test/submission1.test b/tests/data/tasks/strcpy/test/submission1.test
new file mode 100644
index 0000000000000000000000000000000000000000..60029c0e85832e6871a2f80f7b2bd111bca0d8d6
--- /dev/null
+++ b/tests/data/tasks/strcpy/test/submission1.test
@@ -0,0 +1,23 @@
+_id: 5a8e8660aff4146a5a4cb2bf
+archive: 5b9a61461a750b1640772c86
+courseid: LSINF1252
+custom: {}
+grade: 100.0
+input: {'@lang': en, '@username': anonymous, strcpy_impl: "char *ret=(char *) malloc(strlen(src)+1);\n\
+    if(src==NULL || ret==NULL)\n    return NULL;\nfor(int i=0; i<=strlen(src); i++){\n\
+    \    ret[i]=src[i];\n}\nreturn ret;"}
+problems:
+  strcpy_impl: [success, "* Check if the string is correctly put in memory\n\n  =>\
+      \ r\xE9ussi (1/1) pts)\n\n\n* Check the behavior of the function when the call\
+      \ to malloc fails\n\n  => r\xE9ussi (1/1) pts)\n\n\n"]
+response_type: rst
+result: success
+state: ''
+status: done
+stderr: ''
+stdout: ''
+submitted_on: 2018-02-22 09:59:12.418000
+taskid: strcpy
+tests: {}
+text: "- Votre code compile.\n\n- Votre code a pass\xE9 tous les tests."
+username: anonymous
diff --git a/tests/test_task_data.py b/tests/test_task_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..bfcfe9df4ef25dc83cbfb6d521a2320e7e56629c
--- /dev/null
+++ b/tests/test_task_data.py
@@ -0,0 +1,28 @@
+from task_common import TaskData, task_dir_to_TaskData
+from pathlib import Path
+import unittest
+import os
+import sys
+project_root = os.path.dirname(sys.modules['__main__'].__file__)
+test_data_path = os.path.join('tests', 'data')
+task_dirs = [
+    {
+        'root': Path(os.path.join(test_data_path, 'tasks', 'strcpy')),
+        'build': Path(os.path.join(test_data_path, 'tasks', 'strcpy', 'student', 'Makefile')),
+        'libs': [Path(os.path.join(test_data_path, 'tasks', 'strcpy', 'student', 'CTester'))]
+    }
+]
+
+class TaskDataTestCase(unittest.TestCase):
+
+    def setUp(self):
+        pass 
+
+    def test_task_dir_to_TaskData(self):
+        task_dir = task_dirs[0]
+        task_data = task_dir_to_TaskData(task_dir['root'], task_dir['build'], task_dir['libs'])
+        self.assertIsNotNone(task_data)
+        for prop in ['task', 'build_script', 'lib_dirs', 'annex']:
+            self.assertIsNotNone(task_data.__dict__[prop], msg=f"Property {prop} is None !")
+
+