def test_init(self):
        root = MagicMock()
        parent = MagicMock()
        owner = 'owner'

        state = datafork.State(root, parent, owner)

        self.assertEqual(
            state.root,
            root,
        )
        self.assertEqual(
            state.parent,
            parent,
        )
        self.assertEqual(
            state.owner,
            owner,
        )
        self.assertTrue(
            isinstance(state.slot_values, collections.MutableMapping),
        )
        self.assertTrue(
            isinstance(state.slot_positions, collections.MutableMapping),
        )
        # slot_positions is a defaultdict that should make any element start
        # its life as an empty set.
        self.assertTrue(
            isinstance(state.slot_positions["foo"], collections.MutableSet),
        )
    def test_single_or_none(self):
        parent = datafork.State(self.mock_root)
        parent.set_slot(self.slot_a, 1, "parent_a")
        parent.set_slot(self.slot_c, 9, "parent_c")
        child = datafork.State(self.mock_root, parent)
        child.set_slot(self.slot_a, 3, "child_a")
        child.set_slot(self.slot_b, 4, "child_b")

        parent.merge_children([child], or_none=True)

        self.assert_expected_merge_call(
            self.slot_a,
            [
                (1, {"parent_a"}),
                (3, {"child_a"}),
            ]
        )
        self.assert_expected_merge_call(
            self.slot_b,
            [
                (datafork.Slot.NOT_KNOWN, set()),
                (4, {"child_b"}),
            ]
        )
        self.assert_no_merge_call(
            self.slot_c,
        )

        self.assertEqual(
            parent.get_slot_value(self.slot_a),
            "merge_a",
        )
        self.assertEqual(
            parent.get_slot_value(self.slot_b),
            "merge_b",
        )
        self.assertEqual(
            parent.get_slot_value(self.slot_c),
            9,
        )
    def test_indirect_merge(self):
        # This tests the case where we have more than one level of fork
        # active, and where the two forks happen to agree but the
        # parent disagrees with the both.
        # This test is in response to a previous bug where this behavior
        # was exhibited.
        parent = datafork.State(self.mock_root)
        parent.set_slot(self.slot_a, 1, "parent_a")
        parent.set_slot(self.slot_b, 5, "parent_b")
        child = datafork.State(self.mock_root, parent)
        child.set_slot(self.slot_a, 2, "child_a")
        grandchild = datafork.State(self.mock_root, child)
        grandchild.set_slot(self.slot_a, 2, "grandchild_a")
        grandchild.set_slot(self.slot_b, 5, "grandchild_b")

        child.merge_children([grandchild])
        parent.merge_children([child])

        self.assertEqual(
            self.slot_a.merge.call_count,
            2
        )
    def test_three(self):
        parent = datafork.State(self.mock_root)
        parent.set_slot(self.slot_a, 1, "parent_a")
        parent.set_slot(self.slot_c, 9, "parent_c")
        child_1 = datafork.State(self.mock_root, parent)
        child_1.set_slot(self.slot_a, 2, "child_1_a")
        child_1.set_slot(self.slot_b, 3, "child_1_b")
        child_1.set_slot(self.slot_d, 27, "child_1_d")
        child_2 = datafork.State(self.mock_root, parent)
        child_2.set_slot(self.slot_a, 4, "child_2_a")
        child_2.set_slot(self.slot_d, 27, "child_2_d")
        child_3 = datafork.State(self.mock_root, parent)
        child_3.set_slot(self.slot_a, 5, "child_3_a")
        child_3.set_slot(self.slot_d, 27, "child_3_d")

        parent.merge_children([child_1, child_2, child_3])

        self.assert_expected_merge_call(
            self.slot_a,
            [
                (2, {"child_1_a"}),
                (4, {"child_2_a"}),
                (5, {"child_3_a"}),
            ]
        )
        self.assert_expected_merge_call(
            self.slot_b,
            [
                (datafork.Slot.NOT_KNOWN, set()), # not set in parent
                (3, {"child_1_b"}),
            ]
        )
        self.assert_no_merge_call(
            self.slot_c,
        )
        self.assert_expected_merge_call(
            self.slot_d,
            [
                (27, {"child_1_d"}),
                (27, {"child_2_d"}),
                (27, {"child_3_d"}),
            ]
        )

        self.assertEqual(
            parent.get_slot_value(self.slot_a),
            "merge_a",
        )
        self.assertEqual(
            parent.get_slot_positions(self.slot_a),
            {
                "child_1_a",
                "child_2_a",
                "child_3_a",
            },
        )

        self.assertEqual(
            parent.get_slot_value(self.slot_b),
            "merge_b",
        )
        self.assertEqual(
            parent.get_slot_positions(self.slot_b),
            {
                "child_1_b",
            },
        )

        self.assertEqual(
            parent.get_slot_value(self.slot_c),
            9,
        )
        self.assertEqual(
            parent.get_slot_positions(self.slot_c),
            {
                "parent_c",
            },
        )

        self.assertEqual(
            parent.get_slot_value(self.slot_d),
            "merge_d",
        )
        self.assertEqual(
            parent.get_slot_positions(self.slot_d),
            {
                "child_1_d",
                "child_2_d",
                "child_3_d",
            },
        )