public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-commits] proj/portage:master commit in: lib/portage/package/ebuild/, lib/portage/tests/ebuild/, lib/_emerge/
@ 2019-04-30  2:47 Zac Medico
  0 siblings, 0 replies; only message in thread
From: Zac Medico @ 2019-04-30  2:47 UTC (permalink / raw
  To: gentoo-commits

commit:     ebbde237d33e783c562cc6c70987969ac7228b96
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Sat Apr 27 21:59:57 2019 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Tue Apr 30 02:28:21 2019 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=ebbde237

fetch: atomic downloads (bug 175612)

Direct FETCHCOMMAND/RESUMECOMMAND output to a temporary file with
a constant .__download__ suffix, and atomically rename the file
to remove the suffix only after the download has completed
successfully (includes digest verification when applicable).
Also add unit tests to cover most fetch cases.

Bug: https://bugs.gentoo.org/175612
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/BinpkgVerifier.py          |   4 +-
 lib/portage/package/ebuild/fetch.py    | 105 ++++++++++-----
 lib/portage/tests/ebuild/test_fetch.py | 230 +++++++++++++++++++++++++++++++++
 3 files changed, 303 insertions(+), 36 deletions(-)

diff --git a/lib/_emerge/BinpkgVerifier.py b/lib/_emerge/BinpkgVerifier.py
index 7a6d15e80..f98f511a8 100644
--- a/lib/_emerge/BinpkgVerifier.py
+++ b/lib/_emerge/BinpkgVerifier.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2013 Gentoo Foundation
+# Copyright 1999-2019 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import errno
@@ -108,7 +108,7 @@ class BinpkgVerifier(CompositeTask):
 	def _digest_exception(self, name, value, expected):
 
 		head, tail = os.path.split(self._pkg_path)
-		temp_filename = _checksum_failure_temp_file(head, tail)
+		temp_filename = _checksum_failure_temp_file(self.pkg.root_config.settings, head, tail)
 
 		self.scheduler.output((
 			"\n!!! Digest verification failed:\n"

diff --git a/lib/portage/package/ebuild/fetch.py b/lib/portage/package/ebuild/fetch.py
index bfd97601c..227bf45ae 100644
--- a/lib/portage/package/ebuild/fetch.py
+++ b/lib/portage/package/ebuild/fetch.py
@@ -30,7 +30,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
 )
 
 from portage import os, selinux, shutil, _encodings, \
-	_shell_quote, _unicode_encode
+	_movefile, _shell_quote, _unicode_encode
 from portage.checksum import (get_valid_checksum_keys, perform_md5, verify_all,
 	_filter_unaccelarated_hashes, _hash_filter, _apply_hash_filter)
 from portage.const import BASH_BINARY, CUSTOM_MIRRORS_FILE, \
@@ -46,6 +46,8 @@ from portage.util import apply_recursive_permissions, \
 	varexpand, writemsg, writemsg_level, writemsg_stdout
 from portage.process import spawn
 
+_download_suffix = '.__download__'
+
 _userpriv_spawn_kwargs = (
 	("uid",    portage_uid),
 	("gid",    portage_gid),
@@ -139,7 +141,7 @@ def _userpriv_test_write_file(settings, file_path):
 	_userpriv_test_write_file_cache[file_path] = rval
 	return rval
 
-def _checksum_failure_temp_file(distdir, basename):
+def _checksum_failure_temp_file(settings, distdir, basename):
 	"""
 	First try to find a duplicate temp file with the same checksum and return
 	that filename if available. Otherwise, use mkstemp to create a new unique
@@ -149,9 +151,13 @@ def _checksum_failure_temp_file(distdir, basename):
 	"""
 
 	filename = os.path.join(distdir, basename)
+	if basename.endswith(_download_suffix):
+		normal_basename = basename[:-len(_download_suffix)]
+	else:
+		normal_basename = basename
 	size = os.stat(filename).st_size
 	checksum = None
-	tempfile_re = re.compile(re.escape(basename) + r'\._checksum_failure_\..*')
+	tempfile_re = re.compile(re.escape(normal_basename) + r'\._checksum_failure_\..*')
 	for temp_filename in os.listdir(distdir):
 		if not tempfile_re.match(temp_filename):
 			continue
@@ -173,9 +179,9 @@ def _checksum_failure_temp_file(distdir, basename):
 			return temp_filename
 
 	fd, temp_filename = \
-		tempfile.mkstemp("", basename + "._checksum_failure_.", distdir)
+		tempfile.mkstemp("", normal_basename + "._checksum_failure_.", distdir)
 	os.close(fd)
-	os.rename(filename, temp_filename)
+	_movefile(filename, temp_filename, mysettings=settings)
 	return temp_filename
 
 def _check_digests(filename, digests, show_errors=1):
@@ -602,6 +608,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 				pruned_digests["size"] = size
 
 		myfile_path = os.path.join(mysettings["DISTDIR"], myfile)
+		download_path = myfile_path + _download_suffix
 		has_space = True
 		has_space_superuser = True
 		file_lock = None
@@ -679,12 +686,15 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 							del e
 					continue
 
-				if distdir_writable and mystat is None:
-					# Remove broken symlinks if necessary.
+				# Remove broken symlinks or symlinks to files which
+				# _check_distfile did not match above.
+				if distdir_writable and mystat is None or os.path.islink(myfile_path):
 					try:
 						os.unlink(myfile_path)
-					except OSError:
-						pass
+					except OSError as e:
+						if e.errno not in (errno.ENOENT, errno.ESTALE):
+							raise
+					mystat = None
 
 				if mystat is not None:
 					if stat.S_ISDIR(mystat.st_mode):
@@ -695,10 +705,30 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 							level=logging.ERROR, noiselevel=-1)
 						return 0
 
+					if distdir_writable:
+						# Since _check_distfile did not match above, the file
+						# is either corrupt or its identity has changed since
+						# the last time it was fetched, so rename it.
+						temp_filename = _checksum_failure_temp_file(
+							mysettings, mysettings["DISTDIR"], myfile)
+						writemsg_stdout(_("Refetching... "
+							"File renamed to '%s'\n\n") % \
+							temp_filename, noiselevel=-1)
+
+				# Stat the temporary download file for comparison with
+				# fetch_resume_size.
+				try:
+					mystat = os.stat(download_path)
+				except OSError as e:
+					if e.errno not in (errno.ENOENT, errno.ESTALE):
+						raise
+					mystat = None
+
+				if mystat is not None:
 					if mystat.st_size == 0:
 						if distdir_writable:
 							try:
-								os.unlink(myfile_path)
+								os.unlink(download_path)
 							except OSError:
 								pass
 					elif distdir_writable and size is not None:
@@ -717,14 +747,16 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 								"ME_MIN_SIZE)\n") % mystat.st_size)
 							temp_filename = \
 								_checksum_failure_temp_file(
-								mysettings["DISTDIR"], myfile)
+									mysettings, mysettings["DISTDIR"],
+									os.path.basename(download_path))
 							writemsg_stdout(_("Refetching... "
 								"File renamed to '%s'\n\n") % \
 								temp_filename, noiselevel=-1)
 						elif mystat.st_size >= size:
 							temp_filename = \
 								_checksum_failure_temp_file(
-								mysettings["DISTDIR"], myfile)
+									mysettings, mysettings["DISTDIR"],
+									os.path.basename(download_path))
 							writemsg_stdout(_("Refetching... "
 								"File renamed to '%s'\n\n") % \
 								temp_filename, noiselevel=-1)
