diff --git a/core/serializers/__init__.py b/core/serializers/__init__.py
index 5cafb28c394618046fc8a9d31c79b496a01cd422..0306ce607329110c9e7268cf88d30968638a2615 100644
--- a/core/serializers/__init__.py
+++ b/core/serializers/__init__.py
@@ -1,5 +1,5 @@
 from .common_serializers import *  # noqa
-from .feedback import FeedbackSerializer  # noqa
+from .feedback import FeedbackSerializer, FeedbackCommentSerializer  # noqa
 from .subscription import *  # noqa
 from .student import *  # noqa
 from .submission import *  # noqa
diff --git a/core/tests/test_feedback.py b/core/tests/test_feedback.py
index c219388ed43498336a754a6d572db27745edcfad..6677eb066caa509bdab25fac4c937d055fe0ad52 100644
--- a/core/tests/test_feedback.py
+++ b/core/tests/test_feedback.py
@@ -1,7 +1,7 @@
 import unittest
 
 from rest_framework import status
-from rest_framework.test import (APIRequestFactory, APITestCase)
+from rest_framework.test import APIRequestFactory, APITestCase
 
 from core import models
 from core.models import Feedback, FeedbackComment, Submission, SubmissionType
@@ -447,12 +447,12 @@ class FeedbackCommentApiEndpointTest(APITestCase):
         comment = FeedbackComment.objects.get(of_tutor=self.tutor02)
         self.client.force_authenticate(self.tutor01)
         response = self.client.delete(self.url % comment.pk)
-        self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code)
+        self.assertEqual(status.HTTP_404_NOT_FOUND, response.status_code)
 
     def test_reviewer_can_delete_everything_they_want(self):
         reviewer = self.data['reviewers'][0]
         self.client.force_authenticate(user=reviewer)
-        comment01 = FeedbackComment.objects.get(of_tutor=self.tutor02)
+        comment01 = FeedbackComment.objects.get(of_tutor=self.tutor01)
         comment02 = FeedbackComment.objects.get(of_tutor=self.tutor02)
 
         response = self.client.delete(self.url % comment01.pk)
@@ -460,3 +460,26 @@ class FeedbackCommentApiEndpointTest(APITestCase):
 
         response = self.client.delete(self.url % comment02.pk)
         self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
