From 3ecb63aaf5fb8cece4aa543ee17c63986b06447e Mon Sep 17 00:00:00 2001
From: Peter Hamilton <peter.hamilton@jhuapl.edu>
Date: Wed, 17 Jun 2015 14:10:31 -0400
Subject: [PATCH] Finishing Boolean primitive implementation

This change finishes the Boolean primitive implementation, including a
complete test suite for the Boolean class.
---
 kmip/core/primitives.py                       | 118 +++++--
 .../unit/core/primitives/test_boolean.py      | 303 ++++++++++++++++++
 .../unit/core/primitives/test_primitives.py   |  37 ---
 3 files changed, 395 insertions(+), 63 deletions(-)
 create mode 100644 kmip/tests/unit/core/primitives/test_boolean.py

diff --git a/kmip/core/primitives.py b/kmip/core/primitives.py
index 8779364..6c0ddc6 100644
--- a/kmip/core/primitives.py
+++ b/kmip/core/primitives.py
@@ -13,6 +13,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import logging
 import six
 import sys
 
@@ -428,60 +429,125 @@ class Enumeration(Integer):
 
 
 class Boolean(Base):
+    """
+    An encodeable object representing a boolean value.
 
-    def __init__(self, value=None, tag=Tags.DEFAULT):
+    A Boolean is one of the KMIP primitive object types. It is encoded as an
+    unsigned, big-endian, 8-byte value, capable of taking the values True (1)
+    or False (0). For more information, see Section 9.1 of the KMIP 1.1
+    specification.
+    """
+    LENGTH = 8
+
+    def __init__(self, value=True, tag=Tags.DEFAULT):
+        """
+        Create a Boolean object.
+
+        Args:
+            value (bool): The value of the Boolean. Optional, defaults to True.
+            tag (Tags): An enumeration defining the tag of the Boolean object.
+                Optional, defaults to Tags.DEFAULT.
+        """
         super(Boolean, self).__init__(tag, type=Types.BOOLEAN)
+        self.logger = logging.getLogger(__name__)
         self.value = value
-        self.length = 8
+        self.length = self.LENGTH
+
+        self.validate()
 
     def read_value(self, istream):
-        value = unpack('!Q', str(istream[0:self.length]))[0]
+        """
+        Read the value of the Boolean object from the input stream.
+
+        Args:
+            istream (Stream): A buffer containing the encoded bytes of the
+                value of a Boolean object. Usually a BytearrayStream object.
+                Required.
+
+        Raises:
+            ValueError: if the read boolean value is not a 0 or 1.
+        """
+        try:
+            value = unpack('!Q', istream.read(self.LENGTH))[0]
+        except:
+            self.logger.error("Error reading boolean value from buffer")
+            raise
 
         if value == 1:
             self.value = True
         elif value == 0:
             self.value = False
         else:
-            raise errors.ReadValueError(Boolean.__name__, 'value',
-                                        value)
+            raise ValueError("expected: 0 or 1, observed: {0}".format(value))
 
-        for _ in range(self.length):
-            istream.pop(0)
+        self.validate()
 
     def read(self, istream):
+        """
+        Read the encoding of the Boolean object from the input stream.
+
+        Args:
+            istream (Stream): A buffer containing the encoded bytes of a
+                Boolean object. Usually a BytearrayStream object. Required.
+        """
         super(Boolean, self).read(istream)
         self.read_value(istream)
 
     def write_value(self, ostream):
