Skip to content

Commit

Permalink
refactor: better error messages for empty sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
henryiii committed Dec 23, 2021
1 parent 8750ad0 commit 9f5a380
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 50 deletions.
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
79 changes: 44 additions & 35 deletions nox/tasks.py
Expand Up @@ -168,6 +168,11 @@ 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.
Expand All @@ -178,16 +183,13 @@ def filter_manifest(
logger.error("Error while collecting sessions.")
logger.error(exc.args[0])
return 3
elif global_config.sessions is not None:
# If the user did not specify sessions, and the noxfile is set
# to an empty list, then list the sessions instead.
global_config.list_sessions = True

# 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:
logger.error("Python version selection caused no sessions to selected.")
return 3

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

# Return the modified manifest.
return manifest
if not manifest:
logger.error("No sessions selected after filtering by keyword.")
return 3

if (
not global_config.keywords
and not global_config.pythons
and not global_config.sessions
and not global_config.list_sessions
and global_config.sessions is not None
):
print("No sessions selected. Please select a session with -s <session name>.\n")
_produce_listing(manifest, global_config, select=False)
return 0

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

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, *, select: bool = True
) -> 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 All @@ -237,7 +243,7 @@ def honor_list_request(
for session, selected in manifest.list_all_sessions():
output = "{marker} {color}{session}{reset}"

if selected:
if selected or not select:
marker = "*"
color = selected_color
else:
Expand All @@ -257,28 +263,31 @@ def honor_list_request(
)
)

print(
f"\nsessions marked with {selected_color}*{reset} are selected, sessions marked with {skipped_color}-{reset} are skipped."
)
return 0
if select:
print(
f"\nsessions marked with {selected_color}*{reset} are selected, sessions marked with {skipped_color}-{reset} are skipped."
)


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
1 change: 1 addition & 0 deletions noxfile.py
Expand Up @@ -27,6 +27,7 @@
nox.options.sessions = ["tests", "cover", "blacken", "lint", "docs"]
if shutil.which("conda"):
nox.options.sessions.append("conda_tests")
nox.options.sessions = []


def is_python_version(session, version):
Expand Down
62 changes: 48 additions & 14 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 Down Expand Up @@ -239,6 +239,19 @@ def test_filter_manifest_pythons():
assert len(manifest) == 1


def test_filter_manifest_pythons_not_found(caplog):
config = _options.options.namespace(
sessions=(), 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 selected." in caplog.text


def test_filter_manifest_keywords():
config = _options.options.namespace(
sessions=(), pythons=(), keywords="foo or bar", posargs=[]
Expand All @@ -251,6 +264,18 @@ def test_filter_manifest_keywords():
assert len(manifest) == 2


def test_filter_manifest_keywords_not_found(caplog):
config = _options.options.namespace(
sessions=(), 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=[]
Expand Down Expand Up @@ -346,34 +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_emtpy_session_list_in_noxfile():
config = _options.options.namespace(noxfile="noxfile.py", sessions=())
manifest = Manifest({}, config)
tasks.filter_manifest(manifest, global_config=config)
assert config.list_sessions
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_emtpy_session_None_in_noxfile():
config = _options.options.namespace(noxfile="noxfile.py", sessions=None)
manifest = Manifest({}, config)
tasks.filter_manifest(manifest, global_config=config)
assert not config.list_sessions
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

0 comments on commit 9f5a380

Please sign in to comment.