The reparse of existing job is done by a delete and then an insert, this
introduces a new job_idx/test_idx for the same job and voids the old ones,
so users won't be able to use job_idx/test_idx to refer to job/test as their
index might change.

This cl makes reparse to keep the same job_idx and test_idx for existing
jobs.

Also fixes a bug where user created test attributes are being deleted
inadvertently during reparse.

Signed-off-by: Jiqing Tang <jiqingtang@google.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@3992 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/tko/db.py b/tko/db.py
index 4b3f82a..5303ff0 100644
--- a/tko/db.py
+++ b/tko/db.py
@@ -324,16 +324,20 @@
         if match:
             afe_job_id = match.group(1)
 
-        self.insert('jobs', {'tag':tag,
-                             'label': job.label,
-                             'username': job.user,
-                             'machine_idx': job.machine_idx,
-                             'queued_time': job.queued_time,
-                             'started_time': job.started_time,
-                             'finished_time': job.finished_time,
-                             'afe_job_id': afe_job_id},
-                             commit=commit)
-        job.index = self.get_last_autonumber_value()
+        data = {'tag':tag,
+                'label': job.label,
+                'username': job.user,
+                'machine_idx': job.machine_idx,
+                'queued_time': job.queued_time,
+                'started_time': job.started_time,
+                'finished_time': job.finished_time,
+                'afe_job_id': afe_job_id}
+        is_update = hasattr(job, 'index')
+        if is_update:
+            self.update('jobs', data, {'job_idx': job.index}, commit=commit)
+        else:
+            self.insert('jobs', data, commit=commit)
+            job.index = self.get_last_autonumber_value()
         for test in job.tests:
             self.insert_test(job, test, commit=commit)
 
@@ -353,6 +357,7 @@
             where = {'test_idx': test_idx}
             self.delete('iteration_result', where)
             self.delete('iteration_attributes', where)
+            where['user_created'] = 0
             self.delete('test_attributes', where)
         else:
             self.insert('tests', data, commit=commit)
diff --git a/tko/parse.py b/tko/parse.py
index 67f76a5..a82ab0a 100755
--- a/tko/parse.py
+++ b/tko/parse.py
@@ -71,48 +71,24 @@
     mail.send("", job.user, "", subject, message_header + message)
 
 
-def find_old_tests(db, job_idx):
-    """
-    Given a job index, return a list of all the test objects associated with
-    it in the database, including labels, but excluding other "complex"
-    data (attributes, iteration data, kernels).
-    """
-    raw_tests = db.select("test_idx,subdir,test,started_time,finished_time",
-                          "tests", {"job_idx": job_idx})
-    if not raw_tests:
-        return []
-
-    test_ids = ", ".join(str(raw_test[0]) for raw_test in raw_tests)
-
-    labels = db.select("test_id, testlabel_id", "test_labels_tests",
-                       "test_id in (%s)" % test_ids)
-    label_map = {}
-    for test_id, testlabel_id in labels:
-        label_map.setdefault(test_id, []).append(testlabel_id)
-
-    tests = []
-    for raw_test in raw_tests:
-        tests.append(models.test(raw_test[1], raw_test[2], None, None, None,
-                                 None, raw_test[3], raw_test[4],
-                                 [], {}, label_map.get(raw_test[0], [])))
-    return tests
-
-
 def parse_one(db, jobname, path, reparse, mail_on_failure):
     """
     Parse a single job. Optionally send email on failure.
     """
     tko_utils.dprint("\nScanning %s (%s)" % (jobname, path))
     old_job_idx = db.find_job(jobname)
-    old_tests = []
-    if reparse and old_job_idx:
-        tko_utils.dprint("! Deleting old copy of job results to "
-                         "reparse it")
-        old_tests = find_old_tests(db, old_job_idx)
-        db.delete_job(jobname)
-    if db.find_job(jobname):
-        tko_utils.dprint("! Job is already parsed, done")
-        return
+    # old tests is a dict from tuple (test_name, subdir) to test_idx
+    old_tests = {}
+    if old_job_idx is not None:
+        if not reparse:
+            tko_utils.dprint("! Job is already parsed, done")
+            return
+
+        raw_old_tests = db.select("test_idx,subdir,test", "tests",
+                                  {"job_idx": old_job_idx})
+        if raw_old_tests:
+            old_tests = dict(((test, subdir), test_idx)
+                             for test_idx, subdir, test in raw_old_tests)
 
     # look up the status version
     job_keyval = models.job.read_keyval(path)
@@ -142,19 +118,25 @@
             already_added.add(test)
             job.tests.append(test)
 
-    # try and port labels over from the old tests, but if old tests stop
+    # try and port test_idx over from the old tests, but if old tests stop
     # matching up with new ones just give up
-    for test, old_test in zip(job.tests, old_tests):
-        tests_are_the_same = (test.testname == old_test.testname and
-                              test.subdir == old_test.subdir and
-                              test.started_time == old_test.started_time and
-                              (test.finished_time == old_test.finished_time or
-                               old_test.finished_time is None))
-        if tests_are_the_same:
-            test.labels = old_test.labels
-        else:
-            tko_utils.dprint("! Reparse returned new tests, "
-                             "dropping old test labels")
+    if reparse and old_job_idx is not None:
+        job.index = old_job_idx
+        for test in job.tests:
+            test_idx = old_tests.pop((test.testname, test.subdir), None)
+            if test_idx is not None:
+                test.test_idx = test_idx
+            else:
+                tko_utils.dprint("! Reparse returned new test "
+                                 "testname=%r subdir=%r" %
+                                 (test.testname, test.subdir))
+        for test_idx in old_tests.itervalues():
+            where = {'test_idx' : test_idx}
+            db.delete('iteration_result', where)
+            db.delete('iteration_attributes', where)
+            db.delete('test_attributes', where)
+            db.delete('test_labels_tests', {'test_id': test_idx})
+            db.delete('tests', where)
 
     # check for failures
     message_lines = [""]