cgpt: more python3 porting work

Fix more `cros lint` warnings, and add unittests to `repo upload`
to help catch regressions.

BUG=chromium:981405
TEST=`./cgpt_unittest.py` passes

Change-Id: Iaab164ed8b3ebe0bdff4988f7e58bf50f001cc12
Reviewed-on: https://chromium-review.googlesource.com/1772036
Tested-by: Mike Frysinger <vapier@chromium.org>
Commit-Ready: Mike Frysinger <vapier@chromium.org>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
Reviewed-by: Michael Mortensen <mmortensen@google.com>
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
index 5e25fff..fafdbe2 100644
--- a/PRESUBMIT.cfg
+++ b/PRESUBMIT.cfg
@@ -1,2 +1,3 @@
 [Hook Scripts]
-hook0=../../chromite/bin/cros lint ${PRESUBMIT_FILES}
+cros lint = cros lint ${PRESUBMIT_FILES}
+cgpt_unittest = ./build_library/cgpt_unittest.py
diff --git a/build_library/cgpt.py b/build_library/cgpt.py
index 9e1699a..e3ce566 100755
--- a/build_library/cgpt.py
+++ b/build_library/cgpt.py
@@ -27,6 +27,7 @@
   --adjust_part ROOT-A:-20MiB
 """
 
+from __future__ import division
 from __future__ import print_function
 
 import argparse
@@ -171,7 +172,7 @@
   max_number = int(max_number)
   number = str(number)
   if number.endswith('%'):
-    percent = float(number[:-1]) / 100
+    percent = number[:-1] / 100
     return int(max_number * percent)
   else:
     number = ParseHumanNumber(number)
@@ -443,7 +444,7 @@
                    ProduceHumanNumber(fs_bytes)))
 
         if 'fs_blocks' in part:
-          max_fs_blocks = part['bytes'] / metadata['fs_block_size']
+          max_fs_blocks = part['bytes'] // metadata['fs_block_size']
           part['fs_blocks'] = ParseRelativeNumber(max_fs_blocks,
                                                   part['fs_blocks'])
           part['fs_bytes'] = part['fs_blocks'] * metadata['fs_block_size']
@@ -655,7 +656,7 @@
     # specified is being used for the filesytem, minus the space reserved for
     # the hashpad.
     partition['fs_bytes'] = partition['bytes']
-    partition['fs_blocks'] = partition['fs_bytes'] / metadata['fs_block_size']
+    partition['fs_blocks'] = partition['fs_bytes'] // metadata['fs_block_size']
     partition['bytes'] = int(partition['bytes'] * 1.15)
 
 def GetPartitionTableFromConfig(options, layout_filename, image_type):
@@ -1313,7 +1314,7 @@
       continue
 
     size = ProduceHumanNumber(partition['bytes'])
-    if 'fs_bytes' in partition.iterkeys():
+    if 'fs_bytes' in partition:
       fs_size = ProduceHumanNumber(partition['fs_bytes'])
     else:
       fs_size = 'auto'
@@ -1373,7 +1374,7 @@
   """
   if k < 0 or n < k:
     return 0
-  return math.factorial(n) / (math.factorial(k) * math.factorial(n - k))
+  return math.factorial(n) // (math.factorial(k) * math.factorial(n - k))
 
 
 def CheckReservedEraseBlocks(partitions):
@@ -1409,10 +1410,10 @@
 
       reserved = partition['reserved_erase_blocks']
       erase_block_size = metadata['erase_block_size']
-      device_erase_blocks = metadata['bytes'] / erase_block_size
+      device_erase_blocks = metadata['bytes'] // erase_block_size
       device_bad_blocks = metadata['max_bad_erase_blocks']
       distributions = Combinations(device_erase_blocks, device_bad_blocks)
-      partition_erase_blocks = partition['bytes'] / erase_block_size
+      partition_erase_blocks = partition['bytes'] // erase_block_size
       # The idea is to calculate the number of ways that there could be reserved
       # or more bad blocks inside the partition, assuming that there are
       # device_bad_blocks in the device in total (the worst case). To get the
@@ -1428,7 +1429,7 @@
                        device_bad_blocks - partition_bad_blocks)
           for partition_bad_blocks
           in range(reserved + 1, device_bad_blocks + 1))
