Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support setting an empty session list #523

Merged
merged 9 commits into from Dec 27, 2021
2 changes: 1 addition & 1 deletion docs/config.rst
Expand Up @@ -426,7 +426,7 @@ Or, if you wanted to provide a set of sessions that are run by default:
The following options can be specified in the Noxfile:

* ``nox.options.envdir`` is equivalent to specifying :ref:`--envdir <opt-envdir>`.
* ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions <opt-sessions-pythons-and-keywords>`.
* ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions <opt-sessions-pythons-and-keywords>`. If set to an empty list, no sessions will be run if no sessions were given on the command line, and the list of available sessions will be shown instead.
* ``nox.options.pythons`` is equivalent to specifying :ref:`-p or --pythons <opt-sessions-pythons-and-keywords>`.
* ``nox.options.keywords`` is equivalent to specifying :ref:`-k or --keywords <opt-sessions-pythons-and-keywords>`.
* ``nox.options.default_venv_backend`` is equivalent to specifying :ref:`-db or --default-venv-backend <opt-default-venv-backend>`.
Expand Down
14 changes: 14 additions & 0 deletions docs/tutorial.rst
Expand Up @@ -258,6 +258,20 @@ And if you run ``nox --sessions lint`` Nox will just run the lint session:
nox > ...
nox > Session lint was successful.


In the noxfile, you can specify a default set of sessions to run. If so, a plain
``nox`` call will only trigger certain sessions:

.. code-block:: python

import nox

nox.options.sessions = ["lint", "test"]

If you set this to an empty list, Nox will not run any sessions by default, and
will print a helpful message with the ``--list`` output when a user does not
specify a session to run.

There are many more ways to select and run sessions! You can read more about
invoking Nox in :doc:`usage`.