-        if self.value is None:
-            raise errors.WriteValueError(Boolean.__name__, 'value',
-                                         self.value)
+        """
+        Write the value of the Boolean object to the output stream.
 
-        data_buffer = bytearray()
-
-        if isinstance(self.value, type(True)):
-            if self.value:
-                data_buffer.extend(pack('!Q', 1))
-            else:
-                data_buffer.extend(pack('!Q', 0))
-        else:
-            raise errors.WriteTypeError(Boolean.__name__, 'value',
-                                        type(self.value))
-
-        ostream.extend(data_buffer)
+        Args:
+            ostream (Stream): A buffer to contain the encoded bytes of the
+                value of a Boolean object. Usually a BytearrayStream object.
+                Required.
+        """
+        try:
+            ostream.write(pack('!Q', self.value))
+        except:
+            self.logger.error("Error writing boolean value to buffer")
+            raise
 
     def write(self, ostream):
+        """
+        Write the encoding of the Boolean object to the output stream.
+
+        Args:
+            ostream (Stream): A buffer to contain the encoded bytes of a
+                Boolean object. Usually a BytearrayStream object. Required.
+        """
         super(Boolean, self).write(ostream)
         self.write_value(ostream)
 
     def validate(self):
-        self.__validate()
+        """
+        Verify that the value of the Boolean object is valid.
 
-    def __validate(self):
-        pass
+        Raises:
+            TypeError: if the value is not of type bool.
+        """
+        if self.value:
+            if not isinstance(self.value, bool):
+                raise TypeError("expected: {0}, observed: {1}".format(
+                    bool, type(self.value)))
 
     def __repr__(self):
-        return '<Boolean, %s>' % (self.value)
+        return "{0}(value={1})".format(type(self).__name__, repr(self.value))
+
+    def __str__(self):
+        return "{0}".format(repr(self.value))
+
+    def __eq__(self, other):
+        if isinstance(other, Boolean):
+            return self.value == other.value
+        else:
+            return NotImplemented
+
+    def __ne__(self, other):
+        if isinstance(other, Boolean):
+            return not self.__eq__(other)
+        else:
+            return NotImplemented
 
 
 class TextString(Base):
