diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..5989d64
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,13 @@
+
+little text describing your pull request.
+
+
+Following tickets are finished:
+resolved #115
+resolved #116
+
+
+tested on Pi:
+- [ ] test_R_Server_VLAN.py
+- [ ] test_AP_*.py
+- [ ] test_A_Server_2.py
diff --git a/.travis.yml b/.travis.yml
index e596f5f..7f48a93 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
 language: python
 python:
   - "3.4"
+  - "3.5"
 
 cache:
   directories:
@@ -33,6 +34,7 @@ script:
   - python -m pytest --cov-append --cov=./ framework_unittests/test_A_Server.py
   - python -m pytest --cov-append --cov=./ test_server_alive.py
   - python -m pytest --cov-append --cov=./ framework_unittests/test_A_IPC.py
+  - python -m pytest --cov-append --cov=./ framework_unittests/test_A_cli.py
   - python -m pytest --ff -n auto --cov-append --cov=./  framework_unittests/test_AP_*.py
 
   - "python -m pep8 --exclude=migrations --ignore=E501,E225,E126 */*.py *.py"
diff --git a/cli.py b/cli.py
index 7002ec1..634ae87 100755
--- a/cli.py
+++ b/cli.py
@@ -152,6 +152,22 @@ def create_parsers():
                                help="List of routers", nargs="+")
     parser_online.add_argument("-a", "--all", action="store_true", default=False, help="Apply to all routers")
 
+    # subparser for test set
+    parser_test_set = subparsers.add_parser("start", help="Start a test set")
+    parser_test_set.add_argument("-r", "--routers", metavar="Router ID", type=int, default=[], action="store",
+                                 help="", nargs="+")
+    parser_test_set.add_argument("-a", "--all", action="store_true", default=False, help="Apply to all routers")
+    parser_test_set.add_argument("-s", "--set", metavar="Test set", type=str, default=[], action="store",
+                                 help="Name of set")
+
+    # subparser for test results
+    parser_test_result = subparsers.add_parser("results", help="Manage the test results")
+    parser_test_result.add_argument("-r", "--routers", metavar="Router ID", type=int, default=[], action="store",
+                                    help="", nargs="+")
+    parser_test_result.add_argument("-a", "--all", action="store_true", default=False, help="Apply to all routers")
+    parser_test_result.add_argument("-rm", "--remove", action="store_true", default=False,
+                                    help="Remove all results. Ignoring parameter -r.")
+
     return parser
 
 
@@ -249,6 +265,32 @@ def main():
         online_all = args.all
         server_proxy.router_online(args.routers, online_all)
 
+    elif args.mode == "start":
+        """
+        subparse: start
+        """
+        if args.all:
+            router_id = -1
+        else:
+            router_id = args.routers[0]
+        set_name = args.set
+        server_proxy.start_test_set(router_id, set_name)
+
+    elif args.mode == "results":
+        """
+        subparse: results
+        """
+
+        if args.remove:
+            removed = server_proxy.delete_test_results()
+            print("Removed all " + str(removed) + " results.")
+        else:
+            if args.all:
+                router_id = -1
+            else:
+                router_id = args.routers[0]
+            util.print_test_results(server_proxy.get_test_results(router_id))
+
     else:
         logging.info("Check --help for help")
 
diff --git a/config/configmanager.py b/config/configmanager.py
index cc4985c..440d828 100644
--- a/config/configmanager.py
+++ b/config/configmanager.py
@@ -222,7 +222,7 @@ class ConfigManager:
         return None
 
     @staticmethod
-    def get_test_config() -> []:
+    def _get_test_config() -> []:
         """
         Read the Test Config file
 
@@ -232,29 +232,15 @@ class ConfigManager:
         return ConfigManager.read_file(path)
 
     @staticmethod
-    def get_test_dict() -> []:
+    def get_test_sets() -> []:
         """
         Read the Test Config file
 
         :return: Dictionary with a specific output from the file
         """
-        output = ConfigManager.get_test_config()
+        output = ConfigManager._get_test_config()
         return output
 
-    @staticmethod
-    def get_test_list() -> []:
-        """
-        Read the Test Config file
-
-        :return: List with a specific output from the file
-        """
-        output = ConfigManager.get_test_config()
-        test_list = []
-        for x in output:
-            for v in x.values():
-                test_list.append(v)
-        return test_list
-
     @staticmethod
     def get_firmware_config() -> []:
         """
diff --git a/firmware/firmware_handler.py b/firmware/firmware_handler.py
index a28c6c8..e0c4dd0 100644
--- a/firmware/firmware_handler.py
+++ b/firmware/firmware_handler.py
@@ -138,6 +138,8 @@ class FirmwareHandler:
         hashs = []
         non_parsed_firmwares = self._read_firmwares_from_manifest(release_model)
         for firmware in non_parsed_firmwares:
