pre-upload: display overall hook runtime

When people report issues and include logs, it can help to see how
long the overall repohooks took to execute.

Bug: None
Test: unittests pass
Change-Id: I87119874cb4d2d0a2aac08f171a876734fb73b52
diff --git a/pre-upload.py b/pre-upload.py
index 0862dea..0fb4015 100755
--- a/pre-upload.py
+++ b/pre-upload.py
@@ -23,6 +23,7 @@
 from __future__ import print_function
 
 import argparse
+import datetime
 import os
 import sys
 
@@ -81,6 +82,7 @@
         self.num_hooks = None
         self.hook_index = 0
         self.success = True
+        self.start_time = datetime.datetime.now()
 
     def set_num_hooks(self, num_hooks):
         """Keep track of how many hooks we'll be running.
@@ -146,10 +148,11 @@
 
     def finish(self):
         """Print summary for all the hooks."""
-        status_line = '[%s] repohooks for %s %s' % (
+        status_line = '[%s] repohooks for %s %s in %s' % (
             self.PASSED if self.success else self.FAILED,
             self.project_name,
-            'passed' if self.success else 'failed')
+            'passed' if self.success else 'failed',
+            rh.utils.timedelta_str(datetime.datetime.now() - self.start_time))
         rh.terminal.print_status_line(status_line, print_newline=True)
 
 
diff --git a/rh/utils.py b/rh/utils.py
index a1aca6e..a4b7b5f 100644
--- a/rh/utils.py
+++ b/rh/utils.py
@@ -37,6 +37,23 @@
 from rh.sixish import string_types
 
 
+def timedelta_str(delta):
+    """A less noisy timedelta.__str__.
+
+    The default timedelta stringification contains a lot of leading zeros and
+    uses microsecond resolution.  This makes for noisy output.
+    """
+    total = delta.total_seconds()
+    hours, rem = divmod(total, 3600)
+    mins, secs = divmod(rem, 60)
+    ret = '%i.%03is' % (secs, delta.microseconds // 1000)
+    if mins:
+        ret = '%im%s' % (mins, ret)
+    if hours:
+        ret = '%ih%s' % (hours, ret)
+    return ret
+
+
 class CommandResult(object):
     """An object to store various attributes of a child process."""
 
diff --git a/rh/utils_unittest.py b/rh/utils_unittest.py
index 881d031..f60e22a 100755
--- a/rh/utils_unittest.py
+++ b/rh/utils_unittest.py
@@ -18,6 +18,7 @@
 
 from __future__ import print_function
 
+import datetime
 import os
 import sys
 import unittest
@@ -35,6 +36,35 @@
 from rh.sixish import mock
 
 
+class TimeDeltaStrTests(unittest.TestCase):
+    """Verify behavior of timedelta_str object."""
+
+    def test_same(self):
+        """Check timedelta of 0 seconds."""
+        delta = datetime.timedelta(0)
+        self.assertEqual('0.000s', rh.utils.timedelta_str(delta))
+
+    def test_millisecondss(self):
+        """Check timedelta of milliseconds."""
+        delta = datetime.timedelta(seconds=0.123456)
+        self.assertEqual('0.123s', rh.utils.timedelta_str(delta))
+
+    def test_seconds(self):
+        """Check timedelta of seconds."""
+        delta = datetime.timedelta(seconds=12.3)
+        self.assertEqual('12.300s', rh.utils.timedelta_str(delta))
+
+    def test_minutes(self):
+        """Check timedelta of minutes."""
+        delta = datetime.timedelta(seconds=72.3)
+        self.assertEqual('1m12.300s', rh.utils.timedelta_str(delta))
+
+    def test_hours(self):
+        """Check timedelta of hours."""
+        delta = datetime.timedelta(seconds=4000.3)
+        self.assertEqual('1h6m40.300s', rh.utils.timedelta_str(delta))
+
+
 class CommandResultTests(unittest.TestCase):
     """Verify behavior of CommandResult object."""