[dvsim] Fix for missing coverage

- Fixed and optimized the logic that tracks the status of currently
dispatched processes. There was a bug in the existing code the caused
dvsim to prematurely exit before all coverage jobs were done.

Signed-off-by: Srikrishna Iyer <sriyer@google.com>
diff --git a/util/dvsim/Deploy.py b/util/dvsim/Deploy.py
index 7ec8494..c88cb3b 100644
--- a/util/dvsim/Deploy.py
+++ b/util/dvsim/Deploy.py
@@ -25,8 +25,10 @@
     Abstraction for deploying builds and runs.
     """
 
-    # Timer.
-    num_secs = 0
+    # Timer in hours, minutes and seconds.
+    hh = 0
+    mm = 0
+    ss = 0
 
     # Maintain a list of dispatched items.
     dispatch_counter = 0
@@ -310,23 +312,35 @@
             del self.process
 
     @staticmethod
+    def increment_timer():
+        # sub function that increments with overflow = 60
+        def _incr_ovf_60(val):
+            if val >= 59:
+                val = 0
+                return val, True
+            else:
+                val += 1
+                return val, False
+
+        incr_hh = False
+        Deploy.ss, incr_mm = _incr_ovf_60(Deploy.ss)
+        if incr_mm: Deploy.mm, incr_hh = _incr_ovf_60(Deploy.mm)
+        if incr_hh: Deploy.hh += 1
+
+    @staticmethod
     def deploy(items):
         dispatched_items = []
         queued_items = []
 
-        if Deploy.print_legend:
-            # Print legend once at the start of the run.
-            log.info("[legend]: [Q: queued, D: dispatched, " + \
-                     "P: passed, F: failed, K: killed, T: total]")
-            Deploy.print_legend = False
+        # Print timer val in hh:mm:ss.
+        def get_timer_val():
+            return "%02i:%02i:%02i" % (Deploy.hh, Deploy.mm, Deploy.ss)
 
-        def get_etime():
-            # Convert num_secs to hh:mm:ss
-            hh = Deploy.num_secs // (3600)
-            ss = Deploy.num_secs % (3600)
-            mm = ss // 60
-            ss %= 60
-            return "%02i:%02i:%02i" % (hh, mm, ss)
+        # Check if elapsed time has reached the next print interval.
+        def has_print_interval_reached():
+            # Deploy.print_interval is expected to be < 1 hour.
+            return (((Deploy.mm * 60 + Deploy.ss) %
+                     Deploy.print_interval) == 0)
 
         def dispatch_items(items):
             item_names = OrderedDict()
@@ -341,48 +355,75 @@
                 if item_names[target] != "":
                     item_names[target] = "  [" + item_names[target][:-2] + "]"
                     log.log(VERBOSE, "[%s]: [%s]: [dispatch]:\n%s",
-                            get_etime(), target, item_names[target])
+                            get_timer_val(), target, item_names[target])
 
-        def track_progress(status, print_status_flag):
+        # Initialize status for a target, add '_stats_' for the said target
+        # and initialize counters for queued, dispatched, passed, failed,
+        # killed and total to 0. Also adds a boolean key to indicate if all
+        # items in a given target are done.
+        def init_status_target_stats(status, target):
+            status[target] = OrderedDict()
+            status[target]['_stats_'] = OrderedDict()
+            status[target]['_stats_']['Q'] = 0
+            status[target]['_stats_']['D'] = 0
+            status[target]['_stats_']['P'] = 0
+            status[target]['_stats_']['F'] = 0
+            status[target]['_stats_']['K'] = 0
+            status[target]['_stats_']['T'] = 0
+            status[target]['_done_'] = False
+
+        # Update status counter for a newly queued item.
+        def add_status_target_queued(status, item):
+            if item.target not in status.keys():
+                init_status_target_stats(status, item.target)
+            status[item.target][item] = "Q"
+            status[item.target]['_stats_']['Q'] += 1
+            status[item.target]['_stats_']['T'] += 1
+
+        # Update status counters for a target.
+        def update_status_target_stats(status, item):
+            old_status = status[item.target][item]
+            status[item.target]['_stats_'][old_status] -= 1
+            status[item.target]['_stats_'][item.status] += 1
+            status[item.target][item] = item.status
+
+        def check_if_done_and_print_status(status, print_status_flag):
             all_done = True
             for target in status.keys():
-                if "target_done" in status[target].keys(): continue
-                stats = OrderedDict()
-                stats["Q"] = 0
-                stats["D"] = 0
-                stats["P"] = 0
-                stats["F"] = 0
-                stats["K"] = 0
-                stats["T"] = 0
-                target_done = False
-                for item in status[target].keys():
-                    stats[status[target][item]] += 1
-                    stats["T"] += 1
-                if stats["Q"] == 0 and stats["D"] == 0:
-                    target_done = True
-                all_done &= target_done
+                target_done_prev = status[target]['_done_']
+                target_done_curr = ((status[target]['_stats_']["Q"] == 0) and
+                                    (status[target]['_stats_']["D"] == 0))
+                status[target]['_done_'] = target_done_curr
+                all_done &= target_done_curr
 
-                if print_status_flag:
+                # Print if flag is set and target_done is not True for two
+                # consecutive times.
+                if not (target_done_prev and
+                        target_done_curr) and print_status_flag:
+                    stats = status[target]['_stats_']
                     width = "0{}d".format(len(str(stats["T"])))
                     msg = "["
                     for s in stats.keys():
                         msg += s + ": {:{}}, ".format(stats[s], width)
                     msg = msg[:-2] + "]"
-                    log.info("[%s]: [%s]: %s", get_etime(), target, msg)
-                    if target_done: status[target]["target_done"] = True
+                    log.info("[%s]: [%s]: %s", get_timer_val(), target, msg)
             return all_done
 
-        all_done = False
+        # Print legend once at the start of the run.
+        if Deploy.print_legend:
+            log.info("[legend]: [Q: queued, D: dispatched, "
+                     "P: passed, F: failed, K: killed, T: total]")
+            Deploy.print_legend = False
+
         status = OrderedDict()
         print_status_flag = True
 
         # Queue all items
-        queued_items.extend(items)
+        queued_items = items
         for item in queued_items:
-            if item.target not in status.keys():
-                status[item.target] = OrderedDict()
-            status[item.target][item] = "Q"
+            add_status_target_queued(status, item)
 
+        all_done = False
         while not all_done:
             # Get status of dispatched items.
             for item in dispatched_items:
@@ -394,19 +435,17 @@
                             # Kill its sub items if item did not pass.
                             item.set_sub_status("K")
                             log.error("[%s]: [%s]: [status] [%s: %s]",
-                                      get_etime(), item.target,
+                                      get_timer_val(), item.target,
                                       item.identifier, item.status)
                         else:
                             log.log(VERBOSE, "[%s]: [%s]: [status] [%s: %s]",
-                                    get_etime(), item.target, item.identifier,
-                                    item.status)
+                                    get_timer_val(), item.target,
+                                    item.identifier, item.status)
                         # Queue items' sub-items if it is done.
                         queued_items.extend(item.sub)
                         for sub_item in item.sub:
-                            if sub_item.target not in status.keys():
-                                status[sub_item.target] = OrderedDict()
-                            status[sub_item.target][sub_item] = "Q"
-                status[item.target][item] = item.status
+                            add_status_target_queued(status, sub_item)
+                    update_status_target_stats(status, item)
 
             # Dispatch items from the queue as slots free up.
             all_done = (len(queued_items) == 0)
@@ -420,17 +459,17 @@
                     else:
                         dispatch_items(queued_items)
                         dispatched_items.extend(queued_items)
-                        queued_items = []
+                        del queued_items[:]
 
             # Check if we are done and print the status periodically.
-            all_done &= track_progress(status, print_status_flag)
+            all_done &= check_if_done_and_print_status(status,
+                                                       print_status_flag)
 
             # Advance time by 1s if there is more work to do.
             if not all_done:
                 time.sleep(1)
-                Deploy.num_secs += 1
-                print_status_flag = ((Deploy.num_secs %
-                                      Deploy.print_interval) == 0)
+                Deploy.increment_timer()
+                print_status_flag = has_print_interval_reached()
 
 
 class CompileSim(Deploy):