+            if firmware.find("---\n") != -1:  # skip the end of the file
+                continue
             firmware_name = "gluon" + firmware.split("gluon")[1].split("-sysupgrade")[0] + "-" + \
                             FirmwareHandler.UPDATE_TYPE + "." + firmware.split(".")[-1].replace("\n", "")
             hash_firmware = firmware.split(' ')[4]
diff --git a/requirements.txt b/requirements.txt
index 33608a8..595564a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,18 +1,17 @@
-pyflakes
-pep8
-pyyaml
-typing
-paramiko
-pyroute2
-pylint
-recommonmark
-sphinx
-selenium
-sphinx_rtd_theme
-pytest-cov
-pytest-cache
-pytest-xdist
-codecov
-pycrypto
-ecdsa
-ipaddress
\ No newline at end of file
+pyflakes==1.0.0
+pep8==1.6.2
+PyYAML==3.11
+typing==3.5.0.1
+paramiko==1.16.0
+pyroute2==0.3.15
+pylint==1.5.3
+recommonmark==0.4.0
+Sphinx==1.3.3
+sphinx-rtd-theme==0.1.9
+selenium==2.48.0
+pytest-cov==2.2.0
+pytest-cache==1.0
+pytest-xdist==1.13.1
+pycrypto==2.6.1
+ecdsa==0.13
+ipaddress==1.0.16
\ No newline at end of file
diff --git a/server/server.py b/server/server.py
index d494619..9c018da 100755
--- a/server/server.py
+++ b/server/server.py
@@ -9,6 +9,7 @@ from log.loggersetup import LoggerSetup
 import logging
 from concurrent.futures import ThreadPoolExecutor
 from unittest.result import TestResult
+import importlib
 from threading import Event, Semaphore
 from network.remote_system import RemoteSystem, RemoteSystemJob
 from unittest import defaultTestLoader
@@ -45,7 +46,7 @@ class Server(ServerProxy):
 
     # runtime vars
     _routers = []  # all registered routers on the system
-    _reports = []  # all test reports
+    _test_results = []  # all test reports in form (router.id, str(test), TestResult)
     _stopped = False  # marks if the server is still running
 
     _max_subprocesses = 0  # will be set at start. describes how many Processes are needed in the Pool
@@ -56,6 +57,7 @@ class Server(ServerProxy):
     _task_pool = None  # multiprocessing.pool.Pool for task execution
     _job_wait_executor = None  # ThreadPoolExecutor for I/O handling on tasks
     _semaphore_task_management = Semaphore(1)
+    _test_sets = {}  # Dict[List[str]]
 
     # NVAssistent
     _nv_assistent = None
@@ -135,6 +137,7 @@ class Server(ServerProxy):
     def __load_configuration(cls):
         logging.debug("Load configuration")
         cls._routers = ConfigManager.get_router_manual_list()
+        cls._test_sets = ConfigManager.get_test_sets()
 
     @classmethod
     def stop(cls) -> None:
@@ -247,31 +250,28 @@ class Server(ServerProxy):
                 return cls.__start_task(remote_sys, remote_job)
 
     @classmethod
-    def start_test(cls, router_id: int, test_name: str) -> bool:
+    def start_test_set(cls, router_id: int, test_set_name: str) -> bool:
         """
         Start an specific test on a router
 
-        :param router_id: The id of the router on which the test will run
-        :param test_name: The name of the test to execute
+        :param router_id: The id of the router on which the test will run.
+        If id is -1 the test will be executed on all routers.
+        :param test_set_name: The name of the test set to execute
         :return: True if test was successful added in the queue
         """
-        router = cls.get_router_by_id(router_id)
-        if router is None:
-            logging.error("Router ID unknown")
-            return False
-
-        # TODO: Testverwaltung - ermittlung des passenden Tests #36
-        # cls.get_test_by_name
-        from firmware_tests.connection_test import ConnectionTest, VeryLongTest
-        if test_name == "ConnectionTest":
-            demo_test = ConnectionTest  # Important: Param is a class and not an object
-        elif test_name == "VeryLongTest":
-            demo_test = VeryLongTest
-        else:
-            logging.error("Testname unknown")
-            return False
 
-        return cls.__start_task(router, demo_test)
+        for file_name in cls._test_sets[test_set_name]:
+            module = importlib.import_module("firmware_tests." + file_name)
+            import inspect
+
+            for name, obj in inspect.getmembers(module):
+                if inspect.isclass(obj) and issubclass(obj, FirmwareTest) and name != "FirmwareTest":
+                    if router_id == -1:
+                        for router in cls._routers:
+                            cls.__start_task(router, obj)
+                    else:
+                        cls.__start_task(cls.get_router_by_id(router_id), obj)
+        return True
 
     @classmethod
     def __start_task(cls, remote_sys: RemoteSystem, job: Union[RemoteSystemJobClass, RemoteSystemJob]) -> bool:
