Merge lp:~javier.collado/utah/bug1089884 into lp:utah
- bug1089884
- Merge into dev
Status: | Merged |
---|---|
Merged at revision: | 792 |
Proposed branch: | lp:~javier.collado/utah/bug1089884 |
Merge into: | lp:utah |
Diff against target: |
271 lines (+133/-19) 2 files modified
utah/isotest/iso_static_validation.py (+127/-14) utah/run.py (+6/-5) |
To merge this branch: | bzr merge lp:~javier.collado/utah/bug1089884 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Joe Talbott (community) | Approve | ||
Javier Collado (community) | Needs Resubmitting | ||
Review via email:
|
Commit message
Description of the change
This branch updates the ISO validation script to generate a yaml file report
that can be parsed by the QA dashboard.
Aside from this, the script output has been modified to:
- Include all debug logs
- Print backtraces and skipped test cases
Backtraces were already in the script, but was lost when switiching to running
the test suite manually to process results to generate the yaml report.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Javier Collado (javier.collado) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Max Brustkern (nuclearbob) wrote : | # |
There are a few things I'd like to see changed here, but I'm not sure they're necessary to deployment.
First, I ran this on a raring ISO from last week, and it passed. I know we've discussed changing the tests to support validating old ISOs, but the current default behavior is to fail on an old ISO, which I think is appropriate for daily smoke testing, where we always want to be testing the latest ISO.
Second, and less important, I don't like that the default behavior of this script is to write a file to my current working directory. In my view, the only time that's useful is when we're running under jenkins, and if we are running under jenkins, there are several ways we can handle that. We can write the yaml to standard out by default (and redirect it to a file in the jenkins job.) We can default to writing a file to a logs directory, and either add a parameter to write that file to a specific location (such as the current working directory) or provide clear and consistent output regarding where that file is written so that the output can be parsed and the file picked up. If I'm testing the scripts, or manually validating an ISO for some reason, I'm going to end up with extra files in my branch that need to be cleaned up.
- 800. By Javier Collado
-
Added log directory default (relative path not used anymore)
- 801. By Javier Collado
-
Fixed error/failure counters
Bad subclassing of `unittest.
TestResult` was the problem. Using `super` to call
the default implementation, brings the counters correct value back.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Javier Collado (javier.collado) wrote : | # |
Fixed both issues in previous comment.
- 802. By Javier Collado
-
Fixed log filename printed to stdout
- 803. By Javier Collado
-
Fixed unknown `uname` should be an empty array
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Javier Collado (javier.collado) wrote : | # |
Log file name is now printed to stdout (and prefixed with a tab) and `uname` is
set to an empty array.
- 804. By Javier Collado
-
Fixed timestamp format
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Javier Collado (javier.collado) wrote : | # |
Generated report example:
---
arch: i386
build_number: '20121108.2'
commands: []
errors: 5
failures: 0
fetch_errors: 0
install_type: desktop
media-info: Ubuntu 13.04 "Raring Ringtail" - Alpha i386 (20121108.2)
passes: 5
ran_at: 2012-12-13_15-06-22
release: raring
runlist: iso_static_
uname: []
- 805. By Javier Collado
-
Fixed use different timestamp format in the report and in the filename
- 806. By Javier Collado
-
Added name field to the report
- 807. By Javier Collado
-
Expanded comment about `name` and `runlist` fields
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Javier Collado (javier.collado) wrote : | # |
New format after the fixes above:
---
arch: i386
build_number: '20121108.2'
commands: []
errors: 5
failures: 0
fetch_errors: 0
install_type: desktop
media-info: Ubuntu 13.04 "Raring Ringtail" - Alpha i386 (20121108.2)
name: iso_static_
passes: 5
ran_at: '2012-12-13 16:18:35'
release: raring
runlist: iso_static_
uname: []
- 808. By Javier Collado
-
Moved logging message outside of loop to get it just once
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Joe Talbott (joetalbott) wrote : | # |
Looks good to me.
Preview Diff
1 | === modified file 'utah/isotest/iso_static_validation.py' |
2 | --- utah/isotest/iso_static_validation.py 2012-12-07 18:04:00 +0000 |
3 | +++ utah/isotest/iso_static_validation.py 2012-12-13 17:31:21 +0000 |
4 | @@ -20,7 +20,7 @@ |
5 | # |
6 | # This file is part of utah. |
7 | # |
8 | -# iso_satatic_validation is free software: you can redistribute it |
9 | +# iso_static_validation is free software: you can redistribute it |
10 | # and/or modify it under the terms of the GNU General Public License |
11 | # as published by the Free Software Foundation, either version 3 of |
12 | # the License, or (at your option) any later version. |
13 | @@ -37,6 +37,13 @@ |
14 | |
15 | # TODO: support for non-daily images |
16 | |
17 | +"""ISO static validation. |
18 | + |
19 | +The test cases in this scripts are used to make sure that an ISO image isn't |
20 | +broken and can be used for smoke testing. |
21 | + |
22 | +""" |
23 | + |
24 | import os |
25 | import argparse |
26 | import sys |
27 | @@ -48,6 +55,11 @@ |
28 | import re |
29 | import tempfile |
30 | import shutil |
31 | +import time |
32 | +import platform |
33 | +from traceback import format_exception |
34 | + |
35 | +import yaml |
36 | |
37 | lib_path = os.path.abspath('../') |
38 | sys.path.append(lib_path) |
39 | @@ -57,6 +69,7 @@ |
40 | # Defaults |
41 | DEFAULT_ISOROOT = os.path.expanduser('~/Downloads') |
42 | DEFAULT_URL = 'http://cdimage.ubuntu.com/' |
43 | +DEFAULT_LOG_DIR = '/var/log/utah/' |
44 | |
45 | #constants |
46 | ONE_MB_BLOCK = 2 ** 20 |
47 | @@ -66,7 +79,26 @@ |
48 | parser.add_argument('--name') |
49 | args = parser.parse_args() |
50 | |
51 | -logging.basicConfig(level=logging.INFO) |
52 | + |
53 | +def configure_logging(log_level=logging.DEBUG): |
54 | + """Configure logging to stderr using the passed log_level. |
55 | + |
56 | + :param log_level: Log level to be used |
57 | + :type log_level: int |
58 | + |
59 | + """ |
60 | + logger = logging.getLogger() |
61 | + logger.setLevel(logging.DEBUG) |
62 | + formatter = logging.Formatter('%(levelname)s: %(message)s') |
63 | + |
64 | + log_handler = logging.StreamHandler() |
65 | + log_handler.setFormatter(formatter) |
66 | + log_handler.setLevel(log_level) |
67 | + |
68 | + logger.addHandler(log_handler) |
69 | + |
70 | + |
71 | +configure_logging() |
72 | |
73 | if args.name is None or '.iso' not in args.name: |
74 | print 'The usage:', sys.argv[0], '--name release-variant-arch.iso' |
75 | @@ -101,10 +133,12 @@ |
76 | try: |
77 | cls.temp_dir = tempfile.mkdtemp() |
78 | except OSError: |
79 | - logging.error("Creating temp dirrectory failed") |
80 | + logging.error("Creating temp directory failed") |
81 | unittest.fail("Setup error") |
82 | |
83 | def setUp(self): |
84 | + logging.info('\n{separator}\n{id}\n{separator}' |
85 | + .format(separator='-' * 80, id=self.id())) |
86 | self.block_size = ONE_MB_BLOCK |
87 | self.iso_location = iso_location |
88 | logging.debug('Using iso at: ' + self.iso_location) |
89 | @@ -198,8 +232,8 @@ |
90 | logging.debug('Checking for error in extracting the file list') |
91 | self.assertEqual(stderr, '') |
92 | |
93 | + logging.debug('Checking that each list entry is in the iso') |
94 | for list_entry in list_repository: |
95 | - logging.debug('Checking that each list entry is in the iso') |
96 | self.assertIn(list_entry[1:].rstrip(), stdout) |
97 | |
98 | # Test if the manfest is the same as that is given in the server |
99 | @@ -221,8 +255,8 @@ |
100 | manifest_path = self.iso.extract('casper/filesystem.manifest', |
101 | self.temp_dir) |
102 | manifest_local = open(manifest_path) |
103 | + logging.debug('Checking manifest are the same in iso and repo') |
104 | for manifest_entry in manifest: |
105 | - logging.debug('Checking manifest are the same in iso and repo') |
106 | self.assertIn(manifest_entry, manifest_local) |
107 | |
108 | # Test if the buildid present in the download server is |
109 | @@ -278,8 +312,8 @@ |
110 | # wubi is not shipped in raring |
111 | exclude_files.append('autorun.inf') |
112 | exclude_files.append('wubi.exe') |
113 | + logging.debug('Check if relevant files are present in the iso') |
114 | for list_server in files_list: |
115 | - logging.debug('Check if relevant files are present in the iso') |
116 | path = list_server.rstrip() |
117 | if path in exclude_files: |
118 | logging.debug(path + ' excluded based on release') |
119 | @@ -359,12 +393,17 @@ |
120 | self.assertIn("Squashfs filesystem", stdout) |
121 | |
122 | #unsquashfs extracts only in pwd |
123 | - os.chdir(self.temp_dir) |
124 | - |
125 | - cmd = ["unsquashfs", "-f", squashfs_path, "-l", "vmlinuz"] |
126 | - output = subprocess.Popen(cmd, stdout=subprocess.PIPE, |
127 | - stderr=subprocess.PIPE) |
128 | - (stdout, stderr) = output.communicate() |
129 | + cwd = os.getcwd() |
130 | + try: |
131 | + os.chdir(self.temp_dir) |
132 | + |
133 | + cmd = ["unsquashfs", "-f", squashfs_path, "-l", "vmlinuz"] |
134 | + output = subprocess.Popen(cmd, stdout=subprocess.PIPE, |
135 | + stderr=subprocess.PIPE) |
136 | + (stdout, stderr) = output.communicate() |
137 | + finally: |
138 | + os.chdir(cwd) |
139 | + |
140 | logging.debug('Check that vmlinuz could be extracted') |
141 | self.assertEqual(stderr, '') |
142 | self.assertIn("created 1 directories", stdout) |
143 | @@ -378,6 +417,80 @@ |
144 | logging.error("Removing the temp directory failed") |
145 | unittest.fail("Cleanup error") |
146 | |
147 | + |
148 | +def get_report_data(runlist, results): |
149 | + errors = len(results.errors) |
150 | + failures = len(results.failures) |
151 | + skipped = len(results.skipped) |
152 | + passes = results.testsRun - skipped - errors - failures |
153 | + report_timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) |
154 | + |
155 | + # Note: Ideally 'name' is the runlist name and 'runlist' the path to the |
156 | + # runlist file. Given that there isn't a real runlist file here, the name |
157 | + # is being used for both fields. |
158 | + report_data = { |
159 | + 'name': runlist, |
160 | + 'runlist': runlist, |
161 | + 'media-info': iso.media_info, |
162 | + 'build_number': iso.buildnumber, |
163 | + 'release': iso.series, |
164 | + 'ran_at': report_timestamp, |
165 | + 'install_type': iso.installtype, |
166 | + 'arch': iso.arch, |
167 | + 'commands': [], |
168 | + 'fetch_errors': 0, |
169 | + 'errors': errors, |
170 | + 'failures': failures, |
171 | + 'passes': passes, |
172 | + 'uname': [], |
173 | + } |
174 | + |
175 | + return report_data |
176 | + |
177 | + |
178 | +class ISOResult(unittest.TestResult): |
179 | + def _print_error(self, test, error): |
180 | + logging.error(test) |
181 | + logging.error(''.join(format_exception(*error))) |
182 | + |
183 | + def addError(self, test, error): |
184 | + super(ISOResult, self).addError(test, error) |
185 | + self._print_error(test, error) |
186 | + |
187 | + def addFailure(self, test, error): |
188 | + super(ISOResult, self).addError(test, error) |
189 | + self._print_error(test, error) |
190 | + |
191 | + def addSkip(self, test, reason): |
192 | + logging.info('\n{separator}\n{id}\n{separator}' |
193 | + .format(separator='-' * 80, id=test.id())) |
194 | + logging.info('Skipped: {}'.format(reason)) |
195 | + |
196 | + |
197 | +def main(): |
198 | + """Run test cases and write qa dashboard friendly report.""" |
199 | + suite = unittest.TestLoader().loadTestsFromTestCase(TestValidateISO) |
200 | + results = ISOResult() |
201 | + results.buffer = True |
202 | + suite.run(results) |
203 | + |
204 | + fake_runlist = 'iso_static_validation' |
205 | + report_data = get_report_data(fake_runlist, results) |
206 | + report = yaml.dump(report_data, |
207 | + explicit_start='---', |
208 | + default_flow_style=False) |
209 | + filename_timestamp = time.strftime('%Y-%m-%d_%H-%M-%S', time.gmtime()) |
210 | + log_filename = ('{machine}_{runlist}_{timestamp}.{suffix}' |
211 | + .format(machine=platform.node(), |
212 | + runlist=fake_runlist, |
213 | + timestamp=filename_timestamp, |
214 | + suffix='yaml')) |
215 | + absolute_log_filename = os.path.join(DEFAULT_LOG_DIR, log_filename) |
216 | + with open(absolute_log_filename, 'w') as f: |
217 | + f.write(report) |
218 | + print('Test log copied to\n\t{}'.format(absolute_log_filename)) |
219 | + |
220 | + return 0 if results.wasSuccessful() else 1 |
221 | + |
222 | if __name__ == '__main__': |
223 | - from test import test_support |
224 | - test_support.run_unittest(TestValidateISO) |
225 | + sys.exit(main()) |
226 | |
227 | === modified file 'utah/run.py' |
228 | --- utah/run.py 2012-12-06 16:30:34 +0000 |
229 | +++ utah/run.py 2012-12-13 17:31:21 +0000 |
230 | @@ -59,16 +59,16 @@ |
231 | report will be generated. |
232 | |
233 | """ |
234 | - timestamp = time.strftime('%Y-%m-%d_%H-%m-%S', time.gmtime()) |
235 | - |
236 | + gmtime = time.gmtime() |
237 | if report is None: |
238 | # Generate default report with empty results to fake client output |
239 | + report_timestamp = time.strftime('%Y-%m-%d %H:%M:%S', gmtime) |
240 | default_report = { |
241 | 'runlist': runlist, |
242 | 'media-info': machine.image.media_info, |
243 | 'build_number': machine.image.buildnumber, |
244 | 'release': machine.image.series, |
245 | - 'ran_at': timestamp, |
246 | + 'ran_at': report_timestamp, |
247 | 'install_type': machine.installtype, |
248 | 'arch': machine.arch, |
249 | 'commands': [], |
250 | @@ -76,7 +76,7 @@ |
251 | 'errors': 0, |
252 | 'failures': 0, |
253 | 'passes': 0, |
254 | - 'uname': '', |
255 | + 'uname': [], |
256 | |
257 | |
258 | } |
259 | @@ -91,10 +91,11 @@ |
260 | .format(report_type)) |
261 | |
262 | listname = os.path.basename(runlist) |
263 | + filename_timestamp = time.strftime('%Y-%m-%d %H:%M:%S', gmtime) |
264 | log_filename = ('{machine}_{runlist}_{timestamp}.{suffix}' |
265 | .format(machine=machine.name, |
266 | runlist=listname, |
267 | - timestamp=timestamp, |
268 | + timestamp=filename_timestamp, |
269 | suffix=report_type)) |
270 | locallog = os.path.join(config.logpath, log_filename) |
271 | locallog = os.path.normpath(locallog) |
The changes in this branch should be enough to make the ISO validation script
friendlier. However, I still need to look at the setup-jobs.py script to
update the job so that the yaml file is archived as an artifact in jenkins.
Please let me know if some other thing is missing. Thanks.