diff --git a/reframe/frontend/executors/__init__.py b/reframe/frontend/executors/__init__.py
index 795d2869..aa2b47e2 100644
--- a/reframe/frontend/executors/__init__.py
+++ b/reframe/frontend/executors/__init__.py
@@ -36,11 +36,11 @@ class TestCase:
 
     def __init__(self, check, partition, environ):
         self._check_orig = check
-        self._check = copy.deepcopy(check)
-        self._partition = copy.deepcopy(partition)
-        self._environ = copy.deepcopy(environ)
-        self._check._case = weakref.ref(self)
+        self._check = check
+        self._partition = partition
+        self._environ = environ
         self._deps = []
+        self._is_ready = False
 
         # Incoming dependencies
         self.in_degree = 0
@@ -72,6 +72,15 @@ class TestCase:
         e = self.environ.name if self.environ else None
         return f'({c!r}, {p!r}, {e!r})'
 
+    def prepare(self):
+        '''Prepare test case for sending down the test pipeline'''
+        if self._is_ready:
+            return
+
+        self._check = copy.deepcopy(self._check)
+        self._check._case = weakref.ref(self)
+        self._is_ready = True
+
     @property
     def check(self):
         return self._check
@@ -97,8 +106,14 @@ class TestCase:
         return TestCase(self._check_orig, self._partition, self._environ)
 
 
-def generate_testcases(checks):
-    '''Generate concrete test cases from checks.'''
+def generate_testcases(checks, prepare=False):
+    '''Generate concrete test cases from checks.
+
+    If `prepare` is true then each of the cases will also be prepared for
+    being sent to the test pipeline. Note that setting this to true may slow down
+    the test case generation.
+
+    '''
 
     rt = runtime.runtime()
     cases = []
@@ -107,7 +122,11 @@ def generate_testcases(checks):
                                                c.valid_prog_environs)
         for part, environs in valid_comb.items():
             for env in environs:
-                cases.append(TestCase(c, part, env))
+                case = TestCase(c, part, env)
+                if prepare:
+                    case.prepare()
+
+                cases.append(case)
 
     return cases
 
@@ -295,6 +314,7 @@ class RegressionTask:
             raise TaskExit from e
 
     def setup(self, *args, **kwargs):
+        self.testcase.prepare()
         self._safe_call(self.check.setup, *args, **kwargs)
         self._notify_listeners('on_task_setup')
 
diff --git a/reframe/schemas/config.json b/reframe/schemas/config.json
index 472b0d8a..46df7c9f 100644
--- a/reframe/schemas/config.json
+++ b/reframe/schemas/config.json
@@ -514,7 +514,7 @@
         "environments/features": [],
         "environments/target_systems": ["*"],
         "general/dump_pipeline_progress": false,
-        "general/pipeline_timeout": null,
+        "general/pipeline_timeout": 3,
         "general/check_search_path": ["${RFM_INSTALL_PREFIX}/checks/"],
         "general/check_search_recursive": false,
         "general/clean_stagedir": true,

diff --git a/unittests/test_dependencies.py b/unittests/test_dependencies.py
index 3cf15f3e..29066810 100644
--- a/unittests/test_dependencies.py
+++ b/unittests/test_dependencies.py
@@ -375,7 +375,11 @@ def test_build_deps_deprecated_syntax(loader, default_exec_ctx):
 
 def test_build_deps(loader, default_exec_ctx):
     checks = loader.load_all(force=True)
-    cases = executors.generate_testcases(checks)
+
+    # We need to prepare the test cases as if we were about to run them,
+    # because we want to test `getdep()` as well, which normally gets resolved
+    # during the `setup` phase of the pipeline
+    cases = executors.generate_testcases(checks, prepare=True)
 
     # Test calling getdep() before having built the graph
     t = find_check('Test1_fully', checks)
diff --git a/unittests/test_pipeline.py b/unittests/test_pipeline.py
index f3ba74ed..8ef7818b 100644
--- a/unittests/test_pipeline.py
+++ b/unittests/test_pipeline.py
@@ -1210,7 +1210,7 @@ def test_require_deps(HelloTest, local_exec_ctx):
         def setz(self, T0):
             self.z = T0().x + 2
 
-    cases = executors.generate_testcases([T0(), T1()])
+    cases = executors.generate_testcases([T0(), T1()], prepare=True)
     deps, _ = dependencies.build_deps(cases)
     for c in dependencies.toposort(deps):
         _run(*c)

`--distribute` option is too slow
```
time ./bin/reframe -C config/cscs.py -c cscstests/microbenchmarks/gpu/gpu_burn/gpu_burn_test.py -S num_tasks=1 -J reservation=foo -l
...
real	0m0.734s
user	0m0.509s
sys	0m0.105s
```