diff --git a/kmip/tests/unit/core/primitives/test_boolean.py b/kmip/tests/unit/core/primitives/test_boolean.py
new file mode 100644
index 0000000..0074fe1
--- /dev/null
+++ b/kmip/tests/unit/core/primitives/test_boolean.py
@@ -0,0 +1,303 @@
+# Copyright (c) 2015 The Johns Hopkins University/Applied Physics Laboratory
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from testtools import TestCase
+
+from kmip.core.primitives import Boolean
+from kmip.core.utils import BytearrayStream
+
+
+class TestBoolean(TestCase):
+
+    def setUp(self):
+        super(TestBoolean, self).setUp()
+        self.stream = BytearrayStream()
+
+    def tearDown(self):
+        super(TestBoolean, self).tearDown()
+
+    def test_init(self):
+        """
+        Test that a Boolean object can be instantiated.
+        """
+        boolean = Boolean(False)
+        self.assertEqual(False, boolean.value)
+
+    def test_init_unset(self):
+        """
+        Test that a Boolean object can be instantiated with no input.
+        """
+        boolean = Boolean()
+        self.assertEqual(True, boolean.value)
+
+    def test_validate_on_valid(self):
+        """
+        Test that a Boolean object can be validated on good input.
+        """
+        boolean = Boolean(True)
+        boolean.validate()
+
+    def test_validate_on_valid_unset(self):
+        """
+        Test that a Boolean object with no preset value can be validated.
+        """
+        boolean = Boolean()
+        boolean.validate()
+
+    def test_validate_on_invalid_type(self):
+        """
+        Test that a TypeError is raised when a Boolean object is built with an
+        invalid value.
+        """
+        self.assertRaises(TypeError, Boolean, 'invalid')
+
+    def test_read_true(self):
+        """
+        Test that a Boolean object representing the value True can be read
+        from a byte stream.
+        """
+        encoding = (b'\x42\x00\x00\x06\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00'
+                    b'\x00\x01')
+        stream = BytearrayStream(encoding)
+        boolean = Boolean()
+
+        boolean.read(stream)
+
+        self.assertTrue(boolean.value)
+
+    def test_read_false(self):
+        """
+        Test that a Boolean object representing the value False can be read
+        from a byte stream.
+        """
+        encoding = (b'\x42\x00\x00\x06\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00'
+                    b'\x00\x00')
+        stream = BytearrayStream(encoding)
+        boolean = Boolean()
+
+        boolean.read(stream)
+
+        self.assertFalse(boolean.value)
+
+    def test_read_bad_encoding(self):
+        """
+        Test that an Exception is raised when the Boolean read operation fails
+        on a bad encoding.
+        """
+        encoding = (b'\x42\x00\x00\x06\x00\x00\x00\x08')
+        stream = BytearrayStream(encoding)
+        boolean = Boolean()
+
+        self.assertRaises(Exception, boolean.read, stream)
+
+    def test_read_bad_value(self):
+        """
+        Test that a ValueError is raised when the Boolean read operations
+        reads a valid integer but invalid boolean.
+        """
+        encoding = (b'\x42\x00\x00\x06\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00'
+                    b'\x00\x02')
+        stream = BytearrayStream(encoding)
+        boolean = Boolean()
+
+        self.assertRaises(ValueError, boolean.read, stream)
+
+    def test_write_true(self):
+        """
+        Test that a Boolean object representing the value True can be written
+        to a byte stream.
+        """
+        encoding = (b'\x42\x00\x00\x06\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00'
+                    b'\x00\x01')
+        stream = BytearrayStream()
+        boolean = Boolean(True)
+
+        boolean.write(stream)
+
+        self.assertEqual(encoding, stream.read())
+
+    def test_write_false(self):
+        """
+        Test that a Boolean object representing the value False can be written
+        to a byte stream.
+        """
+        encoding = (b'\x42\x00\x00\x06\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00'
+                    b'\x00\x00')
+        stream = BytearrayStream()
+        boolean = Boolean(False)
+
+        boolean.write(stream)
+
+        self.assertEqual(encoding, stream.read())
+
+    def test_write_bad_value(self):
+        """
+        Test that an Exception is raised when the Boolean write operation fails
+        on a bad boolean value.
+        """
+        stream = BytearrayStream()
+        boolean = Boolean()
+        boolean.value = 'invalid'
+
+        self.assertRaises(Exception, boolean.write, stream)
+
+    def test_repr_default(self):
+        """
+        Test that the representation of a Boolean object is formatted properly
+        and can be used by eval to create a new Boolean object.
+        """
+        boolean = Boolean()
+
+        self.assertEqual("Boolean(value=True)", repr(boolean))
+        self.assertEqual(boolean, eval(repr(boolean)))
+
+    def test_repr_true(self):
+        """
+        Test that the representation of a Boolean object representing the
+        value True is formatted properly and can be used by eval to create a
+        new Boolean object.
+        """
+        boolean = Boolean(True)
+
+        self.assertEqual("Boolean(value=True)", repr(boolean))
+        self.assertEqual(boolean, eval(repr(boolean)))
+        self.assertTrue(eval(repr(boolean)).value)
+
+    def test_repr_false(self):
+        """
+        Test that the representation of a Boolean object representing the
+        value False is formatted properly and can be used by eval to create a
+        new Boolean object.
+        """
+        boolean = Boolean(False)
+
+        self.assertEqual("Boolean(value=False)", repr(boolean))
+        self.assertEqual(boolean, eval(repr(boolean)))
+        self.assertFalse(eval(repr(boolean)).value)
+
+    def test_str_default(self):
+        """
+        Test that the string representation of a Boolean object is formatted
+        properly.
+        """
+        boolean = Boolean()
+
+        self.assertEqual("True", str(boolean))
+
+    def test_str_true(self):
+        """
+        Test that the string representation of a Boolean object representing
+        the value True is formatted properly.
+        """
+        boolean = Boolean(True)
+
+        self.assertEqual("True", str(boolean))
+
+    def test_str_false(self):
+        """
+        Test that the string representation of a Boolean object representing
+        the value False is formatted properly.
+        """
+        boolean = Boolean(False)
+
+        self.assertEqual("False", str(boolean))
+
+    def test_equal_on_equal(self):
+        """
+        Test that the equality operator returns True when comparing two
+        Boolean objects.
+        """
+        a = Boolean(False)
+        b = Boolean(False)
+
+        self.assertTrue(a == b)
+        self.assertTrue(b == a)
+
+    def test_equal_on_equal_and_empty(self):
+        """
+        Test that the equality operator returns True when comparing two
+        Boolean objects.
+        """
+        a = Boolean()
+        b = Boolean()
+
+        self.assertTrue(a == b)
+        self.assertTrue(b == a)
+
+    def test_equal_on_not_equal(self):
+        """
+        Test that the equality operator returns False when comparing two
+        Boolean objects with different values.
+        """
+        a = Boolean(True)
+        b = Boolean(False)
+
+        self.assertFalse(a == b)
+        self.assertFalse(b == a)
+
+    def test_equal_on_type_mismatch(self):
+        """
+        Test that the equality operator returns False when comparing a
+        Boolean object to a non-Boolean object.
+        """
+        a = Boolean()
+        b = 'invalid'
+
+        self.assertFalse(a == b)
+        self.assertFalse(b == a)
+
+    def test_not_equal_on_equal(self):
+        """
+        Test that the inequality operator returns False when comparing
+        two Boolean objects with the same values.
+        """
+        a = Boolean(False)
+        b = Boolean(False)
+
+        self.assertFalse(a != b)
+        self.assertFalse(b != a)
+
+    def test_not_equal_on_equal_and_empty(self):
+        """
+        Test that the inequality operator returns False when comparing
+        two Boolean objects.
+        """
+        a = Boolean()
+        b = Boolean()
+
+        self.assertFalse(a != b)
+        self.assertFalse(b != a)
+
+    def test_not_equal_on_not_equal(self):
+        """
+        Test that the inequality operator returns True when comparing two
+        Boolean objects with different values.
+        """
+        a = Boolean(True)
+        b = Boolean(False)
+
+        self.assertTrue(a != b)
+        self.assertTrue(b != a)
+
+    def test_not_equal_on_type_mismatch(self):
+        """
+        Test that the inequality operator returns True when comparing a
+        Boolean object to a non-Boolean object.
+        """
+        a = Boolean()
+        b = 'invalid'
+
+        self.assertTrue(a != b)
+        self.assertTrue(b != a)
diff --git a/kmip/tests/unit/core/primitives/test_primitives.py b/kmip/tests/unit/core/primitives/test_primitives.py
index 6c0086e..f0b5335 100644
--- a/kmip/tests/unit/core/primitives/test_primitives.py
+++ b/kmip/tests/unit/core/primitives/test_primitives.py
@@ -906,43 +906,6 @@ class TestEnumeration(TestCase):
         self.assertEqual(encoding, result, self.bad_encoding)
 
 
-class TestBoolean(TestCase):
-
-    def setUp(self):
-        super(TestBoolean, self).setUp()
-        self.stream = BytearrayStream()
-
-    def tearDown(self):
-        super(TestBoolean, self).tearDown()
-
-    def test_init(self):
-        self.skip('')
-
-    def test_init_unset(self):
-        self.skip('')
-
-    def test_validate_on_valid(self):
-        self.skip('')
-
-    def test_validate_on_valid_unset(self):
-        self.skip('')
-
-    def test_validate_on_invalid_type(self):
-        self.skip('')
-
-    def test_read_value(self):
-        self.skip('')
-
-    def test_read(self):
-        self.skip('')
-
-    def test_write_value(self):
-        self.skip('')
-
-    def test_write(self):
-        self.skip('')
-
-
 class TestTextString(TestCase):
 
     def setUp(self):