Risk: Medium
Visibility: Changes the test keyval interface, deprecating the existing
test.write_keyval method in favour of a pair of write_*_keyval methods
for writing test & iteration keyvals. The deprecated method will still
work as it did before, but will generate a warning.

Adds a new iteration_attributes table to the database for storing
generic string attributes on a per-iteration basis, in the same way
that test_attributes allows generic string attributes on a per-test
basis.

This also adds new methods to the test class for writing these keyvals
so that tests can write out attributes by calling
self.write_test_keyval (or self.write_iteration_keyval). The iteration
method accepts parameters for both generic attributes and performance
data.

In order to store both performance and non-performance data in the
iteration keyvals, the format of the line has been extended to look
like "key{blah}=value", with no {blah} being interpreted as equvalent
to "{perf}", for backwards compatiblity.

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



git-svn-id: http://test.kernel.org/svn/autotest/trunk@1535 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/tko/models.py b/tko/models.py
index d6c1c1e..cc9d5ab 100644
--- a/tko/models.py
+++ b/tko/models.py
@@ -1,4 +1,7 @@
-import md5
+import os, md5
+
+from autotest_lib.client.common_lib import utils
+from autotest_lib.tko import utils as tko_utils
 
 
 class job(object):
@@ -44,6 +47,51 @@
 		self.attributes = attributes
 
 
+	@staticmethod
+	def load_iterations(keyval_path):
+		"""Abstract method to load a list of iterations from a keyval
+		file."""
+		raise NotImplemented
+
+
+	@classmethod
+	def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
+		       started_time, finished_time):
+		"""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
+		instance."""
+		tko_utils.dprint("parsing test %s %s" % (subdir, testname))
+
+		if subdir:
+			# grab iterations from the results keyval
+			iteration_keyval = os.path.join(job.dir, subdir,
+							"results", "keyval")
+			iterations = cls.load_iterations(iteration_keyval)
+			iterations = iteration.load_from_keyval(
+			    iteration_keyval)
+
+			# grab test attributes from the subdir keyval
+			test_keyval = os.path.join(job.dir, subdir, "keyval")
+			attributes = test.load_attributes(test_keyval)
+		else:
+			iterations = []
+			attributes = {}
+
+		return cls(subdir, testname, status, reason, test_kernel,
+			   job.machine, started_time, finished_time,
+			   iterations, attributes)
+
+
+	@staticmethod
+	def load_attributes(keyval_path):
+		"""Load the test attributes into a dictionary from a test
+		keyval path. Does not assume that the path actually exists."""
+		if not os.path.exists(keyval_path):
+			return {}
+		return utils.read_keyval(keyval_path)
+
+
 class patch(object):
 	def __init__(self, spec, reference, hash):
 		self.spec = spec
@@ -52,6 +100,42 @@
 
 
 class iteration(object):
-	def __init__(self, index, keyval):
+	def __init__(self, index, attr_keyval, perf_keyval):
 		self.index = index
-		self.keyval = keyval
+		self.attr_keyval = attr_keyval
+		self.perf_keyval = perf_keyval
+
+
+	@staticmethod
+	def parse_line_into_dicts(line, attr_dict, perf_dict):
+		"""Abstract method to parse a keyval line and insert it into
+		the appropriate dictionary.
+			attr_dict: generic iteration attributes
+			perf_dict: iteration performance results
+		"""
+		raise NotImplemented
+
+
+	@classmethod
+	def load_from_keyval(cls, keyval_path):
+		"""Load a list of iterations from an iteration keyval file.
+		Keyval data from separate iterations is separated by blank
+		lines. Makes use of the parse_line_into_dicts method to
+		actually parse the individual lines."""
+		if not os.path.exists(keyval_path):
+			return []
+
+		iterations = []
+		index = 1
+		attr, perf = {}, {}
+		for line in file(path):
+			line = line.strip()
+			if line:
+				cls.parse_line_into_dicts(line, attr, perf)
+			else:
+				iterations.append(cls(index, attr, perf))
+				index += 1
+				attr, perf = {}, {}
+		if attr or perf:
+			iterations.append(cls(index, attr, perf))
+		return iterations