@@ -766,7 +798,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 					for mydir in fsmirrors:
 						mirror_file = os.path.join(mydir, myfile)
 						try:
-							shutil.copyfile(mirror_file, myfile_path)
+							shutil.copyfile(mirror_file, download_path)
 							writemsg(_("Local mirror has file: %s\n") % myfile)
 							break
 						except (IOError, OSError) as e:
@@ -775,7 +807,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 							del e
 
 				try:
-					mystat = os.stat(myfile_path)
+					mystat = os.stat(download_path)
 				except OSError as e:
 					if e.errno not in (errno.ENOENT, errno.ESTALE):
 						raise
@@ -784,13 +816,13 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 					# Skip permission adjustment for symlinks, since we don't
 					# want to modify anything outside of the primary DISTDIR,
 					# and symlinks typically point to PORTAGE_RO_DISTDIRS.
-					if not os.path.islink(myfile_path):
+					if not os.path.islink(download_path):
 						try:
-							apply_secpass_permissions(myfile_path,
+							apply_secpass_permissions(download_path,
 								gid=portage_gid, mode=0o664, mask=0o2,
 								stat_cached=mystat)
 						except PortageException as e:
-							if not os.access(myfile_path, os.R_OK):
+							if not os.access(download_path, os.R_OK):
 								writemsg(_("!!! Failed to adjust permissions:"
 									" %s\n") % (e,), noiselevel=-1)
 
@@ -799,7 +831,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 					if mystat.st_size == 0:
 						if distdir_writable:
 							try:
-								os.unlink(myfile_path)
+								os.unlink(download_path)
 							except EnvironmentError:
 								pass
 					elif myfile not in mydigests:
@@ -824,7 +856,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 							digests = _filter_unaccelarated_hashes(mydigests[myfile])
 							if hash_filter is not None:
 								digests = _apply_hash_filter(digests, hash_filter)
-							verified_ok, reason = verify_all(myfile_path, digests)
+							verified_ok, reason = verify_all(download_path, digests)
 							if not verified_ok:
 								writemsg(_("!!! Previously fetched"
 									" file: '%s'\n") % myfile, noiselevel=-1)
@@ -838,11 +870,13 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 								if distdir_writable:
 									temp_filename = \
 										_checksum_failure_temp_file(
-										mysettings["DISTDIR"], myfile)
+											mysettings, mysettings["DISTDIR"],
+											os.path.basename(download_path))
 									writemsg_stdout(_("Refetching... "
 										"File renamed to '%s'\n\n") % \
 										temp_filename, noiselevel=-1)
 							else:
