diff options
Diffstat (limited to '')
-rw-r--r-- | unittests/subprojectscommandtests.py | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/unittests/subprojectscommandtests.py b/unittests/subprojectscommandtests.py new file mode 100644 index 0000000..bca124d --- /dev/null +++ b/unittests/subprojectscommandtests.py @@ -0,0 +1,300 @@ +# Copyright 2016-2021 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess +import tempfile +import textwrap +import os +from pathlib import Path +import typing as T + +from mesonbuild.mesonlib import ( + version_compare, git, search_version +) + + + +from .baseplatformtests import BasePlatformTests +from .helpers import * + +class SubprojectsCommandTests(BasePlatformTests): + def setUp(self): + super().setUp() + self.root_dir = Path(self.builddir) + + self.project_dir = self.root_dir / 'src' + self._create_project(self.project_dir) + + self.subprojects_dir = self.project_dir / 'subprojects' + os.makedirs(str(self.subprojects_dir)) + self.packagecache_dir = self.subprojects_dir / 'packagecache' + os.makedirs(str(self.packagecache_dir)) + + def _create_project(self, path, project_name='dummy'): + os.makedirs(str(path), exist_ok=True) + with open(str(path / 'meson.build'), 'w', encoding='utf-8') as f: + f.write(f"project('{project_name}')") + + def _git(self, cmd, workdir): + return git(cmd, str(workdir), check=True)[1].strip() + + def _git_config(self, workdir): + self._git(['config', 'user.name', 'Meson Test'], workdir) + self._git(['config', 'user.email', 'meson.test@example.com'], workdir) + + def _git_remote(self, cmd, name): + return self._git(cmd, self.root_dir / name) + + def _git_local(self, cmd, name): + return self._git(cmd, self.subprojects_dir / name) + + def _git_local_branch(self, name): + # Same as `git branch --show-current` but compatible with older git version + branch = self._git_local(['rev-parse', '--abbrev-ref', 'HEAD'], name) + return branch if branch != 'HEAD' else '' + + def _git_local_commit(self, name, ref='HEAD'): + return self._git_local(['rev-parse', ref], name) + + def _git_remote_commit(self, name, ref='HEAD'): + return self._git_remote(['rev-parse', ref], name) + + def _git_create_repo(self, path): + # If a user has git configuration init.defaultBranch set we want to override that + with tempfile.TemporaryDirectory() as d: + out = git(['--version'], str(d))[1] + if version_compare(search_version(out), '>= 2.28'): + extra_cmd = ['--initial-branch', 'master'] + else: + extra_cmd = [] + + self._create_project(path) + self._git(['init'] + extra_cmd, path) + self._git_config(path) + self._git(['add', '.'], path) + self._git(['commit', '--no-gpg-sign', '-m', 'Initial commit'], path) + + def _git_create_remote_repo(self, name): + self._git_create_repo(self.root_dir / name) + + def _git_create_local_repo(self, name): + self._git_create_repo(self.subprojects_dir / name) + + def _git_create_remote_commit(self, name, branch): + self._git_remote(['checkout', branch], name) + self._git_remote(['commit', '--no-gpg-sign', '--allow-empty', '-m', f'initial {branch} commit'], name) + + def _git_create_remote_branch(self, name, branch): + self._git_remote(['checkout', '-b', branch], name) + self._git_remote(['commit', '--no-gpg-sign', '--allow-empty', '-m', f'initial {branch} commit'], name) + + def _git_create_remote_tag(self, name, tag): + self._git_remote(['commit', '--no-gpg-sign', '--allow-empty', '-m', f'tag {tag} commit'], name) + self._git_remote(['tag', '--no-sign', tag], name) + + def _wrap_create_git(self, name, revision='master', depth=None): + path = self.root_dir / name + with open(str((self.subprojects_dir / name).with_suffix('.wrap')), 'w', encoding='utf-8') as f: + if depth is None: + depth_line = '' + else: + depth_line = 'depth = {}'.format(depth) + f.write(textwrap.dedent( + ''' + [wrap-git] + url={} + revision={} + {} + '''.format(os.path.abspath(str(path)), revision, depth_line))) + + def _wrap_create_file(self, name, tarball='dummy.tar.gz'): + path = self.root_dir / tarball + with open(str((self.subprojects_dir / name).with_suffix('.wrap')), 'w', encoding='utf-8') as f: + f.write(textwrap.dedent( + f''' + [wrap-file] + source_url={os.path.abspath(str(path))} + source_filename={tarball} + ''')) + Path(self.packagecache_dir / tarball).touch() + + def _subprojects_cmd(self, args): + return self._run(self.meson_command + ['subprojects'] + args, workdir=str(self.project_dir)) + + def test_git_update(self): + subp_name = 'sub1' + + # Create a fake remote git repository and a wrap file. Checks that + # "meson subprojects download" works. + self._git_create_remote_repo(subp_name) + self._wrap_create_git(subp_name) + self._subprojects_cmd(['download']) + self.assertPathExists(str(self.subprojects_dir / subp_name)) + self._git_config(self.subprojects_dir / subp_name) + + # Create a new remote branch and update the wrap file. Checks that + # "meson subprojects update --reset" checkout the new branch. + self._git_create_remote_branch(subp_name, 'newbranch') + self._wrap_create_git(subp_name, 'newbranch') + self._subprojects_cmd(['update', '--reset']) + self.assertEqual(self._git_local_branch(subp_name), 'newbranch') + self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch')) + + # Update remote newbranch. Checks the new commit is pulled into existing + # local newbranch. Make sure it does not print spurious 'git stash' message. + self._git_create_remote_commit(subp_name, 'newbranch') + out = self._subprojects_cmd(['update', '--reset']) + self.assertNotIn('No local changes to save', out) + self.assertEqual(self._git_local_branch(subp_name), 'newbranch') + self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch')) + + # Update remote newbranch and switch to another branch. Checks that it + # switch current branch to newbranch and pull latest commit. + self._git_local(['checkout', 'master'], subp_name) + self._git_create_remote_commit(subp_name, 'newbranch') + self._subprojects_cmd(['update', '--reset']) + self.assertEqual(self._git_local_branch(subp_name), 'newbranch') + self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch')) + + # Stage some local changes then update. Checks that local changes got + # stashed. + self._create_project(self.subprojects_dir / subp_name, 'new_project_name') + self._git_local(['add', '.'], subp_name) + self._git_create_remote_commit(subp_name, 'newbranch') + self._subprojects_cmd(['update', '--reset']) + self.assertEqual(self._git_local_branch(subp_name), 'newbranch') + self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch')) + self.assertTrue(self._git_local(['stash', 'list'], subp_name)) + + # Create a new remote tag and update the wrap file. Checks that + # "meson subprojects update --reset" checkout the new tag in detached mode. + self._git_create_remote_tag(subp_name, 'newtag') + self._wrap_create_git(subp_name, 'newtag') + self._subprojects_cmd(['update', '--reset']) + self.assertEqual(self._git_local_branch(subp_name), '') + self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newtag')) + + # Create a new remote commit and update the wrap file with the commit id. + # Checks that "meson subprojects update --reset" checkout the new commit + # in detached mode. + self._git_local(['checkout', 'master'], subp_name) + self._git_create_remote_commit(subp_name, 'newbranch') + new_commit = self._git_remote(['rev-parse', 'HEAD'], subp_name) + self._wrap_create_git(subp_name, new_commit) + self._subprojects_cmd(['update', '--reset']) + self.assertEqual(self._git_local_branch(subp_name), '') + self.assertEqual(self._git_local_commit(subp_name), new_commit) + + # Create a local project not in a git repository, then update it with + # a git wrap. Without --reset it should print error message and return + # failure. With --reset it should delete existing project and clone the + # new project. + subp_name = 'sub2' + self._create_project(self.subprojects_dir / subp_name) + self._git_create_remote_repo(subp_name) + self._wrap_create_git(subp_name) + with self.assertRaises(subprocess.CalledProcessError) as cm: + self._subprojects_cmd(['update']) + self.assertIn('Not a git repository', cm.exception.output) + self._subprojects_cmd(['update', '--reset']) + self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name)) + + # Create a fake remote git repository and a wrap file targeting + # HEAD and depth = 1. Checks that "meson subprojects download" works. + subp_name = 'sub3' + self._git_create_remote_repo(subp_name) + self._wrap_create_git(subp_name, revision='head', depth='1') + self._subprojects_cmd(['download']) + self.assertPathExists(str(self.subprojects_dir / subp_name)) + self._git_config(self.subprojects_dir / subp_name) + + @skipIfNoExecutable('true') + def test_foreach(self): + self._create_project(self.subprojects_dir / 'sub_file') + self._wrap_create_file('sub_file') + self._git_create_local_repo('sub_git') + self._wrap_create_git('sub_git') + self._git_create_local_repo('sub_git_no_wrap') + + def ran_in(s): + ret = [] + prefix = 'Executing command in ' + for l in s.splitlines(): + if l.startswith(prefix): + ret.append(l[len(prefix):]) + return sorted(ret) + + dummy_cmd = ['true'] + out = self._subprojects_cmd(['foreach'] + dummy_cmd) + self.assertEqual(ran_in(out), sorted(['subprojects/sub_file', 'subprojects/sub_git', 'subprojects/sub_git_no_wrap'])) + out = self._subprojects_cmd(['foreach', '--types', 'git,file'] + dummy_cmd) + self.assertEqual(ran_in(out), sorted(['subprojects/sub_file', 'subprojects/sub_git'])) + out = self._subprojects_cmd(['foreach', '--types', 'file'] + dummy_cmd) + self.assertEqual(ran_in(out), ['subprojects/sub_file']) + out = self._subprojects_cmd(['foreach', '--types', 'git'] + dummy_cmd) + self.assertEqual(ran_in(out), ['subprojects/sub_git']) + + def test_purge(self): + self._create_project(self.subprojects_dir / 'sub_file') + self._wrap_create_file('sub_file') + self._git_create_local_repo('sub_git') + self._wrap_create_git('sub_git') + + sub_file_subprojects_dir = self.subprojects_dir / 'sub_file' / 'subprojects' + sub_file_subprojects_dir.mkdir(exist_ok=True, parents=True) + real_dir = Path('sub_file') / 'subprojects' / 'real' + + self._wrap_create_file(real_dir, tarball='dummy2.tar.gz') + + with open(str((self.subprojects_dir / 'redirect').with_suffix('.wrap')), 'w', encoding='utf-8') as f: + f.write(textwrap.dedent( + f''' + [wrap-redirect] + filename = {real_dir}.wrap + ''')) + + def deleting(s: str) -> T.List[str]: + ret = [] + prefix = 'Deleting ' + for l in s.splitlines(): + if l.startswith(prefix): + ret.append(l[len(prefix):]) + return sorted(ret) + + out = self._subprojects_cmd(['purge']) + self.assertEqual(deleting(out), sorted([ + str(self.subprojects_dir / 'redirect.wrap'), + str(self.subprojects_dir / 'sub_file'), + str(self.subprojects_dir / 'sub_git'), + ])) + out = self._subprojects_cmd(['purge', '--include-cache']) + self.assertEqual(deleting(out), sorted([ + str(self.subprojects_dir / 'sub_git'), + str(self.subprojects_dir / 'redirect.wrap'), + str(self.subprojects_dir / 'packagecache' / 'dummy.tar.gz'), + str(self.subprojects_dir / 'packagecache' / 'dummy2.tar.gz'), + str(self.subprojects_dir / 'sub_file'), + ])) + out = self._subprojects_cmd(['purge', '--include-cache', '--confirm']) + self.assertEqual(deleting(out), sorted([ + str(self.subprojects_dir / 'sub_git'), + str(self.subprojects_dir / 'redirect.wrap'), + str(self.subprojects_dir / 'packagecache' / 'dummy.tar.gz'), + str(self.subprojects_dir / 'packagecache' / 'dummy2.tar.gz'), + str(self.subprojects_dir / 'sub_file'), + ])) + self.assertFalse(Path(self.subprojects_dir / 'packagecache' / 'dummy.tar.gz').exists()) + self.assertFalse(Path(self.subprojects_dir / 'sub_file').exists()) + self.assertFalse(Path(self.subprojects_dir / 'sub_git').exists()) + self.assertFalse(Path(self.subprojects_dir / 'redirect.wrap').exists()) |