1 |
Direct FETCHCOMMAND/RESUMECOMMAND output to a temporary file with |
2 |
a constant .__download__ suffix, and atomically rename the file |
3 |
to remove the suffix only after the download has completed |
4 |
successfully (includes digest verification when applicable). |
5 |
Also add unit tests to cover most fetch cases. |
6 |
|
7 |
Bug: https://bugs.gentoo.org/175612 |
8 |
Signed-off-by: Zac Medico <zmedico@g.o> |
9 |
--- |
10 |
lib/_emerge/BinpkgVerifier.py | 2 +- |
11 |
lib/portage/package/ebuild/fetch.py | 97 ++++++++----- |
12 |
lib/portage/tests/ebuild/test_fetch.py | 186 +++++++++++++++++++++++++ |
13 |
3 files changed, 251 insertions(+), 34 deletions(-) |
14 |
create mode 100644 lib/portage/tests/ebuild/test_fetch.py |
15 |
|
16 |
diff --git a/lib/_emerge/BinpkgVerifier.py b/lib/_emerge/BinpkgVerifier.py |
17 |
index 7a6d15e80..bde1328ea 100644 |
18 |
--- a/lib/_emerge/BinpkgVerifier.py |
19 |
+++ b/lib/_emerge/BinpkgVerifier.py |
20 |
@@ -108,7 +108,7 @@ class BinpkgVerifier(CompositeTask): |
21 |
def _digest_exception(self, name, value, expected): |
22 |
|
23 |
head, tail = os.path.split(self._pkg_path) |
24 |
- temp_filename = _checksum_failure_temp_file(head, tail) |
25 |
+ temp_filename = _checksum_failure_temp_file(self.pkg.root_config.settings, head, tail) |
26 |
|
27 |
self.scheduler.output(( |
28 |
"\n!!! Digest verification failed:\n" |
29 |
diff --git a/lib/portage/package/ebuild/fetch.py b/lib/portage/package/ebuild/fetch.py |
30 |
index bfd97601c..cd4a5955c 100644 |
31 |
--- a/lib/portage/package/ebuild/fetch.py |
32 |
+++ b/lib/portage/package/ebuild/fetch.py |
33 |
@@ -30,7 +30,7 @@ portage.proxy.lazyimport.lazyimport(globals(), |
34 |
) |
35 |
|
36 |
from portage import os, selinux, shutil, _encodings, \ |
37 |
- _shell_quote, _unicode_encode |
38 |
+ _movefile, _shell_quote, _unicode_encode |
39 |
from portage.checksum import (get_valid_checksum_keys, perform_md5, verify_all, |
40 |
_filter_unaccelarated_hashes, _hash_filter, _apply_hash_filter) |
41 |
from portage.const import BASH_BINARY, CUSTOM_MIRRORS_FILE, \ |
42 |
@@ -46,6 +46,8 @@ from portage.util import apply_recursive_permissions, \ |
43 |
varexpand, writemsg, writemsg_level, writemsg_stdout |
44 |
from portage.process import spawn |
45 |
|
46 |
+_download_suffix = '.__download__' |
47 |
+ |
48 |
_userpriv_spawn_kwargs = ( |
49 |
("uid", portage_uid), |
50 |
("gid", portage_gid), |
51 |
@@ -139,7 +141,7 @@ def _userpriv_test_write_file(settings, file_path): |
52 |
_userpriv_test_write_file_cache[file_path] = rval |
53 |
return rval |
54 |
|
55 |
-def _checksum_failure_temp_file(distdir, basename): |
56 |
+def _checksum_failure_temp_file(settings, distdir, basename): |
57 |
""" |
58 |
First try to find a duplicate temp file with the same checksum and return |
59 |
that filename if available. Otherwise, use mkstemp to create a new unique |
60 |
@@ -149,9 +151,13 @@ def _checksum_failure_temp_file(distdir, basename): |
61 |
""" |
62 |
|
63 |
filename = os.path.join(distdir, basename) |
64 |
+ if basename.endswith(_download_suffix): |
65 |
+ normal_basename = basename[:-len(_download_suffix)] |
66 |
+ else: |
67 |
+ normal_basename = basename |
68 |
size = os.stat(filename).st_size |
69 |
checksum = None |
70 |
- tempfile_re = re.compile(re.escape(basename) + r'\._checksum_failure_\..*') |
71 |
+ tempfile_re = re.compile(re.escape(normal_basename) + r'\._checksum_failure_\..*') |
72 |
for temp_filename in os.listdir(distdir): |
73 |
if not tempfile_re.match(temp_filename): |
74 |
continue |
75 |
@@ -173,9 +179,9 @@ def _checksum_failure_temp_file(distdir, basename): |
76 |
return temp_filename |
77 |
|
78 |
fd, temp_filename = \ |
79 |
- tempfile.mkstemp("", basename + "._checksum_failure_.", distdir) |
80 |
+ tempfile.mkstemp("", normal_basename + "._checksum_failure_.", distdir) |
81 |
os.close(fd) |
82 |
- os.rename(filename, temp_filename) |
83 |
+ _movefile(filename, temp_filename, mysettings=settings) |
84 |
return temp_filename |
85 |
|
86 |
def _check_digests(filename, digests, show_errors=1): |
87 |
@@ -602,6 +608,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
88 |
pruned_digests["size"] = size |
89 |
|
90 |
myfile_path = os.path.join(mysettings["DISTDIR"], myfile) |
91 |
+ download_path = myfile_path + _download_suffix |
92 |
has_space = True |
93 |
has_space_superuser = True |
94 |
file_lock = None |
95 |
@@ -679,12 +686,15 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
96 |
del e |
97 |
continue |
98 |
|
99 |
- if distdir_writable and mystat is None: |
100 |
- # Remove broken symlinks if necessary. |
101 |
+ # Remove broken symlinks or symlinks to files which |
102 |
+ # _check_distfile did not match above. |
103 |
+ if distdir_writable and mystat is None or os.path.islink(myfile_path): |
104 |
try: |
105 |
os.unlink(myfile_path) |
106 |
- except OSError: |
107 |
- pass |
108 |
+ except OSError as e: |
109 |
+ if e.errno not in (errno.ENOENT, errno.ESTALE): |
110 |
+ raise |
111 |
+ mystat = None |
112 |
|
113 |
if mystat is not None: |
114 |
if stat.S_ISDIR(mystat.st_mode): |
115 |
@@ -695,10 +705,28 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
116 |
level=logging.ERROR, noiselevel=-1) |
117 |
return 0 |
118 |
|
119 |
+ # Since _check_distfile did not match above, the file |
120 |
+ # is either corrupt or its identity has changed since |
121 |
+ # the last time it was fetched, so rename it. |
122 |
+ temp_filename = \ |
123 |
+ _checksum_failure_temp_file( |
124 |
+ mysettings, mysettings["DISTDIR"], myfile) |
125 |
+ writemsg_stdout(_("Refetching... " |
126 |
+ "File renamed to '%s'\n\n") % \ |
127 |
+ temp_filename, noiselevel=-1) |
128 |
+ |
129 |
+ # Stat the temporary download file for comparison with |
130 |
+ # fetch_resume_size. |
131 |
+ try: |
132 |
+ mystat = os.stat(download_path) |
133 |
+ except OSError: |
134 |
+ mystat = None |
135 |
+ |
136 |
+ if mystat is not None: |
137 |
if mystat.st_size == 0: |
138 |
if distdir_writable: |
139 |
try: |
140 |
- os.unlink(myfile_path) |
141 |
+ os.unlink(download_path) |
142 |
except OSError: |
143 |
pass |
144 |
elif distdir_writable and size is not None: |
145 |
@@ -717,14 +745,14 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
146 |
"ME_MIN_SIZE)\n") % mystat.st_size) |
147 |
temp_filename = \ |
148 |
_checksum_failure_temp_file( |
149 |
- mysettings["DISTDIR"], myfile) |
150 |
+ mysettings, mysettings["DISTDIR"], os.path.basename(download_path)) |
151 |
writemsg_stdout(_("Refetching... " |
152 |
"File renamed to '%s'\n\n") % \ |
153 |
temp_filename, noiselevel=-1) |
154 |
elif mystat.st_size >= size: |
155 |
temp_filename = \ |
156 |
_checksum_failure_temp_file( |
157 |
- mysettings["DISTDIR"], myfile) |
158 |
+ mysettings, mysettings["DISTDIR"], os.path.basename(download_path)) |
159 |
writemsg_stdout(_("Refetching... " |
160 |
"File renamed to '%s'\n\n") % \ |
161 |
temp_filename, noiselevel=-1) |
162 |
@@ -766,7 +794,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
163 |
for mydir in fsmirrors: |
164 |
mirror_file = os.path.join(mydir, myfile) |
165 |
try: |
166 |
- shutil.copyfile(mirror_file, myfile_path) |
167 |
+ shutil.copyfile(mirror_file, download_path) |
168 |
writemsg(_("Local mirror has file: %s\n") % myfile) |
169 |
break |
170 |
except (IOError, OSError) as e: |
171 |
@@ -775,7 +803,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
172 |
del e |
173 |
|
174 |
try: |
175 |
- mystat = os.stat(myfile_path) |
176 |
+ mystat = os.stat(download_path) |
177 |
except OSError as e: |
178 |
if e.errno not in (errno.ENOENT, errno.ESTALE): |
179 |
raise |
180 |
@@ -784,13 +812,13 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
181 |
# Skip permission adjustment for symlinks, since we don't |
182 |
# want to modify anything outside of the primary DISTDIR, |
183 |
# and symlinks typically point to PORTAGE_RO_DISTDIRS. |
184 |
- if not os.path.islink(myfile_path): |
185 |
+ if not os.path.islink(download_path): |
186 |
try: |
187 |
- apply_secpass_permissions(myfile_path, |
188 |
+ apply_secpass_permissions(download_path, |
189 |
gid=portage_gid, mode=0o664, mask=0o2, |
190 |
stat_cached=mystat) |
191 |
except PortageException as e: |
192 |
- if not os.access(myfile_path, os.R_OK): |
193 |
+ if not os.access(download_path, os.R_OK): |
194 |
writemsg(_("!!! Failed to adjust permissions:" |
195 |
" %s\n") % (e,), noiselevel=-1) |
196 |
|
197 |
@@ -799,7 +827,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
198 |
if mystat.st_size == 0: |
199 |
if distdir_writable: |
200 |
try: |
201 |
- os.unlink(myfile_path) |
202 |
+ os.unlink(download_path) |
203 |
except EnvironmentError: |
204 |
pass |
205 |
elif myfile not in mydigests: |
206 |
@@ -824,7 +852,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
207 |
digests = _filter_unaccelarated_hashes(mydigests[myfile]) |
208 |
if hash_filter is not None: |
209 |
digests = _apply_hash_filter(digests, hash_filter) |
210 |
- verified_ok, reason = verify_all(myfile_path, digests) |
211 |
+ verified_ok, reason = verify_all(download_path, digests) |
212 |
if not verified_ok: |
213 |
writemsg(_("!!! Previously fetched" |
214 |
" file: '%s'\n") % myfile, noiselevel=-1) |
215 |
@@ -838,11 +866,12 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
216 |
if distdir_writable: |
217 |
temp_filename = \ |
218 |
_checksum_failure_temp_file( |
219 |
- mysettings["DISTDIR"], myfile) |
220 |
+ mysettings, mysettings["DISTDIR"], os.path.basename(download_path)) |
221 |
writemsg_stdout(_("Refetching... " |
222 |
"File renamed to '%s'\n\n") % \ |
223 |
temp_filename, noiselevel=-1) |
224 |
else: |
225 |
+ _movefile(download_path, myfile_path, mysettings=mysettings) |
226 |
eout = EOutput() |
227 |
eout.quiet = \ |
228 |
mysettings.get("PORTAGE_QUIET", None) == "1" |
229 |
@@ -928,7 +957,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
230 |
if not can_fetch: |
231 |
if fetched != 2: |
232 |
try: |
233 |
- mysize = os.stat(myfile_path).st_size |
234 |
+ mysize = os.stat(download_path).st_size |
235 |
except OSError as e: |
236 |
if e.errno not in (errno.ENOENT, errno.ESTALE): |
237 |
raise |
238 |
@@ -952,7 +981,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
239 |
#we either need to resume or start the download |
240 |
if fetched == 1: |
241 |
try: |
242 |
- mystat = os.stat(myfile_path) |
243 |
+ mystat = os.stat(download_path) |
244 |
except OSError as e: |
245 |
if e.errno not in (errno.ENOENT, errno.ESTALE): |
246 |
raise |
247 |
@@ -964,7 +993,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
248 |
"%d (smaller than " "PORTAGE_FETCH_RESU" |
249 |
"ME_MIN_SIZE)\n") % mystat.st_size) |
250 |
try: |
251 |
- os.unlink(myfile_path) |
252 |
+ os.unlink(download_path) |
253 |
except OSError as e: |
254 |
if e.errno not in \ |
255 |
(errno.ENOENT, errno.ESTALE): |
256 |
@@ -984,7 +1013,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
257 |
_hide_url_passwd(loc)) |
258 |
variables = { |
259 |
"URI": loc, |
260 |
- "FILE": myfile |
261 |
+ "FILE": os.path.basename(download_path) |
262 |
} |
263 |
|
264 |
for k in ("DISTDIR", "PORTAGE_SSH_OPTS"): |
265 |
@@ -1001,12 +1030,12 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
266 |
|
267 |
finally: |
268 |
try: |
269 |
- apply_secpass_permissions(myfile_path, |
270 |
+ apply_secpass_permissions(download_path, |
271 |
gid=portage_gid, mode=0o664, mask=0o2) |
272 |
except FileNotFound: |
273 |
pass |
274 |
except PortageException as e: |
275 |
- if not os.access(myfile_path, os.R_OK): |
276 |
+ if not os.access(download_path, os.R_OK): |
277 |
writemsg(_("!!! Failed to adjust permissions:" |
278 |
" %s\n") % str(e), noiselevel=-1) |
279 |
del e |
280 |
@@ -1015,8 +1044,8 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
281 |
# trust the return value from the fetcher. Remove the |
282 |
# empty file and try to download again. |
283 |
try: |
284 |
- if os.stat(myfile_path).st_size == 0: |
285 |
- os.unlink(myfile_path) |
286 |
+ if os.stat(download_path).st_size == 0: |
287 |
+ os.unlink(download_path) |
288 |
fetched = 0 |
289 |
continue |
290 |
except EnvironmentError: |
291 |
@@ -1024,7 +1053,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
292 |
|
293 |
if mydigests is not None and myfile in mydigests: |
294 |
try: |
295 |
- mystat = os.stat(myfile_path) |
296 |
+ mystat = os.stat(download_path) |
297 |
except OSError as e: |
298 |
if e.errno not in (errno.ENOENT, errno.ESTALE): |
299 |
raise |
300 |
@@ -1065,13 +1094,13 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
301 |
if (mystat[stat.ST_SIZE]<100000) and (len(myfile)>4) and not ((myfile[-5:]==".html") or (myfile[-4:]==".htm")): |
302 |
html404=re.compile("<title>.*(not found|404).*</title>",re.I|re.M) |
303 |
with io.open( |
304 |
- _unicode_encode(myfile_path, |
305 |
+ _unicode_encode(download_path, |
306 |
encoding=_encodings['fs'], errors='strict'), |
307 |
mode='r', encoding=_encodings['content'], errors='replace' |
308 |
) as f: |
309 |
if html404.search(f.read()): |
310 |
try: |
311 |
- os.unlink(mysettings["DISTDIR"]+"/"+myfile) |
312 |
+ os.unlink(download_path) |
313 |
writemsg(_(">>> Deleting invalid distfile. (Improper 404 redirect from server.)\n")) |
314 |
fetched = 0 |
315 |
continue |
316 |
@@ -1087,7 +1116,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
317 |
digests = _filter_unaccelarated_hashes(mydigests[myfile]) |
318 |
if hash_filter is not None: |
319 |
digests = _apply_hash_filter(digests, hash_filter) |
320 |
- verified_ok, reason = verify_all(myfile_path, digests) |
321 |
+ verified_ok, reason = verify_all(download_path, digests) |
322 |
if not verified_ok: |
323 |
writemsg(_("!!! Fetched file: %s VERIFY FAILED!\n") % myfile, |
324 |
noiselevel=-1) |
325 |
@@ -1099,7 +1128,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
326 |
return 0 |
327 |
temp_filename = \ |
328 |
_checksum_failure_temp_file( |
329 |
- mysettings["DISTDIR"], myfile) |
330 |
+ mysettings, mysettings["DISTDIR"], os.path.basename(download_path)) |
331 |
writemsg_stdout(_("Refetching... " |
332 |
"File renamed to '%s'\n\n") % \ |
333 |
temp_filename, noiselevel=-1) |
334 |
@@ -1119,6 +1148,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
335 |
checksum_failure_max_tries: |
336 |
break |
337 |
else: |
338 |
+ _movefile(download_path, myfile_path, mysettings=mysettings) |
339 |
eout = EOutput() |
340 |
eout.quiet = mysettings.get("PORTAGE_QUIET", None) == "1" |
341 |
if digests: |
342 |
@@ -1130,6 +1160,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0, |
343 |
else: |
344 |
if not myret: |
345 |
fetched=2 |
346 |
+ _movefile(download_path, myfile_path, mysettings=mysettings) |
347 |
break |
348 |
elif mydigests!=None: |
349 |
writemsg(_("No digest file available and download failed.\n\n"), |
350 |
diff --git a/lib/portage/tests/ebuild/test_fetch.py b/lib/portage/tests/ebuild/test_fetch.py |
351 |
new file mode 100644 |
352 |
index 000000000..d345f7703 |
353 |
--- /dev/null |
354 |
+++ b/lib/portage/tests/ebuild/test_fetch.py |
355 |
@@ -0,0 +1,186 @@ |
356 |
+# Copyright 2019 Gentoo Authors |
357 |
+# Distributed under the terms of the GNU General Public License v2 |
358 |
+ |
359 |
+from __future__ import unicode_literals |
360 |
+ |
361 |
+import tempfile |
362 |
+ |
363 |
+import portage |
364 |
+from portage import shutil, os |
365 |
+from portage.tests import TestCase |
366 |
+from portage.tests.resolver.ResolverPlayground import ResolverPlayground |
367 |
+from portage.tests.util.test_socks5 import AsyncHTTPServer |
368 |
+from portage.util._async.SchedulerInterface import SchedulerInterface |
369 |
+from portage.util._eventloop.global_event_loop import global_event_loop |
370 |
+from portage.package.ebuild.config import config |
371 |
+from _emerge.EbuildFetcher import EbuildFetcher |
372 |
+from _emerge.Package import Package |
373 |
+ |
374 |
+ |
375 |
+class EbuildFetchTestCase(TestCase): |
376 |
+ |
377 |
+ def testEbuildFetch(self): |
378 |
+ |
379 |
+ distfiles = { |
380 |
+ 'bar': b'bar\n', |
381 |
+ 'foo': b'foo\n', |
382 |
+ } |
383 |
+ |
384 |
+ ebuilds = { |
385 |
+ 'dev-libs/A-1': { |
386 |
+ 'EAPI': '7', |
387 |
+ 'RESTRICT': 'primaryuri', |
388 |
+ 'SRC_URI': '''{scheme}://{host}:{port}/distfiles/bar.txt -> bar |
389 |
+ {scheme}://{host}:{port}/distfiles/foo.txt -> foo''', |
390 |
+ }, |
391 |
+ } |
392 |
+ |
393 |
+ loop = SchedulerInterface(global_event_loop()) |
394 |
+ scheme = 'http' |
395 |
+ host = '127.0.0.1' |
396 |
+ content = {} |
397 |
+ for k, v in distfiles.items(): |
398 |
+ content['/distfiles/{}.txt'.format(k)] = v |
399 |
+ |
400 |
+ with AsyncHTTPServer(host, content, loop) as server: |
401 |
+ ebuilds_subst = {} |
402 |
+ for cpv, metadata in ebuilds.items(): |
403 |
+ metadata = metadata.copy() |
404 |
+ metadata['SRC_URI'] = metadata['SRC_URI'].format( |
405 |
+ scheme=scheme, host=host, port=server.server_port) |
406 |
+ ebuilds_subst[cpv] = metadata |
407 |
+ |
408 |
+ playground = ResolverPlayground(ebuilds=ebuilds_subst, distfiles=distfiles) |
409 |
+ ro_distdir = tempfile.mkdtemp() |
410 |
+ try: |
411 |
+ fetchcommand = portage.util.shlex_split(playground.settings['FETCHCOMMAND']) |
412 |
+ fetch_bin = portage.process.find_binary(fetchcommand[0]) |
413 |
+ if fetch_bin is None: |
414 |
+ self.skipTest('FETCHCOMMAND not found: {}'.format(playground.settings['FETCHCOMMAND'])) |
415 |
+ root_config = playground.trees[playground.eroot]['root_config'] |
416 |
+ portdb = root_config.trees["porttree"].dbapi |
417 |
+ settings = config(clone=playground.settings) |
418 |
+ |
419 |
+ # Tests only work with one ebuild at a time, so the config |
420 |
+ # pool only needs a single config instance. |
421 |
+ class config_pool: |
422 |
+ @staticmethod |
423 |
+ def allocate(): |
424 |
+ return settings |
425 |
+ @staticmethod |
426 |
+ def deallocate(settings): |
427 |
+ pass |
428 |
+ |
429 |
+ def async_fetch(pkg, ebuild_path): |
430 |
+ fetcher = EbuildFetcher(config_pool=config_pool, ebuild_path=ebuild_path, |
431 |
+ fetchonly=False, fetchall=True, pkg=pkg, scheduler=loop) |
432 |
+ fetcher.start() |
433 |
+ return fetcher.async_wait() |
434 |
+ |
435 |
+ for cpv in ebuilds: |
436 |
+ metadata = dict(zip(Package.metadata_keys, |
437 |
+ portdb.aux_get(cpv, Package.metadata_keys))) |
438 |
+ |
439 |
+ pkg = Package(built=False, cpv=cpv, installed=False, |
440 |
+ metadata=metadata, root_config=root_config, |
441 |
+ type_name='ebuild') |
442 |
+ |
443 |
+ settings.setcpv(pkg) |
444 |
+ ebuild_path = portdb.findname(pkg.cpv) |
445 |
+ portage.doebuild_environment(ebuild_path, 'fetch', settings=settings, db=portdb) |
446 |
+ |
447 |
+ # Test good files in DISTDIR |
448 |
+ for k in settings['AA'].split(): |
449 |
+ os.stat(os.path.join(settings['DISTDIR'], k)) |
450 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
451 |
+ for k in settings['AA'].split(): |
452 |
+ with open(os.path.join(settings['DISTDIR'], k), 'rb') as f: |
453 |
+ self.assertEqual(f.read(), distfiles[k]) |
454 |
+ |
455 |
+ # Test missing files in DISTDIR |
456 |
+ for k in settings['AA'].split(): |
457 |
+ os.unlink(os.path.join(settings['DISTDIR'], k)) |
458 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
459 |
+ for k in settings['AA'].split(): |
460 |
+ with open(os.path.join(settings['DISTDIR'], k), 'rb') as f: |
461 |
+ self.assertEqual(f.read(), distfiles[k]) |
462 |
+ |
463 |
+ # Test empty files in DISTDIR |
464 |
+ for k in settings['AA'].split(): |
465 |
+ file_path = os.path.join(settings['DISTDIR'], k) |
466 |
+ with open(file_path, 'wb') as f: |
467 |
+ pass |
468 |
+ self.assertEqual(os.stat(file_path).st_size, 0) |
469 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
470 |
+ for k in settings['AA'].split(): |
471 |
+ with open(os.path.join(settings['DISTDIR'], k), 'rb') as f: |
472 |
+ self.assertEqual(f.read(), distfiles[k]) |
473 |
+ |
474 |
+ # Test non-empty files containing null bytes in DISTDIR |
475 |
+ for k in settings['AA'].split(): |
476 |
+ file_path = os.path.join(settings['DISTDIR'], k) |
477 |
+ with open(file_path, 'wb') as f: |
478 |
+ for i in range(len(distfiles[k])): |
479 |
+ f.write(b'\0') |
480 |
+ self.assertEqual(os.stat(file_path).st_size, len(distfiles[k])) |
481 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
482 |
+ for k in settings['AA'].split(): |
483 |
+ with open(os.path.join(settings['DISTDIR'], k), 'rb') as f: |
484 |
+ self.assertEqual(f.read(), distfiles[k]) |
485 |
+ |
486 |
+ # Test PORTAGE_RO_DISTDIRS |
487 |
+ settings['PORTAGE_RO_DISTDIRS'] = '"{}"'.format(ro_distdir) |
488 |
+ try: |
489 |
+ for k in settings['AA'].split(): |
490 |
+ file_path = os.path.join(settings['DISTDIR'], k) |
491 |
+ os.rename(file_path, os.path.join(ro_distdir, k)) |
492 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
493 |
+ for k in settings['AA'].split(): |
494 |
+ file_path = os.path.join(settings['DISTDIR'], k) |
495 |
+ self.assertTrue(os.path.islink(file_path)) |
496 |
+ with open(file_path, 'rb') as f: |
497 |
+ self.assertEqual(f.read(), distfiles[k]) |
498 |
+ os.unlink(file_path) |
499 |
+ finally: |
500 |
+ settings.pop('PORTAGE_RO_DISTDIRS') |
501 |
+ |
502 |
+ # Test local filesystem in GENTOO_MIRRORS |
503 |
+ orig_mirrors = settings['GENTOO_MIRRORS'] |
504 |
+ try: |
505 |
+ settings['GENTOO_MIRRORS'] = ro_distdir |
506 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
507 |
+ for k in settings['AA'].split(): |
508 |
+ with open(os.path.join(settings['DISTDIR'], k), 'rb') as f: |
509 |
+ self.assertEqual(f.read(), distfiles[k]) |
510 |
+ finally: |
511 |
+ settings['GENTOO_MIRRORS'] = orig_mirrors |
512 |
+ |
513 |
+ # Test readonly DISTDIR |
514 |
+ orig_distdir_mode = os.stat(settings['DISTDIR']).st_mode |
515 |
+ try: |
516 |
+ os.chmod(settings['DISTDIR'], 0o555) |
517 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
518 |
+ for k in settings['AA'].split(): |
519 |
+ with open(os.path.join(settings['DISTDIR'], k), 'rb') as f: |
520 |
+ self.assertEqual(f.read(), distfiles[k]) |
521 |
+ finally: |
522 |
+ os.chmod(settings['DISTDIR'], orig_distdir_mode) |
523 |
+ |
524 |
+ # Test parallel-fetch mode |
525 |
+ settings['PORTAGE_PARALLEL_FETCHONLY'] = '1' |
526 |
+ try: |
527 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
528 |
+ for k in settings['AA'].split(): |
529 |
+ with open(os.path.join(settings['DISTDIR'], k), 'rb') as f: |
530 |
+ self.assertEqual(f.read(), distfiles[k]) |
531 |
+ for k in settings['AA'].split(): |
532 |
+ os.unlink(os.path.join(settings['DISTDIR'], k)) |
533 |
+ self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0) |
534 |
+ for k in settings['AA'].split(): |
535 |
+ with open(os.path.join(settings['DISTDIR'], k), 'rb') as f: |
536 |
+ self.assertEqual(f.read(), distfiles[k]) |
537 |
+ finally: |
538 |
+ settings.pop('PORTAGE_PARALLEL_FETCHONLY') |
539 |
+ finally: |
540 |
+ shutil.rmtree(ro_distdir) |
541 |
+ playground.cleanup() |
542 |
-- |
543 |
2.21.0 |