summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbst-marge-bot <marge-bot@buildstream.build>2019-07-25 14:22:25 +0000
committerbst-marge-bot <marge-bot@buildstream.build>2019-07-25 14:22:25 +0000
commit41b4eb57df05f52140c68c27b5e6dd3d1b461d32 (patch)
tree7622421b4722fe37ba01f102409d7a69bb7a6be7
parent64c1ceed084b71304180738f9104d637410433ed (diff)
parentb2328f913cb571692e342a9cd7f98b57fa882c5f (diff)
downloadbuildstream-41b4eb57df05f52140c68c27b5e6dd3d1b461d32.tar.gz
Merge branch 'buildbox' into 'master'
Add BuildBox backend for sandboxing See merge request BuildStream/buildstream!951
-rw-r--r--.gitlab-ci.yml32
-rw-r--r--NEWS4
-rw-r--r--src/buildstream/_platform/linux.py25
-rw-r--r--src/buildstream/sandbox/_sandboxbuildbox.py247
-rw-r--r--src/buildstream/sandbox/_sandboxbwrap.py2
-rw-r--r--src/buildstream/sandbox/sandbox.py28
-rw-r--r--src/buildstream/storage/_casbaseddirectory.py91
-rw-r--r--src/buildstream/storage/_filebaseddirectory.py15
-rw-r--r--src/buildstream/storage/directory.py2
-rw-r--r--src/buildstream/testing/_sourcetests/source_determinism.py1
-rw-r--r--tests/elements/filter.py2
-rw-r--r--tests/examples/autotools.py11
-rw-r--r--tests/examples/developing.py16
-rw-r--r--tests/examples/integration-commands.py12
-rw-r--r--tests/examples/junctions.py12
-rw-r--r--tests/examples/running-commands.py12
-rw-r--r--tests/frontend/buildcheckout.py43
-rw-r--r--tests/frontend/configurable_warnings.py2
-rw-r--r--tests/frontend/project/elements/import-links.bst4
-rw-r--r--tests/frontend/project/files/files-and-links/basicfile1
l---------tests/frontend/project/files/files-and-links/basicfolder/basicsymlink1
-rw-r--r--tests/frontend/project/files/files-and-links/basicfolder/subdir-file0
-rw-r--r--tests/integration/artifact.py4
-rw-r--r--tests/integration/autotools.py5
-rw-r--r--tests/integration/cachedfail.py3
-rw-r--r--tests/integration/cmake.py5
-rw-r--r--tests/integration/compose.py2
-rw-r--r--tests/integration/make.py2
-rw-r--r--tests/integration/manual.py8
-rw-r--r--tests/integration/messages.py2
-rw-r--r--tests/integration/pip_element.py6
-rw-r--r--tests/integration/pip_source.py2
-rw-r--r--tests/integration/project/elements/symlinks/link-on-path-use.bst10
-rw-r--r--tests/integration/project/elements/symlinks/link-on-path.bst13
-rw-r--r--tests/integration/pullbuildtrees.py2
-rw-r--r--tests/integration/script.py14
-rw-r--r--tests/integration/shell.py24
-rw-r--r--tests/integration/shellbuildtrees.py24
-rw-r--r--tests/integration/sockets.py4
-rw-r--r--tests/integration/source-determinism.py2
-rw-r--r--tests/integration/stack.py2
-rw-r--r--tests/integration/symlinks.py27
-rw-r--r--tests/integration/workspace.py7
-rw-r--r--tests/internals/storage_vdir_import.py142
-rw-r--r--tests/sourcecache/push.py3
45 files changed, 842 insertions, 34 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index bcf7f37ce..009999065 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -100,6 +100,38 @@ tests-unix:
# Since the unix platform is required to run as root, no user change required
- ${TEST_COMMAND}
+tests-buildbox:
+ image: registry.gitlab.com/buildstream/buildstream-docker-images/testsuite-fedora:29-master-47052095
+ <<: *tests
+ variables:
+ BST_FORCE_SANDBOX: "buildbox"
+
+ script:
+
+ # We remove the Bubblewrap package here so that we catch any
+ # codepaths that try to use them.
+ - dnf install -y fuse3
+ - dnf erase -y bubblewrap
+
+ # Before buildbox is a first class citizen we need a good install story for users and this test
+ # should mirror that story, for now we build in the test as it is quick and easy.
+
+ # Build and install buildbox
+ - dnf install -y fuse3-devel glibc-static grpc-plugins grpc-devel protobuf-devel cmake gcc gcc-c++ libuuid-devel
+ - git clone https://gitlab.com/BuildGrid/buildbox/buildbox-fuse.git
+ - cd buildbox-fuse
+ # Pin a specific commit so that any changes to buildbox do not result in unexpected/unannounced buildstream failures
+ - git checkout cdd2b00842c39a8f7162c2ae55bf2cefb925e339
+ - cmake -B build
+ - cmake --build build
+ - cmake --build build --target install
+ - cd ..
+
+ - useradd -Um buildstream
+ - chown -R buildstream:buildstream .
+
+ - su buildstream -c "${TEST_COMMAND}"
+
tests-fedora-missing-deps:
# Ensure that tests behave nicely while missing bwrap and ostree
image: registry.gitlab.com/buildstream/buildstream-docker-images/testsuite-fedora:29-master-47052095
diff --git a/NEWS b/NEWS
index 2fabe0c65..5d5906500 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@
buildstream 1.3.1
=================
+ o Added Basic support for the BuildBox sandbox. The sand box will only be used if the
+ environment variable BST_FORCE_SANDBOX is set to `buildbox`. This is the first step in
+ transitioning to only using BuildBox for local sandboxing.
+
o BREAKING CHANGE: The yaml API has been rewritten entirely. When accessing
configuration from YAML, please use the new `Node` classes exposed in the
`buildstream` package. See the documentation for how to use it.
diff --git a/src/buildstream/_platform/linux.py b/src/buildstream/_platform/linux.py
index b69dd456e..b400bfaac 100644
--- a/src/buildstream/_platform/linux.py
+++ b/src/buildstream/_platform/linux.py
@@ -24,6 +24,7 @@ from .. import utils
from ..sandbox import SandboxDummy
from .platform import Platform
+from .._exceptions import PlatformError
class Linux(Platform):
@@ -31,6 +32,7 @@ class Linux(Platform):
def _setup_sandbox(self, force_sandbox):
sandbox_setups = {
'bwrap': self._setup_bwrap_sandbox,
+ 'buildbox': self._setup_buildbox_sandbox,
'chroot': self._setup_chroot_sandbox,
'dummy': self._setup_dummy_sandbox,
}
@@ -67,6 +69,7 @@ class Linux(Platform):
# Private Methods #
################################################
+ # Dummy sandbox methods
@staticmethod
def _check_dummy_sandbox_config(config):
return True
@@ -81,6 +84,7 @@ class Linux(Platform):
self.create_sandbox = self._create_dummy_sandbox
return True
+ # Bubble-wrap sandbox methods
def _check_sandbox_config_bwrap(self, config):
from ..sandbox._sandboxbwrap import SandboxBwrap
return SandboxBwrap.check_sandbox_config(self, config)
@@ -103,6 +107,7 @@ class Linux(Platform):
self.create_sandbox = self._create_bwrap_sandbox
return True
+ # Chroot sandbox methods
def _check_sandbox_config_chroot(self, config):
from ..sandbox._sandboxchroot import SandboxChroot
return SandboxChroot.check_sandbox_config(self, config)
@@ -118,3 +123,23 @@ class Linux(Platform):
self.check_sandbox_config = self._check_sandbox_config_chroot
self.create_sandbox = Linux._create_chroot_sandbox
return True
+
+ # Buildbox sandbox methods
+ def _check_sandbox_config_buildbox(self, config):
+ from ..sandbox._sandboxbuildbox import SandboxBuildBox
+ return SandboxBuildBox.check_sandbox_config(self, config)
+
+ @staticmethod
+ def _create_buildbox_sandbox(*args, **kwargs):
+ from ..sandbox._sandboxbuildbox import SandboxBuildBox
+ if kwargs.get('allow_real_directory'):
+ raise PlatformError("The BuildBox Sandbox does not support real directories.",
+ reason="You are using BuildBox sandbox because BST_FORCE_SANBOX=buildbox")
+ return SandboxBuildBox(*args, **kwargs)
+
+ def _setup_buildbox_sandbox(self):
+ from ..sandbox._sandboxbuildbox import SandboxBuildBox
+ self._check_sandbox(SandboxBuildBox)
+ self.check_sandbox_config = self._check_sandbox_config_buildbox
+ self.create_sandbox = self._create_buildbox_sandbox
+ return True
diff --git a/src/buildstream/sandbox/_sandboxbuildbox.py b/src/buildstream/sandbox/_sandboxbuildbox.py
new file mode 100644
index 000000000..417d2224d
--- /dev/null
+++ b/src/buildstream/sandbox/_sandboxbuildbox.py
@@ -0,0 +1,247 @@
+#
+# Copyright (C) 2018 Bloomberg LP
+#
+# 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/>.
+
+import os
+import sys
+import signal
+import subprocess
+from contextlib import ExitStack
+
+import psutil
+
+from .. import utils, _signals, ProgramNotFoundError
+from . import Sandbox, SandboxFlags
+from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
+from ..storage._casbaseddirectory import CasBasedDirectory
+from .._exceptions import SandboxError
+
+
+# SandboxBuidBox()
+#
+# BuildBox-based sandbox implementation.
+#
+class SandboxBuildBox(Sandbox):
+
+ def __init__(self, context, project, directory, **kwargs):
+ if kwargs.get('allow_real_directory'):
+ raise SandboxError("BuildBox does not support real directories")
+ else:
+ kwargs['allow_real_directory'] = False
+ super().__init__(context, project, directory, **kwargs)
+
+ @classmethod
+ def check_available(cls):
+ try:
+ utils.get_host_tool('buildbox')
+ except utils.ProgramNotFoundError as Error:
+ cls._dummy_reasons += ["buildbox not found"]
+ raise SandboxError(" and ".join(cls._dummy_reasons),
+ reason="unavailable-local-sandbox") from Error
+
+ @classmethod
+ def check_sandbox_config(cls, platform, config):
+ # Report error for elements requiring non-0 UID/GID
+ # TODO
+ if config.build_uid != 0 or config.build_gid != 0:
+ return False
+
+ # Check host os and architecture match
+ if config.build_os != platform.get_host_os():
+ raise SandboxError("Configured and host OS don't match.")
+ elif config.build_arch != platform.get_host_arch():
+ raise SandboxError("Configured and host architecture don't match.")
+
+ return True
+
+ def _run(self, command, flags, *, cwd, env):
+ stdout, stderr = self._get_output()
+
+ root_directory = self.get_virtual_directory()
+ scratch_directory = self._get_scratch_directory()
+
+ if not self._has_command(command[0], env):
+ raise SandboxError("Staged artifacts do not provide command "
+ "'{}'".format(command[0]),
+ reason='missing-command')
+
+ # Grab the full path of the buildbox binary
+ try:
+ buildbox_command = [utils.get_host_tool('buildbox')]
+ except ProgramNotFoundError as Err:
+ raise SandboxError(("BuildBox not on path, you are using the BuildBox sandbox because "
+ "BST_FORCE_SANDBOX=buildbox")) from Err
+
+ for mark in self._get_marked_directories():
+ path = mark['directory']
+ assert path.startswith('/') and len(path) > 1
+ root_directory.descend(*path[1:].split(os.path.sep), create=True)
+
+ digest = root_directory._get_digest()
+ with open(os.path.join(scratch_directory, 'in'), 'wb') as input_digest_file:
+ input_digest_file.write(digest.SerializeToString())
+
+ buildbox_command += ["--local=" + root_directory.cas_cache.casdir]
+ buildbox_command += ["--input-digest=in"]
+ buildbox_command += ["--output-digest=out"]
+
+ common_details = ("BuildBox is a experimental sandbox and does not support the requested feature.\n"
+ "You are using this feature because BST_FORCE_SANDBOX=buildbox.")
+
+ if not flags & SandboxFlags.NETWORK_ENABLED:
+ # TODO
+ self._issue_warning(
+ "BuildBox sandbox does not have Networking yet",
+ detail=common_details
+ )
+
+ if cwd is not None:
+ buildbox_command += ['--chdir=' + cwd]
+
+ # In interactive mode, we want a complete devpts inside
+ # the container, so there is a /dev/console and such. In
+ # the regular non-interactive sandbox, we want to hand pick
+ # a minimal set of devices to expose to the sandbox.
+ #
+ if flags & SandboxFlags.INTERACTIVE:
+ # TODO
+ self._issue_warning(
+ "BuildBox sandbox does not fully support BuildStream shells yet",
+ detail=common_details
+ )
+
+ if flags & SandboxFlags.ROOT_READ_ONLY:
+ # TODO
+ self._issue_warning(
+ "BuildBox sandbox does not fully support BuildStream `Read only Root`",
+ detail=common_details
+ )
+
+ # Set UID and GID
+ if not flags & SandboxFlags.INHERIT_UID:
+ # TODO
+ self._issue_warning(
+ "BuildBox sandbox does not fully support BuildStream Inherit UID",
+ detail=common_details
+ )
+
+ os.makedirs(os.path.join(scratch_directory, 'mnt'), exist_ok=True)
+ buildbox_command += ['mnt']
+
+ # Add the command
+ buildbox_command += command
+
+ # Use the MountMap context manager to ensure that any redirected
+ # mounts through fuse layers are in context and ready for buildbox
+ # to mount them from.
+ #
+ with ExitStack() as stack:
+ # Ensure the cwd exists
+ if cwd is not None and len(cwd) > 1:
+ assert cwd.startswith('/')
+ root_directory.descend(*cwd[1:].split(os.path.sep), create=True)
+
+ # If we're interactive, we want to inherit our stdin,
+ # otherwise redirect to /dev/null, ensuring process
+ # disconnected from terminal.
+ if flags & SandboxFlags.INTERACTIVE:
+ stdin = sys.stdin
+ else:
+ stdin = stack.enter_context(open(os.devnull, "r"))
+
+ # Run buildbox !
+ exit_code = self.run_buildbox(buildbox_command, stdin, stdout, stderr, env,
+ interactive=(flags & SandboxFlags.INTERACTIVE),
+ cwd=scratch_directory)
+
+ if exit_code == 0:
+ with open(os.path.join(scratch_directory, 'out'), 'rb') as output_digest_file:
+ output_digest = remote_execution_pb2.Digest()
+ output_digest.ParseFromString(output_digest_file.read())
+ self._vdir = CasBasedDirectory(root_directory.cas_cache, digest=output_digest)
+
+ return exit_code
+
+ def run_buildbox(self, argv, stdin, stdout, stderr, env, *, interactive, cwd):
+ def kill_proc():
+ if process:
+ # First attempt to gracefully terminate
+ proc = psutil.Process(process.pid)
+ proc.terminate()
+
+ try:
+ proc.wait(20)
+ except psutil.TimeoutExpired:
+ utils._kill_process_tree(process.pid)
+
+ def suspend_proc():
+ group_id = os.getpgid(process.pid)
+ os.killpg(group_id, signal.SIGSTOP)
+
+ def resume_proc():
+ group_id = os.getpgid(process.pid)
+ os.killpg(group_id, signal.SIGCONT)
+
+ with _signals.suspendable(suspend_proc, resume_proc), _signals.terminator(kill_proc):
+ process = subprocess.Popen(
+ argv,
+ close_fds=True,
+ env=env,
+ stdin=stdin,
+ stdout=stdout,
+ stderr=stderr,
+ cwd=cwd,
+ start_new_session=interactive
+ )
+
+ # Wait for the child process to finish, ensuring that
+ # a SIGINT has exactly the effect the user probably
+ # expects (i.e. let the child process handle it).
+ try:
+ while True:
+ try:
+ _, status = os.waitpid(process.pid, 0)
+ # If the process exits due to a signal, we
+ # brutally murder it to avoid zombies
+ if not os.WIFEXITED(status):
+ utils._kill_process_tree(process.pid)
+
+ # Unlike in the bwrap case, here only the main
+ # process seems to receive the SIGINT. We pass
+ # on the signal to the child and then continue
+ # to wait.
+ except KeyboardInterrupt:
+ process.send_signal(signal.SIGINT)
+ continue
+
+ break
+ # If we can't find the process, it has already died of
+ # its own accord, and therefore we don't need to check
+ # or kill anything.
+ except psutil.NoSuchProcess:
+ pass
+
+ # Return the exit code - see the documentation for
+ # os.WEXITSTATUS to see why this is required.
+ if os.WIFEXITED(status):
+ exit_code = os.WEXITSTATUS(status)
+ else:
+ exit_code = -1
+
+ return exit_code
+
+ def _use_cas_based_directory(self):
+ # Always use CasBasedDirectory for BuildBox
+ return True
diff --git a/src/buildstream/sandbox/_sandboxbwrap.py b/src/buildstream/sandbox/_sandboxbwrap.py
index 1155793c6..81e9f34de 100644
--- a/src/buildstream/sandbox/_sandboxbwrap.py
+++ b/src/buildstream/sandbox/_sandboxbwrap.py
@@ -336,7 +336,7 @@ class SandboxBwrap(Sandbox):
# The only message relevant to us now is the exit-code of the subprocess.
for line in json_status_file:
with suppress(json.decoder.JSONDecodeError):
- o = json.loads(line)
+ o = json.loads(line.decode())
if isinstance(o, collections.abc.Mapping) and 'exit-code' in o:
child_exit_code = o['exit-code']
break
diff --git a/src/buildstream/sandbox/sandbox.py b/src/buildstream/sandbox/sandbox.py
index 4cab7d6b8..ece15c949 100644
--- a/src/buildstream/sandbox/sandbox.py
+++ b/src/buildstream/sandbox/sandbox.py
@@ -548,13 +548,17 @@ class Sandbox():
# Returns:
# (bool): Whether a command exists inside the sandbox.
def _has_command(self, command, env=None):
+ vroot = self.get_virtual_directory()
+ command_as_parts = command.lstrip(os.sep).split(os.sep)
if os.path.isabs(command):
- return os.path.lexists(os.path.join(
- self._root, command.lstrip(os.sep)))
+ return vroot._exists(*command_as_parts, follow_symlinks=True)
+
+ if len(command_as_parts) > 1:
+ return False
for path in env.get('PATH').split(':'):
- if os.path.lexists(os.path.join(
- self._root, path.lstrip(os.sep), command)):
+ path_as_parts = path.lstrip(os.sep).split(os.sep)
+ if vroot._exists(*path_as_parts, command, follow_symlinks=True):
return True
return False
@@ -609,6 +613,22 @@ class Sandbox():
self._build_directory = directory
self._build_directory_always = always
+ # _issue_warning()
+ #
+ # Issue warning with __context that is not available with subclasses
+ #
+ # Args:
+ # message (str): A message to issue
+ # details (str): optional, more detatils
+ def _issue_warning(self, message, detail=None):
+ self.__context.messenger.message(
+ Message(None,
+ MessageType.WARN,
+ message,
+ detail=detail
+ )
+ )
+
# _SandboxBatch()
#
diff --git a/src/buildstream/storage/_casbaseddirectory.py b/src/buildstream/storage/_casbaseddirectory.py
index 2c5d751e4..424b7ef63 100644
--- a/src/buildstream/storage/_casbaseddirectory.py
+++ b/src/buildstream/storage/_casbaseddirectory.py
@@ -28,6 +28,9 @@ See also: :ref:`sandboxing`.
"""
import os
+import stat
+import tarfile as tarfilelib
+from io import StringIO
from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
from .directory import Directory, VirtualDirectoryError, _FileType
@@ -156,7 +159,15 @@ class CasBasedDirectory(Directory):
self.__invalidate_digest()
- def descend(self, *paths, create=False):
+ def find_root(self):
+ """ Finds the root of this directory tree by following 'parent' until there is
+ no parent. """
+ if self.parent:
+ return self.parent.find_root()
+ else:
+ return self
+
+ def descend(self, *paths, create=False, follow_symlinks=False):
"""Descend one or more levels of directory hierarchy and return a new
Directory object for that directory.
@@ -174,6 +185,7 @@ class CasBasedDirectory(Directory):
"""
current_dir = self
+ paths = list(paths)
for path in paths:
# Skip empty path segments
@@ -181,20 +193,37 @@ class CasBasedDirectory(Directory):
continue
entry = current_dir.index.get(path)
+
if entry:
if entry.type == _FileType.DIRECTORY:
current_dir = entry.get_directory(current_dir)
+ elif follow_symlinks and entry.type == _FileType.SYMLINK:
+ linklocation = entry.target
+ newpaths = linklocation.split(os.path.sep)
+ if os.path.isabs(linklocation):
+ current_dir = current_dir.find_root().descend(*newpaths, follow_symlinks=True)
+ else:
+ current_dir = current_dir.descend(*newpaths, follow_symlinks=True)
else:
error = "Cannot descend into {}, which is a '{}' in the directory {}"
raise VirtualDirectoryError(error.format(path,
current_dir.index[path].type,
- current_dir))
+ current_dir),
+ reason="not-a-directory")
else:
- if create:
+ if path == '.':
+ continue
+ elif path == '..':
+ if current_dir.parent is not None:
+ current_dir = current_dir.parent
+ # In POSIX /.. == / so just stay at the root dir
+ continue
+ elif create:
current_dir = current_dir._add_directory(path)
else:
error = "'{}' not found in {}"
- raise VirtualDirectoryError(error.format(path, str(current_dir)))
+ raise VirtualDirectoryError(error.format(path, str(current_dir)),
+ reason="directory-not-found")
return current_dir
@@ -270,8 +299,10 @@ class CasBasedDirectory(Directory):
self._copy_link_from_filesystem(source_directory, direntry.name)
result.files_written.append(relative_pathname)
- def _partial_import_cas_into_cas(self, source_directory, filter_callback, *, path_prefix="", result):
+ def _partial_import_cas_into_cas(self, source_directory, filter_callback, *, path_prefix="", origin=None, result):
""" Import files from a CAS-based directory. """
+ if origin is None:
+ origin = self
for name, entry in source_directory.index.items():
# The destination filename, relative to the root where the import started
@@ -307,6 +338,8 @@ class CasBasedDirectory(Directory):
subdir.__add_files_to_result(path_prefix=relative_pathname, result=result)
else:
src_subdir = source_directory.descend(name)
+ if src_subdir == origin:
+ continue
try:
dest_subdir = self.descend(name, create=create_subdir)
@@ -316,7 +349,8 @@ class CasBasedDirectory(Directory):
.format(filetype, relative_pathname))
dest_subdir._partial_import_cas_into_cas(src_subdir, filter_callback,
- path_prefix=relative_pathname, result=result)
+ path_prefix=relative_pathname,
+ origin=origin, result=result)
if filter_callback and not filter_callback(relative_pathname):
if is_dir and create_subdir and dest_subdir.is_empty():
@@ -408,7 +442,33 @@ class CasBasedDirectory(Directory):
self.cas_cache.checkout(to_directory, self._get_digest(), can_link=can_link)
def export_to_tar(self, tarfile, destination_dir, mtime=BST_ARBITRARY_TIMESTAMP):
- raise NotImplementedError()
+ for filename, entry in self.index.items():
+ arcname = os.path.join(destination_dir, filename)
+ if entry.type == _FileType.DIRECTORY:
+ tarinfo = tarfilelib.TarInfo(arcname)
+ tarinfo.mtime = mtime
+ tarinfo.type = tarfilelib.DIRTYPE
+ tarinfo.mode = 0o755
+ tarfile.addfile(tarinfo)
+ self.descend(filename).export_to_tar(tarfile, arcname, mtime)
+ elif entry.type == _FileType.REGULAR_FILE:
+ source_name = self.cas_cache.objpath(entry.digest)
+ tarinfo = tarfilelib.TarInfo(arcname)
+ tarinfo.mtime = mtime
+ tarinfo.mode |= entry.is_executable & stat.S_IXUSR
+ tarinfo.size = os.path.getsize(source_name)
+ with open(source_name, "rb") as f:
+ tarfile.addfile(tarinfo, f)
+ elif entry.type == _FileType.SYMLINK:
+ tarinfo = tarfilelib.TarInfo(arcname)
+ tarinfo.mtime = mtime
+ tarinfo.mode |= entry.is_executable & stat.S_IXUSR
+ tarinfo.linkname = entry.target
+ tarinfo.type = tarfilelib.SYMTYPE
+ f = StringIO(entry.target)
+ tarfile.addfile(tarinfo, f)
+ else:
+ raise VirtualDirectoryError("can not export file type {} to tar".format(entry.type))
def _mark_changed(self):
""" It should not be possible to externally modify a CAS-based
@@ -584,6 +644,23 @@ class CasBasedDirectory(Directory):
return self.__digest
+ def _exists(self, *path, follow_symlinks=False):
+ try:
+ subdir = self.descend(*path[:-1], follow_symlinks=follow_symlinks)
+ target = subdir.index.get(path[-1])
+ if target is not None:
+ if target.type == _FileType.REGULAR_FILE:
+ return True
+ elif follow_symlinks and target.type == _FileType.SYMLINK:
+ linklocation = target.target
+ newpath = linklocation.split(os.path.sep)
+ if os.path.isabs(linklocation):
+ return subdir.find_root()._exists(*newpath, follow_symlinks=True)
+ return subdir._exists(*newpath, follow_symlinks=True)
+ return False
+ except VirtualDirectoryError:
+ return False
+
def __invalidate_digest(self):
if self.__digest:
self.__digest = None
diff --git a/src/buildstream/storage/_filebaseddirectory.py b/src/buildstream/storage/_filebaseddirectory.py
index 8c55819c9..07c23c192 100644
--- a/src/buildstream/storage/_filebaseddirectory.py
+++ b/src/buildstream/storage/_filebaseddirectory.py
@@ -37,6 +37,7 @@ from .. import utils
from ..utils import link_files, copy_files, list_relative_paths, _get_link_mtime, BST_ARBITRARY_TIMESTAMP
from ..utils import _set_deterministic_user, _set_deterministic_mtime
from ..utils import FileListResult
+from .._exceptions import ImplError
# FileBasedDirectory intentionally doesn't call its superclass constuctor,
# which is meant to be unimplemented.
@@ -47,9 +48,12 @@ class FileBasedDirectory(Directory):
def __init__(self, external_directory=None):
self.external_directory = external_directory
- def descend(self, *paths, create=False):
+ def descend(self, *paths, create=False, follow_symlinks=False):
""" See superclass Directory for arguments """
+ if follow_symlinks:
+ ImplError("FileBasedDirectory.Decend dose not implement follow_symlinks=True")
+
current_dir = self
for path in paths:
@@ -281,3 +285,12 @@ class FileBasedDirectory(Directory):
assert entry.type == _FileType.SYMLINK
os.symlink(entry.target, dest_path)
result.files_written.append(relative_pathname)
+
+ def _exists(self, *path, follow_symlinks=False):
+ """This is very simple but mirrors the cas based storage were it is less trivial"""
+ if follow_symlinks:
+ # The lexists is not ideal as it cant spot broken symlinks but this is a long
+ # standing bug in buildstream as exists follow absolute syslinks to real root
+ # and incorrectly thinks they are broken the new casbaseddirectory dose not have this bug.
+ return os.path.lexists(os.path.join(self.external_directory, *path))
+ raise ImplError("_exists can only follow symlinks in filebaseddirectory")
diff --git a/src/buildstream/storage/directory.py b/src/buildstream/storage/directory.py
index c9906b058..d32ac0063 100644
--- a/src/buildstream/storage/directory.py
+++ b/src/buildstream/storage/directory.py
@@ -52,7 +52,7 @@ class Directory():
def __init__(self, external_directory=None):
raise NotImplementedError()
- def descend(self, *paths, create=False):
+ def descend(self, *paths, create=False, follow_symlinks=False):
"""Descend one or more levels of directory hierarchy and return a new
Directory object for that directory.
diff --git a/src/buildstream/testing/_sourcetests/source_determinism.py b/src/buildstream/testing/_sourcetests/source_determinism.py
index fc8ad9893..d46d38b33 100644
--- a/src/buildstream/testing/_sourcetests/source_determinism.py
+++ b/src/buildstream/testing/_sourcetests/source_determinism.py
@@ -50,6 +50,7 @@ def create_test_directory(*path, mode=0o644):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("kind", [*ALL_REPO_KINDS])
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.skipif(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox, Must Fix')
def test_deterministic_source_umask(cli, tmpdir, datafiles, kind):
project = str(datafiles)
element_name = 'list.bst'
diff --git a/tests/elements/filter.py b/tests/elements/filter.py
index db20529bc..d8370c6bb 100644
--- a/tests/elements/filter.py
+++ b/tests/elements/filter.py
@@ -8,6 +8,7 @@ import pytest
from buildstream.testing import create_repo
from buildstream.testing import cli # pylint: disable=unused-import
+from buildstream.testing._utils.site import HAVE_SANDBOX
from buildstream._exceptions import ErrorDomain
from buildstream import _yaml
@@ -30,6 +31,7 @@ def test_filter_include(datafiles, cli, tmpdir):
assert not os.path.exists(os.path.join(checkout, "bar"))
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
def test_filter_include_dynamic(datafiles, cli, tmpdir):
project = str(datafiles)
diff --git a/tests/examples/autotools.py b/tests/examples/autotools.py
index ca311c4bb..e55f33493 100644
--- a/tests/examples/autotools.py
+++ b/tests/examples/autotools.py
@@ -6,7 +6,7 @@ import pytest
from buildstream.testing import cli_integration as cli # pylint: disable=unused-import
from buildstream.testing.integration import assert_contains
-from buildstream.testing._utils.site import HAVE_BWRAP, IS_LINUX, MACHINE_ARCH
+from buildstream.testing._utils.site import IS_LINUX, MACHINE_ARCH, HAVE_SANDBOX
pytestmark = pytest.mark.integration
@@ -18,7 +18,10 @@ DATA_DIR = os.path.join(
# Tests a build of the autotools amhello project on a alpine-linux base runtime
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with sandbox')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This test is not meant to work with chroot sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
@pytest.mark.datafiles(DATA_DIR)
def test_autotools_build(cli, datafiles):
project = str(datafiles)
@@ -41,7 +44,9 @@ def test_autotools_build(cli, datafiles):
# Test running an executable built with autotools.
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This test is not meant to work with chroot sandbox')
@pytest.mark.datafiles(DATA_DIR)
def test_autotools_run(cli, datafiles):
project = str(datafiles)
diff --git a/tests/examples/developing.py b/tests/examples/developing.py
index c2b950c01..58416414e 100644
--- a/tests/examples/developing.py
+++ b/tests/examples/developing.py
@@ -6,7 +6,7 @@ import pytest
from buildstream.testing import cli_integration as cli # pylint: disable=unused-import
from buildstream.testing.integration import assert_contains
-from buildstream.testing._utils.site import HAVE_BWRAP, IS_LINUX, MACHINE_ARCH
+from buildstream.testing._utils.site import IS_LINUX, MACHINE_ARCH, HAVE_SANDBOX
import tests.testutils.patch as patch
pytestmark = pytest.mark.integration
@@ -19,7 +19,10 @@ DATA_DIR = os.path.join(
# Test that the project builds successfully
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with SANDBOX')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This is not meant to work with chroot')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
@pytest.mark.datafiles(DATA_DIR)
def test_autotools_build(cli, datafiles):
project = str(datafiles)
@@ -40,7 +43,10 @@ def test_autotools_build(cli, datafiles):
# Test the unmodified hello command works as expected.
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with SANDBOX')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This is not meant to work with chroot')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
@pytest.mark.datafiles(DATA_DIR)
def test_run_unmodified_hello(cli, datafiles):
project = str(datafiles)
@@ -73,7 +79,9 @@ def test_open_workspace(cli, tmpdir, datafiles):
# Test making a change using the workspace
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with SANDBOX')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This is not meant to work with chroot')
@pytest.mark.datafiles(DATA_DIR)
def test_make_change_in_workspace(cli, tmpdir, datafiles):
project = str(datafiles)
diff --git a/tests/examples/integration-commands.py b/tests/examples/integration-commands.py
index ad794351f..146ccdb80 100644
--- a/tests/examples/integration-commands.py
+++ b/tests/examples/integration-commands.py
@@ -5,7 +5,7 @@ import os
import pytest
from buildstream.testing import cli_integration as cli # pylint: disable=unused-import
-from buildstream.testing._utils.site import HAVE_BWRAP, IS_LINUX, MACHINE_ARCH
+from buildstream.testing._utils.site import IS_LINUX, MACHINE_ARCH, HAVE_SANDBOX
pytestmark = pytest.mark.integration
@@ -16,7 +16,10 @@ DATA_DIR = os.path.join(
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with sandbox')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This test is not meant to work with chroot sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
@pytest.mark.datafiles(DATA_DIR)
def test_integration_commands_build(cli, datafiles):
project = str(datafiles)
@@ -28,7 +31,10 @@ def test_integration_commands_build(cli, datafiles):
# Test running the executable
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with sandbox')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This test is not meant to work with chroot sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
@pytest.mark.datafiles(DATA_DIR)
def test_integration_commands_run(cli, datafiles):
project = str(datafiles)
diff --git a/tests/examples/junctions.py b/tests/examples/junctions.py
index bb88e5068..b77fcba8a 100644
--- a/tests/examples/junctions.py
+++ b/tests/examples/junctions.py
@@ -5,7 +5,7 @@ import os
import pytest
from buildstream.testing import cli_integration as cli # pylint: disable=unused-import
-from buildstream.testing._utils.site import HAVE_BWRAP, IS_LINUX, MACHINE_ARCH
+from buildstream.testing._utils.site import IS_LINUX, MACHINE_ARCH, HAVE_SANDBOX
pytestmark = pytest.mark.integration
@@ -17,7 +17,10 @@ DATA_DIR = os.path.join(
# Test that the project builds successfully
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This test is not meant to work with chroot sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
@pytest.mark.datafiles(DATA_DIR)
def test_build(cli, datafiles):
project = str(datafiles)
@@ -29,7 +32,10 @@ def test_build(cli, datafiles):
# Test the callHello script works as expected.
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This test is not meant to work with chroot sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
@pytest.mark.datafiles(DATA_DIR)
def test_shell_call_hello(cli, datafiles):
project = str(datafiles)
diff --git a/tests/examples/running-commands.py b/tests/examples/running-commands.py
index 23b3e6467..ce81da0c8 100644
--- a/tests/examples/running-commands.py
+++ b/tests/examples/running-commands.py
@@ -5,7 +5,7 @@ import os
import pytest
from buildstream.testing import cli_integration as cli # pylint: disable=unused-import
-from buildstream.testing._utils.site import HAVE_BWRAP, IS_LINUX, MACHINE_ARCH
+from buildstream.testing._utils.site import IS_LINUX, MACHINE_ARCH, HAVE_SANDBOX
pytestmark = pytest.mark.integration
@@ -16,8 +16,11 @@ DATA_DIR = os.path.join(
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
@pytest.mark.datafiles(DATA_DIR)
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with sandbox')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This test is not meant to work with chroot sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_running_commands_build(cli, datafiles):
project = str(datafiles)
@@ -28,7 +31,10 @@ def test_running_commands_build(cli, datafiles):
# Test running the executable
@pytest.mark.skipif(MACHINE_ARCH != 'x86-64',
reason='Examples are written for x86-64')
-@pytest.mark.skipif(not IS_LINUX or not HAVE_BWRAP, reason='Only available on linux with bubblewrap')
+@pytest.mark.skipif(not IS_LINUX or not HAVE_SANDBOX, reason='Only available on linux with sandbox')
+@pytest.mark.skipif(HAVE_SANDBOX == 'chroot', reason='This test is not meant to work with chroot sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
@pytest.mark.datafiles(DATA_DIR)
def test_running_commands_run(cli, datafiles):
project = str(datafiles)
diff --git a/tests/frontend/buildcheckout.py b/tests/frontend/buildcheckout.py
index a3f68c031..dd4a461ea 100644
--- a/tests/frontend/buildcheckout.py
+++ b/tests/frontend/buildcheckout.py
@@ -425,6 +425,49 @@ def test_build_checkout_tarball_is_deterministic(datafiles, cli):
@pytest.mark.datafiles(DATA_DIR)
+def test_build_checkout_tarball_links(datafiles, cli):
+ project = str(datafiles)
+ checkout = os.path.join(cli.directory, 'checkout.tar')
+ extract = os.path.join(cli.directory, 'extract')
+
+ result = cli.run(project=project, args=['build', 'import-links.bst'])
+ result.assert_success()
+
+ builddir = os.path.join(cli.directory, 'build')
+ assert os.path.isdir(builddir)
+ assert not os.listdir(builddir)
+
+ checkout_args = ['artifact', 'checkout', '--tar', checkout, 'import-links.bst']
+
+ result = cli.run(project=project, args=checkout_args)
+ result.assert_success()
+
+ tar = tarfile.open(name=checkout, mode="r:")
+ tar.extractall(extract)
+ assert open(os.path.join(extract, 'basicfolder', 'basicsymlink')).read() == "file contents\n"
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_build_checkout_links(datafiles, cli):
+ project = str(datafiles)
+ checkout = os.path.join(cli.directory, 'checkout')
+
+ result = cli.run(project=project, args=['build', 'import-links.bst'])
+ result.assert_success()
+
+ builddir = os.path.join(cli.directory, 'build')
+ assert os.path.isdir(builddir)
+ assert not os.listdir(builddir)
+
+ checkout_args = ['artifact', 'checkout', '--directory', checkout, 'import-links.bst']
+
+ result = cli.run(project=project, args=checkout_args)
+ result.assert_success()
+
+ assert open(os.path.join(checkout, 'basicfolder', 'basicsymlink')).read() == "file contents\n"
+
+
+@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("hardlinks", [("copies"), ("hardlinks")])
def test_build_checkout_nonempty(datafiles, cli, hardlinks):
project = str(datafiles)
diff --git a/tests/frontend/configurable_warnings.py b/tests/frontend/configurable_warnings.py
index 7936b2f89..38b7f2912 100644
--- a/tests/frontend/configurable_warnings.py
+++ b/tests/frontend/configurable_warnings.py
@@ -9,6 +9,7 @@ from buildstream.plugin import CoreWarnings
from buildstream._exceptions import ErrorDomain
from buildstream import _yaml
from buildstream.testing.runcli import cli # pylint: disable=unused-import
+from buildstream.testing._utils.site import HAVE_SANDBOX
TOP_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
@@ -45,6 +46,7 @@ def build_project(datafiles, fatal_warnings):
return project_path
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
@pytest.mark.datafiles(TOP_DIR)
@pytest.mark.parametrize("element_name, fatal_warnings, expect_fatal, error_domain", [
("corewarn.bst", [CoreWarnings.OVERLAPS], True, ErrorDomain.STREAM),
diff --git a/tests/frontend/project/elements/import-links.bst b/tests/frontend/project/elements/import-links.bst
new file mode 100644
index 000000000..42b279ee2
--- /dev/null
+++ b/tests/frontend/project/elements/import-links.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+ path: files/files-and-links
diff --git a/tests/frontend/project/files/files-and-links/basicfile b/tests/frontend/project/files/files-and-links/basicfile
new file mode 100644
index 000000000..d03e2425c
--- /dev/null
+++ b/tests/frontend/project/files/files-and-links/basicfile
@@ -0,0 +1 @@
+file contents
diff --git a/tests/frontend/project/files/files-and-links/basicfolder/basicsymlink b/tests/frontend/project/files/files-and-links/basicfolder/basicsymlink
new file mode 120000
index 000000000..e2b4f7423
--- /dev/null
+++ b/tests/frontend/project/files/files-and-links/basicfolder/basicsymlink
@@ -0,0 +1 @@
+../basicfile \ No newline at end of file
diff --git a/tests/frontend/project/files/files-and-links/basicfolder/subdir-file b/tests/frontend/project/files/files-and-links/basicfolder/subdir-file
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/frontend/project/files/files-and-links/basicfolder/subdir-file
diff --git a/tests/integration/artifact.py b/tests/integration/artifact.py
index 56c516e67..6ad0f8477 100644
--- a/tests/integration/artifact.py
+++ b/tests/integration/artifact.py
@@ -45,8 +45,12 @@ DATA_DIR = os.path.join(
# A test to capture the integration of the cachebuildtrees
# behaviour, which by default is to include the buildtree
# content of an element on caching.
+
+# Dse this really need a sandbox?
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_cache_buildtrees(cli, tmpdir, datafiles):
project = str(datafiles)
element_name = 'autotools/amhello.bst'
diff --git a/tests/integration/autotools.py b/tests/integration/autotools.py
index c4bf429f5..250ab90d1 100644
--- a/tests/integration/autotools.py
+++ b/tests/integration/autotools.py
@@ -22,6 +22,8 @@ DATA_DIR = os.path.join(
# amhello project for this.
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_autotools_build(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -44,6 +46,7 @@ def test_autotools_build(cli, datafiles):
# amhello project for this.
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_autotools_confroot_build(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -65,6 +68,8 @@ def test_autotools_confroot_build(cli, datafiles):
# Test running an executable built with autotools
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_autotools_run(cli, datafiles):
project = str(datafiles)
element_name = 'autotools/amhello.bst'
diff --git a/tests/integration/cachedfail.py b/tests/integration/cachedfail.py
index a7509ab3b..4a469e21a 100644
--- a/tests/integration/cachedfail.py
+++ b/tests/integration/cachedfail.py
@@ -39,6 +39,8 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_build_checkout_cached_fail(cli, datafiles):
project = str(datafiles)
element_path = os.path.join(project, 'elements', 'element.bst')
@@ -139,6 +141,7 @@ def test_build_depend_on_cached_fail(cli, datafiles):
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("on_error", ("continue", "quit"))
def test_push_cached_fail(cli, tmpdir, datafiles, on_error):
diff --git a/tests/integration/cmake.py b/tests/integration/cmake.py
index 84ea96af2..3cb56ab5c 100644
--- a/tests/integration/cmake.py
+++ b/tests/integration/cmake.py
@@ -20,6 +20,8 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_cmake_build(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -36,6 +38,7 @@ def test_cmake_build(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_cmake_confroot_build(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -52,6 +55,8 @@ def test_cmake_confroot_build(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_cmake_run(cli, datafiles):
project = str(datafiles)
element_name = 'cmake/cmakehello.bst'
diff --git a/tests/integration/compose.py b/tests/integration/compose.py
index 3562ed94b..dc8f4f858 100644
--- a/tests/integration/compose.py
+++ b/tests/integration/compose.py
@@ -80,6 +80,8 @@ def create_compose_element(name, path, config=None):
'/usr/share/doc/amhello/README'])
])
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_compose_include(cli, datafiles, include_domains,
exclude_domains, expected):
project = str(datafiles)
diff --git a/tests/integration/make.py b/tests/integration/make.py
index 664e7ca7a..4678c5319 100644
--- a/tests/integration/make.py
+++ b/tests/integration/make.py
@@ -22,6 +22,7 @@ DATA_DIR = os.path.join(
# makehello project for this.
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
def test_make_build(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -40,6 +41,7 @@ def test_make_build(cli, datafiles):
# Test running an executable built with make
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
def test_make_run(cli, datafiles):
project = str(datafiles)
element_name = 'make/makehello.bst'
diff --git a/tests/integration/manual.py b/tests/integration/manual.py
index 2ac7f74d0..bcfcaaf41 100644
--- a/tests/integration/manual.py
+++ b/tests/integration/manual.py
@@ -36,6 +36,8 @@ def create_manual_element(name, path, config, variables, environment):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_manual_element(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -70,6 +72,8 @@ strip
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_manual_element_environment(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -100,6 +104,8 @@ def test_manual_element_environment(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_manual_element_noparallel(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -135,6 +141,8 @@ def test_manual_element_noparallel(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_manual_element_logging(cli, datafiles):
project = str(datafiles)
element_path = os.path.join(project, 'elements')
diff --git a/tests/integration/messages.py b/tests/integration/messages.py
index 42725fc5b..0313a6347 100644
--- a/tests/integration/messages.py
+++ b/tests/integration/messages.py
@@ -41,6 +41,8 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_disable_message_lines(cli, datafiles):
project = str(datafiles)
element_path = os.path.join(project, 'elements')
diff --git a/tests/integration/pip_element.py b/tests/integration/pip_element.py
index da0badcb3..85a922c00 100644
--- a/tests/integration/pip_element.py
+++ b/tests/integration/pip_element.py
@@ -25,6 +25,8 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_pip_build(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -62,6 +64,8 @@ def test_pip_build(cli, datafiles):
# Test running an executable built with pip
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_pip_run(cli, datafiles):
# Create and build our test element
test_pip_build(cli, datafiles)
@@ -76,6 +80,8 @@ def test_pip_run(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_pip_element_should_install_pip_deps(cli, datafiles, setup_pypi_repo):
project = str(datafiles)
elements_path = os.path.join(project, 'elements')
diff --git a/tests/integration/pip_source.py b/tests/integration/pip_source.py
index c221910a6..c8f997800 100644
--- a/tests/integration/pip_source.py
+++ b/tests/integration/pip_source.py
@@ -140,6 +140,8 @@ def test_pip_source_import_requirements_files(cli, datafiles, setup_pypi_repo):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_pip_source_build(cli, datafiles, setup_pypi_repo):
project = str(datafiles)
element_path = os.path.join(project, 'elements')
diff --git a/tests/integration/project/elements/symlinks/link-on-path-use.bst b/tests/integration/project/elements/symlinks/link-on-path-use.bst
new file mode 100644
index 000000000..ce57872a6
--- /dev/null
+++ b/tests/integration/project/elements/symlinks/link-on-path-use.bst
@@ -0,0 +1,10 @@
+kind: manual
+
+depends:
+ - filename: symlinks/link-on-path.bst
+
+config:
+ build-commands:
+ - touch %{install-root}/foo
+
+
diff --git a/tests/integration/project/elements/symlinks/link-on-path.bst b/tests/integration/project/elements/symlinks/link-on-path.bst
new file mode 100644
index 000000000..d74cbb81c
--- /dev/null
+++ b/tests/integration/project/elements/symlinks/link-on-path.bst
@@ -0,0 +1,13 @@
+kind: manual
+
+depends:
+- filename: base.bst
+ type: build
+
+config:
+ install-commands:
+ - |
+ cd "%{install-root}"
+ cp -r /bin /lib .
+ mv bin altbin
+ ln -s altbin bin
diff --git a/tests/integration/pullbuildtrees.py b/tests/integration/pullbuildtrees.py
index af9186b1b..437b3e0a5 100644
--- a/tests/integration/pullbuildtrees.py
+++ b/tests/integration/pullbuildtrees.py
@@ -37,6 +37,8 @@ def default_state(cli, tmpdir, share):
@pytest.mark.integration
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_pullbuildtrees(cli2, tmpdir, datafiles):
project = str(datafiles)
element_name = 'autotools/amhello.bst'
diff --git a/tests/integration/script.py b/tests/integration/script.py
index fc57e8744..04e70af32 100644
--- a/tests/integration/script.py
+++ b/tests/integration/script.py
@@ -40,6 +40,8 @@ def create_script_element(name, path, config=None, variables=None):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_script(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -68,6 +70,8 @@ def test_script(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_script_root(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -100,6 +104,7 @@ def test_script_root(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_script_no_root(cli, datafiles):
project = str(datafiles)
element_path = os.path.join(project, 'elements')
@@ -123,6 +128,7 @@ def test_script_no_root(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_script_cwd(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -154,6 +160,8 @@ def test_script_cwd(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_script_layout(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -173,6 +181,8 @@ def test_script_layout(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_regression_cache_corruption(cli, datafiles):
project = str(datafiles)
checkout_original = os.path.join(cli.directory, 'checkout-original')
@@ -203,6 +213,8 @@ def test_regression_cache_corruption(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_regression_tmpdir(cli, datafiles):
project = str(datafiles)
element_name = 'script/tmpdir.bst'
@@ -213,6 +225,8 @@ def test_regression_tmpdir(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_regression_cache_corruption_2(cli, datafiles):
project = str(datafiles)
checkout_original = os.path.join(cli.directory, 'checkout-original')
diff --git a/tests/integration/shell.py b/tests/integration/shell.py
index a1f38d879..8ea5d5e69 100644
--- a/tests/integration/shell.py
+++ b/tests/integration/shell.py
@@ -57,6 +57,8 @@ def execute_shell(cli, project, command, *, config=None, mount=None, element='ba
# executable
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_shell(cli, datafiles):
project = str(datafiles)
@@ -68,6 +70,8 @@ def test_shell(cli, datafiles):
# Test running an executable directly
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_executable(cli, datafiles):
project = str(datafiles)
@@ -80,6 +84,8 @@ def test_executable(cli, datafiles):
@pytest.mark.parametrize("animal", [("Horse"), ("Pony")])
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_env_assign(cli, datafiles, animal):
project = str(datafiles)
expected = animal + '\n'
@@ -100,6 +106,8 @@ def test_env_assign(cli, datafiles, animal):
@pytest.mark.parametrize("animal", [("Horse"), ("Pony")])
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_env_assign_expand_host_environ(cli, datafiles, animal):
project = str(datafiles)
expected = 'The animal is: {}\n'.format(animal)
@@ -123,6 +131,8 @@ def test_env_assign_expand_host_environ(cli, datafiles, animal):
@pytest.mark.parametrize("animal", [("Horse"), ("Pony")])
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_env_assign_isolated(cli, datafiles, animal):
project = str(datafiles)
result = execute_shell(cli, project, ['/bin/sh', '-c', 'echo ${ANIMAL}'], isolate=True, config={
@@ -141,6 +151,8 @@ def test_env_assign_isolated(cli, datafiles, animal):
# /bin/sh)
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_no_shell(cli, datafiles):
project = str(datafiles)
element_path = os.path.join(project, 'elements')
@@ -174,6 +186,7 @@ def test_no_shell(cli, datafiles):
@pytest.mark.parametrize("path", [("/etc/pony.conf"), ("/usr/share/pony/pony.txt")])
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_host_files(cli, datafiles, path):
project = str(datafiles)
ponyfile = os.path.join(project, 'files', 'shell-mount', 'pony.txt')
@@ -195,6 +208,7 @@ def test_host_files(cli, datafiles, path):
@pytest.mark.parametrize("path", [("/etc"), ("/usr/share/pony")])
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_host_files_expand_environ(cli, datafiles, path):
project = str(datafiles)
hostpath = os.path.join(project, 'files', 'shell-mount')
@@ -221,6 +235,7 @@ def test_host_files_expand_environ(cli, datafiles, path):
@pytest.mark.parametrize("path", [("/etc/pony.conf"), ("/usr/share/pony/pony.txt")])
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
def test_isolated_no_mount(cli, datafiles, path):
project = str(datafiles)
ponyfile = os.path.join(project, 'files', 'shell-mount', 'pony.txt')
@@ -244,6 +259,7 @@ def test_isolated_no_mount(cli, datafiles, path):
@pytest.mark.parametrize("optional", [("mandatory"), ("optional")])
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
def test_host_files_missing(cli, datafiles, optional):
project = str(datafiles)
ponyfile = os.path.join(project, 'files', 'shell-mount', 'horsy.txt')
@@ -277,6 +293,7 @@ def test_host_files_missing(cli, datafiles, optional):
@pytest.mark.parametrize("path", [("/etc/pony.conf"), ("/usr/share/pony/pony.txt")])
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_cli_mount(cli, datafiles, path):
project = str(datafiles)
ponyfile = os.path.join(project, 'files', 'shell-mount', 'pony.txt')
@@ -289,6 +306,7 @@ def test_cli_mount(cli, datafiles, path):
# Test that we can see the workspace files in a shell
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_workspace_visible(cli, datafiles):
project = str(datafiles)
workspace = os.path.join(cli.directory, 'workspace')
@@ -323,6 +341,7 @@ def test_workspace_visible(cli, datafiles):
# Test that '--sysroot' works
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_sysroot(cli, tmpdir, datafiles):
project = str(datafiles)
base_element = "base/base-alpine.bst"
@@ -353,6 +372,8 @@ def test_sysroot(cli, tmpdir, datafiles):
# Test system integration commands can access devices in /dev
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_integration_devices(cli, datafiles):
project = str(datafiles)
element_name = 'integration.bst'
@@ -366,6 +387,7 @@ def test_integration_devices(cli, datafiles):
@pytest.mark.parametrize("build_shell", [("build"), ("nobuild")])
@pytest.mark.parametrize("guess_element", [True, False], ids=["guess", "no-guess"])
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_integration_external_workspace(cli, tmpdir_factory, datafiles, build_shell, guess_element):
tmpdir = tmpdir_factory.mktemp("")
project = str(datafiles)
@@ -399,6 +421,8 @@ def test_integration_external_workspace(cli, tmpdir_factory, datafiles, build_sh
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_integration_partial_artifact(cli, datafiles, tmpdir, integration_cache):
project = str(datafiles)
diff --git a/tests/integration/shellbuildtrees.py b/tests/integration/shellbuildtrees.py
index a1eecb1eb..4f4d8b0b5 100644
--- a/tests/integration/shellbuildtrees.py
+++ b/tests/integration/shellbuildtrees.py
@@ -24,6 +24,8 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_buildtree_staged(cli_integration, datafiles):
# We can only test the non interacitve case
# The non interactive case defaults to not using buildtrees
@@ -42,6 +44,8 @@ def test_buildtree_staged(cli_integration, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_buildtree_staged_forced_true(cli_integration, datafiles):
# Test that if we ask for a build tree it is there.
project = str(datafiles)
@@ -59,8 +63,10 @@ def test_buildtree_staged_forced_true(cli_integration, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
-def test_buildtree_staged_warn_non_cached(cli_integration, tmpdir, datafiles):
- # Test that if we attempt to stage a buildtree that was never cached, we warn the user.
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
+def test_buildtree_staged_warn_empty_cached(cli_integration, tmpdir, datafiles):
+ # Test that if we stage a cached and empty buildtree, we warn the user.
project = str(datafiles)
element_name = 'build-shell/buildtree.bst'
@@ -90,6 +96,8 @@ def test_buildtree_staged_warn_non_cached(cli_integration, tmpdir, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_buildtree_staged_if_available(cli_integration, datafiles):
# Test that a build tree can be correctly detected.
project = str(datafiles)
@@ -107,6 +115,8 @@ def test_buildtree_staged_if_available(cli_integration, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_buildtree_staged_forced_false(cli_integration, datafiles):
# Test that if we ask not to have a build tree it is not there
project = str(datafiles)
@@ -125,6 +135,8 @@ def test_buildtree_staged_forced_false(cli_integration, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_buildtree_from_failure(cli_integration, datafiles):
# Test that we can use a build tree after a failure
project = str(datafiles)
@@ -167,6 +179,8 @@ def test_buildtree_from_failure_option_never(cli_integration, tmpdir, datafiles)
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_buildtree_from_failure_option_always(cli_integration, tmpdir, datafiles):
project = str(datafiles)
@@ -194,6 +208,8 @@ def test_buildtree_from_failure_option_always(cli_integration, tmpdir, datafiles
# This is to roughly simulate remote execution
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_buildtree_pulled(cli, tmpdir, datafiles):
project = str(datafiles)
element_name = 'build-shell/buildtree.bst'
@@ -227,6 +243,8 @@ def test_buildtree_pulled(cli, tmpdir, datafiles):
# This test checks for correct behaviour if a buildtree is not present in the local cache.
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_buildtree_options(cli, tmpdir, datafiles):
project = str(datafiles)
element_name = 'build-shell/buildtree.bst'
@@ -310,6 +328,8 @@ def test_buildtree_options(cli, tmpdir, datafiles):
# Tests running pull and pull-buildtree options at the same time.
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_pull_buildtree_pulled(cli, tmpdir, datafiles):
project = str(datafiles)
element_name = 'build-shell/buildtree.bst'
diff --git a/tests/integration/sockets.py b/tests/integration/sockets.py
index 763238baf..246e48595 100644
--- a/tests/integration/sockets.py
+++ b/tests/integration/sockets.py
@@ -18,6 +18,8 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_builddir_socket_ignored(cli, datafiles):
project = str(datafiles)
element_name = 'sockets/make-builddir-socket.bst'
@@ -28,6 +30,8 @@ def test_builddir_socket_ignored(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_install_root_socket_ignored(cli, datafiles):
project = str(datafiles)
element_name = 'sockets/make-install-root-socket.bst'
diff --git a/tests/integration/source-determinism.py b/tests/integration/source-determinism.py
index 70c4b79de..0ac954f4f 100644
--- a/tests/integration/source-determinism.py
+++ b/tests/integration/source-determinism.py
@@ -32,6 +32,8 @@ def create_test_directory(*path, mode=0o644):
@pytest.mark.integration
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_deterministic_source_local(cli, tmpdir, datafiles):
"""Only user rights should be considered for local source.
"""
diff --git a/tests/integration/stack.py b/tests/integration/stack.py
index 9d6b38345..c6eaba3c4 100644
--- a/tests/integration/stack.py
+++ b/tests/integration/stack.py
@@ -19,6 +19,8 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_stack(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
diff --git a/tests/integration/symlinks.py b/tests/integration/symlinks.py
index ed6ee109c..c62bad586 100644
--- a/tests/integration/symlinks.py
+++ b/tests/integration/symlinks.py
@@ -19,6 +19,8 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_absolute_symlinks(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -40,6 +42,8 @@ def test_absolute_symlinks(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_disallow_overlaps_inside_symlink_with_dangling_target(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -55,6 +59,8 @@ def test_disallow_overlaps_inside_symlink_with_dangling_target(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
def test_detect_symlink_overlaps_pointing_outside_sandbox(cli, datafiles):
project = str(datafiles)
checkout = os.path.join(cli.directory, 'checkout')
@@ -70,3 +76,24 @@ def test_detect_symlink_overlaps_pointing_outside_sandbox(cli, datafiles):
result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkout])
assert result.exit_code == -1
assert 'Destination is a symlink, not a directory: /opt/escape-hatch' in result.stderr
+
+
+@pytest.mark.datafiles(DATA_DIR)
+@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox')
+# Not stricked xfail as only fails in CI
+def test_symlink_in_sandbox_path(cli, datafiles):
+ project = str(datafiles)
+ element_name = 'symlinks/link-on-path-use.bst'
+ base_element_name = 'symlinks/link-on-path.bst'
+ # This test is inspired by how freedesktop-SDK has /bin -> /usr/bin
+
+ # Create a element that has sh in altbin and a link from bin to altbin
+ result1 = cli.run(project=project, args=['build', base_element_name])
+ result1.assert_success()
+ # Build a element that uses the element that has sh in altbin.
+ result2 = cli.run(project=project, args=['build', element_name])
+ result2.assert_success()
+ # When this element is built it demonstrates that the virtual sandbox
+ # can detect sh across links and that the sandbox can find sh accross
+ # the link from its PATH.
diff --git a/tests/integration/workspace.py b/tests/integration/workspace.py
index 78379912c..045f8c490 100644
--- a/tests/integration/workspace.py
+++ b/tests/integration/workspace.py
@@ -20,6 +20,7 @@ DATA_DIR = os.path.join(
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_workspace_mount(cli, datafiles):
project = str(datafiles)
workspace = os.path.join(cli.directory, 'workspace')
@@ -36,6 +37,7 @@ def test_workspace_mount(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_workspace_commanddir(cli, datafiles):
project = str(datafiles)
workspace = os.path.join(cli.directory, 'workspace')
@@ -53,6 +55,7 @@ def test_workspace_commanddir(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_workspace_updated_dependency(cli, datafiles):
project = str(datafiles)
workspace = os.path.join(cli.directory, 'workspace')
@@ -107,6 +110,7 @@ def test_workspace_updated_dependency(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_workspace_update_dependency_failed(cli, datafiles):
project = str(datafiles)
workspace = os.path.join(cli.directory, 'workspace')
@@ -182,6 +186,7 @@ def test_workspace_update_dependency_failed(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_updated_dependency_nested(cli, datafiles):
project = str(datafiles)
workspace = os.path.join(cli.directory, 'workspace')
@@ -235,6 +240,7 @@ def test_updated_dependency_nested(cli, datafiles):
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_incremental_configure_commands_run_only_once(cli, datafiles):
project = str(datafiles)
workspace = os.path.join(cli.directory, 'workspace')
@@ -287,6 +293,7 @@ def test_incremental_configure_commands_run_only_once(cli, datafiles):
#
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
def test_workspace_missing_last_successful(cli, datafiles):
project = str(datafiles)
workspace = os.path.join(cli.directory, 'workspace')
diff --git a/tests/internals/storage_vdir_import.py b/tests/internals/storage_vdir_import.py
index 9d42c6e8d..7c6cbe4fb 100644
--- a/tests/internals/storage_vdir_import.py
+++ b/tests/internals/storage_vdir_import.py
@@ -1,3 +1,18 @@
+#
+# Copyright (C) 2019 Bloomberg LP
+#
+# 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/>.
from hashlib import sha256
import os
import random
@@ -7,6 +22,7 @@ import pytest
from buildstream.storage._casbaseddirectory import CasBasedDirectory
from buildstream.storage._filebaseddirectory import FileBasedDirectory
from buildstream._cas import CASCache
+from buildstream.storage.directory import VirtualDirectoryError
# These are comparitive tests that check that FileBasedDirectory and
@@ -42,9 +58,13 @@ NUM_RANDOM_TESTS = 10
def generate_import_roots(rootno, directory):
rootname = "root{}".format(rootno)
rootdir = os.path.join(directory, "content", rootname)
+ generate_import_root(rootdir, root_filesets[rootno - 1])
+
+
+def generate_import_root(rootdir, filelist):
if os.path.exists(rootdir):
return
- for (path, typesymbol, content) in root_filesets[rootno - 1]:
+ for (path, typesymbol, content) in filelist:
if typesymbol == 'F':
(dirnames, filename) = os.path.split(path)
os.makedirs(os.path.join(rootdir, dirnames), exist_ok=True)
@@ -251,3 +271,123 @@ def test_random_directory_listing(tmpdir, root):
@pytest.mark.parametrize("root", [1, 2, 3, 4, 5])
def test_fixed_directory_listing(tmpdir, root):
_listing_test(str(tmpdir), root, generate_import_roots)
+
+
+# Check that the vdir is decending and readable
+def test_descend(tmpdir):
+ cas_dir = os.path.join(str(tmpdir), 'cas')
+ cas_cache = CASCache(cas_dir)
+ d = CasBasedDirectory(cas_cache)
+
+ Content_to_check = 'You got me'
+ test_dir = os.path.join(str(tmpdir), 'importfrom')
+ filesys_discription = [
+ ('a', 'D', ''),
+ ('a/l', 'D', ''),
+ ('a/l/g', 'F', Content_to_check)
+ ]
+ generate_import_root(test_dir, filesys_discription)
+
+ d.import_files(test_dir)
+ digest = d.descend('a', 'l').index['g'].get_digest()
+
+ assert Content_to_check == open(cas_cache.objpath(digest)).read()
+
+
+# Check symlink logic for edgecases
+# Make sure the correct erros are raised when trying
+# to decend in to files or links to files
+def test_bad_symlinks(tmpdir):
+ cas_dir = os.path.join(str(tmpdir), 'cas')
+ cas_cache = CASCache(cas_dir)
+ d = CasBasedDirectory(cas_cache)
+
+ test_dir = os.path.join(str(tmpdir), 'importfrom')
+ filesys_discription = [
+ ('a', 'D', ''),
+ ('a/l', 'S', '../target'),
+ ('target', 'F', 'You got me')
+ ]
+ generate_import_root(test_dir, filesys_discription)
+ d.import_files(test_dir)
+ exp_reason = "not-a-directory"
+
+ with pytest.raises(VirtualDirectoryError) as error:
+ d.descend('a', 'l', follow_symlinks=True)
+ assert error.reason == exp_reason
+
+ with pytest.raises(VirtualDirectoryError) as error:
+ d.descend('a', 'l')
+ assert error.reason == exp_reason
+
+ with pytest.raises(VirtualDirectoryError) as error:
+ d.descend('a', 'f')
+ assert error.reason == exp_reason
+
+
+# Check symlink logic for edgecases
+# Check decend accross relitive link
+def test_relitive_symlink(tmpdir):
+ cas_dir = os.path.join(str(tmpdir), 'cas')
+ cas_cache = CASCache(cas_dir)
+ d = CasBasedDirectory(cas_cache)
+
+ Content_to_check = 'You got me'
+ test_dir = os.path.join(str(tmpdir), 'importfrom')
+ filesys_discription = [
+ ('a', 'D', ''),
+ ('a/l', 'S', '../target'),
+ ('target', 'D', ''),
+ ('target/file', 'F', Content_to_check)
+ ]
+ generate_import_root(test_dir, filesys_discription)
+ d.import_files(test_dir)
+
+ digest = d.descend('a', 'l', follow_symlinks=True).index['file'].get_digest()
+ assert Content_to_check == open(cas_cache.objpath(digest)).read()
+
+
+# Check symlink logic for edgecases
+# Check deccend accross abs link
+def test_abs_symlink(tmpdir):
+ cas_dir = os.path.join(str(tmpdir), 'cas')
+ cas_cache = CASCache(cas_dir)
+ d = CasBasedDirectory(cas_cache)
+
+ Content_to_check = 'two step file'
+ test_dir = os.path.join(str(tmpdir), 'importfrom')
+ filesys_discription = [
+ ('a', 'D', ''),
+ ('a/l', 'S', '/target'),
+ ('target', 'D', ''),
+ ('target/file', 'F', Content_to_check)
+ ]
+ generate_import_root(test_dir, filesys_discription)
+ d.import_files(test_dir)
+
+ digest = d.descend('a', 'l', follow_symlinks=True).index['file'].get_digest()
+
+ assert Content_to_check == open(cas_cache.objpath(digest)).read()
+
+
+# Check symlink logic for edgecases
+# Check symlink can not escape root
+def test_bad_sym_escape(tmpdir):
+ cas_dir = os.path.join(str(tmpdir), 'cas')
+ cas_cache = CASCache(cas_dir)
+ d = CasBasedDirectory(cas_cache)
+
+ test_dir = os.path.join(str(tmpdir), 'importfrom')
+ filesys_discription = [
+ ('jail', 'D', ''),
+ ('jail/a', 'D', ''),
+ ('jail/a/l', 'S', '../../target'),
+ ('target', 'D', ''),
+ ('target/file', 'F', 'two step file')
+ ]
+ generate_import_root(test_dir, filesys_discription)
+ d.import_files(os.path.join(test_dir, 'jail'))
+
+ with pytest.raises(VirtualDirectoryError) as error:
+ d.descend('a', 'l', follow_symlinks=True)
+ assert error.reason == "directory-not-found"
diff --git a/tests/sourcecache/push.py b/tests/sourcecache/push.py
index b0fae616e..ad9653f9d 100644
--- a/tests/sourcecache/push.py
+++ b/tests/sourcecache/push.py
@@ -28,7 +28,7 @@ from buildstream._project import Project
from buildstream import _yaml
from buildstream.testing import cli # pylint: disable=unused-import
from buildstream.testing import create_repo
-
+from buildstream.testing._utils.site import HAVE_SANDBOX
from tests.testutils import create_artifact_share, dummy_context
DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "project")
@@ -182,6 +182,7 @@ def test_push_fail(cli, tmpdir, datafiles):
assert "Pushed" not in res.stderr
+@pytest.mark.xfail(HAVE_SANDBOX == 'buildbox', reason='Not working with BuildBox', strict=True)
@pytest.mark.datafiles(DATA_DIR)
def test_source_push_build_fail(cli, tmpdir, datafiles):
project_dir = str(datafiles)