@@ -282,7 +282,7 @@ class Server(ServerProxy):
 
         :param remote_sys: the RemoteSystem
         :param job: the Job
-        :return: true if job directly started, false if
+        :return: true if job directly started, false if not
         """
         assert(cls._pid == os.getpid())
         # Check if it is the the same PID as the PID Process which started the ProcessPool
@@ -390,7 +390,7 @@ class Server(ServerProxy):
             logging.debug("%sTest done " + str(test), LoggerSetup.get_log_deep(1))
             logging.debug("%sFrom " + str(router), LoggerSetup.get_log_deep(2))
 
-            cls._reports.append(result)
+            cls._test_results.append((router.id, str(test), result))
         except Exception as e:
             # TODO #105
             logging.error("%sTest raised an Exception: " + str(e), LoggerSetup.get_log_deep(1))
@@ -401,11 +401,11 @@ class Server(ServerProxy):
             # result.addError(None, (type(exception), exception, None))
             # TODO exception handling for failed Tests
 
-            cls._reports.append(result)
+            cls._test_results.append((router.id, str(test), result))
 
         finally:
             cls.set_running_task(router, None)
-            # logging.debug(str(cls._reports))
+            # logging.debug(str(cls._test_results))
             # start next test in the queue
             cls.__start_task(router, None)
 
@@ -468,10 +468,6 @@ class Server(ServerProxy):
         :return: List is a copy of the original list.
         """
 
-        # check if list is still valid
-        for router in cls._routers:
-            assert isinstance(router, Router)
-
         return cls._routers.copy()
 
     @classmethod
@@ -502,13 +498,33 @@ class Server(ServerProxy):
         return cls._running_tasks.copy()
 
     @classmethod
-    def get_reports(cls) -> []:
+    def get_test_results(cls, router_id: int = -1) -> [(int, str, TestResult)]:
+        """
+        Returns the firmware test results for the router
+
+        :param router_id: the specific router or all router if id = -1
+        :return: List of results
+        """
+
+        if router_id == -1:
+            return cls._test_results
+        else:
+            results = []
+            for result in cls._test_results:
+                if result[0] == router_id:
+                    results.append(result)
+            return results
+
+    @classmethod
+    def delete_test_results(cls) -> int:
         """
-        Returns the test results.
+        Remove all test results
 
-        :return: List of reports
+        :return: Number of deleted results
         """
-        return cls._reports
+        size_results = len(cls._test_results)
+        cls._test_results = []
+        return size_results
 
     @classmethod
     def get_tests(cls) -> List[FirmwareTestClass]:
diff --git a/server/serverproxy.py b/server/serverproxy.py
index 102cb67..a072aa8 100644
--- a/server/serverproxy.py
+++ b/server/serverproxy.py
@@ -1,6 +1,7 @@
 from abc import ABCMeta, abstractclassmethod
 from typing import List
 from router.router import Router
+from unittest import TestResult
 
 
 class ServerProxy(metaclass=ABCMeta):
@@ -11,12 +12,12 @@ class ServerProxy(metaclass=ABCMeta):
     the return value is given by copy and not by reference!
     """""
     @abstractclassmethod
-    def start_test(self, router_id: int, test_name: str) -> bool:
+    def start_test_set(self, router_id: int, test_set_name: str) -> bool:
         """
         Start an specific test on a router
 
         :param router_id: The id of the router on which the test will run
-        :param test_name: The name of the test to execute
+        :param test_set_name: The name of the test set to execute
         :return: True if test was successful added in the queue
         """
         pass
@@ -50,11 +51,21 @@ class ServerProxy(metaclass=ABCMeta):
         pass
 
     @abstractclassmethod
-    def get_reports(self) -> []:
+    def get_test_results(self, router_id: int = -1) -> [(int, str, TestResult)]:
         """
-        Returns the test results.
+        Returns the firmware test results for the router
 
-        :return: List of reports
+        :param router_id: the specific router or all router if id = -1
+        :return: List of results
+        """
+        pass
+
+    @abstractclassmethod
+    def delete_test_results(self) -> int:
+        """
+        Remove all test results
+
+        :return: Number of deleted results
         """
         pass
 
diff --git a/util/cli_util.py b/util/cli_util.py
index abac537..ad95e15 100644
--- a/util/cli_util.py
+++ b/util/cli_util.py
@@ -1,4 +1,5 @@
 import logging
+from unittest import TestResult
 
 
 class CLIUtil:
@@ -11,6 +12,7 @@ class CLIUtil:
     def print_dynamic_table(content, headers):
         """
         prints a dynamically formatted table
+
         :param content: list of lists of data
         :param headers: list of headers
         """
@@ -48,6 +50,7 @@ class CLIUtil:
     def print_status(self, routers, headers):
         """
         prints the status of all routers
