Repository: exasol/integration-test-docker-environment (language: python)

Issue:
Test container does not get restarted if it gets rebuild and reuse is activated
Situation:

- A test environment existed
- test_environment_reuse was active
- requirements file for the test_container was changed
- The test_container image got build but not restarted

Remarks:

- Probably, the SpawnTestContainer class doesn't check if the test_container image was newly build


Discussion / hints:

Fix patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..2805d8227
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,8 @@
+### All Submissions:
+
+* [ ] Is the title of the Pull Request correct?
+* [ ] Is the title of the corresponding issue correct?
+* [ ] Have you updated the changelog?
+* [ ] Have you checked to ensure there aren't other open [Pull Requests](../../../../pulls) for the same update/change? <!-- markdown-link-check-disable-line --> 
+* [ ] Are you mentioning the issue which this PullRequest fixes ("Fixes...")
+* [ ] Before you merge don't forget to run all tests for all Exasol version, by adding `[run all tests]` to the commit message
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index 00d7f73a8..000000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,113 +0,0 @@
-name: CI
-
-on: [push, pull_request]
-
-jobs:
-  test-docker-starter:
-    strategy:
-      fail-fast: false
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-
-    - name: Build new Docker image
-      run: bash build_test_environment_docker_runner.sh 
-
-    - name: Test ./start-test-env 
-      run: ./start-test-env --help
-
-  test-all:
-    strategy:
-      fail-fast: false
-      matrix:
-        exasol_version:
-        - "default"
-        python_version:
-        - 3.6
-        - 3.7
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-
-    - name: Setup Python 3.6 for integration-test-docker-environment
-      uses: actions/setup-python@v2
-      with:
-        python-version: 3.6
-    - name: Install Poetry
-      uses: abatilo/actions-poetry@v2.0.0
-
-    - name: Run all tests
-      run: ./run_all_tests.sh
-      env:
-        EXASOL_VERSION: "${{ matrix.exasol_version }}"
-
-    - name: Test ./start-test-env-with-poetry 
-      run: ./start-test-env-with-poetry --help
-
-  test-db-versions:
-    strategy:
-      fail-fast: false
-      matrix:
-        exasol_version:
-        - 7.1.0-d1
-        - 7.0.12
-        - 7.0.11
-        - 7.0.10
-        - 7.0.9
-        - 7.0.8
-        - 7.0.7
-        - 7.0.6
-        - 7.0.5
-        - 7.0.4
-        - 7.0.3
-        - 7.0.2
-        - 7.0.1
-        - 7.0.0
-        - 6.2.16-d1
-        - 6.2.15-d1
-        - 6.2.14-d1
-        - 6.2.13-d1
-        - 6.2.12-d1
-        - 6.2.11-d1
-        - "default"
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-
-    - name: Setup Python 3.6 for integration-test-docker-environment
-      uses: actions/setup-python@v2
-      with:
-        python-version: 3.6
-    - name: Install Poetry
-      uses: abatilo/actions-poetry@v2.0.0
-
-    - name: Run tests
-      run: ./run_test.sh exasol_integration_test_docker_environment/test/test_test_environment.py
-      env:
-        EXASOL_VERSION: "${{ matrix.exasol_version }}"
-      if: github.ref != 'refs/heads/master' && !contains(github.event.head_commit.message, '[run all tests]') && github.event_name == 'push'
-    - name: Run all tests
-      run: ./run_all_tests.sh
-      env:
-        EXASOL_VERSION: "${{ matrix.exasol_version }}"
-      if: github.ref == 'refs/heads/master' || contains(github.event.head_commit.message, '[run all tests]') || github.event_name != 'push'
-  publish:
-    if: github.ref == 'refs/heads/master'
-    runs-on: ubuntu-latest
-    environment: publish
-    # needs:
-    # - test-db-versions
-    # - test-all
-    # - test-docker-starter
-    steps:
-    - uses: actions/checkout@v2
-    - name: Build new Docker image
-      run: bash build_test_environment_docker_runner.sh 
-    - name: Docker login
-      run: echo "$SECRET_DOCKER_TOKEN" | docker login --username "$SECRET_DOCKER_USER_NAME" --password-stdin 
-      env: # Set the secret as an input
-        SECRET_DOCKER_USER_NAME: ${{ secrets.DOCKER_USER_NAME }}
-        SECRET_DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
-    - name: Push new Docker image
-      run: bash push_test_environment_docker_runner.sh 
-
diff --git a/.github/workflows/publish_docker_runner.yml b/.github/workflows/publish_docker_runner.yml
new file mode 100644
index 000000000..f070bef9e
--- /dev/null
+++ b/.github/workflows/publish_docker_runner.yml
@@ -0,0 +1,23 @@
+name: Publish Docker Runner
+
+on:
+  push:
+    branches:
+      - main
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    environment: publish
+    steps:
+    - uses: actions/checkout@v2
+    - name: Build new Docker image
+      run: bash build_test_environment_docker_runner.sh 
+    - name: Docker login
+      run: echo "$SECRET_DOCKER_TOKEN" | docker login --username "$SECRET_DOCKER_USER_NAME" --password-stdin 
+      env: # Set the secret as an input
+        SECRET_DOCKER_USER_NAME: ${{ secrets.DOCKER_USER_NAME }}
+        SECRET_DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
+    - name: Push new Docker image
+      run: bash push_test_environment_docker_runner.sh 
+
diff --git a/doc/changes/changes_0.5.0.md b/doc/changes/changes_0.5.0.md
index 594ee4fc3..8387f6cab 100644
--- a/doc/changes/changes_0.5.0.md
+++ b/doc/changes/changes_0.5.0.md
@@ -21,7 +21,7 @@ If you need further versions, please open an issue.
 
 ## Bug Fixes:
 