+        try:
+            FeedbackComment.objects.get(of_tutor=self.tutor01)
+            FeedbackComment.objects.get(of_tutor=self.tutor02)
+        except FeedbackComment.DoesNotExist:
+            pass
+        else:
+            self.fail('No exception raised')
+
+    def test_reviewer_can_set_comment_visibility(self):
+        reviewer = self.data['reviewers'][0]
+        self.client.force_authenticate(user=reviewer)
+        comment = FeedbackComment.objects.get(of_tutor=self.tutor01)
+        self.assertTrue(comment.visible_to_student)
+
+        data = {
+            'visible_to_student': False
+        }
+
+        response = self.client.patch(self.url % comment.pk, data)
+
+        self.assertFalse(response.data['visible_to_student'])
+        comment.refresh_from_db()
+        self.assertFalse(comment.visible_to_student)
diff --git a/core/views/feedback.py b/core/views/feedback.py
index 3e7a10cf2713cf9b7f12acd799ba173bdd793cbe..1eb11a056e7f8fae24044e8616358cec9ca2b907 100644
--- a/core/views/feedback.py
+++ b/core/views/feedback.py
@@ -112,16 +112,27 @@ class FeedbackApiView(
         return Response(serializer.data)
 
 
-class FeedbackCommentApiView(viewsets.GenericViewSet):
+class FeedbackCommentApiView(
+        mixins.DestroyModelMixin,
+        viewsets.GenericViewSet):
     """ Gets a list of an individual exam by Id if provided """
     permission_classes = (permissions.IsTutorOrReviewer,)
     queryset = models.FeedbackComment.objects.all()
+    serializer_class = serializers.FeedbackCommentSerializer
 
-    def destroy(self, request, *args, **kwargs):
-        comment = self.get_object()
+    def get_queryset(self):
+        user = self.request.user
+        if user.role == models.UserAccount.REVIEWER:
+            return self.queryset
+        return self.queryset.filter(of_tutor=user)
 
-        user = request.user
-        if user.role == models.UserAccount.TUTOR and user != comment.of_tutor:
-            raise PermissionDenied(detail='Can only delete your own commits.')
+    def partial_update(self, request, **kwargs):
+        keys = self.request.data.keys()
+        if keys - {'visible_to_student', 'of_line', 'text'}:
+            raise PermissionDenied('These fields cannot be changed.')
 
-        return Response(status=status.HTTP_204_NO_CONTENT)
+        comment = self.get_object()
+        serializer = self.get_serializer(comment, request.data, partial=True)
+        serializer.is_valid()
+        serializer.save()
+        return Response(serializer.data)
diff --git a/util/convert.py b/util/convert.py
index 82041e1997e20dfb14e7494ca8d76444759d4552..0c45f9472e83c59c0d2e752097fab37ae5301ae9 100755
--- a/util/convert.py
+++ b/util/convert.py
@@ -60,7 +60,7 @@ parser.add_argument(
 user_t = namedtuple('user_head', 'name matrikel_no')
 
 # one task has a title and id and hpfly code
-task_head_re = re.compile(r'^Quellcode Frage (?P<title>.*) ?(\d{8})?$')
+task_head_re = re.compile(r'^Quellcode Frage (?P<title>.*?) ?(\d{8})?$')
 
 # nor parsing the weird mat no
 matno_re = re.compile(r'^(?P<matrikel_no>\d{8})-(\d+)-(\d+)$')
@@ -73,8 +73,9 @@ def converter(infile, usernames=None, number_of_tasks=0,):
     def sheet_iter_meta(sheet):
         """ yield first and second col entry as tuple of (name, matnr) """
         for row in (sheet.row(i) for i in range(1, sheet.nrows)):
-            m = re.search(matno_re, row[1].value)
-            yield row[0].value, m.group('matrikel_no') if m else row[1].value
+            match = re.search(matno_re, row[1].value)
+            if match:
+                yield row[0].value, match.group('matrikel_no')
 
     def sheet_iter_data(sheet):
         """ yields all source code titel and code tuples """
@@ -90,7 +91,7 @@ def converter(infile, usernames=None, number_of_tasks=0,):
 
     # nice!
     name2mat = dict(sheet_iter_meta(meta))
-    assert meta.nrows - 1 == len(name2mat), f'{meta.nrows} != {len(name2mat)}'
+    assert len(name2mat) == len(data), f'{len(name2mat)} names != {len(data)} sheets'  # noqa
 
     # from xls to lists and namedtuples
     # [ [user0, task0_h, code0, ..., taskn, coden ], ..., [...] ]
diff --git a/util/importer.py b/util/importer.py
index 9f60042b62339373c21f7a43fb1a95da937202e1..e83312bee8c6b3e882d91d5e4aa5cca826c8dc6b 100644
--- a/util/importer.py
+++ b/util/importer.py
@@ -216,7 +216,7 @@ def do_load_submission_types():
         for row in csv_rows:
             tid, name, score = (col.strip() for col in row)
             with \
-                open(os.path.join(lsg_dir, tid + '-lsg.c'),
+                open(os.path.join(lsg_dir, tid + '.c'),
                      encoding='utf-8') as lsg, \
                 open(os.path.join(desc_dir, tid + '.html'),
                      encoding='utf-8') as desc:
diff --git a/util/processing.py b/util/processing.py
index 58b0420f835aae0edba04f650d81d49d9ad9f7b7..2ffd2d86010103b110c5cc5aeaa20cd765c6c4f8 100644
--- a/util/processing.py
+++ b/util/processing.py
@@ -1,6 +1,7 @@
 import abc
 import hashlib
 import json
+import logging
 import os
 import re
 import shutil
@@ -14,6 +15,8 @@ try:
 except ModuleNotFoundError:
     from util import testcases
 
+log = logging.getLogger(__name__)
+
 
 def run_cmd(cmd, stdin=None, check=False, timeout=1):
     return subprocess.run(
@@ -37,6 +40,12 @@ def sha1(submission_obj):
     return hashlib.sha1(submission_obj['code'].encode()).hexdigest()
 
 
+def get_submission_id(submission_obj):
+    t = submission_obj['type']
+    m = re.search(r'(a0\d)', t)
+    return m.group(0)
+
+
 class Test(metaclass=abc.ABCMeta):
     """docstring for IliasQuestion"""
 
@@ -127,11 +136,11 @@ class LinkTest(Test):
 
     def run_test(self, submission_obj):
 
-        t = submission_obj['type']
-        m = re.search(r'(a0\d)', t)
+        if submission_obj['type'] not in testcases_dict:
+            return False, 'This program was not required to be executable.'
 
-        ret = run_cmd(
-            f"gcc-7 -o code objects/{m.group(0)}-testing.o code.o")
+        cid = get_submission_id(submission_obj)
+        ret = run_cmd(f"gcc-7 -o ./bin/{cid} objects/{cid}-testing.o code.o")
         return not ret.returncode, ret.stderr
 
 
@@ -143,9 +152,9 @@ class UnitTestTest(Test):
     label_failure = 'UNITTEST_FAILED'
 
     @staticmethod
-    def testcase(i, args, stdout):
+    def testcase(i, args, stdout, cid):
         try:
-            ret = run_cmd("./code %s" % args, check=True, timeout=0.1)
+            ret = run_cmd("./bin/%s %s" % (cid, args), check=True, timeout=0.1)
             assert ret.stdout == stdout
         except AssertionError:
             return False, "Case #{}: [ASSERT FAIL] ./prog {:>2} WAS '{}' SHOULD '{}'".format(  # noqa: E501
@@ -160,10 +169,13 @@ class UnitTestTest(Test):
 
     def run_test(self, submission_obj):
 
-        task = self.testcases_dict[submission_obj['type']]
-        results, messages = zip(*list(self.testcase(i, case, result)
-                                      for i, (case, result) in enumerate(
-            zip(task['cases'], task['results']))))
+        task = testcases_dict[submission_obj['type']]
+        cid = get_submission_id(submission_obj)
+        return_data = [self.testcase(i, case, result, cid)
+                       for i, (case, result) in enumerate(zip(task['cases'],
+                                                              task['results']))
+                       ]
+        results, messages = zip(*return_data)
 
         return all(results), '\n'.join(messages)
 
@@ -171,8 +183,8 @@ class UnitTestTest(Test):
 def process(descfile, binaries, objects, submissions, header, highest_test):
     if isinstance(highest_test, str):
         highestTestClass = Test.available_tests()[highest_test]
-        highestTestClass.testcases_dict = testcases.evaluated_testcases(
-            descfile, binaries)
+        global testcases_dict
+        testcases_dict = testcases.evaluated_testcases(descfile, binaries)
 
     with open(submissions) as submission_file:
         submissions_json = json.JSONDecoder().decode(
@@ -180,10 +192,11 @@ def process(descfile, binaries, objects, submissions, header, highest_test):
 
     # Get something disposable
     path = tempfile.mkdtemp()
-    run_cmd(f'cp -r {objects}  {path}')
+    run_cmd(f'cp -r {objects} {path}')
     run_cmd(f'cp -r {binaries} {path}')
     run_cmd(f'cp -r {header} {path}')
     os.chdir(path)
+    os.makedirs('bin')
 
     def iterate_submissions():
         yield from (obj
@@ -207,6 +220,7 @@ def parseme():
     parser.add_argument('objects')
     parser.add_argument('submissions')
     parser.add_argument('header')
+    parser.add_argument('test')
     return parser.parse_args()
 
 
@@ -214,10 +228,12 @@ if __name__ == '__main__':
     args = parseme()
     testcases_dict = testcases.evaluated_testcases(args.descfile,
                                                    args.binaries)
+
     print(json.dumps(process(args.descfile,
                              args.binaries,
                              args.objects,
                              args.submissions,
-                             args.header, UnitTestTest),
+                             args.header,
+                             args.test),
                      sort_keys=True,
                      indent=4))
diff --git a/util/testcases.py b/util/testcases.py
index 99729af47c06f6fa4d79ad25bace529f0ee955f6..874d8549c7aea1c64f36ef82657d6ad05a86216a 100644
--- a/util/testcases.py
+++ b/util/testcases.py
@@ -62,6 +62,10 @@ def testcases_generator(task, n=10):
     if not syntax:
         return
 
+    if syntax == 'NO INPUT':
+        yield 'NO INPUT'
+        return
+
     yield ''
     yield '0'