diff options
author | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2018-08-12 06:59:46 +0000 |
---|---|---|
committer | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2018-08-12 06:59:46 +0000 |
commit | c1fdebb3137891305bea7db8b4f1ad9f0b1e2043 (patch) | |
tree | fa56fac70ad5685eb764f21934a5a0fb56bb9465 | |
parent | 48c715e3f5eb06372f75dcf778f0278273569812 (diff) | |
parent | 67df390473cfa17cc3a8bfff7d451b3b48327d6b (diff) | |
download | buildstream-c1fdebb3137891305bea7db8b4f1ad9f0b1e2043.tar.gz |
Merge branch 'valentindavid/deterministic-source' into 'master'
Deterministic staging
Closes #543, #544, #555, and #527
See merge request BuildStream/buildstream!616
30 files changed, 246 insertions, 30 deletions
diff --git a/buildstream/_versions.py b/buildstream/_versions.py index d774e5786..6d5077a2a 100644 --- a/buildstream/_versions.py +++ b/buildstream/_versions.py @@ -33,4 +33,4 @@ BST_FORMAT_VERSION = 13 # or if buildstream was changed in a way which can cause # the same cache key to produce something that is no longer # the same. -BST_CORE_ARTIFACT_VERSION = 3 +BST_CORE_ARTIFACT_VERSION = 4 diff --git a/buildstream/plugins/sources/local.py b/buildstream/plugins/sources/local.py index 058553424..7c19e1f90 100644 --- a/buildstream/plugins/sources/local.py +++ b/buildstream/plugins/sources/local.py @@ -37,6 +37,7 @@ local - stage local files and directories """ import os +import stat from buildstream import Source, Consistency from buildstream import utils @@ -94,12 +95,35 @@ class LocalSource(Source): # Dont use hardlinks to stage sources, they are not write protected # in the sandbox. with self.timed_activity("Staging local files at {}".format(self.path)): + if os.path.isdir(self.fullpath): - utils.copy_files(self.fullpath, directory) + files = list(utils.list_relative_paths(self.fullpath, list_dirs=True)) + utils.copy_files(self.fullpath, directory, files=files) else: destfile = os.path.join(directory, os.path.basename(self.path)) + files = [os.path.basename(self.path)] utils.safe_copy(self.fullpath, destfile) + for f in files: + # Non empty directories are not listed by list_relative_paths + dirs = f.split(os.sep) + for i in range(1, len(dirs)): + d = os.path.join(directory, *(dirs[:i])) + assert os.path.isdir(d) and not os.path.islink(d) + os.chmod(d, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) + + path = os.path.join(directory, f) + if os.path.islink(path): + pass + elif os.path.isdir(path): + os.chmod(path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) + else: + st = os.stat(path) + if st.st_mode & stat.S_IXUSR: + os.chmod(path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) + else: + os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) + # Create a unique key for a file def unique_key(filename): diff --git a/buildstream/plugins/sources/zip.py b/buildstream/plugins/sources/zip.py index 9b47d7f78..d3ce0f16d 100644 --- a/buildstream/plugins/sources/zip.py +++ b/buildstream/plugins/sources/zip.py @@ -49,10 +49,17 @@ zip - stage files from zip archives # To extract the root of the archive directly, this can be set # to an empty string. base-dir: '*' + +.. attention:: + + File permissions are not preserved. All extracted directories have + permissions 0755 and all extracted files have permissions 0644. + """ import os import zipfile +import stat from buildstream import SourceError from buildstream import utils @@ -74,6 +81,9 @@ class ZipSource(DownloadableFileSource): return super().get_unique_key() + [self.base_dir] def stage(self, directory): + exec_rights = (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) & ~(stat.S_IWGRP | stat.S_IWOTH) + noexec_rights = exec_rights & ~(stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + try: with zipfile.ZipFile(self._get_mirror_file()) as archive: base_dir = None @@ -81,9 +91,27 @@ class ZipSource(DownloadableFileSource): base_dir = self._find_base_dir(archive, self.base_dir) if base_dir: - archive.extractall(path=directory, members=self._extract_members(archive, base_dir)) + members = self._extract_members(archive, base_dir) else: - archive.extractall(path=directory) + members = archive.namelist() + + for member in members: + written = archive.extract(member, path=directory) + + # zipfile.extract might create missing directories + rel = os.path.relpath(written, start=directory) + assert not os.path.isabs(rel) + rel = os.path.dirname(rel) + while rel: + os.chmod(os.path.join(directory, rel), exec_rights) + rel = os.path.dirname(rel) + + if os.path.islink(written): + pass + elif os.path.isdir(written): + os.chmod(written, exec_rights) + else: + os.chmod(written, noexec_rights) except (zipfile.BadZipFile, zipfile.LargeZipFile, OSError) as e: raise SourceError("{}: Error staging source: {}".format(self, e)) from e diff --git a/buildstream/utils.py b/buildstream/utils.py index 93ab6fb0e..149ee7b90 100644 --- a/buildstream/utils.py +++ b/buildstream/utils.py @@ -1010,6 +1010,15 @@ def _call(*popenargs, terminate=False, **kwargs): process = None + old_preexec_fn = kwargs.get('preexec_fn') + if 'preexec_fn' in kwargs: + del kwargs['preexec_fn'] + + def preexec_fn(): + os.umask(stat.S_IWGRP | stat.S_IWOTH) + if old_preexec_fn is not None: + old_preexec_fn() + # Handle termination, suspend and resume def kill_proc(): if process: @@ -1054,7 +1063,7 @@ def _call(*popenargs, terminate=False, **kwargs): os.killpg(group_id, signal.SIGCONT) with _signals.suspendable(suspend_proc, resume_proc), _signals.terminator(kill_proc): - process = subprocess.Popen(*popenargs, **kwargs) + process = subprocess.Popen(*popenargs, preexec_fn=preexec_fn, **kwargs) output, _ = process.communicate() exit_code = process.poll() diff --git a/tests/cachekey/project/elements/build1.expected b/tests/cachekey/project/elements/build1.expected index a1db77232..fe515806d 100644 --- a/tests/cachekey/project/elements/build1.expected +++ b/tests/cachekey/project/elements/build1.expected @@ -1 +1 @@ -ba707203aa7dfcaea014e65482058a538910da9934c9f91e3ff0d807613c3586
\ No newline at end of file +594be3eb2a211f706557b156ec4b0ffb3ca256af35bcd0116b97fdb8c942d1c5
\ No newline at end of file diff --git a/tests/cachekey/project/elements/build2.expected b/tests/cachekey/project/elements/build2.expected index a61d5e7bb..f28fe802c 100644 --- a/tests/cachekey/project/elements/build2.expected +++ b/tests/cachekey/project/elements/build2.expected @@ -1 +1 @@ -7df53245c07746b2d63be01e631a7fcd5befed165fd50175c40a169f07cc35d4
\ No newline at end of file +5038d37bf1714180d160271e688ec1715d69666ca266ed5b1d880abdee36b310
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose1.expected b/tests/cachekey/project/elements/compose1.expected index 820ee93dc..ec0b94943 100644 --- a/tests/cachekey/project/elements/compose1.expected +++ b/tests/cachekey/project/elements/compose1.expected @@ -1 +1 @@ -ecc49680860cba13bacde567b7cad6c77887518adb7b4cfcce8652baec9a23c9
\ No newline at end of file +e050172c2d445849a2716720814501f0e294b81d28be8cd911ee294291ec60d0
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose2.expected b/tests/cachekey/project/elements/compose2.expected index 20ebc8d4d..fd738fb74 100644 --- a/tests/cachekey/project/elements/compose2.expected +++ b/tests/cachekey/project/elements/compose2.expected @@ -1 +1 @@ -8a845f648d40c69c50528960bcda1ef03477e80f7a5f8cc36853801651de4b27
\ No newline at end of file +ad985f243163aab22576fea63a0b89f7560e361326cb041872c568c5feaabf5c
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose3.expected b/tests/cachekey/project/elements/compose3.expected index 5782e2adb..24899fb04 100644 --- a/tests/cachekey/project/elements/compose3.expected +++ b/tests/cachekey/project/elements/compose3.expected @@ -1 +1 @@ -638f0b81a2062a0e9bd4f4b14fdb20fe62891826debefa80a22866d3dcb92862
\ No newline at end of file +ec0fec2c821eb34286e9799a6e8d1f5587f161d1d653bd1dbe385602340d86ae
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose4.expected b/tests/cachekey/project/elements/compose4.expected index b57d00dc9..8f7730a28 100644 --- a/tests/cachekey/project/elements/compose4.expected +++ b/tests/cachekey/project/elements/compose4.expected @@ -1 +1 @@ -ce19faef9a28bd14739876785c125ed288d74b01e3861ddbea1c7f7a5fb428b2
\ No newline at end of file +7cfe8f6161c00d8cf10114e7458f0b97eb003a41504ae301b24f45d48d42155b
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose5.expected b/tests/cachekey/project/elements/compose5.expected index bc1b0b05e..9653745ae 100644 --- a/tests/cachekey/project/elements/compose5.expected +++ b/tests/cachekey/project/elements/compose5.expected @@ -1 +1 @@ -c29d00bd91a9f1ef61c4b3279759371c9097e1ebfcd9ddf035c8e4997291e35e
\ No newline at end of file +7f016c3165f2de9161312b74f29513dff7dfdcba5ff8c6897beb5b123eaafd3d
\ No newline at end of file diff --git a/tests/cachekey/project/elements/import1.expected b/tests/cachekey/project/elements/import1.expected index 93dcccae0..f28d7c036 100644 --- a/tests/cachekey/project/elements/import1.expected +++ b/tests/cachekey/project/elements/import1.expected @@ -1 +1 @@ -e12330e4a0bd5456f460f47d05d52f434634a14549f01786b9b975ec6bf622cc
\ No newline at end of file +41ce5a640fdfd7b6ce8a2c3fa1dde7983bc4df0e4c3ca926670118bae3c051fe
\ No newline at end of file diff --git a/tests/cachekey/project/elements/import2.expected b/tests/cachekey/project/elements/import2.expected index e9669f003..3ec64e941 100644 --- a/tests/cachekey/project/elements/import2.expected +++ b/tests/cachekey/project/elements/import2.expected @@ -1 +1 @@ -55625e5ee18294703fa7005e7688dac4e28cc485b306d534a92c0cf77e434b12
\ No newline at end of file +d657db503460486a6de80d87f15c6b1fa84b0c4dacabed374acdc70172d4761d
\ No newline at end of file diff --git a/tests/cachekey/project/elements/import3.expected b/tests/cachekey/project/elements/import3.expected index d9a12b818..09c525c75 100644 --- a/tests/cachekey/project/elements/import3.expected +++ b/tests/cachekey/project/elements/import3.expected @@ -1 +1 @@ -714e19b870e61af8f4cba9c5a948f2d0d16d63d796d568b9ed4d6329546cce53
\ No newline at end of file +8de0293a6231dc661cf7229aa5a2f25abdf9a6d38ff70bd6f2562dae51ff05d3
\ No newline at end of file diff --git a/tests/cachekey/project/elements/script1.expected b/tests/cachekey/project/elements/script1.expected index 94a8c92d6..b81856cd6 100644 --- a/tests/cachekey/project/elements/script1.expected +++ b/tests/cachekey/project/elements/script1.expected @@ -1 +1 @@ -958b4d93aed53a150c6e15246596e122140a64bd338232619abf4a8cb1b8a3ba
\ No newline at end of file +d0a6b7d29226b083c404d76d9551a0eee98753058580cd62901f8dfac06ca08d
\ No newline at end of file diff --git a/tests/cachekey/project/sources/bzr1.expected b/tests/cachekey/project/sources/bzr1.expected index e09478788..9b29c17fe 100644 --- a/tests/cachekey/project/sources/bzr1.expected +++ b/tests/cachekey/project/sources/bzr1.expected @@ -1 +1 @@ -e688b31f8b79f30d11e5fc1121798776d3f9ee68d2d97bb3029bf809fb95892b
\ No newline at end of file +51415ebc7d72315c5c7704759025d6a9237e786bfe9c2bda8c51e15840c3470a
\ No newline at end of file diff --git a/tests/cachekey/project/sources/git1.expected b/tests/cachekey/project/sources/git1.expected index d7ed0f584..858cc2b7d 100644 --- a/tests/cachekey/project/sources/git1.expected +++ b/tests/cachekey/project/sources/git1.expected @@ -1 +1 @@ -40eb6b2bd3783189b72ac8465fcae7c7424f5804fbf229cc8984c86b9c07fd56
\ No newline at end of file +ef9bd728a328301e0b819be7109761aacfb4c87092904306d4117c86f30478a4
\ No newline at end of file diff --git a/tests/cachekey/project/sources/git2.expected b/tests/cachekey/project/sources/git2.expected index 5c88dae88..31d291b6c 100644 --- a/tests/cachekey/project/sources/git2.expected +++ b/tests/cachekey/project/sources/git2.expected @@ -1 +1 @@ -3a85acc6dee15a828bfabc4daa9bc1ea72bafa2293a1d2479a99938afa8ee1ff
\ No newline at end of file +a818930895e164bd342ab786061f4d521b27a4470791f55cc28732fdf92794de
\ No newline at end of file diff --git a/tests/cachekey/project/sources/local1.expected b/tests/cachekey/project/sources/local1.expected index 93dcccae0..f28d7c036 100644 --- a/tests/cachekey/project/sources/local1.expected +++ b/tests/cachekey/project/sources/local1.expected @@ -1 +1 @@ -e12330e4a0bd5456f460f47d05d52f434634a14549f01786b9b975ec6bf622cc
\ No newline at end of file +41ce5a640fdfd7b6ce8a2c3fa1dde7983bc4df0e4c3ca926670118bae3c051fe
\ No newline at end of file diff --git a/tests/cachekey/project/sources/local2.expected b/tests/cachekey/project/sources/local2.expected index e72b827b4..f1954ac6b 100644 --- a/tests/cachekey/project/sources/local2.expected +++ b/tests/cachekey/project/sources/local2.expected @@ -1 +1 @@ -e080c2b9f00416050cb5bfc64b7f8d79d8ee5b6b4e34e9375dfe75c4207593e6
\ No newline at end of file +bf3ceaa62d472c10ce1e991e5b07a81ddb5206e043d39e60167292910e6bd31e
\ No newline at end of file diff --git a/tests/cachekey/project/sources/ostree1.expected b/tests/cachekey/project/sources/ostree1.expected index 7d9694771..6e0aa34ff 100644 --- a/tests/cachekey/project/sources/ostree1.expected +++ b/tests/cachekey/project/sources/ostree1.expected @@ -1 +1 @@ -4782d65dc4492ddec81163478d10973afff6b69e65e4a74fed43afff06cd854b
\ No newline at end of file +c9dcee5ad0822df19984ba68e2a2068266427ee583e3dd3265f85f0515cf7510
\ No newline at end of file diff --git a/tests/cachekey/project/sources/patch1.expected b/tests/cachekey/project/sources/patch1.expected index edef8c833..ec306edfd 100644 --- a/tests/cachekey/project/sources/patch1.expected +++ b/tests/cachekey/project/sources/patch1.expected @@ -1 +1 @@ -7bf30dfdcb7bc546c9065c992f75122ab8033413eb52777a69a32be61eade124
\ No newline at end of file +96f2cf27bb2145290fc85b8cbeb7193738a6d22e1328c384bca7ba1e8754d0fd
\ No newline at end of file diff --git a/tests/cachekey/project/sources/patch2.expected b/tests/cachekey/project/sources/patch2.expected index e19dfdd59..57ee7f0a4 100644 --- a/tests/cachekey/project/sources/patch2.expected +++ b/tests/cachekey/project/sources/patch2.expected @@ -1 +1 @@ -d7459c7255caca1915aecc9cbd9ee7a3360c7e88d2ed76e050ab7f90653ecfea
\ No newline at end of file +bc7cca687951fbaecb365f39fc9d9d7b0fd509b861c77de71f3639e6f49e25fd
\ No newline at end of file diff --git a/tests/cachekey/project/sources/patch3.expected b/tests/cachekey/project/sources/patch3.expected index d5c8ac788..62b7b3bd7 100644 --- a/tests/cachekey/project/sources/patch3.expected +++ b/tests/cachekey/project/sources/patch3.expected @@ -1 +1 @@ -7d968e2f0675fd998ca880edadd51074d27390ce81f86df3d1435f333bf5712e
\ No newline at end of file +411a041ef82eb9021b308dbc873328e4cc8774c9b8aa0901ff865764bdf82c51
\ No newline at end of file diff --git a/tests/cachekey/project/sources/tar1.expected b/tests/cachekey/project/sources/tar1.expected index 7a31d64c9..07f541a7f 100644 --- a/tests/cachekey/project/sources/tar1.expected +++ b/tests/cachekey/project/sources/tar1.expected @@ -1 +1 @@ -9866109275d09a1c7c2ca882c6a3ed5c93345481f2295fd0afc5cf893f7cb432
\ No newline at end of file +a907dfad1c12f6303e7ed20896e49ba3fd5ef566777e7b47098116ec03e0e1f4
\ No newline at end of file diff --git a/tests/cachekey/project/sources/tar2.expected b/tests/cachekey/project/sources/tar2.expected index f8fcc601e..3da7239f3 100644 --- a/tests/cachekey/project/sources/tar2.expected +++ b/tests/cachekey/project/sources/tar2.expected @@ -1 +1 @@ -ac35ce227347a0f0d17a4ecacf959f24290c3fd2488284bbe2c879ed9bb3eaea
\ No newline at end of file +99865afccb0926ba5bbaa24e0ded7d8353b56fe499511ad6a809580d17abd80e
\ No newline at end of file diff --git a/tests/cachekey/project/sources/zip1.expected b/tests/cachekey/project/sources/zip1.expected index 518e63635..e15ed0ac2 100644 --- a/tests/cachekey/project/sources/zip1.expected +++ b/tests/cachekey/project/sources/zip1.expected @@ -1 +1 @@ -38a1d0a72ca251d445d655eb8b5536b10f29ee33924591577ac7e5f9262cc51f
\ No newline at end of file +23ac1cc41c6a72214b3e59664fe0ef85d909013befd8afde13cf8877510579e4
\ No newline at end of file diff --git a/tests/cachekey/project/sources/zip2.expected b/tests/cachekey/project/sources/zip2.expected index d22be4dbe..5208381c1 100644 --- a/tests/cachekey/project/sources/zip2.expected +++ b/tests/cachekey/project/sources/zip2.expected @@ -1 +1 @@ -98bbec0b0cc9229f5f11f0a829eb192f041b6ac6e5bdb5fc91c849ce91f1e5cf
\ No newline at end of file +be26d9222bf53589686861ce21391548dd5d6284fdd003ff8a7e39601d6e8bef
\ No newline at end of file diff --git a/tests/cachekey/project/target.expected b/tests/cachekey/project/target.expected index 252a85376..e5847c94e 100644 --- a/tests/cachekey/project/target.expected +++ b/tests/cachekey/project/target.expected @@ -1 +1 @@ -29c25f47cf186515a7adbec8a613a8ada9fc125b044299cddf1a372b8b4971b3
\ No newline at end of file +0f64d5abf95ea4d5c8e13978e4d8e52fa707a02c9554247ca70a21d7933c4ede
\ No newline at end of file diff --git a/tests/integration/source-determinism.py b/tests/integration/source-determinism.py new file mode 100644 index 000000000..b60bc25f7 --- /dev/null +++ b/tests/integration/source-determinism.py @@ -0,0 +1,155 @@ +import os +import pytest + +from buildstream import _yaml, utils +from tests.testutils import cli, create_repo, ALL_REPO_KINDS + + +DATA_DIR = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "project" +) + + +def create_test_file(*path, mode=0o644, content='content\n'): + path = os.path.join(*path) + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w') as f: + f.write(content) + os.fchmod(f.fileno(), mode) + + +def create_test_directory(*path, mode=0o644): + create_test_file(*path, '.keep', content='') + path = os.path.join(*path) + os.chmod(path, mode) + + +@pytest.mark.integration +@pytest.mark.datafiles(DATA_DIR) +@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS] + ['local']) +def test_deterministic_source_umask(cli, tmpdir, datafiles, kind): + project = str(datafiles) + element_name = 'list' + element_path = os.path.join(project, 'elements', element_name) + repodir = os.path.join(str(tmpdir), 'repo') + sourcedir = os.path.join(project, 'source') + + create_test_file(sourcedir, 'a.txt', mode=0o700) + create_test_file(sourcedir, 'b.txt', mode=0o755) + create_test_file(sourcedir, 'c.txt', mode=0o600) + create_test_file(sourcedir, 'd.txt', mode=0o400) + create_test_file(sourcedir, 'e.txt', mode=0o644) + create_test_file(sourcedir, 'f.txt', mode=0o4755) + create_test_file(sourcedir, 'g.txt', mode=0o2755) + create_test_file(sourcedir, 'h.txt', mode=0o1755) + create_test_directory(sourcedir, 'dir-a', mode=0o0700) + create_test_directory(sourcedir, 'dir-c', mode=0o0755) + create_test_directory(sourcedir, 'dir-d', mode=0o4755) + create_test_directory(sourcedir, 'dir-e', mode=0o2755) + create_test_directory(sourcedir, 'dir-f', mode=0o1755) + + if kind == 'local': + source = {'kind': 'local', + 'path': 'source'} + else: + repo = create_repo(kind, repodir) + ref = repo.create(sourcedir) + source = repo.source_config(ref=ref) + element = { + 'kind': 'manual', + 'depends': [ + { + 'filename': 'base.bst', + 'type': 'build' + } + ], + 'sources': [ + source + ], + 'config': { + 'install-commands': [ + 'ls -l >"%{install-root}/ls-l"' + ] + } + } + _yaml.dump(element, element_path) + + def get_value_for_umask(umask): + checkoutdir = os.path.join(str(tmpdir), 'checkout-{}'.format(umask)) + + old_umask = os.umask(umask) + + try: + result = cli.run(project=project, args=['build', element_name]) + result.assert_success() + + result = cli.run(project=project, args=['checkout', element_name, checkoutdir]) + result.assert_success() + + with open(os.path.join(checkoutdir, 'ls-l'), 'r') as f: + return f.read() + finally: + os.umask(old_umask) + cli.remove_artifact_from_cache(project, element_name) + + assert get_value_for_umask(0o022) == get_value_for_umask(0o077) + + +@pytest.mark.integration +@pytest.mark.datafiles(DATA_DIR) +def test_deterministic_source_local(cli, tmpdir, datafiles): + """Only user rights should be considered for local source. + """ + project = str(datafiles) + element_name = 'test' + element_path = os.path.join(project, 'elements', element_name) + sourcedir = os.path.join(project, 'source') + + element = { + 'kind': 'manual', + 'depends': [ + { + 'filename': 'base.bst', + 'type': 'build' + } + ], + 'sources': [ + { + 'kind': 'local', + 'path': 'source' + } + ], + 'config': { + 'install-commands': [ + 'ls -l >"%{install-root}/ls-l"' + ] + } + } + _yaml.dump(element, element_path) + + def get_value_for_mask(mask): + checkoutdir = os.path.join(str(tmpdir), 'checkout-{}'.format(mask)) + + create_test_file(sourcedir, 'a.txt', mode=0o644 & mask) + create_test_file(sourcedir, 'b.txt', mode=0o755 & mask) + create_test_file(sourcedir, 'c.txt', mode=0o4755 & mask) + create_test_file(sourcedir, 'd.txt', mode=0o2755 & mask) + create_test_file(sourcedir, 'e.txt', mode=0o1755 & mask) + create_test_directory(sourcedir, 'dir-a', mode=0o0755 & mask) + create_test_directory(sourcedir, 'dir-b', mode=0o4755 & mask) + create_test_directory(sourcedir, 'dir-c', mode=0o2755 & mask) + create_test_directory(sourcedir, 'dir-d', mode=0o1755 & mask) + try: + result = cli.run(project=project, args=['build', element_name]) + result.assert_success() + + result = cli.run(project=project, args=['checkout', element_name, checkoutdir]) + result.assert_success() + + with open(os.path.join(checkoutdir, 'ls-l'), 'r') as f: + return f.read() + finally: + cli.remove_artifact_from_cache(project, element_name) + + assert get_value_for_mask(0o7777) == get_value_for_mask(0o0700) |