-n/a
+- #34 :Test container does not get restarted if it gets rebuild and reuse is activated
 
 ## Features / Enhancements:
 


Test patch:
diff --git a/.github/workflows/test_db_versions_all_tests.yml b/.github/workflows/test_db_versions_all_tests.yml
new file mode 100644
index 000000000..9b8b62643
--- /dev/null
+++ b/.github/workflows/test_db_versions_all_tests.yml
@@ -0,0 +1,67 @@
+name: Test DB Versions with all tests
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+jobs:
+  test-db-versions-all-tests:
+    strategy:
+      fail-fast: false
+      matrix:
+        exasol_version:
+        - 7.1.0-d1
+        - 7.0.12
+        - 7.0.11
+        - 7.0.10
+        - 7.0.9
+        - 7.0.8
+        - 7.0.7
+        - 7.0.6
+        - 7.0.5
+        - 7.0.4
+        - 7.0.3
+        - 7.0.2
+        - 7.0.1
+        - 7.0.0
+        - 6.2.16-d1
+        - 6.2.15-d1
+        - 6.2.14-d1
+        - 6.2.13-d1
+        - 6.2.12-d1
+        - 6.2.11-d1
+        - "default"
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Setup Python 3.6 for integration-test-docker-environment
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.6
+    - name: Install Poetry
+      uses: abatilo/actions-poetry@v2.0.0
+
+    - name: Fail, because not all tests are activated
+      run: |
+        git fetch
+        GIT_LOG="$(git log --pretty=oneline --abbrev-commit ${{ github.event.after }} | head -n 1)"
+        echo "$GIT_LOG"
+        if [[ "$GIT_LOG" == *"[run all tests]"* ]]; then
+          echo "All tests activated"
+          exit 0
+        else
+          echo "Fail, because not all tests are deactivated"
+          exit 1
+        fi
+      env:
+        EXASOL_VERSION: "${{ matrix.exasol_version }}"
+      # We want to deactivate running all tests only for pull_requests
+      if: ${{ github.event_name == 'pull_request' }}
+
+    - name: Run all tests
+      run: ./run_all_tests.sh
+      env:
+        EXASOL_VERSION: "${{ matrix.exasol_version }}"
\ No newline at end of file
diff --git a/.github/workflows/test_db_versions_minimal.yml b/.github/workflows/test_db_versions_minimal.yml
new file mode 100644
index 000000000..c10f4b57d
--- /dev/null
+++ b/.github/workflows/test_db_versions_minimal.yml
@@ -0,0 +1,51 @@
+name: Test DB Versions with minimal tests
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+
+jobs:
+  test-db-versions-minimal:
+    strategy:
+      fail-fast: false
+      matrix:
+        exasol_version:
+        - 7.1.0-d1
+        - 7.0.12
+        - 7.0.11
+        - 7.0.10
+        - 7.0.9
+        - 7.0.8
+        - 7.0.7
+        - 7.0.6
+        - 7.0.5
+        - 7.0.4
+        - 7.0.3
+        - 7.0.2
+        - 7.0.1
+        - 7.0.0
+        - 6.2.16-d1
+        - 6.2.15-d1
+        - 6.2.14-d1
+        - 6.2.13-d1
+        - 6.2.12-d1
+        - 6.2.11-d1
+        - "default"
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Setup Python 3.6 for integration-test-docker-environment
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.6
+    - name: Install Poetry
+      uses: abatilo/actions-poetry@v2.0.0
+
+    - name: Run minimal tests
+      run: ./run_test.sh exasol_integration_test_docker_environment/test/test_test_environment.py
+      env:
+        EXASOL_VERSION: "${{ matrix.exasol_version }}"
\ No newline at end of file
diff --git a/.github/workflows/test_docker_starter.yml b/.github/workflows/test_docker_starter.yml
new file mode 100644
index 000000000..36d07e892
--- /dev/null
+++ b/.github/workflows/test_docker_starter.yml
@@ -0,0 +1,19 @@
+name: Test Docker Starter
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+jobs:
+  test-docker-starter:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Build new Docker image
+      run: bash build_test_environment_docker_runner.sh 
+
+    - name: Test ./start-test-env 
+      run: ./start-test-env --help
\ No newline at end of file
diff --git a/.github/workflows/test_python_version.yml b/.github/workflows/test_python_version.yml
new file mode 100644
index 000000000..6191bfb4e
--- /dev/null
+++ b/.github/workflows/test_python_version.yml
@@ -0,0 +1,36 @@
+name: Test Python Versions with all tests
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+
+jobs:
+  test-python-version-all-tests:
+    strategy:
+      fail-fast: false
+      matrix:
+        exasol_version:
+        - "default"
+        python_version:
+        - 3.6
+        - 3.7
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Setup Python 3.6 for integration-test-docker-environment
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.6
+    - name: Install Poetry
+      uses: abatilo/actions-poetry@v2.0.0
+
+    - name: Run all tests
+      run: ./run_all_tests.sh
+      env:
+        EXASOL_VERSION: "${{ matrix.exasol_version }}"
+
+    - name: Test ./start-test-env-with-poetry 
+      run: ./start-test-env-with-poetry --help
\ No newline at end of file
diff --git a/exasol_integration_test_docker_environment/lib/base/base_task.py b/exasol_integration_test_docker_environment/lib/base/base_task.py
index 87fa1af86..073143af5 100644
--- a/exasol_integration_test_docker_environment/lib/base/base_task.py
+++ b/exasol_integration_test_docker_environment/lib/base/base_task.py
@@ -83,7 +83,7 @@ def __init__(self, *args, **kwargs):
         self.__hash = hash(self.task_id)
         self._init_non_pickle_attributes()
         self.register_required()
