dtoc: Update fdt tests to increase code coverage

At present only some of the fdt functionality is tested. Add more tests to
cover the rest of it. Also turn on test coverage, which is now 100% with
a small exclusion for a Python 3 feature.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/tools/dtoc/dtoc_test_simple.dts b/tools/dtoc/dtoc_test_simple.dts
index 895cc1f..165680b 100644
--- a/tools/dtoc/dtoc_test_simple.dts
+++ b/tools/dtoc/dtoc_test_simple.dts
@@ -21,6 +21,7 @@
 		longbytearray = [09 0a 0b 0c 0d 0e 0f 10 11];
 		stringval = "message";
 		stringarray = "multi-word", "message";
+		notstring = [20 21 22 10 00];
 	};
 
 	spl-test2 {
diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py
index e24acf1..fd016bb 100644
--- a/tools/dtoc/fdt.py
+++ b/tools/dtoc/fdt.py
@@ -49,13 +49,6 @@
             return
         self.type, self.value = self.BytesToValue(bytes)
 
-    def GetPhandle(self):
-        """Get a (single) phandle value from a property
-
-        Gets the phandle valuie from a property and returns it as an integer
-        """
-        return fdt_util.fdt32_to_cpu(self.value[:4])
-
     def Widen(self, newprop):
         """Figure out which property type is more general
 
@@ -344,11 +337,6 @@
         """
         return self._fdt_obj
 
-    def CheckErr(self, errnum, msg):
-        if errnum:
-            raise ValueError('Error %d: %s: %s' %
-                (errnum, libfdt.fdt_strerror(errnum), msg))
-
     def GetProps(self, node):
         """Get all properties from a node.
 
diff --git a/tools/dtoc/fdt_util.py b/tools/dtoc/fdt_util.py
index 2d09649..88fc318 100644
--- a/tools/dtoc/fdt_util.py
+++ b/tools/dtoc/fdt_util.py
@@ -13,6 +13,14 @@
 import command
 import tools
 
+VERSION3 = sys.version_info > (3, 0)
+
+def get_plain_bytes(val):
+    """Handle Python 3 strings"""
+    if isinstance(val, bytes):
+        val = val.decode('utf-8')
+    return val.encode('raw_unicode_escape')
+
 def fdt32_to_cpu(val):
     """Convert a device tree cell to an integer
 
@@ -22,10 +30,9 @@
     Return:
         A native-endian integer value
     """
-    if sys.version_info > (3, 0):
-        if isinstance(val, bytes):
-            val = val.decode('utf-8')
-        val = val.encode('raw_unicode_escape')
+    if VERSION3:
+        # This code is not reached in Python 2
+        val = get_plain_bytes(val)  # pragma: no cover
     return struct.unpack('>I', val)[0]
 
 def fdt_cells_to_cpu(val, cells):
@@ -86,10 +93,10 @@
     prop = node.props.get(propname)
     if not prop:
         return default
-    value = fdt32_to_cpu(prop.value)
-    if type(value) == type(list):
-        raise ValueError("Node '%s' property '%' has list value: expecting"
+    if isinstance(prop.value, list):
+        raise ValueError("Node '%s' property '%s' has list value: expecting "
                          "a single integer" % (node.name, propname))
+    value = fdt32_to_cpu(prop.value)
     return value
 
 def GetString(node, propname, default=None):
@@ -97,8 +104,8 @@
     if not prop:
         return default
     value = prop.value
-    if type(value) == type(list):
-        raise ValueError("Node '%s' property '%' has list value: expecting"
+    if isinstance(value, list):
+        raise ValueError("Node '%s' property '%s' has list value: expecting "
                          "a single string" % (node.name, propname))
     return value
 
diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py
index ce6d258..20fea52 100644
--- a/tools/dtoc/test_dtoc.py
+++ b/tools/dtoc/test_dtoc.py
@@ -180,6 +180,7 @@
 \tfdt32_t\t\tintarray[4];
 \tfdt32_t\t\tintval;
 \tunsigned char\tlongbytearray[9];
+\tunsigned char\tnotstring[5];
 \tconst char *\tstringarray[3];
 \tconst char *\tstringval;
 };
@@ -195,6 +196,7 @@
 \t.bytearray\t\t= {0x6, 0x0, 0x0},
 \t.byteval\t\t= 0x5,
 \t.intval\t\t\t= 0x1,
+\t.notstring\t\t= {0x20, 0x21, 0x22, 0x10, 0x0},
 \t.longbytearray\t\t= {0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10,
 \t\t0x11},
 \t.stringval\t\t= "message",
diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py
index daa9d12..d44e4dd 100755
--- a/tools/dtoc/test_fdt.py
+++ b/tools/dtoc/test_fdt.py
@@ -18,6 +18,7 @@
 import command
 import fdt
 from fdt import TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL
+import fdt_util
 from fdt_util import fdt32_to_cpu
 import libfdt
 import test_util
@@ -53,6 +54,7 @@
         node = self.dtb.GetNode('/i2c@0/pmic@9')
         self.assertTrue(isinstance(node, fdt.Node))
         self.assertEqual('pmic@9', node.name)
+        self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing'))
 
     def testFlush(self):
         """Check that we can flush the device tree out to its file"""
@@ -79,14 +81,14 @@
         node = self.dtb.GetNode('/spl-test')
         props = self.dtb.GetProps(node)
         self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible',
-                          'intarray', 'intval', 'longbytearray',
+                          'intarray', 'intval', 'longbytearray', 'notstring',
                           'stringarray', 'stringval', 'u-boot,dm-pre-reloc'],
                          sorted(props.keys()))
 
     def testCheckError(self):
         """Tests the ChecKError() function"""
         with self.assertRaises(ValueError) as e:
-            self.dtb.CheckErr(-libfdt.NOTFOUND, 'hello')
+            fdt.CheckErr(-libfdt.NOTFOUND, 'hello')
         self.assertIn('FDT_ERR_NOTFOUND: hello', str(e.exception))
 
 
@@ -119,6 +121,8 @@
         self.node.DeleteProp('intarray')
         offset3 = node2.Offset()
         self.assertTrue(offset3 < offset2)
+        with self.assertRaises(libfdt.FdtException):
+            self.node.DeleteProp('missing')
 
     def testFindNode(self):
         """Tests that we can find a node using the _FindNode() functoin"""
@@ -126,6 +130,7 @@
         self.assertEqual('i2c@0', node.name)
         subnode = node._FindNode('pmic@9')
         self.assertEqual('pmic@9', subnode.name)
+        self.assertEqual(None, node._FindNode('missing'))
 
 
 class TestProp(unittest.TestCase):
@@ -144,6 +149,58 @@
         self.node = self.dtb.GetNode('/spl-test')
         self.fdt = self.dtb.GetFdtObj()
 
+    def testPhandle(self):
+        dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
+        node = dtb.GetNode('/phandle-source')
+
+    def _ConvertProp(self, prop_name):
+        """Helper function to look up a property in self.node and return it
+
+        Args:
+            Property name to find
+
+        Return fdt.Prop object for this property
+        """
+        p = self.fdt.get_property(self.node.Offset(), prop_name)
+        return fdt.Prop(self.node, -1, prop_name, p)
+
+    def testMakeProp(self):
+        """Test we can convert all the the types that are supported"""
+        prop = self._ConvertProp('boolval')
+        self.assertEqual(fdt.TYPE_BOOL, prop.type)
+        self.assertEqual(True, prop.value)
+
+        prop = self._ConvertProp('intval')
+        self.assertEqual(fdt.TYPE_INT, prop.type)
+        self.assertEqual(1, fdt32_to_cpu(prop.value))
+
+        prop = self._ConvertProp('intarray')
+        self.assertEqual(fdt.TYPE_INT, prop.type)
+        val = [fdt32_to_cpu(val) for val in prop.value]
+        self.assertEqual([2, 3, 4], val)
+
+        prop = self._ConvertProp('byteval')
+        self.assertEqual(fdt.TYPE_BYTE, prop.type)
+        self.assertEqual(5, ord(prop.value))
+
+        prop = self._ConvertProp('longbytearray')
+        self.assertEqual(fdt.TYPE_BYTE, prop.type)
+        val = [ord(val) for val in prop.value]
+        self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val)
+
+        prop = self._ConvertProp('stringval')
+        self.assertEqual(fdt.TYPE_STRING, prop.type)
+        self.assertEqual('message', prop.value)
+
+        prop = self._ConvertProp('stringarray')
+        self.assertEqual(fdt.TYPE_STRING, prop.type)
+        self.assertEqual(['multi-word', 'message'], prop.value)
+
+        prop = self._ConvertProp('notstring')
+        self.assertEqual(fdt.TYPE_BYTE, prop.type)
+        val = [ord(val) for val in prop.value]
+        self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val)
+
     def testGetEmpty(self):
         """Tests the GetEmpty() function for the various supported types"""
         self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL))
@@ -207,6 +264,71 @@
         self.assertEqual(3, len(prop.value))
 
 
+class TestFdtUtil(unittest.TestCase):
+    """Tests for the fdt_util module
+
+    This module will likely be mostly replaced at some point, once upstream
+    libfdt has better Python support. For now, this provides tests for current
+    functionality.
+    """
+    @classmethod
+    def setUpClass(cls):
+        tools.PrepareOutputDir(None)
+
+    def setUp(self):
+        self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
+        self.node = self.dtb.GetNode('/spl-test')
+
+    def testGetInt(self):
+        self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
+        self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
+
+        with self.assertRaises(ValueError) as e:
+            self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
+        self.assertIn("property 'intarray' has list value: expecting a single "
+                      'integer', str(e.exception))
+
+    def testGetString(self):
+        self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
+        self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
+                                                    'test'))
+
+        with self.assertRaises(ValueError) as e:
+            self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
+        self.assertIn("property 'stringarray' has list value: expecting a "
+                      'single string', str(e.exception))
+
+    def testGetBool(self):
+        self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
+        self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
+        self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
+        self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
+
+    def testFdtCellsToCpu(self):
+        val = self.node.props['intarray'].value
+        self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
+        self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
+
+        dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
+        node2 = dtb2.GetNode('/test1')
+        val = node2.props['reg'].value
+        self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
+
+    def testEnsureCompiled(self):
+        """Test a degenerate case of this function"""
+        dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
+        self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
+
+    def testGetPlainBytes(self):
+        self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
+
+
+def RunTestCoverage():
+    """Run the tests and check that we get 100% coverage"""
+    test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
+            ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
+
+
 def RunTests(args):
     """Run all the test we have for the fdt model
 
@@ -217,7 +339,7 @@
     result = unittest.TestResult()
     sys.argv = [sys.argv[0]]
     test_name = args and args[0] or None
-    for module in (TestFdt, TestNode, TestProp):
+    for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
         if test_name:
             try:
                 suite = unittest.TestLoader().loadTestsFromName(test_name, module)
@@ -237,10 +359,16 @@
     sys.exit(1)
 
 parser = OptionParser()
+parser.add_option('-B', '--build-dir', type='string', default='b',
+        help='Directory containing the build output')
 parser.add_option('-t', '--test', action='store_true', dest='test',
                   default=False, help='run tests')
+parser.add_option('-T', '--test-coverage', action='store_true',
+                default=False, help='run tests and check for 100% coverage')
 (options, args) = parser.parse_args()
 
 # Run our meagre tests
 if options.test:
     RunTests(args)
+elif options.test_coverage:
+    RunTestCoverage()