def collect_feature_locations(paths, strict=True): """ Collect feature file names by processing list of paths (from command line). A path can be a: * filename (ending with ".feature") * location, ala "{filename}:{line_number}" * features configuration filename, ala "@features.txt" * directory, to discover and collect all "*.feature" files below. :param paths: Paths to process. :return: Feature file locations to use (as list of FileLocations). """ locations = [] for path in paths: if os.path.isdir(path): for dirpath, dirnames, filenames in os.walk(path, followlinks=True): dirnames.sort() for filename in sorted(filenames): if filename.endswith(".feature"): location = FileLocation(os.path.join(dirpath, filename)) locations.append(location) elif path.startswith('@'): # -- USE: behave @list_of_features.txt locations.extend(FeatureListParser.parse_file(path[1:])) else: # -- OTHERWISE: Normal filename or location (schema: filename:line) location = FileLocationParser.parse(path) if not location.filename.endswith(".feature"): raise InvalidFilenameError(location.filename) elif location.exists(): locations.append(location) elif strict: raise FileNotFoundError(path) return locations
def parse(cls, text): match = cls.pattern.match(text) if match: filename = match.group("filename").strip() line = int(match.group("line")) return FileLocation(filename, line) # -- NORMAL PATH/FILENAME: filename = text.strip() return FileLocation(filename)
def make_location(step_function): """Extracts the location information from the step function and builds a FileLocation object with (filename, line_number) info. :param step_function: Function whose location should be determined. :return: FileLocation object for step function. """ return FileLocation.for_function(step_function)
def make_location(step_function): ''' Extracts the location information from the step function and builds the location string (schema: "{source_filename}:{line_number}"). :param step_function: Function whose location should be determined. :return: Step function location as string. ''' step_function_code = six.get_function_code(step_function) filename = os.path.relpath(step_function_code.co_filename, os.getcwd()) line_number = step_function_code.co_firstlineno return FileLocation(filename, line_number)
def parse_features(feature_files, language=None): """ Parse feature files and return list of Feature model objects. Handles: * feature file names, ala "alice.feature" * feature file locations, ala: "alice.feature:10" :param feature_files: List of feature file names to parse. :param language: Default language to use. :return: List of feature objects. """ scenario_collector = FeatureScenarioLocationCollector() features = [] for location in feature_files: if not isinstance(location, FileLocation): assert isinstance(location, string_types) location = FileLocation(os.path.normpath(location)) if location.filename == scenario_collector.filename: scenario_collector.add_location(location) continue elif scenario_collector.feature: # -- ADD CURRENT FEATURE: As collection of scenarios. current_feature = scenario_collector.build_feature() features.append(current_feature) scenario_collector.clear() # -- NEW FEATURE: assert isinstance(location, FileLocation) filename = os.path.abspath(location.filename) feature = parser.parse_file(filename, language=language) if feature: # -- VALID FEATURE: # SKIP CORNER-CASE: Feature file without any feature(s). scenario_collector.feature = feature scenario_collector.add_location(location) # -- FINALLY: if scenario_collector.feature: current_feature = scenario_collector.build_feature() features.append(current_feature) return features
class TestFileLocation(unittest.TestCase): # pylint: disable=invalid-name ordered_locations1 = [ FileLocation("features/alice.feature", 1), FileLocation("features/alice.feature", 5), FileLocation("features/alice.feature", 10), FileLocation("features/alice.feature", 11), FileLocation("features/alice.feature", 100), ] ordered_locations2 = [ FileLocation("features/alice.feature", 1), FileLocation("features/alice.feature", 10), FileLocation("features/bob.feature", 5), FileLocation("features/charly.feature", None), FileLocation("features/charly.feature", 0), FileLocation("features/charly.feature", 100), ] same_locations = [ ( FileLocation("alice.feature"), FileLocation("alice.feature", None), ), ( FileLocation("alice.feature", 10), FileLocation("alice.feature", 10), ), ( FileLocation("features/bob.feature", 11), FileLocation("features/bob.feature", 11), ), ] def test_compare_equal(self): for value1, value2 in self.same_locations: eq_(value1, value2) def test_compare_equal_with_string(self): for location in self.ordered_locations2: eq_(location, location.filename) eq_(location.filename, location) def test_compare_not_equal(self): for value1, value2 in self.same_locations: assert not (value1 != value2) # pylint: disable=unneeded-not, superfluous-parens for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value1 != value2 def test_compare_less_than(self): for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2)) assert value1 != value2 def test_compare_less_than_with_string(self): locations = self.ordered_locations2 for value1, value2 in zip(locations, locations[1:]): if value1.filename == value2.filename: continue assert value1 < value2.filename, \ "FAILED: %s < %s" % (_text(value1), _text(value2.filename)) assert value1.filename < value2, \ "FAILED: %s < %s" % (_text(value1.filename), _text(value2)) def test_compare_greater_than(self): for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1)) assert value2 != value1 def test_compare_less_or_equal(self): for value1, value2 in self.same_locations: assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2)) assert value1 == value2 for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2)) assert value1 != value2 def test_compare_greater_or_equal(self): for value1, value2 in self.same_locations: assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1)) assert value2 == value1 for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1)) assert value2 != value1 def test_filename_should_be_same_as_self(self): for location in self.ordered_locations2: assert location == location.filename assert location.filename == location def test_string_conversion(self): for location in self.ordered_locations2: expected = u"%s:%s" % (location.filename, location.line) if location.line is None: expected = location.filename assert six.text_type(location) == expected def test_repr_conversion(self): for location in self.ordered_locations2: expected = u'<FileLocation: filename="%s", line=%s>' % \ (location.filename, location.line) actual = repr(location) assert actual == expected, "FAILED: %s == %s" % (actual, expected)