bot_update_coverage_test.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/env python
  2. # Copyright (c) 2015 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 codecs
  6. import copy
  7. import json
  8. import os
  9. import sys
  10. import unittest
  11. #import test_env # pylint: disable=W0403,W0611
  12. sys.path.insert(0, os.path.join(
  13. os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
  14. 'recipe_modules', 'bot_update', 'resources'))
  15. sys.platform = 'linux2' # For consistency, ya know?
  16. import bot_update
  17. DEFAULT_PARAMS = {
  18. 'solutions': [{
  19. 'name': 'somename',
  20. 'url': 'https://fake.com'
  21. }],
  22. 'revisions': [],
  23. 'first_sln': 'somename',
  24. 'target_os': None,
  25. 'target_os_only': None,
  26. 'patch_root': None,
  27. 'issue': None,
  28. 'patchset': None,
  29. 'rietveld_server': None,
  30. 'gerrit_repo': None,
  31. 'gerrit_ref': None,
  32. 'gerrit_rebase_patch_ref': None,
  33. 'revision_mapping': {},
  34. 'apply_issue_email_file': None,
  35. 'apply_issue_key_file': None,
  36. 'apply_issue_oauth2_file': None,
  37. 'shallow': False,
  38. 'refs': [],
  39. 'git_cache_dir': '',
  40. 'gerrit_reset': None,
  41. }
  42. class MockedPopen(object):
  43. """A fake instance of a called subprocess.
  44. This is meant to be used in conjunction with MockedCall.
  45. """
  46. def __init__(self, args=None, kwargs=None):
  47. self.args = args or []
  48. self.kwargs = kwargs or {}
  49. self.return_value = None
  50. self.fails = False
  51. def returns(self, rv):
  52. """Set the return value when this popen is called.
  53. rv can be a string, or a callable (eg function).
  54. """
  55. self.return_value = rv
  56. return self
  57. def check(self, args, kwargs):
  58. """Check to see if the given args/kwargs call match this instance.
  59. This does a partial match, so that a call to "git clone foo" will match
  60. this instance if this instance was recorded as "git clone"
  61. """
  62. if any(input_arg != expected_arg
  63. for (input_arg, expected_arg) in zip(args, self.args)):
  64. return False
  65. return self.return_value
  66. def __call__(self, args, kwargs):
  67. """Actually call this popen instance."""
  68. if hasattr(self.return_value, '__call__'):
  69. return self.return_value(*args, **kwargs)
  70. return self.return_value
  71. class MockedCall(object):
  72. """A fake instance of bot_update.call().
  73. This object is pre-seeded with "answers" in self.expectations. The type
  74. is a MockedPopen object, or any object with a __call__() and check() method.
  75. The check() method is used to check to see if the correct popen object is
  76. chosen (can be a partial match, eg a "git clone" popen module would match
  77. a "git clone foo" call).
  78. By default, if no answers have been pre-seeded, the call() returns successful
  79. with an empty string.
  80. """
  81. def __init__(self, fake_filesystem):
  82. self.expectations = []
  83. self.records = []
  84. def expect(self, args=None, kwargs=None):
  85. args = args or []
  86. kwargs = kwargs or {}
  87. popen = MockedPopen(args, kwargs)
  88. self.expectations.append(popen)
  89. return popen
  90. def __call__(self, *args, **kwargs):
  91. self.records.append((args, kwargs))
  92. for popen in self.expectations:
  93. if popen.check(args, kwargs):
  94. self.expectations.remove(popen)
  95. return popen(args, kwargs)
  96. return ''
  97. class MockedGclientSync():
  98. """A class producing a callable instance of gclient sync.
  99. Because for bot_update, gclient sync also emits an output json file, we need
  100. a callable object that can understand where the output json file is going, and
  101. emit a (albite) fake file for bot_update to consume.
  102. """
  103. def __init__(self, fake_filesystem):
  104. self.output = {}
  105. self.fake_filesystem = fake_filesystem
  106. def __call__(self, *args, **_):
  107. output_json_index = args.index('--output-json') + 1
  108. with self.fake_filesystem.open(args[output_json_index], 'w') as f:
  109. json.dump(self.output, f)
  110. class FakeFile():
  111. def __init__(self):
  112. self.contents = ''
  113. def write(self, buf):
  114. self.contents += buf
  115. def read(self):
  116. return self.contents
  117. def __enter__(self):
  118. return self
  119. def __exit__(self, _, __, ___):
  120. pass
  121. class FakeFilesystem():
  122. def __init__(self):
  123. self.files = {}
  124. def open(self, target, mode='r', encoding=None):
  125. if 'w' in mode:
  126. self.files[target] = FakeFile()
  127. return self.files[target]
  128. return self.files[target]
  129. def fake_git(*args, **kwargs):
  130. return bot_update.call('git', *args, **kwargs)
  131. class BotUpdateUnittests(unittest.TestCase):
  132. def setUp(self):
  133. self.filesystem = FakeFilesystem()
  134. self.call = MockedCall(self.filesystem)
  135. self.gclient = MockedGclientSync(self.filesystem)
  136. self.call.expect(('gclient', 'sync')).returns(self.gclient)
  137. self.old_call = getattr(bot_update, 'call')
  138. self.params = copy.deepcopy(DEFAULT_PARAMS)
  139. setattr(bot_update, 'call', self.call)
  140. setattr(bot_update, 'git', fake_git)
  141. self.old_os_cwd = os.getcwd
  142. setattr(os, 'getcwd', lambda: '/b/build/slave/foo/build')
  143. setattr(bot_update, 'open', self.filesystem.open)
  144. self.old_codecs_open = codecs.open
  145. setattr(codecs, 'open', self.filesystem.open)
  146. def tearDown(self):
  147. setattr(bot_update, 'call', self.old_call)
  148. setattr(os, 'getcwd', self.old_os_cwd)
  149. delattr(bot_update, 'open')
  150. setattr(codecs, 'open', self.old_codecs_open)
  151. def testBasic(self):
  152. bot_update.ensure_checkout(**self.params)
  153. return self.call.records
  154. def testBasicShallow(self):
  155. self.params['shallow'] = True
  156. bot_update.ensure_checkout(**self.params)
  157. return self.call.records
  158. if __name__ == '__main__':
  159. unittest.main()