-      probability = (1.0 * ways_for_failure) / distributions
+      probability = ways_for_failure / distributions
       if probability > 0.00001:
         raise ExcessFailureProbability('excessive probability %f of too many '
                                        'bad blocks in partition %s'
@@ -1537,7 +1538,12 @@
       'validate': Validate,
   }
 
-  subparsers = parser.add_subparsers(title='Commands')
+  # Subparsers are required by default under Python 2.  Python 3 changed to
+  # not required, but didn't include a required option until 3.7.  Setting
+  # the required member works in all versions (and setting dest name).
+  subparsers = parser.add_subparsers(title='Commands', dest='command')
+  subparsers.required = True
+
   for name, func in sorted(action_map.items()):
     # Turn the func's docstring into something we can show the user.
     desc, doc = func.__doc__.split('\n', 1)
diff --git a/build_library/cgpt_unittest.py b/build_library/cgpt_unittest.py
index 271325c..6e8aea6 100755
--- a/build_library/cgpt_unittest.py
+++ b/build_library/cgpt_unittest.py
@@ -10,13 +10,13 @@
 
 from __future__ import print_function
 
-import cgpt
-
 import os
 import shutil
 import tempfile
 import unittest
 
+import cgpt
+
 
 class JSONLoadingTest(unittest.TestCase):
   """Test stacked JSON loading functions."""
@@ -223,9 +223,9 @@
             'layouts': {
                 'common': [],
                 'base': [
-                    {'num': 2, 'name': "Part 2"},
-                    {'type': 'blank', 'size': "64 MiB"},
-                    {'num': 1, 'name': "Part 1"}
+                    {'num': 2, 'name': 'Part 2'},
+                    {'type': 'blank', 'size': '64 MiB'},
+                    {'num': 1, 'name': 'Part 1'}
                 ]
             }})
 
@@ -499,7 +499,7 @@
                         'features': [],
                         u'format': u'ubi',
                         'fs_bytes': 253952,
-                        u'fs_size': u"253952",
+                        u'fs_size': u'253952',
                         u'label': u'ROOT-A',
                         u'num': 1,
                         u'size': u'256 KiB',
@@ -522,18 +522,18 @@
   def testParseHumanNumber(self):
     """Test that ParseHumanNumber is correct."""
     test_cases = [
-        ("1", 1),
-        ("2", 2),
-        ("1KB", 1000),
-        ("1KiB", 1024),
-        ("1 K", 1024),
-        ("1 KiB", 1024),
-        ("3 MB", 3000000),
-        ("4 MiB", 4 * 2**20),
-        ("5GB", 5 * 10**9),
-        ("6GiB", 6 * 2**30),
-        ("7TB", 7 * 10**12),
-        ("8TiB", 8 * 2**40),
+        ('1', 1),
+        ('2', 2),
+        ('1KB', 1000),
+        ('1KiB', 1024),
+        ('1 K', 1024),
+        ('1 KiB', 1024),
+        ('3 MB', 3000000),
+        ('4 MiB', 4 * 2**20),
+        ('5GB', 5 * 10**9),
+        ('6GiB', 6 * 2**30),
+        ('7TB', 7 * 10**12),
+        ('8TiB', 8 * 2**40),
     ]
     for inp, exp in test_cases:
       self.assertEqual(cgpt.ParseHumanNumber(inp), exp)
@@ -541,16 +541,16 @@
   def testProduceHumanNumber(self):
     """Test that ProduceHumanNumber is correct."""
     test_cases = [
-        ("1", 1),
-        ("2", 2),
-        ("1 KB", 1000),
-        ("1 KiB", 1024),
-        ("3 MB", 3 * 10**6),
-        ("4 MiB", 4 * 2**20),
-        ("5 GB", 5 * 10**9),
-        ("6 GiB", 6 * 2**30),
-        ("7 TB", 7 * 10**12),
-        ("8 TiB", 8 * 2**40),
+        ('1', 1),
+        ('2', 2),
+        ('1 KB', 1000),
+        ('1 KiB', 1024),
+        ('3 MB', 3 * 10**6),
+        ('4 MiB', 4 * 2**20),
+        ('5 GB', 5 * 10**9),
+        ('6 GiB', 6 * 2**30),
+        ('7 TB', 7 * 10**12),
+        ('8 TiB', 8 * 2**40),
     ]
     for exp, inp in test_cases:
       self.assertEqual(cgpt.ProduceHumanNumber(inp), exp)