diff options
31 files changed, 249 insertions, 30 deletions
diff --git a/buildstream/_versions.py b/buildstream/_versions.py index ff85d2443..eddb34fc6 100644 --- a/buildstream/_versions.py +++ b/buildstream/_versions.py @@ -33,4 +33,5 @@ BST_FORMAT_VERSION = 12 # 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 = 2 + +BST_CORE_ARTIFACT_VERSION = ('bst-1.2', 3) 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/remote.py b/buildstream/plugins/sources/remote.py index ad4cdab8b..ea0e612c2 100644 --- a/buildstream/plugins/sources/remote.py +++ b/buildstream/plugins/sources/remote.py @@ -49,6 +49,7 @@ remote - stage files from remote urls """ import os +import stat from buildstream import SourceError, utils from ._downloadablefilesource import DownloadableFileSource @@ -75,6 +76,7 @@ class RemoteSource(DownloadableFileSource): dest = os.path.join(directory, self.filename) with self.timed_activity("Staging remote file to {}".format(dest)): utils.safe_copy(self._get_mirror_file(), dest) + os.chmod(dest, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) def setup(): 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 68f99b9a3..9546f13cd 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 7c5af6054..cc4bf4229 100644 --- a/tests/cachekey/project/elements/build1.expected +++ b/tests/cachekey/project/elements/build1.expected @@ -1 +1 @@ -3db51572837956b28ffbc4aabdce659b4a1d91dcbb8b75954210346959ed5fa9
\ No newline at end of file +e7de3dd12a1e5307e07859ddf2192443a0ccb1ff48e0adcc6c18f9edc2bd0d7d
\ No newline at end of file diff --git a/tests/cachekey/project/elements/build2.expected b/tests/cachekey/project/elements/build2.expected index e1bd91218..3cb726dde 100644 --- a/tests/cachekey/project/elements/build2.expected +++ b/tests/cachekey/project/elements/build2.expected @@ -1 +1 @@ -bcde6fc389b7d8bb7788989b68f68653ab8ed658117012c0611f218f4a585d38
\ No newline at end of file +d74957e0f20a7664e9ceed6cc2ba6c140bd8d8d0712d02066feb442638e8e6ed
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose1.expected b/tests/cachekey/project/elements/compose1.expected index 86a2a2f2a..7289d9919 100644 --- a/tests/cachekey/project/elements/compose1.expected +++ b/tests/cachekey/project/elements/compose1.expected @@ -1 +1 @@ -6736bbcc055e1801a19288d3a64b622e0b9223164f8ad2ce842b18a4eaa0cfb9
\ No newline at end of file +f8b69ac5ce84a8e8db30f9ae58d7560054d41da311176f74047694ec1203d7e8
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose2.expected b/tests/cachekey/project/elements/compose2.expected index a811cc421..f80f6fb1e 100644 --- a/tests/cachekey/project/elements/compose2.expected +++ b/tests/cachekey/project/elements/compose2.expected @@ -1 +1 @@ -9294428a0b5c0d44fdb3ab0f883ee87f9e62d51f96c7de1e5e81ed5e3934d403
\ No newline at end of file +4f542af0ebf3136b0affe42cb5574b7cf1034db6fb60d272ab2304e1c99b0d6f
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose3.expected b/tests/cachekey/project/elements/compose3.expected index ce28c853a..7b9d23ed4 100644 --- a/tests/cachekey/project/elements/compose3.expected +++ b/tests/cachekey/project/elements/compose3.expected @@ -1 +1 @@ -4f1569b9a6317280e6299f9f7f706a6adcc89603030cde51d529dd6dfe2851be
\ No newline at end of file +93863a5513f3b59a107a3ba23a6e47b38738e7c99ac462d2379308dab9910d8f
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose4.expected b/tests/cachekey/project/elements/compose4.expected index 8d95a3d87..7feb88917 100644 --- a/tests/cachekey/project/elements/compose4.expected +++ b/tests/cachekey/project/elements/compose4.expected @@ -1 +1 @@ -4c83744bec21c8c38bce2d48396b8df1eb4df7b2f155424016bd012743efd808
\ No newline at end of file +e66827c4f0beffbb9eff52522539b10945ad108bd5ad722107e5dfbce7d064ef
\ No newline at end of file diff --git a/tests/cachekey/project/elements/compose5.expected b/tests/cachekey/project/elements/compose5.expected index 183534aa4..d99aa675f 100644 --- a/tests/cachekey/project/elements/compose5.expected +++ b/tests/cachekey/project/elements/compose5.expected @@ -1 +1 @@ -97385aa2192ef0295dd2601e78491d8bdf6b74e98938d0f8011747c2caf3a5c6
\ No newline at end of file +74a1fcb3c1c7829962398cc104a30d52aeb38af9fe8631a6b77112a1fe99b653
\ No newline at end of file diff --git a/tests/cachekey/project/elements/import1.expected b/tests/cachekey/project/elements/import1.expected index 387da88b7..b9020e2d0 100644 --- a/tests/cachekey/project/elements/import1.expected +++ b/tests/cachekey/project/elements/import1.expected @@ -1 +1 @@ -99c8f61d415de3a6c96e48299fda5554bf4bbaf56bb4b5acd85861ab37ede0c3
\ No newline at end of file +619692c94c9b499eaa23358b45fd75b4526d9f5b6d3a6061faad9b6726510db3
\ No newline at end of file diff --git a/tests/cachekey/project/elements/import2.expected b/tests/cachekey/project/elements/import2.expected index 0893dde2a..27a365a64 100644 --- a/tests/cachekey/project/elements/import2.expected +++ b/tests/cachekey/project/elements/import2.expected @@ -1 +1 @@ -5f5884c5e4bb7066eede3a135e49753ec06b757a30983513a7a4e0cdd2a8f402
\ No newline at end of file +8e1ee1f99738be5162e97194d9aa72aef0a9d3458218747f9721102f2d7104d7
\ No newline at end of file diff --git a/tests/cachekey/project/elements/import3.expected b/tests/cachekey/project/elements/import3.expected index 6d0fe864a..8e8eed096 100644 --- a/tests/cachekey/project/elements/import3.expected +++ b/tests/cachekey/project/elements/import3.expected @@ -1 +1 @@ -e11f93ec629bc3556e15bd374e67a0b5e34350e1e9b1d1f98f8de984a27bbead
\ No newline at end of file +5d9cfb59d10bb98ca17c52cf8862b84a39202b1d83074a8b8dd3da83a0303c19
\ No newline at end of file diff --git a/tests/cachekey/project/elements/script1.expected b/tests/cachekey/project/elements/script1.expected index e8d5b24c4..3613c35d7 100644 --- a/tests/cachekey/project/elements/script1.expected +++ b/tests/cachekey/project/elements/script1.expected @@ -1 +1 @@ -d8388b756de5c8441375ba32cedd9560a65a8f9a85e41038837d342c8fb10004
\ No newline at end of file +7df04616b29ee538ec5e290dcfd7fdfce9cacdbaf224597856272aec3939d5c8
\ No newline at end of file diff --git a/tests/cachekey/project/sources/bzr1.expected b/tests/cachekey/project/sources/bzr1.expected index ca11c959a..cb276c4c2 100644 --- a/tests/cachekey/project/sources/bzr1.expected +++ b/tests/cachekey/project/sources/bzr1.expected @@ -1 +1 @@ -519ee88fcca7fea091245713ec68baa048e3d876ea22559d4b2035d3d2ab2494
\ No newline at end of file +a2682d5e230ea207054fef05eecc1bb8ebc856ae12984ca5ab187d648551e917
\ No newline at end of file diff --git a/tests/cachekey/project/sources/git1.expected b/tests/cachekey/project/sources/git1.expected index 85dc88500..427db1397 100644 --- a/tests/cachekey/project/sources/git1.expected +++ b/tests/cachekey/project/sources/git1.expected @@ -1 +1 @@ -a5424aa7cc25f0ada9ac1245b33d55d078559ae6c50b10bea3db9acb964b058c
\ No newline at end of file +572276304251917e3ce611b19a6c95cb984d989274405344d307e463c53c6b41
\ No newline at end of file diff --git a/tests/cachekey/project/sources/git2.expected b/tests/cachekey/project/sources/git2.expected index 9a643c000..a481139d9 100644 --- a/tests/cachekey/project/sources/git2.expected +++ b/tests/cachekey/project/sources/git2.expected @@ -1 +1 @@ -93bf7344c118664f0d7f2b8e5a6731b2a95de6df83ba7fa2a2ab28227b0b3e8b
\ No newline at end of file +e1c86b2e7a5e87f01a7ea10beacff0c9d2771b39e295729e76db42f2133ad478
\ No newline at end of file diff --git a/tests/cachekey/project/sources/local1.expected b/tests/cachekey/project/sources/local1.expected index 387da88b7..b9020e2d0 100644 --- a/tests/cachekey/project/sources/local1.expected +++ b/tests/cachekey/project/sources/local1.expected @@ -1 +1 @@ -99c8f61d415de3a6c96e48299fda5554bf4bbaf56bb4b5acd85861ab37ede0c3
\ No newline at end of file +619692c94c9b499eaa23358b45fd75b4526d9f5b6d3a6061faad9b6726510db3
\ No newline at end of file diff --git a/tests/cachekey/project/sources/local2.expected b/tests/cachekey/project/sources/local2.expected index 598fe73ba..7a5a9fcc7 100644 --- a/tests/cachekey/project/sources/local2.expected +++ b/tests/cachekey/project/sources/local2.expected @@ -1 +1 @@ -780a7e62bbe5bc0f975ec6cd749de6a85f9080d3628f16f881605801597916a7
\ No newline at end of file +080e7416809ccc1a433e28263f6d719c2ac18047ea6def5991ef0dbd049a76f7
\ No newline at end of file diff --git a/tests/cachekey/project/sources/ostree1.expected b/tests/cachekey/project/sources/ostree1.expected index 0e8e83014..b318f5051 100644 --- a/tests/cachekey/project/sources/ostree1.expected +++ b/tests/cachekey/project/sources/ostree1.expected @@ -1 +1 @@ -9b06b6e0c213a5475d2b0fcfee537c41dbec579e6109e95f7e7aeb0488f079f6
\ No newline at end of file +9ea532e5911bae30f07910a5da05190c74477e4b5038e8aa43e0178c48d85f92
\ No newline at end of file diff --git a/tests/cachekey/project/sources/patch1.expected b/tests/cachekey/project/sources/patch1.expected index d7cf73c34..8887de99d 100644 --- a/tests/cachekey/project/sources/patch1.expected +++ b/tests/cachekey/project/sources/patch1.expected @@ -1 +1 @@ -d5b0f1fa5b4e3e7aa617de303125268c7a7461e415ecf1eccc8aee2cda56897e
\ No newline at end of file +11cab57689dcbb0afd4ee70d589d41d641e1f103427df51fd1d944ec66edc21b
\ No newline at end of file diff --git a/tests/cachekey/project/sources/patch2.expected b/tests/cachekey/project/sources/patch2.expected index 56a92dc8e..196bfedf4 100644 --- a/tests/cachekey/project/sources/patch2.expected +++ b/tests/cachekey/project/sources/patch2.expected @@ -1 +1 @@ -6decb6b49e48a5869b2a438254c911423275662aff73348cd95e64148011c097
\ No newline at end of file +4ab69bc0ecbe926e5659a11bb3b82ac0e5155f1571923b1a57668ce93f27cb46
\ No newline at end of file diff --git a/tests/cachekey/project/sources/patch3.expected b/tests/cachekey/project/sources/patch3.expected index f1257bb31..abac783cd 100644 --- a/tests/cachekey/project/sources/patch3.expected +++ b/tests/cachekey/project/sources/patch3.expected @@ -1 +1 @@ -ab91e0ab9e167c4e9d31480c96a6a91a47ff27246f4eeff4ce6b671cbd865901
\ No newline at end of file +d2d943fa7e0bc7188eaa461d9b92c23aee43361c279106d92dd5f2260ebf8110
\ No newline at end of file diff --git a/tests/cachekey/project/sources/tar1.expected b/tests/cachekey/project/sources/tar1.expected index ab0bd56ea..0e657b4a7 100644 --- a/tests/cachekey/project/sources/tar1.expected +++ b/tests/cachekey/project/sources/tar1.expected @@ -1 +1 @@ -ccb35d04789b0d83fd93a6c2f8688c4abfe20f5bc77420f63054893450b2a832
\ No newline at end of file +a284396dae1e98302c98b150d1bb04b576d3039bea138fe0244c3cde6d5ccea5
\ No newline at end of file diff --git a/tests/cachekey/project/sources/tar2.expected b/tests/cachekey/project/sources/tar2.expected index 03241f460..720ea6cfd 100644 --- a/tests/cachekey/project/sources/tar2.expected +++ b/tests/cachekey/project/sources/tar2.expected @@ -1 +1 @@ -441c80ed92c77df8247344337f470ac7ab7fe91d2fe3900b498708b0faeac4b5
\ No newline at end of file +db0d89c04aa964931ee65c456ae91aa2cb784c189f03f2ad36c3ba91381c2005
\ No newline at end of file diff --git a/tests/cachekey/project/sources/zip1.expected b/tests/cachekey/project/sources/zip1.expected index a3ac93ecf..799277681 100644 --- a/tests/cachekey/project/sources/zip1.expected +++ b/tests/cachekey/project/sources/zip1.expected @@ -1 +1 @@ -be47de64162c9cce0322d0af327092c7afc3a890ba9d6ef92eef016dcced5bae
\ No newline at end of file +d106ec29cbd3d04f54d6416edb2300d3e7acb5cb21b231efa6e9f251384d7bc0
\ No newline at end of file diff --git a/tests/cachekey/project/sources/zip2.expected b/tests/cachekey/project/sources/zip2.expected index 49bd45fd0..82ea3c545 100644 --- a/tests/cachekey/project/sources/zip2.expected +++ b/tests/cachekey/project/sources/zip2.expected @@ -1 +1 @@ -bedd330938f9405e2febcf1de8428b7180eb62ab73f8e31e49871874ae351735
\ No newline at end of file +5085978ab2f7f228a6d58ddad945d146d353be6f313e9e13981b7f3a88819d72
\ No newline at end of file diff --git a/tests/cachekey/project/target.expected b/tests/cachekey/project/target.expected index 4f4c7c1f8..e2e2e665a 100644 --- a/tests/cachekey/project/target.expected +++ b/tests/cachekey/project/target.expected @@ -1 +1 @@ -a408b3e4b6ba4d6a6338bd3153728be89a18b74b13bde554411a4371fda487bc
\ No newline at end of file +01f611e61e948f32035b659d33cdae662d863c99051d0e6746f9c5626138655f
\ 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) |