+
         :param routers: list of routers
         :param headers: list of headers
         """
@@ -62,6 +65,7 @@ class CLIUtil:
     def print_header():
         """
         prints header for the command line
+
         :return:
         """
         print("\v\t" + OutputColors.bold + "Freifunk Testframework\v" + OutputColors.clear)
@@ -70,6 +74,7 @@ class CLIUtil:
     def return_progressbar(router, tid, percentage):
         """
         returns the visual progress of a test on a router
+
         :param router: router name
         :param tid: ID of test
         :param percentage: progress of test in percent
@@ -84,6 +89,7 @@ class CLIUtil:
     def print_list(content):
         """
         prints a simple list(table) sorted by the first row and formatted
+
         :param content: list of list (table)
         :return:
         """
@@ -107,6 +113,7 @@ class CLIUtil:
     def print_router(router_list):
         """
         prints a detailed list of info on a router
+
         :param router_list: list of info on router
         :return:
         """
@@ -114,6 +121,25 @@ class CLIUtil:
         for elem in router_list:
             print("{:<15}{:<20}".format(str(elem[0]) + ":", str(elem[1])))
 
+    @staticmethod
+    def print_test_results(result_list: [(int, str, TestResult)]):
+        """
+        Prints a the TestResult list
+
+        :param result_list:
+        :return:
+        """
+        headers = ["Router ID", "Test", "(S|F|E)"]
+        content = []
+        print("------Testresults------")
+        for result in result_list:
+            content.append([str(result[0]), result[1], "(" + str(result[2].testsRun - len(result[2].failures) -
+                                                                 len(result[2].errors)) +
+                            "|" + str(len(result[2].failures)) +
+                            "|" + str(len(result[2].errors)) + ")"])
+
+        CLIUtil.print_dynamic_table(content, headers)
+
 
 class OutputColors:
     green = '\033[92m'

(29) TestResult Verwaltung
Ziel:
Testergebnisse sollten verwaltet werden können über die CLI.

Aufgabe: 
Die CLI muss TestResults ausgeben können und sie auch ggf. löschen können.

MetaDaten zum dazugehörigen Test können womöglich schon von der Testverwaltung #36 geladen werden. Beim Anzeigen der Tests oder direkt nach der Ausführung + Speicherung.

Vor der Umsetzung ist zu überlegen, ob die TestResults über das IPC bezogen werden oder über das Dateisystem oder einer externen DB.
Ggf. kann also in diesem Ticket auch die QS Maßnahme für die Persistenz der TestResults erfolgen und somit insgesamt Zeit gespart werden.

Infos:
Die Testergebnisse werden während der Laufzeit im Server in der Liste _reports gespeichert.

DatenTyp: unittest.TestResult

Unteraufgaben:
- [x] TestResults ausgeben auf der CLI
- [x] TestResults löschen über die CLI

Neue UserStory/Tickets:
- [ ] Optional: TestResults persistent
- [ ] Optional: MetaDaten zum TestResult
diff --git a/config/test_config.yaml b/config/test_config.yaml
index b977360..3d8be05 100644
--- a/config/test_config.yaml
+++ b/config/test_config.yaml
@@ -1,3 +1,3 @@
 --- # config for the Test-Files
