Update requirements.

  o This required the regular updates to .pylintrc and various changes
    in the source code to pass the new linter checks.

  o This updates to the new setuptools

  o This upgrades to the new pytest-datafiles 3.0 API

    This required many changes to tests, the new API is however
    a bit simpler to work with, and now preserves file permissions
    staged into the test location.

    Especially:

     o artifact_list_contents.py: Expect different results now that
       the hello script is executable.

     o completions.py: Update cache keys. This is not a cache key break,
       but upgrading pytest-datafiles causes the artifact used in the
       `bst artifact log` completion test to be legitimately different.

  o This required limiting the indirect isort dependency in dev-requirements
    since the latest version is not available for Python 3.7
diff --git a/.pylintrc b/.pylintrc
index 4a1ddd7..9e12487 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -93,14 +93,13 @@
 #    pylint to also check for those things.
 #
 
-disable=#####################################
+disable=,
+        #####################################
         # Messages that are of no use to us #
         #####################################
-        ,
 	consider-using-f-string,
         fixme,
         missing-docstring,
-        no-self-use,
         no-else-return,
         protected-access,
         too-few-public-methods,
@@ -143,7 +142,6 @@
         # Formatting-related messages, enforced by Black #
         ##################################################
 
-        bad-continuation,
         line-too-long,
         superfluous-parens,
 
@@ -443,13 +441,6 @@
 # Maximum number of lines in a module
 max-module-lines=1000
 
-# List of optional constructs for which whitespace checking is disabled. `dict-
-# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
-# `trailing-comma` allows a space between comma and closing bracket: (a, ).
-# `empty-line` allows space-only lines.
-no-space-check=trailing-comma,
-               dict-separator
-
 # Allow the body of a class to be on the same line as the declaration if body
 # contains single statement.
 single-line-class-stmt=no
@@ -551,4 +542,4 @@
 
 # Exceptions that will emit a warning when being caught. Defaults to
 # "Exception"
-overgeneral-exceptions=Exception
+overgeneral-exceptions=builtins.Exception
diff --git a/requirements/cov-requirements.txt b/requirements/cov-requirements.txt
index 9df1cf0..04df6cb 100644
--- a/requirements/cov-requirements.txt
+++ b/requirements/cov-requirements.txt
@@ -1,11 +1,11 @@
-coverage==7.0.5
+coverage==7.2.1
 pytest-cov==4.0.0
-pytest==7.2.0
-Cython==0.29.32
+pytest==7.2.1
+Cython==0.29.33
 ## The following requirements were added by pip freeze:
 attrs==22.2.0
 exceptiongroup==1.1.0
-iniconfig==1.1.1
-packaging==22.0
+iniconfig==2.0.0
+packaging==23.0
 pluggy==1.0.0
 tomli==2.0.1
diff --git a/requirements/dev-requirements.in b/requirements/dev-requirements.in
index 5809351..e077709 100644
--- a/requirements/dev-requirements.in
+++ b/requirements/dev-requirements.in
@@ -2,8 +2,11 @@
 pylint
 # Pytest 6.0.0 doesn't play well with pylint
 pytest >= 6.0.1
-pytest-datafiles >= 2.0
+pytest-datafiles >= 3.0
 pytest-env
 pytest-xdist
 pytest-timeout
 pyftpdlib
+# isort is an indirect requirement, but it must be constrained
+# as 5.12 is not available for python 3.7
+isort < 5.12
diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt
index 203e5e3..ae88300 100644
--- a/requirements/dev-requirements.txt
+++ b/requirements/dev-requirements.txt
@@ -1,27 +1,29 @@
 pexpect==4.8.0
-pylint==2.13.8
+pylint==2.16.3
 # Pytest 6.0.0 doesn't play well with pylint
-pytest==7.2.0
-pytest-datafiles==2.0.1
+pytest==7.2.1
+pytest-datafiles==3.0.0
 pytest-env==0.8.1
-pytest-xdist==3.1.0
+pytest-xdist==3.2.0
 pytest-timeout==2.1.0
 pyftpdlib==1.5.7
+# isort is an indirect requirement, but it must be constrained
+# as 5.12 is not available for python 3.7
+isort==5.11.5
 ## The following requirements were added by pip freeze:
-astroid==2.11.7
+astroid==2.14.2
 attrs==22.2.0
 dill==0.3.6
 exceptiongroup==1.1.0
 execnet==1.9.0
-iniconfig==1.1.1
-isort==5.11.4
-lazy-object-proxy==1.8.0
+iniconfig==2.0.0
+lazy-object-proxy==1.9.0
 mccabe==0.7.0
-packaging==22.0
-platformdirs==2.6.2
+packaging==23.0
+platformdirs==3.0.0
 pluggy==1.0.0
 ptyprocess==0.7.0
-py==1.11.0
 tomli==2.0.1
-typing_extensions==4.4.0
-wrapt==1.14.1
+tomlkit==0.11.6
+typing-extensions==4.5.0
+wrapt==1.15.0
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index 3c621fb..4d24d43 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -1,15 +1,15 @@
 click==8.1.3
-grpcio==1.51.1
+grpcio==1.51.3
 Jinja2==3.1.2
 pluginbase==1.0.1
-protobuf==4.21.12
+protobuf==4.22.0
 psutil==5.9.4
 ruamel.yaml==0.17.21
 ruamel.yaml.clib==0.2.7
-setuptools==56.0.0
-pyroaring==0.3.4
-ujson==5.6.0
+setuptools==67.4.0
+pyroaring==0.3.8
+ujson==5.7.0
 python-dateutil==2.8.2
 ## The following requirements were added by pip freeze:
-MarkupSafe==2.1.1
+MarkupSafe==2.1.2
 six==1.16.0
diff --git a/src/buildstream/_frontend/cli.py b/src/buildstream/_frontend/cli.py
index 995f40b..468342c 100644
--- a/src/buildstream/_frontend/cli.py
+++ b/src/buildstream/_frontend/cli.py
@@ -290,7 +290,7 @@
     ctx.exit()
 
 
