OLD | NEW |
1 from __future__ import print_function | 1 from __future__ import print_function |
2 | 2 |
3 import argparse | 3 import argparse |
4 import logging | 4 import logging |
5 import os | 5 import os |
6 import re | 6 import re |
7 import stat | 7 import stat |
8 import subprocess | 8 import subprocess |
9 import sys | 9 import sys |
10 import tarfile | 10 import tarfile |
11 import zipfile | 11 import zipfile |
12 from abc import ABCMeta, abstractmethod | 12 from abc import ABCMeta, abstractmethod |
13 from cStringIO import StringIO | 13 from cStringIO import StringIO as CStringIO |
14 from collections import defaultdict | 14 from collections import defaultdict |
15 from ConfigParser import RawConfigParser | 15 from ConfigParser import RawConfigParser |
16 from io import BytesIO | 16 from io import BytesIO, StringIO |
17 from tools.manifest import manifest | |
18 | 17 |
19 import requests | 18 import requests |
20 | 19 |
21 BaseHandler = None | 20 BaseHandler = None |
22 LogActionFilter = None | 21 LogActionFilter = None |
23 LogHandler = None | 22 LogHandler = None |
24 LogLevelFilter = None | 23 LogLevelFilter = None |
25 StreamHandler = None | 24 StreamHandler = None |
26 TbplFormatter = None | 25 TbplFormatter = None |
| 26 manifest = None |
27 reader = None | 27 reader = None |
28 wptcommandline = None | 28 wptcommandline = None |
29 wptrunner = None | 29 wptrunner = None |
30 wpt_root = None | 30 wpt_root = None |
31 wptrunner_root = None | 31 wptrunner_root = None |
32 | 32 |
33 logger = logging.getLogger(os.path.splitext(__file__)[0]) | 33 logger = None |
34 | |
35 | 34 |
36 def do_delayed_imports(): | 35 def do_delayed_imports(): |
37 """Import and set up modules only needed if execution gets to this point.""" | 36 """Import and set up modules only needed if execution gets to this point.""" |
38 global BaseHandler | 37 global BaseHandler |
39 global LogLevelFilter | 38 global LogLevelFilter |
40 global StreamHandler | 39 global StreamHandler |
41 global TbplFormatter | 40 global TbplFormatter |
| 41 global manifest |
42 global reader | 42 global reader |
43 global wptcommandline | 43 global wptcommandline |
44 global wptrunner | 44 global wptrunner |
45 from mozlog import reader | 45 from mozlog import reader |
46 from mozlog.formatters import TbplFormatter | 46 from mozlog.formatters import TbplFormatter |
47 from mozlog.handlers import BaseHandler, LogLevelFilter, StreamHandler | 47 from mozlog.handlers import BaseHandler, LogLevelFilter, StreamHandler |
| 48 from tools.manifest import manifest |
48 from wptrunner import wptcommandline, wptrunner | 49 from wptrunner import wptcommandline, wptrunner |
49 setup_log_handler() | 50 setup_log_handler() |
50 setup_action_filter() | 51 setup_action_filter() |
51 | 52 |
52 | 53 |
53 def setup_logging(): | 54 def setup_logging(): |
54 """Set up basic debug logger.""" | 55 """Set up basic debug logger.""" |
55 handler = logging.StreamHandler(sys.stdout) | 56 handler = logging.StreamHandler(sys.stdout) |
56 formatter = logging.Formatter(logging.BASIC_FORMAT, None) | 57 formatter = logging.Formatter(logging.BASIC_FORMAT, None) |
57 handler.setFormatter(formatter) | 58 handler.setFormatter(formatter) |
58 logger.addHandler(handler) | 59 logger.addHandler(handler) |
59 logger.setLevel(logging.DEBUG) | 60 logger.setLevel(logging.DEBUG) |
60 | 61 |
61 setup_logging() | |
62 | |
63 | 62 |
64 def setup_action_filter(): | 63 def setup_action_filter(): |
65 """Create global LogActionFilter class as part of deferred module load.""" | 64 """Create global LogActionFilter class as part of deferred module load.""" |
66 global LogActionFilter | 65 global LogActionFilter |
67 | 66 |
68 class LogActionFilter(BaseHandler): | 67 class LogActionFilter(BaseHandler): |
69 | 68 |
70 """Handler that filters out messages not of a given set of actions. | 69 """Handler that filters out messages not of a given set of actions. |
71 | 70 |
72 Subclasses BaseHandler. | 71 Subclasses BaseHandler. |
(...skipping 27 matching lines...) Expand all Loading... |
100 | 99 |
101 def __enter__(self): | 100 def __enter__(self): |
102 """Emit fold start syntax.""" | 101 """Emit fold start syntax.""" |
103 print("travis_fold:start:%s" % self.name, file=sys.stderr) | 102 print("travis_fold:start:%s" % self.name, file=sys.stderr) |
104 | 103 |
105 def __exit__(self, type, value, traceback): | 104 def __exit__(self, type, value, traceback): |
106 """Emit fold end syntax.""" | 105 """Emit fold end syntax.""" |
107 print("travis_fold:end:%s" % self.name, file=sys.stderr) | 106 print("travis_fold:end:%s" % self.name, file=sys.stderr) |
108 | 107 |
109 | 108 |
| 109 class FilteredIO(object): |
| 110 """Wrap a file object, invoking the provided callback for every call to |
| 111 `write` and only proceeding with the operation when that callback returns |
| 112 True.""" |
| 113 def __init__(self, original, on_write): |
| 114 self.original = original |
| 115 self.on_write = on_write |
| 116 |
| 117 def __getattr__(self, name): |
| 118 return getattr(self.original, name) |
| 119 |
| 120 def disable(self): |
| 121 self.write = lambda msg: None |
| 122 |
| 123 def write(self, msg): |
| 124 encoded = msg.encode("utf8", "backslashreplace").decode("utf8") |
| 125 if self.on_write(self.original, encoded) is True: |
| 126 self.original.write(encoded) |
| 127 |
| 128 |
| 129 def replace_streams(capacity, warning_msg): |
| 130 # Value must be boxed to support modification from inner function scope |
| 131 count = [0] |
| 132 capacity -= 2 + len(warning_msg) |
| 133 stderr = sys.stderr |
| 134 |
| 135 def on_write(handle, msg): |
| 136 length = len(msg) |
| 137 count[0] += length |
| 138 |
| 139 if count[0] > capacity: |
| 140 sys.stdout.disable() |
| 141 sys.stderr.disable() |
| 142 handle.write(msg[0:capacity - count[0]]) |
| 143 handle.flush() |
| 144 stderr.write("\n%s\n" % warning_msg) |
| 145 return False |
| 146 |
| 147 return True |
| 148 |
| 149 sys.stdout = FilteredIO(sys.stdout, on_write) |
| 150 sys.stderr = FilteredIO(sys.stderr, on_write) |
| 151 |
| 152 |
110 class Browser(object): | 153 class Browser(object): |
111 __metaclass__ = ABCMeta | 154 __metaclass__ = ABCMeta |
112 | 155 |
113 @abstractmethod | 156 @abstractmethod |
114 def install(self): | 157 def install(self): |
115 return NotImplemented | 158 return NotImplemented |
116 | 159 |
117 @abstractmethod | 160 @abstractmethod |
118 def install_webdriver(self): | 161 def install_webdriver(self): |
119 return NotImplemented | 162 return NotImplemented |
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 logger.error(e.output) | 311 logger.error(e.output) |
269 sys.exit(1) | 312 sys.exit(1) |
270 return git | 313 return git |
271 | 314 |
272 | 315 |
273 def seekable(fileobj): | 316 def seekable(fileobj): |
274 """Attempt to use file.seek on given file, with fallbacks.""" | 317 """Attempt to use file.seek on given file, with fallbacks.""" |
275 try: | 318 try: |
276 fileobj.seek(fileobj.tell()) | 319 fileobj.seek(fileobj.tell()) |
277 except Exception: | 320 except Exception: |
278 return StringIO(fileobj.read()) | 321 return CStringIO(fileobj.read()) |
279 else: | 322 else: |
280 return fileobj | 323 return fileobj |
281 | 324 |
282 | 325 |
283 def untar(fileobj): | 326 def untar(fileobj): |
284 """Extract tar archive.""" | 327 """Extract tar archive.""" |
285 logger.debug("untar") | 328 logger.debug("untar") |
286 fileobj = seekable(fileobj) | 329 fileobj = seekable(fileobj) |
287 with tarfile.open(fileobj=fileobj) as tar_data: | 330 with tarfile.open(fileobj=fileobj) as tar_data: |
288 tar_data.extractall() | 331 tar_data.extractall() |
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
515 else prev[i] | 558 else prev[i] |
516 for i in cols], | 559 for i in cols], |
517 data, | 560 data, |
518 [len(item) + 2 for item in headings]) | 561 [len(item) + 2 for item in headings]) |
519 log("|%s|" % "|".join(item.center(max_widths[i]) for i, item in enumerate(he
adings))) | 562 log("|%s|" % "|".join(item.center(max_widths[i]) for i, item in enumerate(he
adings))) |
520 log("|%s|" % "|".join("-" * max_widths[i] for i in cols)) | 563 log("|%s|" % "|".join("-" * max_widths[i] for i in cols)) |
521 for row in data: | 564 for row in data: |
522 log("|%s|" % "|".join(" %s" % row[i].ljust(max_widths[i] - 1) for i in c
ols)) | 565 log("|%s|" % "|".join(" %s" % row[i].ljust(max_widths[i] - 1) for i in c
ols)) |
523 log("") | 566 log("") |
524 | 567 |
525 | |
526 def write_inconsistent(inconsistent, iterations): | 568 def write_inconsistent(inconsistent, iterations): |
527 """Output inconsistent tests to logger.error.""" | 569 """Output inconsistent tests to logger.error.""" |
528 logger.error("## Unstable results ##\n") | 570 logger.error("## Unstable results ##\n") |
529 strings = [("`%s`" % markdown_adjust(test), ("`%s`" % markdown_adjust(subtes
t)) if subtest else "", err_string(results, iterations)) | 571 strings = [("`%s`" % markdown_adjust(test), ("`%s`" % markdown_adjust(subtes
t)) if subtest else "", err_string(results, iterations)) |
530 for test, subtest, results in inconsistent] | 572 for test, subtest, results in inconsistent] |
531 table(["Test", "Subtest", "Results"], strings, logger.error) | 573 table(["Test", "Subtest", "Results"], strings, logger.error) |
532 | 574 |
533 | 575 |
534 def write_results(results, iterations, comment_pr): | 576 def write_results(results, iterations, comment_pr): |
535 """Output all test results to logger.info.""" | 577 """Output all test results to logger.info.""" |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
575 parser.add_argument("--comment-pr", | 617 parser.add_argument("--comment-pr", |
576 action="store", | 618 action="store", |
577 default=os.environ.get("TRAVIS_PULL_REQUEST"), | 619 default=os.environ.get("TRAVIS_PULL_REQUEST"), |
578 help="PR to comment on with stability results") | 620 help="PR to comment on with stability results") |
579 parser.add_argument("--user", | 621 parser.add_argument("--user", |
580 action="store", | 622 action="store", |
581 # Travis docs say do not depend on USER env variable. | 623 # Travis docs say do not depend on USER env variable. |
582 # This is a workaround to get what should be the same va
lue | 624 # This is a workaround to get what should be the same va
lue |
583 default=os.environ.get("TRAVIS_REPO_SLUG").split('/')[0]
, | 625 default=os.environ.get("TRAVIS_REPO_SLUG").split('/')[0]
, |
584 help="Travis user name") | 626 help="Travis user name") |
| 627 parser.add_argument("--output-bytes", |
| 628 action="store", |
| 629 type=int, |
| 630 help="Maximum number of bytes to write to standard outpu
t/error") |
585 parser.add_argument("product", | 631 parser.add_argument("product", |
586 action="store", | 632 action="store", |
587 help="Product to run against (`browser-name` or 'browser
-name:channel')") | 633 help="Product to run against (`browser-name` or 'browser
-name:channel')") |
588 return parser | 634 return parser |
589 | 635 |
590 | 636 |
591 def main(): | 637 def main(): |
592 """Perform check_stability functionality and return exit code.""" | 638 """Perform check_stability functionality and return exit code.""" |
593 global wpt_root | 639 global wpt_root |
594 global wptrunner_root | 640 global wptrunner_root |
| 641 global logger |
595 | 642 |
596 retcode = 0 | 643 retcode = 0 |
597 parser = get_parser() | 644 parser = get_parser() |
598 args = parser.parse_args() | 645 args = parser.parse_args() |
599 | 646 |
| 647 if args.output_bytes is not None: |
| 648 replace_streams(args.output_bytes, |
| 649 "Log reached capacity (%s bytes); output disabled." % ar
gs.output_bytes) |
| 650 |
| 651 logger = logging.getLogger(os.path.splitext(__file__)[0]) |
| 652 setup_logging() |
| 653 |
600 wpt_root = os.path.abspath(os.curdir) | 654 wpt_root = os.path.abspath(os.curdir) |
601 wptrunner_root = os.path.normpath(os.path.join(wpt_root, "..", "wptrunner")) | 655 wptrunner_root = os.path.normpath(os.path.join(wpt_root, "..", "wptrunner")) |
602 | 656 |
603 if not os.path.exists(args.root): | 657 if not os.path.exists(args.root): |
604 logger.critical("Root directory %s does not exist" % args.root) | 658 logger.critical("Root directory %s does not exist" % args.root) |
605 return 1 | 659 return 1 |
606 | 660 |
607 os.chdir(args.root) | 661 os.chdir(args.root) |
608 | 662 |
609 browser_name = args.product.split(":")[0] | 663 browser_name = args.product.split(":")[0] |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
693 return retcode | 747 return retcode |
694 | 748 |
695 | 749 |
696 if __name__ == "__main__": | 750 if __name__ == "__main__": |
697 try: | 751 try: |
698 retcode = main() | 752 retcode = main() |
699 except: | 753 except: |
700 raise | 754 raise |
701 else: | 755 else: |
702 sys.exit(retcode) | 756 sys.exit(retcode) |
OLD | NEW |