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."""
12 from chromite.cbuildbot import failures_lib
13 from chromite.lib import cros_build_lib
16 def _GetCheckpointFile(buildroot):
17 return os.path.join(buildroot, '.completed_stages')
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)
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'
35 with open(completed_stages_file, 'r') as load_file:
36 Results.RestoreCompletedStages(load_file)
39 class RecordedTraceback(object):
40 """This class represents a traceback recorded in the list of results."""
42 def __init__(self, failed_stage, failed_prefix, exception, traceback):
43 """Construct a RecordedTraceback object.
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.
51 self.failed_stage = failed_stage
52 self.failed_prefix = failed_prefix
53 self.exception = exception
54 self.traceback = traceback
57 _result_fields = ['name', 'result', 'description', 'prefix', 'board', 'time']
58 Result = collections.namedtuple('Result', _result_fields)
61 class _Results(object):
62 """Static class that collects the results of our BuildStages as they run."""
64 SUCCESS = "Stage was successful"
65 FORGIVEN = "Stage failed but was optional"
66 SKIPPED = "Stage was skipped"
67 NON_FAILURE_TYPES = (SUCCESS, FORGIVEN, SKIPPED)
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 = []
76 # Stages run in a previous run and restored. Stored as a dictionary of
77 # names to previous records.
80 self.start_time = datetime.datetime.now()
83 """Clear existing stage results."""
86 def PreviouslyCompletedRecord(self, name):
87 """Check to see if this stage was previously completed.
90 A boolean showing the stage was successful in the previous run.
92 return self._previous.get(name)
94 def BuildSucceededSoFar(self):
95 """Return true if all stages so far have passing states.
97 This method returns true if all was successful or forgiven.
99 return all(entry.result in self.NON_FAILURE_TYPES
100 for entry in self._results_log)
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
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]
116 def Record(self, name, result, description=None, prefix=None, board='',
118 """Store off an additional stage result.
121 name: The name of the stage (e.g. HWTest [bvt])
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.
129 The textual backtrace of the exception, or None
130 prefix: The prefix of the stage (e.g. HWTest). Defaults to
132 board: The board associated with the stage, if any. Defaults to ''.
133 time: How long the result took to complete.
137 result = Result(name, result, description, prefix, board, time)
138 self._results_log.append(result)
141 """Fetch stage results.
144 A list with one entry per stage run with a result.
146 return self._results_log
148 def GetPrevious(self):
149 """Fetch stage results.
152 A list of stages names that were completed in a previous run.
154 return self._previous
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:
161 out.write(self.SPLIT_TOKEN.join(map(str, entry)) + '\n')
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.
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.
175 self._previous[record[0]] = Result(*record)
177 def GetTracebacks(self):
178 """Get a list of the exceptions that failed the build.
181 A list of RecordedTraceback objects.
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,
191 tracebacks.append(traceback)
194 def Report(self, out, archive_urls=None, current_version=None):
195 """Generate a user friendly text display of the results data.
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.
204 results = self._results_log
206 line = '*' * 60 + '\n'
212 ' RELEASE VERSION: ' +
217 out.write(edge + ' Stage Results\n')
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))
224 # Don't print data on skipped stages.
225 if result == self.SKIPPED:
230 if result == self.SUCCESS:
232 elif result == self.FORGIVEN:
233 status = 'FAILED BUT FORGIVEN'
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
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
247 # There was a normal error. Give the type of exception.
248 details = ' with %s' % type(result).__name__
250 out.write('%s %s %s (%s)%s\n' % (edge, status, name, timestr, details))
255 out.write('%s BUILD ARTIFACTS FOR THIS BUILD CAN BE FOUND AT:\n' % edge)
256 for name, url in sorted(archive_urls.iteritems()):
258 link_name = 'Artifacts'
260 named_url = '%s: %s' % (name, url)
261 link_name = 'Artifacts[%s]' % name
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)
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)
276 cros_build_lib.PrintBuildbotStepWarnings(out)