forked from yadm-dev/yadm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_clone.py
328 lines (277 loc) · 10.4 KB
/
test_clone.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
"""Test clone"""
import os
import re
import pytest
BOOTSTRAP_CODE = 123
BOOTSTRAP_MSG = "Bootstrap successful"
@pytest.mark.usefixtures("remote")
@pytest.mark.parametrize(
"good_remote, repo_exists, force, conflicts",
[
(False, False, False, False),
(True, False, False, False),
(True, True, False, False),
(True, True, True, False),
(True, False, False, True),
],
ids=[
"bad remote",
"simple",
"existing repo",
"-f",
"conflicts",
],
)
def test_clone(runner, paths, yadm_cmd, repo_config, ds1, good_remote, repo_exists, force, conflicts):
"""Test basic clone operation"""
# clear out the work path
paths.work.remove()
paths.work.mkdir()
# determine remote url
remote_url = f"file://{paths.remote}"
if not good_remote:
remote_url = "file://bad_remote"
old_repo = None
if repo_exists:
# put a repo in the way
paths.repo.mkdir()
old_repo = paths.repo.join("old_repo")
old_repo.write("old_repo")
if conflicts:
ds1.tracked[0].relative.write("conflict")
assert ds1.tracked[0].relative.exists()
# run the clone command
args = ["clone", "-w", paths.work]
if force:
args += ["-f"]
args += [remote_url]
run = runner(command=yadm_cmd(*args))
if not good_remote:
# clone should fail
assert run.failure
assert run.out == ""
assert "Unable to clone the repository" in run.err
assert not paths.repo.exists()
elif repo_exists and not force:
# can't overwrite data
assert run.failure
assert run.out == ""
assert "Git repo already exists" in run.err
else:
# clone should succeed, and repo should be configured properly
assert successful_clone(run, paths, repo_config)
# these clones should have master as HEAD
verify_head(paths, "master")
# ensure conflicts are handled properly
if conflicts:
assert "NOTE" in run.out
assert "Local files with content that differs" in run.out
# confirm correct Git origin
run = runner(command=("git", "remote", "-v", "show"), env={"GIT_DIR": paths.repo})
assert run.success
assert run.err == ""
assert f"origin\t{remote_url}" in run.out
# ensure conflicts are really preserved
if conflicts:
# test that the conflicts are preserved in the work tree
run = runner(command=yadm_cmd("status", "-uno", "--porcelain"), cwd=paths.work)
assert run.success
assert run.err == ""
assert str(ds1.tracked[0].path) in run.out
# verify content of the conflicts
run = runner(command=yadm_cmd("diff"), cwd=paths.work)
assert run.success
assert run.err == ""
assert "\n+conflict" in run.out, "conflict overwritten"
# another force-related assertion
if old_repo:
if force:
assert not old_repo.exists()
else:
assert old_repo.exists()
@pytest.mark.usefixtures("remote")
@pytest.mark.parametrize(
"bs_exists, bs_param, answer",
[
(False, "--bootstrap", None),
(True, "--bootstrap", None),
(True, "--no-bootstrap", None),
(True, None, "n"),
(True, None, "y"),
],
ids=[
"force, missing",
"force, existing",
"prevent",
"existing, answer n",
"existing, answer y",
],
)
def test_clone_bootstrap(runner, paths, yadm_cmd, repo_config, bs_exists, bs_param, answer):
"""Test bootstrap clone features"""
# establish a bootstrap
create_bootstrap(paths, bs_exists)
# run the clone command
args = ["clone", "-w", paths.work]
if bs_param:
args += [bs_param]
args += [f"file://{paths.remote}"]
expect = []
if answer:
expect.append(("Would you like to execute it now", answer))
run = runner(command=yadm_cmd(*args), expect=expect)
if answer:
assert "Would you like to execute it now" in run.out
expected_code = 0
if bs_exists and bs_param != "--no-bootstrap":
expected_code = BOOTSTRAP_CODE
if answer == "y":
expected_code = BOOTSTRAP_CODE
assert BOOTSTRAP_MSG in run.out
elif answer == "n":
expected_code = 0
assert BOOTSTRAP_MSG not in run.out
assert successful_clone(run, paths, repo_config, expected_code)
verify_head(paths, "master")
if not bs_exists:
assert BOOTSTRAP_MSG not in run.out
def create_bootstrap(paths, exists):
"""Create bootstrap file for test"""
if exists:
paths.bootstrap.write("#!/bin/sh\n" f"echo {BOOTSTRAP_MSG}\n" f"exit {BOOTSTRAP_CODE}\n")
paths.bootstrap.chmod(0o775)
assert paths.bootstrap.exists()
else:
assert not paths.bootstrap.exists()
@pytest.mark.usefixtures("remote")
@pytest.mark.parametrize(
"private_type, in_repo, in_work",
[
("ssh", False, True),
("gnupg", False, True),
("ssh", True, True),
("gnupg", True, True),
("ssh", True, False),
("gnupg", True, False),
],
ids=[
"open ssh, not tracked",
"open gnupg, not tracked",
"open ssh, tracked",
"open gnupg, tracked",
"missing ssh, tracked",
"missing gnupg, tracked",
],
)
def test_clone_perms(runner, yadm_cmd, paths, repo_config, private_type, in_repo, in_work):
"""Test clone permission-related functions"""
# update remote repo to include private data
if in_repo:
rpath = paths.work.mkdir(f".{private_type}").join("related")
rpath.write("related")
os.system(f'GIT_DIR="{paths.remote}" git add {rpath}')
os.system(f'GIT_DIR="{paths.remote}" git commit -m "{rpath}"')
rpath.remove()
# ensure local private data is insecure at the start
if in_work:
pdir = paths.work.join(f".{private_type}")
if not pdir.exists():
pdir.mkdir()
pfile = pdir.join("existing")
pfile.write("existing")
pdir.chmod(0o777)
pfile.chmod(0o777)
else:
paths.work.remove()
paths.work.mkdir()
env = {"HOME": paths.work}
run = runner(yadm_cmd("clone", "-d", "-w", paths.work, f"file://{paths.remote}"), env=env)
assert successful_clone(run, paths, repo_config)
verify_head(paths, "master")
if in_work:
# private directories which already exist, should be left as they are,
# which in this test is "insecure".
assert re.search(f"initial private dir perms drwxrwxrwx.+.{private_type}", run.out)
assert re.search(f"pre-checkout private dir perms drwxrwxrwx.+.{private_type}", run.out)
assert re.search(f"post-checkout private dir perms drwxrwxrwx.+.{private_type}", run.out)
else:
# private directories which are created, should be done prior to
# checkout, and with secure permissions.
assert "initial private dir perms" not in run.out
assert re.search(f"pre-checkout private dir perms drwx------.+.{private_type}", run.out)
assert re.search(f"post-checkout private dir perms drwx------.+.{private_type}", run.out)
# standard perms still apply afterwards unless disabled with auto.perms
assert oct(paths.work.join(f".{private_type}").stat().mode).endswith(
"00"
), f".{private_type} has not been secured by auto.perms"
@pytest.mark.usefixtures("remote")
@pytest.mark.parametrize("branch", ["master", "default", "valid", "invalid"])
def test_alternate_branch(runner, paths, yadm_cmd, repo_config, branch):
"""Test cloning a branch other than master"""
# add a "valid" branch to the remote
os.system(f'GIT_DIR="{paths.remote}" git checkout -b valid')
os.system(f'GIT_DIR="{paths.remote}" git commit ' f'--allow-empty -m "This branch is valid"')
if branch != "default":
# When branch == 'default', the "default" branch of the remote repo
# will remain "valid" to validate identification the correct default
# branch by inspecting the repo. Otherwise it will be set back to
# "master"
os.system(f'GIT_DIR="{paths.remote}" git checkout master')
# clear out the work path
paths.work.remove()
paths.work.mkdir()
remote_url = f"file://{paths.remote}"
# run the clone command
args = ["clone", "-w", paths.work]
if branch not in ["master", "default"]:
args += ["-b", branch]
args += [remote_url]
run = runner(command=yadm_cmd(*args))
if branch == "invalid":
assert run.failure
assert "ERROR: Unable to clone the repository" in run.err
assert f"Remote branch {branch} not found in upstream" in run.err
else:
assert successful_clone(run, paths, repo_config)
# confirm correct Git origin
run = runner(command=("git", "remote", "-v", "show"), env={"GIT_DIR": paths.repo})
assert run.success
assert run.err == ""
assert f"origin\t{remote_url}" in run.out
run = runner(command=yadm_cmd("show"))
if branch == "master":
assert "Initial commit" in run.out
verify_head(paths, "master")
else:
assert "This branch is valid" in run.out
verify_head(paths, "valid")
def successful_clone(run, paths, repo_config, expected_code=0):
"""Assert clone is successful"""
assert run.code == expected_code
assert oct(paths.repo.stat().mode).endswith("00"), "Repo is not secured"
assert repo_config("core.bare") == "false"
assert repo_config("status.showUntrackedFiles") == "no"
assert repo_config("yadm.managed") == "true"
return True
@pytest.fixture()
def remote(paths, ds1_repo_copy):
"""Function scoped remote (based on ds1)"""
# pylint: disable=unused-argument
# This is ignored because
# @pytest.mark.usefixtures('ds1_remote_copy')
# cannot be applied to another fixture.
paths.remote.remove()
paths.repo.move(paths.remote)
def test_no_repo(
runner,
yadm_cmd,
):
"""Test cloning without specifying a repo"""
run = runner(command=yadm_cmd("clone", "-f"))
assert run.failure
assert run.out == ""
assert "ERROR: Unable to clone the repository" in run.err
assert "repository 'repo.git' does not exist" in run.err
def verify_head(paths, branch):
"""Assert the local repo has the correct head branch"""
assert paths.repo.join("HEAD").read() == f"ref: refs/heads/{branch}\n"