Skip to content

Commit

Permalink
Improve test runs for platform=Windows.
Browse files Browse the repository at this point in the history
Make test runs more platform independent.
  • Loading branch information
jenisys committed Oct 29, 2016
1 parent 0a6279d commit 0ee1423
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 35 deletions.
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ clone_folder: C:\projects\behave.ci

environment:
PYTHONPATH: ".;%CD%"
ROOTDIR_PREFIX: "C:"
BEHAVE_ROOTDIR_PREFIX: "C:"
matrix:
- PYTHON: C:\Python27
ROOTDIR_PREFIX: "c:"
BEHAVE_ROOTDIR_PREFIX: "c:"
- PYTHON: C:\Python34
- PYTHON: C:\Python35

Expand Down
36 changes: 13 additions & 23 deletions behave/formatter/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@


class StreamOpener(object):
"""
Provides a transport vehicle to open the formatter output stream
"""Provides a transport vehicle to open the formatter output stream
when the formatter needs it.
In addition, it provides the formatter with more control:
Expand Down Expand Up @@ -130,24 +129,21 @@ def open(self):
return self.stream

def uri(self, uri):
"""
Called before processing a file (normally a feature file).
"""Called before processing a file (normally a feature file).
:param uri: URI or filename (as string).
"""
pass

def feature(self, feature):
"""
Called before a feature is executed.
"""Called before a feature is executed.
:param feature: Feature object (as :class:`behave.model.Feature`)
"""
pass

def background(self, background):
"""
Called when a (Feature) Background is provided.
"""Called when a (Feature) Background is provided.
Called after :method:`feature()` is called.
Called before processing any scenarios or scenario outlines.
Expand All @@ -156,51 +152,45 @@ def background(self, background):
pass

def scenario(self, scenario):
"""
Called before a scenario is executed (or an example of ScenarioOutline).
"""Called before a scenario is executed (or ScenarioOutline scenarios).
:param scenario: Scenario object (as :class:`behave.model.Scenario`)
"""
pass

def step(self, step):
"""
Called before a step is executed (and matched).
"""Called before a step is executed (and matched).
NOTE: Normally called before scenario is executed for all its steps.
:param step: Step object (as :class:`behave.model.Step`)
"""

def match(self, match):
"""
Called when a step was matched against its step implementation.
"""Called when a step was matched against its step implementation.
:param match: Registered step (as Match), undefined step (as NoMatch).
"""
pass

def result(self, step_result):
"""
Called after processing a step (when the step result is known).
"""Called after processing a step (when the step result is known).
:param step_result: Step result (as string-enum).
"""
pass

def eof(self):
"""
Called after processing a feature (or a feature file).
"""
"""Called after processing a feature (or a feature file)."""
pass

def close(self):
"""
Called before the formatter is no longer used (stream/io compatibility).
"""Called before the formatter is no longer used
(stream/io compatibility).
"""
self.close_stream()

def close_stream(self):
"""
Close the stream, but only if this is needed.
"""Close the stream, but only if this is needed.
This step is skipped if the stream is sys.stdout.
"""
if self.stream:
Expand Down
6 changes: 2 additions & 4 deletions behave/formatter/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ def reset(self):
# -- FORMATTER API:
def feature(self, feature):
self.current_feature = feature
short_filename = os.path.relpath(feature.filename, os.getcwd())
self.stream.write("%s " % six.text_type(short_filename))
self.stream.write("%s " % six.text_type(feature.filename))
self.stream.flush()

def background(self, background):
Expand Down Expand Up @@ -226,8 +225,7 @@ class ScenarioStepProgressFormatter(StepProgressFormatter):
# -- FORMATTER API:
def feature(self, feature):
self.current_feature = feature
short_filename = os.path.relpath(feature.filename, os.getcwd())
self.stream.write(u"%s # %s" % (feature.name, short_filename))
self.stream.write(u"%s # %s" % (feature.name, feature.filename))

def scenario(self, scenario):
"""Process the next scenario."""
Expand Down
7 changes: 7 additions & 0 deletions behave/model_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
from enum import Enum


PLATFORM_WIN = sys.platform.startswith("win")
def posixpath_normalize(path):
return path.replace("\\", "/")


# -----------------------------------------------------------------------------
# GENERIC MODEL CLASSES:
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -134,6 +139,8 @@ class FileLocation(object):
__pychecker__ = "missingattrs=line" # -- Ignore warnings for 'line'.

def __init__(self, filename, line=None):
if PLATFORM_WIN:
filename = posixpath_normalize(filename)
self.filename = filename
self.line = line

Expand Down
6 changes: 5 additions & 1 deletion behave/reporter/junit.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
import os.path
import codecs
import sys
import traceback
from xml.etree import ElementTree
from datetime import datetime
from behave.reporter.base import Reporter
Expand All @@ -82,6 +81,11 @@
from behave.model_describe import ModelDescriptor
from behave.textutil import indent, make_indentation, text as _text
import six
if six.PY2:
# -- USE: Python3 backport for better unicode compatibility.
import traceback2 as traceback
else:
import traceback


