diff --git a/egcg_core/config.py b/egcg_core/config.py
index 059fe28..8376c98 100644
--- a/egcg_core/config.py
+++ b/egcg_core/config.py
@@ -27,7 +27,9 @@ class Configuration:
             self.env_var = env_var
         self.config_file = self._find_config_file(search_path)
         if self.config_file:
-            self.content = yaml.safe_load(open(self.config_file, 'r'))
+            with open(self.config_file, 'r') as f:
+                self.content = yaml.safe_load(f)
+
             self._select_env()
         else:
             raise ConfigError('Could not find any config file in specified search path')
diff --git a/egcg_core/executor/array_executor.py b/egcg_core/executor/array_executor.py
index 882b220..66ecd46 100644
--- a/egcg_core/executor/array_executor.py
+++ b/egcg_core/executor/array_executor.py
@@ -36,6 +36,6 @@ class ArrayExecutor(StreamExecutor):
         if self.exception:
             self._stop()
             self.error(self.exception.__class__.__name__ + ': ' + str(self.exception))
-            raise EGCGError('Commands failed: ' + str(self.exit_statuses))
+            raise EGCGError('Commands failed: ' + str(self.exit_statuses)) from self.exception
         self.info('Exit statuses: %s', self.exit_statuses)
         return sum(self.exit_statuses)
diff --git a/egcg_core/executor/cluster_executor.py b/egcg_core/executor/cluster_executor.py
index f38f74f..ff5926a 100644
--- a/egcg_core/executor/cluster_executor.py
+++ b/egcg_core/executor/cluster_executor.py
@@ -27,10 +27,10 @@ class ClusterExecutor(AppLogger):
         """
         self.interval = cfg.query('executor', 'join_interval', ret_default=30)
         self.job_id = None
-        self.job_name = cluster_config.get('job_name')
+        self.job_name = cluster_config['job_name']
         self.cmds = cmds
         self.prelim_cmds = prelim_cmds
-        self.writer = self._get_writer(job_queue=cfg['executor']['job_queue'], **cluster_config)
+        self.writer = self._get_writer(**cluster_config)
 
     def write_script(self):
         if self.prelim_cmds:
@@ -60,8 +60,16 @@ class ClusterExecutor(AppLogger):
         running_executors.pop(self.job_id, None)  # unregister from running_executors
         return self._job_exit_code()
 
-    def _get_writer(self, job_name, working_dir, job_queue, walltime=None, cpus=1, mem=2, log_commands=True):
-        return self.script_writer(job_name, working_dir, job_queue, log_commands=log_commands, cpus=cpus, mem=mem, walltime=walltime)
+    def _get_writer(self, job_name, working_dir, job_queue=None, cpus=1, mem=2, walltime=None, log_commands=True):
+        return self.script_writer(
+            job_name,
+            working_dir,
+            job_queue or cfg['executor']['job_queue'],
+            cpus,
+            mem,
+            walltime,
+            log_commands
+        )
 
     def _job_statuses(self):
         return ()
diff --git a/egcg_core/executor/executor.py b/egcg_core/executor/executor.py
index 453d617..8bfdc36 100644
--- a/egcg_core/executor/executor.py
+++ b/egcg_core/executor/executor.py
@@ -1,5 +1,4 @@
 import subprocess
-import shlex
 from egcg_core.app_logging import AppLogger
 from egcg_core.exceptions import EGCGError
 
@@ -25,8 +24,15 @@ class Executor(AppLogger):
         except Exception as e:
             raise EGCGError('Command failed: ' + self.cmd) from e
 
-    def start(self):
-        raise NotImplementedError
+    @staticmethod
+    def start():
+        """
+        Subclasses of Executor implement this method from different sources:
+          - StreamExecutor and ArrayExecutor inherit it from threading.Thread
+          - ClusterExecutor implements it so it writes and submits a script to a queue
+        The definition here has no effect, but allows Executor to be handled identically to other executors.
+        """
+        pass
 
     def _process(self):
         """
