mach_commands.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. from __future__ import absolute_import, unicode_literals
  5. import logging
  6. import os
  7. import subprocess
  8. from mach.decorators import (
  9. Command,
  10. CommandArgument,
  11. CommandProvider,
  12. )
  13. from mozbuild.base import (
  14. MachCommandBase,
  15. MachCommandConditions as conditions,
  16. )
  17. def is_valgrind_build(cls):
  18. '''Must be a build with --enable-valgrind and --disable-jemalloc.'''
  19. defines = cls.config_environment.defines
  20. return 'MOZ_VALGRIND' in defines and 'MOZ_MEMORY' not in defines
  21. @CommandProvider
  22. class MachCommands(MachCommandBase):
  23. '''
  24. Run Valgrind tests.
  25. '''
  26. def __init__(self, context):
  27. MachCommandBase.__init__(self, context)
  28. @Command('valgrind-test', category='testing',
  29. conditions=[conditions.is_firefox, is_valgrind_build],
  30. description='Run the Valgrind test job (memory-related errors).')
  31. @CommandArgument('--suppressions', default=[], action='append',
  32. metavar='FILENAME',
  33. help='Specify a suppression file for Valgrind to use. Use '
  34. '--suppression multiple times to specify multiple suppression '
  35. 'files.')
  36. def valgrind_test(self, suppressions):
  37. import json
  38. import sys
  39. import tempfile
  40. from mozbuild.base import MozbuildObject
  41. from mozfile import TemporaryDirectory
  42. from mozhttpd import MozHttpd
  43. from mozprofile import FirefoxProfile, Preferences
  44. from mozprofile.permissions import ServerLocations
  45. from mozrunner import FirefoxRunner
  46. from mozrunner.utils import findInPath
  47. from valgrind.output_handler import OutputHandler
  48. build_dir = os.path.join(self.topsrcdir, 'build')
  49. # XXX: currently we just use the PGO inputs for Valgrind runs. This may
  50. # change in the future.
  51. httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo'))
  52. httpd.start(block=False)
  53. with TemporaryDirectory() as profilePath:
  54. #TODO: refactor this into mozprofile
  55. prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js')
  56. prefs = {}
  57. prefs.update(Preferences.read_prefs(prefpath))
  58. interpolation = { 'server': '%s:%d' % httpd.httpd.server_address,
  59. 'OOP': 'false'}
  60. prefs = json.loads(json.dumps(prefs) % interpolation)
  61. for pref in prefs:
  62. prefs[pref] = Preferences.cast(prefs[pref])
  63. quitter = os.path.join(self.topsrcdir, 'tools', 'quitter', 'quitter@mozilla.org.xpi')
  64. locations = ServerLocations()
  65. locations.add_host(host='127.0.0.1',
  66. port=httpd.httpd.server_port,
  67. options='primary')
  68. profile = FirefoxProfile(profile=profilePath,
  69. preferences=prefs,
  70. addons=[quitter],
  71. locations=locations)
  72. firefox_args = [httpd.get_url()]
  73. env = os.environ.copy()
  74. env['G_SLICE'] = 'always-malloc'
  75. env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1'
  76. env['XPCOM_DEBUG_BREAK'] = 'warn'
  77. env.update(self.extra_environment_variables)
  78. outputHandler = OutputHandler(self.log)
  79. kp_kwargs = {'processOutputLine': [outputHandler]}
  80. valgrind = 'valgrind'
  81. if not os.path.exists(valgrind):
  82. valgrind = findInPath(valgrind)
  83. valgrind_args = [
  84. valgrind,
  85. '--smc-check=all-non-file',
  86. '--vex-iropt-register-updates=allregs-at-mem-access',
  87. '--gen-suppressions=all',
  88. '--num-callers=36',
  89. '--leak-check=full',
  90. '--show-possibly-lost=no',
  91. '--track-origins=yes',
  92. '--trace-children=yes',
  93. '-v', # Enable verbosity to get the list of used suppressions
  94. # Avoid excessive delays in the presence of spinlocks.
  95. # See bug 1309851.
  96. '--fair-sched=yes',
  97. ]
  98. for s in suppressions:
  99. valgrind_args.append('--suppressions=' + s)
  100. supps_dir = os.path.join(build_dir, 'valgrind')
  101. supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup')
  102. valgrind_args.append('--suppressions=' + supps_file1)
  103. # MACHTYPE is an odd bash-only environment variable that doesn't
  104. # show up in os.environ, so we have to get it another way.
  105. machtype = subprocess.check_output(['bash', '-c', 'echo $MACHTYPE']).rstrip()
  106. supps_file2 = os.path.join(supps_dir, machtype + '.sup')
  107. if os.path.isfile(supps_file2):
  108. valgrind_args.append('--suppressions=' + supps_file2)
  109. exitcode = None
  110. timeout = 1800
  111. try:
  112. runner = FirefoxRunner(profile=profile,
  113. binary=self.get_binary_path(),
  114. cmdargs=firefox_args,
  115. env=env,
  116. process_args=kp_kwargs)
  117. runner.start(debug_args=valgrind_args)
  118. exitcode = runner.wait(timeout=timeout)
  119. finally:
  120. errs = outputHandler.error_count
  121. supps = outputHandler.suppression_count
  122. if errs != supps:
  123. status = 1 # turns the TBPL job orange
  124. self.log(logging.ERROR, 'valgrind-fail-parsing',
  125. {'errs': errs, 'supps': supps},
  126. 'TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors seen, but {supps} generated suppressions seen')
  127. elif errs == 0:
  128. status = 0
  129. self.log(logging.INFO, 'valgrind-pass', {},
  130. 'TEST-PASS | valgrind-test | valgrind found no errors')
  131. else:
  132. status = 1 # turns the TBPL job orange
  133. # We've already printed details of the errors.
  134. if exitcode == None:
  135. status = 2 # turns the TBPL job red
  136. self.log(logging.ERROR, 'valgrind-fail-timeout',
  137. {'timeout': timeout},
  138. 'TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out (reached {timeout} second limit)')
  139. elif exitcode != 0:
  140. status = 2 # turns the TBPL job red
  141. self.log(logging.ERROR, 'valgrind-fail-errors', {},
  142. 'TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind')
  143. httpd.stop()
  144. return status