Example #1
0
class InMemoryEnvironment(object):
    @classmethod
    def attribute(selfclass, test_func):
        pass
    
    def __init__(self, name):
        self.name = name
        self.storage = MemStorage("testing")
        self.editor_content = ""
    
    def deft(self, *args, **kwargs):
        command = ["deft"] + list(args)
        
        editor_content = kwargs.get('editor_input', 'description-not-important')
        stdout = StringIO()
        stderr = StringIO()
        
        def fake_editor(path):
            relpath = self.storage.relpath(path)
            with self.storage.open(relpath, "w") as output:
                output.write(editor_content)
        
        cli = CommandLineInterface(self, out=stdout, err=stderr, editor=fake_editor)
        try:
            cli.run(command)
            return ProcessResult(command, 0, stdout.getvalue(), stderr.getvalue())
        except UserError:
            raise ProcessError(command, 1, stdout.getvalue(), 
                               traceback.format_exc() + "\n\nstderr:\n\n" + stderr.getvalue())
    
    def init_tracker(self, warning_listener, **config_overrides):
        return init_with_storage(self.storage, warning_listener, config_overrides)
    
    def load_tracker(self, warning_listener):
        return load_with_storage(self.storage, warning_listener)
    
    def abspath(self, subpath):
        return self.storage.abspath(subpath)
Example #2
0
 def __init__(self, name):
     self.name = name
     self.storage = MemStorage("testing")
     self.editor_content = ""
Example #3
0
 def setup(self):
     self.storage = MemStorage()
