| OLD | NEW |
| 1 # Copyright 2015 The LUCI Authors. All rights reserved. | 1 # Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Abstract stream interface for representing recipe runs. | 5 """Abstract stream interface for representing recipe runs. |
| 6 | 6 |
| 7 We need to create streams for steps (and substeps) and also LOG_LINE steps. | 7 We need to create streams for steps (and substeps) and also LOG_LINE steps. |
| 8 LogDog will implement LOG_LINE steps as real logs (i.e. uniformly), but | 8 LogDog will implement LOG_LINE steps as real logs (i.e. uniformly), but |
| 9 annotations will implement them differently from normal logs, so we need | 9 annotations will implement them differently from normal logs, so we need |
| 10 a way to distinguish. | 10 a way to distinguish. |
| (...skipping 28 matching lines...) Expand all Loading... |
| 39 Args: | 39 Args: |
| 40 stream (StreamEngine.Stream): The stream to output to. | 40 stream (StreamEngine.Stream): The stream to output to. |
| 41 it (iterable): An iterable that yields strings to write. | 41 it (iterable): An iterable that yields strings to write. |
| 42 """ | 42 """ |
| 43 for text in it: | 43 for text in it: |
| 44 lines = (text.split('\n') if text else ('',)) | 44 lines = (text.split('\n') if text else ('',)) |
| 45 for line in lines: | 45 for line in lines: |
| 46 stream.write_line(line) | 46 stream.write_line(line) |
| 47 | 47 |
| 48 | 48 |
| 49 def _check(cond, msg, **kwargs): | |
| 50 """Runtime assertion used for parameter correctness.""" | |
| 51 if not cond: | |
| 52 raise ValueError('Failed assertion: %s %r' % (msg, kwargs)) | |
| 53 | |
| 54 | |
| 55 class StreamEngine(object): | 49 class StreamEngine(object): |
| 56 | |
| 57 class Stream(object): | 50 class Stream(object): |
| 58 def write_line(self, line): | 51 def write_line(self, line): |
| 59 raise NotImplementedError() | 52 raise NotImplementedError() |
| 60 | 53 |
| 61 def write_split(self, string): | 54 def write_split(self, string): |
| 62 """Write a string (which may contain newlines) to the stream. It will | 55 """Write a string (which may contain newlines) to the stream. It will |
| 63 be terminated by a newline.""" | 56 be terminated by a newline.""" |
| 64 for actual_line in string.splitlines() or ['']: # preserve empty lines | 57 for actual_line in string.splitlines() or ['']: # preserve empty lines |
| 65 self.write_line(actual_line) | 58 self.write_line(actual_line) |
| 66 | 59 |
| (...skipping 26 matching lines...) Expand all Loading... |
| 93 raise NotImplementedError() | 86 raise NotImplementedError() |
| 94 | 87 |
| 95 def set_build_property(self, key, value): | 88 def set_build_property(self, key, value): |
| 96 raise NotImplementedError() | 89 raise NotImplementedError() |
| 97 | 90 |
| 98 def trigger(self, trigger_spec): | 91 def trigger(self, trigger_spec): |
| 99 raise NotImplementedError() | 92 raise NotImplementedError() |
| 100 | 93 |
| 101 def make_step_stream(self, name, **kwargs): | 94 def make_step_stream(self, name, **kwargs): |
| 102 """Shorthand for creating a step stream from a step configuration dict.""" | 95 """Shorthand for creating a step stream from a step configuration dict.""" |
| 103 _check('\n' not in name, 'Stream name must not contain a newline.', | |
| 104 name=name) | |
| 105 kwargs['name'] = name | 96 kwargs['name'] = name |
| 106 return self.new_step_stream(recipe_api.StepConfig.create(**kwargs)) | 97 return self.new_step_stream(recipe_api.StepConfig.create(**kwargs)) |
| 107 | 98 |
| 108 def new_step_stream(self, step_config): | 99 def new_step_stream(self, step_config): |
| 109 """Creates a new StepStream in this engine. | 100 """Creates a new StepStream in this engine. |
| 110 | 101 |
| 111 The step will be considered started at the moment this method is called. | 102 The step will be considered started at the moment this method is called. |
| 112 | 103 |
| 113 TODO(luqui): allow_subannotations is a bit of a hack, whether to allow | 104 TODO(luqui): allow_subannotations is a bit of a hack, whether to allow |
| 114 annotations that this step emits through to the annotator (True), or | 105 annotations that this step emits through to the annotator (True), or |
| (...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 316 class StepStream(StreamEngine.StepStream): | 307 class StepStream(StreamEngine.StepStream): |
| 317 def __init__(self, engine, step_name): | 308 def __init__(self, engine, step_name): |
| 318 super(StreamEngineInvariants.StepStream, self).__init__() | 309 super(StreamEngineInvariants.StepStream, self).__init__() |
| 319 self._engine = engine | 310 self._engine = engine |
| 320 self._step_name = step_name | 311 self._step_name = step_name |
| 321 self._open = True | 312 self._open = True |
| 322 self._logs = {} | 313 self._logs = {} |
| 323 self._status = 'SUCCESS' | 314 self._status = 'SUCCESS' |
| 324 | 315 |
| 325 def write_line(self, line): | 316 def write_line(self, line): |
| 326 assert '\n' not in line, 'Individual line must not contain a newline.' | 317 assert '\n' not in line |
| 327 assert self._open | 318 assert self._open |
| 328 | 319 |
| 329 def close(self): | 320 def close(self): |
| 330 assert self._open | 321 assert self._open |
| 331 for log_name, log in self._logs.iteritems(): | 322 for log_name, log in self._logs.iteritems(): |
| 332 assert not log._open, 'Log %s still open when closing step %s' % ( | 323 assert not log._open, 'Log %s still open when closing step %s' % ( |
| 333 log_name, self._step_name) | 324 log_name, self._step_name) |
| 334 self._open = False | 325 self._open = False |
| 335 | 326 |
| 336 def new_log_stream(self, log_name): | 327 def new_log_stream(self, log_name): |
| 337 assert self._open | 328 assert self._open |
| 338 assert log_name not in self._logs, 'Log %s already exists in step %s' % ( | 329 assert log_name not in self._logs, 'Log %s already exists in step %s' % ( |
| 339 log_name, self._step_name) | 330 log_name, self._step_name) |
| 340 | |
| 341 _check('\n' not in log_name, 'Log name must not contain a newline.', | |
| 342 log_name=log_name) | |
| 343 ret = self._engine.LogStream(self, log_name) | 331 ret = self._engine.LogStream(self, log_name) |
| 344 self._logs[log_name] = ret | 332 self._logs[log_name] = ret |
| 345 return ret | 333 return ret |
| 346 | 334 |
| 347 def add_step_text(self, text): | 335 def add_step_text(self, text): |
| 348 _check('\n' not in text, 'Step text must not contain a newline.', | 336 pass |
| 349 text=text) | |
| 350 | 337 |
| 351 def add_step_summary_text(self, text): | 338 def add_step_summary_text(self, text): |
| 352 _check('\n' not in text, 'Step summary text must not contain a newline.', | 339 pass |
| 353 text=text) | |
| 354 | 340 |
| 355 def add_step_link(self, name, url): | 341 def add_step_link(self, name, url): |
| 356 _check(isinstance(name, basestring), 'Link name is not a string', | 342 assert isinstance(name, basestring), 'Link name %s is not a string' % name |
| 357 name=name) | 343 assert isinstance(url, basestring), 'Link url %s is not a string' % url |
| 358 _check('\n' not in name, 'Link name must not contain a newline.', | |
| 359 name=name) | |
| 360 _check(isinstance(url, basestring), | |
| 361 'Link URL is not a string', url=url) | |
| 362 _check('\n' not in url, 'Link URL must not contain a newline.', | |
| 363 url=url) | |
| 364 | 344 |
| 365 def set_step_status(self, status): | 345 def set_step_status(self, status): |
| 366 assert status in ('SUCCESS', 'WARNING', 'FAILURE', 'EXCEPTION') | 346 assert status in ('SUCCESS', 'WARNING', 'FAILURE', 'EXCEPTION') |
| 367 if status == 'SUCCESS': | 347 if status == 'SUCCESS': |
| 368 # A constraint imposed by the annotations implementation | 348 # A constraint imposed by the annotations implementation |
| 369 assert self._status == 'SUCCESS', ( | 349 assert self._status == 'SUCCESS', ( |
| 370 'Cannot set successful status after status is %s' % self._status) | 350 'Cannot set successful status after status is %s' % self._status) |
| 371 self._status = status | 351 self._status = status |
| 372 | 352 |
| 373 def set_build_property(self, key, value): | 353 def set_build_property(self, key, value): |
| 374 _check('\n' not in key, 'Property key must not contain a newline.', | 354 pass |
| 375 key=key) | |
| 376 _check('\n' not in value, 'Property value must not contain a newline.', | |
| 377 value=value) | |
| 378 json.loads(value) # value must be a valid JSON object. | |
| 379 | 355 |
| 380 def trigger(self, spec): | 356 def trigger(self, spec): |
| 381 _check('\n' not in spec, 'Spec must not contain a newline.', | 357 assert '\n' not in spec # Spec must fit on one line. |
| 382 spec=spec) | 358 json.loads(spec) # Spec must be a valid json object. |
| 383 json.loads(spec) # Spec must be a valid JSON object. | |
| 384 | 359 |
| 385 class LogStream(StreamEngine.Stream): | 360 class LogStream(StreamEngine.Stream): |
| 386 def __init__(self, step_stream, log_name): | 361 def __init__(self, step_stream, log_name): |
| 387 self._step_stream = step_stream | 362 self._step_stream = step_stream |
| 388 self._log_name = log_name | 363 self._log_name = log_name |
| 389 self._open = True | 364 self._open = True |
| 390 | 365 |
| 391 def write_line(self, line): | 366 def write_line(self, line): |
| 392 assert '\n' not in line, 'Individual line must not contain a newline.' | 367 assert '\n' not in line |
| 393 assert self._step_stream._open | 368 assert self._step_stream._open |
| 394 assert self._open | 369 assert self._open |
| 395 | 370 |
| 396 def close(self): | 371 def close(self): |
| 397 assert self._step_stream._open | 372 assert self._step_stream._open |
| 398 assert self._open | 373 assert self._open |
| 399 self._open = False | 374 self._open = False |
| 400 | 375 |
| 401 def new_step_stream(self, step_config): | 376 def new_step_stream(self, step_config): |
| 402 assert step_config.name not in self._streams, ( | 377 assert step_config.name not in self._streams, ( |
| (...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 555 string (which means it might not be valid ascii), we decode the string with | 530 string (which means it might not be valid ascii), we decode the string with |
| 556 the 'replace' error mode, which replaces invalid characters with a suitable | 531 the 'replace' error mode, which replaces invalid characters with a suitable |
| 557 replacement character. | 532 replacement character. |
| 558 """ | 533 """ |
| 559 try: | 534 try: |
| 560 return str(s) | 535 return str(s) |
| 561 except UnicodeEncodeError: | 536 except UnicodeEncodeError: |
| 562 return s.encode('utf-8', 'replace') | 537 return s.encode('utf-8', 'replace') |
| 563 except UnicodeDecodeError: | 538 except UnicodeDecodeError: |
| 564 return s.decode('utf-8', 'replace') | 539 return s.decode('utf-8', 'replace') |
| OLD | NEW |