gclient_smoketest_base.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. #!/usr/bin/env vpython3
  2. # Copyright (c) 2020 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import logging
  6. import os
  7. import re
  8. import sys
  9. ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  10. GCLIENT_PATH = os.path.join(ROOT_DIR, 'gclient')
  11. sys.path.insert(0, ROOT_DIR)
  12. import subprocess2
  13. from testing_support import fake_repos
  14. class GClientSmokeBase(fake_repos.FakeReposTestBase):
  15. def setUp(self):
  16. super(GClientSmokeBase, self).setUp()
  17. # Make sure it doesn't try to auto update when testing!
  18. self.env = os.environ.copy()
  19. self.env['DEPOT_TOOLS_UPDATE'] = '0'
  20. self.env['DEPOT_TOOLS_METRICS'] = '0'
  21. # Suppress Python 3 warnings and other test undesirables.
  22. self.env['GCLIENT_TEST'] = '1'
  23. self.maxDiff = None
  24. def gclient(self, cmd, cwd=None, error_ok=False):
  25. if not cwd:
  26. cwd = self.root_dir
  27. cmd = [GCLIENT_PATH] + cmd
  28. process = subprocess2.Popen(cmd,
  29. cwd=cwd,
  30. env=self.env,
  31. stdout=subprocess2.PIPE,
  32. stderr=subprocess2.PIPE,
  33. universal_newlines=True)
  34. (stdout, stderr) = process.communicate()
  35. logging.debug("XXX: %s\n%s\nXXX" % (' '.join(cmd), stdout))
  36. logging.debug("YYY: %s\n%s\nYYY" % (' '.join(cmd), stderr))
  37. if not error_ok:
  38. self.assertEqual(0, process.returncode, stderr)
  39. return (stdout.replace('\r\n',
  40. '\n'), stderr.replace('\r\n',
  41. '\n'), process.returncode)
  42. def untangle(self, stdout):
  43. """Separates output based on thread IDs."""
  44. tasks = {}
  45. remaining = []
  46. task_id = 0
  47. for line in stdout.splitlines(False):
  48. m = re.match(r'^(\d)+>(.*)$', line)
  49. if not m:
  50. if task_id:
  51. # Lines broken with carriage breaks don't have a thread ID,
  52. # but belong to the last seen thread ID.
  53. tasks.setdefault(task_id, []).append(line)
  54. else:
  55. remaining.append(line)
  56. else:
  57. self.assertEqual([], remaining)
  58. task_id = int(m.group(1))
  59. tasks.setdefault(task_id, []).append(m.group(2))
  60. out = []
  61. for key in sorted(tasks.keys()):
  62. out.extend(tasks[key])
  63. out.extend(remaining)
  64. return '\n'.join(out)
  65. def parseGclient(self, cmd, items, expected_stderr='', untangle=False):
  66. """Parse gclient's output to make it easier to test.
  67. If untangle is True, tries to sort out the output from parallel checkout."""
  68. (stdout, stderr, _) = self.gclient(cmd)
  69. if untangle:
  70. stdout = self.untangle(stdout)
  71. self.checkString(expected_stderr, stderr)
  72. return self.checkBlock(stdout, items)
  73. def splitBlock(self, stdout):
  74. """Split gclient's output into logical execution blocks.
  75. ___ running 'foo' at '/bar'
  76. (...)
  77. ___ running 'baz' at '/bar'
  78. (...)
  79. will result in 2 items of len((...).splitlines()) each.
  80. """
  81. results = []
  82. for line in stdout.splitlines(False):
  83. # Intentionally skips empty lines.
  84. if not line:
  85. continue
  86. if not line.startswith('__'):
  87. if results:
  88. results[-1].append(line)
  89. else:
  90. # TODO(maruel): gclient's git stdout is inconsistent.
  91. # This should fail the test instead!!
  92. pass
  93. continue
  94. match = re.match(r'^________ ([a-z]+) \'(.*)\' in \'(.*)\'$', line)
  95. if match:
  96. results.append(
  97. [[match.group(1),
  98. match.group(2),
  99. match.group(3)]])
  100. continue
  101. match = re.match(r'^_____ (.*) is missing, syncing instead$', line)
  102. if match:
  103. # Blah, it's when a dependency is deleted, we should probably
  104. # not output this message.
  105. results.append([line])
  106. continue
  107. # These two regexps are a bit too broad, they are necessary only for
  108. # git checkouts.
  109. if (re.match(r'_____ [^ ]+ at [^ ]+', line) or re.match(
  110. r'_____ [^ ]+ : Attempting rebase onto [0-9a-f]+...',
  111. line)):
  112. continue
  113. # Fail for any unrecognized lines that start with '__'.
  114. self.fail(line)
  115. return results
  116. def checkBlock(self, stdout, items):
  117. results = self.splitBlock(stdout)
  118. for i in range(min(len(results), len(items))):
  119. if isinstance(items[i], (list, tuple)):
  120. verb = items[i][0]
  121. path = items[i][1]
  122. else:
  123. verb = items[i]
  124. path = self.root_dir
  125. self.checkString(results[i][0][0], verb,
  126. (i, results[i][0][0], verb))
  127. if sys.platform == 'win32':
  128. # Make path lower case since casing can change randomly.
  129. self.checkString(results[i][0][2].lower(), path.lower(),
  130. (i, results[i][0][2].lower(), path.lower()))
  131. else:
  132. self.checkString(results[i][0][2], path,
  133. (i, results[i][0][2], path))
  134. self.assertEqual(len(results), len(items),
  135. (stdout, items, len(results)))
  136. return results