Modify the parser and tko db code to flag test models as being for
insertion or update, add a new "RUNNING" status to tko, and then add
code to the v1 parser to generate a "RUNNING" record when the test
starts and then a final update record when the test ends.

Risk: High
Visibility: When a test is in progress mostly-empty RUNNING results
should show up in tko, to be replaced by the final record when the
test finishes.

Signed-off-by: John Admanski <jadmanski@google.com>



git-svn-id: http://test.kernel.org/svn/autotest/trunk@1836 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/client/common_lib/logging.py b/client/common_lib/logging.py
index 4f15a62..5f6f620 100644
--- a/client/common_lib/logging.py
+++ b/client/common_lib/logging.py
@@ -8,7 +8,7 @@
 
 
 job_statuses = ["TEST_NA", "ABORT", "ERROR", "FAIL", "WARN", "GOOD", "ALERT",
-                "NOSTATUS"]
+                "RUNNING", "NOSTATUS"]
 
 def is_valid_status(status):
     if not re.match(r'(START|(END )?('+'|'.join(job_statuses)+'))$',
diff --git a/tko/db.py b/tko/db.py
index 4bac507..df91103 100644
--- a/tko/db.py
+++ b/tko/db.py
@@ -164,10 +164,14 @@
 
         values = []
         if where and isinstance(where, types.DictionaryType):
-            # key/value pairs (which should be equal)
-            keys = [field + '=%s' for field in where.keys()]
-            values = [where[field] for field in where.keys()]
-
+            # key/value pairs (which should be equal, or None for null)
+            keys, values = [], []
+            for field, value in where.iteritems():
+                if value is None:
+                    keys.append(field + ' is null')
+                else:
+                    keys.append(field + '=%s')
+                    values.append(value)
             cmd.append(' where ' + ' and '.join(keys))
         elif where and isinstance(where, types.StringTypes):
             # the exact string
@@ -282,14 +286,14 @@
         fields = data.keys()
         data_refs = [field + '=%s' for field in fields]
         data_values = [data[field] for field in fields]
-        cmd += ' set ' + ' and '.join(data_refs)
+        cmd += ' set ' + ', '.join(data_refs)
 
         where_keys = [field + '=%s' for field in where.keys()]
         where_values = [where[field] for field in where.keys()]
         cmd += ' where ' + ' and '.join(where_keys)
 
         values = data_values + where_values
-        print '%s %s' % (cmd, values)
+        self.dprint('%s %s' % (cmd, values))
 
         self._exec_sql_with_commit(cmd, values, commit)
 
@@ -299,6 +303,7 @@
         for test_idx in self.find_tests(job_idx):
             where = {'test_idx' : test_idx}
             self.delete('iteration_result', where)
+            self.delete('iteration_attributes', where)
             self.delete('test_attributes', where)
         where = {'job_idx' : job_idx}
         self.delete('tests', where)
@@ -331,9 +336,13 @@
                 'reason':test.reason, 'machine_idx':job.machine_idx,
                 'started_time': test.started_time,
                 'finished_time':test.finished_time}
-        self.insert('tests', data, commit=commit)
-        test_idx = self.get_last_autonumber_value()
-        data = { 'test_idx':test_idx }
+        if hasattr(test, "test_idx"):
+            test_idx = test.test_idx
+            self.update('tests', data, {'test_idx': test_idx}, commit=commit)
+        else:
+            self.insert('tests', data, commit=commit)
+            test_idx = test.test_idx = self.get_last_autonumber_value()
+        data = {'test_idx': test_idx}
 
         for i in test.iterations:
             data['iteration'] = i.index
@@ -441,8 +450,8 @@
                     commit=commit)
 
 
-    def find_test(self, job_idx, subdir):
-        where = { 'job_idx':job_idx , 'subdir':subdir }
+    def find_test(self, job_idx, testname, subdir):
+        where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
         rows = self.select('test_idx', 'tests', where)
         if rows:
             return rows[0][0]