-        self._task_state = TaskState.NONE
+        self._task_state = TaskState.AFTER_INIT
 
     def _init_non_pickle_attributes(self):
         logger = logging.getLogger(f'luigi-interface.{self.__class__.__name__}')
@@ -224,10 +224,11 @@ def run(self):
             task_generator = self.run_task()
             if task_generator is not None:
                 yield from task_generator
-            self._task_state = TaskState.NONE
+            self._task_state = TaskState.FINISHED
             self.logger.info("Write complete_target")
             self._complete_target.write(self._registered_return_targets)
         except Exception as e:
+            self._task_state = TaskState.ERROR
             self.logger.exception("Exception in run: %s", e)
             raise e
 
@@ -239,7 +240,7 @@ def run_dependencies(self, tasks) -> Generator["BaseTask", PickleTarget, Any]:
             self._register_run_dependencies(tasks)
             self._run_dependencies_target.write(self._run_dependencies_tasks)
             completion_targets = yield tasks
-            task_futures = self._generate_run_task_furtures(completion_targets)
+            task_futures = self._generate_run_task_futures(completion_targets)
             return task_futures
         else:
             raise WrongTaskStateException(self._task_state, "run_dependency")
@@ -254,11 +255,12 @@ def _register_run_dependencies(self, tasks):
         elif isinstance(tasks, BaseTask):
             self._run_dependencies_tasks.append(tasks)
 