@@ -35,6 +41,5 @@ class Executor(AppLogger):
         :rtype: subprocess.Popen
         """
         self.info('Executing: %s', self.cmd)
-        # TODO: explore how to run commands with Bash constructs , e.g. 'command <(sub command)'
-        self.proc = subprocess.Popen(shlex.split(self.cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        self.proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
         return self.proc
diff --git a/egcg_core/executor/script_writers.py b/egcg_core/executor/script_writers.py
index c96d516..e706677 100644
--- a/egcg_core/executor/script_writers.py
+++ b/egcg_core/executor/script_writers.py
@@ -22,7 +22,7 @@ class ScriptWriter(AppLogger):
     suffix = '.sh'
     array_index = 'JOB_INDEX'
 
-    def __init__(self, job_name, working_dir, job_queue, log_commands=True, **cluster_config):
+    def __init__(self, job_name, working_dir, job_queue, cpus, mem, walltime=None, log_commands=True):
         """
         :param str job_name: Desired full path to the pbs script to write
         """
@@ -33,7 +33,14 @@ class ScriptWriter(AppLogger):
         self.debug('Writing job "%s" in %s', job_name, working_dir)
         self.lines = []
         self.array_jobs_written = 0
-        self.cluster_config = dict(cluster_config, job_name=job_name, log_file=self.log_file, job_queue=job_queue)
+        self.cluster_config = {
+            'job_name': job_name,
+            'job_queue': job_queue,
+            'cpus': cpus,
+            'mem': mem,
+            'walltime': walltime,
+            'log_file': self.log_file
+        }
 
     def register_cmd(self, cmd, log_file=None):
         if log_file:
@@ -95,7 +102,7 @@ class ScriptWriter(AppLogger):
 
     def add_header(self):
         """Write a header for a given resource manager. If multiple jobs, split them into a job array."""
-        header_mapping = dict(self.cluster_config, log_file=self.log_file, jobs=str(self.array_jobs_written))
+        header_mapping = dict(self.cluster_config, jobs=str(self.array_jobs_written))
         header_lines = list(self.header)
 
         if self.cluster_config.get('walltime'):
@@ -141,7 +148,7 @@ class PBSWriter(ScriptWriter):
     walltime_header = '#PBS -l walltime={walltime}:00:00'
     array_header = '#PBS -J 1-{jobs}'
 
-    def __init__(self, job_name, working_dir, job_queue, log_commands=True, **cluster_config):
-        super().__init__(job_name, working_dir, job_queue, log_commands, **cluster_config)
+    def __init__(self, job_name, working_dir, job_queue, cpus, mem, walltime=None, log_commands=True):
+        super().__init__(job_name, working_dir, job_queue, cpus, mem, walltime, log_commands)
         if len(self.cluster_config['job_name']) > 15:  # job names longer than 15 chars break PBS
             self.cluster_config['job_name'] = self.cluster_config['job_name'][:15]
diff --git a/egcg_core/executor/stream_executor.py b/egcg_core/executor/stream_executor.py
index 2e4e1d9..64c6225 100644
--- a/egcg_core/executor/stream_executor.py
+++ b/egcg_core/executor/stream_executor.py
@@ -21,8 +21,9 @@ class StreamExecutor(Thread, Executor):
         super().join(timeout=timeout)
         if self.exception:
             self._stop()
-            self.error('Encountered a %s error: %s', self.exception.__class__.__name__, str(self.exception))
-            raise EGCGError('self.proc command failed: ' + self.cmd)
+            self.error('Encountered a %s error: %s', self.exception.__class__.__name__, self.exception)
+            raise EGCGError('Command failed: ' + self.cmd) from self.exception
+
         return self.proc.wait()
 
     def run(self):
@@ -32,9 +33,7 @@ class StreamExecutor(Thread, Executor):
             self.exception = e
 
     def _stream_output(self):
-        """
-        Run self._process and log its stdout/stderr until an EOF.
-        """
+        """Run self._process and log its stdout/stderr until an EOF."""
         proc = self._process()
         read_set = [proc.stdout, proc.stderr]
         while read_set:
diff --git a/egcg_core/notifications/__init__.py b/egcg_core/notifications/__init__.py
index 10b1ae4..91c0b76 100644
--- a/egcg_core/notifications/__init__.py
+++ b/egcg_core/notifications/__init__.py
@@ -43,7 +43,9 @@ class NotificationCentre(AppLogger):
         if exceptions:
             raise EGCGError(
                 'Encountered the following errors during notification: %s' % ', '.join(
-                    '%s: %s' % (e.__class__.__name__, str(e)) for e in exceptions)
+                    '%s: %s' % (e.__class__.__name__, str(e))
+                    for e in exceptions
+                )
             )
 
     def notify_all(self, msg):
diff --git a/egcg_core/rest_communication.py b/egcg_core/rest_communication.py
index 096648e..5930487 100644
--- a/egcg_core/rest_communication.py
+++ b/egcg_core/rest_communication.py
@@ -267,7 +267,7 @@ class Communicator(AppLogger):
     def __del__(self):
         try:
             self.close()
-        except ReferenceError:
+        except Exception:
             pass
 
 default = Communicator()
diff --git a/requirements.txt b/requirements.txt
index 21ff006..663de39 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,9 @@
-pytest>=2.7.2
-pytest_xdist>=1.22.2
-pytest_cov>=2.5.1
-requests==2.14.2
-PyYAML>=3.11
-pyclarity_lims>=0.4
+pytest==4.1.1
+pytest_xdist==1.24.1
+pytest_cov==2.6.1
+requests==2.20.0
+PyYAML==3.13
+pyclarity_lims==0.4.5
 jinja2==2.8
 asana==0.6.7
-cached_property
+cached_property==1.5.1

Bash syntax in local_execute
At the moment, we can use Bash constructs like `>`, `|`, etc. in Slurm/PBS execution, but not local execution - we should make this consistent.
diff --git a/egcg_core/integration_testing.py b/egcg_core/integration_testing.py
index fe96f4c..fa9d305 100644
--- a/egcg_core/integration_testing.py
+++ b/egcg_core/integration_testing.py
@@ -4,8 +4,9 @@ from os import getenv
 from time import sleep
 from datetime import datetime
 from unittest import TestCase
-from egcg_core import config, rest_communication
+from multiprocessing import Lock
 from subprocess import check_output
+from egcg_core import config, rest_communication
 
 
 def get_cfg():
@@ -26,6 +27,7 @@ class WrappedFunc:
     def __init__(self, test_case, assert_func):
         self.test_case = test_case
         self.assert_func = assert_func
+        self.lock = Lock()
 
     def __call__(self, check_name, *args, **kwargs):
         if not isinstance(check_name, str):
@@ -48,10 +50,10 @@ class WrappedFunc:
             self.log(assertion_report + 'failed' + args_used)
             raise
 
-    @staticmethod
-    def log(msg):
-        with open('checks.log', 'a') as f:
-            f.write(msg + '\n')
+    def log(self, msg):
+        with self.lock:
+            with open('checks.log', 'a') as f:
+                f.write(msg + '\n')
 
 
 class IntegrationTest(TestCase):
diff --git a/tests/__init__.py b/tests/__init__.py
index d14abb2..2931bd0 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -2,6 +2,7 @@ import json
 import os.path
 import unittest
 from unittest.mock import Mock
+from egcg_core.config import cfg
 
 
 class FakeRestResponse(Mock):
@@ -26,3 +27,7 @@ class TestEGCG(unittest.TestCase):
     assets_path = os.path.join(file_path, 'assets')
     etc = os.path.join(os.path.dirname(file_path), 'etc')
     etc_config = os.path.join(etc, 'example_egcg.yaml')
+
+    @classmethod
+    def setUpClass(cls):
+        cfg.load_config_file(cls.etc_config)
diff --git a/tests/test_app_logging.py b/tests/test_app_logging.py
index fd4b37b..c0aa66c 100644
--- a/tests/test_app_logging.py
+++ b/tests/test_app_logging.py
@@ -6,7 +6,6 @@ from unittest.mock import Mock
 from tests import TestEGCG
 from egcg_core import app_logging
 from egcg_core.config import cfg
-cfg.load_config_file(TestEGCG.etc_config)
 
 
 class TestLoggingConfiguration(TestEGCG):
diff --git a/tests/test_clarity.py b/tests/test_clarity.py
index 271d1d2..8d51d9a 100644
--- a/tests/test_clarity.py
+++ b/tests/test_clarity.py
@@ -1,7 +1,6 @@
 import os
 from unittest.mock import patch, Mock
 from egcg_core import clarity
-from egcg_core.config import cfg
 from tests import TestEGCG
 
 
@@ -62,7 +61,6 @@ class TestClarity(TestEGCG):
         clarity.app_logger = Mock()
 
     def test_connection(self):
-        cfg.load_config_file(self.etc_config)
         clarity._lims = None
         # create connection from config
         lims = clarity.connection()
diff --git a/tests/test_executor/test_executors.py b/tests/test_executor/test_executors.py
index 2a2f6fb..18d426d 100644
--- a/tests/test_executor/test_executors.py
+++ b/tests/test_executor/test_executors.py
@@ -13,79 +13,128 @@ sleep = 'egcg_core.executor.cluster_executor.sleep'
 
 
 class TestExecutor(TestEGCG):
+    executor_cls = Executor
+
+    def execute(self, *args, **kwargs):
+        e = self.executor_cls(*args, **kwargs)
+        e.start()
+        return e.join()
+
     def test_cmd(self):
-        e = Executor('ls ' + os.path.join(self.assets_path, '..'))
-        exit_status = e.join()
-        assert exit_status == 0
+        assert self.execute('ls ' + os.path.dirname(self.assets_path)) == 0
 
     def test_dodgy_cmd(self):
-        with pytest.raises(EGCGError) as err:
-            e = Executor('dodgy_cmd')
-            e.join()
-            assert 'Command failed: \'dodgy_cmd\'' in str(err)
+        assert self.execute('dodgy_cmd') == 127  # command not found
+
+    def test_script(self):
+        assert self.execute(os.path.join(self.assets_path, 'countdown.sh')) == 0
+
+    def test_dodgy_script(self):
+        # same exit status as the running script
+        assert self.execute('%s dodgy' % os.path.join(self.assets_path, 'countdown.sh')) == 13
+
+    def test_internal_error(self):
+        with patch.object(self.executor_cls, 'info', side_effect=ValueError('Something went wrong')):
+            with pytest.raises(EGCGError) as err:
+                self.execute('ls')
+
+            assert 'Command failed: ls' in str(err)
 
     def test_process(self):
-        e = Executor('ls ' + os.path.join(self.assets_path, '..'))
+        e = self.executor_cls('ls ' + os.path.dirname(self.assets_path))
         assert e.proc is None
         proc = e._process()
         assert proc is e.proc and isinstance(e.proc, subprocess.Popen)
 
+    def test_bash_syntax(self):
+        with patch.object(self.executor_cls, 'info') as mocked_info:
+            assert self.execute('ls -lh %s | grep __init__' % os.path.dirname(self.assets_path)) == 0
 
-class TestStreamExecutor(TestExecutor):
-    def test_cmd(self):
-        e = StreamExecutor(os.path.join(self.assets_path, 'countdown.sh'))
-        e.start()
-        assert e.join() == 0
+        assert '__init__.py' in mocked_info.call_args_list[1][0][0]
 
-    def test_dodgy_command(self):
-        e = StreamExecutor(os.path.join(self.assets_path, 'countdown.sh') + ' dodgy')
-        e.start()
-        assert e.join() == 13  # same exit status as the running script
 
-    def test_dodgy_cmd(self):
-        with pytest.raises(EGCGError) as err:
-            e = StreamExecutor('dodgy_cmd')
-            e.start()
-            e.error = Mock()
-            e.join()
-            assert 'self.proc command failed: \'dodgy_cmd\'' in str(err)
+class TestStreamExecutor(TestExecutor):
+    executor_cls = StreamExecutor
 
 
 class TestArrayExecutor(TestExecutor):
+    executor_cls = ArrayExecutor
+
+    def execute(self, *args, **kwargs):
+        return super().execute(args, stream=True, **kwargs)
+
     def test_cmd(self):
-        e = ArrayExecutor(['ls', 'ls -lh', 'pwd'], stream=True)
+        e = self.executor_cls(['ls', 'ls -lh', 'pwd'], stream=True)
         e.start()
         assert e.join() == 0
         assert e.exit_statuses == [0, 0, 0]
 
     def test_dodgy_cmd(self):
-        e = ArrayExecutor(['ls', 'non_existent_cmd', 'pwd'], stream=True)
+        e = self.executor_cls(['ls', 'non_existent_cmd', 'pwd'], stream=True)
         for s in e.executors:
             s.error = Mock()
 
         e.error = Mock()
         e.start()
-        with pytest.raises(EGCGError) as err:
-            e.join()
+        assert e.join() == 127
+        assert e.exit_statuses == [
+            0,  # ls
+            127,  # command not found
+            0  # pwd
+        ]
+
+    def test_internal_error(self):
+        with patch.object(StreamExecutor, 'info', side_effect=ValueError('Something went wrong')):
+            with pytest.raises(EGCGError) as err:
+                self.execute('ls')
+
+            assert 'Commands failed' in str(err)
+
+    def test_process(self):
+        e = self.executor_cls(['ls ' + os.path.dirname(self.assets_path)], stream=True)
+        assert e.proc is None
+        proc = e._process()
+        assert proc is e.proc and isinstance(e.proc, subprocess.Popen)
+
+    def test_bash_syntax(self):
+        with patch.object(StreamExecutor, 'info') as mocked_info:
+            assert self.execute('ls -lh %s | grep __init__' % os.path.dirname(self.assets_path)) == 0
 
-        assert 'Commands failed' in str(err)
-        e.error.assert_called_with('EGCGError: self.proc command failed: non_existent_cmd')
+        assert '__init__.py' in mocked_info.call_args_list[1][0][0]
 
 
 class TestClusterExecutor(TestEGCG):
-    ppath = 'egcg_core.executor.cluster_executor.ClusterExecutor'
     script = os.path.join(TestEGCG.assets_path, 'countdown.sh')
+    executor_cls = ClusterExecutor
 
     def setUp(self):
-        os.makedirs(os.path.join(self.assets_path, 'a_run_id'), exist_ok=True)
-        self.executor = ClusterExecutor(
-            self.script,
-            job_name='test_job',
-            working_dir=os.path.join(self.assets_path, 'a_run_id')
-        )
+        self.working_dir = os.path.join(self.assets_path, 'a_working_dir')
+        os.makedirs(self.working_dir, exist_ok=True)
+        self.executor = self.executor_cls(self.script, job_name='test_job', working_dir=self.working_dir)
 
     def tearDown(self):
-        shutil.rmtree(os.path.join(self.assets_path, 'a_run_id'))
+        shutil.rmtree(self.working_dir)
+
+    def test_get_writer(self):
+        w = self.executor.writer  # default args
+        assert w.cluster_config == {
+            'job_name': 'test_job',
+            'job_queue': 'a_job_queue',
+            'cpus': 1,
+            'mem': 2,
+            'walltime': None,
+            'log_file': w.log_file
+        }
+
+        w = self.executor._get_writer('test_job_2', self.working_dir, 'another_job_queue', 2, 4, 1, False)
+        assert w.cluster_config == {
+            'job_name': 'test_job_2',
+            'job_queue': 'another_job_queue',
+            'cpus': 2,
+            'mem': 4,
+            'walltime': 1,
+            'log_file': w.log_file
+        }
 
     def test_get_stdout(self):
         popen = 'egcg_core.executor.executor.subprocess.Popen'
@@ -102,19 +151,22 @@ class TestClusterExecutor(TestEGCG):
         with pytest.raises(EGCGError) as err, patch(get_stdout, return_value=None), patch(sleep):
             self.executor.cmds = [os.path.join(self.assets_path, 'non_existent_script.sh')]
             self.executor.start()
-            assert str(err) == 'Job submission failed'
+
+        assert str(err).endswith('Job submission failed')
 
     def test_join(self):
-        job_finished = self.ppath + '._job_finished'
-        exit_code = self.ppath + '._job_exit_code'
+        patched_job_finished = patch.object(self.executor_cls, '_job_finished', return_value=True)
+        patched_exit_code = patch.object(self.executor_cls, '_job_exit_code', return_value=0)
         self.executor.finished_statuses = 'FXM'
-        with patch(job_finished, return_value=True), patch(exit_code, return_value=0), patch(sleep):
+        with patched_job_finished, patched_exit_code, patch(sleep):
             assert self.executor.join() == 0
 
     def test_job_cancellation(self):
-        with patch(self.ppath + '._submit_job'), patch(self.ppath + '._job_finished', return_value=True),\
-             patch(self.ppath + '.write_script'), patch(self.ppath + '._job_exit_code', return_value=9),\
-             patch(self.ppath + '.cancel_job'), patch(sleep):
+        with patch.object(self.executor_cls, '_submit_job'),\
+             patch.object(self.executor_cls, '_job_finished', return_value=True),\
+             patch.object(self.executor_cls, 'write_script'),\
+             patch.object(self.executor_cls, '_job_exit_code', return_value=9),\
+             patch.object(self.executor_cls, 'cancel_job'), patch(sleep):
 
             self.executor.job_id = 'test_job'
             self.executor.start()
@@ -124,15 +176,7 @@ class TestClusterExecutor(TestEGCG):
 
 
 class TestPBSExecutor(TestClusterExecutor):
-    ppath = 'egcg_core.executor.cluster_executor.PBSExecutor'
-
-    def setUp(self):
-        os.makedirs(os.path.join(self.assets_path, 'a_run_id'), exist_ok=True)
-        self.executor = PBSExecutor(
-            self.script,
-            job_name='test_job',
-            working_dir=os.path.join(self.assets_path, 'a_run_id')
-        )
+    executor_cls = PBSExecutor
 
     def test_qstat(self):
         fake_report = ('Job id            Name             User              Time Use S Queue\n'
@@ -161,15 +205,7 @@ class TestPBSExecutor(TestClusterExecutor):
 
 
 class TestSlurmExecutor(TestClusterExecutor):
-    ppath = 'egcg_core.executor.cluster_executor.SlurmExecutor'
-
-    def setUp(self):
-        os.makedirs(os.path.join(self.assets_path, 'a_run_id'), exist_ok=True)
-        self.executor = SlurmExecutor(
-            self.script,
-            job_name='test_job',
-            working_dir=os.path.join(self.assets_path, 'a_run_id')
-        )
+    executor_cls = SlurmExecutor
 
     def test_sacct(self):
         with patch(get_stdout, return_value=' COMPLETED  0:0 \n COMPLETED  0:0\n FAILED 1:0') as p:
diff --git a/tests/test_executor/test_script_writers.py b/tests/test_executor/test_script_writers.py
index 52b8a7a..86fb703 100644
--- a/tests/test_executor/test_script_writers.py
+++ b/tests/test_executor/test_script_writers.py
@@ -81,10 +81,6 @@ class TestScriptWriter(TestEGCG):
         self.script_writer.save()
         assert 'a_line\n' in open(self.script_writer.script_name, 'r').readlines()
 
-    def test_trim_field(self):
-        s = script_writers.PBSWriter('a_job_name_too_long_for_pbs', 'a_working_dir', 'a_job_queue')
-        assert s.cluster_config['job_name'] == 'a_job_name_too_'
-
     def test(self):
         self.script_writer.log_commands = False
         self.script_writer.register_cmds('some', 'preliminary', 'cmds', parallel=False)
@@ -112,6 +108,10 @@ class TestPBSWriter(TestScriptWriter):
         'cd ' + working_dir
     ]
 
+    def test_trim_field(self):
+        s = script_writers.PBSWriter('a_job_name_too_long_for_pbs', 'a_working_dir', 'a_job_queue', 1, 1)
+        assert s.cluster_config['job_name'] == 'a_job_name_too_'
+
 
 class TestSlurmWriter(TestScriptWriter):
     writer_cls = script_writers.SlurmWriter
diff --git a/tests/test_ncbi.py b/tests/test_ncbi.py
index 287bc57..39770cd 100644
--- a/tests/test_ncbi.py
+++ b/tests/test_ncbi.py
@@ -1,9 +1,7 @@
 import sqlite3
 from unittest.mock import patch
-from tests import TestEGCG, FakeRestResponse
+from tests import FakeRestResponse
 from egcg_core import ncbi
-from egcg_core.config import cfg
-cfg.load_config_file(TestEGCG.etc_config)
 
 
 fetch_from_eutils = ncbi._fetch_from_eutils
diff --git a/tests/test_notifications.py b/tests/test_notifications.py
index 99c7c76..6ccc75d 100644
--- a/tests/test_notifications.py
+++ b/tests/test_notifications.py
@@ -6,7 +6,6 @@ from smtplib import SMTPException
 from email.mime.text import MIMEText
 from email.mime.application import MIMEApplication
 from email.mime.multipart import MIMEMultipart
-from egcg_core.config import cfg
 from egcg_core import notifications as n
 from egcg_core.exceptions import EGCGError
 from tests import TestEGCG
@@ -31,7 +30,6 @@ class FakeSMTP(Mock):
 
 class TestNotificationCentre(TestEGCG):
     def setUp(self):
-        cfg.load_config_file(TestEGCG.etc_config)
         self.notification_centre = n.NotificationCentre('a_name')
 
     def test_config_init(self):

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
asana==0.6.7
attrs==22.2.0
cached-property==1.5.2
certifi==2021.5.30
coverage==6.2
-e git+https://github.com/EdinburghGenomics/EGCG-Core.git@1c278a1f45f591d92e712391b1efc137d92aeb0d#egg=EGCG_Core
execnet==1.9.0
importlib-metadata==4.8.3
iniconfig==1.1.1
Jinja2==2.8
MarkupSafe==2.0.1
oauthlib==3.2.2
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyclarity-lims==0.4.8
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
pytest-xdist==3.0.2
PyYAML==6.0.1
requests==2.14.2
requests-oauthlib==0.8.0
six==1.17.0
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: EGCG-Core
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
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - 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=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - asana==0.6.7
      - attrs==22.2.0
      - cached-property==1.5.2
      - coverage==6.2
      - execnet==1.9.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jinja2==2.8
      - markupsafe==2.0.1
      - oauthlib==3.2.2
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyclarity-lims==0.4.8
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-xdist==3.0.2
      - pyyaml==6.0.1
      - requests==2.14.2
      - requests-oauthlib==0.8.0
      - six==1.17.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/EGCG-Core

tests/test_executor/test_executors.py::TestExecutor::test_bash_syntax tests/test_executor/test_executors.py::TestExecutor::test_cmd tests/test_executor/test_executors.py::TestExecutor::test_dodgy_cmd tests/test_executor/test_executors.py::TestExecutor::test_dodgy_script tests/test_executor/test_executors.py::TestExecutor::test_internal_error tests/test_executor/test_executors.py::TestExecutor::test_script tests/test_executor/test_executors.py::TestStreamExecutor::test_bash_syntax tests/test_executor/test_executors.py::TestStreamExecutor::test_dodgy_cmd tests/test_executor/test_executors.py::TestStreamExecutor::test_internal_error tests/test_executor/test_executors.py::TestArrayExecutor::test_bash_syntax tests/test_executor/test_executors.py::TestArrayExecutor::test_dodgy_cmd tests/test_executor/test_executors.py::TestArrayExecutor::test_process tests/test_executor/test_executors.py::TestClusterExecutor::test_get_writer tests/test_executor/test_executors.py::TestPBSExecutor::test_get_writer tests/test_executor/test_executors.py::TestSlurmExecutor::test_get_writer tests/test_executor/test_script_writers.py::TestPBSWriter::test_trim_field
tests/test_app_logging.py::TestLoggingConfiguration::test_get_logger tests/test_executor/test_executors.py::TestClusterExecutor::test_dodgy_cmd tests/test_executor/test_executors.py::TestPBSExecutor::test_dodgy_cmd tests/test_executor/test_executors.py::TestSlurmExecutor::test_dodgy_cmd
tests/test_app_logging.py::TestLoggingConfiguration::test_add_handler tests/test_app_logging.py::TestLoggingConfiguration::test_configure_handlers_from_config tests/test_app_logging.py::TestLoggingConfiguration::test_formatters tests/test_app_logging.py::TestLoggingConfiguration::test_set_formatter tests/test_app_logging.py::TestLoggingConfiguration::test_set_log_level tests/test_app_logging.py::TestAppLogger::test_get_logger tests/test_clarity.py::TestClarity::test_connection tests/test_clarity.py::TestClarity::test_find_project_from_sample tests/test_clarity.py::TestClarity::test_find_run_elements_from_sample tests/test_clarity.py::TestClarity::test_get_genotype_information_from_lims tests/test_clarity.py::TestClarity::test_get_list_of_samples tests/test_clarity.py::TestClarity::test_get_list_of_samples_broken tests/test_clarity.py::TestClarity::test_get_output_containers_from_sample_and_step_name tests/test_clarity.py::TestClarity::test_get_plate_id_and_well_from_lims tests/test_clarity.py::TestClarity::test_get_released_samples tests/test_clarity.py::TestClarity::test_get_run tests/test_clarity.py::TestClarity::test_get_sample tests/test_clarity.py::TestClarity::test_get_sample_gender tests/test_clarity.py::TestClarity::test_get_sample_names_from_plate_from_lims tests/test_clarity.py::TestClarity::test_get_sample_names_from_project_from_lims tests/test_clarity.py::TestClarity::test_get_sample_release_date tests/test_clarity.py::TestClarity::test_get_samples tests/test_clarity.py::TestClarity::test_get_samples_arrived_with tests/test_clarity.py::TestClarity::test_get_samples_genotyped_with tests/test_clarity.py::TestClarity::test_get_samples_sequenced_with tests/test_clarity.py::TestClarity::test_get_species_from_sample tests/test_clarity.py::TestClarity::test_get_user_sample_name tests/test_clarity.py::TestClarity::test_get_valid_lanes tests/test_clarity.py::TestClarity::test_route_samples_to_delivery_workflow_no_name tests/test_clarity.py::TestClarity::test_route_samples_to_delivery_workflow_with_name tests/test_clarity.py::TestClarity::test_sanitize_user_id tests/test_executor/test_executors.py::TestExecutor::test_process tests/test_executor/test_executors.py::TestStreamExecutor::test_cmd tests/test_executor/test_executors.py::TestStreamExecutor::test_dodgy_script tests/test_executor/test_executors.py::TestStreamExecutor::test_process tests/test_executor/test_executors.py::TestStreamExecutor::test_script tests/test_executor/test_executors.py::TestArrayExecutor::test_cmd tests/test_executor/test_executors.py::TestArrayExecutor::test_dodgy_script tests/test_executor/test_executors.py::TestArrayExecutor::test_internal_error tests/test_executor/test_executors.py::TestArrayExecutor::test_script tests/test_executor/test_executors.py::TestClusterExecutor::test_get_stdout tests/test_executor/test_executors.py::TestClusterExecutor::test_job_cancellation tests/test_executor/test_executors.py::TestClusterExecutor::test_join tests/test_executor/test_executors.py::TestClusterExecutor::test_run_and_retry tests/test_executor/test_executors.py::TestPBSExecutor::test_get_stdout tests/test_executor/test_executors.py::TestPBSExecutor::test_job_cancellation tests/test_executor/test_executors.py::TestPBSExecutor::test_job_finished tests/test_executor/test_executors.py::TestPBSExecutor::test_job_status tests/test_executor/test_executors.py::TestPBSExecutor::test_join tests/test_executor/test_executors.py::TestPBSExecutor::test_qstat tests/test_executor/test_executors.py::TestPBSExecutor::test_run_and_retry tests/test_executor/test_executors.py::TestSlurmExecutor::test_get_stdout tests/test_executor/test_executors.py::TestSlurmExecutor::test_job_cancellation tests/test_executor/test_executors.py::TestSlurmExecutor::test_job_exit_code tests/test_executor/test_executors.py::TestSlurmExecutor::test_job_finished tests/test_executor/test_executors.py::TestSlurmExecutor::test_join tests/test_executor/test_executors.py::TestSlurmExecutor::test_run_and_retry tests/test_executor/test_executors.py::TestSlurmExecutor::test_sacct tests/test_executor/test_executors.py::TestSlurmExecutor::test_squeue tests/test_executor/test_script_writers.py::TestScriptWriter::test tests/test_executor/test_script_writers.py::TestScriptWriter::test_add_job_array tests/test_executor/test_script_writers.py::TestScriptWriter::test_init tests/test_executor/test_script_writers.py::TestScriptWriter::test_register_cmd tests/test_executor/test_script_writers.py::TestScriptWriter::test_register_cmds tests/test_executor/test_script_writers.py::TestScriptWriter::test_save tests/test_executor/test_script_writers.py::TestPBSWriter::test tests/test_executor/test_script_writers.py::TestPBSWriter::test_add_job_array tests/test_executor/test_script_writers.py::TestPBSWriter::test_init tests/test_executor/test_script_writers.py::TestPBSWriter::test_register_cmd tests/test_executor/test_script_writers.py::TestPBSWriter::test_register_cmds tests/test_executor/test_script_writers.py::TestPBSWriter::test_save tests/test_executor/test_script_writers.py::TestSlurmWriter::test tests/test_executor/test_script_writers.py::TestSlurmWriter::test_add_job_array tests/test_executor/test_script_writers.py::TestSlurmWriter::test_init tests/test_executor/test_script_writers.py::TestSlurmWriter::test_register_cmd tests/test_executor/test_script_writers.py::TestSlurmWriter::test_register_cmds tests/test_executor/test_script_writers.py::TestSlurmWriter::test_save tests/test_ncbi.py::test_fetch_from_eutils tests/test_ncbi.py::test_cache tests/test_ncbi.py::test_get_species_name tests/test_notifications.py::TestNotificationCentre::test_config_init tests/test_notifications.py::TestNotificationCentre::test_notify tests/test_notifications.py::TestNotificationCentre::test_notify_failure tests/test_notifications.py::TestLogNotification::test_notify tests/test_notifications.py::TestLogNotification::test_notify_with_attachment tests/test_notifications.py::TestLogNotification::test_notify_with_attachments tests/test_notifications.py::TestEmailSender::test_build_email tests/test_notifications.py::TestEmailSender::test_build_email_attachment tests/test_notifications.py::TestEmailSender::test_build_email_attachments tests/test_notifications.py::TestEmailSender::test_build_email_plain_text tests/test_notifications.py::TestEmailSender::test_retries tests/test_notifications.py::TestEmailNotification::test_notify tests/test_notifications.py::TestEmailNotification::test_notify_fail tests/test_notifications.py::test_send_plain_text_email tests/test_notifications.py::test_send_html_email tests/test_notifications.py::test_send_email tests/test_notifications.py::TestAsanaNotification::test_add_comment tests/test_notifications.py::TestAsanaNotification::test_add_comment_with_attachments tests/test_notifications.py::TestAsanaNotification::test_add_comment_with_one_attachment tests/test_notifications.py::TestAsanaNotification::test_create_task tests/test_notifications.py::TestAsanaNotification::test_get_entity tests/test_notifications.py::TestAsanaNotification::test_task
egcg_core/executor/stream_executor.py egcg_core/notifications/__init__.py egcg_core/executor/cluster_executor.py egcg_core/executor/array_executor.py egcg_core/config.py egcg_core/executor/script_writers.py egcg_core/rest_communication.py egcg_core/executor/executor.py requirements.txt
egcg_core/executor/stream_executor.py egcg_core/notifications/__init__.py egcg_core/executor/cluster_executor.py egcg_core/executor/array_executor.py egcg_core/config.py egcg_core/executor/script_writers.py egcg_core/rest_communication.py egcg_core/executor/executor.py requirements.txt