+								_movefile(download_path, myfile_path, mysettings=mysettings)
 								eout = EOutput()
 								eout.quiet = \
 									mysettings.get("PORTAGE_QUIET", None) == "1"
@@ -928,7 +962,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 				if not can_fetch:
 					if fetched != 2:
 						try:
-							mysize = os.stat(myfile_path).st_size
+							mysize = os.stat(download_path).st_size
 						except OSError as e:
 							if e.errno not in (errno.ENOENT, errno.ESTALE):
 								raise
@@ -952,7 +986,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 					#we either need to resume or start the download
 					if fetched == 1:
 						try:
-							mystat = os.stat(myfile_path)
+							mystat = os.stat(download_path)
 						except OSError as e:
 							if e.errno not in (errno.ENOENT, errno.ESTALE):
 								raise
@@ -964,7 +998,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 									"%d (smaller than " "PORTAGE_FETCH_RESU"
 									"ME_MIN_SIZE)\n") % mystat.st_size)
 								try:
-									os.unlink(myfile_path)
+									os.unlink(download_path)
 								except OSError as e:
 									if e.errno not in \
 										(errno.ENOENT, errno.ESTALE):
@@ -984,7 +1018,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 						_hide_url_passwd(loc))
 					variables = {
 						"URI":     loc,
-						"FILE":    myfile
+						"FILE":    os.path.basename(download_path)
 					}
 
 					for k in ("DISTDIR", "PORTAGE_SSH_OPTS"):
@@ -1001,12 +1035,12 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 
 					finally:
 						try:
