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

Allow showing the commands output #174

Merged
merged 3 commits into from
Aug 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ However, this will never output colorful logs:
.. _opt-report:


Controling commands verbosity
-----------------------------

By default, Nox will only show output of commands that fail, or, when the commands get passed `silent=False`.
By passing `--verbose` to Nox, all output of all commands run is shown, regardless of the silent argument.


Outputting a machine-readable report
------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion nox/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def main():
print(dist.version, file=sys.stderr)
return

setup_logging(color=args.color)
setup_logging(color=args.color, verbose=args.verbose)

# Execute the appropriate tasks.
exit_code = workflow.execute(
Expand Down
9 changes: 9 additions & 0 deletions nox/_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,15 @@ def _session_completer(prefix, parsed_args, **kwargs):
help="Arguments following ``--`` that are passed through to the session(s).",
finalizer_func=_posargs_finalizer,
),
_option_set.Option(
"verbose",
"-v",
"--verbose",
group="secondary",
action="store_true",
help="Logs the output of all commands run including commands marked silent.",
noxfile=True,
),
*_option_set.make_flag_pair(
"reuse_existing_virtualenvs",
("-r", "--reuse-existing-virtualenvs"),
Expand Down
3 changes: 3 additions & 0 deletions nox/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ def run(

raise CommandFailed("Returned code {}".format(return_code))

if output:
logger.output(output)

return output if silent else True

except KeyboardInterrupt:
Expand Down
35 changes: 28 additions & 7 deletions nox/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,59 @@
from colorlog import ColoredFormatter # type: ignore

SUCCESS = 25
OUTPUT = logging.DEBUG - 1


class LoggerWithSuccess(logging.getLoggerClass()): # type: ignore
class NoxFormatter(ColoredFormatter):
def __init__(self, *args, **kwargs):
super(NoxFormatter, self).__init__(*args, **kwargs)
self._simple_fmt = logging.Formatter("%(message)s")

def format(self, record):
if record.levelname == "OUTPUT":
return self._simple_fmt.format(record)
return super(NoxFormatter, self).format(record)


class LoggerWithSuccessAndOutput(logging.getLoggerClass()): # type: ignore
def __init__(self, name, level=logging.NOTSET):
super(LoggerWithSuccess, self).__init__(name, level)
super(LoggerWithSuccessAndOutput, self).__init__(name, level)
logging.addLevelName(SUCCESS, "SUCCESS")
logging.addLevelName(OUTPUT, "OUTPUT")

def success(self, msg, *args, **kwargs):
if self.isEnabledFor(SUCCESS):
self._log(SUCCESS, msg, args, **kwargs)
else: # pragma: no cover
pass

def output(self, msg, *args, **kwargs):
if self.isEnabledFor(OUTPUT):
self._log(OUTPUT, msg, args, **kwargs)
else: # pragma: no cover
pass


logging.setLoggerClass(LoggerWithSuccess)
logging.setLoggerClass(LoggerWithSuccessAndOutput)
logger = logging.getLogger("nox")
logger.setLevel(logging.DEBUG)


def setup_logging(color): # pragma: no cover
def setup_logging(color, verbose=False): # pragma: no cover
"""Setup logging.

Args:
color (bool): If true, the output will be colored using
colorlog. Otherwise, it will be plaintext.
"""
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
if verbose:
root_logger.setLevel(OUTPUT)
else:
root_logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()

if color is True:
formatter = ColoredFormatter(
formatter = NoxFormatter(
"%(cyan)s%(name)s > %(log_color)s%(message)s",
reset=True,
log_colors={
Expand Down
55 changes: 55 additions & 0 deletions tests/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,61 @@ def test_run_silent(capsys):
assert out == ""


def test_run_verbosity(capsys, caplog):
caplog.clear()
with caplog.at_level(logging.DEBUG):
result = nox.command.run([PYTHON, "-c", "print(123)"], silent=True)

out, _ = capsys.readouterr()

assert "123" in result
assert out == ""

logs = [rec for rec in caplog.records if rec.levelname == "OUTPUT"]
assert not logs

caplog.clear()
with caplog.at_level(logging.DEBUG - 1):
result = nox.command.run([PYTHON, "-c", "print(123)"], silent=True)

out, _ = capsys.readouterr()

assert "123" in result
assert out == ""

logs = [rec for rec in caplog.records if rec.levelname == "OUTPUT"]
assert logs
assert logs[0].message.strip() == "123"


def test_run_verbosity_failed_command(capsys, caplog):
caplog.clear()
with caplog.at_level(logging.DEBUG):
with pytest.raises(nox.command.CommandFailed):
nox.command.run([PYTHON, "-c", "print(123); exit(1)"], silent=True)

out, err = capsys.readouterr()

assert "123" in err
assert out == ""

logs = [rec for rec in caplog.records if rec.levelname == "OUTPUT"]
assert not logs

caplog.clear()
with caplog.at_level(logging.DEBUG - 1):
with pytest.raises(nox.command.CommandFailed):
nox.command.run([PYTHON, "-c", "print(123); exit(1)"], silent=True)

out, err = capsys.readouterr()

assert "123" in err
assert out == ""

# Nothing is logged but the error is still written to stderr
assert not logs


def test_run_env_unicode():
result = nox.command.run(
[PYTHON, "-c", 'import os; print(os.environ["SIGIL"])'],
Expand Down
35 changes: 33 additions & 2 deletions tests/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,43 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
from unittest import mock

from nox import logger


def test_success():
with mock.patch.object(logger.LoggerWithSuccess, "_log") as _log:
logger.LoggerWithSuccess("foo").success("bar")
with mock.patch.object(logger.LoggerWithSuccessAndOutput, "_log") as _log:
logger.LoggerWithSuccessAndOutput("foo").success("bar")
_log.assert_called_once_with(logger.SUCCESS, "bar", ())


def test_output():
with mock.patch.object(logger.LoggerWithSuccessAndOutput, "_log") as _log:
logger.LoggerWithSuccessAndOutput("foo").output("bar")
_log.assert_called_once_with(logger.OUTPUT, "bar", ())


def test_formatter(caplog):
caplog.clear()
logger.setup_logging(True, verbose=True)
with caplog.at_level(logging.DEBUG):
logger.logger.info("bar")
logger.logger.output("foo")

logs = [rec for rec in caplog.records if rec.levelname in ("INFO", "OUTPUT")]
assert len(logs) == 1

caplog.clear()
with caplog.at_level(logger.OUTPUT):
logger.logger.info("bar")
logger.logger.output("foo")

logs = [rec for rec in caplog.records if rec.levelname in ("INFO", "OUTPUT")]
assert len(logs) == 2

logs = [rec for rec in caplog.records if rec.levelname == "OUTPUT"]
assert len(logs) == 1
# Make sure output level log records are not nox prefixed
assert "nox" not in logs[0].message