base.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # Copyright 2010-2011 OpenStack Foundation
  2. # Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. # Copyright (C) 2013 Association of Universities for Research in Astronomy
  16. # (AURA)
  17. #
  18. # Redistribution and use in source and binary forms, with or without
  19. # modification, are permitted provided that the following conditions are met:
  20. #
  21. # 1. Redistributions of source code must retain the above copyright
  22. # notice, this list of conditions and the following disclaimer.
  23. #
  24. # 2. Redistributions in binary form must reproduce the above
  25. # copyright notice, this list of conditions and the following
  26. # disclaimer in the documentation and/or other materials provided
  27. # with the distribution.
  28. #
  29. # 3. The name of AURA and its representatives may not be used to
  30. # endorse or promote products derived from this software without
  31. # specific prior written permission.
  32. #
  33. # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
  34. # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  35. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  36. # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
  37. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  38. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  39. """Common utilities used in testing"""
  40. import os
  41. import shutil
  42. import subprocess
  43. import sys
  44. import fixtures
  45. import testresources
  46. import testtools
  47. from testtools import content
  48. from pbr import options
  49. class DiveDir(fixtures.Fixture):
  50. """Dive into given directory and return back on cleanup.
  51. :ivar path: The target directory.
  52. """
  53. def __init__(self, path):
  54. self.path = path
  55. def setUp(self):
  56. super(DiveDir, self).setUp()
  57. self.addCleanup(os.chdir, os.getcwd())
  58. os.chdir(self.path)
  59. class BaseTestCase(testtools.TestCase, testresources.ResourcedTestCase):
  60. def setUp(self):
  61. super(BaseTestCase, self).setUp()
  62. test_timeout = os.environ.get('OS_TEST_TIMEOUT', 30)
  63. try:
  64. test_timeout = int(test_timeout)
  65. except ValueError:
  66. # If timeout value is invalid, fail hard.
  67. print("OS_TEST_TIMEOUT set to invalid value"
  68. " defaulting to no timeout")
  69. test_timeout = 0
  70. if test_timeout > 0:
  71. self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
  72. if os.environ.get('OS_STDOUT_CAPTURE') in options.TRUE_VALUES:
  73. stdout = self.useFixture(fixtures.StringStream('stdout')).stream
  74. self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
  75. if os.environ.get('OS_STDERR_CAPTURE') in options.TRUE_VALUES:
  76. stderr = self.useFixture(fixtures.StringStream('stderr')).stream
  77. self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
  78. self.log_fixture = self.useFixture(
  79. fixtures.FakeLogger('pbr'))
  80. # Older git does not have config --local, so create a temporary home
  81. # directory to permit using git config --global without stepping on
  82. # developer configuration.
  83. self.useFixture(fixtures.TempHomeDir())
  84. self.useFixture(fixtures.NestedTempfile())
  85. self.useFixture(fixtures.FakeLogger())
  86. # TODO(lifeless) we should remove PBR_VERSION from the environment.
  87. # rather than setting it, because thats not representative - we need to
  88. # test non-preversioned codepaths too!
  89. self.useFixture(fixtures.EnvironmentVariable('PBR_VERSION', '0.0'))
  90. self.temp_dir = self.useFixture(fixtures.TempDir()).path
  91. self.package_dir = os.path.join(self.temp_dir, 'testpackage')
  92. shutil.copytree(os.path.join(os.path.dirname(__file__), 'testpackage'),
  93. self.package_dir)
  94. self.addCleanup(os.chdir, os.getcwd())
  95. os.chdir(self.package_dir)
  96. self.addCleanup(self._discard_testpackage)
  97. # Tests can opt into non-PBR_VERSION by setting preversioned=False as
  98. # an attribute.
  99. if not getattr(self, 'preversioned', True):
  100. self.useFixture(fixtures.EnvironmentVariable('PBR_VERSION'))
  101. setup_cfg_path = os.path.join(self.package_dir, 'setup.cfg')
  102. with open(setup_cfg_path, 'rt') as cfg:
  103. content = cfg.read()
  104. content = content.replace(u'version = 0.1.dev', u'')
  105. with open(setup_cfg_path, 'wt') as cfg:
  106. cfg.write(content)
  107. def _discard_testpackage(self):
  108. # Remove pbr.testpackage from sys.modules so that it can be freshly
  109. # re-imported by the next test
  110. for k in list(sys.modules):
  111. if (k == 'pbr_testpackage' or
  112. k.startswith('pbr_testpackage.')):
  113. del sys.modules[k]
  114. def run_pbr(self, *args, **kwargs):
  115. return self._run_cmd('pbr', args, **kwargs)
  116. def run_setup(self, *args, **kwargs):
  117. return self._run_cmd(sys.executable, ('setup.py',) + args, **kwargs)
  118. def _run_cmd(self, cmd, args=[], allow_fail=True, cwd=None):
  119. """Run a command in the root of the test working copy.
  120. Runs a command, with the given argument list, in the root of the test
  121. working copy--returns the stdout and stderr streams and the exit code
  122. from the subprocess.
  123. :param cwd: If falsy run within the test package dir, otherwise run
  124. within the named path.
  125. """
  126. cwd = cwd or self.package_dir
  127. result = _run_cmd([cmd] + list(args), cwd=cwd)
  128. if result[2] and not allow_fail:
  129. raise Exception("Command failed retcode=%s" % result[2])
  130. return result
  131. class CapturedSubprocess(fixtures.Fixture):
  132. """Run a process and capture its output.
  133. :attr stdout: The output (a string).
  134. :attr stderr: The standard error (a string).
  135. :attr returncode: The return code of the process.
  136. Note that stdout and stderr are decoded from the bytestrings subprocess
  137. returns using error=replace.
  138. """
  139. def __init__(self, label, *args, **kwargs):
  140. """Create a CapturedSubprocess.
  141. :param label: A label for the subprocess in the test log. E.g. 'foo'.
  142. :param *args: The *args to pass to Popen.
  143. :param **kwargs: The **kwargs to pass to Popen.
  144. """
  145. super(CapturedSubprocess, self).__init__()
  146. self.label = label
  147. self.args = args
  148. self.kwargs = kwargs
  149. self.kwargs['stderr'] = subprocess.PIPE
  150. self.kwargs['stdin'] = subprocess.PIPE
  151. self.kwargs['stdout'] = subprocess.PIPE
  152. def setUp(self):
  153. super(CapturedSubprocess, self).setUp()
  154. proc = subprocess.Popen(*self.args, **self.kwargs)
  155. out, err = proc.communicate()
  156. self.out = out.decode('utf-8', 'replace')
  157. self.err = err.decode('utf-8', 'replace')
  158. self.addDetail(self.label + '-stdout', content.text_content(self.out))
  159. self.addDetail(self.label + '-stderr', content.text_content(self.err))
  160. self.returncode = proc.returncode
  161. if proc.returncode:
  162. raise AssertionError(
  163. 'Failed process args=%r, kwargs=%r, returncode=%s' % (
  164. self.args, self.kwargs, proc.returncode))
  165. self.addCleanup(delattr, self, 'out')
  166. self.addCleanup(delattr, self, 'err')
  167. self.addCleanup(delattr, self, 'returncode')
  168. def _run_cmd(args, cwd):
  169. """Run the command args in cwd.
  170. :param args: The command to run e.g. ['git', 'status']
  171. :param cwd: The directory to run the comamnd in.
  172. :return: ((stdout, stderr), returncode)
  173. """
  174. print('Running %s' % ' '.join(args))
  175. p = subprocess.Popen(
  176. args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
  177. stderr=subprocess.PIPE, cwd=cwd)
  178. streams = tuple(s.decode('latin1').strip() for s in p.communicate())
  179. print('STDOUT:')
  180. print(streams[0])
  181. print('STDERR:')
  182. print(streams[1])
  183. return (streams) + (p.returncode,)
  184. def _config_git():
  185. _run_cmd(
  186. ['git', 'config', '--global', 'user.email', 'example@example.com'],
  187. None)
  188. _run_cmd(
  189. ['git', 'config', '--global', 'user.name', 'OpenStack Developer'],
  190. None)
  191. _run_cmd(
  192. ['git', 'config', '--global', 'user.signingkey',
  193. 'example@example.com'], None)