def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = io.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = io.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture
def setup_capture(self, context): assert context is not None if self.config.stdout_capture: self.stdout_capture = StringIO() context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO() context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() context.log_capture = self.log_capture
def test_get_value_returns_all_log_records(self): class FakeConfig(object): logging_filter = None logging_format = None logging_datefmt = None logging_level = None fake_records = [object() for x in range(0, 10)] handler = LoggingCapture(FakeConfig()) handler.buffer = fake_records with patch.object(handler.formatter, 'format') as format: format.return_value = 'foo' expected = '\n'.join(['foo'] * len(fake_records)) eq_(handler.getvalue(), expected) calls = [args[0][0] for args in format.call_args_list] eq_(calls, fake_records)
def step_I_capture_logrecords(context): """ .. code-block: gherkin Given I capture log records When I capture log records :param context: """ raise NotImplementedError() logcapture = getattr(context, "logcapture", None) if not logcapture: context.logcapture = LoggingCapture()
def test_get_value_returns_all_log_records(self): __pychecker__ = "no-shadowbuiltin unusednames=x" class FakeConfig(object): logging_filter = None logging_format = None logging_datefmt = None logging_level = None fake_records = [object() for x in range(0, 10)] handler = LoggingCapture(FakeConfig()) handler.buffer = fake_records # pylint: disable=W0622 # W0622 Redefining built-in format with patch.object(handler.formatter, 'format') as format: format.return_value = 'foo' expected = '\n'.join(['foo'] * len(fake_records)) eq_(handler.getvalue(), expected) calls = [args[0][0] for args in format.call_args_list] eq_(calls, fake_records)
def setup_capture(self): # pylint: disable=W0201 # W0201 Attribute ... defined outside __init__ # => stdout_capture, log_capture (BUT WRONG) if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture
def setup_capture(self): if not self.context: self.context = Context(self) if self.config.stdout_capture: self.stdout_capture = six.moves.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = six.moves.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture
class ModelRunner(object): """ Test runner for a behave model (features). Provides the core functionality of a test runner and the functional API needed by model elements. .. attribute:: aborted This is set to true when the user aborts a test run (:exc:`KeyboardInterrupt` exception). Initially: False. Stored as derived attribute in :attr:`Context.aborted`. """ def __init__(self, config, features=None): self.config = config self.features = features or [] self.hooks = {} self.formatters = [] self.undefined_steps = [] self.context = None self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None # @property def _get_aborted(self): value = False if self.context: value = self.context.aborted return value # @aborted.setter def _set_aborted(self, value): assert self.context self.context._set_root_attribute('aborted', bool(value)) aborted = property(_get_aborted, _set_aborted, doc="Indicates that test run is aborted by the user.") def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): # try: with context.user_mode(): self.hooks[name](context, *args) # except KeyboardInterrupt: # self.aborted = True # if name not in ("before_all", "after_all"): # raise def setup_capture(self): if not self.context: self.context = Context(self) if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stdout: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture assert sys.stdout is self.stdout_capture if self.config.stderr_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stderr: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture assert sys.stderr is self.stderr_capture def stop_capture(self): if self.config.stdout_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stdout: sys.stdout = self.old_stdout self.old_stdout = None assert sys.stdout is not self.stdout_capture if self.config.stderr_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stderr: sys.stderr = self.old_stderr self.old_stderr = None assert sys.stderr is not self.stderr_capture def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon() def run_model(self, features=None): if not self.context: self.context = Context(self) if features is None: features = self.features # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) context = self.context self.setup_capture() self.run_hook('before_all', context) run_feature = not self.aborted failed_count = 0 undefined_steps_initial_size = len(self.undefined_steps) for feature in features: if run_feature: try: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop or self.aborted: # -- FAIL-EARLY: After first failure. run_feature = False except KeyboardInterrupt: self.aborted = True failed_count += 1 run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: if self.aborted: print("\nABORTED: By user.") for formatter in self.formatters: formatter.close() self.run_hook('after_all', self.context) for reporter in self.config.reporters: reporter.end() # if self.aborted: # print("\nABORTED: By user.") failed = ((failed_count > 0) or self.aborted or (len(self.undefined_steps) > undefined_steps_initial_size)) return failed def run(self): """ Implements the run method by running the model. """ self.context = Context(self) return self.run_model()
class ModelRunner(object): """ Test runner for a behave model (features). Provides the core functionality of a test runner and the functional API needed by model elements. .. attribute:: aborted This is set to true when the user aborts a test run (:exc:`KeyboardInterrupt` exception). Initially: False. Stored as derived attribute in :attr:`Context.aborted`. """ # pylint: disable=too-many-instance-attributes def __init__(self, config, features=None, step_registry=None): self.config = config self.features = features or [] self.hooks = {} self.formatters = [] self.undefined_steps = [] self.step_registry = step_registry self.context = None self.feature = None self.hook_failures = 0 self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None # @property def _get_aborted(self): value = False if self.context: value = self.context.aborted return value # @aborted.setter def _set_aborted(self, value): # pylint: disable=protected-access assert self.context, "REQUIRE: context, but context=%r" % self.context self.context._set_root_attribute("aborted", bool(value)) aborted = property(_get_aborted, _set_aborted, doc="Indicates that test run is aborted by the user.") def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): try: with context.user_mode(): self.hooks[name](context, *args) # except KeyboardInterrupt: # self.aborted = True # if name not in ("before_all", "after_all"): # raise except Exception as e: # pylint: disable=broad-except # -- HANDLE HOOK ERRORS: use_traceback = False if self.config.verbose: use_traceback = True ExceptionUtil.set_traceback(e) extra = u"" if "tag" in name: extra = "(tag=%s)" % args[0] error_text = ExceptionUtil.describe(e, use_traceback).rstrip() error_message = u"HOOK-ERROR in %s%s: %s" % (name, extra, error_text) print(error_message) self.hook_failures += 1 if "tag" in name: # -- SCENARIO or FEATURE statement = getattr(context, "scenario", context.feature) elif "all" in name: # -- ABORT EXECUTION: For before_all/after_all self.aborted = True statement = None else: # -- CASE: feature, scenario, step statement = args[0] if statement: # -- CASE: feature, scenario, step statement.hook_failed = True statement.store_exception_context(e) statement.error_message = error_message def setup_capture(self): if not self.context: self.context = Context(self) if self.config.stdout_capture: self.stdout_capture = StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stdout: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture assert sys.stdout is self.stdout_capture if self.config.stderr_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stderr: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture assert sys.stderr is self.stderr_capture def stop_capture(self): if self.config.stdout_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stdout: sys.stdout = self.old_stdout self.old_stdout = None assert sys.stdout is not self.stdout_capture if self.config.stderr_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stderr: sys.stderr = self.old_stderr self.old_stderr = None assert sys.stderr is not self.stderr_capture def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon() def run_model(self, features=None): # pylint: disable=too-many-branches if not self.context: self.context = Context(self) if self.step_registry is None: self.step_registry = the_step_registry if features is None: features = self.features # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) context = self.context self.hook_failures = 0 self.setup_capture() self.run_hook("before_all", context) run_feature = not self.aborted failed_count = 0 undefined_steps_initial_size = len(self.undefined_steps) for feature in features: if run_feature: try: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop or self.aborted: # -- FAIL-EARLY: After first failure. run_feature = False except KeyboardInterrupt: self.aborted = True failed_count += 1 run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: if self.aborted: print("\nABORTED: By user.") for formatter in self.formatters: formatter.close() self.run_hook("after_all", self.context) for reporter in self.config.reporters: reporter.end() failed = ((failed_count > 0) or self.aborted or (self.hook_failures > 0) or (len(self.undefined_steps) > undefined_steps_initial_size)) return failed def run(self): """ Implements the run method by running the model. """ self.context = Context(self) return self.run_model()
class Runner(object): def __init__(self, config): self.config = config self.hooks = {} self.features = [] self.passed = [] self.failed = [] self.undefined = [] self.skipped = [] self.path_manager = PathManager() self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.out_stdout = None def setup_paths(self): if self.config.paths: if self.config.verbose: print 'Supplied path:', ', '.join('"%s"' % path for path in self.config.paths) base_dir = os.path.abspath(self.config.paths[0]) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print 'Primary path is to a file so using its directory' base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print 'Using default path "./features"' base_dir = os.path.abspath('features') # Get the root. This is not guaranteed to be '/' because Windows. root_dir = os.path.split(base_dir)[0] new_base_dir = base_dir while True: if self.config.verbose: print 'Trying base directory:', new_base_dir if os.path.isdir(os.path.join(new_base_dir, 'steps')): break if os.path.isfile(os.path.join(new_base_dir, 'environment.py')): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find "steps" directory. Please '\ 'specify where to find your features.' else: print 'ERROR: Could not find "steps" directory in your '\ 'specified path "%s"' % base_dir raise ConfigError('No steps directory in "%s"' % base_dir) base_dir = new_base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith('.feature')]: break else: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find any "<name>.feature" files. '\ 'Please specify where to find your features.' else: print 'ERROR: Could not find any "<name>.feature" files '\ 'in your specified path "%s"' % base_dir raise ConfigError('No feature files in "%s"' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def load_hooks(self, filename='environment.py'): hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) def load_step_definitions(self, extra_step_paths=[]): steps_dir = os.path.join(self.base_dir, 'steps') # allow steps to import other stuff from the steps dir sys.path.insert(0, steps_dir) step_globals = { 'step_matcher': matchers.step_matcher, } for step_type in ('given', 'when', 'then', 'step'): decorator = getattr(step_registry, step_type) step_globals[step_type] = decorator step_globals[step_type.title()] = decorator for path in [steps_dir] + list(extra_step_paths): for name in os.listdir(path): if name.endswith('.py'): exec_file(os.path.join(path, name), step_globals) # clean up the path sys.path.pop(0) def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): with context.user_mode(): self.hooks[name](context, *args) def feature_files(self): files = [] for path in self.config.paths: if os.path.isdir(path): for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: if filename.endswith('.feature'): files.append(os.path.join(dirpath, filename)) elif path.startswith('@'): files.extend([filename.strip() for filename in open(path)]) elif os.path.exists(path): files.append(path) else: raise Exception("Can't find path: " + path) return files def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): self.load_hooks() self.load_step_definitions() context = self.context = Context(self) stream = self.config.output failed = False self.run_hook('before_all', context) for filename in self.feature_files(): if self.config.exclude(filename): continue feature = parser.parse_file(os.path.abspath(filename), language=self.config.lang) self.features.append(feature) self.feature = feature self.formatter = formatters.get_formatter(self.config, stream) self.formatter.uri(filename) failed |= feature.run(self) self.formatter.close() stream.write('\n') [reporter.feature(feature) for reporter in self.config.reporters] if failed and self.config.stop: break self.run_hook('after_all', context) [reporter.end() for reporter in self.config.reporters] return failed def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture if self.config.stderr_capture: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture def stop_capture(self): if self.config.stdout_capture: sys.stdout = self.old_stdout if self.config.stderr_capture: sys.stderr = self.old_stderr def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon()
class Runner(object): def __init__(self, config): self.config = config self.hooks = {} self.features = [] self.undefined = [] # -- XXX-JE-UNUSED: # self.passed = [] # self.failed = [] # self.skipped = [] self.path_manager = PathManager() self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.base_dir = None self.context = None self.formatter = None def setup_paths(self): if self.config.paths: if self.config.verbose: print 'Supplied path:', \ ', '.join('"%s"' % path for path in self.config.paths) base_dir = self.config.paths[0] if base_dir.startswith('@'): # -- USE: behave @features.txt base_dir = base_dir[1:] files = self.feature_files() if files: base_dir = os.path.dirname(files[0]) base_dir = os.path.abspath(base_dir) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print 'Primary path is to a file so using its directory' base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print 'Using default path "./features"' base_dir = os.path.abspath('features') # Get the root. This is not guaranteed to be '/' because Windows. root_dir = path_getrootdir(base_dir) new_base_dir = base_dir while True: if self.config.verbose: print 'Trying base directory:', new_base_dir if os.path.isdir(os.path.join(new_base_dir, 'steps')): break if os.path.isfile(os.path.join(new_base_dir, 'environment.py')): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find "steps" directory. Please '\ 'specify where to find your features.' else: print 'ERROR: Could not find "steps" directory in your '\ 'specified path "%s"' % base_dir raise ConfigError('No steps directory in "%s"' % base_dir) base_dir = new_base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith('.feature')]: break else: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find any "<name>.feature" files. '\ 'Please specify where to find your features.' else: print 'ERROR: Could not find any "<name>.feature" files '\ 'in your specified path "%s"' % base_dir raise ConfigError('No feature files in "%s"' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def load_hooks(self, filename='environment.py'): hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) def load_step_definitions(self, extra_step_paths=[]): steps_dir = os.path.join(self.base_dir, 'steps') # allow steps to import other stuff from the steps dir sys.path.insert(0, steps_dir) step_globals = { 'step_matcher': matchers.step_matcher, } # -- Default matcher can be overridden in "environment.py" hook. default_matcher = matchers.current_matcher for step_type in ('given', 'when', 'then', 'step'): decorator = getattr(step_registry, step_type) step_globals[step_type] = decorator step_globals[step_type.title()] = decorator for path in [steps_dir] + list(extra_step_paths): for name in os.listdir(path): if name.endswith('.py'): # -- LOAD STEP DEFINITION: # Reset to default matcher after each step-definition. # A step-definition may change the matcher 0..N times. # ENSURE: Each step definition has clean globals. step_module_globals = dict(step_globals) exec_file(os.path.join(path, name), step_module_globals) matchers.current_matcher = default_matcher # -- CLEANUP: Clean up the path. sys.path.pop(0) def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): with context.user_mode(): self.hooks[name](context, *args) @staticmethod def parse_features_configfile(features_configfile): """ Read textual file, ala '@features.txt'. This file contains: * a feature filename in each line * empty lines (skipped) * comment lines (skipped) Relative path names are evaluated relative to the configfile directory. A leading '@' (AT) character is removed from the configfile name. :param features_configfile: Name of features configfile. :return: List of feature filenames. """ if features_configfile.startswith('@'): features_configfile = features_configfile[1:] here = os.path.dirname(features_configfile) or "." files = [] for line in open(features_configfile).readlines(): line = line.strip() if not line: continue # SKIP: Over empty line(s). elif line.startswith('#'): continue # SKIP: Over comment line(s). files.append(os.path.normpath(os.path.join(here, line))) return files def feature_files(self): files = [] for path in self.config.paths: if os.path.isdir(path): for dirpath, dirnames, filenames in os.walk(path): dirnames.sort() for filename in sorted(filenames): if filename.endswith('.feature'): files.append(os.path.join(dirpath, filename)) elif path.startswith('@'): # -- USE: behave @list_of_features.txt files.extend(self.parse_features_configfile(path[1:])) elif os.path.exists(path): files.append(path) else: raise Exception("Can't find path: " + path) return files def parse_features(self, feature_files): """ Parse feature files and return list of Feature model objects. :param feature_files: List of feature files to parse. :return: List of feature objects. """ features = [] for filename in feature_files: if self.config.exclude(filename): continue filename2 = os.path.abspath(filename) feature = parser.parse_file(filename2, language=self.config.lang) if not feature: # -- CORNER-CASE: Feature file without any feature(s). continue features.append(feature) return features def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): self.load_hooks() self.load_step_definitions() context = self.context = Context(self) # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) self.setup_capture() stream = self.config.output failed_count = 0 self.run_hook('before_all', context) # -- STEP: Parse all feature files. features = self.parse_features(self.feature_files()) self.features.extend(features) # -- STEP: Run all features. undefined_steps_initial_size = len(self.undefined) run_feature = True for feature in features: if run_feature: self.feature = feature self.formatter = formatters.get_formatter(self.config, stream) self.formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop: # -- FAIL-EARLY: After first failure. run_feature = False self.formatter.close() # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) self.run_hook('after_all', context) for reporter in self.config.reporters: reporter.end() failed = ((failed_count > 0) or (len(self.undefined) > undefined_steps_initial_size)) return failed def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture if self.config.stderr_capture: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture def stop_capture(self): if self.config.stdout_capture: sys.stdout = self.old_stdout if self.config.stderr_capture: sys.stderr = self.old_stderr def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon()
class CaptureController(object): """Simplifies the lifecycle to capture output from various sources.""" def __init__(self, config): self.config = config self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None @property def captured(self): """Provides access of the captured output data. :return: Object that stores the captured output parts (as Captured). """ stdout = None stderr = None log_out = None if self.config.stdout_capture and self.stdout_capture: stdout = _text(self.stdout_capture.getvalue()) if self.config.stderr_capture and self.stderr_capture: stderr = _text(self.stderr_capture.getvalue()) if self.config.log_capture and self.log_capture: log_out = _text(self.log_capture.getvalue()) return Captured(stdout, stderr, log_out) def setup_capture(self, context): assert context is not None if self.config.stdout_capture: self.stdout_capture = StringIO() context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO() context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stdout: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture assert sys.stdout is self.stdout_capture if self.config.stderr_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stderr: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture assert sys.stderr is self.stderr_capture def stop_capture(self): if self.config.stdout_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stdout: sys.stdout = self.old_stdout self.old_stdout = None assert sys.stdout is not self.stdout_capture if self.config.stderr_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stderr: sys.stderr = self.old_stderr self.old_stderr = None assert sys.stderr is not self.stderr_capture def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon() def make_capture_report(self): """Combine collected output and return as string.""" return self.captured.make_report()
class Runner(object): def __init__(self, config): self.config = config self.hooks = {} self.features = [] self.undefined = [] # -- XXX-JE-UNUSED: # self.passed = [] # self.failed = [] # self.skipped = [] self.path_manager = PathManager() self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None self.base_dir = None self.context = None self.formatters = None def setup_paths(self): if self.config.paths: if self.config.verbose: print 'Supplied path:', \ ', '.join('"%s"' % path for path in self.config.paths) base_dir = self.config.paths[0] if base_dir.startswith('@'): # -- USE: behave @features.txt base_dir = base_dir[1:] files = self.feature_files() if files: base_dir = os.path.dirname(files[0]) base_dir = os.path.abspath(base_dir) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print 'Primary path is to a file so using its directory' base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print 'Using default path "./features"' base_dir = os.path.abspath('features') # Get the root. This is not guaranteed to be '/' because Windows. root_dir = path_getrootdir(base_dir) new_base_dir = base_dir while True: if self.config.verbose: print 'Trying base directory:', new_base_dir if os.path.isdir(os.path.join(new_base_dir, 'steps')): break if os.path.isfile(os.path.join(new_base_dir, 'environment.py')): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find "steps" directory. Please '\ 'specify where to find your features.' else: print 'ERROR: Could not find "steps" directory in your '\ 'specified path "%s"' % base_dir raise ConfigError('No steps directory in "%s"' % base_dir) base_dir = new_base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith('.feature')]: break else: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find any "<name>.feature" files. '\ 'Please specify where to find your features.' else: print 'ERROR: Could not find any "<name>.feature" files '\ 'in your specified path "%s"' % base_dir raise ConfigError('No feature files in "%s"' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def load_hooks(self, filename='environment.py'): hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) def load_step_definitions(self, extra_step_paths=[]): steps_dir = os.path.join(self.base_dir, 'steps') # allow steps to import other stuff from the steps dir sys.path.insert(0, steps_dir) step_globals = { 'step_matcher': matchers.step_matcher, } # -- Default matcher can be overridden in "environment.py" hook. default_matcher = matchers.current_matcher for step_type in ('given', 'when', 'then', 'step'): decorator = getattr(step_registry, step_type) step_globals[step_type] = decorator step_globals[step_type.title()] = decorator for path in [steps_dir] + list(extra_step_paths): for name in os.listdir(path): if name.endswith('.py'): # -- LOAD STEP DEFINITION: # Reset to default matcher after each step-definition. # A step-definition may change the matcher 0..N times. # ENSURE: Each step definition has clean globals. step_module_globals = dict(step_globals) exec_file(os.path.join(path, name), step_module_globals) matchers.current_matcher = default_matcher # -- CLEANUP: Clean up the path. sys.path.pop(0) def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): with context.user_mode(): self.hooks[name](context, *args) def feature_files(self): return collect_feature_files(self.config.paths) def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): self.load_hooks() self.load_step_definitions() context = self.context = Context(self) # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) self.setup_capture() stream_openers = self.config.outputs failed_count = 0 self.run_hook('before_all', context) # -- STEP: Parse all feature files. feature_files = [ filename for filename in self.feature_files() if not self.config.exclude(filename) ] features = parse_features(feature_files, language=self.config.lang) self.features.extend(features) # -- STEP: Run all features. self.formatters = formatters.get_formatter(self.config, stream_openers) undefined_steps_initial_size = len(self.undefined) run_feature = True for feature in features: if run_feature: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop: # -- FAIL-EARLY: After first failure. run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: for formatter in self.formatters: formatter.close() self.run_hook('after_all', context) for reporter in self.config.reporters: reporter.end() failed = ((failed_count > 0) or (len(self.undefined) > undefined_steps_initial_size)) return failed def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture if self.config.stderr_capture: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture def stop_capture(self): if self.config.stdout_capture: sys.stdout = self.old_stdout if self.config.stderr_capture: sys.stderr = self.old_stderr def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon()
class Runner(object): ''' Test runner for behave. .. attribute:: aborted This is set to true when the user aborts a test run (:exc:`KeyboardInterrupt` exception). Initially: False. Stored as derived attribute in :attr:`Context.aborted`. ''' def __init__(self, config): self.config = config self.hooks = {} self.features = [] self.undefined = [] # -- XXX-JE-UNUSED: # self.passed = [] # self.failed = [] # self.skipped = [] self.path_manager = PathManager() self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None self.base_dir = None self.context = None self.formatters = None # @property def _get_aborted(self): """ Indicates that a test run was aborted by the user (:exc:`KeyboardInterrupt` exception). Stored in :attr:`Context.aborted` attribute (as root attribute). :return: Current aborted state, initially false. :rtype: bool """ value = False if self.context: value = self.context.aborted return value # @aborted.setter def _set_aborted(self, value): """ Set the aborted value. :param value: New aborted value (as bool). """ assert self.context self.context._set_root_attribute('aborted', bool(value)) aborted = property(_get_aborted, _set_aborted, doc="Indicates that test run is aborted by the user.") def setup_paths(self): if self.config.paths: if self.config.verbose: print 'Supplied path:', \ ', '.join('"%s"' % path for path in self.config.paths) first_path = self.config.paths[0] if hasattr(first_path, "filename"): # -- BETTER: isinstance(first_path, FileLocation): first_path = first_path.filename base_dir = first_path if base_dir.startswith('@'): # -- USE: behave @features.txt base_dir = base_dir[1:] file_locations = self.feature_locations() if file_locations: base_dir = os.path.dirname(file_locations[0].filename) base_dir = os.path.abspath(base_dir) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print 'Primary path is to a file so using its directory' base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print 'Using default path "./features"' base_dir = os.path.abspath('features') # Get the root. This is not guaranteed to be '/' because Windows. root_dir = path_getrootdir(base_dir) new_base_dir = base_dir while True: if self.config.verbose: print 'Trying base directory:', new_base_dir if os.path.isdir(os.path.join(new_base_dir, 'steps')): break if os.path.isfile(os.path.join(new_base_dir, 'environment.py')): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find "steps" directory. Please '\ 'specify where to find your features.' else: print 'ERROR: Could not find "steps" directory in your '\ 'specified path "%s"' % base_dir raise ConfigError('No steps directory in "%s"' % base_dir) base_dir = new_base_dir self.config.base_dir = base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith('.feature')]: break else: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find any "<name>.feature" files. '\ 'Please specify where to find your features.' else: print 'ERROR: Could not find any "<name>.feature" files '\ 'in your specified path "%s"' % base_dir raise ConfigError('No feature files in "%s"' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def load_hooks(self, filename='environment.py'): hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) def load_step_definitions(self, extra_step_paths=[]): step_globals = { 'step_matcher': matchers.step_matcher, } setup_step_decorators(step_globals) # -- Allow steps to import other stuff from the steps dir # NOTE: Default matcher can be overridden in "environment.py" hook. steps_dir = os.path.join(self.base_dir, 'steps') paths = [steps_dir] + list(extra_step_paths) with PathManager(paths): default_matcher = matchers.current_matcher for path in paths: for name in sorted(os.listdir(path)): if name.endswith('.py'): # -- LOAD STEP DEFINITION: # Reset to default matcher after each step-definition. # A step-definition may change the matcher 0..N times. # ENSURE: Each step definition has clean globals. step_module_globals = step_globals.copy() exec_file(os.path.join(path, name), step_module_globals) matchers.current_matcher = default_matcher def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): # try: with context.user_mode(): self.hooks[name](context, *args) # except KeyboardInterrupt: # self.aborted = True # if name not in ("before_all", "after_all"): # raise def feature_locations(self): return collect_feature_locations(self.config.paths) def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): self.load_hooks() self.load_step_definitions() context = self.context = Context(self) assert not self.aborted # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) self.setup_capture() stream_openers = self.config.outputs failed_count = 0 self.run_hook('before_all', context) # -- STEP: Parse all feature files (by using their file location). feature_locations = [ filename for filename in self.feature_locations() if not self.config.exclude(filename) ] features = parse_features(feature_locations, language=self.config.lang) self.features.extend(features) # -- STEP: Run all features. self.formatters = formatters.get_formatter(self.config, stream_openers) undefined_steps_initial_size = len(self.undefined) run_feature = True for feature in features: if run_feature: try: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop or self.aborted: # -- FAIL-EARLY: After first failure. run_feature = False except KeyboardInterrupt: self.aborted = True failed_count += 1 run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: if self.aborted: print "\nABORTED: By user." for formatter in self.formatters: formatter.close() self.run_hook('after_all', context) for reporter in self.config.reporters: reporter.end() # if self.aborted: # print "\nABORTED: By user." failed = ((failed_count > 0) or self.aborted or (len(self.undefined) > undefined_steps_initial_size)) return failed def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture if self.config.stderr_capture: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture def stop_capture(self): if self.config.stdout_capture: sys.stdout = self.old_stdout if self.config.stderr_capture: sys.stderr = self.old_stderr def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon()
class Runner(object): def __init__(self, config): self.config = config self.hooks = {} self.features = [] self.passed = [] self.failed = [] self.undefined = [] self.skipped = [] self.path_manager = PathManager() self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.base_dir = None self.context = None self.formatter = None def setup_paths(self): if self.config.paths: if self.config.verbose: print 'Supplied path:', ', '.join('"%s"' % path for path in self.config.paths) base_dir = self.config.paths[0] if base_dir.startswith('@'): # -- USE: behave @features.txt base_dir = base_dir[1:] files = self.feature_files() if files: base_dir = os.path.dirname(files[0]) base_dir = os.path.abspath(base_dir) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print 'Primary path is to a file so using its directory' base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print 'Using default path "./features"' base_dir = os.path.abspath('features') # Get the root. This is not guaranteed to be '/' because Windows. root_dir = path_getrootdir(base_dir) new_base_dir = base_dir while True: if self.config.verbose: print 'Trying base directory:', new_base_dir if os.path.isdir(os.path.join(new_base_dir, 'steps')): break if os.path.isfile(os.path.join(new_base_dir, 'environment.py')): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find "steps" directory. Please '\ 'specify where to find your features.' else: print 'ERROR: Could not find "steps" directory in your '\ 'specified path "%s"' % base_dir raise ConfigError('No steps directory in "%s"' % base_dir) base_dir = new_base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith('.feature')]: break else: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find any "<name>.feature" files. '\ 'Please specify where to find your features.' else: print 'ERROR: Could not find any "<name>.feature" files '\ 'in your specified path "%s"' % base_dir raise ConfigError('No feature files in "%s"' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def load_hooks(self, filename='environment.py'): hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) def load_step_definitions(self, extra_step_paths=[]): steps_dir = os.path.join(self.base_dir, 'steps') # allow steps to import other stuff from the steps dir sys.path.insert(0, steps_dir) step_globals = { 'step_matcher': matchers.step_matcher, } # -- Default matcher can be overridden in "environment.py" hook. default_matcher = matchers.current_matcher for step_type in ('given', 'when', 'then', 'step'): decorator = getattr(step_registry, step_type) step_globals[step_type] = decorator step_globals[step_type.title()] = decorator for path in [steps_dir] + list(extra_step_paths): for name in os.listdir(path): if name.endswith('.py'): # -- LOAD STEP DEFINITION: # Reset to default matcher after each step-definition. # A step-definition may change the matcher 0..N times. exec_file(os.path.join(path, name), step_globals) matchers.current_matcher = default_matcher # -- CLEANUP: Clean up the path. sys.path.pop(0) def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): with context.user_mode(): self.hooks[name](context, *args) @staticmethod def parse_features_file(features_filename): """ Read textual file, ala '@features.txt' :param features_filename: Name of features file. :return: List of feature names. """ if features_filename.startswith('@'): features_filename = features_filename[1:] here = os.path.dirname(features_filename) or "." files = [] for line in open(features_filename).readlines(): line = line.strip() if not line: continue # SKIP: Over empty line(s). elif line.startswith('#'): continue # SKIP: Over comment line(s). files.append(os.path.normpath(os.path.join(here, line))) return files def feature_files(self): files = [] for path in self.config.paths: if os.path.isdir(path): for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: if filename.endswith('.feature'): files.append(os.path.join(dirpath, filename)) elif path.startswith('@'): # -- USE: behave @list_of_features.txt files.extend(self.parse_features_file(path[1:])) elif os.path.exists(path): files.append(path) else: raise Exception("Can't find path: " + path) return files def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): self.load_hooks() self.load_step_definitions() context = self.context = Context(self) # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) self.setup_capture() stream = self.config.output failed = False failed_count = 0 self.run_hook('before_all', context) for filename in self.feature_files(): if self.config.exclude(filename): continue feature = parser.parse_file(os.path.abspath(filename), language=self.config.lang) self.features.append(feature) self.feature = feature self.formatter = formatters.get_formatter(self.config, stream) self.formatter.uri(filename) failed = feature.run(self) if failed: failed_count += 1 self.formatter.close() for reporter in self.config.reporters: reporter.feature(feature) if failed and self.config.stop: break self.run_hook('after_all', context) for reporter in self.config.reporters: reporter.end() failed = (failed_count > 0) return failed def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture if self.config.stderr_capture: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture def stop_capture(self): if self.config.stdout_capture: sys.stdout = self.old_stdout if self.config.stderr_capture: sys.stderr = self.old_stderr def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon()
class Runner(object): """ Standard test runner for behave: * setup paths * loads environment hooks * loads step definitions * select feature files, parses them and creates model (elements) """ def __init__(self, config): self.config = config self.hooks = {} self.features= [] self.undefined_steps = [] self.path_manager = PathManager() self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None self.base_dir = None self.context = None self.formatters = None # @aborted.setter def _get_aborted(self): """ Indicates that a test run was aborted by the user (:exc:`KeyboardInterrupt` exception). Stored in :attr:`Context.aborted` attribute (as root attribute). :return: Current aborted state, initially false. :rtype: bool """ value = False if self.context: value = self.context.aborted return value def _set_aborted(self, value): """ Set the aborted value. :param value: New aborted value (as bool). """ assert self.context self.context._set_root_attribute('aborted', bool(value)) aborted = property(_get_aborted, _set_aborted, doc="Indicates that test run is aborted by the user.") def setup_paths(self): if self.config.paths: if self.config.verbose: print(('Supplied path:', ', '.join('"%s"' % path for path in self.config.paths))) first_path = self.config.paths[0] if hasattr(first_path, "filename"): # -- BETTER: isinstance(first_path, FileLocation): first_path = first_path.filename base_dir = first_path if base_dir.startswith('@'): # -- USE: behave @features.txt base_dir = base_dir[1:] file_locations = self.feature_locations() if file_locations: base_dir = os.path.dirname(file_locations[0].filename) base_dir = os.path.abspath(base_dir) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print('Primary path is to a file so using its directory') base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print('Using default path "./features"') base_dir = os.path.abspath('features') # Get the root. This is not guaranteed to be '/' because Windows. root_dir = path_getrootdir(base_dir) new_base_dir = base_dir while True: if self.config.verbose: print(('Trying base directory:', new_base_dir)) if os.path.isdir(os.path.join(new_base_dir, 'steps')): break if os.path.isfile(os.path.join(new_base_dir, 'environment')): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print('ERROR: Could not find "steps" directory. Please specify where to find your features.') else: print(('ERROR: Could not find "steps" directory in your specified path "%s"' % base_dir)) raise ConfigError('No steps directory in "%s"' % base_dir) base_dir = new_base_dir self.config.base_dir = base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith('.feature')]: break else: if self.config.verbose: if not self.config.paths: print('ERROR: Could not find any "<name>.feature" files. Please specify where to find your features.') else: print(('ERROR: Could not find any "<name>.feature" files in your specified path "%s"' % base_dir)) raise ConfigError('No feature files in "%s"' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def before_all_default_hook(self, context): """ Default implementation for :func:`before_all()` hook. Setup the logging subsystem based on the configuration data. """ context.config.setup_logging() def load_hooks(self, filename=None): filename = filename or self.config.environment_file hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) if 'before_all' not in self.hooks: self.hooks['before_all'] = self.before_all_default_hook def load_step_definitions(self, extra_step_paths=[]): step_globals = { 'step_matcher': matchers.step_matcher, } setup_step_decorators(step_globals) # -- Allow steps to import other stuff from the steps dir # NOTE: Default matcher can be overridden in "environment.py" hook. steps_dir = os.path.join(self.base_dir, 'steps') paths = [steps_dir] + list(extra_step_paths) with PathManager(paths): default_matcher = matchers.current_matcher for path in paths: for name in sorted(os.listdir(path)): if name.endswith('.py'): # -- LOAD STEP DEFINITION: # Reset to default matcher after each step-definition. # A step-definition may change the matcher 0..N times. # ENSURE: Each step definition has clean globals. # try: step_module_globals = step_globals.copy() exec_file(os.path.join(path, name), step_module_globals) matchers.current_matcher = default_matcher # except Exception as e: # e_text = _text(e) # print("Exception %s: %s" % (e.__class__.__name__, e_text)) # raise def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): # try: with context.user_mode(): self.hooks[name](context, *args) # except KeyboardInterrupt: # self.aborted = True # if name not in ("before_all", "after_all"): # raise def feature_locations(self): return collect_feature_locations(self.config.paths) def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): context = self.context = Context(self) self.load_hooks() self.load_step_definitions() assert not self.aborted stream_openers = self.config.outputs failed_count = 0 # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) self.setup_capture() self.run_hook('before_all', context) # -- STEP: Parse all feature files (by using their file location). feature_locations = [ filename for filename in self.feature_locations() if not self.config.exclude(filename) ] features = parse_features(feature_locations, language=self.config.lang) self.features.extend(features) # -- STEP: Multi-processing! if getattr(self.config, 'proc_count'): return self.run_multiproc() # -- STEP: Run all features. self.formatters = make_formatters(self.config, stream_openers) undefined_steps_initial_size = len(self.undefined_steps) run_feature = True for feature in features: if run_feature: try: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop or self.aborted: # -- FAIL-EARLY: After first failure. run_feature = False except KeyboardInterrupt: self.aborted = True failed_count += 1 run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: if self.aborted: print("\nABORTED: By user.") for formatter in self.formatters: formatter.close() self.run_hook('after_all', context) for reporter in self.config.reporters: reporter.end() # if self.aborted: # print "\nABORTED: By user." failed = ((failed_count > 0) or self.aborted or (len(self.undefined_steps) > undefined_steps_initial_size)) return failed def run_multiproc(self): if not multiprocessing: print ("ERROR: Cannot import multiprocessing module." " If you're on python2.5, go get the backport") return 1 self.config.format = ['plain'] self.parallel_element = getattr(self.config, 'parallel_element') if not self.parallel_element: self.parallel_element = 'scenario' print("INFO: Without giving --parallel-element, defaulting to 'scenario'...") else: if self.parallel_element != 'feature' and \ self.parallel_element != 'scenario': print(("ERROR: When using --processes, --parallel-element" " option must be set to 'feature' or 'scenario'. You gave '"+ str(self.parallel_element)+"', which isn't valid.")) return 1 # -- Prevent context warnings. def do_nothing(obj2, obj3): pass self.context._emit_warning = do_nothing self.joblist_index_queue = multiprocessing.Manager().JoinableQueue() self.resultsqueue = multiprocessing.Manager().JoinableQueue() self.joblist = [] scenario_count = 0 feature_count = 0 for feature in self.features: if self.parallel_element == 'feature' or 'serial' in feature.tags: self.joblist.append(feature) self.joblist_index_queue.put(feature_count + scenario_count) feature_count += 1 continue for scenario in feature.scenarios: if scenario.type == 'scenario': self.joblist.append(scenario) self.joblist_index_queue.put( feature_count + scenario_count) scenario_count += 1 else: for subscenario in scenario.scenarios: self.joblist.append(subscenario) self.joblist_index_queue.put( feature_count + scenario_count) scenario_count += 1 proc_count = int(getattr(self.config, 'proc_count')) if feature_count <= proc_count: proc_count = feature_count print(("INFO: {0} scenario(s) and {1} feature(s) queued for" " consideration by {2} workers. Some may be skipped if the" " -t option was given..." .format(scenario_count, feature_count, proc_count))) time.sleep(0.5) # processes = {} # n = 0 # procs = [] pool = multiprocessing.Pool(proc_count) test_runs = pool.map(self.worker, range(feature_count)) pool.close() pool.join() # for i in range(proc_count): # p = multiprocessing.Process(target=self.worker, args=(i, )) # procs.append(p) # processes[n] = p # n += 1 # # print(procs) # print(processes) # # for p in procs: # p.start() # # while True: # # if p.is_alive(): # # break # while len(processes) > 0: # for n in processes.keys(): # p = processes[n] # print(p) # time.sleep(0.5) # if p.exitcode is None and p.is_alive(): # pass # elif int(p.exitcode) is None and not p.is_alive(): # print('2nd if Processes restarted') # p.terminate() # p = multiprocessing.Process(target=self.worker, args=(i,)) # procs.append(p) # processes[n] = p # n += 1 # p.start() # elif int(p.exitcode) < 0: # print('3rd if Processes restarted') # p.terminate() # p = multiprocessing.Process(target=self.worker, args=(i,)) # procs.append(p) # processes[n] = p # n += 1 # p.start() # elif p.exitcode == 0: # p.join() # del processes[n] # print('Processes finished') self.run_hook('after_all', self.context) return self.multiproc_fullreport() def worker(self, proc_number): while 1: try: joblist_index = self.joblist_index_queue.get_nowait() except Exception as e: break current_job = self.joblist[joblist_index] writebuf = io.StringIO() self.setfeature(current_job) self.config.outputs = [] self.config.outputs.append(StreamOpener(stream=writebuf)) stream_openers = self.config.outputs self.formatters = make_formatters(self.config, stream_openers) for formatter in self.formatters: formatter.uri(current_job.filename) start_time = time.strftime("%Y-%m-%d %H:%M:%S") current_job.run(self) end_time = time.strftime("%Y-%m-%d %H:%M:%S") sys.stderr.write(current_job.status[0]+"\n") if current_job.type == 'feature': for reporter in self.config.reporters: reporter.feature(current_job) # self.clean_buffer(writebuf) job_report_text = self.generatereport( proc_number, current_job, start_time, end_time, writebuf) if job_report_text: results = dict() results['steps_passed'] = 0 results['steps_failed'] = 0 results['steps_skipped'] = 0 results['steps_undefined'] = 0 results['steps_untested'] = 0 results['jobtype'] = current_job.type results['reportinginfo'] = job_report_text results['status'] = current_job.status if current_job.type != 'feature': results['uniquekey'] = current_job.filename + current_job.feature.name else: results['scenarios_passed'] = 0 results['scenarios_failed'] = 0 results['scenarios_skipped'] = 0 self.countscenariostatus(current_job, results) self.countstepstatus(current_job, results) if current_job.type != 'feature' and getattr(self.config, 'junit'): results['junit_report'] = self.generate_junit_report(current_job, writebuf) self.resultsqueue.put(results) def setfeature(self, current_job): if current_job.type == 'feature': self.feature = current_job else: self.feature = current_job.feature def generatereport(self, proc_number, current_job, start_time, end_time, writebuf): if not writebuf.tell(): return "" reportheader = start_time + "|WORKER" + str(proc_number) + " START|" + \ "status:" + current_job.status + "|" + current_job.filename + "\n" reportfooter = end_time + "|WORKER" + str(proc_number) + " END|" + \ "status:" + current_job.status + "|" + current_job.filename + \ "|Duration:" + str(current_job.duration) if self.config.format[0] == 'plain' and len(current_job.tags): tags = "@" for tag in current_job.tags: tags += tag + " " reportheader += "\n" + tags + "\n" if current_job.status == 'failed': self.getskippedsteps(current_job, writebuf) try: writebuf.seek(0) except UnicodeDecodeError as e: print(("SEEK: %s" % e)) return "" header_unicode = self.to_unicode(reportheader) footer_unicode = self.to_unicode(reportfooter) try: result = header_unicode + writebuf.read() + "\n" + footer_unicode except UnicodeError as err: print(("HEADER ERROR: %s" % err)) result = header_unicode + str(writebuf.read(), errors='replace') + "\n" + footer_unicode #result = err.object[0:err.start]+err.object[err.end:len(err.object)-1] return result def getskippedsteps(self, current_job, writebuf): if current_job.type != 'scenario': [self.getskippedsteps(s, writebuf) for s in current_job.scenarios] else: for step in current_job.all_steps: if step.status == 'skipped': writebuf.write("Skipped step because of previous error - Scenario:{0}|step:{1}\n" .format(current_job.name, step.name)) def countscenariostatus(self, current_job, results): if current_job.type != 'scenario': [self.countscenariostatus(s, results) for s in current_job.scenarios] else: results['scenarios_' + current_job.status] += 1 def countstepstatus(self, current_job, results): if current_job.type != 'scenario': [self.countstepstatus(s, results) for s in current_job.scenarios] else: for step in current_job.all_steps: results['steps_' + step.status] += 1 def multiproc_fullreport(self): metrics = collections.defaultdict(int) combined_features_from_scenarios_results = collections.defaultdict(lambda: '') junit_report_objs = [] while not self.resultsqueue.empty(): print(("\n" * 3)) print(("_" * 75)) jobresult = self.resultsqueue.get() try: print((self.to_unicode(jobresult['reportinginfo']))) except Exception as e: logging.info(e) if 'junit_report' in jobresult: junit_report_objs.append(jobresult['junit_report']) if jobresult['jobtype'] != 'feature': combined_features_from_scenarios_results[ jobresult['uniquekey']] += '|' + jobresult['status'] metrics['scenarios_' + jobresult['status']] += 1 else: metrics['features_' + jobresult['status']] += 1 metrics['steps_passed'] += jobresult['steps_passed'] metrics['steps_failed'] += jobresult['steps_failed'] metrics['steps_skipped'] += jobresult['steps_skipped'] metrics['steps_undefined'] += jobresult['steps_undefined'] if jobresult['jobtype'] == 'feature': metrics['scenarios_passed'] += jobresult['scenarios_passed'] metrics['scenarios_failed'] += jobresult['scenarios_failed'] metrics['scenarios_skipped'] += jobresult['scenarios_skipped'] for uniquekey in combined_features_from_scenarios_results: if 'failed' in combined_features_from_scenarios_results[uniquekey]: metrics['features_failed'] += 1 elif 'passed' in combined_features_from_scenarios_results[uniquekey]: metrics['features_passed'] += 1 else: metrics['features_skipped'] += 1 print(("\n" * 3)) print(("_" * 75)) print((("{0} features passed, {1} features failed, {2} features skipped\n" "{3} scenarios passed, {4} scenarios failed, {5} scenarios skipped\n" "{6} steps passed, {7} steps failed, {8} steps skipped, {9} steps undefined\n")\ .format( metrics['features_passed'], metrics['features_failed'], metrics['features_skipped'], metrics['scenarios_passed'], metrics['scenarios_failed'], metrics['scenarios_skipped'], metrics['steps_passed'], metrics['steps_failed'], metrics['steps_skipped'], metrics['steps_undefined']))) if getattr(self.config,'junit'): self.write_paralleltestresults_to_junitfile(junit_report_objs) return metrics['features_failed'] def generate_junit_report(self, cj, writebuf): report_obj = {} report_string = "" report_obj['filebasename'] = cj.location.basename()[:-8] report_obj['feature_name'] = cj.feature.name report_obj['status'] = cj.status report_obj['duration'] = round(cj.duration,4) report_string += '<testcase classname="' report_string += report_obj['filebasename']+'.' report_string += report_obj['feature_name']+'" ' report_string += 'name="'+cj.name+'" ' report_string += 'status="'+cj.status+'" ' report_string += 'time="'+str(round(cj.duration,4))+'">' if cj.status == 'failed': report_string += self.get_junit_error(cj, writebuf) report_string += "<system-out>\n<![CDATA[\n" report_string += "@scenario.begin\n" writebuf.seek(0) loglines = writebuf.readlines() report_string += loglines[1] for step in cj.all_steps: report_string += " "*4 report_string += step.keyword + " " report_string += step.name + " ... " report_string += step.status + " in " report_string += str(round(step.duration,4)) + "s\n" report_string += "\[email protected]\n" report_string += "-"*80 report_string += "\n" report_string += self.get_junit_stdoutstderr(cj,loglines) report_string += "</testcase>" report_obj['report_string'] = report_string report_obj = convert(report_obj) return report_obj def get_junit_stdoutstderr(self, cj, loglines): substring = "" if cj.status == 'passed': substring += "\nCaptured stdout:\n" substring += cj.stdout substring += "\n]]>\n</system-out>" if cj.stderr: substring += "<system-err>\n<![CDATA[\n" substring += "Captured stderr:\n" substring += cj.stderr substring += "\n]]>\n</system-err>" return substring q = 0 while q < len(loglines): if loglines[q] == "Captured stdout:\n": while q < len(loglines) and \ loglines[q] != "Captured stderr:\n": substring += loglines[q] q += 1 break q += 1 substring += "]]>\n</system-out>" if q < len(loglines): substring += "<system-err>\n<![CDATA[\n" while q < len(loglines): substring += loglines[q] q = q + 1 substring += "]]>\n</system-err>" return substring def get_junit_error(self, cj, writebuf): failed_step = None error_string = "" error_string += '<error message="' for step in cj.steps: if step.status == 'failed': failed_step = step break if not failed_step: error_string += "Unknown Error" '" type="AttributeError">\n' error_string += "Failing step: Unknown\n" error_string += "<![CDATA[\n" error_string += "]]>\n" error_string += "</error>" return error_string try: error_string += failed_step.exception[0]+'" ' except Exception: error_string += 'No Exception" ' error_string += 'type="' error_string += re.sub(".*?\.(.*?)\'.*","\\1",\ str(type(failed_step.exception)))+'">\n' error_string += "Failing step: " error_string += failed_step.name + " ... failed in " error_string += str(round(failed_step.duration,4))+"s\n" error_string += "Location: " + str(failed_step.location) error_string += "<![CDATA[\n" error_string += failed_step.error_message error_string += "]]>\n</error>" return error_string def write_paralleltestresults_to_junitfile(self,junit_report_objs): feature_reports = {} for jro in junit_report_objs: #NOTE: There's an edge-case where this key would not be unique #Where a feature has the same filename and feature name but #different directory. uniquekey = jro['filebasename']+"."+jro['feature_name'] if uniquekey not in feature_reports: newfeature = {} newfeature['duration'] = float(jro['duration']) newfeature['statuses'] = jro['status'] newfeature['filebasename'] = jro['filebasename'] newfeature['total_scenarios'] = 1 newfeature['data'] = jro['report_string'] feature_reports[uniquekey] = newfeature else: feature_reports[uniquekey]['duration'] += float(jro['duration']) feature_reports[uniquekey]['statuses'] += jro['status'] feature_reports[uniquekey]['total_scenarios'] += 1 feature_reports[uniquekey]['data'] += jro['report_string'] for uniquekey in list(feature_reports.keys()): filedata = "<?xml version='1.0' encoding='UTF-8'?>\n" filedata += '<testsuite errors="' filedata += str(len(re.findall\ ("failed",feature_reports[uniquekey]['statuses']))) filedata += '" failures="0" name="' filedata += uniquekey+'" ' filedata += 'skipped="' filedata += str(len(re.findall\ ("skipped",feature_reports[uniquekey]['statuses']))) filedata += '" tests="' filedata += str(feature_reports[uniquekey]['total_scenarios']) filedata += '" time="' filedata += str(round(feature_reports[uniquekey]['duration'],4)) filedata += '">' filedata += "\n\n" filedata += feature_reports[uniquekey]['data'] filedata += "</testsuite>" outputdir = "reports" custdir = getattr(self.config,'junit_directory') if custdir: outputdir = custdir if not os.path.exists(outputdir): os.makedirs(outputdir) filename = outputdir+"/"+"TESTS-" filename += feature_reports[uniquekey]['filebasename'] filename += ".xml" fd = open(filename,"wb") fd.write(filedata.encode('utf8')) fd.close() def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = io.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = io.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture if self.config.stderr_capture: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture def stop_capture(self): if self.config.stdout_capture: sys.stdout = self.old_stdout if self.config.stderr_capture: sys.stderr = self.old_stderr def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon() # def clean_buffer(self, buf): # for i in range(len(buf.buflist)): # buf.buflist[i] = self.to_unicode(buf.buflist[i]) @staticmethod def to_unicode(var): string = str(var) if isinstance(var, int) else var return str(string) if isinstance(string, str) else string
class ModelRunner(object): """ Test runner for a behave model (features). Provides the core functionality of a test runner and the functional API needed by model elements. .. attribute:: aborted This is set to true when the user aborts a test run (:exc:`KeyboardInterrupt` exception). Initially: False. Stored as derived attribute in :attr:`Context.aborted`. """ # pylint: disable=too-many-instance-attributes def __init__(self, config, features=None, step_registry=None): self.config = config self.features = features or [] self.hooks = {} self.formatters = [] self.undefined_steps = [] self.step_registry = step_registry self.context = None self.feature = None self.hook_failures = 0 self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None # @property def _get_aborted(self): value = False if self.context: value = self.context.aborted return value # @aborted.setter def _set_aborted(self, value): # pylint: disable=protected-access assert self.context self.context._set_root_attribute("aborted", bool(value)) aborted = property(_get_aborted, _set_aborted, doc="Indicates that test run is aborted by the user.") def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): try: with context.user_mode(): self.hooks[name](context, *args) # except KeyboardInterrupt: # self.aborted = True # if name not in ("before_all", "after_all"): # raise except Exception as e: # pylint: disable=broad-except # -- HANDLE HOOK ERRORS: use_traceback = False if self.config.verbose: use_traceback = True ExceptionUtil.set_traceback(e) extra = u"" if "tag" in name: extra = "(tag=%s)" % args[0] error_text = ExceptionUtil.describe(e, use_traceback) print(u"HOOK-ERROR in %s%s: %s" % (name, extra, error_text)) self.hook_failures += 1 if "step" in name: step = args[0] step.hook_failed = True elif "tag" in name: # -- FEATURE or SCENARIO => Use Feature as collector. context.feature.hook_failed = True elif "scenario" in name: scenario = args[0] scenario.hook_failed = True elif "feature" in name: feature = args[0] feature.hook_failed = True elif "all" in name: # -- ABORT EXECUTION: For before_all/after_all self.aborted = True def setup_capture(self): if not self.context: self.context = Context(self) if self.config.stdout_capture: self.stdout_capture = StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stdout: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture assert sys.stdout is self.stdout_capture if self.config.stderr_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stderr: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture assert sys.stderr is self.stderr_capture def stop_capture(self): if self.config.stdout_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stdout: sys.stdout = self.old_stdout self.old_stdout = None assert sys.stdout is not self.stdout_capture if self.config.stderr_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stderr: sys.stderr = self.old_stderr self.old_stderr = None assert sys.stderr is not self.stderr_capture def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon() def run_model(self, features=None): # pylint: disable=too-many-branches if not self.context: self.context = Context(self) if self.step_registry is None: self.step_registry = the_step_registry if features is None: features = self.features # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) context = self.context self.hook_failures = 0 self.setup_capture() self.run_hook("before_all", context) run_feature = not self.aborted failed_count = 0 undefined_steps_initial_size = len(self.undefined_steps) for feature in features: if run_feature: try: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop or self.aborted: # -- FAIL-EARLY: After first failure. run_feature = False except KeyboardInterrupt: self.aborted = True failed_count += 1 run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: if self.aborted: print("\nABORTED: By user.") for formatter in self.formatters: formatter.close() self.run_hook("after_all", self.context) for reporter in self.config.reporters: reporter.end() failed = ((failed_count > 0) or self.aborted or (self.hook_failures > 0) or (len(self.undefined_steps) > undefined_steps_initial_size)) return failed def run(self): """ Implements the run method by running the model. """ self.context = Context(self) return self.run_model()
class Runner(object): ''' Test runner for behave. .. attribute:: aborted This is set to true when the user aborts a test run (:exc:`KeyboardInterrupt` exception). Initially: False. Stored as derived attribute in :attr:`Context.aborted`. ''' def __init__(self, config): self.config = config self.hooks = {} self.features = [] self.undefined = [] # -- XXX-JE-UNUSED: # self.passed = [] # self.failed = [] # self.skipped = [] self.path_manager = PathManager() self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None self.base_dir = None self.context = None self.formatters = None # @property def _get_aborted(self): """ Indicates that a test run was aborted by the user (:exc:`KeyboardInterrupt` exception). Stored in :attr:`Context.aborted` attribute (as root attribute). :return: Current aborted state, initially false. :rtype: bool """ value = False if self.context: value = self.context.aborted return value # @aborted.setter def _set_aborted(self, value): """ Set the aborted value. :param value: New aborted value (as bool). """ assert self.context self.context._set_root_attribute('aborted', bool(value)) aborted = property(_get_aborted, _set_aborted, doc="Indicates that test run is aborted by the user.") def setup_paths(self): if self.config.paths: if self.config.verbose: print 'Supplied path:', \ ', '.join('"%s"' % path for path in self.config.paths) first_path = self.config.paths[0] if hasattr(first_path, "filename"): # -- BETTER: isinstance(first_path, FileLocation): first_path = first_path.filename base_dir = first_path if base_dir.startswith('@'): # -- USE: behave @features.txt base_dir = base_dir[1:] file_locations = self.feature_locations() if file_locations: base_dir = os.path.dirname(file_locations[0].filename) base_dir = os.path.abspath(base_dir) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print 'Primary path is to a file so using its directory' base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print 'Using default path "./features"' base_dir = os.path.abspath('features') # Get the root. This is not guaranteed to be '/' because Windows. root_dir = path_getrootdir(base_dir) new_base_dir = base_dir while True: if self.config.verbose: print 'Trying base directory:', new_base_dir if os.path.isdir(os.path.join(new_base_dir, 'steps')): break if os.path.isfile(os.path.join(new_base_dir, 'environment.py')): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find "steps" directory. Please '\ 'specify where to find your features.' else: print 'ERROR: Could not find "steps" directory in your '\ 'specified path "%s"' % base_dir raise ConfigError('No steps directory in "%s"' % base_dir) base_dir = new_base_dir self.config.base_dir = base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith('.feature')]: break else: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find any "<name>.feature" files. '\ 'Please specify where to find your features.' else: print 'ERROR: Could not find any "<name>.feature" files '\ 'in your specified path "%s"' % base_dir raise ConfigError('No feature files in "%s"' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def before_all_default_hook(self, context): """ Default implementation for :func:`before_all()` hook. Setup the logging subsystem based on the configuration data. """ context.config.setup_logging() def load_hooks(self, filename='environment.py'): hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) if 'before_all' not in self.hooks: self.hooks['before_all'] = self.before_all_default_hook def load_step_definitions(self, extra_step_paths=[]): step_globals = { 'step_matcher': matchers.step_matcher, } setup_step_decorators(step_globals) # -- Allow steps to import other stuff from the steps dir # NOTE: Default matcher can be overridden in "environment.py" hook. steps_dir = os.path.join(self.base_dir, 'steps') paths = [steps_dir] + list(extra_step_paths) with PathManager(paths): default_matcher = matchers.current_matcher for path in paths: for name in sorted(os.listdir(path)): if name.endswith('.py'): # -- LOAD STEP DEFINITION: # Reset to default matcher after each step-definition. # A step-definition may change the matcher 0..N times. # ENSURE: Each step definition has clean globals. step_module_globals = step_globals.copy() exec_file(os.path.join(path, name), step_module_globals) matchers.current_matcher = default_matcher def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): # try: with context.user_mode(): self.hooks[name](context, *args) # except KeyboardInterrupt: # self.aborted = True # if name not in ("before_all", "after_all"): # raise def feature_locations(self): return collect_feature_locations(self.config.paths) def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): context = self.context = Context(self) self.load_hooks() self.load_step_definitions() assert not self.aborted stream_openers = self.config.outputs failed_count = 0 # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) self.setup_capture() self.run_hook('before_all', context) # -- STEP: Parse all feature files (by using their file location). feature_locations = [ filename for filename in self.feature_locations() if not self.config.exclude(filename) ] features = parse_features(feature_locations, language=self.config.lang) self.features.extend(features) # -- STEP: Run all features. self.formatters = formatters.get_formatter(self.config, stream_openers) undefined_steps_initial_size = len(self.undefined) run_feature = True for feature in features: if run_feature: try: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop or self.aborted: # -- FAIL-EARLY: After first failure. run_feature = False except KeyboardInterrupt: self.aborted = True failed_count += 1 run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: if self.aborted: print "\nABORTED: By user." for formatter in self.formatters: formatter.close() self.run_hook('after_all', context) for reporter in self.config.reporters: reporter.end() # if self.aborted: # print "\nABORTED: By user." failed = ((failed_count > 0) or self.aborted or (len(self.undefined) > undefined_steps_initial_size)) return failed def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stdout: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture assert sys.stdout is self.stdout_capture if self.config.stderr_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stderr: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture assert sys.stderr is self.stderr_capture def stop_capture(self): if self.config.stdout_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stdout: sys.stdout = self.old_stdout self.old_stdout = None assert sys.stdout is not self.stdout_capture if self.config.stderr_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stderr: sys.stderr = self.old_stderr self.old_stderr = None assert sys.stderr is not self.stderr_capture def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon()
class Runner(object): def __init__(self, config): self.config = config self.hooks = {} self.features = [] self.passed = [] self.failed = [] self.undefined = [] self.skipped = [] self.path_manager = PathManager() self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.out_stdout = None def setup_paths(self): if self.config.paths: if self.config.verbose: print 'Supplied path:', ', '.join( '"%s"' % path for path in self.config.paths) base_dir = os.path.abspath(self.config.paths[0]) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print 'Primary path is to a file so using its directory' base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print 'Using default path "./features"' base_dir = os.path.abspath('features') # Get the root. This is not guaranteed to be '/' because Windows. root_dir = os.path.split(base_dir)[0] new_base_dir = base_dir while True: if self.config.verbose: print 'Trying base directory:', new_base_dir if os.path.isdir(os.path.join(new_base_dir, 'steps')): break if os.path.isfile(os.path.join(new_base_dir, 'environment.py')): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find "steps" directory. Please '\ 'specify where to find your features.' else: print 'ERROR: Could not find "steps" directory in your '\ 'specified path "%s"' % base_dir raise ConfigError('No steps directory in "%s"' % base_dir) base_dir = new_base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith('.feature')]: break else: if self.config.verbose: if not self.config.paths: print 'ERROR: Could not find any "<name>.feature" files. '\ 'Please specify where to find your features.' else: print 'ERROR: Could not find any "<name>.feature" files '\ 'in your specified path "%s"' % base_dir raise ConfigError('No feature files in "%s"' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def load_hooks(self, filename='environment.py'): hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) def load_step_definitions(self, extra_step_paths=[]): steps_dir = os.path.join(self.base_dir, 'steps') # allow steps to import other stuff from the steps dir sys.path.insert(0, steps_dir) step_globals = { 'step_matcher': matchers.step_matcher, } for step_type in ('given', 'when', 'then', 'step'): decorator = getattr(step_registry, step_type) step_globals[step_type] = decorator step_globals[step_type.title()] = decorator for path in [steps_dir] + list(extra_step_paths): for name in os.listdir(path): if name.endswith('.py'): exec_file(os.path.join(path, name), step_globals) # clean up the path sys.path.pop(0) def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): with context.user_mode(): self.hooks[name](context, *args) def feature_files(self): files = [] for path in self.config.paths: if os.path.isdir(path): for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: if filename.endswith('.feature'): files.append(os.path.join(dirpath, filename)) elif path.startswith('@'): files.extend([filename.strip() for filename in open(path)]) elif os.path.exists(path): files.append(path) else: raise Exception("Can't find path: " + path) return files def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): self.load_hooks() self.load_step_definitions() context = self.context = Context(self) stream = self.config.output failed = False self.run_hook('before_all', context) for filename in self.feature_files(): if self.config.exclude(filename): continue feature = parser.parse_file(os.path.abspath(filename), language=self.config.lang) self.features.append(feature) self.feature = feature self.formatter = formatters.get_formatter(self.config, stream) self.formatter.uri(filename) failed = feature.run(self) self.formatter.close() stream.write('\n') [reporter.feature(feature) for reporter in self.config.reporters] if failed and self.config.stop: break self.run_hook('after_all', context) [reporter.end() for reporter in self.config.reporters] return failed def setup_capture(self): if self.config.stdout_capture: self.stdout_capture = StringIO.StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO.StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture if self.config.stderr_capture: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture def stop_capture(self): if self.config.stdout_capture: sys.stdout = self.old_stdout if self.config.stderr_capture: sys.stderr = self.old_stderr def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon()
class ModelRunner(object): """ Test runner for a behave model (features). Provides the core functionality of a test runner and the functional API needed by model elements. .. attribute:: aborted This is set to true when the user aborts a test run (:exc:`KeyboardInterrupt` exception). Initially: False. Stored as derived attribute in :attr:`Context.aborted`. """ def __init__(self, config, features=None): self.config = config self.features = features or [] self.hooks = {} self.formatters = [] self.undefined_steps = [] self.context = None self.feature = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None # @property def _get_aborted(self): value = False if self.context: value = self.context.aborted return value # @aborted.setter def _set_aborted(self, value): assert self.context self.context._set_root_attribute('aborted', bool(value)) aborted = property(_get_aborted, _set_aborted, doc="Indicates that test run is aborted by the user.") def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): # try: with context.user_mode(): self.hooks[name](context, *args) # except KeyboardInterrupt: # self.aborted = True # if name not in ("before_all", "after_all"): # raise def setup_capture(self): if not self.context: self.context = Context(self) if self.config.stdout_capture: self.stdout_capture = StringIO() self.context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO() self.context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() self.context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stdout: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture assert sys.stdout is self.stdout_capture if self.config.stderr_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stderr: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture assert sys.stderr is self.stderr_capture def stop_capture(self): if self.config.stdout_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stdout: sys.stdout = self.old_stdout self.old_stdout = None assert sys.stdout is not self.stdout_capture if self.config.stderr_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stderr: sys.stderr = self.old_stderr self.old_stderr = None assert sys.stderr is not self.stderr_capture def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon() def run_model(self, features=None): if not self.context: self.context = Context(self) if features is None: features = self.features # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) context = self.context self.setup_capture() self.run_hook('before_all', context) run_feature = not self.aborted failed_count = 0 undefined_steps_initial_size = len(self.undefined_steps) for feature in features: if run_feature: try: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) tries = int(self.config.retry_count) failed = True while failed and tries > 0: failed = feature.run(self) tries -= 1 if failed: failed_count += 1 if self.config.stop or self.aborted: # -- FAIL-EARLY: After first failure. run_feature = False except KeyboardInterrupt: self.aborted = True failed_count += 1 run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: if self.aborted: print("\nABORTED: By user.") for formatter in self.formatters: formatter.close() self.run_hook('after_all', self.context) for reporter in self.config.reporters: reporter.end() # if self.aborted: # print("\nABORTED: By user.") failed = ((failed_count > 0) or self.aborted or (len(self.undefined_steps) > undefined_steps_initial_size)) return failed def run(self): """ Implements the run method by running the model. """ self.context = Context(self) return self.run_model()