class TestSubTreeExchangeMutator(TestCase):

    def setUp(self):
        self.tree = BinaryTree()
        self.root = BinaryTreeNode('*')
        self.tree.root = self.root
        self.root.add_left('A')
        self.root.add_right('B')

    def test_max_depth(self):
        self.assertRaises(ValueError, SubTreeExchangeMutator, max_depth=-2, binary_tree_node_cls=BinaryTreeNode)

    def test__mutate_subtree_exchange(self):
        max_depth = 2
        tree_gen = GrowGenerator(max_depth)

        result = SubTreeExchangeMutator._mutate_subtree_exchange(['+', '*'], [1, 2, 3], self.tree, tree_gen)
        self.assertIsInstance(result, BinaryTree)
        max_height = max_depth + 1
        initial_height = self.tree.height()
        final_height = result.height()
        self.assertLessEqual(final_height, initial_height + max_height)

    def test__swap_mut_subtree(self):
        random_tree = BinaryTree()
        left = random_tree.root = BinaryTreeNode('*')
        ll = random_tree.root.add_left('C')
        lr = random_tree.root.add_right('D')

        r = 0  # A
        result = SubTreeExchangeMutator._swap_mut_subtree(self.tree, r, random_tree)
        self.assertIsInstance(result, BinaryTree)
        self.assertEqual(result.height(), 3)
        self.assertEqual(self.tree.root.left, left)
        self.assertEqual(self.tree.root.left.left, ll)
        self.assertEqual(self.tree.root.left.right, lr)

    def test_to_dict(self):
        mutator = SubTreeExchangeMutator(4, BinaryTreeNode)
        actual = mutator.to_dict()
        self.assertIsInstance(actual, dict)
        self.assertEqual("src.evalg.genprog.mutation", actual["__module__"])
        self.assertEqual("SubTreeExchangeMutator", actual["__class__"])
        self.assertEqual("src.evalg.encoding", actual["binary_tree_node_module_name"])
        self.assertEqual("BinaryTreeNode", actual["binary_tree_node_cls_name"])
        self.assertEqual(mutator.max_depth, actual["max_depth"])

    def test_from_dict(self):
        test_cases = (SubTreeExchangeMutator, TreeMutator, Serializable)
        for cls in test_cases:
            with self.subTest(name=cls.__name__):
                mutator = SubTreeExchangeMutator(4, BinaryTreeNode)
                actual = cls.from_dict(mutator.to_dict())
                self.assertIsInstance(actual, SubTreeExchangeMutator)
                self.assertEqual(BinaryTreeNode, actual.binary_tree_node_cls)
                self.assertEqual(mutator.max_depth, actual.max_depth)
    def test_height(self):
        tree = BinaryTree()
        self.assertEqual(tree.height(), 0)

        tree.root = BinaryTreeNode('*')
        self.assertEqual(tree.height(), 1)

        left = tree.root.add_left(10)
        self.assertEqual(tree.height(), 2)
        right = tree.root.add_right(20)
        self.assertEqual(tree.height(), 2)

        ll = left.add_left(40)
        self.assertEqual(tree.height(), 3)
        left.add_right(50)
        self.assertEqual(tree.height(), 3)
        right.add_left(60)
        self.assertEqual(tree.height(), 3)
        right.add_right(70)
        self.assertEqual(tree.height(), 3)

        ll.add_left(80)
        self.assertEqual(tree.height(), 4)
class TestHalfAndHalfMutator(TestCase):

    def setUp(self):
        self.tree = BinaryTree()
        self.root = BinaryTreeNode('*')
        self.tree.root = self.root
        self.root.add_left('A')
        self.root.add_right('B')

    def test_mutate(self):
        individual = self.tree
        operands = ['A', 'B', 'C']
        mutator = HalfAndHalfMutator(max_depth=2)
        result = mutator.mutate(['+', '*'], operands, individual)
        self.assertIsInstance(result, BinaryTree)
        max_height = mutator.max_depth + 1
        self.assertLessEqual(result.height(), self.tree.height() + max_height)

    def test_to_dict(self):
        mutator = HalfAndHalfMutator(4, BinaryTreeNode)
        actual = mutator.to_dict()
        self.assertIsInstance(actual, dict)
        self.assertEqual("src.evalg.genprog.mutation", actual["__module__"])
        self.assertEqual("HalfAndHalfMutator", actual["__class__"])
        self.assertEqual("src.evalg.encoding", actual["binary_tree_node_module_name"])
        self.assertEqual("BinaryTreeNode", actual["binary_tree_node_cls_name"])
        self.assertEqual(mutator.max_depth, actual["max_depth"])

    def test_from_dict(self):
        test_cases = (HalfAndHalfMutator, SubTreeExchangeMutator, TreeMutator, Serializable)
        for cls in test_cases:
            with self.subTest(name=cls.__name__):
                mutator = HalfAndHalfMutator(4, BinaryTreeNode)
                actual = cls.from_dict(mutator.to_dict())
                self.assertIsInstance(actual, HalfAndHalfMutator)
                self.assertEqual(BinaryTreeNode, actual.binary_tree_node_cls)
                self.assertEqual(mutator.max_depth, actual.max_depth)