-    def _generate_run_task_furtures(self, completion_targets):
+    def _generate_run_task_futures(self, completion_targets: Union[Any]) -> Union[
+        List[Any], Dict[Any, Any], RunTaskFuture, Any]:
         if isinstance(completion_targets, dict):
-            return {key: self._generate_run_task_furtures(task) for key, task in completion_targets.items()}
+            return {key: self._generate_run_task_futures(task) for key, task in completion_targets.items()}
         elif isinstance(completion_targets, list):
-            return [self._generate_run_task_furtures(task) for task in completion_targets]
+            return [self._generate_run_task_futures(task) for task in completion_targets]
         elif isinstance(completion_targets, PickleTarget):
             return RunTaskFuture(completion_targets)
         else:
@@ -276,6 +278,12 @@ def return_object(self, object: Any, name: str = DEFAULT_RETURN_OBJECT_NAME):
         else:
             raise WrongTaskStateException(self._task_state, "return_target")
 
+    def get_return_object(self, name: str = DEFAULT_RETURN_OBJECT_NAME) -> Any:
+        if self._task_state == TaskState.FINISHED:
+            return self._registered_return_targets.get(name).read()
+        else:
+            raise WrongTaskStateException(self._task_state, "get_return_object")
+
     def __repr__(self):
         """
         Build a task representation like `MyTask(param1=1.5, param2='5')`
@@ -307,7 +315,7 @@ def create_child_task(self, task_class, **kwargs):
         params.update(kwargs)
         return task_class(**params)
 
-    def cleanup(self, success):
+    def cleanup(self, success:bool):
         self.logger.debug("Cleaning up")
         if self._task_state != TaskState.CLEANUP and self._task_state != TaskState.CLEANED:
             self._task_state = TaskState.CLEANUP
@@ -324,7 +332,7 @@ def cleanup(self, success):
             self._task_state = TaskState.CLEANED
         self.logger.debug("Cleanup finished")
 
-    def cleanup_child_task(self, success):
+    def cleanup_child_task(self, success:bool):
         if self._run_dependencies_target.exists():
             _run_dependencies_tasks_from_target = self._run_dependencies_target.read()
         else:
@@ -340,5 +348,5 @@ def cleanup_child_task(self, success):
         for task in reversed_registered_task_list:
             task.cleanup(success)
 
-    def cleanup_task(self, success):
+    def cleanup_task(self, success:bool):
         pass
diff --git a/exasol_integration_test_docker_environment/lib/base/task_state.py b/exasol_integration_test_docker_environment/lib/base/task_state.py
index a4ed9b6d1..b6b9797b2 100644
--- a/exasol_integration_test_docker_environment/lib/base/task_state.py
+++ b/exasol_integration_test_docker_environment/lib/base/task_state.py
@@ -4,7 +4,9 @@
 class TaskState(Enum):
     NONE = 0,
     INIT = 1,
-    RUN = 2,
-    FINISHED = 3,
-    CLEANUP = 4,
-    CLEANED = 5
+    AFTER_INIT =2,
+    RUN = 3,
+    FINISHED = 4,
+    CLEANUP = 5,
+    CLEANED = 6,
+    ERROR = 7
diff --git a/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_container.py b/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_container.py
index 35401d522..698fa72eb 100644
--- a/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_container.py
+++ b/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_container.py
@@ -9,6 +9,7 @@
 from exasol_integration_test_docker_environment.lib.base.json_pickle_parameter import JsonPickleParameter
 from exasol_integration_test_docker_environment.lib.data.container_info import ContainerInfo
 from exasol_integration_test_docker_environment.lib.data.docker_network_info import DockerNetworkInfo
+from exasol_integration_test_docker_environment.lib.docker.images.image_info import ImageState, ImageInfo
 from exasol_integration_test_docker_environment.lib.test_environment.analyze_test_container import \
     DockerTestContainerBuild
 from exasol_integration_test_docker_environment.lib.test_environment.create_export_directory import \
@@ -38,11 +39,26 @@ def register_required(self):
         self.test_container_image_future = self.register_dependency(DockerTestContainerBuild())
         self.export_directory_future = self.register_dependency(CreateExportDirectory())
 
+    def is_reuse_possible(self) -> bool:
+        test_container_image_info = \
+            self.get_values_from_futures(self.test_container_image_future)["test-container"]  # type: ImageInfo
+        test_container = None
+        try:
+            test_container = \
+                self._client.containers.get(self.test_container_name)
+        except Exception as e:
+            pass
+        return self.network_info.reused and self.reuse_test_container and \
+               test_container is not None and \
+               test_container.image == test_container_image_info.get_target_complete_name() and \
+               test_container_image_info.image_state == ImageState.USED_LOCAL
+
     def run_task(self):
         subnet = netaddr.IPNetwork(self.network_info.subnet)
         ip_address = str(subnet[2 + self.ip_address_index_in_subnet])
         container_info = None
-        if self.network_info.reused and self.reuse_test_container:
+
+        if self.is_reuse_possible():
             container_info = self._try_to_reuse_test_container(ip_address, self.network_info)
         if container_info is None:
             container_info = self._create_test_container(ip_address, self.network_info)
@@ -77,6 +93,7 @@ def _try_to_reuse_test_container(self, ip_address: str,
     def _create_test_container(self, ip_address,
                                network_info: DockerNetworkInfo) -> ContainerInfo:
         self._remove_container(self.test_container_name)
+        self.logger.info(f"Creating new test container {self.test_container_name}")
         test_container_image_info = \
             self.get_values_from_futures(self.test_container_image_future)["test-container"]
 
@@ -146,12 +163,11 @@ def _remove_container(self, container_name: str):
         except Exception as e:
             pass
 
-    def cleanup_task(self, success):
+    def cleanup_task(self, success:bool):
         if (success and not self.no_test_container_cleanup_after_success) or \
-            (not success and not self.no_test_container_cleanup_after_failure):
+                (not success and not self.no_test_container_cleanup_after_failure):
             try:
-                self.logger.info(f"Cleaning up container %s",self.test_container_name)
+                self.logger.info(f"Cleaning up container %s", self.test_container_name)
                 self._remove_container(self.test_container_name)
             except Exception as e:
-                self.logger.error(f"Error during removing container %s: %s",self.test_container_name,e)
-
+                self.logger.error(f"Error during removing container %s: %s", self.test_container_name, e)
diff --git a/exasol_integration_test_docker_environment/test/resources/test_test_container_reuse/ext/,gitkeep b/exasol_integration_test_docker_environment/test/resources/test_test_container_reuse/ext/,gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/exasol_integration_test_docker_environment/test/resources/test_test_container_reuse/tests/Dockerfile b/exasol_integration_test_docker_environment/test/resources/test_test_container_reuse/tests/Dockerfile
new file mode 100644
index 000000000..311a4a0ba
--- /dev/null
+++ b/exasol_integration_test_docker_environment/test/resources/test_test_container_reuse/tests/Dockerfile
@@ -0,0 +1,1 @@
+FROM ubuntu:18.04
\ No newline at end of file
diff --git a/exasol_integration_test_docker_environment/test/resources/test_test_container_reuse/tests/requirements.txt b/exasol_integration_test_docker_environment/test/resources/test_test_container_reuse/tests/requirements.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/exasol_integration_test_docker_environment/test/test_test_container_reuse.py b/exasol_integration_test_docker_environment/test/test_test_container_reuse.py
new file mode 100644
index 000000000..351c589b1
--- /dev/null
+++ b/exasol_integration_test_docker_environment/test/test_test_container_reuse.py
@@ -0,0 +1,179 @@
+import os
+import shutil
+import tempfile
+import unittest
+from datetime import datetime
+from multiprocessing import Process, Queue
+from pathlib import Path
+from typing import Callable
+
+import docker
+import luigi
+
+from exasol_integration_test_docker_environment.cli.common import set_build_config, set_docker_repository_config
+from exasol_integration_test_docker_environment.lib.base.docker_base_task import DockerBaseTask
+from exasol_integration_test_docker_environment.lib.data.container_info import ContainerInfo
+from exasol_integration_test_docker_environment.lib.docker.images.clean.clean_images import CleanImagesStartingWith
+from exasol_integration_test_docker_environment.lib.test_environment.prepare_network_for_test_environment import \
+    PrepareDockerNetworkForTestEnvironment
+from exasol_integration_test_docker_environment.lib.test_environment.spawn_test_container import SpawnTestContainer
+
+
+class TestTask(DockerBaseTask):
+    reuse = luigi.BoolParameter()
+    attempt = luigi.IntParameter()
+
+    def run_task(self):
+        docker_network_task_1 = PrepareDockerNetworkForTestEnvironment(
+            environment_name="test_environment_TestContainerReuseTest",
+            network_name="docker_network_TestContainerReuseTest",
+            test_container_name="test_container_TestContainerReuseTest",
+            db_container_name="db_container_TestContainerReuseTest",
+            reuse=self.reuse,
+            no_cleanup_after_success=True,
+            no_cleanup_after_failure=False,
+            attempt=self.attempt
+        )
+        self.docker_network_future_1 = yield from self.run_dependencies(docker_network_task_1)
+
+        test_container_task_1 = \
+            SpawnTestContainer(
+                environment_name="test_environment_TestContainerReuseTest",
+                test_container_name="test_container_TestContainerReuseTest",
+                network_info=self.docker_network_future_1.get_output(),
+                ip_address_index_in_subnet=2,
+                attempt=self.attempt,
+                reuse_test_container=self.reuse,
+                no_test_container_cleanup_after_success=True,
+                no_test_container_cleanup_after_failure=False
+            )
+        test_container_future_1 = yield from self.run_dependencies(test_container_task_1)
+        container_info = test_container_future_1.get_output()  # type: ContainerInfo
+        container = self._client.containers.get(container_info.container_name)
+
+        self.return_object({"container_id": container.image.id, "image_id": container.image.id})
+
+class TestContainerReuseTest(unittest.TestCase):
+
+    def set_job_id(self, task_cls):
+        strftime = datetime.now().strftime('%Y_%m_%d_%H_%M_%S')
+        job_id = f"{strftime}_{task_cls.__name__}"
+        config = luigi.configuration.get_config()
+        config.set('job_config', 'job_id', job_id)
+        return job_id
+
+    def clean(self):
+        jobid = self.set_job_id(CleanImagesStartingWith)
+        task = CleanImagesStartingWith(starts_with_pattern=self.docker_repository_name, jobid=jobid)
+        luigi.build([task], workers=1, local_scheduler=True, log_level="INFO")
+        if task._get_tmp_path_for_job().exists():
+            shutil.rmtree(str(task._get_tmp_path_for_job()))
+
+    def setUp(self):
+        self.client = docker.from_env()
+        self.temp_directory = tempfile.mkdtemp()
+        resource_directory = Path(Path(__file__).parent, "resources/test_test_container_reuse")
+        print("resource_directory content", list(Path(resource_directory).iterdir()))
+        self.working_directory = shutil.copytree(resource_directory,
+                                                 Path(self.temp_directory, "test_test_container_reuse"))
+        print("working_directory",self.working_directory)
+        self.old_working_directory = os.getcwd()
+        os.chdir(self.working_directory)
+        print("working_directory content",list(Path(self.working_directory).iterdir()))
+        self.docker_repository_name = self.__class__.__name__.lower()
+        print("docker_repository_name",self.docker_repository_name)
+        self.clean()
+
+    def tearDown(self):
+        os.chdir(self.old_working_directory)
+        self.clean()
+        shutil.rmtree(self.temp_directory)
+        self.client.close()
+
+    def setup_luigi_config(self):
+        set_build_config(force_rebuild=False,
+                         force_pull=False,
+                         force_rebuild_from=tuple(),
+                         log_build_context_content=False,
+                         output_directory=self.temp_directory,
+                         cache_directory="",
+                         build_name="",
+                         temporary_base_directory="/tmp"
+                         )
+        set_docker_repository_config(
+            docker_password=None,
+            docker_repository_name=self.docker_repository_name,
+            docker_username=None,
+            tag_prefix="",
+            kind="target"
+        )
+
+    def run1(self):
+        self.setup_luigi_config()
+        self.set_job_id(SpawnTestContainer)
+        task = TestTask(reuse=False, attempt=1)
+        try:
+            success = luigi.build([task], workers=1, local_scheduler=True, log_level="INFO")
+            if success:
+                result = task.get_return_object()
+                task.cleanup(True)
+                return result
+            else:
+                task.cleanup(False)
+                Exception("Task failed")
+        except Exception as e:
+            task.cleanup(False)
+
+    def run2(self):
+        self.setup_luigi_config()
+        self.set_job_id(SpawnTestContainer)
+        task = TestTask(reuse=True, attempt=2)
+        try:
+            success = luigi.build([task], workers=1, local_scheduler=True, log_level="INFO")
+
+            if success:
+                result = task.get_return_object()
+                return result
+            else:
+                raise Exception("Task failed")
+        finally:
+            task.cleanup(False)
+
+    def process_main(self, queue: Queue, func: Callable):
+        try:
+            result = func()
+            queue.put(("result",result))
+        except Exception as e:
+            queue.put(("exception",e))
+
+    def run_in_process(self, func):
+        queue = Queue()
+        p1 = Process(target=self.process_main, args=(queue, func))
+        p1.start()
+        p1.join()
+        result = queue.get()
+        if result[0] == "result":
+            return result[1]
+        else:
+            raise result[1]
+
+    def test_test_container_no_reuse_after_change(self):
+        p1 = self.run_in_process(self.run1)
+        dockerfile = Path(self.working_directory, "tests/Dockerfile")
+        with dockerfile.open("a") as f:
+            f.write("\n#Test\n")
+        p2 = self.run_in_process(self.run2)
+        print(p1)
+        print(p2)
+        assert p1 != p2
+
+    def test_test_container_reuse(self):
+        p1 = self.run_in_process(self.run1)
+        p2 = self.run_in_process(self.run2)
+        print(p1)
+        print(p2)
+        assert p1 == p2
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/run_all_tests.sh b/run_all_tests.sh
index d202601ef..418973a4c 100755
--- a/run_all_tests.sh
+++ b/run_all_tests.sh
@@ -13,7 +13,7 @@ init_poetry
 
 if [ -n "$POETRY_BIN" ]
 then
-  PYTHONPATH=. $POETRY_BIN run python3 -m unittest discover exasol_integration_test_docker_environment/test
+  PYTHONPATH=. $POETRY_BIN run python3 -u -m unittest discover exasol_integration_test_docker_environment/test
 else
   echo "Could not find poetry!"
   exit 1
diff --git a/run_test.sh b/run_test.sh
index da985e3aa..9b9177f45 100755
--- a/run_test.sh
+++ b/run_test.sh
@@ -12,7 +12,7 @@ init_poetry
 
 if [ -n "$POETRY_BIN" ]
 then
-  PYTHONPATH=. $POETRY_BIN run python3 "${@}"
+  PYTHONPATH=. $POETRY_BIN run python3 -u "${@}"
 else
   echo "Could not find poetry!"
   exit 1