-@click.group(context_settings=dict(help_option_names=["-h", "--help"]))
+@click.group(context_settings={"help_option_names": ["-h", "--help"]})
 @click.option("--version", is_flag=True, callback=print_version, expose_value=False, is_eager=True)
 @click.option(
     "--config", "-c", type=click.Path(exists=True, dir_okay=False, readable=True), help="Configuration file to use"
diff --git a/src/buildstream/_frontend/widget.py b/src/buildstream/_frontend/widget.py
index 20054a7..e6d22fc 100644
--- a/src/buildstream/_frontend/widget.py
+++ b/src/buildstream/_frontend/widget.py
@@ -34,6 +34,10 @@
 ERROR_MESSAGES = [MessageType.FAIL, MessageType.ERROR, MessageType.BUG]
 
 
+class FormattingError(Exception):
+    pass
+
+
 # Widget()
 #
 # Args:
@@ -694,7 +698,7 @@
                 variable = m.group(1)
                 format_string = format_string[m.end(0) :]
                 if variable not in self.logfile_variable_names:
-                    raise Exception("'{0}' is not a valid log variable name.".format(variable))
+                    raise FormattingError("'{0}' is not a valid log variable name.".format(variable))
                 logfile_tokens.append(self.logfile_variable_names[variable])
             else:
                 m = re.search("^[^%]+", format_string)
@@ -704,7 +708,9 @@
                     logfile_tokens.append(text)
                 else:
                     # No idea what to do now
-                    raise Exception("'{0}' could not be parsed into a valid logging format.".format(format_string))
+                    raise FormattingError(
+                        "'{0}' could not be parsed into a valid logging format.".format(format_string)
+                    )
         return logfile_tokens
 
     def _render(self, message):
diff --git a/src/buildstream/_scheduler/scheduler.py b/src/buildstream/_scheduler/scheduler.py
index 2e1ec69..7faa07a 100644
--- a/src/buildstream/_scheduler/scheduler.py
+++ b/src/buildstream/_scheduler/scheduler.py
@@ -32,7 +32,6 @@
 from .jobs import JobStatus
 from ..types import FastEnum
 from .._profile import Topics, PROFILER
-from ..plugin import Plugin
 from .. import _signals
 
 
diff --git a/src/buildstream/_testing/_cachekeys.py b/src/buildstream/_testing/_cachekeys.py
index 9a834c8..aa28f15 100644
--- a/src/buildstream/_testing/_cachekeys.py
+++ b/src/buildstream/_testing/_cachekeys.py
@@ -18,6 +18,10 @@
 from .runcli import Cli
 
 
+class CacheKeyTestError(Exception):
+    pass
+
+
 def check_cache_key_stability(project_path: os.PathLike, cli: Cli) -> None:
     """
     Check that the cache key of various elements has not changed.
@@ -105,7 +109,7 @@
         except FileNotFoundError:
             expected_key = None
             if raise_error:
-                raise Exception(
+                raise CacheKeyTestError(
                     "Cache key test needs update, "
                     + "expected file {} not found.\n\n".format(expected)
                     + "Use python3 -m buildstream._testing._update_cachekeys in the"
diff --git a/tests/artifactcache/config.py b/tests/artifactcache/config.py
index 20297c8..025d11f 100644
--- a/tests/artifactcache/config.py
+++ b/tests/artifactcache/config.py
@@ -136,7 +136,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("config_key, config_value", [("client-cert", "client.crt"), ("client-key", "client.key")])
 def test_missing_certs(cli, datafiles, config_key, config_value):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "missing-certs")
+    project = os.path.join(datafiles, "missing-certs")
 
     project_conf = {
         "name": "test",
@@ -169,7 +169,7 @@
     ],
 )
 def test_only_one(cli, datafiles, override_caches, project_caches, user_caches):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "only-one")
+    project = os.path.join(datafiles, "only-one")
 
     # Produce a fake user and project config with the cache configuration.
     user_config, project_config = configure_remote_caches(override_caches, project_caches, user_caches)
diff --git a/tests/cachekey/cachekey.py b/tests/cachekey/cachekey.py
index 409498f..4deac33 100644
--- a/tests/cachekey/cachekey.py
+++ b/tests/cachekey/cachekey.py
@@ -53,7 +53,6 @@
 # Pylint doesn't play well with fixtures and dependency injection from pytest
 # pylint: disable=redefined-outer-name
 
-from collections import OrderedDict
 import os
 
 import pytest
diff --git a/tests/format/listdirectiveerrors.py b/tests/format/listdirectiveerrors.py
index 4647f18..7efe114 100644
--- a/tests/format/listdirectiveerrors.py
+++ b/tests/format/listdirectiveerrors.py
@@ -26,7 +26,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_project_error(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "list-directive-error-project")
+    project = os.path.join(datafiles, "list-directive-error-project")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
     )
@@ -37,7 +37,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("target", [("variables.bst"), ("environment.bst"), ("config.bst"), ("public.bst")])
 def test_element_error(cli, datafiles, target):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "list-directive-error-element")
+    project = os.path.join(datafiles, "list-directive-error-element")
     result = cli.run(project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", target])
 
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.TRAILING_LIST_DIRECTIVE)
@@ -45,7 +45,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_project_composite_error(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "list-directive-type-error")
+    project = os.path.join(datafiles, "list-directive-type-error")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
     )
diff --git a/tests/format/option-list-directive.py b/tests/format/option-list-directive.py
index 27704f3..1553387 100644
--- a/tests/format/option-list-directive.py
+++ b/tests/format/option-list-directive.py
@@ -12,7 +12,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("mount_devices", [("true"), ("false")])
 def test_override(cli, datafiles, mount_devices):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-list-directive")
+    project = os.path.join(datafiles, "option-list-directive")
 
     bst_args = ["--option", "shell_mount_devices", mount_devices, "build"]
     result = cli.run(project=project, silent=True, args=bst_args)
diff --git a/tests/format/optionarch.py b/tests/format/optionarch.py
index 789a32f..b3ab8f0 100644
--- a/tests/format/optionarch.py
+++ b/tests/format/optionarch.py
@@ -47,7 +47,7 @@
 )
 def test_conditional(cli, datafiles, machine, value, expected):
     with override_platform_uname(machine=machine):
-        project = os.path.join(datafiles.dirname, datafiles.basename, "option-arch")
+        project = os.path.join(datafiles, "option-arch")
 
         bst_args = []
         if value is not None:
@@ -65,7 +65,7 @@
 def test_unsupported_arch(cli, datafiles):
 
     with override_platform_uname(machine="x86_64"):
-        project = os.path.join(datafiles.dirname, datafiles.basename, "option-arch")
+        project = os.path.join(datafiles, "option-arch")
         result = cli.run(
             project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
         )
@@ -77,7 +77,7 @@
 def test_alias(cli, datafiles):
 
     with override_platform_uname(machine="arm"):
-        project = os.path.join(datafiles.dirname, datafiles.basename, "option-arch-alias")
+        project = os.path.join(datafiles, "option-arch-alias")
         result = cli.run(
             project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
         )
@@ -89,7 +89,7 @@
 def test_unknown_host_arch(cli, datafiles):
 
     with override_platform_uname(machine="x86_128"):
-        project = os.path.join(datafiles.dirname, datafiles.basename, "option-arch")
+        project = os.path.join(datafiles, "option-arch")
         result = cli.run(
             project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
         )
@@ -100,7 +100,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 def test_unknown_project_arch(cli, datafiles):
 
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-arch-unknown")
+    project = os.path.join(datafiles, "option-arch-unknown")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
     )
diff --git a/tests/format/optionbool.py b/tests/format/optionbool.py
index b1a9fa9..64e28f5 100644
--- a/tests/format/optionbool.py
+++ b/tests/format/optionbool.py
@@ -46,7 +46,7 @@
     ],
 )
 def test_conditional_cli(cli, datafiles, target, option, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-bool")
+    project = os.path.join(datafiles, "option-bool")
     result = cli.run(
         project=project,
         silent=True,
@@ -69,7 +69,7 @@
     ],
 )
 def test_conditional_config(cli, datafiles, target, option, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-bool")
+    project = os.path.join(datafiles, "option-bool")
     cli.configure({"projects": {"test": {"options": {"pony": option}}}})
     result = cli.run(project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", target])
     result.assert_success()
@@ -81,7 +81,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("cli_option", [("falsey"), ("pony"), ("trUE")])
 def test_invalid_value_cli(cli, datafiles, cli_option):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-bool")
+    project = os.path.join(datafiles, "option-bool")
     result = cli.run(
         project=project,
         silent=True,
@@ -93,7 +93,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("config_option", [("pony"), (["its", "a", "list"]), ({"dic": "tionary"})])
 def test_invalid_value_config(cli, datafiles, config_option):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-bool")
+    project = os.path.join(datafiles, "option-bool")
     cli.configure({"projects": {"test": {"options": {"pony": config_option}}}})
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
diff --git a/tests/format/optioneltmask.py b/tests/format/optioneltmask.py
index 12e6a79..362d6b2 100644
--- a/tests/format/optioneltmask.py
+++ b/tests/format/optioneltmask.py
@@ -35,7 +35,7 @@
     ],
 )
 def test_conditional_cli(cli, datafiles, target, value, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-element-mask")
+    project = os.path.join(datafiles, "option-element-mask")
     result = cli.run(
         project=project,
         silent=True,
@@ -57,7 +57,7 @@
     ],
 )
 def test_conditional_config(cli, datafiles, target, value, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-element-mask")
+    project = os.path.join(datafiles, "option-element-mask")
     cli.configure({"projects": {"test": {"options": {"debug_elements": value}}}})
     result = cli.run(project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", target])
 
@@ -68,7 +68,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_invalid_declaration(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-element-mask-invalid")
+    project = os.path.join(datafiles, "option-element-mask-invalid")
     result = cli.run(project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "pony.bst"])
 
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA)
@@ -76,7 +76,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_invalid_value(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-element-mask")
+    project = os.path.join(datafiles, "option-element-mask")
     result = cli.run(
         project=project,
         silent=True,
diff --git a/tests/format/optionenum.py b/tests/format/optionenum.py
index 58c2d51..dfe9884 100644
--- a/tests/format/optionenum.py
+++ b/tests/format/optionenum.py
@@ -40,7 +40,7 @@
     ],
 )
 def test_conditional_cli(cli, datafiles, target, option, value, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-enum")
+    project = os.path.join(datafiles, "option-enum")
     result = cli.run(
         project=project,
         silent=True,
@@ -67,7 +67,7 @@
     ],
 )
 def test_conditional_config(cli, datafiles, target, option, value, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-enum")
+    project = os.path.join(datafiles, "option-enum")
     cli.configure({"projects": {"test": {"options": {option: value}}}})
     result = cli.run(project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", target])
 
@@ -78,7 +78,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_invalid_value_cli(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-enum")
+    project = os.path.join(datafiles, "option-enum")
     result = cli.run(
         project=project,
         silent=True,
@@ -90,7 +90,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("config_option", [("giraffy"), (["its", "a", "list"]), ({"dic": "tionary"})])
 def test_invalid_value_config(cli, datafiles, config_option):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-enum")
+    project = os.path.join(datafiles, "option-enum")
     cli.configure({"projects": {"test": {"options": {"brother": config_option}}}})
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
@@ -100,7 +100,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_missing_values(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-enum-missing")
+    project = os.path.join(datafiles, "option-enum-missing")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
     )
diff --git a/tests/format/optionexports.py b/tests/format/optionexports.py
index 27f7c6c..22df457 100644
--- a/tests/format/optionexports.py
+++ b/tests/format/optionexports.py
@@ -40,7 +40,7 @@
     ],
 )
 def test_export(cli, datafiles, option_name, option_value, var_name, var_value):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-exports")
+    project = os.path.join(datafiles, "option-exports")
     result = cli.run(
         project=project,
         silent=True,
diff --git a/tests/format/optionflags.py b/tests/format/optionflags.py
index dbeaa53..b926ef3 100644
--- a/tests/format/optionflags.py
+++ b/tests/format/optionflags.py
@@ -43,7 +43,7 @@
     ],
 )
 def test_conditional_cli(cli, datafiles, target, option, value, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-flags")
+    project = os.path.join(datafiles, "option-flags")
     result = cli.run(
         project=project,
         silent=True,
@@ -67,7 +67,7 @@
     ],
 )
 def test_conditional_config(cli, datafiles, target, option, value, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-flags")
+    project = os.path.join(datafiles, "option-flags")
     cli.configure({"projects": {"test": {"options": {option: value}}}})
     result = cli.run(project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", target])
 
@@ -82,7 +82,7 @@
     [("giraffy"), ("horsy pony")],  # Not a valid animal for the farm option  # Does not include comma separators
 )
 def test_invalid_value_cli(cli, datafiles, cli_option):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-flags")
+    project = os.path.join(datafiles, "option-flags")
     result = cli.run(
         project=project,
         silent=True,
@@ -101,7 +101,7 @@
     ],
 )
 def test_invalid_value_config(cli, datafiles, config_option):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-flags")
+    project = os.path.join(datafiles, "option-flags")
     cli.configure({"projects": {"test": {"options": {"farm": config_option}}}})
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
@@ -111,7 +111,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_missing_values(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-flags-missing")
+    project = os.path.join(datafiles, "option-flags-missing")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
     )
diff --git a/tests/format/optionos.py b/tests/format/optionos.py
index fbf9115..79316b2 100644
--- a/tests/format/optionos.py
+++ b/tests/format/optionos.py
@@ -46,7 +46,7 @@
 )
 def test_conditionals(cli, datafiles, system, value, expected):
     with override_platform_uname(system=system):
-        project = os.path.join(datafiles.dirname, datafiles.basename, "option-os")
+        project = os.path.join(datafiles, "option-os")
 
         bst_args = []
         if value is not None:
@@ -64,7 +64,7 @@
 def test_unsupported_arch(cli, datafiles):
 
     with override_platform_uname(system="ULTRIX"):
-        project = os.path.join(datafiles.dirname, datafiles.basename, "option-os")
+        project = os.path.join(datafiles, "option-os")
         result = cli.run(
             project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
         )
diff --git a/tests/format/optionoverrides.py b/tests/format/optionoverrides.py
index 69f5004..a105ee8 100644
--- a/tests/format/optionoverrides.py
+++ b/tests/format/optionoverrides.py
@@ -27,7 +27,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("arch", [("i686"), ("x86_64")])
 def test_override(cli, datafiles, arch):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-overrides")
+    project = os.path.join(datafiles, "option-overrides")
 
     bst_args = ["--option", "arch", arch]
     bst_args += ["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
diff --git a/tests/format/optionprojectroot.py b/tests/format/optionprojectroot.py
index 8460cc5..768ae1e 100644
--- a/tests/format/optionprojectroot.py
+++ b/tests/format/optionprojectroot.py
@@ -31,7 +31,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("value,expected", [("pony", "a pony"), ("horsy", "a horsy")], ids=["pony", "horsy"])
 def test_resolve_project_root_conditional(cli, datafiles, value, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-project-root")
+    project = os.path.join(datafiles, "option-project-root")
     result = cli.run(
         project=project,
         silent=True,
@@ -48,7 +48,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("value,expected", [("pony", "a pony"), ("horsy", "a horsy")], ids=["pony", "horsy"])
 def test_resolve_element_override_conditional(cli, datafiles, value, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "option-element-override")
+    project = os.path.join(datafiles, "option-element-override")
     result = cli.run(
         project=project,
         silent=True,
@@ -72,7 +72,7 @@
     ids=["name", "options"],
 )
 def test_restricted_conditionals(cli, datafiles, project_dir, provenance):
-    project = os.path.join(datafiles.dirname, datafiles.basename, project_dir)
+    project = os.path.join(datafiles, project_dir)
     result = cli.run(
         project=project,
         silent=True,
diff --git a/tests/format/options.py b/tests/format/options.py
index 0e5e707..1cfa714 100644
--- a/tests/format/options.py
+++ b/tests/format/options.py
@@ -36,7 +36,7 @@
     ],
 )
 def test_invalid_option_name(cli, datafiles, project_dir):
-    project = os.path.join(datafiles.dirname, datafiles.basename, project_dir)
+    project = os.path.join(datafiles, project_dir)
     result = cli.run(project=project, silent=True, args=["show", "element.bst"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_SYMBOL_NAME)
 
@@ -50,14 +50,14 @@
     ],
 )
 def test_invalid_variable_name(cli, datafiles, project_dir):
-    project = os.path.join(datafiles.dirname, datafiles.basename, project_dir)
+    project = os.path.join(datafiles, project_dir)
     result = cli.run(project=project, silent=True, args=["show", "element.bst"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_SYMBOL_NAME)
 
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_invalid_option_type(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "invalid-type")
+    project = os.path.join(datafiles, "invalid-type")
 
     # Test with the opt option set
     result = cli.run(
@@ -70,7 +70,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_invalid_option_cli(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "simple-condition")
+    project = os.path.join(datafiles, "simple-condition")
 
     # Test with the opt option set
     result = cli.run(
@@ -83,7 +83,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_invalid_option_config(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "simple-condition")
+    project = os.path.join(datafiles, "simple-condition")
     cli.configure({"projects": {"test": {"options": {"fart": "Hello"}}}})
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
@@ -93,7 +93,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_invalid_expression(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "invalid-expression")
+    project = os.path.join(datafiles, "invalid-expression")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
     )
@@ -102,7 +102,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_undefined(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "undefined-variable")
+    project = os.path.join(datafiles, "undefined-variable")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
     )
@@ -111,7 +111,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_invalid_condition(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "invalid-condition")
+    project = os.path.join(datafiles, "invalid-condition")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{vars}", "element.bst"]
     )
@@ -127,7 +127,7 @@
     ],
 )
 def test_simple_conditional(cli, datafiles, opt_option, expected_prefix):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "simple-condition")
+    project = os.path.join(datafiles, "simple-condition")
 
     # Test with the opt option set
     result = cli.run(
@@ -151,7 +151,7 @@
     ],
 )
 def test_nested_conditional(cli, datafiles, debug, logging, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "nested-condition")
+    project = os.path.join(datafiles, "nested-condition")
 
     # Test with the opt option set
     result = cli.run(
@@ -188,7 +188,7 @@
     ],
 )
 def test_compound_and_conditional(cli, datafiles, debug, logging, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "compound-and-condition")
+    project = os.path.join(datafiles, "compound-and-condition")
 
     # Test with the opt option set
     result = cli.run(
@@ -225,7 +225,7 @@
     ],
 )
 def test_compound_or_conditional(cli, datafiles, debug, logging, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "compound-or-condition")
+    project = os.path.join(datafiles, "compound-or-condition")
 
     # Test with the opt option set
     result = cli.run(
@@ -260,7 +260,7 @@
     ],
 )
 def test_deep_nesting_level1(cli, datafiles, option, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "deep-nesting")
+    project = os.path.join(datafiles, "deep-nesting")
     result = cli.run(
         project=project,
         silent=True,
@@ -283,7 +283,7 @@
     ],
 )
 def test_deep_nesting_level2(cli, datafiles, option, expected):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "deep-nesting")
+    project = os.path.join(datafiles, "deep-nesting")
     result = cli.run(
         project=project,
         silent=True,
diff --git a/tests/format/project.py b/tests/format/project.py
index 568f9ab..227bd8c 100644
--- a/tests/format/project.py
+++ b/tests/format/project.py
@@ -38,14 +38,14 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_missing_project_name(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "missingname")
+    project = os.path.join(datafiles, "missingname")
     result = cli.run(project=project, args=["workspace", "list"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA)
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_missing_element(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "missing-element")
+    project = os.path.join(datafiles, "missing-element")
     result = cli.run(project=project, args=["show", "manual.bst"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
 
@@ -55,7 +55,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_missing_junction(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "missing-junction")
+    project = os.path.join(datafiles, "missing-junction")
     result = cli.run(project=project, args=["show", "manual.bst"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
 
@@ -65,28 +65,28 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_empty_project_name(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "emptyname")
+    project = os.path.join(datafiles, "emptyname")
     result = cli.run(project=project, args=["workspace", "list"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_SYMBOL_NAME)
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_invalid_project_name(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "invalidname")
+    project = os.path.join(datafiles, "invalidname")
     result = cli.run(project=project, args=["workspace", "list"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_SYMBOL_NAME)
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_invalid_yaml(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "invalid-yaml")
+    project = os.path.join(datafiles, "invalid-yaml")
     result = cli.run(project=project, args=["workspace", "list"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_YAML)
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_load_default_project(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "default")
+    project = os.path.join(datafiles, "default")
     result = cli.run(project=project, args=["show", "--format", "%{env}", "manual.bst"])
     result.assert_success()
 
@@ -98,7 +98,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_load_project_from_subdir(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "project-from-subdir")
+    project = os.path.join(datafiles, "project-from-subdir")
     result = cli.run(
         project=project, cwd=os.path.join(project, "subdirectory"), args=["show", "--format", "%{env}", "manual.bst"]
     )
@@ -112,7 +112,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_override_project_path(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "overridepath")
+    project = os.path.join(datafiles, "overridepath")
     result = cli.run(project=project, args=["show", "--format", "%{env}", "manual.bst"])
     result.assert_success()
 
@@ -123,7 +123,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_project_unsupported(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "unsupported")
+    project = os.path.join(datafiles, "unsupported")
 
     result = cli.run(project=project, args=["workspace", "list"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.UNSUPPORTED_PROJECT)
@@ -170,7 +170,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("ref_storage", [("inline"), ("project.refs")])
 def test_plugin_no_load_ref(cli, datafiles, ref_storage):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "plugin-no-load-ref")
+    project = os.path.join(datafiles, "plugin-no-load-ref")
 
     # Generate project with access to the noloadref plugin and project.refs enabled
     #
@@ -194,14 +194,14 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_plugin_preflight_error(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "plugin-preflight-error")
+    project = os.path.join(datafiles, "plugin-preflight-error")
     result = cli.run(project=project, args=["source", "fetch", "error.bst"])
     result.assert_main_error(ErrorDomain.SOURCE, "the-preflight-error")
 
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_duplicate_plugins(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "duplicate-plugins")
+    project = os.path.join(datafiles, "duplicate-plugins")
     result = cli.run(project=project, silent=True, args=["show", "element.bst"])
     result.assert_main_error(ErrorDomain.PLUGIN, "duplicate-plugin")
 
@@ -211,7 +211,7 @@
 #
 @pytest.mark.datafiles(DATA_DIR)
 def test_project_refs_options(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "refs-options")
+    project = os.path.join(datafiles, "refs-options")
 
     result1 = cli.run(
         project=project,
@@ -245,6 +245,6 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_empty_depends(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "empty-depends")
+    project = os.path.join(datafiles, "empty-depends")
     result = cli.run(project=project, args=["show", "manual.bst"])
     result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA)
diff --git a/tests/format/projectoverrides.py b/tests/format/projectoverrides.py
index 42793db..7e884ec 100644
--- a/tests/format/projectoverrides.py
+++ b/tests/format/projectoverrides.py
@@ -30,7 +30,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.skipif("not pip_sample_packages()", reason=SAMPLE_PACKAGES_SKIP_REASON)
 def test_prepend_configure_commands(cli, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "prepend-configure-commands")
+    project = os.path.join(datafiles, "prepend-configure-commands")
     result = cli.run(
         project=project, silent=True, args=["show", "--deps", "none", "--format", "%{config}", "element.bst"]
     )
diff --git a/tests/frontend/artifact_list_contents.py b/tests/frontend/artifact_list_contents.py
index 7a72ae6..37cfe66 100644
--- a/tests/frontend/artifact_list_contents.py
+++ b/tests/frontend/artifact_list_contents.py
@@ -97,7 +97,7 @@
         "{target}:\n"
         "\tdrwxr-xr-x  dir    0           usr\n"
         "\tdrwxr-xr-x  dir    0           usr/bin\n"
-        "\t-rw-r--r--  reg    28          usr/bin/hello\n\n"
+        "\t-rwxr-xr-x  exe    28          usr/bin/hello\n\n"
     )
     expected_output = expected_output_template.format(target=arg)
 
diff --git a/tests/frontend/completions.py b/tests/frontend/completions.py
index af477fb..2c4afa1 100644
--- a/tests/frontend/completions.py
+++ b/tests/frontend/completions.py
@@ -354,8 +354,8 @@
 
     # Use hard coded artifact names, cache keys should be stable now
     artifacts = [
-        "test/import-bin/b8eabff4ad70f954d6ba751340cff8f8e85cddd1537904cd107889b73f7b7041",
-        "test/import-bin/c737117d716278363c8398879ab557446c6f35d3d7472b75cb2e956f622da704",
+        "test/import-bin/4066f11d4e3f681bc7b9f738af8093473478907946801be2f28033e0f4c904a1",
+        "test/import-bin/b9f5aa9c50349a109431e2fbc23d3384a3a0bf490158b0352e896b82033c5137",
     ]
 
     # Test autocompletion of the artifact
diff --git a/tests/frontend/consistencyerror/plugins/consistencybug.py b/tests/frontend/consistencyerror/plugins/consistencybug.py
index 1952894..ea1ab2b 100644
--- a/tests/frontend/consistencyerror/plugins/consistencybug.py
+++ b/tests/frontend/consistencyerror/plugins/consistencybug.py
@@ -1,6 +1,10 @@
 from buildstream import Source
 
 
+class CustomError(Exception):
+    pass
+
+
 class ConsistencyBugSource(Source):
 
     BST_MIN_VERSION = "2.0"
@@ -20,7 +24,7 @@
     def is_cached(self):
 
         # Raise an unhandled exception (not a BstError)
-        raise Exception("Something went terribly wrong")
+        raise CustomError("Something went terribly wrong")
 
     def get_ref(self):
         return None
diff --git a/tests/frontend/logging.py b/tests/frontend/logging.py
index f7ccab2..0ab0fc5 100644
--- a/tests/frontend/logging.py
+++ b/tests/frontend/logging.py
@@ -81,7 +81,7 @@
     result.assert_success()
 
     m = re.search(
-        r"\d\d:\d\d:\d\d,\d\d:\d\d:\d\d.\d{6},\d\d:\d\d:\d\d,\d\d:\d\d:\d\d.\d{6}\s*,.*" r",SUCCESS,Query cache",
+        r"\d\d:\d\d:\d\d,\d\d:\d\d:\d\d.\d{6},\d\d:\d\d:\d\d,\d\d:\d\d:\d\d.\d{6}\s*,.*,SUCCESS,Query cache",
         result.stderr,
     )
     assert m is not None
diff --git a/tests/frontend/pull.py b/tests/frontend/pull.py
index 32067cc..87cc2de 100644
--- a/tests/frontend/pull.py
+++ b/tests/frontend/pull.py
@@ -357,7 +357,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_pull_missing_local_blob(cli, tmpdir, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename)
+    project = str(datafiles)
     repo = create_repo("tar", str(tmpdir))
     repo.create(os.path.join(str(datafiles), "files"))
     element_dir = os.path.join(str(tmpdir), "elements")
diff --git a/tests/frontend/push.py b/tests/frontend/push.py
index b7ed393..1a24771 100644
--- a/tests/frontend/push.py
+++ b/tests/frontend/push.py
@@ -744,7 +744,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("buildtrees", [("buildtrees"), ("normal")])
 def test_push_no_strict(caplog, cli, tmpdir, datafiles, buildtrees):
-    project = os.path.join(datafiles.dirname, datafiles.basename)
+    project = str(datafiles)
     caplog.set_level(1)
 
     with create_artifact_share(os.path.join(str(tmpdir), "artifactshare")) as share:
@@ -786,7 +786,7 @@
 # of a non-reproducible element.
 @pytest.mark.datafiles(DATA_DIR)
 def test_push_after_rebuild(cli, tmpdir, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename)
+    project = str(datafiles)
 
     generate_project(
         project,
@@ -822,7 +822,7 @@
 # artifact on the server.
 @pytest.mark.datafiles(DATA_DIR)
 def test_push_update_after_rebuild(cli, tmpdir, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename)
+    project = str(datafiles)
 
     generate_project(
         project,
diff --git a/tests/frontend/show.py b/tests/frontend/show.py
index 6aee29a..9b486f6 100644
--- a/tests/frontend/show.py
+++ b/tests/frontend/show.py
@@ -345,7 +345,6 @@
 @pytest.mark.parametrize("workspaced", [True, False], ids=["workspace", "no-workspace"])
 def test_fetched_junction(cli, tmpdir, datafiles, element_name, workspaced):
     project = str(datafiles)
-    project = os.path.join(datafiles.dirname, datafiles.basename)
     subproject_path = os.path.join(project, "files", "sub-project")
     junction_path = os.path.join(project, "elements", "junction.bst")
     element_path = os.path.join(project, "elements", "junction-dep.bst")
diff --git a/tests/frontend/track.py b/tests/frontend/track.py
index b30af4f..0ed8a7e 100644
--- a/tests/frontend/track.py
+++ b/tests/frontend/track.py
@@ -90,7 +90,7 @@
 @pytest.mark.parametrize("ref_storage", [("inline"), ("project-refs")])
 @pytest.mark.skipif("not pip_sample_packages()", reason=SAMPLE_PACKAGES_SKIP_REASON)
 def test_track_optional(cli, tmpdir, datafiles, ref_storage):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "track-optional-" + ref_storage)
+    project = os.path.join(datafiles, "track-optional-" + ref_storage)
     dev_files_path = os.path.join(project, "files")
     element_path = os.path.join(project, "target.bst")
 
@@ -398,7 +398,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.skipif("not pip_sample_packages()", reason=SAMPLE_PACKAGES_SKIP_REASON)
 def test_no_needless_overwrite(cli, tmpdir, datafiles):
-    project = os.path.join(datafiles.dirname, datafiles.basename)
+    project = str(datafiles)
     dev_files_path = os.path.join(project, "files", "dev-files")
     element_path = os.path.join(project, "elements")
     target = "track-test-target.bst"
diff --git a/tests/frontend/workspace.py b/tests/frontend/workspace.py
index 51887bf..8372a17 100644
--- a/tests/frontend/workspace.py
+++ b/tests/frontend/workspace.py
@@ -440,7 +440,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 def test_close_nonexistant_element(cli, tmpdir, datafiles):
     element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, "tar")
-    element_path = os.path.join(datafiles.dirname, datafiles.basename, "elements", element_name)
+    element_path = os.path.join(datafiles, "elements", element_name)
 
     # First brutally remove the element.bst file, ensuring that
     # the element does not exist anymore in the project where
diff --git a/tests/internals/context.py b/tests/internals/context.py
index bd68140..7c37491 100644
--- a/tests/internals/context.py
+++ b/tests/internals/context.py
@@ -88,7 +88,7 @@
     cache_home = context_fixture["xdg-cache"]
     assert isinstance(context, Context)
 
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, "userconf.yaml")
+    conf_file = os.path.join(datafiles, "userconf.yaml")
     context.load(conf_file)
 
     assert context.sourcedir == os.path.expanduser("~/pony")
@@ -132,7 +132,7 @@
     context = context_fixture["context"]
     assert isinstance(context, Context)
 
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, "nonexistant.yaml")
+    conf_file = os.path.join(datafiles, "nonexistant.yaml")
 
     with pytest.raises(LoadError) as exc:
         context.load(conf_file)
@@ -145,7 +145,7 @@
     context = context_fixture["context"]
     assert isinstance(context, Context)
 
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, "malformed.yaml")
+    conf_file = os.path.join(datafiles, "malformed.yaml")
 
     with pytest.raises(LoadError) as exc:
         context.load(conf_file)
@@ -158,7 +158,7 @@
     context = context_fixture["context"]
     assert isinstance(context, Context)
 
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, "notdict.yaml")
+    conf_file = os.path.join(datafiles, "notdict.yaml")
 
     with pytest.raises(LoadError) as exc:
         context.load(conf_file)
diff --git a/tests/internals/yaml.py b/tests/internals/yaml.py
index 662576e..4a18700 100644
--- a/tests/internals/yaml.py
+++ b/tests/internals/yaml.py
@@ -16,7 +16,7 @@
 
 import pytest
 
-from buildstream import _yaml, Node, MappingNode, ProvenanceInformation, SequenceNode
+from buildstream import _yaml, Node, ProvenanceInformation, SequenceNode
 from buildstream.exceptions import LoadErrorReason
 from buildstream._exceptions import LoadError
 
@@ -30,7 +30,7 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_load_yaml(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
 
     loaded = _yaml.load(filename, shortname=None)
     assert loaded.get_str("kind") == "pony"
@@ -49,7 +49,7 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_basic_provenance(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
 
     loaded = _yaml.load(filename, shortname=None)
     assert loaded.get_str("kind") == "pony"
@@ -60,7 +60,7 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_member_provenance(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
 
     loaded = _yaml.load(filename, shortname=None)
     assert loaded.get_str("kind") == "pony"
@@ -70,7 +70,7 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_element_provenance(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
 
     loaded = _yaml.load(filename, shortname=None)
     assert loaded.get_str("kind") == "pony"
@@ -80,8 +80,8 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_mapping_validate_keys(datafiles):
 
-    valid = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
-    invalid = os.path.join(datafiles.dirname, datafiles.basename, "invalid.yaml")
+    valid = os.path.join(datafiles, "basics.yaml")
+    invalid = os.path.join(datafiles, "invalid.yaml")
 
     base = _yaml.load(valid, shortname=None)
 
@@ -98,7 +98,7 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_node_get(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
 
     base = _yaml.load(filename, shortname=None)
     assert base.get_str("kind") == "pony"
@@ -120,7 +120,7 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_node_set(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
 
     base = _yaml.load(filename, shortname=None)
 
@@ -132,7 +132,7 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_node_set_overwrite(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
 
     base = _yaml.load(filename, shortname=None)
 
@@ -150,7 +150,7 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_node_set_list_element(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
 
     base = _yaml.load(filename, shortname=None)
 
@@ -167,8 +167,8 @@
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_composite_preserve_originals(datafiles):
 
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
-    overlayfile = os.path.join(datafiles.dirname, datafiles.basename, "composite.yaml")
+    filename = os.path.join(datafiles, "basics.yaml")
+    overlayfile = os.path.join(datafiles, "composite.yaml")
 
     base = _yaml.load(filename, shortname=None)
     overlay = _yaml.load(overlayfile, shortname=None)
@@ -229,8 +229,8 @@
     ],
 )
 def test_list_composition(datafiles, filename, tmpdir, index, length, mood, prov_file, prov_line, prov_col):
-    base_file = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
-    overlay_file = os.path.join(datafiles.dirname, datafiles.basename, filename)
+    base_file = os.path.join(datafiles, "basics.yaml")
+    overlay_file = os.path.join(datafiles, filename)
 
     base = _yaml.load(base_file, shortname="basics.yaml")
     overlay = _yaml.load(overlay_file, shortname=filename)
@@ -248,8 +248,8 @@
 # Test that overwriting a list with an empty list works as expected.
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_list_deletion(datafiles):
-    base = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
-    overlay = os.path.join(datafiles.dirname, datafiles.basename, "listoverwriteempty.yaml")
+    base = os.path.join(datafiles, "basics.yaml")
+    overlay = os.path.join(datafiles, "listoverwriteempty.yaml")
 
     base = _yaml.load(base, shortname="basics.yaml")
     overlay = _yaml.load(overlay, shortname="listoverwriteempty.yaml")
@@ -341,9 +341,9 @@
 def test_list_composition_twice(
     datafiles, tmpdir, filename1, filename2, index, length, mood, prov_file, prov_line, prov_col
 ):
-    file_base = os.path.join(datafiles.dirname, datafiles.basename, "basics.yaml")
-    file1 = os.path.join(datafiles.dirname, datafiles.basename, filename1)
-    file2 = os.path.join(datafiles.dirname, datafiles.basename, filename2)
+    file_base = os.path.join(datafiles, "basics.yaml")
+    file1 = os.path.join(datafiles, filename1)
+    file2 = os.path.join(datafiles, filename2)
 
     #####################
     # Round 1 - Fight !
@@ -382,7 +382,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_convert_value_to_string(datafiles):
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, "convert_value_to_str.yaml")
+    conf_file = os.path.join(datafiles, "convert_value_to_str.yaml")
 
     # Run file through yaml to convert it
     test_dict = _yaml.load(conf_file, shortname=None)
@@ -406,7 +406,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_value_doesnt_match_expected(datafiles):
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, "convert_value_to_str.yaml")
+    conf_file = os.path.join(datafiles, "convert_value_to_str.yaml")
 
     # Run file through yaml to convert it
     test_dict = _yaml.load(conf_file, shortname=None)
@@ -418,7 +418,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_roundtrip_dump(datafiles):
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "roundtrip-test.yaml")
+    filename = os.path.join(datafiles, "roundtrip-test.yaml")
     with open(filename, "r", encoding="utf-8") as fh:
         rt_raw = fh.read()
 
@@ -463,7 +463,7 @@
     ],
 )
 def test_node_find_target(datafiles, case):
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "traversal.yaml")
+    filename = os.path.join(datafiles, "traversal.yaml")
     # We set copy_tree in order to ensure that the nodes in `loaded`
     # are not the same nodes as in `prov.toplevel`
     loaded = _yaml.load(filename, shortname=None, copy_tree=True)
@@ -497,7 +497,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_node_find_target_fails(datafiles):
-    filename = os.path.join(datafiles.dirname, datafiles.basename, "traversal.yaml")
+    filename = os.path.join(datafiles, "traversal.yaml")
     loaded = _yaml.load(filename, shortname=None, copy_tree=True)
 
     brand_new = Node.from_dict({})
@@ -515,7 +515,7 @@
     ids=["list-of-dict", "list-of-list"],
 )
 def test_get_str_list_invalid(datafiles, filename, provenance):
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, filename)
+    conf_file = os.path.join(datafiles, filename)
 
     base = _yaml.load(conf_file, shortname=None)
 
@@ -527,7 +527,7 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_get_str_list_default_none(datafiles):
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, "list-of-dict.yaml")
+    conf_file = os.path.join(datafiles, "list-of-dict.yaml")
 
     base = _yaml.load(conf_file, shortname=None)
 
@@ -538,8 +538,8 @@
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))
 def test_mapping_node_assign_none(datafiles):
-    conf_file = os.path.join(datafiles.dirname, datafiles.basename, "dictionary.yaml")
-    dump_file = os.path.join(datafiles.dirname, datafiles.basename, "dictionary-dump.yaml")
+    conf_file = os.path.join(datafiles, "dictionary.yaml")
+    dump_file = os.path.join(datafiles, "dictionary-dump.yaml")
 
     base = _yaml.load(conf_file, shortname=None)
     nested = base.get_mapping("nested")
diff --git a/tests/sandboxes/remote-exec-config.py b/tests/sandboxes/remote-exec-config.py
index c89efc2..53e17c2 100644
--- a/tests/sandboxes/remote-exec-config.py
+++ b/tests/sandboxes/remote-exec-config.py
@@ -31,7 +31,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("config_key, config_value", [("client-cert", "client.crt"), ("client-key", "client.key")])
 def test_missing_certs(cli, datafiles, config_key, config_value):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "missing-certs")
+    project = os.path.join(datafiles, "missing-certs")
 
     cli.configure(
         {
diff --git a/tests/sourcecache/config.py b/tests/sourcecache/config.py
index 85dea84..3b4363c 100644
--- a/tests/sourcecache/config.py
+++ b/tests/sourcecache/config.py
@@ -35,7 +35,7 @@
 @pytest.mark.datafiles(DATA_DIR)
 @pytest.mark.parametrize("config_key, config_value", [("client-cert", "client.crt"), ("client-key", "client.key")])
 def test_missing_certs(cli, datafiles, config_key, config_value):
-    project = os.path.join(datafiles.dirname, datafiles.basename, "missing-certs")
+    project = os.path.join(datafiles, "missing-certs")
 
     project_conf = {
         "name": "test",
diff --git a/tests/sourcecache/staging.py b/tests/sourcecache/staging.py
index 786a240..933013a 100644
--- a/tests/sourcecache/staging.py
+++ b/tests/sourcecache/staging.py
@@ -42,7 +42,7 @@
 
 @pytest.mark.datafiles(DATA_DIR)
 def test_source_staged(tmpdir, cli, datafiles):
-    project_dir = os.path.join(datafiles.dirname, datafiles.basename, "project")
+    project_dir = os.path.join(datafiles, "project")
     cachedir = os.path.join(str(tmpdir), "cache")
 
     cli.configure({"cachedir": cachedir})
@@ -79,7 +79,7 @@
 # Check sources are staged during a fetch
 @pytest.mark.datafiles(DATA_DIR)
 def test_source_fetch(tmpdir, cli, datafiles):
-    project_dir = os.path.join(datafiles.dirname, datafiles.basename, "project")
+    project_dir = os.path.join(datafiles, "project")
     cachedir = os.path.join(str(tmpdir), "cache")
 
     cli.configure({"cachedir": cachedir})
@@ -113,7 +113,7 @@
 # Check that with sources only in the CAS build successfully completes
 @pytest.mark.datafiles(DATA_DIR)
 def test_staged_source_build(tmpdir, datafiles, cli):
-    project_dir = os.path.join(datafiles.dirname, datafiles.basename, "project")
+    project_dir = os.path.join(datafiles, "project")
     cachedir = os.path.join(str(tmpdir), "cache")
     element_path = "elements"
     source_protos = os.path.join(str(tmpdir), "cache", "source_protos")
diff --git a/tests/testutils/artifactshare.py b/tests/testutils/artifactshare.py
index 753384b..515164a 100644
--- a/tests/testutils/artifactshare.py
+++ b/tests/testutils/artifactshare.py
@@ -34,6 +34,10 @@
 REMOTE_ASSET_SOURCE_URN_TEMPLATE = "urn:fdc:buildstream.build:2020:source:{}"
 
 
+class ArtifactSharror(Exception):
+    pass
+
+
 class BaseArtifactShare:
     def __init__(self):
         multiprocessing_context = multiprocessing.get_context("forkserver")
@@ -47,7 +51,7 @@
         port = q.get()
 
         if port is None:
-            raise Exception("Error occurred when starting artifact server.")
+            raise ArtifactSharror("Error occurred when starting artifact server.")
 
         self.repo = "http://localhost:{}".format(port)
 
diff --git a/tests/testutils/setuptools.py b/tests/testutils/setuptools.py
index 17f4b86..d14c70b 100644
--- a/tests/testutils/setuptools.py
+++ b/tests/testutils/setuptools.py
@@ -23,7 +23,7 @@
         self.module_name = module_name
 
     def get_resource_filename(self, *_args, **_kwargs):
-        return os.path.join(self.datafiles.dirname, self.datafiles.basename, self.module_name)
+        return os.path.join(self.datafiles, self.module_name)
 
 
 # A mock setuptools entry object.