```
time ./bin/reframe -C config/cscs.py -c cscstests/microbenchmarks/gpu/gpu_burn/gpu_burn_test.py -S num_tasks=1 -J reservation=foo --distribute=all -l
...
real	0m4.629s
user	0m3.077s
sys	0m0.443s
```

I think that the slowdown is due to the `scontrol`s done behind the scenes, but we need to investigate further and either improve it or add a note in the documentation.
unittests/test_dependencies.py::test_build_deps unittests/test_pipeline.py::test_require_deps
unittests/test_dependencies.py::test_eq_hash unittests/test_dependencies.py::test_dependecies_how_functions unittests/test_dependencies.py::test_dependecies_how_functions_undoc unittests/test_dependencies.py::test_build_deps_deprecated_syntax unittests/test_dependencies.py::test_build_deps_empty unittests/test_dependencies.py::test_valid_deps unittests/test_dependencies.py::test_cyclic_deps unittests/test_dependencies.py::test_cyclic_deps_by_env unittests/test_dependencies.py::test_validate_deps_empty unittests/test_dependencies.py::test_skip_unresolved_deps unittests/test_dependencies.py::test_prune_deps unittests/test_dependencies.py::test_toposort unittests/test_dependencies.py::test_toposort_subgraph unittests/test_pipeline.py::test_hellocheck_local_prepost_run unittests/test_pipeline.py::test_run_only_set_sanity_in_a_hook unittests/test_pipeline.py::test_run_only_decorated_sanity unittests/test_pipeline.py::test_run_only_no_srcdir unittests/test_pipeline.py::test_run_only_srcdir_set_to_none unittests/test_pipeline.py::test_executable_is_required unittests/test_pipeline.py::test_compile_only_failure unittests/test_pipeline.py::test_compile_only_warning unittests/test_pipeline.py::test_pinned_test unittests/test_pipeline.py::test_supports_sysenv unittests/test_pipeline.py::test_sourcesdir_none unittests/test_pipeline.py::test_sourcesdir_build_system unittests/test_pipeline.py::test_sourcesdir_none_generated_sources unittests/test_pipeline.py::test_sourcesdir_none_compile_only unittests/test_pipeline.py::test_sourcesdir_none_run_only unittests/test_pipeline.py::test_sourcepath_abs unittests/test_pipeline.py::test_sourcepath_upref unittests/test_pipeline.py::test_sourcepath_non_existent unittests/test_pipeline.py::test_extra_resources unittests/test_pipeline.py::test_unkown_pre_hook unittests/test_pipeline.py::test_unkown_post_hook unittests/test_pipeline.py::test_pre_init_hook unittests/test_pipeline.py::test_post_init_hook unittests/test_pipeline.py::test_setup_hooks unittests/test_pipeline.py::test_compile_hooks unittests/test_pipeline.py::test_run_hooks unittests/test_pipeline.py::test_multiple_hooks unittests/test_pipeline.py::test_stacked_hooks unittests/test_pipeline.py::test_multiple_inheritance unittests/test_pipeline.py::test_inherited_hooks unittests/test_pipeline.py::test_inherited_hooks_order unittests/test_pipeline.py::test_inherited_hooks_from_instantiated_tests unittests/test_pipeline.py::test_overriden_hooks unittests/test_pipeline.py::test_overriden_hook_different_stages unittests/test_pipeline.py::test_disabled_hooks unittests/test_pipeline.py::test_trap_job_errors_without_sanity_patterns unittests/test_pipeline.py::test_trap_job_errors_with_sanity_patterns unittests/test_pipeline.py::test_sanity_success unittests/test_pipeline.py::test_sanity_failure unittests/test_pipeline.py::test_sanity_failure_noassert unittests/test_pipeline.py::test_sanity_multiple_patterns unittests/test_pipeline.py::test_sanity_multiple_files unittests/test_pipeline.py::test_performance_failure unittests/test_pipeline.py::test_reference_unknown_tag unittests/test_pipeline.py::test_reference_unknown_system unittests/test_pipeline.py::test_reference_empty unittests/test_pipeline.py::test_reference_default unittests/test_pipeline.py::test_reference_tag_resolution unittests/test_pipeline.py::test_performance_invalid_value unittests/test_pipeline.py::test_perf_patterns_evaluation unittests/test_pipeline.py::test_validate_default_perf_variables unittests/test_pipeline.py::test_perf_vars_without_reference unittests/test_pipeline.py::test_perf_vars_with_reference unittests/test_pipeline.py::test_incompat_perf_syntax unittests/test_pipeline.py::test_unknown_container_platform unittests/test_pipeline.py::test_not_configured_container_platform unittests/test_pipeline.py::test_skip_if_no_topo unittests/test_pipeline.py::test_make_test_without_builtins unittests/test_pipeline.py::test_make_test_with_builtins unittests/test_pipeline.py::test_make_test_with_builtins_inline