Clarify manual download required if unable to fetch package (#18242)

Clarify manual download required if unable to fetch (from mirror(s)); support (and tests) for package-specific download instructions
This commit is contained in:
Tamara Dahlgren 2020-09-08 17:15:48 -07:00 committed by GitHub
parent 6b30cd18d6
commit 88749de5c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 11 deletions

View file

@ -577,7 +577,8 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
archive_files = [] archive_files = []
#: Boolean. Set to ``True`` for packages that require a manual download. #: Boolean. Set to ``True`` for packages that require a manual download.
#: This is currently only used by package sanity tests. #: This is currently used by package sanity tests and generation of a
#: more meaningful fetch failure error.
manual_download = False manual_download = False
#: Set of additional options used when fetching package versions. #: Set of additional options used when fetching package versions.
@ -1210,6 +1211,20 @@ def remove_prefix(self):
""" """
spack.store.layout.remove_install_directory(self.spec) spack.store.layout.remove_install_directory(self.spec)
@property
def download_instr(self):
"""
Defines the default manual download instructions. Packages can
override the property to provide more information.
Returns:
(str): default manual download instructions
"""
required = ('Manual download is required for {0}. '
.format(self.spec.name) if self.manual_download else '')
return ('{0}Refer to {1} for download instructions.'
.format(required, self.spec.package.homepage))
def do_fetch(self, mirror_only=False): def do_fetch(self, mirror_only=False):
""" """
Creates a stage directory and downloads the tarball for this package. Creates a stage directory and downloads the tarball for this package.
@ -1244,7 +1259,8 @@ def do_fetch(self, mirror_only=False):
self.spec.format('{name}{@version}'), ck_msg) self.spec.format('{name}{@version}'), ck_msg)
self.stage.create() self.stage.create()
self.stage.fetch(mirror_only) err_msg = None if not self.manual_download else self.download_instr
self.stage.fetch(mirror_only, err_msg=err_msg)
self._fetch_time = time.time() - start_time self._fetch_time = time.time() - start_time
if checksum and self.version in self.versions: if checksum and self.version in self.versions:

View file

@ -400,8 +400,14 @@ def source_path(self):
"""Returns the well-known source directory path.""" """Returns the well-known source directory path."""
return os.path.join(self.path, _source_path_subdir) return os.path.join(self.path, _source_path_subdir)
def fetch(self, mirror_only=False): def fetch(self, mirror_only=False, err_msg=None):
"""Downloads an archive or checks out code from a repository.""" """Retrieves the code or archive
Args:
mirror_only (bool): only fetch from a mirror
err_msg (str or None): the error message to display if all fetchers
fail or ``None`` for the default fetch failure message
"""
fetchers = [] fetchers = []
if not mirror_only: if not mirror_only:
fetchers.append(self.default_fetcher) fetchers.append(self.default_fetcher)
@ -480,9 +486,8 @@ def print_errors(errors):
else: else:
print_errors(errors) print_errors(errors)
err_msg = 'All fetchers failed for {0}'.format(self.name)
self.fetcher = self.default_fetcher self.fetcher = self.default_fetcher
raise fs.FetchError(err_msg, None) raise fs.FetchError(err_msg or 'All fetchers failed', None)
print_errors(errors) print_errors(errors)

View file

@ -560,3 +560,52 @@ def test_macho_make_paths():
'/Users/Shared/spack/pkgB/libB.dylib', '/Users/Shared/spack/pkgB/libB.dylib',
'/usr/local/lib/libloco.dylib': '/usr/local/lib/libloco.dylib':
'/usr/local/lib/libloco.dylib'} '/usr/local/lib/libloco.dylib'}
@pytest.fixture()
def mock_download():
"""Mock a failing download strategy."""
class FailedDownloadStrategy(spack.fetch_strategy.FetchStrategy):
def mirror_id(self):
return None
def fetch(self):
raise spack.fetch_strategy.FailedDownloadError(
"<non-existent URL>", "This FetchStrategy always fails")
fetcher = FetchStrategyComposite()
fetcher.append(FailedDownloadStrategy())
@property
def fake_fn(self):
return fetcher
orig_fn = spack.package.PackageBase.fetcher
spack.package.PackageBase.fetcher = fake_fn
yield
spack.package.PackageBase.fetcher = orig_fn
@pytest.mark.parametrize("manual,instr", [(False, False), (False, True),
(True, False), (True, True)])
@pytest.mark.disable_clean_stage_check
def test_manual_download(install_mockery, mock_download, monkeypatch, manual,
instr):
"""
Ensure expected fetcher fail message based on manual download and instr.
"""
@property
def _instr(pkg):
return 'Download instructions for {0}'.format(pkg.spec.name)
spec = Spec('a').concretized()
pkg = spec.package
pkg.manual_download = manual
if instr:
monkeypatch.setattr(spack.package.PackageBase, 'download_instr',
_instr)
expected = pkg.download_instr if manual else 'All fetchers failed'
with pytest.raises(spack.fetch_strategy.FetchError, match=expected):
pkg.do_fetch()

View file

@ -529,15 +529,21 @@ def test_no_search_mirror_only(
pass pass
check_destroy(stage, self.stage_name) check_destroy(stage, self.stage_name)
def test_search_if_default_fails(self, failing_fetch_strategy, search_fn): @pytest.mark.parametrize(
"err_msg,expected", [('Fetch from fetch.test.com',
'Fetch from fetch.test.com'),
(None, 'All fetchers failed')])
def test_search_if_default_fails(self, failing_fetch_strategy, search_fn,
err_msg, expected):
stage = Stage(failing_fetch_strategy, stage = Stage(failing_fetch_strategy,
name=self.stage_name, name=self.stage_name,
search_fn=search_fn) search_fn=search_fn)
with stage: with stage:
try: with pytest.raises(spack.fetch_strategy.FetchError,
stage.fetch(mirror_only=False) match=expected):
except spack.fetch_strategy.FetchError: stage.fetch(mirror_only=False, err_msg=err_msg)
pass
check_destroy(stage, self.stage_name) check_destroy(stage, self.stage_name)
assert search_fn.performed_search assert search_fn.performed_search