Expand Down
1 change: 0 additions & 1 deletion nox/__main__.py
Expand Up @@ -50,7 +50,6 @@ def main() -> None:
tasks.discover_manifest,
tasks.filter_manifest,
tasks.honor_list_request,
tasks.verify_manifest_nonempty,
tasks.run_manifest,
tasks.print_summary,
tasks.create_report,
Expand Down
62 changes: 33 additions & 29 deletions nox/tasks.py
Expand Up @@ -167,22 +167,33 @@ def filter_manifest(
the manifest otherwise (to be sent to the next task).

"""
# Shouldn't happen unless the noxfile is empty
if not manifest:
logger.error(f"No sessions found in {global_config.noxfile}.")
return 3

# Filter by the name of any explicit sessions.
# This can raise KeyError if a specified session does not exist;
# log this if it happens.
if global_config.sessions:
# log this if it happens. The sessions does not come from the noxfile
# if keywords is not empty.
if global_config.sessions is not None:
try:
manifest.filter_by_name(global_config.sessions)
except KeyError as exc:
logger.error("Error while collecting sessions.")
logger.error(exc.args[0])
return 3
if not manifest and not global_config.list_sessions:
print("No sessions selected. Please select a session with -s <session name>.\n")
_produce_listing(manifest, global_config)
return 0

# Filter by python interpreter versions.
# This function never errors, but may cause an empty list of sessions
# (which is an error condition later).
if global_config.pythons:
manifest.filter_by_python_interpreter(global_config.pythons)
if not manifest and not global_config.list_sessions:
logger.error("Python version selection caused no sessions to be selected.")
return 3

# Filter by keywords.
if global_config.keywords:
Expand All @@ -198,28 +209,19 @@ def filter_manifest(
# (which is an error condition later).
manifest.filter_by_keywords(global_config.keywords)

if not manifest and not global_config.list_sessions:
logger.error("No sessions selected after filtering by keyword.")
return 3

# Return the modified manifest.
return manifest


def honor_list_request(
manifest: Manifest, global_config: Namespace
) -> Union[Manifest, int]:
"""If --list was passed, simply list the manifest and exit cleanly.

Args:
manifest (~.Manifest): The manifest of sessions to be run.
global_config (~nox.main.GlobalConfig): The global configuration.

Returns:
Union[~.Manifest,int]: ``0`` if a listing is all that is requested,
the manifest otherwise (to be sent to the next task).
"""
if not global_config.list_sessions:
return manifest

def _produce_listing(manifest: Manifest, global_config: Namespace) -> None:
# If the user just asked for a list of sessions, print that
# and any docstring specified in noxfile.py and be done.
# and any docstring specified in noxfile.py and be done. This
# can also be called if noxfile sessions is an empty list.

if manifest.module_docstring:
print(manifest.module_docstring.strip(), end="\n\n")

Expand Down Expand Up @@ -255,25 +257,27 @@ def honor_list_request(
print(
f"\nsessions marked with {selected_color}*{reset} are selected, sessions marked with {skipped_color}-{reset} are skipped."
)
return 0


def verify_manifest_nonempty(
def honor_list_request(
manifest: Manifest, global_config: Namespace
) -> Union[Manifest, int]:
"""Abort with an error code if the manifest is empty.
"""If --list was passed, simply list the manifest and exit cleanly.

Args:
manifest (~.Manifest): The manifest of sessions to be run.
global_config (~nox.main.GlobalConfig): The global configuration.

Returns:
Union[~.Manifest,int]: ``3`` on an empty manifest, the manifest
otherwise.
Union[~.Manifest,int]: ``0`` if a listing is all that is requested,
the manifest otherwise (to be sent to the next task).
"""
if not manifest:
return 3
return manifest
if not global_config.list_sessions:
return manifest

_produce_listing(manifest, global_config)

return 0


def run_manifest(manifest: Manifest, global_config: Namespace) -> List[Result]:
Expand Down
2 changes: 2 additions & 0 deletions noxfile.py
Expand Up @@ -28,6 +28,8 @@
if shutil.which("conda"):
nox.options.sessions.append("conda_tests")

nox.options.sessions = []
henryiii marked this conversation as resolved.
Show resolved Hide resolved
cjolowicz marked this conversation as resolved.
Show resolved Hide resolved


def is_python_version(session, version):
if not version.startswith(session.python):
Expand Down
62 changes: 55 additions & 7 deletions tests/test_tasks.py
Expand Up @@ -209,7 +209,7 @@ def notasession():

def test_filter_manifest():
config = _options.options.namespace(
sessions=(), pythons=(), keywords=(), posargs=[]
sessions=None, pythons=(), keywords=(), posargs=[]
)
manifest = Manifest({"foo": session_func, "bar": session_func}, config)
return_value = tasks.filter_manifest(manifest, config)
Expand All @@ -228,7 +228,7 @@ def test_filter_manifest_not_found():

def test_filter_manifest_pythons():
config = _options.options.namespace(
sessions=(), pythons=("3.8",), keywords=(), posargs=[]
sessions=None, pythons=("3.8",), keywords=(), posargs=[]
)
manifest = Manifest(
{"foo": session_func_with_python, "bar": session_func, "baz": session_func},
Expand All @@ -239,9 +239,22 @@ def test_filter_manifest_pythons():
assert len(manifest) == 1


def test_filter_manifest_pythons_not_found(caplog):
config = _options.options.namespace(
sessions=None, pythons=("1.2",), keywords=(), posargs=[]
)
manifest = Manifest(
{"foo": session_func_with_python, "bar": session_func, "baz": session_func},
config,
)
return_value = tasks.filter_manifest(manifest, config)
assert return_value == 3
assert "Python version selection caused no sessions to be selected." in caplog.text


def test_filter_manifest_keywords():
config = _options.options.namespace(
sessions=(), pythons=(), keywords="foo or bar", posargs=[]
sessions=None, pythons=(), keywords="foo or bar", posargs=[]
)
manifest = Manifest(
{"foo": session_func, "bar": session_func, "baz": session_func}, config
Expand All @@ -251,9 +264,21 @@ def test_filter_manifest_keywords():
assert len(manifest) == 2


def test_filter_manifest_keywords_not_found(caplog):
config = _options.options.namespace(
sessions=None, pythons=(), keywords="mouse or python", posargs=[]
)
manifest = Manifest(
{"foo": session_func, "bar": session_func, "baz": session_func}, config
)
return_value = tasks.filter_manifest(manifest, config)
assert return_value == 3
assert "No sessions selected after filtering by keyword." in caplog.text


def test_filter_manifest_keywords_syntax_error():
config = _options.options.namespace(
sessions=(), pythons=(), keywords="foo:bar", posargs=[]
sessions=None, pythons=(), keywords="foo:bar", posargs=[]
)
manifest = Manifest({"foo_bar": session_func, "foo_baz": session_func}, config)
return_value = tasks.filter_manifest(manifest, config)
Expand Down Expand Up @@ -346,20 +371,43 @@ def test_honor_list_request_doesnt_print_docstring_if_not_present(capsys):
assert "Hello I'm a docstring" not in out


def test_empty_session_list_in_noxfile(capsys):
config = _options.options.namespace(noxfile="noxfile.py", sessions=(), posargs=[])
manifest = Manifest({"session": session_func}, config)
return_value = tasks.filter_manifest(manifest, global_config=config)
assert return_value == 0
assert "No sessions selected." in capsys.readouterr().out


def test_empty_session_None_in_noxfile(capsys):
config = _options.options.namespace(noxfile="noxfile.py", sessions=None, posargs=[])
manifest = Manifest({"session": session_func}, config)
return_value = tasks.filter_manifest(manifest, global_config=config)
assert return_value == manifest


def test_verify_manifest_empty():
config = _options.options.namespace(sessions=(), keywords=())
manifest = Manifest({}, config)
return_value = tasks.verify_manifest_nonempty(manifest, global_config=config)
return_value = tasks.filter_manifest(manifest, global_config=config)
assert return_value == 3


def test_verify_manifest_nonempty():
config = _options.options.namespace(sessions=(), keywords=(), posargs=[])
config = _options.options.namespace(sessions=None, keywords=(), posargs=[])
manifest = Manifest({"session": session_func}, config)
return_value = tasks.verify_manifest_nonempty(manifest, global_config=config)
return_value = tasks.filter_manifest(manifest, global_config=config)
assert return_value == manifest


def test_verify_manifest_list(capsys):
config = _options.options.namespace(sessions=(), keywords=(), posargs=[])
manifest = Manifest({"session": session_func}, config)
return_value = tasks.filter_manifest(manifest, global_config=config)
assert return_value == 0
assert "Please select a session" in capsys.readouterr().out


@pytest.mark.parametrize("with_warnings", [False, True], ids="with_warnings={}".format)
def test_run_manifest(with_warnings):
# Set up a valid manifest.
Expand Down