Merge lp:~pwlars/lava-test/demo-parse into lp:lava-test/0.0

Proposed by Paul Larson
Status: Merged
Merged at revision: 19
Proposed branch: lp:~pwlars/lava-test/demo-parse
Merge into: lp:lava-test/0.0
Diff against target: 250 lines (+166/-4)
5 files modified
abrek/builtins.py (+19/-0)
abrek/test_definitions/stream.py (+4/-1)
abrek/testdef.py (+92/-2)
tests/__init__.py (+2/-1)
tests/test_abrektestparser.py (+49/-0)
To merge this branch: bzr merge lp:~pwlars/lava-test/demo-parse
Reviewer Review Type Date Requested Status
James Westby (community) Approve
Paul Larson (community) Needs Resubmitting
Review via email: mp+30688@code.launchpad.net

Description of the change

I want to make this more widely known, but this wasn't precisely what I wanted to merge. This version includes a cmd_parse class to add a command that I don't intend to actually add. It is just for demonstration purposes, but I could possibly see committing it for now, then pulling it out later. The actual parsing would not be a manual step that the user needs to enter a command for, but would rather happen as part of the results submission process. In the interest of moving forward though, I'd like to get this committed soon, so that I don't end up with too many branches queued up.

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote :

29 + testdata = json.loads(file(testdatafile,'r').read())
30 + test = abrek.testdef.testloader(testdata['testname'])
31 + try:
32 + test.parse(self.args[0]

Why read all the data and then not pass it to parse?

51 +class StreamTestParser(abrek.testdef.AbrekTestParser):
52 + def parse(self):
53 + super(StreamTestParser, self).parse()
54 + self.appendtoall({'units':'MB/s'})

This class isn't used, is it necessary?

104 + the parse() method should be called while already in the results
105 + directory and assumes that a file for test output will exist called
106 + testoutput.log.

Why not take a file descriptor with the test results? It's more flexible,
and chdir() isn't a great idea in library code.

122 + appenall=

typo in append.

182 + t['result'] = fixupdict[t['result']]

Do you want to assume that fixupdict will contain a key for every
result that will be found?

155 + def append(self,testid,entry):

Please add spaces after the comments in argument lists.

127 + self.results = {'testlist':[]}

Why a dict containing a list, and not just a list?

Seeing as you plan to remove cmd_parse I don't mind it missing tests,
but perhaps we should consider tests for the test_definitions?

Thanks,

James

review: Needs Information
Revision history for this message
Paul Larson (pwlars) wrote :

> Why read all the data and then not pass it to parse?
I need to read that bit to figure out which test this is exactly, so
that I can load the right parser. The parser then acts on the actual
test output

> This class isn't used, is it necessary?
 No, it was a leftover from a previous revision. I really thought I
had removed it. I will certainly do that now

> Why not take a file descriptor with the test results? It's more flexible,
> and chdir() isn't a great idea in library code.
I know, I don't like the chdir either, but I wanted to handle cases that are outside the norm more easily. For instance, if for some reason there is more than one output file that needs to be parsed. It would be useful to let the test definition extend the AbrekTestParser class and have it handle whatever files it needs to in the parse() method.

> typo in append.
Fixed

> Do you want to assume that fixupdict will contain a key for every
> result that will be found?
Typically, I expect it probably will. But there's always some chance
that you need to convert from a result format that is almost right,
except for one kind of result. Or a better example might be where the
results you get are good for things like pass, fail, etc, but you have
some odd result that you need to categorize differently.

> Please add spaces after the comments in argument lists.
Done

> Why a dict containing a list, and not just a list?
This is going to get combined with other test data for the submission, of which the testlist is just one thing.

> Seeing as you plan to remove cmd_parse I don't mind it missing tests,
> but perhaps we should consider tests for the test_definitions?
I'm not sure it would serve a
terribly useful purpose. The test_definitions are using the classes
defined elsewhere that I do test - and those are the things I want to
keep from breaking. If the test_definitions break, it would more likely
be due to something changing in the test that they download. That's why
I would like to target a specific version of the test. But if I were to
write a unit test for it, it would have to be based on assumptions about
the version of the test we know about today. So the test_definition
could still be broken by a new version, but the unit test wouldn't help
catch that.

review: Needs Resubmitting
lp:~pwlars/lava-test/demo-parse updated
16. By Paul Larson

Some minor cleanups

Revision history for this message
James Westby (james-w) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'abrek/builtins.py'
2--- abrek/builtins.py 2010-07-16 17:09:35 +0000
3+++ abrek/builtins.py 2010-07-26 14:22:47 +0000
4@@ -1,3 +1,4 @@
5+import json
6 import os
7 import sys
8 from optparse import make_option
9@@ -5,6 +6,7 @@
10 import abrek.command
11 import abrek.testdef
12
13+
14 class cmd_version(abrek.command.AbrekCmd):
15 """
16 Show the version of abrek
17@@ -69,6 +71,23 @@
18 print "Test execution error: %s" % strerror
19 sys.exit(1)
20
21+class cmd_parse(abrek.command.AbrekCmd):
22+ def run(self):
23+ if len(self.args) != 1:
24+ print "please specify the name of the result dir"
25+ sys.exit(1)
26+ config = abrek.config.AbrekConfig()
27+ resultsdir = os.path.join(config.resultsdir,self.args[0])
28+ testdatafile = os.path.join(resultsdir,"testdata.json")
29+ testdata = json.loads(file(testdatafile,'r').read())
30+ test = abrek.testdef.testloader(testdata['testname'])
31+ try:
32+ test.parse(self.args[0])
33+ except Exception as strerror:
34+ print "Test parse error: %s" % strerror
35+ sys.exit(1)
36+ print test.parser.results
37+
38 class cmd_uninstall(abrek.command.AbrekCmd):
39 """
40 Uninstall a test
41
42=== modified file 'abrek/test_definitions/stream.py'
43--- abrek/test_definitions/stream.py 2010-06-28 20:23:02 +0000
44+++ abrek/test_definitions/stream.py 2010-07-26 14:22:47 +0000
45@@ -4,8 +4,11 @@
46 MD5="b6cd43b848e0d8b0824703369392f3c5"
47 INSTALLSTEPS = ['cc stream.c -O2 -fopenmp -o stream']
48 RUNSTEPS = ['./stream']
49+PATTERN = "^(?P<testid>\w+):\W+(?P<result>\d+\.\d+)"
50
51 streaminst = abrek.testdef.AbrekTestInstaller(INSTALLSTEPS, url=URL, md5=MD5)
52 streamrun = abrek.testdef.AbrekTestRunner(RUNSTEPS)
53+streamparser = abrek.testdef.AbrekTestParser(PATTERN,
54+ appendall={'units':'MB/s'})
55 testobj = abrek.testdef.AbrekTest(testname="stream", installer=streaminst,
56- runner=streamrun)
57+ runner=streamrun, parser=streamparser)
58
59=== modified file 'abrek/testdef.py'
60--- abrek/testdef.py 2010-07-14 21:36:48 +0000
61+++ abrek/testdef.py 2010-07-26 14:22:47 +0000
62@@ -1,6 +1,7 @@
63 import hashlib
64 import json
65 import os
66+import re
67 import shutil
68 import sys
69 import time
70@@ -91,14 +92,17 @@
71 self.runner.run(self.resultsdir)
72 self._savetestdata()
73
74- def parse(self,results):
75+ def parse(self, resultname):
76 if not self.parser:
77 raise RuntimeError("no test parser defined for '%s'" %
78 self.testname)
79- self.parser.parse(results)
80+ self.resultsdir = os.path.join(self.config.resultsdir, resultname)
81+ os.chdir(self.resultsdir)
82+ self.parser.parse()
83
84 class AbrekTestInstaller(object):
85 """Base class for defining an installer object.
86+
87 This class can be used as-is for simple installers, or extended for more
88 advanced funcionality.
89
90@@ -181,6 +185,92 @@
91 self._runsteps(resultsdir)
92 self.endtime = datetime.utcnow()
93
94+class AbrekTestParser(object):
95+ """Base class for defining a test parser
96+
97+ This class can be used as-is for simple results parsers, but will
98+ likely need to be extended slightly for many. If used as it is,
99+ the parse() method should be called while already in the results
100+ directory and assumes that a file for test output will exist called
101+ testoutput.log.
102+
103+ pattern - regexp pattern to identify important elements of test output
104+ For example: If your testoutput had lines that look like:
105+ "test01: PASS", then you could use a pattern like this:
106+ "^(?P<testid>\w+):\W+(?P<result>\w+)"
107+ This would result in identifying "test01" as testid and "PASS"
108+ as result. Once parse() has been called, self.results.testlist[]
109+ contains a list of dicts of all the key,value pairs found for
110+ each test result
111+ fixupdict - dict of strings to convert test results to standard strings
112+ For example: if you want to standardize on having pass/fail results
113+ in lower case, but your test outputs them in upper case, you could
114+ use a fixupdict of something like: {'PASS':'pass','FAIL':'fail'}
115+ appendall - Append a dict to the testlist entry for each result.
116+ For example: if you would like to add units="MB/s" to each result:
117+ appendall={'units':'MB/s'}
118+ """
119+ def __init__(self, pattern=None, fixupdict=None, appendall={}):
120+ self.pattern = pattern
121+ self.fixupdict = fixupdict
122+ self.results = {'testlist':[]}
123+ self.appendall = appendall
124+
125+ def _find_testid(self, id):
126+ for x in self.results['testlist']:
127+ if x['testid'] == id:
128+ return self.results['testlist'].index(x)
129+
130+ def parse(self):
131+ """Parse test output to gather results
132+
133+ Use the pattern specified when the class was instantiated to look
134+ through the results line-by-line and find lines that match it.
135+ Results are then stored in self.results. If a fixupdict was supplied
136+ it is used to convert test result strings to a standard format.
137+ """
138+ filename = "testoutput.log"
139+ pat = re.compile(self.pattern)
140+ with open(filename, 'r') as fd:
141+ for line in fd.readlines():
142+ match = pat.search(line)
143+ if match:
144+ self.results['testlist'].append(match.groupdict())
145+ if self.fixupdict:
146+ self.fixresults(self.fixupdict)
147+ if self.appendall:
148+ self.appendtoall(self.appendall)
149+
150+ def append(self, testid, entry):
151+ """Appends a dict to the testlist entry for a specified testid
152+
153+ This lets you add a dict to the entry for a specific testid
154+ entry should be a dict, updates it in place
155+ """
156+ index = self._find_testid(testid)
157+ self.results['testlist'][index].update(entry)
158+
159+ def appendtoall(self, entry):
160+ """Append entry to each item in the testlist.
161+
162+ entry - dict of key,value pairs to add to each item in the testlist
163+ """
164+ for t in self.results['testlist']:
165+ t.update(entry)
166+
167+ def fixresults(self, fixupdict):
168+ """Convert results to a known, standard format
169+
170+ pass it a dict of keys/values to replace
171+ For instance:
172+ {"TPASS":"pass", "TFAIL":"fail"}
173+ This is really only used for qualitative tests
174+ """
175+ for t in self.results['testlist']:
176+ if t.has_key("result"):
177+ t['result'] = fixupdict[t['result']]
178+
179+
180 def testloader(testname):
181 """
182 Load the test definition, which can be either an individual
183
184=== modified file 'tests/__init__.py'
185--- tests/__init__.py 2010-07-09 21:49:54 +0000
186+++ tests/__init__.py 2010-07-26 14:22:47 +0000
187@@ -4,7 +4,8 @@
188 module_names = ['tests.test_builtins',
189 'tests.test_abrekcmd',
190 'tests.test_abrektestinstaller',
191- 'tests.test_abrektestrunner']
192+ 'tests.test_abrektestrunner',
193+ 'tests.test_abrektestparser']
194 loader = unittest.TestLoader()
195 suite = loader.loadTestsFromNames(module_names)
196 return suite
197
198=== added file 'tests/test_abrektestparser.py'
199--- tests/test_abrektestparser.py 1970-01-01 00:00:00 +0000
200+++ tests/test_abrektestparser.py 2010-07-26 14:22:47 +0000
201@@ -0,0 +1,49 @@
202+import os
203+import shutil
204+import tempfile
205+import unittest
206+
207+from abrek.testdef import AbrekTestParser
208+
209+class testAbrekTestParser(unittest.TestCase):
210+ def setUp(self):
211+ self.origdir = os.path.abspath(os.curdir)
212+ self.tmpdir = tempfile.mkdtemp()
213+ self.filename = os.path.abspath(__file__)
214+ os.chdir(self.tmpdir)
215+
216+ def tearDown(self):
217+ os.chdir(self.origdir)
218+ shutil.rmtree(self.tmpdir)
219+
220+ def makeparser(self, *args, **kwargs):
221+ return AbrekTestParser(*args, **kwargs)
222+
223+ def writeoutputlog(self, str):
224+ with open("testoutput.log", "a") as fd:
225+ fd.write(str)
226+
227+ def test_parse(self):
228+ pattern = "^(?P<testid>\w+):\W+(?P<result>\w+)"
229+ self.writeoutputlog("test001: pass")
230+ parser = self.makeparser(pattern)
231+ parser.parse()
232+ self.assertTrue(parser.results["testlist"][0]["testid"] == "test001" and
233+ parser.results["testlist"][0]["result"] == "pass")
234+
235+ def test_fixupdict(self):
236+ pattern = "^(?P<testid>\w+):\W+(?P<result>\w+)"
237+ fixup = {"pass":"PASS"}
238+ self.writeoutputlog("test001: pass")
239+ parser = self.makeparser(pattern, fixupdict=fixup)
240+ parser.parse()
241+ self.assertEquals("PASS", parser.results["testlist"][0]["result"])
242+
243+ def test_appendall(self):
244+ pattern = "^(?P<testid>\w+):\W+(?P<result>\w+)"
245+ append = {"units":"foo/s"}
246+ self.writeoutputlog("test001: pass")
247+ parser = self.makeparser(pattern, appendall=append)
248+ parser.parse()
249+ self.assertEqual("foo/s", parser.results["testlist"][0]["units"])
250+

Subscribers

People subscribed via source and target branches