Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
Merge branch 'u/soehms/hide_features_34185' of trac.sagemath.org:sage…
Browse files Browse the repository at this point in the history
… into hide_features_34185
  • Loading branch information
soehms committed Aug 3, 2022
2 parents cd1e2b1 + b4b562c commit ddd7ada
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 9 deletions.
4 changes: 4 additions & 0 deletions src/bin/sage-runtests
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ if __name__ == "__main__":
'if set to "all", then all tests will be run; '
'use "!FEATURE" to disable tests marked "# optional - FEATURE". '
'Note that "!" needs to be quoted or escaped in the shell.')
parser.add_argument("--hide", metavar="FEATURES", default="",
help='run tests pretending that the software listed in FEATURES (separated by commas) is not installed; '
'if "all" is listed, will also hide features corresponding to all non standard packages; '
'if "optional" is listed, will also hide features corresponding to optional packages.')
parser.add_argument("--randorder", type=int, metavar="SEED", help="randomize order of tests")
parser.add_argument("--random-seed", dest="random_seed", type=int, metavar="SEED", help="random seed (integer) for fuzzing doctests",
default=os.environ.get("SAGE_DOCTEST_RANDOM_SEED"))
Expand Down
120 changes: 119 additions & 1 deletion src/sage/doctest/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
except ImportError:
pass


class DocTestDefaults(SageObject):
"""
This class is used for doctesting the Sage doctest module.
Expand Down Expand Up @@ -137,6 +136,7 @@ def __init__(self, **kwds):
# displaying user-defined optional tags and we don't want to see
# the auto_optional_tags there.
self.optional = set(['sage']) | auto_optional_tags
self.hide = ''

# > 0: always run GC before every test
# < 0: disable GC
Expand Down Expand Up @@ -400,6 +400,28 @@ def __init__(self, options, args):
if options.verbose:
options.show_skipped = True

options.hidden_features = set()
if isinstance(options.hide, str):
if not len(options.hide):
options.hide = set([])
else:
s = options.hide.lower()
options.hide = set(s.split(','))
for h in options.hide:
if not optionaltag_regex.search(h):
raise ValueError('invalid optional tag {!r}'.format(h))
if 'all' in options.hide:
options.hide.discard('all')
from sage.features.all import all_features
feature_names = set([f.name for f in all_features() if not f.is_standard()])
options.hide = options.hide.union(feature_names)
if 'optional' in options.hide:
options.hide.discard('optional')
from sage.features.all import all_features
feature_names = set([f.name for f in all_features() if f.is_optional()])
options.hide = options.hide.union(feature_names)


options.disabled_optional = set()
if isinstance(options.optional, str):
s = options.optional.lower()
Expand All @@ -416,6 +438,8 @@ def __init__(self, options, args):
options.optional.discard('optional')
from sage.misc.package import list_packages
for pkg in list_packages('optional', local=True).values():
if pkg.name in options.hide:
continue
if pkg.is_installed() and pkg.installed_version == pkg.remote_version:
options.optional.add(pkg.name)

Expand Down Expand Up @@ -1328,6 +1352,49 @@ def run(self):
Features detected...
0
We test the ``--hide`` option (:trac:`34185`):
sage: from sage.doctest.control import test_hide
sage: filename = tmp_filename(ext='.py')
sage: with open(filename, 'w') as f:
....: f.write(test_hide)
....: f.close()
729
sage: DF = DocTestDefaults(hide='buckygen,all')
sage: DC = DocTestController(DF, [filename])
sage: DC.run()
Running doctests with ID ...
Using --optional=sage
Features to be detected: ...
Doctesting 1 file.
sage -t ....py
[4 tests, ... s]
----------------------------------------------------------------------
All tests passed!
----------------------------------------------------------------------
Total time for all tests: ... seconds
cpu time: ... seconds
cumulative wall time: ... seconds
Features detected...
0
sage: DF = DocTestDefaults(hide='benzene,optional')
sage: DC = DocTestController(DF, [filename])
sage: DC.run()
Running doctests with ID ...
Using --optional=sage
Features to be detected: ...
Doctesting 1 file.
sage -t ....py
[4 tests, ... s]
----------------------------------------------------------------------
All tests passed!
----------------------------------------------------------------------
Total time for all tests: ... seconds
cpu time: ... seconds
cumulative wall time: ... seconds
Features detected...
0
"""
opt = self.options
L = (opt.gdb, opt.lldb, opt.valgrind, opt.massif, opt.cachegrind, opt.omega)
Expand Down Expand Up @@ -1368,6 +1435,21 @@ def run(self):