Example #4
0
class FeatureTracker_StorageRepair_Tests:
    """
    These tests monkey about with the underlying storage to simulate failed merges.
    """
    
    def setup(self):
        self.storage = MemStorage()
    
    def test_puts_features_that_are_not_in_any_status_index_into_the_lost_and_found_index(self):
        self.create_features({"testing": ["alice", "bob", "carol", "dave"]})
        self.corrupt_index("testing", delete_entry("bob"))
        tracker = self.create_tracker()
        
        repaired_feature = tracker.feature_named("bob")
            
        assert_that(repaired_feature in tracker.features_with_status(LostAndFoundStatus), 
                    LostAndFoundStatus + " status should include the repaired feature")
            
        assert_status("after repair", tracker,
                      {"testing": [tracker.feature_named("alice"),
                                   tracker.feature_named("carol"),
                                   tracker.feature_named("dave")],
                       "lost+found": [repaired_feature]})
    
    
    def test_persists_lost_and_found_index_when_orphaned_feature_is_repaired(self):
        self.test_puts_features_that_are_not_in_any_status_index_into_the_lost_and_found_index()
        
        # Should not need to repair the indices and so will not issue repair warnings
        tracker = self.create_tracker(warning_listener=WarningRaiser(AssertionError))
        
        assert_status("after repair", tracker,
                      {"testing": [tracker.feature_named("alice"),
                                   tracker.feature_named("carol"),
                                   tracker.feature_named("dave")],
                       "lost+found": [tracker.feature_named("bob")]})
 
    def test_saves_lost_and_found_index_when_multiple_orphaned_features_are_repaired(self):
        self.create_features({"testing": ["alice", "bob", "carol", "dave"]})
        self.corrupt_index("testing", delete_entry("bob"))
        self.corrupt_index("testing", delete_entry("carol"))
        self.create_tracker() # will repair the index
        
        
        tracker = self.create_tracker() # another tracker
        bob = tracker.feature_named("bob")
        carol = tracker.feature_named("carol")
        
        for repaired_feature in [bob, carol]:
            assert_that(repaired_feature in tracker.features_with_status(LostAndFoundStatus), 
                        LostAndFoundStatus + " status should include the repaired feature")

        assert_status("after repair", tracker,
                      {"testing": [tracker.feature_named("alice"),
                                   tracker.feature_named("dave")],
                       "lost+found": [bob, carol]})
   
    def test_issues_warning_when_repairing_feature_that_is_not_in_any_status_index(self):
        self.create_features({"testing": ["alice", "bob", "carol", "dave"]})
        self.corrupt_index("testing", delete_entry("bob"))
        
        warnings = WarningRecorder()
        tracker = self.create_tracker(warning_listener=warnings)
        
        assert_that(list(warnings), equal_to([
                    ("unindexed_feature", {"feature": tracker.feature_named("bob")})]))
        
    
    def test_removes_duplicated_entries_when_in_same_index(self):
        self.create_features({"testing": ["alice", "bob", "carol", "dave"]})
        self.corrupt_index("testing", insert_entry(3, "bob"))
        tracker = self.create_tracker()
        
        assert_features_not_duplicated_in_indices("after repair", tracker)
    

    def test_removes_duplicated_entries_when_in_different_indices(self):
        self.create_features({"s1": ["alice", "bob", "carol"],
                              "s2": ["dave", "eve"]})
        self.corrupt_index("s2", insert_entry(2, "bob"))
        tracker = self.create_tracker()
        
        assert_features_not_duplicated_in_indices("after repair", tracker)
        
        assert_status("tracker state after repair", tracker,
                      {"s1": [tracker.feature_named("alice"), 
                              tracker.feature_named("bob"), 
                              tracker.feature_named("carol")],
                       "s2": [tracker.feature_named("dave"), 
                              tracker.feature_named("eve")]})
        

    def test_persists_removal_of_duplicate_index_entries(self):
        self.test_removes_duplicated_entries_when_in_different_indices()
        
        # Should not need to repair the indices and so will not issue repair warnings
        self.create_tracker(warning_listener=WarningRaiser(AssertionError))
        
    
    def test_warns_when_removing_duplicate_index_entries(self):
        self.create_features({"s1": ["alice", "bob", "carol"],
                              "s2": ["dave", "eve"]})
        self.corrupt_index("s2", insert_entry(2, "bob"))
        
        warnings = WarningRecorder()
        tracker = self.create_tracker(warning_listener=warnings)
            
        assert_that(list(warnings), equal_to([
                    ("duplicate_entries", {"feature": tracker.feature_named("bob"), 
                                           "removed_from_status": "s2"})]))

    def test_filters_nonexistent_features_from_indices(self):
        self.create_features({"s1": ["alice", "bob"],
                              "s2": ["carol", "dave"]})
        self.corrupt_index("s2", insert_entry(2, "eve"))
        
        tracker = self.create_tracker()
        
        assert_status("after repair", tracker,
                      {"s1": [tracker.feature_named("alice"),
                              tracker.feature_named("bob")],
                       "s2": [tracker.feature_named("carol"),
                              tracker.feature_named("dave")]})
    
    def test_warns_if_index_contains_nonexistent_feature(self):
        self.create_features({"s1": ["alice", "bob"],
                              "s2": ["carol", "dave"]})
        self.corrupt_index("s2", insert_entry(2, "eve"))
        
        warnings = WarningRecorder()
        tracker = self.create_tracker(warning_listener=warnings)
        
        assert_that(list(warnings), equal_to([
                    ("unknown_feature", {"name": "eve", "status": "s2"})]))
        
    def test_persists_removal_of_nonexistent_features_from_indices(self):
        self.test_filters_nonexistent_features_from_indices()
        
        # Should not need to repair the indices and so will not issue repair warnings
        self.create_tracker(warning_listener=WarningRaiser(AssertionError))

    

    def create_tracker(self, warning_listener=None):
        return FeatureTracker(config=default_config(datadir="tracker"), 
                              storage=self.storage, 
                              warning_listener=warning_listener if warning_listener is not None else IgnoreWarnings())

    def create_features(self, names_by_status):
        tracker = self.create_tracker()
        for status in names_by_status:
            for name in names_by_status[status]:
                tracker.create(name=name, status=status)


    def corrupt_index(self, name, modifier):
        index_path = path("tracker/status/" + name + ".index")
        with self.storage.open(index_path) as input:
            index_entries = LinesFormat(list).load(input)

        modifier(index_entries)
    
        with self.storage.open(index_path, "w") as output:
            LinesFormat(list).save(index_entries, output)
Example #5
0
 def setup(self):
     self.storage = MemStorage("basedir")
     self.tracker = FeatureTracker(config=default_config(datadir="tracker"), 
                                   storage=self.storage, 
                                   warning_listener=WarningRaiser(AssertionError))
