Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / cbuildbot / results_lib.py
1 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Classes for collecting results of our BuildStages as they run."""
6
7 from __future__ import print_function
8
9 import collections
10 import datetime
11 import math
12 import os
13
14 from chromite.cbuildbot import failures_lib
15 from chromite.lib import cros_build_lib
16
17
18 def _GetCheckpointFile(buildroot):
19   return os.path.join(buildroot, '.completed_stages')
20
21
22 def WriteCheckpoint(buildroot):
23   """Drops a completed stages file with current state."""
24   completed_stages_file = _GetCheckpointFile(buildroot)
25   with open(completed_stages_file, 'w+') as save_file:
26     Results.SaveCompletedStages(save_file)
27
28
29 def LoadCheckpoint(buildroot):
30   """Restore completed stage info from checkpoint file."""
31   completed_stages_file = _GetCheckpointFile(buildroot)
32   if not os.path.exists(completed_stages_file):
33     cros_build_lib.Warning('Checkpoint file not found in buildroot %s'
34                            % buildroot)
35     return
36
37   with open(completed_stages_file, 'r') as load_file:
38     Results.RestoreCompletedStages(load_file)
39
40
41 class RecordedTraceback(object):
42   """This class represents a traceback recorded in the list of results."""
43
44   def __init__(self, failed_stage, failed_prefix, exception, traceback):
45     """Construct a RecordedTraceback object.
46
47     Args:
48       failed_stage: The stage that failed during the build. E.g., HWTest [bvt]
49       failed_prefix: The prefix of the stage that failed. E.g., HWTest
50       exception: The raw exception object.
51       traceback: The full stack trace for the failure, as a string.
52     """
53     self.failed_stage = failed_stage
54     self.failed_prefix = failed_prefix
55     self.exception = exception
56     self.traceback = traceback
57
58
59 _result_fields = ['name', 'result', 'description', 'prefix', 'board', 'time']
60 Result = collections.namedtuple('Result', _result_fields)
61
62
63 class _Results(object):
64   """Static class that collects the results of our BuildStages as they run."""
65
66   SUCCESS = "Stage was successful"
67   FORGIVEN = "Stage failed but was optional"
68   SKIPPED = "Stage was skipped"
69   NON_FAILURE_TYPES = (SUCCESS, FORGIVEN, SKIPPED)
70
71   SPLIT_TOKEN = "\_O_/"
72
73   def __init__(self):
74     # List of results for all stages that's built up as we run. Members are of
75     #  the form ('name', SUCCESS | FORGIVEN | Exception, None | description)
76     self._results_log = []
77
78     # Stages run in a previous run and restored. Stored as a dictionary of
79     # names to previous records.
80     self._previous = {}
81
82     self.start_time = datetime.datetime.now()
83
84   def Clear(self):
85     """Clear existing stage results."""
86     self.__init__()
87
88   def PreviouslyCompletedRecord(self, name):
89     """Check to see if this stage was previously completed.
90
91     Returns:
92       A boolean showing the stage was successful in the previous run.
93     """
94     return self._previous.get(name)
95
96   def BuildSucceededSoFar(self):
97     """Return true if all stages so far have passing states.
98
99     This method returns true if all was successful or forgiven.
100     """
101     return all(entry.result in self.NON_FAILURE_TYPES
102                for entry in self._results_log)
103
104   def WasStageSuccessful(self, name):
105     """Return true if stage passed."""
106     cros_build_lib.Info('Checking for %s' % name)
107     for entry in self._results_log:
108       if entry.name == name:
109         cros_build_lib.Info('Found %s' % entry.result)
110         return entry.result == self.SUCCESS
111
112     return False
113
114   def StageHasResults(self, name):
115     """Return true if stage has posted results."""
116     return name in [entry.name for entry in self._results_log]
117
118   def Record(self, name, result, description=None, prefix=None, board='',
119              time=0):
120     """Store off an additional stage result.
121
122     Args:
123       name: The name of the stage (e.g. HWTest [bvt])
124       result:
125         Result should be one of:
126           Results.SUCCESS if the stage was successful.
127           Results.SKIPPED if the stage was skipped.
128           Results.FORGIVEN if the stage had warnings.
129           Otherwise, it should be the exception stage errored with.
130       description:
131         The textual backtrace of the exception, or None
132       prefix: The prefix of the stage (e.g. HWTest). Defaults to
133         the value of name.
134       board: The board associated with the stage, if any. Defaults to ''.
135       time: How long the result took to complete.
136     """
137     if prefix is None:
138       prefix = name
139     result = Result(name, result, description, prefix, board, time)
140     self._results_log.append(result)
141
142   def Get(self):
143     """Fetch stage results.
144
145     Returns:
146       A list with one entry per stage run with a result.
147     """
148     return self._results_log
149
150   def GetPrevious(self):
151     """Fetch stage results.
152
153     Returns:
154       A list of stages names that were completed in a previous run.
155     """
156     return self._previous
157
158   def SaveCompletedStages(self, out):
159     """Save the successfully completed stages to the provided file |out|."""
160     for entry in self._results_log:
161       if entry.result != self.SUCCESS:
162         break
163       out.write(self.SPLIT_TOKEN.join(map(str, entry)) + '\n')
164
165   def RestoreCompletedStages(self, out):
166     """Load the successfully completed stages from the provided file |out|."""
167     # Read the file, and strip off the newlines.
168     for line in out:
169       record = line.strip().split(self.SPLIT_TOKEN)
170       if len(record) != len(_result_fields):
171         cros_build_lib.Warning(
172             'State file does not match expected format, ignoring.')
173         # Wipe any partial state.
174         self._previous = {}
175         break
176
177       self._previous[record[0]] = Result(*record)
178
179   def GetTracebacks(self):
180     """Get a list of the exceptions that failed the build.
181
182     Returns:
183        A list of RecordedTraceback objects.
184     """
185     tracebacks = []
186     for entry in self._results_log:
187       # If entry.result is not in NON_FAILURE_TYPES, then the stage failed, and
188       # entry.result is the exception object and entry.description is a string
189       # containing the full traceback.
190       if entry.result not in self.NON_FAILURE_TYPES:
191         traceback = RecordedTraceback(entry.name, entry.prefix, entry.result,
192                                       entry.description)
193         tracebacks.append(traceback)
194     return tracebacks
195
196   def Report(self, out, archive_urls=None, current_version=None):
197     """Generate a user friendly text display of the results data.
198
199     Args:
200       out: Output stream to write to (e.g. sys.stdout).
201       archive_urls: Dict where values are archive URLs and keys are names
202         to associate with those URLs (typically board name).  If None then
203         omit the name when logging the URL.
204       current_version: Chrome OS version associated with this report.
205     """
206     results = self._results_log
207
208     line = '*' * 60 + '\n'
209     edge = '*' * 2
210
211     if current_version:
212       out.write(line)
213       out.write(edge +
214                 ' RELEASE VERSION: ' +
215                 current_version +
216                 '\n')
217
218     out.write(line)
219     out.write(edge + ' Stage Results\n')
220     warnings = False
221
222     for entry in results:
223       name, result, run_time = (entry.name, entry.result, entry.time)
224       timestr = datetime.timedelta(seconds=math.ceil(run_time))
225
226       # Don't print data on skipped stages.
227       if result == self.SKIPPED:
228         continue
229
230       out.write(line)
231       details = ''
232       if result == self.SUCCESS:
233         status = 'PASS'
234       elif result == self.FORGIVEN:
235         status = 'FAILED BUT FORGIVEN'
236         warnings = True
237       else:
238         status = 'FAIL'
239         if isinstance(result, cros_build_lib.RunCommandError):
240           # If there was a RunCommand error, give just the command that
241           # failed, not its full argument list, since those are usually
242           # too long.
243           details = ' in %s' % result.result.cmd[0]
244         elif isinstance(result, failures_lib.BuildScriptFailure):
245           # BuildScriptFailure errors publish a 'short' name of the
246           # command that failed.
247           details = ' in %s' % result.shortname
248         else:
249           # There was a normal error. Give the type of exception.
250           details = ' with %s' % type(result).__name__
251
252       out.write('%s %s %s (%s)%s\n' % (edge, status, name, timestr, details))
253
254     out.write(line)
255
256     if archive_urls:
257       out.write('%s BUILD ARTIFACTS FOR THIS BUILD CAN BE FOUND AT:\n' % edge)
258       for name, url in sorted(archive_urls.iteritems()):
259         named_url = url
260         link_name = 'Artifacts'
261         if name:
262           named_url = '%s: %s' % (name, url)
263           link_name = 'Artifacts[%s]' % name
264
265         # Output the bot-id/version used in the archive url.
266         link_name = '%s: %s' % (link_name, '/'.join(url.split('/')[-3:-1]))
267         out.write('%s  %s' % (edge, named_url))
268         cros_build_lib.PrintBuildbotLink(link_name, url, handle=out)
269       out.write(line)
270
271     for x in self.GetTracebacks():
272       if x.failed_stage and x.traceback:
273         out.write('\nFailed in stage %s:\n\n' % x.failed_stage)
274         out.write(x.traceback)
275         out.write('\n')
276
277     if warnings:
278       cros_build_lib.PrintBuildbotStepWarnings(out)
279
280
281 Results = _Results()