self.log("Using --optional=" + self._optional_tags_string())
available_software._allow_external = self.options.optional is True or 'external' in self.options.optional

for h in self.options.hide:
try:
i = available_software._indices[h]
except KeyError:
pass
else:
f = available_software._features[i]
if f.is_present():
f.hide()
self.options.hidden_features.add(f)
for g in f.joined_features():
if g.name in self.options.optional:
self.options.optional.discard(g.name)

for o in self.options.disabled_optional:
try:
i = available_software._indices[o]
Expand All @@ -1377,12 +1459,17 @@ def run(self):
available_software._seen[i] = -1

self.log("Features to be detected: " + ','.join(available_software.detectable()))
if self.options.hidden_features:
self.log("Hidden features: " + ','.join([f.name for f in self.options.hidden_features]))
self.add_files()
self.expand_files_into_sources()
self.filter_sources()
self.sort_sources()
self.run_doctests()

for f in self.options.hidden_features:
f.unhide()

self.log("Features detected for doctesting: "
+ ','.join(available_software.seen()))
self.cleanup()
Expand Down Expand Up @@ -1463,3 +1550,34 @@ def stringify(x):
if not save_dtmode and IP is not None:
IP.run_line_magic('colors', old_color)
IP.config.TerminalInteractiveShell.colors = old_config_color


###############################################################################
# Declaration of doctest strings
###############################################################################

test_hide=r"""{}
sage: next(graphs.fullerenes(20))
Traceback (most recent call last):
...
FeatureNotPresentError: buckygen is not available.
...
sage: next(graphs.fullerenes(20)) # optional buckygen
Graph on 20 vertices
sage: len(list(graphs.fusenes(2)))
Traceback (most recent call last):
...
FeatureNotPresentError: benzene is not available.
...
sage: len(list(graphs.fusenes(2))) # optional benzene
1
sage: from sage.matrix.matrix_space import get_matrix_class
sage: get_matrix_class(GF(25,'x'), 4, 4, False, 'meataxe')
Failed lazy import:
sage.matrix.matrix_gfpn_dense is not available.
...
sage: get_matrix_class(GF(25,'x'), 4, 4, False, 'meataxe') # optional meataxe
<class 'sage.matrix.matrix_gfpn_dense.Matrix_gfpn_dense'>
{}
""".format('r"""', '"""')
129 changes: 129 additions & 0 deletions src/sage/features/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def __init__(self, name, spkg=None, url=None, description=None):

self._cache_is_present = None
self._cache_resolution = None
self._hidden = False

def is_present(self):
r"""
Expand Down Expand Up @@ -173,6 +174,8 @@ def is_present(self):
sage: TestFeature("other").is_present()
FeatureTestResult('other', True)
"""
if self._hidden:
return FeatureTestResult(self, False, reason="Feature `{name}` is hidden.".format(name=self.name))
# We do not use @cached_method here because we wish to use
# Feature early in the build system of sagelib.
if self._cache_is_present is None:
Expand Down Expand Up @@ -225,6 +228,25 @@ def __repr__(self):
description = f'{self.name!r}: {self.description}' if self.description else f'{self.name!r}'
return f'Feature({description})'

def _spkg_type(self):
r"""
Return the type of the SPKG corresponding to this feature.
EXAMPLES::
sage: from sage.features.databases import DatabaseCremona
sage: DatabaseCremona()._spkg_type()
'optional'
OUTPUT:
The type as a string in ``('base', 'standard', 'optional', 'experimental')``.
If no SPKG corresponds to this feature ``None`` is returned.
"""
from sage.misc.package import _spkg_type
return _spkg_type(self.name)
spkg_type = None