Example #6
0
class FeatureTracker_HappyPath_Tests:
    def setup(self):
        self.storage = MemStorage("basedir")
        self.tracker = FeatureTracker(config=default_config(datadir="tracker"), 
                                      storage=self.storage, 
                                      warning_listener=WarningRaiser(AssertionError))
    
    def test_initially_contains_no_features(self):
        assert_that(list(self.tracker.all_features()), has_length(0))
    
    def test_reports_initial_status(self):
        tracker = FeatureTracker(config=default_config(initial_status="testing"), 
                                 storage=self.storage, 
                                 warning_listener=IgnoreWarnings())
        assert_that(tracker.initial_status, equal_to("testing"))
        
    def test_default_initial_status_is_new(self):
        assert_that(self.tracker.initial_status, equal_to("new"))

    def test_can_create_new_features(self):
        new_feature = self.tracker.create(name="alice", status="pending")
        
        assert_that(new_feature.name, equal_to("alice"))
        assert_that(new_feature.status, equal_to("pending"))
    
    def test_new_features_have_default_initial_status_if_status_not_explicitly_specified(self):
        new_feature = self.tracker.create(name="alice")
        
        assert_that(new_feature.status, equal_to(self.tracker.initial_status))
    
    def test_new_features_with_same_status_are_assigned_priority_in_order_of_addition(self):
        new_a = self.tracker.create(name="new_a", status="new")
        new_b = self.tracker.create(name="new_b", status="new")
        pending_a = self.tracker.create(name="pending_a", status="pending")
        new_c = self.tracker.create(name="new_c", status="new")
        pending_b = self.tracker.create(name="pending_b", status="pending")
        
        assert_that(new_a.priority, equal_to(1))
        assert_that(new_b.priority, equal_to(2))
        assert_that(new_c.priority, equal_to(3))
        
        assert_that(pending_a.priority, equal_to(1))
        assert_that(pending_b.priority, equal_to(2))
    
    def test_can_change_the_priority_of_a_feature(self):
        alice = self.tracker.create(name="alice")
        bob = self.tracker.create(name="bob")
        carol = self.tracker.create(name="carol")
        dave = self.tracker.create(name="dave")
        eve = self.tracker.create(name="eve")
        
        alice.priority = 3
        assert_priority_order("moved first to middle", self.tracker, [bob, carol, alice, dave, eve])
        
        carol.priority = 1
        assert_priority_order("moved middle to first", self.tracker, [carol, bob, alice, dave, eve])
        
        eve.priority = 1
        assert_priority_order("moved last to first", self.tracker, [eve, carol, bob, alice, dave])
        
        bob.priority = 5
        assert_priority_order("moved middle to last", self.tracker, [eve, carol, alice, dave, bob])
        
        eve.priority = 5
        assert_priority_order("moved first to last", self.tracker, [carol, alice, dave, bob, eve])
        
        eve.priority = 3
        assert_priority_order("moved last to middle", self.tracker, [carol, alice, eve, dave, bob])
        
        dave.priority = 2
        assert_priority_order("moved middle to middle", self.tracker, [carol, dave, alice, eve, bob])
    
        
    def test_can_change_status(self):
        alice = self.tracker.create(name="alice", status="odd")
        bob = self.tracker.create(name="bob", status="odd")
        carol = self.tracker.create(name="carol", status="odd")
        dave = self.tracker.create(name="dave", status="even")
        eve = self.tracker.create(name="eve", status="odd")
        
        alice.status = "female"
        assert_status("alice -> female", self.tracker,
                      {"female": [alice],
                       "odd": [bob, carol, eve],
                       "even": [dave]})
        
        bob.status = "male"
        assert_status("bob -> male", self.tracker,
                      {"female": [alice],
                       "male": [bob],
                       "odd": [carol, eve],
                       "even": [dave]})
        
        carol.status = "female"
        assert_status("carol -> female", self.tracker,
                      {"female": [alice, carol],
                       "male": [bob],
                       "odd": [eve],
                       "even": [dave]})
        
        dave.status = "male"
        assert_status("dave -> male", self.tracker,
                      {"female": [alice, carol],
                       "male": [bob, dave],
                       "odd": [eve],
                       "even": []})
        
        eve.status = "female"
        assert_status("eve -> female", self.tracker,
                      {"female": [alice, carol, eve],
                       "male": [bob, dave],
                       "odd": [],
                       "even": []})
    
    def test_lists_all_statuses_in_alphabetical_order(self):
        alice = self.tracker.create(name="alice", status="S")
        bob = self.tracker.create(name="bob", status="U")
        carol = self.tracker.create(name="carol", status="T")
        dave = self.tracker.create(name="dave", status="T")
        eve = self.tracker.create(name="eve", status="S")
        
        assert_that(self.tracker.statuses(), equal_to(["S", "T", "U"]))
        
    def test_does_not_list_empty_status_indices_by_default(self):
        alice = self.tracker.create(name="alice", status="S")
        bob = self.tracker.create(name="bob", status="U")
        carol = self.tracker.create(name="carol", status="T")
        dave = self.tracker.create(name="dave", status="T")
        eve = self.tracker.create(name="eve", status="S")
        
        bob.status = "V" # U is now empty
        
        assert_that(self.tracker.statuses(), equal_to(["S", "T", "V"]))
        

    def test_can_list_empty_status_indices(self):
        self.test_does_not_list_empty_status_indices_by_default()
        
        assert_that(self.tracker.statuses(include_empty=True), equal_to(["S", "T", "U", "V"]))
        
    def test_an_unused_status_is_reported_as_empty(self):
        assert_status("never used status", self.tracker, {"unused_status": []})
    
    def test_lists_names_of_all_features_in_order_of_status_then_priority(self):
        alice = self.tracker.create(name="alice", status="S")
        bob = self.tracker.create(name="bob", status="U")
        carol = self.tracker.create(name="carol", status="T")
        dave = self.tracker.create(name="dave", status="T")
        eve = self.tracker.create(name="eve", status="S")
        
        assert_that(list(self.tracker.all_features()), equal_to([alice, eve, carol, dave, bob]))
    
    def test_lists_names_of_features_with_given_status_in_priority_order(self):
        alice = self.tracker.create(name="alice", status="same")
        bob = self.tracker.create(name="bob", status="same")
        carol = self.tracker.create(name="carol", status="same")
        dave = self.tracker.create(name="dave", status="same")
        eve = self.tracker.create(name="eve", status="different")
        
        assert_that(list(self.tracker.features_with_status("same")), equal_to([alice, bob, carol, dave]))
    
    def test_can_create_feature_with_a_description(self):
        new_feature = self.tracker.create(name="new-feature", description="the description text")
        
        assert_that(new_feature.description, equal_to("the description text"))

    def test_can_create_feature_with_properties(self):
        new_feature = self.tracker.create(name="new-feature", properties={"a":"1", "b":"2"})
        
        assert_that(new_feature.properties, equal_to({"a":"1", "b":"2"}))
    
    def test_cannot_create_property_with_reserved_name(self):
        for name in ["status", "priority", "description"]:
            try:
                self.tracker.create(name="feature-for-"+name, properties={name: "y"})
                raise AssertionError("should have disallowed property named " + name)
            except UserError as e:
                assert_that(str(e), contains_string(name))
    
    def test_cannot_set_property_with_reserved_name(self):
        feature = self.tracker.create(name="x")
        for name in ["status", "priority", "description"]:
            try:
                feature.properties = {name: "y"}
                raise AssertionError("should have disallowed property named " + name)
            except UserError as e:
                assert_that(str(e), contains_string(name))
    
    
    def test_if_not_given_a_new_feature_has_an_empty_description(self):
        new_feature = self.tracker.create(name="new-feature")
        
        assert_that(new_feature.description, equal_to(""))
        
    def test_can_overwrite_the_description(self):
        new_feature = self.tracker.create(name="new-feature", description="the initial description")
        
        new_feature.description = "a new description"
        
        assert_that(new_feature.description, equal_to("a new description"))
    
    def test_can_overwrite_the_properties(self):
        new_feature = self.tracker.create(name="new-feature", properties={"a":"1", "b":"2"})
        
        new_feature.properties = {"a": "10", "b": "22"}
        
        assert_that(new_feature.properties, equal_to({"a": "10", "b": "22"}))
    
    def returns_a_copy_of_its_properties(self):
        new_feature = self.tracker.create(name="new-feature", properties={"a":"1", "b":"2"})
        
        new_feature.properties["1"] = "10"
        
        assert_that(new_feature.properties, equal_to({"a": "1", "b": "2"}))
        
    def test_can_report_filename_of_description(self):
        feature = self.tracker.create(name="bob")
        assert_that(feature.description_file, equal_to("basedir/tracker/features/bob.description"))
    
    def test_can_report_filename_of_properties(self):
        feature = self.tracker.create(name="carol")
        assert_that(feature.properties_file, equal_to("basedir/tracker/features/carol.properties.yaml"))
    
    def test_description_written_to_storage_immediately_when_feature_is_created(self):
        alice = self.tracker.create(name="alice", description="first-description")
        assert_that(self.storage.open("tracker/features/alice.description").read(), equal_to("first-description"))
        
    def test_description_written_to_storage_immediately_when_description_is_changed(self):
        alice = self.tracker.create(name="alice", description="first-description")
        alice.description = "second-description"
        assert_that(self.storage.open("tracker/features/alice.description").read(), equal_to("second-description"))
        
    def test_can_rename_a_feature(self):
        f = self.tracker.create(name="alice", description="alice-description", properties={"gender": "female"})
        self.tracker.save()
        
        original_status = f.status
        original_priority = f.priority
        original_description = f.description
        original_properties = f.properties
        
        f.name = "carol"
        
        assert_that(f.name, equal_to("carol"))
        assert_that(f.status, equal_to(original_status))
        assert_that(f.priority, equal_to(original_priority))
        assert_that(f.description, equal_to(original_description))
        assert_that(f.properties, equal_to(original_properties))
        
        try:
            self.tracker.feature_named("alice")
            raise AssertionError("should have raised UserError")
        except UserError:
            pass
        
        assert_that(self.tracker.feature_named("carol"), same_instance(f))
        
    def test_can_rename_a_feature_to_same_name(self):
        f = self.tracker.create(name="alice", description="alice-description", properties={"gender": "female"})
        self.tracker.save()
        
        original_status = f.status
        original_priority = f.priority
        original_description = f.description
        original_properties = f.properties
        
        f.name = "alice"
        
        assert_that(f.name, equal_to("alice"))
        assert_that(f.status, equal_to(original_status))
        assert_that(f.priority, equal_to(original_priority))
        assert_that(f.description, equal_to(original_description))
        assert_that(f.properties, equal_to(original_properties))
        
        assert_that(self.tracker.feature_named("alice"), same_instance(f))
    
    def test_cannot_rename_a_feature_to_existing_name(self):
        alice = self.tracker.create(name="alice")
        bob = self.tracker.create(name="bob")
        
        try:
            bob.name = "alice"
            raise AssertionError("should have failed")
        except UserError as expected:
            pass
        

    def test_can_bulk_change_status_of_all_features_with_the_same_status(self):
        alice = self.tracker.create(name="alice", status="S")
        bob = self.tracker.create(name="bob", status="S")
        carol = self.tracker.create(name="carol", status="T")
        dave = self.tracker.create(name="dave", status="U")
        
        self.tracker.bulk_change_status(from_status="S", to_status="T")
        
        assert_status("after bulk change", self.tracker,
                      {"S": [],
                       "T": [carol, alice, bob],
                       "U": [dave]})

    def test_can_bulk_change_to_same_status(self):
        alice = self.tracker.create(name="alice", status="S")
        bob = self.tracker.create(name="bob", status="S")
        carol = self.tracker.create(name="carol", status="T")
        dave = self.tracker.create(name="dave", status="U")
        
        self.tracker.bulk_change_status(from_status="S", to_status="S")
        
        assert_status("after bulk change", self.tracker,
                      {"S": [alice, bob],
                       "T": [carol],
                       "U": [dave]})
        
    def test_purging_a_feature_removes_all_trace_of_it_from_tracker_and_storage(self):
        alice = self.tracker.create(name="alice", status="new", description="alice-description")
        alice.properties = {'a': 'some value'}
        
        self.tracker.save()
        
        assert_that(list(self.storage.list("tracker/features/alice.*")), is_not(equal_to([])))
        
        self.tracker.purge("alice")
        
        try:
            self.tracker.feature_named("alice")
            raise AssertionError("should have thrown UserError")
        except UserError as expected:
            pass
        
        self.tracker.save()
        
        assert_that(list(self.storage.list("tracker/alice.*")), equal_to([]))

    def test_deletes_index_file_when_it_becomes_empty(self):
        alice = self.tracker.create(name="alice", status="testing")
        bob = self.tracker.create(name="bob", status="testing")
        carol = self.tracker.create(name="carol", status="testing")
        
        assert_that(self.storage.exists("tracker/status/testing.index"), 
                    "index file exists")
        
        alice.status = "another-status"
        bob.status = "another-status"
        carol.status = "another-status"
        
        assert_that(not self.storage.exists("tracker/status/testing.index"),
                    "index file should not exist after all entries have been removed")


    def test_deletes_index_file_when_it_becomes_by_purging_features(self):
        self.tracker.create(name="alice", status="testing")
        self.tracker.create(name="bob", status="testing")
        
        assert_that(self.storage.exists("tracker/status/testing.index"), 
                    "index file exists")
        
        self.tracker.purge("alice")
        self.tracker.purge("bob")
        
        assert_that(not self.storage.exists("tracker/status/testing.index"),
                    "index file should not exist after all entries have been removed")

        
    def test_asking_for_an_empty_index_does_not_create_an_empty_file(self):
        empty = self.tracker.features_with_status("testing")
        assert_that(empty, equal_to([]))
        
        assert_that(not self.storage.exists("tracker/status/testing.index"),
                    "should not have created an empty index file")