diff --git a/tko/migrations/012_add_running_status.py b/tko/migrations/012_add_running_status.py
new file mode 100644
index 0000000..db7db2e
--- /dev/null
+++ b/tko/migrations/012_add_running_status.py
@@ -0,0 +1,6 @@
+def migrate_up(manager):
+    manager.execute("INSERT INTO status (word) values ('RUNNING')")
+
+
+def migrate_down(manager):
+    manager.execute("DELETE FROM status where word = 'RUNNING'")
diff --git a/tko/models.py b/tko/models.py
index a5e230b..d754e9e 100644
--- a/tko/models.py
+++ b/tko/models.py
@@ -56,7 +56,7 @@
 
     @classmethod
     def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
-                   started_time, finished_time):
+                   started_time, finished_time, existing_instance=None):
         """Given a job and the basic metadata about the test that
         can be extracted from the status logs, parse the test
         keyval files and use it to construct a complete test
@@ -76,9 +76,28 @@
             iterations = []
             attributes = {}
 
-        return cls(subdir, testname, status, reason, test_kernel,
-                   job.machine, started_time, finished_time,
-                   iterations, attributes)
+        if existing_instance:
+            def constructor(*args, **dargs):
+                existing_instance.__init__(*args, **dargs)
+                return existing_instance
+        else:
+            constructor = cls
+        return constructor(subdir, testname, status, reason, test_kernel,
+                           job.machine, started_time, finished_time,
+                           iterations, attributes)
+
+
+    @classmethod
+    def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
+                           started_time):
+        """Given a job and the basic metadata available when a test is
+        started, create a test instance representing the partial result.
+        Assume that since the test is not complete there are no results files
+        actually available for parsing."""
+        tko_utils.dprint("parsing partial test %s %s" % (subdir, testname))
+
+        return cls(subdir, testname, "RUNNING", reason, test_kernel,
+                   job.machine, started_time, None, [], {})
 
 
     @staticmethod
diff --git a/tko/parsers/version_1.py b/tko/parsers/version_1.py
index 317a082..95f608d 100644
--- a/tko/parsers/version_1.py
+++ b/tko/parsers/version_1.py
@@ -110,6 +110,7 @@
         current_kernel = kernel("", [])  # UNKNOWN
         started_time_stack = [None]
         subdir_stack = [None]
+        running_test = None
 
         while True:
             # are we finished with parsing?
@@ -142,9 +143,25 @@
             # initial line processing
             if line.type == "START":
                 stack.start()
-                if (line.testname, line.subdir) == (None,) * 2:
+                started_time = line.get_timestamp()
+                if (line.testname, line.subdir) == (None, None):
+                    # we just started a client, all tests are relative to here
                     min_stack_size = stack.size()
-                started_time_stack.append(line.get_timestamp())
+                elif stack.size() == min_stack_size + 1:
+                    # we just started a new test, insert a running record
+                    assert(running_test is None)
+                    running_test = test.parse_partial_test(self.job,
+                                                           line.subdir,
+                                                           line.testname,
+                                                           line.reason,
+                                                           current_kernel,
+                                                           started_time)
+                    msg = "RUNNING: %s\nSubdir: %s\nTestname: %s\n%s"
+                    msg %= (running_test.status, running_test.subdir,
+                            running_test.testname, running_test.reason)
+                    tko_utils.dprint(msg)
+                    new_tests.append(running_test)
+                started_time_stack.append(started_time)
                 subdir_stack.append(line.subdir)
                 continue
             elif line.type == "STATUS":
@@ -212,7 +229,9 @@
                                            line.reason,
                                            current_kernel,
                                            started_time,
-                                           finished_time)
+                                           finished_time,
+                                           running_test)
+                running_test = None
                 msg = "ADD: %s\nSubdir: %s\nTestname: %s\n%s"
                 msg %= (new_test.status, new_test.subdir,
                         new_test.testname, new_test.reason)