def resolution(self):
r"""
Return a suggestion on how to make :meth:`is_present` pass if it did not
Expand All @@ -240,6 +262,8 @@ def resolution(self):
sage: Executable(name="CSDP", spkg="csdp", executable="theta", url="https://github.com/dimpase/csdp").resolution() # optional - sage_spkg
'...To install CSDP...you can try to run...sage -i csdp...Further installation instructions might be available at https://github.com/dimpase/csdp.'
"""
if self._hidden:
return "Use method `unhide` to make it available again."
if self._cache_resolution is not None:
return self._cache_resolution
lines = []
Expand All @@ -251,6 +275,111 @@ def resolution(self):
self._cache_resolution = "\n".join(lines)
return self._cache_resolution

def joined_features(self):
r"""
Return a list of features joined with ``self``.
OUTPUT:
A (possibly empty) list of instances of :class:`Feature`.
EXAMPLES::
sage: from sage.features.graphviz import Graphviz
sage: Graphviz().joined_features()
[Feature('dot'), Feature('neato'), Feature('twopi')]
sage: from sage.features.interfaces import Mathematica
sage: Mathematica().joined_features()
[]
"""
from sage.features.join_feature import JoinFeature
if isinstance(self, JoinFeature):
return self._features
return []

def is_standard(self):
r"""
Return whether this feature corresponds to a standard SPKG.
EXAMPLES::
sage: from sage.features.databases import DatabaseCremona, DatabaseConwayPolynomials
sage: DatabaseCremona().is_standard()
False
sage: DatabaseConwayPolynomials().is_standard()
True
"""
if self.name.startswith('sage.'):
return True
return self._spkg_type() == 'standard'

def is_optional(self):
r"""
Return whether this feature corresponds to an optional SPKG.
EXAMPLES::
sage: from sage.features.databases import DatabaseCremona, DatabaseConwayPolynomials
sage: DatabaseCremona().is_optional()
True
sage: DatabaseConwayPolynomials().is_optional()
False
"""
return self._spkg_type() == 'optional'

def hide(self):
r"""
Hide this feature. For example this is used when the doctest option
``--hide``is set. Setting an installed feature as hidden pretends
that it is not available. To revert this use :meth:`unhide`.
EXAMPLES:
Benzene is an optional SPKG. The following test fails if it is hidden or
not installed. Thus, in the second invocation the optional tag is needed::
sage: from sage.features.graph_generators import Benzene
sage: Benzene().hide()
sage: len(list(graphs.fusenes(2)))
Traceback (most recent call last):
...
FeatureNotPresentError: benzene is not available.
Feature `benzene` is hidden.
Use method `unhide` to make it available again.
sage: Benzene().unhide()
sage: len(list(graphs.fusenes(2))) # optional benzene
1
"""
self._hidden = True

def unhide(self):
r"""
Revert what :meth:`hide` does.
EXAMPLES:
Polycyclic is a standard GAP package since 4.10 (see :trac:`26856`). The
following test just fails if it is hidden. Thus, in the second
invocation no optional tag is needed::
sage: from sage.features.gap import GapPackage
sage: Polycyclic = GapPackage("polycyclic", spkg="gap_packages")
sage: Polycyclic.hide()
sage: libgap(AbelianGroup(3, [0,3,4], names="abc"))
Traceback (most recent call last):
...
FeatureNotPresentError: gap_package_polycyclic is not available.
Feature `gap_package_polycyclic` is hidden.
Use method `unhide` to make it available again.
sage: Polycyclic.unhide()
sage: libgap(AbelianGroup(3, [0,3,4], names="abc"))
Pcp-group with orders [ 0, 3, 4 ]
"""
self._hidden = False




class FeatureNotPresentError(RuntimeError):
Expand Down
Loading

0 comments on commit ddd7ada

Please sign in to comment.