-- {Test_Name: Test1, Params: [Param1]}
-- {Test_Name: Test2, Params: [Param1, Param2]}
\ No newline at end of file
+set_1: [demo_1]
+set_2: [demo_2, demo_1]
\ No newline at end of file
diff --git a/firmware_tests/__init__.py b/firmware_tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/firmware_tests/connection_test.py b/firmware_tests/connection_test.py
deleted file mode 100644
index e509601..0000000
--- a/firmware_tests/connection_test.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from server.test import FirmwareTest
-import os
-
-
-class ConnectionTest(FirmwareTest):
-    """
-    This is a demo test - only to test the functionality of the framework itself.
-    """""
-    def test_self_router(self):
-        # print(str(self.router))
-        assert self.remote_system.id == 0
-
-    def test_ping_static(self):
-        # print("connection test: " + str(getpid()))
-        # os.system("ip a")
-        response = os.system("ping -t 5 -c 1 " + "www.p8h.de")
-        assert response == 0  # not working because no normal eth0 stack available
-        # from subprocess import Popen, PIPE
-        # process = Popen(["ip", "a"], stdout=PIPE, stderr=PIPE)
-        # stdout, sterr = process.communicate()
-        # print(str(stdout.decode('utf-8')))
-        # response = os.system("ping -c 1 " + "p8h.de")
-        # assert response == #0
-
-    def test_ping_router(self):
-        hostname = self.remote_system.ip
-        response = os.system("ping -t 5 -c 1 " + hostname)
-        print(hostname)
-        assert response == 0
-
-
-class VeryLongTest(FirmwareTest):
-    """
-    This is a demo test - only to test the functionality of the framework itself and it is very short..
-    """""
-    def test_very_long_test(self):
-        lol = True
-        assert lol
-        assert not not lol
-
-    def test_buzz1(self):
-        lol = True
-        assert lol
-        assert not not lol
-
-    def test_foo2(self):
-        lol = True
-        assert lol
-        assert not not lol
diff --git a/firmware_tests/demo_1.py b/firmware_tests/demo_1.py
new file mode 100644
index 0000000..6f3a139
--- /dev/null
+++ b/firmware_tests/demo_1.py
@@ -0,0 +1,14 @@
+from server.test import FirmwareTest
+import os
+
+
+class ConnectionTest(FirmwareTest):
+    """
+    This is a demo test - only to test the functionality of the framework itself.
+    """""
+    def test_self_router(self):
+        assert self.remote_system.id == 0
+
+    def test_ping_local(self):
+        response = os.system("ping -t 5 -c 1 " + "localhost")
+        assert response == 0
diff --git a/firmware_tests/demo_2.py b/firmware_tests/demo_2.py
new file mode 100644
index 0000000..a63e2b6
--- /dev/null
+++ b/firmware_tests/demo_2.py
@@ -0,0 +1,21 @@
+from server.test import FirmwareTest
+
+
+class StupidTest(FirmwareTest):
+    """
+    This is a demo test - only to test the functionality of the framework itself and it is very short..
+    """""
+    def test_not_very_long_test(self):
+        lol = True
+        assert lol
+        assert not not lol
+
+    def test_buzz1(self):
+        lol = True
+        assert lol
+        assert not not lol
+
+    def test_foo2(self):
+        lol = True
+        assert lol
+        assert not not lol
diff --git a/framework_unittests/configs/config_no_vlan/server_config.yaml b/framework_unittests/configs/config_no_vlan/server_config.yaml
index 37a304b..c38a8d8 100644
--- a/framework_unittests/configs/config_no_vlan/server_config.yaml
+++ b/framework_unittests/configs/config_no_vlan/server_config.yaml
@@ -4,7 +4,5 @@ Server_Name: TestServer,
 # set the level for what you want to log: NOTSET: 0, DEBUG: 10, INFO: 20, WARNING: 30, ERROR: 40, CRITICAL: 50
 Log_Level: 10,
 # set True or False
-Vlan_On: False,
-# set True or False to use output on every console
-Use_Console_Output: False
+Vlan_On: False
 }
\ No newline at end of file
diff --git a/framework_unittests/configs/config_no_vlan/test_config.yaml b/framework_unittests/configs/config_no_vlan/test_config.yaml
index b977360..3d8be05 100644
--- a/framework_unittests/configs/config_no_vlan/test_config.yaml
+++ b/framework_unittests/configs/config_no_vlan/test_config.yaml
@@ -1,3 +1,3 @@
 --- # config for the Test-Files
-- {Test_Name: Test1, Params: [Param1]}
-- {Test_Name: Test2, Params: [Param1, Param2]}
\ No newline at end of file
+set_1: [demo_1]
+set_2: [demo_2, demo_1]
\ No newline at end of file
diff --git a/framework_unittests/test_AP_Yaml.py b/framework_unittests/test_AP_Yaml.py
index 23863a2..f639ac8 100644
--- a/framework_unittests/test_AP_Yaml.py
+++ b/framework_unittests/test_AP_Yaml.py
@@ -79,31 +79,6 @@ class MyTestCase(unittest.TestCase):
         data = ConfigManager.get_server_property("Server_Name")
         self.assertEqual(data, "TestServer", "test_Yaml: Wrong size of the List")
 
