Skip to content

Commit

Permalink
FIXES behave#899: Bad formatters
Browse files Browse the repository at this point in the history
* Show bad formatters in "UNAVAILABLE FORMATTERS" section
* Show bad runners    in "UNAVAILABLE RUNNERS" section

OTHERWISE:
* Changes textual output slighty to use UPPER_CASE for section titles.
  • Loading branch information
jenisys committed Nov 21, 2022
1 parent 5fac450 commit 050fc2a
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 72 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ ENHANCEMENTS:
* Select-by-location: Add support for "Scenario container" (Feature, Rule, ScenarioOutline) (related to: #391)
* pull #988: setup.py: Add category to install additional formatters (html) (provided-by: bittner)
* pull #895: UPDATE: i18n/gherkin-languages.json from cucumber repository #895 (related to: #827)
* issue #889: Warn or error about incorrectly configured formatter aliases (provided by: jenisys, submitted by: bittner)
* pull #827: Fixed keyword translation in Estonian #827 (provided by: ookull)
* issue #740: Enhancement: possibility to add cleanup to be called upon leaving outer context stack frames (submitted by: nizwiz, dcvmoole)
* issue #678: Scenario Outline: Support tags with commas and semicolons (provided by: lawnmowerlatte, pull #679)
Expand Down
95 changes: 54 additions & 41 deletions behave/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ def run_behave(config, runner_class=None):
print(TAG_HELP)
return 0

if config.lang_list:
if config.lang == "help" or config.lang_list:
print_language_list()
return 0

if config.lang_help:
print_language_help(config)
# -- PROVIDE HELP: For one, specific language
language = config.lang_help
print_language_help(language)
return 0

if not config.format:
Expand All @@ -89,7 +91,7 @@ def run_behave(config, runner_class=None):
# -- NO FORMATTER on command-line: Add default formatter.
config.format.append(config.default_format)
if "help" in config.format:
print_formatters("Available formatters:")
print_formatters()
return 0

if len(config.outputs) > len(config.format):
Expand Down Expand Up @@ -150,7 +152,7 @@ def run_behave(config, runner_class=None):
# ---------------------------------------------------------------------------
# MAIN SUPPORT FOR: run_behave()
# ---------------------------------------------------------------------------
def print_language_list(stream=None):
def print_language_list(file=None):
"""Print list of supported languages, like:
* English
Expand All @@ -160,51 +162,49 @@ def print_language_list(stream=None):
"""
from behave.i18n import languages

if stream is None:
stream = sys.stdout
if six.PY2:
# -- PYTHON2: Overcome implicit encode problems (encoding=ASCII).
stream = codecs.getwriter("UTF-8")(sys.stdout)
print_ = lambda text: print(text, file=file)
if six.PY2:
# -- PYTHON2: Overcome implicit encode problems (encoding=ASCII).
file = codecs.getwriter("UTF-8")(file or sys.stdout)

iso_codes = languages.keys()
print("Languages available:")
print("AVAILABLE LANGUAGES:")
for iso_code in sorted(iso_codes):
native = languages[iso_code]["native"]
name = languages[iso_code]["name"]
print(u" %s: %s / %s" % (iso_code, native, name), file=stream)
return 0
print_(u" %s: %s / %s" % (iso_code, native, name))


def print_language_help(config, stream=None):
def print_language_help(language, file=None):
from behave.i18n import languages
# if stream is None:
# stream = sys.stdout
# if six.PY2:
# # -- PYTHON2: Overcome implicit encode problems (encoding=ASCII).
# stream = codecs.getwriter("UTF-8")(sys.stdout)

if stream is None:
stream = sys.stdout
if six.PY2:
# -- PYTHON2: Overcome implicit encode problems (encoding=ASCII).
stream = codecs.getwriter("UTF-8")(sys.stdout)
print_ = lambda text: print(text, file=file)
if six.PY2:
# -- PYTHON2: Overcome implicit encode problems (encoding=ASCII).
file = codecs.getwriter("UTF-8")(file or sys.stdout)

if config.lang_help not in languages:
print("%s is not a recognised language: try --lang-list" % \
config.lang_help, file=stream)
if language not in languages:
print_("%s is not a recognised language: try --lang-list" % language)
return 1

trans = languages[config.lang_help]
print(u"Translations for %s / %s" % (trans["name"],
trans["native"]), file=stream)
trans = languages[language]
print_(u"Translations for %s / %s" % (trans["name"], trans["native"]))
for kw in trans:
if kw in "name native".split():
continue
print(u"%16s: %s" % (kw.title().replace("_", " "),
print_(u"%16s: %s" % (kw.title().replace("_", " "),
u", ".join(w for w in trans[kw] if w != "*")))
return 0


def print_formatters(title=None, file=None):
def print_formatters(file=None):
"""Prints the list of available formatters and their description.
:param title: Optional title (as string).
:param stream: Optional, output stream to use (default: sys.stdout).
:param file: Optional, output file to use (default: sys.stdout).
"""
from behave.formatter._registry import format_items
from operator import itemgetter
Expand All @@ -215,16 +215,23 @@ def print_formatters(title=None, file=None):
formatter_names = [item[0] for item in formatter_items]
column_size = compute_words_maxsize(formatter_names)
schema = u" %-"+ _text(column_size) +"s %s"
problematic_formatters = []

if title:
print_(u"%s" % title)
print_("AVAILABLE FORMATTERS:")
for name, formatter_class in formatter_items:
formatter_description = getattr(formatter_class, "description", "")
formatter_error = getattr(formatter_class, "error", None)
if formatter_error:
# -- DIAGNOSTICS: Indicate if formatter definition has a problem.
formatter_description = formatter_error
print_(schema % (name, formatter_description))
problematic_formatters.append((name, formatter_error))
else:
# -- NORMAL CASE:
print_(schema % (name, formatter_description))

if problematic_formatters:
print_("\nUNAVAILABLE FORMATTERS:")
for name, formatter_error in problematic_formatters:
print_(schema % (name, formatter_error))


def print_runners(runner_aliases, file=None):
Expand All @@ -237,20 +244,26 @@ def print_runners(runner_aliases, file=None):
# MAYBE: file = file or sys.stdout
print_ = lambda text: print(text, file=file)

title = "AVAILABLE RUNNERS:"
runner_names = sorted(runner_aliases.keys())
column_size = compute_words_maxsize(runner_names)
schema = u" %-"+ _text(column_size) +"s = %s%s"
schema1 = u" %-"+ _text(column_size) +"s = %s%s"
schema2 = u" %-"+ _text(column_size) +"s %s"
problematic_runners = []

print_(title)
print_("AVAILABLE RUNNERS:")
for runner_name in runner_names:
scoped_class_name = runner_aliases[runner_name]
annotation = ""
problem = RunnerPlugin.make_problem_description(scoped_class_name)
problem = RunnerPlugin.make_problem_description(scoped_class_name, use_details=True)
if problem:
annotation = " (problem: %s)" % problem

print_(schema % (runner_name, scoped_class_name, annotation))
problematic_runners.append((runner_name, problem))
else:
# -- NORMAL CASE:
print_(schema1 % (runner_name, scoped_class_name, ""))

if problematic_runners:
print_("\nUNAVAILABLE RUNNERS:")
for runner_name, problem_description in problematic_runners:
print_(schema2 % (runner_name, problem_description))


# ---------------------------------------------------------------------------
Expand Down
56 changes: 51 additions & 5 deletions behave/formatter/_registry.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-

import inspect
import sys
import warnings
from behave.formatter.base import Formatter, StreamOpener
Expand All @@ -8,6 +8,29 @@
import six


# -----------------------------------------------------------------------------
# FORMATTER BAD CASES:
# -----------------------------------------------------------------------------
class BadFormatterClass(object):
"""Placeholder class if a formatter class is invalid."""
def __init__(self, name, formatter_class):
self.name = name
self.formatter_class = formatter_class
self._error_text = None

@property
def error(self):
from behave.importer import make_scoped_class_name
if self._error_text is None:
error_text = ""
if not inspect.isclass(self.formatter_class):
error_text = "InvalidClassError: is not a class"
elif not is_formatter_class_valid(self.formatter_class):
error_text = "InvalidClassError: is not a subclass-of Formatter"
self._error_text = error_text
return self._error_text


# -----------------------------------------------------------------------------
# FORMATTER REGISTRY:
# -----------------------------------------------------------------------------
Expand All @@ -22,6 +45,16 @@ def format_items(resolved=False):
if resolved:
# -- ENSURE: All formatter classes are loaded (and resolved).
_formatter_registry.load_all(strict=False)

# -- BETTER DIAGNOSTICS: Ensure problematic cases are covered.
for name, formatter_class in _formatter_registry.items():
if isinstance(formatter_class, BadFormatterClass):
continue
elif not is_formatter_class_valid(formatter_class):
if not hasattr(formatter_class, "error"):
bad_formatter_class = BadFormatterClass(name, formatter_class)
_formatter_registry[name] = bad_formatter_class

return iter(_formatter_registry.items())


Expand Down Expand Up @@ -80,7 +113,10 @@ def load_formatter_class(scoped_class_name):
formatter_module = load_module(module_name)
formatter_class = getattr(formatter_module, class_name, None)
if formatter_class is None:
raise ClassNotFoundError("CLASS NOT FOUND: %s" % scoped_class_name)
raise ClassNotFoundError(scoped_class_name)
elif not is_formatter_class_valid(formatter_class):
# -- BETTER DIAGNOSTICS:
formatter_class = BadFormatterClass(scoped_class_name, formatter_class)
return formatter_class


Expand All @@ -97,14 +133,24 @@ def select_formatter_class(formatter_name):
:raises: ValueError, if formatter name is invalid.
"""
try:
return _formatter_registry[formatter_name]
formatter_class = _formatter_registry[formatter_name]
return formatter_class
# if not is_formatter_class_valid(formatter_class):
# formatter_class = BadFormatterClass(formatter_name, formatter_class)
# _formatter_registry[formatter_name] = formatter_class
# return formatter_class
except KeyError:
# -- NOT-FOUND:
if ":" not in formatter_name:
raise
# -- OTHERWISE: SCOPED-NAME, try to load a user-specific formatter.
# MAY RAISE: ImportError
return load_formatter_class(formatter_name)
formatter_class = load_formatter_class(formatter_name)
return formatter_class


def is_formatter_class_valid(formatter_class):
return inspect.isclass(formatter_class) and issubclass(formatter_class, Formatter)


def is_formatter_valid(formatter_name):
Expand All @@ -115,7 +161,7 @@ def is_formatter_valid(formatter_name):
"""
try:
formatter_class = select_formatter_class(formatter_name)
return issubclass(formatter_class, Formatter)
return is_formatter_class_valid(formatter_class)
except (LookupError, ImportError, ValueError):
return False

Expand Down
2 changes: 1 addition & 1 deletion features/cmdline.lang_list.feature
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Feature: Command-line options: Use behave --lang-list
When I run "behave --lang-list"
Then it should pass with:
"""
Languages available:
AVAILABLE LANGUAGES:
af: Afrikaans / Afrikaans
am: հայերեն / Armenian
amh: አማርኛ / Amharic
Expand Down
Loading

0 comments on commit 050fc2a

Please sign in to comment.