summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Gomes <tiago.avv@gmail.com>2018-08-02 12:10:29 +0000
committerTiago Gomes <tiago.avv@gmail.com>2018-08-02 12:10:29 +0000
commit8656a65d21bd002b1fd490481e736f1748e8db86 (patch)
treea54839da778fc48c22fac07c4d64fa8dc7e3ed93
parent039d43e43431957a42193f12f06acb7b95c2eae4 (diff)
parent9f0c8fcfe348533987ec128b85356655edae77f4 (diff)
downloadbuildstream-8656a65d21bd002b1fd490481e736f1748e8db86.tar.gz
Merge branch 'tiagogomes/issue-195' into 'master'Qinusty/490-artifact-cache-interactivity
Add validation for project paths See merge request BuildStream/buildstream!593
-rw-r--r--.pylintrc2
-rw-r--r--buildstream/_exceptions.py10
-rw-r--r--buildstream/_project.py9
-rw-r--r--buildstream/_yaml.py92
-rw-r--r--buildstream/plugin.py47
-rw-r--r--buildstream/plugins/sources/local.py12
-rw-r--r--buildstream/plugins/sources/ostree.py8
-rw-r--r--buildstream/plugins/sources/patch.py13
-rw-r--r--doc/examples/junctions/autotools/elements/base.bst5
-rw-r--r--doc/examples/junctions/autotools/elements/base/alpine.bst13
-rw-r--r--doc/examples/junctions/autotools/elements/hello.bst21
-rw-r--r--doc/examples/junctions/autotools/project.conf13
-rw-r--r--doc/examples/junctions/elements/hello-junction.bst6
-rw-r--r--doc/source/advanced-features/junction-elements.rst4
-rw-r--r--tests/artifactcache/expiry.py69
-rw-r--r--tests/examples/junctions.py32
-rw-r--r--tests/format/project.py44
-rw-r--r--tests/format/project/element-path/project.conf2
-rw-r--r--tests/format/project/local-plugin/project.conf6
-rw-r--r--tests/frontend/push.py22
-rw-r--r--tests/sources/local.py55
-rw-r--r--tests/sources/local/invalid-relative-path/file.txt1
-rw-r--r--tests/sources/local/invalid-relative-path/project.conf2
-rw-r--r--tests/sources/local/invalid-relative-path/target.bst5
-rw-r--r--tests/sources/patch.py51
-rw-r--r--tests/sources/patch/invalid-relative-path/file_1.patch7
-rw-r--r--tests/sources/patch/invalid-relative-path/irregular.bst5
-rw-r--r--tests/sources/patch/invalid-relative-path/project.conf2
-rw-r--r--tests/testutils/element_generators.py11
-rw-r--r--tests/testutils/filetypegenerator.py62
30 files changed, 492 insertions, 139 deletions
diff --git a/.pylintrc b/.pylintrc
index 93f9eeadf..153a9ef3e 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -184,7 +184,7 @@ ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
-ignored-classes=optparse.Values,thread._local,_thread._local,contextlib.closing,gi.repository.GLib.GError
+ignored-classes=optparse.Values,thread._local,_thread._local,contextlib.closing,gi.repository.GLib.GError,pathlib.PurePath
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
diff --git a/buildstream/_exceptions.py b/buildstream/_exceptions.py
index e55d942fd..5187357c5 100644
--- a/buildstream/_exceptions.py
+++ b/buildstream/_exceptions.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 Codethink Limited
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -16,6 +16,7 @@
#
# Authors:
# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
+# Tiago Gomes <tiago.gomes@codethink.co.uk>
from enum import Enum
@@ -206,6 +207,13 @@ class LoadErrorReason(Enum):
# Try to load a directory not a yaml file
LOADING_DIRECTORY = 18
+ # A project path leads outside of the project directory
+ PROJ_PATH_INVALID = 19
+
+ # A project path points to a file of the not right kind (e.g. a
+ # socket)
+ PROJ_PATH_INVALID_KIND = 20
+
# LoadError
#
diff --git a/buildstream/_project.py b/buildstream/_project.py
index 1c30fb9bb..3ac562836 100644
--- a/buildstream/_project.py
+++ b/buildstream/_project.py
@@ -16,6 +16,7 @@
#
# Authors:
# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
+# Tiago Gomes <tiago.gomes@codethink.co.uk>
import os
import multiprocessing # for cpu_count()
@@ -291,7 +292,8 @@ class Project():
self.element_path = os.path.join(
self.directory,
- _yaml.node_get(config, str, 'element-path')
+ _yaml.node_get_project_path(config, 'element-path', self.directory,
+ check_is_dir=True)
)
# Load project options
@@ -500,8 +502,11 @@ class Project():
if group in origin_dict:
del origin_dict[group]
if origin_dict['origin'] == 'local':
+ path = _yaml.node_get_project_path(origin, 'path',
+ self.directory,
+ check_is_dir=True)
# paths are passed in relative to the project, but must be absolute
- origin_dict['path'] = os.path.join(self.directory, origin_dict['path'])
+ origin_dict['path'] = os.path.join(self.directory, path)
destination.append(origin_dict)
# _ensure_project_dir()
diff --git a/buildstream/_yaml.py b/buildstream/_yaml.py
index 0e090e2e7..33ee444aa 100644
--- a/buildstream/_yaml.py
+++ b/buildstream/_yaml.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 Codethink Limited
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -22,6 +22,7 @@ import collections
import string
from copy import deepcopy
from contextlib import ExitStack
+from pathlib import Path
from ruamel import yaml
from ruamel.yaml.representer import SafeRepresenter, RoundTripRepresenter
@@ -392,6 +393,95 @@ def node_get(node, expected_type, key, indices=None, default_value=_get_sentinel
return value
+# node_get_project_path()
+#
+# Fetches a project path from a dictionary node and validates it
+#
+# Paths are asserted to never lead to a directory outside of the project
+# directory. In addition, paths can not point to symbolic links, fifos,
+# sockets and block/character devices.
+#
+# The `check_is_file` and `check_is_dir` parameters can be used to
+# perform additional validations on the path. Note that an exception
+# will always be raised if both parameters are set to ``True``.
+#
+# Args:
+# node (dict): A dictionary loaded from YAML
+# key (str): The key whose value contains a path to validate
+# project_dir (str): The project directory
+# check_is_file (bool): If ``True`` an error will also be raised
+# if path does not point to a regular file.
+# Defaults to ``False``
+# check_is_dir (bool): If ``True`` an error will be also raised
+# if path does not point to a directory.
+# Defaults to ``False``
+# Returns:
+# (str): The project path
+#
+# Raises:
+# (LoadError): In case that the project path is not valid or does not
+# exist
+#
+def node_get_project_path(node, key, project_dir, *,
+ check_is_file=False, check_is_dir=False):
+ path_str = node_get(node, str, key)
+ path = Path(path_str)
+ project_dir_path = Path(project_dir)
+
+ provenance = node_get_provenance(node, key=key)
+
+ if (project_dir_path / path).is_symlink():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' must not point to "
+ "symbolic links "
+ .format(provenance, path_str))
+
+ if path.parts and path.parts[0] == '..':
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
+ "{}: Specified path '{}' first component must "
+ "not be '..'"
+ .format(provenance, path_str))
+
+ try:
+ if sys.version_info[0] == 3 and sys.version_info[1] < 6:
+ full_resolved_path = (project_dir_path / path).resolve()
+ else:
+ full_resolved_path = (project_dir_path / path).resolve(strict=True)
+ except FileNotFoundError:
+ raise LoadError(LoadErrorReason.MISSING_FILE,
+ "{}: Specified path '{}' does not exist"
+ .format(provenance, path_str))
+
+ is_inside = project_dir_path in full_resolved_path.parents or (
+ full_resolved_path == project_dir_path)
+
+ if path.is_absolute() or not is_inside:
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
+ "{}: Specified path '{}' must not lead outside of the "
+ "project directory"
+ .format(provenance, path_str))
+
+ if full_resolved_path.is_socket() or (
+ full_resolved_path.is_fifo() or
+ full_resolved_path.is_block_device()):
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' points to an unsupported "
+ "file kind"
+ .format(provenance, path_str))
+
+ if check_is_file and not full_resolved_path.is_file():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' is not a regular file"
+ .format(provenance, path_str))
+
+ if check_is_dir and not full_resolved_path.is_dir():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' is not a directory"
+ .format(provenance, path_str))
+
+ return path_str
+
+
# node_items()
#
# A convenience generator for iterating over loaded key/value
diff --git a/buildstream/plugin.py b/buildstream/plugin.py
index 155a9500e..836b60834 100644
--- a/buildstream/plugin.py
+++ b/buildstream/plugin.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2017 Codethink Limited
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -335,6 +335,51 @@ class Plugin():
"""
return _yaml.node_get(node, expected_type, member_name, default_value=default)
+ def node_get_project_path(self, node, key, *,
+ check_is_file=False, check_is_dir=False):
+ """Fetches a project path from a dictionary node and validates it
+
+ Paths are asserted to never lead to a directory outside of the
+ project directory. In addition, paths can not point to symbolic
+ links, fifos, sockets and block/character devices.
+
+ The `check_is_file` and `check_is_dir` parameters can be used to
+ perform additional validations on the path. Note that an
+ exception will always be raised if both parameters are set to
+ ``True``.
+
+ Args:
+ node (dict): A dictionary loaded from YAML
+ key (str): The key whose value contains a path to validate
+ check_is_file (bool): If ``True`` an error will also be raised
+ if path does not point to a regular file.
+ Defaults to ``False``
+ check_is_dir (bool): If ``True`` an error will also be raised
+ if path does not point to a directory.
+ Defaults to ``False``
+
+ Returns:
+ (str): The project path
+
+ Raises:
+ :class:`.LoadError`: In the case that the project path is not
+ valid or does not exist
+
+ *Since: 1.2*
+
+ **Example:**
+
+ .. code:: python
+
+ path = self.node_get_project_path(node, 'path')
+
+ """
+
+ return _yaml.node_get_project_path(node, key,
+ self.__project.directory,
+ check_is_file=check_is_file,
+ check_is_dir=check_is_dir)
+
def node_validate(self, node, valid_keys):
"""This should be used in :func:`~buildstream.plugin.Plugin.configure`
implementations to assert that users have only entered
diff --git a/buildstream/plugins/sources/local.py b/buildstream/plugins/sources/local.py
index e3b019f1a..058553424 100644
--- a/buildstream/plugins/sources/local.py
+++ b/buildstream/plugins/sources/local.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 Codethink Limited
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -16,6 +16,7 @@
#
# Authors:
# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
+# Tiago Gomes <tiago.gomes@codethink.co.uk>
"""
local - stage local files and directories
@@ -36,7 +37,7 @@ local - stage local files and directories
"""
import os
-from buildstream import Source, SourceError, Consistency
+from buildstream import Source, Consistency
from buildstream import utils
@@ -51,14 +52,11 @@ class LocalSource(Source):
def configure(self, node):
self.node_validate(node, ['path'] + Source.COMMON_CONFIG_KEYS)
-
- self.path = self.node_get_member(node, str, 'path')
+ self.path = self.node_get_project_path(node, 'path')
self.fullpath = os.path.join(self.get_project_directory(), self.path)
def preflight(self):
- # Check if the configured file or directory really exists
- if not os.path.exists(self.fullpath):
- raise SourceError("Specified path '{}' does not exist".format(self.path))
+ pass
def get_unique_key(self):
if self.__unique_key is None:
diff --git a/buildstream/plugins/sources/ostree.py b/buildstream/plugins/sources/ostree.py
index 94fe5093f..3a841c488 100644
--- a/buildstream/plugins/sources/ostree.py
+++ b/buildstream/plugins/sources/ostree.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 Codethink Limited
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -16,6 +16,7 @@
#
# Authors:
# Andrew Leeming <andrew.leeming@codethink.co.uk>
+# Tiago Gomes <tiago.gomes@codethink.co.uk>
"""
ostree - stage files from an OSTree repository
@@ -73,9 +74,10 @@ class OSTreeSource(Source):
utils.url_directory_name(self.url))
# (optional) Not all repos are signed. But if they are, get the gpg key
- self.gpg_key = self.node_get_member(node, str, 'gpg-key', None)
self.gpg_key_path = None
- if self.gpg_key is not None:
+ if self.node_get_member(node, str, 'gpg-key', None):
+ self.gpg_key = self.node_get_project_path(node, 'gpg-key',
+ check_is_file=True)
self.gpg_key_path = os.path.join(self.get_project_directory(), self.gpg_key)
# Our OSTree repo handle
diff --git a/buildstream/plugins/sources/patch.py b/buildstream/plugins/sources/patch.py
index 11b66b3ea..2fa002080 100644
--- a/buildstream/plugins/sources/patch.py
+++ b/buildstream/plugins/sources/patch.py
@@ -1,5 +1,6 @@
#
# Copyright Bloomberg Finance LP
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -16,6 +17,7 @@
#
# Authors:
# Chandan Singh <csingh43@bloomberg.net>
+# Tiago Gomes <tiago.gomes@codethink.co.uk>
"""
patch - apply locally stored patches
@@ -52,19 +54,12 @@ class PatchSource(Source):
# pylint: disable=attribute-defined-outside-init
def configure(self, node):
- self.path = self.node_get_member(node, str, "path")
+ self.path = self.node_get_project_path(node, 'path',
+ check_is_file=True)
self.strip_level = self.node_get_member(node, int, "strip-level", 1)
self.fullpath = os.path.join(self.get_project_directory(), self.path)
def preflight(self):
- # Check if the configured file really exists
- if not os.path.exists(self.fullpath):
- raise SourceError("Specified path '{}' does not exist".format(self.path),
- reason="patch-no-exist")
- elif not os.path.isfile(self.fullpath):
- raise SourceError("Specified path '{}' must be a file".format(self.path),
- reason="patch-not-a-file")
-
# Check if patch is installed, get the binary at the same time
self.host_patch = utils.get_host_tool("patch")
diff --git a/doc/examples/junctions/autotools/elements/base.bst b/doc/examples/junctions/autotools/elements/base.bst
new file mode 100644
index 000000000..1b85a9e8c
--- /dev/null
+++ b/doc/examples/junctions/autotools/elements/base.bst
@@ -0,0 +1,5 @@
+kind: stack
+description: Base stack
+
+depends:
+- base/alpine.bst
diff --git a/doc/examples/junctions/autotools/elements/base/alpine.bst b/doc/examples/junctions/autotools/elements/base/alpine.bst
new file mode 100644
index 000000000..cf85df5bf
--- /dev/null
+++ b/doc/examples/junctions/autotools/elements/base/alpine.bst
@@ -0,0 +1,13 @@
+kind: import
+description: |
+
+ Alpine Linux base runtime
+
+sources:
+- kind: tar
+
+ # This is a post doctored, trimmed down system image
+ # of the Alpine linux distribution.
+ #
+ url: alpine:integration-tests-base.v1.x86_64.tar.xz
+ ref: 3eb559250ba82b64a68d86d0636a6b127aa5f6d25d3601a79f79214dc9703639
diff --git a/doc/examples/junctions/autotools/elements/hello.bst b/doc/examples/junctions/autotools/elements/hello.bst
new file mode 100644
index 000000000..510f5b975
--- /dev/null
+++ b/doc/examples/junctions/autotools/elements/hello.bst
@@ -0,0 +1,21 @@
+kind: autotools
+description: |
+
+ Hello world example from automake
+
+variables:
+
+ # The hello world example lives in the doc/amhello folder.
+ #
+ # Set the %{command-subdir} variable to that location
+ # and just have the autotools element run it's commands there.
+ #
+ command-subdir: doc/amhello
+
+sources:
+- kind: tar
+ url: gnu:automake-1.16.tar.gz
+ ref: 80da43bb5665596ee389e6d8b64b4f122ea4b92a685b1dbd813cd1f0e0c2d83f
+
+depends:
+- base.bst
diff --git a/doc/examples/junctions/autotools/project.conf b/doc/examples/junctions/autotools/project.conf
new file mode 100644
index 000000000..7ee58b589
--- /dev/null
+++ b/doc/examples/junctions/autotools/project.conf
@@ -0,0 +1,13 @@
+# Unique project name
+name: autotools
+
+# Required BuildStream format version
+format-version: 9
+
+# Subdirectory where elements are stored
+element-path: elements
+
+# Define some aliases for the tarballs we download
+aliases:
+ alpine: https://gnome7.codethink.co.uk/tarballs/
+ gnu: https://ftp.gnu.org/gnu/automake/
diff --git a/doc/examples/junctions/elements/hello-junction.bst b/doc/examples/junctions/elements/hello-junction.bst
index dda865ecf..6d01e36a1 100644
--- a/doc/examples/junctions/elements/hello-junction.bst
+++ b/doc/examples/junctions/elements/hello-junction.bst
@@ -1,8 +1,4 @@
kind: junction
-
-# Specify the source of the BuildStream project
-# We are going to use the autotools examples distributed with BuildStream in the
-# doc/examples/autotools directory
sources:
- kind: local
- path: ../autotools
+ path: autotools
diff --git a/doc/source/advanced-features/junction-elements.rst b/doc/source/advanced-features/junction-elements.rst
index 929ac1217..81fc01a05 100644
--- a/doc/source/advanced-features/junction-elements.rst
+++ b/doc/source/advanced-features/junction-elements.rst
@@ -21,8 +21,8 @@ Below is a simple example of bst file for a junction element:
.. literalinclude:: ../../examples/junctions/elements/hello-junction.bst
:language: yaml
-This element imports the autotools example project found in the BuildStream
-doc/examples/autotools subdirectory.
+This element imports the autotools example subproject found in the
+BuildStream doc/examples/junctions/autotools subdirectory.
.. note::
diff --git a/tests/artifactcache/expiry.py b/tests/artifactcache/expiry.py
index 4c741054b..9c74eb1c4 100644
--- a/tests/artifactcache/expiry.py
+++ b/tests/artifactcache/expiry.py
@@ -5,7 +5,7 @@ import pytest
from buildstream import _yaml
from buildstream._exceptions import ErrorDomain, LoadErrorReason
-from tests.testutils import cli
+from tests.testutils import cli, create_element_size
DATA_DIR = os.path.join(
@@ -14,32 +14,12 @@ DATA_DIR = os.path.join(
)
-def create_element(name, path, dependencies, size):
- os.makedirs(path, exist_ok=True)
-
- # Create a file to be included in this element's artifact
- with open(os.path.join(path, name + '_data'), 'wb+') as f:
- f.write(os.urandom(size))
-
- element = {
- 'kind': 'import',
- 'sources': [
- {
- 'kind': 'local',
- 'path': os.path.join(path, name + '_data')
- }
- ],
- 'depends': dependencies
- }
- _yaml.dump(element, os.path.join(path, name))
-
-
# Ensure that the cache successfully removes an old artifact if we do
# not have enough space left.
@pytest.mark.datafiles(DATA_DIR)
def test_artifact_expires(cli, datafiles, tmpdir):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ element_path = 'elements'
cache_location = os.path.join(project, 'cache', 'artifacts', 'ostree')
checkout = os.path.join(project, 'checkout')
@@ -52,7 +32,7 @@ def test_artifact_expires(cli, datafiles, tmpdir):
# Create an element that uses almost the entire cache (an empty
# ostree cache starts at about ~10KiB, so we need a bit of a
# buffer)
- create_element('target.bst', element_path, [], 6000000)
+ create_element_size('target.bst', project, element_path, [], 6000000)
res = cli.run(project=project, args=['build', 'target.bst'])
res.assert_success()
@@ -61,7 +41,7 @@ def test_artifact_expires(cli, datafiles, tmpdir):
# Our cache should now be almost full. Let's create another
# artifact and see if we can cause buildstream to delete the old
# one.
- create_element('target2.bst', element_path, [], 6000000)
+ create_element_size('target2.bst', project, element_path, [], 6000000)
res = cli.run(project=project, args=['build', 'target2.bst'])
res.assert_success()
@@ -82,7 +62,7 @@ def test_artifact_expires(cli, datafiles, tmpdir):
@pytest.mark.datafiles(DATA_DIR)
def test_artifact_too_large(cli, datafiles, tmpdir, size):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ element_path = 'elements'
cli.configure({
'cache': {
@@ -91,7 +71,7 @@ def test_artifact_too_large(cli, datafiles, tmpdir, size):
})
# Create an element whose artifact is too large
- create_element('target.bst', element_path, [], size)
+ create_element_size('target.bst', project, element_path, [], size)
res = cli.run(project=project, args=['build', 'target.bst'])
res.assert_main_error(ErrorDomain.STREAM, None)
@@ -99,7 +79,7 @@ def test_artifact_too_large(cli, datafiles, tmpdir, size):
@pytest.mark.datafiles(DATA_DIR)
def test_expiry_order(cli, datafiles, tmpdir):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ element_path = 'elements'
cache_location = os.path.join(project, 'cache', 'artifacts', 'ostree')
checkout = os.path.join(project, 'workspace')
@@ -110,21 +90,21 @@ def test_expiry_order(cli, datafiles, tmpdir):
})
# Create an artifact
- create_element('dep.bst', element_path, [], 2000000)
+ create_element_size('dep.bst', project, element_path, [], 2000000)
res = cli.run(project=project, args=['build', 'dep.bst'])
res.assert_success()
# Create another artifact
- create_element('unrelated.bst', element_path, [], 2000000)
+ create_element_size('unrelated.bst', project, element_path, [], 2000000)
res = cli.run(project=project, args=['build', 'unrelated.bst'])
res.assert_success()
# And build something else
- create_element('target.bst', element_path, [], 2000000)
+ create_element_size('target.bst', project, element_path, [], 2000000)
res = cli.run(project=project, args=['build', 'target.bst'])
res.assert_success()
- create_element('target2.bst', element_path, [], 2000000)
+ create_element_size('target2.bst', project, element_path, [], 2000000)
res = cli.run(project=project, args=['build', 'target2.bst'])
res.assert_success()
@@ -133,7 +113,7 @@ def test_expiry_order(cli, datafiles, tmpdir):
res.assert_success()
# Finally, build something that will cause the cache to overflow
- create_element('expire.bst', element_path, [], 2000000)
+ create_element_size('expire.bst', project, element_path, [], 2000000)
res = cli.run(project=project, args=['build', 'expire.bst'])
res.assert_success()
@@ -153,7 +133,7 @@ def test_expiry_order(cli, datafiles, tmpdir):
@pytest.mark.datafiles(DATA_DIR)
def test_keep_dependencies(cli, datafiles, tmpdir):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ element_path = 'elements'
cache_location = os.path.join(project, 'cache', 'artifacts', 'ostree')
cli.configure({
@@ -163,12 +143,12 @@ def test_keep_dependencies(cli, datafiles, tmpdir):
})
# Create a pretty big dependency
- create_element('dependency.bst', element_path, [], 5000000)
+ create_element_size('dependency.bst', project, element_path, [], 5000000)
res = cli.run(project=project, args=['build', 'dependency.bst'])
res.assert_success()
# Now create some other unrelated artifact
- create_element('unrelated.bst', element_path, [], 4000000)
+ create_element_size('unrelated.bst', project, element_path, [], 4000000)
res = cli.run(project=project, args=['build', 'unrelated.bst'])
res.assert_success()
@@ -184,7 +164,8 @@ def test_keep_dependencies(cli, datafiles, tmpdir):
# duplicating artifacts (bad!) we need to make this equal in size
# or smaller than half the size of its dependencies.
#
- create_element('target.bst', element_path, ['dependency.bst'], 2000000)
+ create_element_size('target.bst', project,
+ element_path, ['dependency.bst'], 2000000)
res = cli.run(project=project, args=['build', 'target.bst'])
res.assert_success()
@@ -197,7 +178,7 @@ def test_keep_dependencies(cli, datafiles, tmpdir):
@pytest.mark.datafiles(DATA_DIR)
def test_never_delete_dependencies(cli, datafiles, tmpdir):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ element_path = 'elements'
cli.configure({
'cache': {
@@ -206,10 +187,14 @@ def test_never_delete_dependencies(cli, datafiles, tmpdir):
})
# Create a build tree
- create_element('dependency.bst', element_path, [], 8000000)
- create_element('related.bst', element_path, ['dependency.bst'], 8000000)
- create_element('target.bst', element_path, ['related.bst'], 8000000)
- create_element('target2.bst', element_path, ['target.bst'], 8000000)
+ create_element_size('dependency.bst', project,
+ element_path, [], 8000000)
+ create_element_size('related.bst', project,
+ element_path, ['dependency.bst'], 8000000)
+ create_element_size('target.bst', project,
+ element_path, ['related.bst'], 8000000)
+ create_element_size('target2.bst', project,
+ element_path, ['target.bst'], 8000000)
# We try to build this pipeline, but it's too big for the
# cache. Since all elements are required, the build should fail.
@@ -249,7 +234,7 @@ def test_never_delete_dependencies(cli, datafiles, tmpdir):
@pytest.mark.datafiles(DATA_DIR)
def test_invalid_cache_quota(cli, datafiles, tmpdir, quota, success):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ os.makedirs(os.path.join(project, 'elements'))
cli.configure({
'cache': {
diff --git a/tests/examples/junctions.py b/tests/examples/junctions.py
index 49e2ebbff..d2a653884 100644
--- a/tests/examples/junctions.py
+++ b/tests/examples/junctions.py
@@ -11,42 +11,12 @@ DATA_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)), '..', '..', 'doc', 'examples', 'junctions'
)
-JUNCTION_IMPORT_PATH = os.path.join(
- os.path.dirname(os.path.realpath(__file__)), '..', '..', 'doc', 'examples', 'autotools'
-)
-
-
-def ammend_juntion_path_paths(tmpdir):
- # The junction element in the examples/junctions project uses a local source type.
- # It's "path:" must specify a relative path from the project's root directory.
- # For the hello-junction element to function during these tests, the copy of the junctions
- # project made in the buildstream/tmp/directory, "path:" must be ammended to be the relative
- # path to the autotools example from the temporary test directory.
- junction_element = os.path.join(tmpdir, "elements", "hello-junction.bst")
- junction_element_bst = ""
- junction_relative_path = os.path.relpath(JUNCTION_IMPORT_PATH, tmpdir)
- with open(junction_element, 'r') as f:
- junction_element_bst = f.read()
- ammended_element_bst = junction_element_bst.replace("../autotools", junction_relative_path)
- with open(junction_element, 'w') as f:
- f.write(ammended_element_bst)
-
-
-# Check that the autotools project is where the junctions example expects and
-# contains the hello.bst element.
-@pytest.mark.datafiles(DATA_DIR)
-def test_autotools_example_is_present(datafiles):
- autotools_path = JUNCTION_IMPORT_PATH
- assert os.path.exists(autotools_path)
- assert os.path.exists(os.path.join(autotools_path, "elements", "hello.bst"))
-
# Test that the project builds successfully
@pytest.mark.skipif(not IS_LINUX, reason='Only available on linux')
@pytest.mark.datafiles(DATA_DIR)
def test_build(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
- ammend_juntion_path_paths(str(tmpdir))
result = cli.run(project=project, args=['build', 'callHello.bst'])
result.assert_success()
@@ -57,7 +27,6 @@ def test_build(cli, tmpdir, datafiles):
@pytest.mark.datafiles(DATA_DIR)
def test_shell_call_hello(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
- ammend_juntion_path_paths(str(tmpdir))
result = cli.run(project=project, args=['build', 'callHello.bst'])
result.assert_success()
@@ -73,7 +42,6 @@ def test_shell_call_hello(cli, tmpdir, datafiles):
def test_open_cross_junction_workspace(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
workspace_dir = os.path.join(str(tmpdir), "workspace_hello_junction")
- ammend_juntion_path_paths(str(tmpdir))
result = cli.run(project=project,
args=['workspace', 'open', 'hello-junction.bst:hello.bst', workspace_dir])
diff --git a/tests/format/project.py b/tests/format/project.py
index 9d595981b..df1a2364b 100644
--- a/tests/format/project.py
+++ b/tests/format/project.py
@@ -2,7 +2,7 @@ import os
import pytest
from buildstream import _yaml
from buildstream._exceptions import ErrorDomain, LoadErrorReason
-from tests.testutils.runcli import cli
+from tests.testutils import cli, filetypegenerator
# Project directory
@@ -90,6 +90,48 @@ def test_project_unsupported(cli, datafiles):
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.UNSUPPORTED_PROJECT)
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'element-path'))
+def test_missing_element_path_directory(cli, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ result = cli.run(project=project, args=['workspace', 'list'])
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.MISSING_FILE)
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'element-path'))
+def test_element_path_not_a_directory(cli, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ path = os.path.join(project, 'elements')
+ for file_type in filetypegenerator.generate_file_types(path):
+ result = cli.run(project=project, args=['workspace', 'list'])
+ if not os.path.isdir(path):
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.PROJ_PATH_INVALID_KIND)
+ else:
+ result.assert_success()
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'local-plugin'))
+def test_missing_local_plugin_directory(cli, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ result = cli.run(project=project, args=['workspace', 'list'])
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.MISSING_FILE)
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'local-plugin'))
+def test_local_plugin_not_directory(cli, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ path = os.path.join(project, 'plugins')
+ for file_type in filetypegenerator.generate_file_types(path):
+ result = cli.run(project=project, args=['workspace', 'list'])
+ if not os.path.isdir(path):
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.PROJ_PATH_INVALID_KIND)
+ else:
+ result.assert_success()
+
+
@pytest.mark.datafiles(DATA_DIR)
def test_project_plugin_load_allowed(cli, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename, 'plugin-allowed')
diff --git a/tests/format/project/element-path/project.conf b/tests/format/project/element-path/project.conf
new file mode 100644
index 000000000..57e87de4f
--- /dev/null
+++ b/tests/format/project/element-path/project.conf
@@ -0,0 +1,2 @@
+name: foo
+element-path: elements
diff --git a/tests/format/project/local-plugin/project.conf b/tests/format/project/local-plugin/project.conf
new file mode 100644
index 000000000..97166e350
--- /dev/null
+++ b/tests/format/project/local-plugin/project.conf
@@ -0,0 +1,6 @@
+name: foo
+plugins:
+- origin: local
+ path: plugins
+ sources:
+ mysource: 0
diff --git a/tests/frontend/push.py b/tests/frontend/push.py
index 471991ef7..be324ca53 100644
--- a/tests/frontend/push.py
+++ b/tests/frontend/push.py
@@ -202,7 +202,7 @@ def test_push_after_pull(cli, tmpdir, datafiles):
@pytest.mark.datafiles(DATA_DIR)
def test_artifact_expires(cli, datafiles, tmpdir):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ element_path = 'elements'
# Create an artifact share (remote artifact cache) in the tmpdir/artifactshare
# Mock a file system with 12 MB free disk space
@@ -215,12 +215,12 @@ def test_artifact_expires(cli, datafiles, tmpdir):
})
# Create and build an element of 5 MB
- create_element_size('element1.bst', element_path, [], int(5e6)) # [] => no deps
+ create_element_size('element1.bst', project, element_path, [], int(5e6))
result = cli.run(project=project, args=['build', 'element1.bst'])
result.assert_success()
# Create and build an element of 5 MB
- create_element_size('element2.bst', element_path, [], int(5e6)) # [] => no deps
+ create_element_size('element2.bst', project, element_path, [], int(5e6))
result = cli.run(project=project, args=['build', 'element2.bst'])
result.assert_success()
@@ -231,7 +231,7 @@ def test_artifact_expires(cli, datafiles, tmpdir):
assert_shared(cli, share, project, 'element2.bst')
# Create and build another element of 5 MB (This will exceed the free disk space available)
- create_element_size('element3.bst', element_path, [], int(5e6))
+ create_element_size('element3.bst', project, element_path, [], int(5e6))
result = cli.run(project=project, args=['build', 'element3.bst'])
result.assert_success()
@@ -250,7 +250,7 @@ def test_artifact_expires(cli, datafiles, tmpdir):
@pytest.mark.datafiles(DATA_DIR)
def test_artifact_too_large(cli, datafiles, tmpdir):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ element_path = 'elements'
# Create an artifact share (remote cache) in tmpdir/artifactshare
# Mock a file system with 5 MB total space
@@ -263,12 +263,12 @@ def test_artifact_too_large(cli, datafiles, tmpdir):
})
# Create and push a 3MB element
- create_element_size('small_element.bst', element_path, [], int(3e6))
+ create_element_size('small_element.bst', project, element_path, [], int(3e6))
result = cli.run(project=project, args=['build', 'small_element.bst'])
result.assert_success()
# Create and try to push a 6MB element.
- create_element_size('large_element.bst', element_path, [], int(6e6))
+ create_element_size('large_element.bst', project, element_path, [], int(6e6))
result = cli.run(project=project, args=['build', 'large_element.bst'])
result.assert_success()
@@ -285,7 +285,7 @@ def test_artifact_too_large(cli, datafiles, tmpdir):
@pytest.mark.datafiles(DATA_DIR)
def test_recently_pulled_artifact_does_not_expire(cli, datafiles, tmpdir):
project = os.path.join(datafiles.dirname, datafiles.basename)
- element_path = os.path.join(project, 'elements')
+ element_path = 'elements'
# Create an artifact share (remote cache) in tmpdir/artifactshare
# Mock a file system with 12 MB free disk space
@@ -298,11 +298,11 @@ def test_recently_pulled_artifact_does_not_expire(cli, datafiles, tmpdir):
})
# Create and build 2 elements, each of 5 MB.
- create_element_size('element1.bst', element_path, [], int(5e6))
+ create_element_size('element1.bst', project, element_path, [], int(5e6))
result = cli.run(project=project, args=['build', 'element1.bst'])
result.assert_success()
- create_element_size('element2.bst', element_path, [], int(5e6))
+ create_element_size('element2.bst', project, element_path, [], int(5e6))
result = cli.run(project=project, args=['build', 'element2.bst'])
result.assert_success()
@@ -327,7 +327,7 @@ def test_recently_pulled_artifact_does_not_expire(cli, datafiles, tmpdir):
assert cli.get_element_state(project, 'element1.bst') == 'cached'
# Create and build the element3 (of 5 MB)
- create_element_size('element3.bst', element_path, [], int(5e6))
+ create_element_size('element3.bst', project, element_path, [], int(5e6))
result = cli.run(project=project, args=['build', 'element3.bst'])
result.assert_success()
diff --git a/tests/sources/local.py b/tests/sources/local.py
index 9dfb5f972..de12473d9 100644
--- a/tests/sources/local.py
+++ b/tests/sources/local.py
@@ -1,8 +1,8 @@
import os
import pytest
-from buildstream._exceptions import ErrorDomain
-from tests.testutils import cli
+from buildstream._exceptions import ErrorDomain, LoadErrorReason
+from tests.testutils import cli, filetypegenerator
DATA_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
@@ -11,17 +11,62 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
-def test_missing_file(cli, tmpdir, datafiles):
+def test_missing_path(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
# Removing the local file causes preflight to fail
- localfile = os.path.join(datafiles.dirname, datafiles.basename, 'file.txt')
+ localfile = os.path.join(project, 'file.txt')
os.remove(localfile)
result = cli.run(project=project, args=[
'show', 'target.bst'
])
- result.assert_main_error(ErrorDomain.SOURCE, None)
+ result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
+def test_non_regular_file_or_directory(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+ localfile = os.path.join(project, 'file.txt')
+
+ for file_type in filetypegenerator.generate_file_types(localfile):
+ result = cli.run(project=project, args=[
+ 'show', 'target.bst'
+ ])
+ if os.path.isdir(localfile) and not os.path.islink(localfile):
+ result.assert_success()
+ elif os.path.isfile(localfile) and not os.path.islink(localfile):
+ result.assert_success()
+ else:
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.PROJ_PATH_INVALID_KIND)
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
+def test_invalid_absolute_path(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+
+ with open(os.path.join(project, "target.bst"), 'r') as f:
+ old_yaml = f.read()
+
+ new_yaml = old_yaml.replace("file.txt", os.path.join(project, "file.txt"))
+ assert old_yaml != new_yaml
+
+ with open(os.path.join(project, "target.bst"), 'w') as f:
+ f.write(new_yaml)
+
+ result = cli.run(project=project, args=['show', 'target.bst'])
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.PROJ_PATH_INVALID)
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'invalid-relative-path'))
+def test_invalid_relative_path(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+
+ result = cli.run(project=project, args=['show', 'target.bst'])
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.PROJ_PATH_INVALID)
@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
diff --git a/tests/sources/local/invalid-relative-path/file.txt b/tests/sources/local/invalid-relative-path/file.txt
new file mode 100644
index 000000000..a496efee8
--- /dev/null
+++ b/tests/sources/local/invalid-relative-path/file.txt
@@ -0,0 +1 @@
+This is a text file
diff --git a/tests/sources/local/invalid-relative-path/project.conf b/tests/sources/local/invalid-relative-path/project.conf
new file mode 100644
index 000000000..afa0f5475
--- /dev/null
+++ b/tests/sources/local/invalid-relative-path/project.conf
@@ -0,0 +1,2 @@
+# Basic project
+name: foo
diff --git a/tests/sources/local/invalid-relative-path/target.bst b/tests/sources/local/invalid-relative-path/target.bst
new file mode 100644
index 000000000..b09f180e8
--- /dev/null
+++ b/tests/sources/local/invalid-relative-path/target.bst
@@ -0,0 +1,5 @@
+kind: import
+description: This is the pony
+sources:
+- kind: local
+ path: ../invalid-relative-path/file.txt
diff --git a/tests/sources/patch.py b/tests/sources/patch.py
index 697a0ccfb..39d43369d 100644
--- a/tests/sources/patch.py
+++ b/tests/sources/patch.py
@@ -1,8 +1,8 @@
import os
import pytest
-from buildstream._exceptions import ErrorDomain
-from tests.testutils import cli
+from buildstream._exceptions import ErrorDomain, LoadErrorReason
+from tests.testutils import cli, filetypegenerator
DATA_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
@@ -15,27 +15,56 @@ def test_missing_patch(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
# Removing the local file causes preflight to fail
- localfile = os.path.join(datafiles.dirname, datafiles.basename, 'file_1.patch')
+ localfile = os.path.join(project, 'file_1.patch')
os.remove(localfile)
result = cli.run(project=project, args=[
'show', 'target.bst'
])
- result.assert_main_error(ErrorDomain.SOURCE, 'patch-no-exist')
+ result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
def test_non_regular_file_patch(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
- # Add a fifo, that's not a regular file, should cause explosions
- patch_path = os.path.join(datafiles.dirname, datafiles.basename, 'irregular_file.patch')
- os.mkfifo(patch_path)
+ patch_path = os.path.join(project, 'irregular_file.patch')
+ for file_type in filetypegenerator.generate_file_types(patch_path):
+ result = cli.run(project=project, args=[
+ 'show', 'irregular.bst'
+ ])
+ if os.path.isfile(patch_path) and not os.path.islink(patch_path):
+ result.assert_success()
+ else:
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.PROJ_PATH_INVALID_KIND)
- result = cli.run(project=project, args=[
- 'show', 'irregular.bst'
- ])
- result.assert_main_error(ErrorDomain.SOURCE, "patch-not-a-file")
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
+def test_invalid_absolute_path(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+
+ with open(os.path.join(project, "target.bst"), 'r') as f:
+ old_yaml = f.read()
+ new_yaml = old_yaml.replace("file_1.patch",
+ os.path.join(project, "file_1.patch"))
+ assert old_yaml != new_yaml
+
+ with open(os.path.join(project, "target.bst"), 'w') as f:
+ f.write(new_yaml)
+
+ result = cli.run(project=project, args=['show', 'target.bst'])
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.PROJ_PATH_INVALID)
+
+
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'invalid-relative-path'))
+def test_invalid_relative_path(cli, tmpdir, datafiles):
+ project = os.path.join(datafiles.dirname, datafiles.basename)
+
+ result = cli.run(project=project, args=['show', 'irregular.bst'])
+ result.assert_main_error(ErrorDomain.LOAD,
+ LoadErrorReason.PROJ_PATH_INVALID)
@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
diff --git a/tests/sources/patch/invalid-relative-path/file_1.patch b/tests/sources/patch/invalid-relative-path/file_1.patch
new file mode 100644
index 000000000..424a486dd
--- /dev/null
+++ b/tests/sources/patch/invalid-relative-path/file_1.patch
@@ -0,0 +1,7 @@
+diff --git a/file.txt b/file.txt
+index a496efe..341ef26 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1 @@
+-This is a text file
++This is text file with superpowers
diff --git a/tests/sources/patch/invalid-relative-path/irregular.bst b/tests/sources/patch/invalid-relative-path/irregular.bst
new file mode 100644
index 000000000..6b63a4edb
--- /dev/null
+++ b/tests/sources/patch/invalid-relative-path/irregular.bst
@@ -0,0 +1,5 @@
+kind: import
+description: This is the pony
+sources:
+- kind: patch
+ path: ../invalid-relative-path/irregular_file.patch
diff --git a/tests/sources/patch/invalid-relative-path/project.conf b/tests/sources/patch/invalid-relative-path/project.conf
new file mode 100644
index 000000000..afa0f5475
--- /dev/null
+++ b/tests/sources/patch/invalid-relative-path/project.conf
@@ -0,0 +1,2 @@
+# Basic project
+name: foo
diff --git a/tests/testutils/element_generators.py b/tests/testutils/element_generators.py
index 3f6090da8..49f235c61 100644
--- a/tests/testutils/element_generators.py
+++ b/tests/testutils/element_generators.py
@@ -18,11 +18,12 @@ from buildstream import _yaml
# Returns:
# Nothing (creates a .bst file of specified size)
#
-def create_element_size(name, path, dependencies, size):
- os.makedirs(path, exist_ok=True)
+def create_element_size(name, project_dir, elements_path, dependencies, size):
+ full_elements_path = os.path.join(project_dir, elements_path)
+ os.makedirs(full_elements_path, exist_ok=True)
# Create a file to be included in this element's artifact
- with open(os.path.join(path, name + '_data'), 'wb+') as f:
+ with open(os.path.join(project_dir, name + '_data'), 'wb+') as f:
f.write(os.urandom(size))
# Simplest case: We want this file (of specified size) to just
@@ -32,9 +33,9 @@ def create_element_size(name, path, dependencies, size):
'sources': [
{
'kind': 'local',
- 'path': os.path.join(path, name + '_data')
+ 'path': name + '_data'
}
],
'depends': dependencies
}
- _yaml.dump(element, os.path.join(path, name))
+ _yaml.dump(element, os.path.join(project_dir, elements_path, name))
diff --git a/tests/testutils/filetypegenerator.py b/tests/testutils/filetypegenerator.py
new file mode 100644
index 000000000..2dadb7e80
--- /dev/null
+++ b/tests/testutils/filetypegenerator.py
@@ -0,0 +1,62 @@
+#
+# Copyright (C) 2018 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+# Tiago Gomes <tiago.gomes@codethink.co.uk>
+
+import os
+import socket
+
+
+# generate_file_types()
+#
+# Generator that creates a regular file directory, symbolic link, fifo
+# and socket at the specified path.
+#
+# Args:
+# path: (str) path where to create each different type of file
+#
+def generate_file_types(path):
+ def clean():
+ if os.path.exists(path):
+ if os.path.isdir(path):
+ os.rmdir(path)
+ else:
+ os.remove(path)
+
+ clean()
+
+ with open(path, 'w') as f:
+ pass
+ yield
+ clean()
+
+ os.makedirs(path)
+ yield
+ clean()
+
+ os.symlink("project.conf", path)
+ yield
+ clean()
+
+ os.mkfifo(path)
+ yield
+ clean()
+
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.bind(path)
+ yield
+ clean()