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.
5 """Classes for collecting results of our BuildStages as they run."""
7 from __future__ import print_function
14 from chromite.cbuildbot import failures_lib
15 from chromite.lib import cros_build_lib
18 def _GetCheckpointFile(buildroot):
19 return os.path.join(buildroot, '.completed_stages')
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)
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'
37 with open(completed_stages_file, 'r') as load_file:
38 Results.RestoreCompletedStages(load_file)
41 class RecordedTraceback(object):
42 """This class represents a traceback recorded in the list of results."""
44 def __init__(self, failed_stage, failed_prefix, exception, traceback):
45 """Construct a RecordedTraceback object.
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.
53 self.failed_stage = failed_stage
54 self.failed_prefix = failed_prefix
55 self.exception = exception
56 self.traceback = traceback
59 _result_fields = ['name', 'result', 'description', 'prefix', 'board', 'time']
60 Result = collections.namedtuple('Result', _result_fields)
63 class _Results(object):
64 """Static class that collects the results of our BuildStages as they run."""
66 SUCCESS = "Stage was successful"
67 FORGIVEN = "Stage failed but was optional"
68 SKIPPED = "Stage was skipped"
69 NON_FAILURE_TYPES = (SUCCESS, FORGIVEN, SKIPPED)
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 = []
78 # Stages run in a previous run and restored. Stored as a dictionary of
79 # names to previous records.
82 self.start_time = datetime.datetime.now()
85 """Clear existing stage results."""
88 def PreviouslyCompletedRecord(self, name):
89 """Check to see if this stage was previously completed.
92 A boolean showing the stage was successful in the previous run.
94 return self._previous.get(name)
96 def BuildSucceededSoFar(self):
97 """Return true if all stages so far have passing states.
99 This method returns true if all was successful or forgiven.
101 return all(entry.result in self.NON_FAILURE_TYPES
102 for entry in self._results_log)
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
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]
118 def Record(self, name, result, description=None, prefix=None, board='',
120 """Store off an additional stage result.
123 name: The name of the stage (e.g. HWTest [bvt])
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.
131 The textual backtrace of the exception, or None
132 prefix: The prefix of the stage (e.g. HWTest). Defaults to
134 board: The board associated with the stage, if any. Defaults to ''.
135 time: How long the result took to complete.
139 result = Result(name, result, description, prefix, board, time)
140 self._results_log.append(result)
143 """Fetch stage results.
146 A list with one entry per stage run with a result.
148 return self._results_log
150 def GetPrevious(self):
151 """Fetch stage results.
154 A list of stages names that were completed in a previous run.
156 return self._previous
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:
163 out.write(self.SPLIT_TOKEN.join(map(str, entry)) + '\n')
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.
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.
177 self._previous[record[0]] = Result(*record)
179 def GetTracebacks(self):
180 """Get a list of the exceptions that failed the build.
183 A list of RecordedTraceback objects.
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,
193 tracebacks.append(traceback)
196 def Report(self, out, archive_urls=None, current_version=None):
197 """Generate a user friendly text display of the results data.
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.
206 results = self._results_log
208 line = '*' * 60 + '\n'
214 ' RELEASE VERSION: ' +
219 out.write(edge + ' Stage Results\n')
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))
226 # Don't print data on skipped stages.
227 if result == self.SKIPPED:
232 if result == self.SUCCESS:
234 elif result == self.FORGIVEN:
235 status = 'FAILED BUT FORGIVEN'
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
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
249 # There was a normal error. Give the type of exception.
250 details = ' with %s' % type(result).__name__
252 out.write('%s %s %s (%s)%s\n' % (edge, status, name, timestr, details))
257 out.write('%s BUILD ARTIFACTS FOR THIS BUILD CAN BE FOUND AT:\n' % edge)
258 for name, url in sorted(archive_urls.iteritems()):
260 link_name = 'Artifacts'
262 named_url = '%s: %s' % (name, url)
263 link_name = 'Artifacts[%s]' % name
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)
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)
278 cros_build_lib.PrintBuildbotStepWarnings(out)