-							apply_secpass_permissions(myfile_path,
+							apply_secpass_permissions(download_path,
 								gid=portage_gid, mode=0o664, mask=0o2)
 						except FileNotFound:
 							pass
 						except PortageException as e:
-							if not os.access(myfile_path, os.R_OK):
+							if not os.access(download_path, os.R_OK):
 								writemsg(_("!!! Failed to adjust permissions:"
 									" %s\n") % str(e), noiselevel=-1)
 							del e
@@ -1015,8 +1049,8 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 					# trust the return value from the fetcher.  Remove the
 					# empty file and try to download again.
 					try:
-						if os.stat(myfile_path).st_size == 0:
-							os.unlink(myfile_path)
+						if os.stat(download_path).st_size == 0:
+							os.unlink(download_path)
 							fetched = 0
 							continue
 					except EnvironmentError:
@@ -1024,7 +1058,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 
 					if mydigests is not None and myfile in mydigests:
 						try:
-							mystat = os.stat(myfile_path)
+							mystat = os.stat(download_path)
 						except OSError as e:
 							if e.errno not in (errno.ENOENT, errno.ESTALE):
 								raise
@@ -1065,13 +1099,13 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 								if (mystat[stat.ST_SIZE]<100000) and (len(myfile)>4) and not ((myfile[-5:]==".html") or (myfile[-4:]==".htm")):
 									html404=re.compile("<title>.*(not found|404).*</title>",re.I|re.M)
 									with io.open(
-										_unicode_encode(myfile_path,
+										_unicode_encode(download_path,
 										encoding=_encodings['fs'], errors='strict'),
 										mode='r', encoding=_encodings['content'], errors='replace'
 										) as f:
 										if html404.search(f.read()):
 											try:
-												os.unlink(mysettings["DISTDIR"]+"/"+myfile)
+												os.unlink(download_path)
 												writemsg(_(">>> Deleting invalid distfile. (Improper 404 redirect from server.)\n"))
 												fetched = 0
 												continue
@@ -1087,7 +1121,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 								digests = _filter_unaccelarated_hashes(mydigests[myfile])
 								if hash_filter is not None:
 									digests = _apply_hash_filter(digests, hash_filter)
-								verified_ok, reason = verify_all(myfile_path, digests)
+								verified_ok, reason = verify_all(download_path, digests)
 								if not verified_ok:
 									writemsg(_("!!! Fetched file: %s VERIFY FAILED!\n") % myfile,
 										noiselevel=-1)
@@ -1099,7 +1133,8 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 										return 0
 									temp_filename = \
 										_checksum_failure_temp_file(
-										mysettings["DISTDIR"], myfile)
+											mysettings, mysettings["DISTDIR"],
+											os.path.basename(download_path))
 									writemsg_stdout(_("Refetching... "
 										"File renamed to '%s'\n\n") % \
 										temp_filename, noiselevel=-1)
@@ -1119,6 +1154,7 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 										checksum_failure_max_tries:
 										break
 								else:
+									_movefile(download_path, myfile_path, mysettings=mysettings)
 									eout = EOutput()
 									eout.quiet = mysettings.get("PORTAGE_QUIET", None) == "1"
 									if digests:
@@ -1127,8 +1163,9 @@ def fetch(myuris, mysettings, listonly=0, fetchonly=0,
 										eout.eend(0)
 									fetched=2
 									break
-					else:
+					else: # no digests available
 						if not myret:
+							_movefile(download_path, myfile_path, mysettings=mysettings)
 							fetched=2
 							break
 						elif mydigests!=None:

diff --git a/lib/portage/tests/ebuild/test_fetch.py b/lib/portage/tests/ebuild/test_fetch.py
new file mode 100644
index 000000000..83321fed7
--- /dev/null
+++ b/lib/portage/tests/ebuild/test_fetch.py
@@ -0,0 +1,230 @@
+# Copyright 2019 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+from __future__ import unicode_literals
+
+import functools
+import tempfile
+
+import portage
+from portage import shutil, os
+from portage.tests import TestCase
+from portage.tests.resolver.ResolverPlayground import ResolverPlayground
+from portage.tests.util.test_socks5 import AsyncHTTPServer
+from portage.util.futures.executor.fork import ForkExecutor
+from portage.util._async.SchedulerInterface import SchedulerInterface
+from portage.util._eventloop.global_event_loop import global_event_loop
+from portage.package.ebuild.config import config
+from portage.package.ebuild.digestgen import digestgen
+from portage.package.ebuild.fetch import _download_suffix
+from _emerge.EbuildFetcher import EbuildFetcher
+from _emerge.Package import Package
+
+
+class EbuildFetchTestCase(TestCase):
+
+	def testEbuildFetch(self):
+
+		distfiles = {
+			'bar': b'bar\n',
+			'foo': b'foo\n',
+		}
+
+		ebuilds = {
+			'dev-libs/A-1': {
+				'EAPI': '7',
+				'RESTRICT': 'primaryuri',
+				'SRC_URI': '''{scheme}://{host}:{port}/distfiles/bar.txt -> bar
+					{scheme}://{host}:{port}/distfiles/foo.txt -> foo''',
+			},
+		}
+
+		loop = SchedulerInterface(global_event_loop())
+		scheme = 'http'
+		host = '127.0.0.1'
+		content = {}
+		for k, v in distfiles.items():
+			content['/distfiles/{}.txt'.format(k)] = v
+
+		with AsyncHTTPServer(host, content, loop) as server:
+			ebuilds_subst = {}
+			for cpv, metadata in ebuilds.items():
+				metadata = metadata.copy()
+				metadata['SRC_URI'] = metadata['SRC_URI'].format(
+					scheme=scheme, host=host, port=server.server_port)
+				ebuilds_subst[cpv] = metadata
+
+			playground = ResolverPlayground(ebuilds=ebuilds_subst, distfiles=distfiles)
+			ro_distdir = tempfile.mkdtemp()
+			try:
+				fetchcommand = portage.util.shlex_split(playground.settings['FETCHCOMMAND'])
+				fetch_bin = portage.process.find_binary(fetchcommand[0])
+				if fetch_bin is None:
+					self.skipTest('FETCHCOMMAND not found: {}'.format(playground.settings['FETCHCOMMAND']))
+				resumecommand = portage.util.shlex_split(playground.settings['RESUMECOMMAND'])
+				resume_bin = portage.process.find_binary(resumecommand[0])
+				if resume_bin is None:
+					self.skipTest('RESUMECOMMAND not found: {}'.format(playground.settings['RESUMECOMMAND']))
+				root_config = playground.trees[playground.eroot]['root_config']
+				portdb = root_config.trees["porttree"].dbapi
+				settings = config(clone=playground.settings)
+
+				# Tests only work with one ebuild at a time, so the config
+				# pool only needs a single config instance.
+				class config_pool:
+					@staticmethod
+					def allocate():
+						return settings
+					@staticmethod
+					def deallocate(settings):
+						pass
+
+				def async_fetch(pkg, ebuild_path):
+					fetcher = EbuildFetcher(config_pool=config_pool, ebuild_path=ebuild_path,
+						fetchonly=False, fetchall=True, pkg=pkg, scheduler=loop)
+					fetcher.start()
+					return fetcher.async_wait()
+
+				for cpv in ebuilds:
+					metadata = dict(zip(Package.metadata_keys,
+						portdb.aux_get(cpv, Package.metadata_keys)))
+
+					pkg = Package(built=False, cpv=cpv, installed=False,
+						metadata=metadata, root_config=root_config,
+						type_name='ebuild')
+
+					settings.setcpv(pkg)
+					ebuild_path = portdb.findname(pkg.cpv)
+					portage.doebuild_environment(ebuild_path, 'fetch', settings=settings, db=portdb)
+
+					# Test good files in DISTDIR
+					for k in settings['AA'].split():
+						os.stat(os.path.join(settings['DISTDIR'], k))
+					self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+					for k in settings['AA'].split():
+						with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+							self.assertEqual(f.read(), distfiles[k])
+
+					# Test digestgen with fetch
+					os.unlink(os.path.join(os.path.dirname(ebuild_path), 'Manifest'))
+					for k in settings['AA'].split():
+						os.unlink(os.path.join(settings['DISTDIR'], k))
+					with ForkExecutor(loop=loop) as executor:
+						self.assertTrue(bool(loop.run_until_complete(
+							loop.run_in_executor(executor, functools.partial(
+								digestgen, mysettings=settings, myportdb=portdb)))))
+					for k in settings['AA'].split():
+						with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+							self.assertEqual(f.read(), distfiles[k])
+
+					# Test missing files in DISTDIR
+					for k in settings['AA'].split():
+						os.unlink(os.path.join(settings['DISTDIR'], k))
+					self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+					for k in settings['AA'].split():
+						with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+							self.assertEqual(f.read(), distfiles[k])
+
+					# Test empty files in DISTDIR
+					for k in settings['AA'].split():
+						file_path = os.path.join(settings['DISTDIR'], k)
+						with open(file_path, 'wb') as f:
+							pass
+						self.assertEqual(os.stat(file_path).st_size, 0)
+					self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+					for k in settings['AA'].split():
+						with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+							self.assertEqual(f.read(), distfiles[k])
+
+					# Test non-empty files containing null bytes in DISTDIR
+					for k in settings['AA'].split():
+						file_path = os.path.join(settings['DISTDIR'], k)
+						with open(file_path, 'wb') as f:
+							f.write(len(distfiles[k]) * b'\0')
+						self.assertEqual(os.stat(file_path).st_size, len(distfiles[k]))
+					self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+					for k in settings['AA'].split():
+						with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+							self.assertEqual(f.read(), distfiles[k])
+
+					# Test PORTAGE_RO_DISTDIRS
+					settings['PORTAGE_RO_DISTDIRS'] = '"{}"'.format(ro_distdir)
+					orig_fetchcommand = settings['FETCHCOMMAND']
+					orig_resumecommand = settings['RESUMECOMMAND']
+					try:
+						settings['FETCHCOMMAND'] = settings['RESUMECOMMAND'] = ''
+						for k in settings['AA'].split():
+							file_path = os.path.join(settings['DISTDIR'], k)
+							os.rename(file_path, os.path.join(ro_distdir, k))
+						self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+						for k in settings['AA'].split():
+							file_path = os.path.join(settings['DISTDIR'], k)
+							self.assertTrue(os.path.islink(file_path))
+							with open(file_path, 'rb') as f:
+								self.assertEqual(f.read(), distfiles[k])
+							os.unlink(file_path)
+					finally:
+						settings.pop('PORTAGE_RO_DISTDIRS')
+						settings['FETCHCOMMAND'] = orig_fetchcommand
+						settings['RESUMECOMMAND'] = orig_resumecommand
+
+					# Test local filesystem in GENTOO_MIRRORS
+					orig_mirrors = settings['GENTOO_MIRRORS']
+					orig_fetchcommand = settings['FETCHCOMMAND']
+					try:
+						settings['GENTOO_MIRRORS'] = ro_distdir
+						settings['FETCHCOMMAND'] = settings['RESUMECOMMAND'] = ''
+						self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+						for k in settings['AA'].split():
+							with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+								self.assertEqual(f.read(), distfiles[k])
+					finally:
+						settings['GENTOO_MIRRORS'] = orig_mirrors
+						settings['FETCHCOMMAND'] = orig_fetchcommand
+						settings['RESUMECOMMAND'] = orig_resumecommand
+
+					# Test readonly DISTDIR
+					orig_distdir_mode = os.stat(settings['DISTDIR']).st_mode
+					try:
+						os.chmod(settings['DISTDIR'], 0o555)
+						self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+						for k in settings['AA'].split():
+							with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+								self.assertEqual(f.read(), distfiles[k])
+					finally:
+						os.chmod(settings['DISTDIR'], orig_distdir_mode)
+
+					# Test parallel-fetch mode
+					settings['PORTAGE_PARALLEL_FETCHONLY'] = '1'
+					try:
+						self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+						for k in settings['AA'].split():
+							with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+								self.assertEqual(f.read(), distfiles[k])
+						for k in settings['AA'].split():
+							os.unlink(os.path.join(settings['DISTDIR'], k))
+						self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+						for k in settings['AA'].split():
+							with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+								self.assertEqual(f.read(), distfiles[k])
+					finally:
+						settings.pop('PORTAGE_PARALLEL_FETCHONLY')
+
+					# Test RESUMECOMMAND
+					orig_resume_min_size = settings['PORTAGE_FETCH_RESUME_MIN_SIZE']
+					try:
+						settings['PORTAGE_FETCH_RESUME_MIN_SIZE'] = '2'
+						for k in settings['AA'].split():
+							file_path = os.path.join(settings['DISTDIR'], k)
+							os.unlink(file_path)
+							with open(file_path + _download_suffix, 'wb') as f:
+								f.write(distfiles[k][:2])
+						self.assertEqual(loop.run_until_complete(async_fetch(pkg, ebuild_path)), 0)
+						for k in settings['AA'].split():
+							with open(os.path.join(settings['DISTDIR'], k), 'rb') as f:
+								self.assertEqual(f.read(), distfiles[k])
+					finally:
+						settings['PORTAGE_FETCH_RESUME_MIN_SIZE'] = orig_resume_min_size
+			finally:
+				shutil.rmtree(ro_distdir)
+				playground.cleanup()


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2019-04-30  2:47 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-04-30  2:47 [gentoo-commits] proj/portage:master commit in: lib/portage/package/ebuild/, lib/portage/tests/ebuild/, lib/_emerge/ Zac Medico

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox