OLD | NEW |
---|---|
1 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 # for details. All rights reserved. Use of this source code is governed by a | 2 # for details. All rights reserved. Use of this source code is governed by a |
3 # BSD-style license that can be found in the LICENSE file. | 3 # BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 # This file contains a set of utilities functions used by other Python-based | 5 # This file contains a set of utilities functions used by other Python-based |
6 # scripts. | 6 # scripts. |
7 | 7 |
8 import commands | 8 import commands |
9 import contextlib | |
9 import datetime | 10 import datetime |
10 import glob | 11 import glob |
11 import imp | 12 import imp |
12 import json | 13 import json |
13 import os | 14 import os |
14 import platform | 15 import platform |
15 import re | 16 import re |
16 import shutil | 17 import shutil |
17 import subprocess | 18 import subprocess |
18 import sys | 19 import sys |
(...skipping 580 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
599 # https://github.com/dart-lang/sdk/wiki/The-checked-in-SDK-in-tools | 600 # https://github.com/dart-lang/sdk/wiki/The-checked-in-SDK-in-tools |
600 def CheckedInSdkPath(): | 601 def CheckedInSdkPath(): |
601 # We don't use the normal macos, linux, win32 directory names here, instead, | 602 # We don't use the normal macos, linux, win32 directory names here, instead, |
602 # we use the names that the download_from_google_storage script uses. | 603 # we use the names that the download_from_google_storage script uses. |
603 osdict = {'Darwin':'mac', 'Linux':'linux', 'Windows':'win'} | 604 osdict = {'Darwin':'mac', 'Linux':'linux', 'Windows':'win'} |
604 system = platform.system() | 605 system = platform.system() |
605 try: | 606 try: |
606 osname = osdict[system] | 607 osname = osdict[system] |
607 except KeyError: | 608 except KeyError: |
608 print >>sys.stderr, ('WARNING: platform "%s" not supported') % (system) | 609 print >>sys.stderr, ('WARNING: platform "%s" not supported') % (system) |
609 return None; | 610 return None |
610 tools_dir = os.path.dirname(os.path.realpath(__file__)) | 611 tools_dir = os.path.dirname(os.path.realpath(__file__)) |
611 return os.path.join(tools_dir, | 612 return os.path.join(tools_dir, |
612 'sdks', | 613 'sdks', |
613 osname, | 614 osname, |
614 'dart-sdk') | 615 'dart-sdk') |
615 | 616 |
616 | 617 |
617 def CheckedInSdkExecutable(): | 618 def CheckedInSdkExecutable(): |
618 name = 'dart' | 619 name = 'dart' |
619 if IsWindows(): | 620 if IsWindows(): |
(...skipping 22 matching lines...) Expand all Loading... | |
642 'canary.dart') | 643 'canary.dart') |
643 try: | 644 try: |
644 with open(os.devnull, 'wb') as silent_sink: | 645 with open(os.devnull, 'wb') as silent_sink: |
645 if 0 == subprocess.call([executable, canary_script], stdout=silent_sink): | 646 if 0 == subprocess.call([executable, canary_script], stdout=silent_sink): |
646 return True | 647 return True |
647 except OSError as e: | 648 except OSError as e: |
648 pass | 649 pass |
649 return False | 650 return False |
650 | 651 |
651 | 652 |
653 def CheckLinuxCoreDumpPattern(fatal=False): | |
654 core_pattern_file = '/proc/sys/kernel/core_pattern' | |
655 core_pattern = open(core_pattern_file).read() | |
656 | |
657 expected_core_pattern = 'core.%p' | |
658 if core_pattern.strip() != expected_core_pattern: | |
659 if fatal: | |
660 message = ('Invalid core_pattern configuration. ' | |
661 'The configuration of core dump handling is *not* correct for ' | |
662 'a buildbot. The content of {0} must be "{1}" instead of "{2}".' | |
663 .format(core_pattern_file, expected_core_pattern, core_pattern)) | |
664 raise Exception(message) | |
665 else: | |
666 return False | |
667 return True | |
668 | |
669 | |
652 class TempDir(object): | 670 class TempDir(object): |
653 def __init__(self, prefix=''): | 671 def __init__(self, prefix=''): |
654 self._temp_dir = None | 672 self._temp_dir = None |
655 self._prefix = prefix | 673 self._prefix = prefix |
656 | 674 |
657 def __enter__(self): | 675 def __enter__(self): |
658 self._temp_dir = tempfile.mkdtemp(self._prefix) | 676 self._temp_dir = tempfile.mkdtemp(self._prefix) |
659 return self._temp_dir | 677 return self._temp_dir |
660 | 678 |
661 def __exit__(self, *_): | 679 def __exit__(self, *_): |
662 shutil.rmtree(self._temp_dir, ignore_errors=True) | 680 shutil.rmtree(self._temp_dir, ignore_errors=True) |
663 | 681 |
664 class ChangedWorkingDirectory(object): | 682 class ChangedWorkingDirectory(object): |
665 def __init__(self, working_directory): | 683 def __init__(self, working_directory): |
666 self._working_directory = working_directory | 684 self._working_directory = working_directory |
667 | 685 |
668 def __enter__(self): | 686 def __enter__(self): |
669 self._old_cwd = os.getcwd() | 687 self._old_cwd = os.getcwd() |
670 print "Enter directory = ", self._working_directory | 688 print "Enter directory = ", self._working_directory |
671 os.chdir(self._working_directory) | 689 os.chdir(self._working_directory) |
672 | 690 |
673 def __exit__(self, *_): | 691 def __exit__(self, *_): |
674 print "Enter directory = ", self._old_cwd | 692 print "Enter directory = ", self._old_cwd |
675 os.chdir(self._old_cwd) | 693 os.chdir(self._old_cwd) |
676 | 694 |
677 class CoreDump(object): | 695 |
678 def __init__(self, test, core, binary): | 696 class UnexpectedCrash(object): |
697 def __init__(self, test, pid, binary): | |
679 self.test = test | 698 self.test = test |
680 self.core = core | 699 self.pid = pid |
681 self.binary = binary | 700 self.binary = binary |
682 | 701 |
683 def __str__(self): | 702 def __str__(self): |
684 return "%s: %s %s" % (self.test, self.binary, self.core) | 703 return "%s: %s %s" % (self.test, self.binary, self.pid) |
685 | 704 |
686 class CoreDumpArchiver(object): | 705 |
706 class PosixCoredumpEnabler(object): | |
707 def __init__(self): | |
708 self._old_limits = None | |
709 | |
710 def __enter__(self): | |
711 self._old_limits = resource.getrlimit(resource.RLIMIT_CORE) | |
712 | |
713 # Bump core limits to unlimited if core_pattern is correctly configured. | |
714 if CheckLinuxCoreDumpPattern(fatal=False): | |
715 resource.setrlimit(resource.RLIMIT_CORE, (-1, -1)) | |
716 | |
717 def __exit__(self, *_): | |
718 resource.setrlimit(resource.RLIMIT_CORE, self._old_limits) | |
719 CheckLinuxCoreDumpPattern(fatal=True) | |
720 | |
721 class WindowsCoredumpEnabler(object): | |
722 """Configure Windows Error Reporting to store crash dumps. | |
723 | |
724 The documentation can be found here: | |
725 https://msdn.microsoft.com/en-us/library/windows/desktop/bb787181.aspx | |
726 """ | |
727 | |
728 WINDOWS_COREDUMP_FOLDER = r'crashes' | |
729 | |
730 WER_NAME = r'SOFTWARE\Microsoft\Windows\Windows Error Reporting' | |
731 WER_LOCALDUMPS_NAME = r'%s\LocalDumps' % WER_NAME | |
732 IMGEXEC_NAME = (r'SOFTWARE\Microsoft\Windows NT\CurrentVersion' | |
733 r'\Image File Execution Options\WerFault.exe') | |
734 | |
735 def __init__(self): | |
736 # Depending on wether we're in cygwin or not we use a different import. | |
Vyacheslav Egorov (Google)
2017/02/14 15:24:07
whether
kustermann
2017/02/14 15:34:28
Done.
| |
737 try: | |
738 import winreg | |
739 except ImportError: | |
740 import _winreg as winreg | |
741 self.winreg = winreg | |
742 | |
743 def __enter__(self): | |
744 # We want 32 and 64 bit coredumps to land in the same coredump directory. | |
745 for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]: | |
746 wer = None | |
747 wer_localdumps = None | |
748 try: | |
749 # In case WerFault.exe was prevented from executing, we fix it here. | |
750 # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed. | |
751 self._prune_existing_key( | |
752 self.winreg.HKEY_LOCAL_MACHINE, self.IMGEXEC_NAME, sam) | |
753 | |
754 # Create (or open) the WER keys. | |
755 wer = self.winreg.CreateKeyEx( | |
Vyacheslav Egorov (Google)
2017/02/14 15:24:07
maybe
with CreateKeyEx() as wer:
...
kustermann
2017/02/14 15:34:28
Done.
| |
756 self.winreg.HKEY_LOCAL_MACHINE, self.WER_NAME, 0, | |
757 self.winreg.KEY_ALL_ACCESS | sam) | |
758 wer_localdumps = self.winreg.CreateKeyEx( | |
759 self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0, | |
760 self.winreg.KEY_ALL_ACCESS | sam) | |
761 | |
762 # Prevent any modal UI dialog & disable normal windows error reporting. | |
763 # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed. | |
764 self.winreg.SetValueEx(wer, "DontShowUI", 0, self.winreg.REG_DWORD, 1) | |
765 self.winreg.SetValueEx(wer, "Disabled", 0, self.winreg.REG_DWORD, 1) | |
766 | |
767 coredump_folder = os.path.join( | |
768 os.getcwd(), WindowsCoredumpEnabler.WINDOWS_COREDUMP_FOLDER) | |
769 | |
770 # Create the directory which will contain the dumps | |
771 if not os.path.exists(coredump_folder): | |
772 os.mkdir(coredump_folder) | |
773 | |
774 # Do full dumps (not just mini dumps), keep max 100 dumps and specify | |
775 # folder. | |
776 self.winreg.SetValueEx( | |
777 wer_localdumps, "DumpType", 0, self.winreg.REG_DWORD, 2) | |
778 self.winreg.SetValueEx( | |
779 wer_localdumps, "DumpCount", 0, self.winreg.REG_DWORD, 200) | |
780 self.winreg.SetValueEx( | |
781 wer_localdumps, "DumpFolder", 0, self.winreg.REG_EXPAND_SZ, | |
782 coredump_folder) | |
783 finally: | |
784 if wer: | |
785 wer.Close() | |
786 if wer_localdumps: | |
787 wer_localdumps.Close() | |
788 | |
789 def __exit__(self, *_): | |
790 # We remove the local dumps settings we have afte running the tests. | |
Vyacheslav Egorov (Google)
2017/02/14 15:24:07
after
kustermann
2017/02/14 15:34:28
Done.
| |
791 for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]: | |
792 wer_localdumps = self.winreg.CreateKeyEx( | |
793 self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0, | |
794 self.winreg.KEY_ALL_ACCESS | sam) | |
795 try: | |
Vyacheslav Egorov (Google)
2017/02/14 15:24:07
maybe
with ... as wer_localdumps:
...
kustermann
2017/02/14 15:34:28
Done.
| |
796 self.winreg.DeleteValue(wer_localdumps, 'DumpType') | |
797 self.winreg.DeleteValue(wer_localdumps, 'DumpCount') | |
798 self.winreg.DeleteValue(wer_localdumps, 'DumpFolder') | |
799 finally: | |
800 wer_localdumps.Close() | |
801 | |
802 def _prune_existing_key(self, key, subkey, wowbit): | |
803 handle = None | |
804 | |
805 # If the open fails, the key doesn't exist and it's fine. | |
806 try: | |
807 handle = self.winreg.OpenKey( | |
808 key, subkey, 0, self.winreg.KEY_READ | wowbit) | |
809 except OSError: | |
810 pass | |
811 | |
812 # If the key exists then we delete it. If the deletion does not work, we | |
813 # let the exception through. | |
814 if handle: | |
815 handle.Close() | |
816 self.winreg.DeleteKeyEx(key, subkey, wowbit, 0) | |
817 | |
818 class BaseCoreDumpArchiver(object): | |
687 """This class reads coredumps file written by UnexpectedCrashDumpArchiver | 819 """This class reads coredumps file written by UnexpectedCrashDumpArchiver |
688 into the current working directory and uploads all cores and binaries | 820 into the current working directory and uploads all cores and binaries |
689 listed in it into Cloud Storage (see tools/testing/dart/test_progress.dart). | 821 listed in it into Cloud Storage (see tools/testing/dart/test_progress.dart). |
690 """ | 822 """ |
691 | 823 |
692 def __init__(self, args): | 824 # test.dart will write a line for each unexpected crash into this file. |
693 self._enabled = '--copy-coredumps' in args and GuessOS() == 'linux' | 825 _UNEXPECTED_CRASHES_FILE = "unexpected-crashes" |
694 self._search_dir = os.getcwd() | 826 |
827 def __init__(self): | |
695 self._bucket = 'dart-temp-crash-archive' | 828 self._bucket = 'dart-temp-crash-archive' |
696 self._old_limits = None | 829 self._binaries_dir = os.getcwd() |
697 | 830 |
698 def __enter__(self): | 831 def __enter__(self): |
699 if not self._enabled: | 832 # Cleanup any stale files |
700 return | |
701 | |
702 # Cleanup any stale coredumps | |
703 if self._cleanup(): | 833 if self._cleanup(): |
704 print "WARNING: Found and removed stale coredumps" | 834 print "WARNING: Found and removed stale coredumps" |
705 | 835 |
706 self._old_limits = resource.getrlimit(resource.RLIMIT_CORE) | |
707 | |
708 # Bump core limits to unlimited if core_pattern is correctly configured. | |
709 if self._check_core_dump_pattern(fatal=False): | |
710 resource.setrlimit(resource.RLIMIT_CORE, (-1, -1)) | |
711 | |
712 def __exit__(self, *_): | 836 def __exit__(self, *_): |
713 if not self._enabled: | |
714 return | |
715 | |
716 try: | 837 try: |
717 # Restore old core limit. | 838 crashes = self._find_unexpected_crashes() |
718 resource.setrlimit(resource.RLIMIT_CORE, self._old_limits) | 839 if crashes: |
719 | |
720 # Check that kernel was correctly configured to use core.%p | |
721 # core_pattern. | |
722 self._check_core_dump_pattern(fatal=True) | |
723 | |
724 coredumps = self._find_coredumps() | |
725 if coredumps: | |
726 # If we get a ton of crashes, only archive 10 dumps. | 840 # If we get a ton of crashes, only archive 10 dumps. |
727 archive_coredumps = coredumps[:10] | 841 archive_crashes = crashes[:10] |
728 print 'Archiving coredumps:' | 842 print 'Archiving coredumps for crash (if possible):' |
729 for core in archive_coredumps: | 843 for crash in archive_crashes: |
730 print '----> %s' % core | 844 print '----> %s' % crash |
731 | 845 |
732 sys.stdout.flush() | 846 sys.stdout.flush() |
733 self._archive(archive_coredumps) | 847 self._archive(archive_crashes) |
734 | 848 |
735 finally: | 849 finally: |
736 self._cleanup() | 850 self._cleanup() |
737 | 851 |
738 def _cleanup(self): | 852 def _archive(self, crashes): |
739 found = False | |
740 for core in glob.glob(os.path.join(self._search_dir, 'core.*')): | |
741 found = True | |
742 os.unlink(core) | |
743 for binary in glob.glob(os.path.join(self._search_dir, 'binary.*')): | |
744 found = True | |
745 os.unlink(binary) | |
746 try: | |
747 os.unlink(os.path.join(self._search_dir, 'coredumps')) | |
748 found = True | |
749 except: | |
750 pass | |
751 | |
752 return found | |
753 | |
754 def _find_coredumps(self): | |
755 """Load coredumps file. Each line has the following format: | |
756 | |
757 test-name,core-file,binary-file | |
758 """ | |
759 try: | |
760 with open('coredumps') as f: | |
761 return [CoreDump(*ln.strip('\n').split(',')) for ln in f.readlines()] | |
762 except: | |
763 return [] | |
764 | |
765 def _archive(self, coredumps): | |
766 files = set() | 853 files = set() |
767 for core in coredumps: | 854 missing = [] |
768 files.add(core.core) | 855 for crash in crashes: |
769 files.add(core.binary) | 856 files.add(crash.binary) |
857 core = self._find_coredump_file(crash) | |
858 if core: | |
859 files.add(core) | |
860 else: | |
861 missing.append(crash) | |
770 self._upload(files) | 862 self._upload(files) |
863 if missing: | |
864 raise Exception('Missing crash dumps for: %s' % ', '.join(missing)) | |
771 | 865 |
772 def _upload(self, files): | 866 def _upload(self, files): |
773 bot_utils = GetBotUtils() | 867 bot_utils = GetBotUtils() |
774 gsutil = bot_utils.GSUtil() | 868 gsutil = bot_utils.GSUtil() |
775 storage_path = '%s/%s/' % (self._bucket, uuid.uuid4()) | 869 storage_path = '%s/%s/' % (self._bucket, uuid.uuid4()) |
776 gs_prefix = 'gs://%s' % storage_path | 870 gs_prefix = 'gs://%s' % storage_path |
777 http_prefix = 'https://storage.cloud.google.com/%s' % storage_path | 871 http_prefix = 'https://storage.cloud.google.com/%s' % storage_path |
778 | 872 |
779 print '\n--- Uploading into %s (%s) ---' % (gs_prefix, http_prefix) | 873 print '\n--- Uploading into %s (%s) ---' % (gs_prefix, http_prefix) |
780 for file in files: | 874 for file in files: |
(...skipping 17 matching lines...) Expand all Loading... | |
798 | 892 |
799 try: | 893 try: |
800 gsutil.upload(tarname, gs_url) | 894 gsutil.upload(tarname, gs_url) |
801 print '+++ Uploaded %s (%s)' % (gs_url, http_url) | 895 print '+++ Uploaded %s (%s)' % (gs_url, http_url) |
802 except Exception as error: | 896 except Exception as error: |
803 print '!!! Failed to upload %s, error: %s' % (tarname, error) | 897 print '!!! Failed to upload %s, error: %s' % (tarname, error) |
804 | 898 |
805 os.unlink(tarname) | 899 os.unlink(tarname) |
806 print '--- Done ---\n' | 900 print '--- Done ---\n' |
807 | 901 |
808 def _check_core_dump_pattern(self, fatal=False): | 902 def _find_unexpected_crashes(self): |
809 core_pattern_file = '/proc/sys/kernel/core_pattern' | 903 """Load coredumps file. Each line has the following format: |
810 core_pattern = open(core_pattern_file).read() | |
811 | 904 |
812 expected_core_pattern = 'core.%p' | 905 test-name,pid,binary-file |
813 if core_pattern.strip() != expected_core_pattern: | 906 """ |
814 if fatal: | 907 try: |
815 message = ('Invalid core_pattern configuration. ' | 908 with open(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE) as f: |
816 'The configuration of core dump handling is *not* correct for ' | 909 return [UnexpectedCrash(*ln.strip('\n').split(',')) for ln in f.readline s()] |
817 'a buildbot. The content of {0} must be "{1}" instead of "{2}".' | 910 except: |
818 .format(core_pattern_file, expected_core_pattern, core_pattern)) | 911 return [] |
819 raise Exception(message) | 912 |
820 else: | 913 def _cleanup(self): |
821 return False | 914 found = False |
822 return True | 915 if os.path.exists(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE): |
916 os.unlink(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE) | |
917 found = True | |
918 for binary in glob.glob(os.path.join(self._binaries_dir, 'binary.*')): | |
919 found = True | |
920 os.unlink(binary) | |
921 return found | |
922 | |
923 class LinuxCoreDumpArchiver(BaseCoreDumpArchiver): | |
924 def __init__(self): | |
925 super(self.__class__, self).__init__() | |
926 self._search_dir = os.getcwd() | |
927 | |
928 def _cleanup(self): | |
929 found = super(self.__class__, self)._cleanup() | |
930 for core in glob.glob(os.path.join(self._search_dir, 'core.*')): | |
931 found = True | |
932 os.unlink(core) | |
933 return found | |
934 | |
935 def _find_coredump_file(self, crash): | |
936 core_filename = os.path.join(self._search_dir, 'core.%s' % crash.pid) | |
937 if os.path.exists(core_filename): | |
938 return core_filename | |
939 | |
940 class WindowsCoreDumpArchiver(BaseCoreDumpArchiver): | |
941 def __init__(self): | |
942 super(self.__class__, self).__init__() | |
943 self._search_dir = os.path.join( | |
944 os.getcwd(), WindowsCoredumpEnabler.WINDOWS_COREDUMP_FOLDER) | |
945 | |
946 def _cleanup(self): | |
947 found = super(self.__class__, self)._cleanup() | |
948 for core in glob.glob(os.path.join(self._search_dir, '*')): | |
949 found = True | |
950 os.unlink(core) | |
951 return found | |
952 | |
953 def _find_coredump_file(self, crash): | |
954 pattern = os.path.join(self._search_dir, '*.%s.*' % crash.pid) | |
955 for core_filename in glob.glob(pattern): | |
956 return core_filename | |
957 | |
958 @contextlib.contextmanager | |
959 def NooptCoreDumpArchiver(): | |
960 yield | |
961 | |
962 | |
963 def CoreDumpArchiver(args): | |
964 enabled = '--copy-coredumps' in args | |
965 | |
966 if not enabled: | |
967 return NooptCoreDumpArchiver() | |
968 | |
969 osname = GuessOS() | |
970 if osname == 'linux': | |
971 return contextlib.nested(PosixCoredumpEnabler(), | |
972 LinuxCoreDumpArchiver()) | |
973 elif osname == 'win32': | |
974 return contextlib.nested(WindowsCoredumpEnabler(), | |
975 WindowsCoreDumpArchiver()) | |
976 else: | |
977 # We don't have support for MacOS yet. | |
978 assert osname == 'macos' | |
979 return NooptCoreDumpArchiver() | |
823 | 980 |
824 if __name__ == "__main__": | 981 if __name__ == "__main__": |
825 import sys | 982 import sys |
826 Main() | 983 Main() |
OLD | NEW |