-    # test tests
-    def test_config_test(self):
-        """
-        Tests the test config
-        :return: Tests results
-        """
-        data = ConfigManager.get_test_config()
-        self.assertEqual(len(data), 2, "test_Yaml: Wrong size of the List")
-
-    def test_config_test_dict(self):
-        """
-        Tests the test config
-        :return: Tests results
-        """
-        data = ConfigManager.get_test_dict()
-        self.assertEqual(len(data), 2, "test_Yaml: Wrong size of the List")
-
-    def test_config_test_list(self):
-        """
-        Tests the test config
-        :return: Tests results
-        """
-        data = ConfigManager.get_test_list()
-        self.assertEqual(len(data), 4, "test_Yaml: Wrong size of the List")
-
     # firmware tests
     def test_firmware_property(self):
         """
diff --git a/framework_unittests/test_A_cli_util.py b/framework_unittests/test_AP_cli_util.py
similarity index 100%
rename from framework_unittests/test_A_cli_util.py
rename to framework_unittests/test_AP_cli_util.py
diff --git a/framework_unittests/test_A_IPC.py b/framework_unittests/test_A_IPC.py
index 9f370dd..790d852 100644
--- a/framework_unittests/test_A_IPC.py
+++ b/framework_unittests/test_A_IPC.py
@@ -38,7 +38,7 @@ class TestIPC(TestCase):
         ipc_client.connect(False)
         server_proxy = ipc_client.get_server_proxy()
 
-        rep = server_proxy.get_reports()
+        rep = server_proxy.get_test_results()
         #  print(rep)
         assert rep[1].text == "test"
 
@@ -76,7 +76,7 @@ class DummyServer(ServerProxy):
         return ["lol"]
 
     @classmethod
-    def get_reports(cls) -> []:
+    def get_test_results(cls) -> []:
         d = DummyObject("test")
         return [id(d), d]
 
@@ -127,3 +127,11 @@ class DummyServer(ServerProxy):
     @classmethod
     def get_routers_task_queue_size(cls, router_id: int) -> [str]:
         pass
+
+    @classmethod
+    def start_test_set(cls, router_id: int, test_set_name: str) -> bool:
+        pass
+
+    @classmethod
+    def delete_test_results(cls) -> int:
+        pass
diff --git a/framework_unittests/test_A_Server_2.py b/framework_unittests/test_A_Server_2.py
index 870fccf..e507cc8 100644
--- a/framework_unittests/test_A_Server_2.py
+++ b/framework_unittests/test_A_Server_2.py
@@ -11,7 +11,7 @@ import socket
 def block_until_server_is_online():
     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     print("wait", flush=True)
-    while not not sock.connect_ex(('localhost', 5000)):
+    while sock.connect_ex(('localhost', 5000)):
         time.sleep(3)
         print('.', end="", flush=True)
     sock.close()
@@ -32,7 +32,7 @@ class ServerCore(object):
         for i in range(2):  # do it two times to be sure
             routers = server_proxy.get_routers()
             for router in routers:
-                while not not server_proxy.get_routers_task_queue_size(router.id):
+                while server_proxy.get_routers_task_queue_size(router.id):
                     time.sleep(2)
                     print('.', end="", flush=True)
 
@@ -47,7 +47,7 @@ class ServerCore(object):
         for i in range(2):  # do it two times to be sure
             routers = self.server_proxy.get_routers()
             for router in routers:
-                while not not self.server_proxy.get_routers_task_queue_size(router.id):
+                while self.server_proxy.get_routers_task_queue_size(router.id):
                     time.sleep(2)
                     print('.', end="", flush=True)
 
@@ -56,38 +56,54 @@ class ServerCore(object):
         assert len(routers) != 0
         assert isinstance(routers[0], Router)
 
-    def test_little_self_check(self):
-        started = self.server_proxy.start_test(0, "ConnectionTest")
+    def test_test_set(self):
+        started = self.server_proxy.start_test_set(0, "set_2")
 
         assert started
 
         # wait until tests are done, assumes that exactly two tests are already finished
-        while not self.server_proxy.get_reports():
+        while not len(self.server_proxy.get_test_results()) == 2:
             time.sleep(2)
             print('.', end="", flush=True)
 
-        reports = self.server_proxy.get_reports()
-        assert len(reports) != 0
-        assert len(reports[-1].errors) == 0  # check last report
+        reports = self.server_proxy.get_test_results()
+        assert len(reports) == 2
+        assert len(reports[-1][2].errors) == 0  # check last report
+
+        started = self.server_proxy.start_test_set(0, "set_1")
 
-    def test_long_self_check(self):
-        started = self.server_proxy.start_test(0, "ConnectionTest")
         assert started
-        started2 = self.server_proxy.start_test(0, "VeryLongTest")
-        assert not started2
-        if started and not started2:
 
-            while not len(self.server_proxy.get_reports()) == 3:
-                time.sleep(2)
-                print('.', end="", flush=True)
+        # wait until tests are done, assumes that exactly two tests are already finished
+        while not len(self.server_proxy.get_test_results()) == 3:
+            time.sleep(2)
+            print('.', end="", flush=True)
+
+        reports = self.server_proxy.get_test_results()
+        assert len(reports) == 3
+        assert len(reports[2][-1].errors) == 0  # check last report
+
+    def test_test_results(self):
+        self.server_proxy.delete_test_results()
+
+        started = self.server_proxy.start_test_set(0, "set_2")
+        assert started
+
+        while not len(self.server_proxy.get_test_results()) == 2:
+            time.sleep(2)
+            print('.', end="", flush=True)
 
-            self.server_proxy.stop_all_tasks()
+        reports = self.server_proxy.get_test_results()
 
-            reports = self.server_proxy.get_reports()
-            assert reports[-1].wasSuccessful()  # check last report
+        for report in reports:
+            assert report[0] == 0
+            assert report[1] != ""
+            assert len(report[2].errors) == 0
 
-    # def test_jobs(self):
-    #    raise NotImplemented
+        removed_results = self.server_proxy.delete_test_results()
+        assert len(reports) == removed_results
+        time.sleep(0.5)
+        assert not len(self.server_proxy.get_test_results())
 
 
 class ServerTestCase2(ServerCore, unittest.TestCase):
diff --git a/framework_unittests/test_A_cli.py b/framework_unittests/test_A_cli.py
index 2184b19..f00dceb 100644
--- a/framework_unittests/test_A_cli.py
+++ b/framework_unittests/test_A_cli.py
@@ -124,7 +124,7 @@ class TestCLItoServerConnection(unittest.TestCase):
     @staticmethod
     def serverStartWithParams():
         base_dir = os.path.dirname(os.path.dirname(__file__))  # This is your Project Root
-        config_path = os.path.join(base_dir, 'tests/configs/config_no_vlan')  # Join Project Root with config
+        config_path = os.path.join(base_dir, 'framework_unittests/configs/config_no_vlan')
         Server.start(config_path=config_path)
 
     def setUp(self):
@@ -136,8 +136,49 @@ class TestCLItoServerConnection(unittest.TestCase):
         response = os.system(self.path_cli)
         assert response == 0
 
+    def test_cli_start_test_set(self):
+        response = os.system(self.path_cli + " start -s set_1 -r 0")
+        assert response == 0
+
+        # assumes that there is only one test in the set
+        while self.server_proxy.get_routers_task_queue_size(0):
+                    time.sleep(2)
+                    print('.', end="", flush=True)
+        assert len(self.server_proxy.get_test_results())
+
+        response = os.system(self.path_cli + " start -s set_1 -a")
+        assert response == 0
+
+        routers = self.server_proxy.get_routers()
+        for router in routers:
+            while self.server_proxy.get_routers_task_queue_size(router.id):
+                    time.sleep(2)
+                    print('.', end="", flush=True)
+        assert len(self.server_proxy.get_test_results()) == len(routers) + 1
+
+    def test_cli_test_results(self):
+        assert not os.system(self.path_cli + " results -rm -a")
+        os.system(self.path_cli + " start -s set_1 -a")
+
+        routers = self.server_proxy.get_routers()
+        for router in routers:
+            while self.server_proxy.get_routers_task_queue_size(router.id):
+                    time.sleep(2)
+                    print('.', end="", flush=True)
+
+        response = os.system(self.path_cli + " results -r 0")
+        assert response == 0
+        response = os.system(self.path_cli + " results -a")
+        assert response == 0
+
+        response = os.system(self.path_cli + " results -rm")
+        assert response == 0
+        response = os.system(self.path_cli + " results -rm -a")
+        assert response == 0
+        response = os.system(self.path_cli + " results -rm -r 0")
+        assert response == 0
+        assert not len(self.server_proxy.get_test_results())
+
     def test_get_version(self):
         version = self.server_proxy.get_server_version()
         assert version == Server.VERSION
-
-        # TODO compare Version with Version from Server.VERSION and ./cli version (exists?)

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-cache pytest-xdist
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
astroid==3.3.9
attrs==25.3.0
babel==2.17.0
bcrypt==4.3.0
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
codecov==2.1.13
commonmark==0.9.1
coverage==7.8.0
cryptography==44.0.2
dill==0.3.9
docutils==0.21.2
ecdsa==0.19.1
exceptiongroup==1.2.2
execnet==2.1.1
h11==0.14.0
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
ipaddress==1.0.23
isort==6.0.1
Jinja2==3.1.6
MarkupSafe==3.0.2
mccabe==0.7.0
outcome==1.3.0.post0
packaging==24.2
paramiko==3.5.1
pep8==1.7.1
platformdirs==4.3.7
pluggy==1.5.0
pycparser==2.22
pycrypto==2.6.1
pyflakes==3.3.2
Pygments==2.19.1
pylint==3.3.6
PyNaCl==1.5.0
pyroute2==0.8.1
PySocks==1.7.1
pytest==8.3.5
pytest-cache==1.0
pytest-cov==6.0.0
pytest-xdist==3.6.1
PyYAML==6.0.2
recommonmark==0.7.1
requests==2.32.3
selenium==4.30.0
six==1.17.0
sniffio==1.3.1
snowballstemmer==2.2.0
sortedcontainers==2.4.0
Sphinx==7.4.7
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
-e git+https://github.com/PumucklOnTheAir/TestFramework.git@8d1c52e7c2569834f12f7a0739748e7333ca0099#egg=TestFramework
tomli==2.2.1
tomlkit==0.13.2
trio==0.29.0
trio-websocket==0.12.2
typing==3.7.4.3
typing_extensions==4.13.0
urllib3==2.3.0
websocket-client==1.8.0
wsproto==1.2.0
zipp==3.21.0

name: TestFramework
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - astroid==3.3.9
      - attrs==25.3.0
      - babel==2.17.0
      - bcrypt==4.3.0
      - certifi==2025.1.31
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - codecov==2.1.13
      - commonmark==0.9.1
      - coverage==7.8.0
      - cryptography==44.0.2
      - dill==0.3.9
      - docutils==0.21.2
      - ecdsa==0.19.1
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - h11==0.14.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - ipaddress==1.0.23
      - isort==6.0.1
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - outcome==1.3.0.post0
      - packaging==24.2
      - paramiko==3.5.1
      - pep8==1.7.1
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycparser==2.22
      - pycrypto==2.6.1
      - pyflakes==3.3.2
      - pygments==2.19.1
      - pylint==3.3.6
      - pynacl==1.5.0
      - pyroute2==0.8.1
      - pysocks==1.7.1
      - pytest==8.3.5
      - pytest-cache==1.0
      - pytest-cov==6.0.0
      - pytest-xdist==3.6.1
      - pyyaml==6.0.2
      - recommonmark==0.7.1
      - requests==2.32.3
      - selenium==4.30.0
      - six==1.17.0
      - sniffio==1.3.1
      - snowballstemmer==2.2.0
      - sortedcontainers==2.4.0
      - sphinx==7.4.7
      - sphinx-rtd-theme==3.0.2
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - tomli==2.2.1
      - tomlkit==0.13.2
      - trio==0.29.0
      - trio-websocket==0.12.2
      - typing==3.7.4.3
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - websocket-client==1.8.0
      - wsproto==1.2.0
      - zipp==3.21.0
prefix: /opt/conda/envs/TestFramework

framework_unittests/test_A_IPC.py::TestIPC::test_proxy_object
framework_unittests/test_A_Server_2.py::ServerTestCase2::test_test_set framework_unittests/test_A_cli.py::TestCLItoServerConnection::test_get_version firmware_tests/demo_1.py::ConnectionTest::test_ping_local firmware_tests/demo_1.py::ConnectionTest::test_self_router framework_unittests/test_A_Server_2.py::ServerTestCase2::test_get_routers framework_unittests/test_A_Server_2.py::ServerTestCase2::test_test_results framework_unittests/test_A_cli.py::TestCLItoServerConnection::test_cli_connected framework_unittests/test_A_cli.py::TestCLItoServerConnection::test_cli_start_test_set framework_unittests/test_A_cli.py::TestCLItoServerConnection::test_cli_test_results
firmware_tests/demo_2.py::StupidTest::test_buzz1 firmware_tests/demo_2.py::StupidTest::test_foo2 firmware_tests/demo_2.py::StupidTest::test_not_very_long_test framework_unittests/test_AP_Yaml.py::MyTestCase::test_config_router_auto framework_unittests/test_AP_Yaml.py::MyTestCase::test_config_router_auto_with_length framework_unittests/test_AP_Yaml.py::MyTestCase::test_config_router_manual framework_unittests/test_AP_Yaml.py::MyTestCase::test_config_server framework_unittests/test_AP_Yaml.py::MyTestCase::test_config_server_dict framework_unittests/test_AP_Yaml.py::MyTestCase::test_config_server_list framework_unittests/test_AP_Yaml.py::MyTestCase::test_config_server_prop framework_unittests/test_AP_Yaml.py::MyTestCase::test_firmware_config framework_unittests/test_AP_Yaml.py::MyTestCase::test_firmware_dict framework_unittests/test_AP_Yaml.py::MyTestCase::test_firmware_list framework_unittests/test_AP_Yaml.py::MyTestCase::test_firmware_property framework_unittests/test_AP_Yaml.py::MyTestCase::test_get_config_router_auto framework_unittests/test_AP_Yaml.py::MyTestCase::test_get_config_router_manual framework_unittests/test_AP_Yaml.py::MyTestCase::test_web_interface_config framework_unittests/test_AP_Yaml.py::MyTestCase::test_web_interface_dict framework_unittests/test_AP_Yaml.py::MyTestCase::test_web_interface_list framework_unittests/test_AP_cli_util.py::MyTestCase::test_create_util framework_unittests/test_A_IPC.py::TestIPC::test_exist_stop_event framework_unittests/test_A_cli.py::CLITestClass::test_no_args framework_unittests/test_A_cli.py::CLITestClass::test_reboot framework_unittests/test_A_cli.py::CLITestClass::test_status framework_unittests/test_A_cli.py::CLITestClass::test_sysupdate framework_unittests/test_A_cli.py::CLITestClass::test_sysupgrade framework_unittests/test_A_cli.py::CLITestClass::test_update_info framework_unittests/test_A_cli.py::CLITestClass::test_webconfig
cli.py firmware/firmware_handler.py config/configmanager.py server/server.py .travis.yml .github/PULL_REQUEST_TEMPLATE.md util/cli_util.py server/serverproxy.py requirements.txt
cli.py firmware/firmware_handler.py config/configmanager.py server/server.py .travis.yml .github/PULL_REQUEST_TEMPLATE.md util/cli_util.py server/serverproxy.py requirements.txt