def CDATA(text=None): # pylint: disable=invalid-name
Expand Down
126 changes: 126 additions & 0 deletions behave4cmd0/command_shell_proc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module provides pre-/post-processors for the mod:`behave4cmd0.command_shell`.
"""

from __future__ import absolute_import, print_function
import re
import sys

# -----------------------------------------------------------------------------
# UTILITY:
# -----------------------------------------------------------------------------
def posixpath_normpath(filename):
return filename.replace("\\", "/")

# -----------------------------------------------------------------------------
# FUNCTIONALITY
# -----------------------------------------------------------------------------
class CommandPostProcessor(object): pass
class CommandOutputProcessor(CommandPostProcessor): pass

class TracebackNormalizer(CommandOutputProcessor):
"""Normalize paths in traceback output to use POSIX path style.
Therefore, it replace backslashes with slashes: '\' => '/'
in stderr output.
"""
# XXX cells = [cell.replace("\\|", "|").strip()
# XXX for cell in re.split(r"(?<!\\)\|", line[1:-1])]
file_pattern = re.compile(r'\s\s+File "(?P<filename>.*)", line .*')
marker = "Traceback (most recent call last):\n"
enabled = sys.platform.startswith("win")
output_parts = ("stderr", "stdout")

def __init__(self, enabled=None):
if enabled is None:
# -- AUTO-DETECT: Enabled on Windows platform
enabled = self.__class__.enabled
self.enabled = enabled

def matches_output(self, text):
"""Indicates it text contains sections of interest (Traceback sections).
:param text: Text to inspect (as string).
:return: True, if text contains Traceback sections. False, otherwise.
"""
return self.marker in text

@classmethod
def normalize_traceback_paths(cls, text):
"""Normalizes File "..." parts in traceback section to use posix-path
naming conventions (replace: backslashes with slashes).
TRACEBACK-OUTPUT EXAMPLE::
Traceback (most recent call last):
File "tests/xplore_traceback.py", line 27, in <module>
sys.exit(main())
...
File "tests/xplore_traceback.py", line 12, in f
return x/0
ZeroDivisionError: integer division or modulo by zero
:param text: Text to process (as string).
:return: Tuple (changed : bool, new_text : string)
"""
marker = cls.marker.strip()
new_lines = []
traceback_section = False
changes = 0
for line in text.splitlines():
stripped_line = line.strip()
if marker == stripped_line:
assert not traceback_section
traceback_section = True
# print("XXX: TRACEBACK-START")
elif traceback_section:
matched = cls.file_pattern.match(line)
if matched:
# matched_range = matched.regs[1]
filename = matched.groups()[0]
new_filename = posixpath_normpath(filename)
if new_filename != filename:
# print("XXX: %r => %r" % (filename, new_filename))
line = line.replace(filename, new_filename)
changes += 1
elif not line.strip() or line[0].isalpha():
# -- DETECTED TRCAEBACK-END: exception-description
# print("XXX: TRACEBACK-END")
traceback_section = False
new_lines.append(line)

if changes:
text = "\n".join(new_lines) + "\n"
return (bool(changes), text)

def process_output(self, command_result):
"""Normalizes stderr output of command result.
EXAMPLE: Traceback output
.. sourcecode:: python
Traceback (most recent call last):
File "tests/xplore_traceback.py", line 27, in <module>
sys.exit(main())
...
File "tests/xplore_traceback.py", line 12, in f
return x/0
ZeroDivisionError: integer division or modulo by zero
"""
changes = 0
for output_name in self.output_parts:
text = getattr(command_result, output_name)
if text and self.matches_output(text):
changed, new_text = self.normalize_traceback_paths(text)
if changed:
changes += 1
setattr(command_result, output_name, new_text)
if changes:
# -- RESET: Composite output
command_result._output = None
return command_result

def __call__(self, command_result):
if (not self.enabled or not command_result.stderr or
not self.matches_output(command_result.stderr)):
return command_result
return self.process_output(command_result)
25 changes: 25 additions & 0 deletions behave4cmd0/setup_command_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Support functionality to simplify the setup for behave tests.
.. sourcecode:: python
# -- FILE: features/environment.py
from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
def before_all(context):
setup_command_shell_processors4behave()
"""

from __future__ import absolute_import
from .command_shell import Command
from .command_shell_proc import TracebackNormalizer

def setup_command_shell_processors4behave():
Command.POSTPROCESSOR_MAP["behave"] = []
for processor_class in [TracebackNormalizer]:
if processor_class.enabled:
processor = processor_class()
Command.POSTPROCESSOR_MAP["behave"].append(processor)

2 changes: 2 additions & 0 deletions features/environment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: UTF-8 -*-

from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
import platform
import sys
import six
Expand Down Expand Up @@ -28,6 +29,7 @@ def before_all(context):
setup_active_tag_values(active_tag_value_provider, context.config.userdata)
setup_python_path()
setup_context_with_global_params_test(context)
setup_command_shell_processors4behave()

def before_feature(context, feature):
if active_tag_matcher.should_exclude_with(feature.tags):
Expand Down
5 changes: 3 additions & 2 deletions issue.features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values
from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave
import six
import sys
import platform
Expand All @@ -26,9 +27,9 @@
def before_all(context):
# -- SETUP ACTIVE-TAG MATCHER (with userdata):
# USE: behave -D browser=safari ...
# NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
# NOT-NEEDED: setup_active_tag_values(active_tag_value_provider,
# context.config.userdata)
pass
setup_command_shell_processors4behave()

def before_feature(context, feature):
if active_tag_matcher.should_exclude_with(feature.tags):
Expand Down
2 changes: 0 additions & 2 deletions issue.features/issue0226.feature
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
@issue
@unicode
@wip
@bad_fail
Feature: UnicodeDecodeError in tracebacks (when an exception in a step implementation)

. Exception with non-ASCII character is raised in a step implementation.
Expand Down
3 changes: 2 additions & 1 deletion test/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
ROOTDIR_PREFIX = ""
if sys.platform.startswith("win"):
# -- OR: ROOTDIR_PREFIX = os.path.splitdrive(sys.executable)
ROOTDIR_PREFIX = os.environ.get("ROOTDIR_PREFIX", "C:")
# NOTE: python2 requires lower-case drive letter.
ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", "c:")

class TestConfiguration(object):

Expand Down

0 comments on commit 0ee1423

Please sign in to comment.