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