base.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. """Base test class for nbconvert"""
  2. # Copyright (c) IPython Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. import io
  5. import os
  6. import glob
  7. import shlex
  8. import shutil
  9. import sys
  10. import unittest
  11. import nbconvert
  12. from subprocess import Popen, PIPE
  13. from nbformat import v4, write
  14. from testpath.tempdir import TemporaryWorkingDirectory
  15. from ipython_genutils.py3compat import string_types, bytes_to_str
  16. class TestsBase(unittest.TestCase):
  17. """Base tests class. Contains useful fuzzy comparison and nbconvert
  18. functions."""
  19. def fuzzy_compare(self, a, b, newlines_are_spaces=True, tabs_are_spaces=True,
  20. fuzzy_spacing=True, ignore_spaces=False,
  21. ignore_newlines=False, case_sensitive=False, leave_padding=False):
  22. """
  23. Performs a fuzzy comparison of two strings. A fuzzy comparison is a
  24. comparison that ignores insignificant differences in the two comparands.
  25. The significance of certain differences can be specified via the keyword
  26. parameters of this method.
  27. """
  28. if not leave_padding:
  29. a = a.strip()
  30. b = b.strip()
  31. if ignore_newlines:
  32. a = a.replace('\n', '')
  33. b = b.replace('\n', '')
  34. if newlines_are_spaces:
  35. a = a.replace('\n', ' ')
  36. b = b.replace('\n', ' ')
  37. if tabs_are_spaces:
  38. a = a.replace('\t', ' ')
  39. b = b.replace('\t', ' ')
  40. if ignore_spaces:
  41. a = a.replace(' ', '')
  42. b = b.replace(' ', '')
  43. if fuzzy_spacing:
  44. a = self.recursive_replace(a, ' ', ' ')
  45. b = self.recursive_replace(b, ' ', ' ')
  46. if not case_sensitive:
  47. a = a.lower()
  48. b = b.lower()
  49. self.assertEqual(a, b)
  50. def recursive_replace(self, text, search, replacement):
  51. """
  52. Performs a recursive replacement operation. Replaces all instances
  53. of a search string in a text string with a replacement string until
  54. the search string no longer exists. Recursion is needed because the
  55. replacement string may generate additional search strings.
  56. For example:
  57. Replace "ii" with "i" in the string "Hiiii" yields "Hii"
  58. Another replacement cds "Hi" (the desired output)
  59. Parameters
  60. ----------
  61. text : string
  62. Text to replace in.
  63. search : string
  64. String to search for within "text"
  65. replacement : string
  66. String to replace "search" with
  67. """
  68. while search in text:
  69. text = text.replace(search, replacement)
  70. return text
  71. def create_temp_cwd(self, copy_filenames=None):
  72. temp_dir = TemporaryWorkingDirectory()
  73. #Copy the files if requested.
  74. if copy_filenames is not None:
  75. self.copy_files_to(copy_filenames, dest=temp_dir.name)
  76. #Return directory handler
  77. return temp_dir
  78. @classmethod
  79. def merge_dicts(cls, *dict_args):
  80. # Because this is annoying to do inline
  81. outcome = {}
  82. for d in dict_args:
  83. outcome.update(d)
  84. return outcome
  85. def create_empty_notebook(self, path):
  86. nb = v4.new_notebook()
  87. with io.open(path, 'w', encoding='utf-8') as f:
  88. write(nb, f, 4)
  89. def copy_files_to(self, copy_filenames, dest='.'):
  90. "Copy test files into the destination directory"
  91. if not os.path.isdir(dest):
  92. os.makedirs(dest)
  93. files_path = self._get_files_path()
  94. for pattern in copy_filenames:
  95. files = glob.glob(os.path.join(files_path, pattern))
  96. assert files
  97. for match in files:
  98. shutil.copyfile(match, os.path.join(dest, os.path.basename(match)))
  99. def _get_files_path(self):
  100. #Get the relative path to this module in the IPython directory.
  101. names = self.__module__.split('.')[1:-1]
  102. names.append('files')
  103. #Build a path using the nbconvert directory and the relative path we just
  104. #found.
  105. path = os.path.dirname(nbconvert.__file__)
  106. return os.path.join(path, *names)
  107. def nbconvert(self, parameters, ignore_return_code=False, stdin=None):
  108. """
  109. Run nbconvert as a shell command, listening for both Errors and
  110. non-zero return codes. Returns the tuple (stdout, stderr) of
  111. output produced during the nbconvert run.
  112. Parameters
  113. ----------
  114. parameters : str, list(str)
  115. List of parameters to pass to IPython.
  116. ignore_return_code : optional bool (default False)
  117. Throw an OSError if the return code
  118. """
  119. cmd = [sys.executable, '-m', 'nbconvert']
  120. if sys.platform == 'win32':
  121. if isinstance(parameters, string_types):
  122. cmd = ' '.join(cmd) + ' ' + parameters
  123. else:
  124. cmd = ' '.join(cmd + parameters)
  125. else:
  126. if isinstance(parameters, string_types):
  127. parameters = shlex.split(parameters)
  128. cmd += parameters
  129. p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
  130. stdout, stderr = p.communicate(input=stdin)
  131. if not (p.returncode == 0 or ignore_return_code):
  132. raise OSError(bytes_to_str(stderr))
  133. return stdout.decode('utf8', 'replace'), stderr.decode('utf8', 'replace')
  134. def assert_big_text_equal(a, b, chunk_size=80):
  135. """assert that large strings are equal
  136. Zooms in on first chunk that differs,
  137. to give better info than vanilla assertEqual for large text blobs.
  138. """
  139. for i in range(0, len(a), chunk_size):
  140. chunk_a = a[i:i + chunk_size]
  141. chunk_b = b[i:i + chunk_size]
  142. assert chunk_a == chunk_b, "[offset: %i]\n%r != \n%r" % (i, chunk_a, chunk_b)
  143. if len(a) > len(b):
  144. raise AssertionError("Length doesn't match (%i > %i). Extra text:\n%r" % (
  145. len(a), len(b), a[len(b):]))
  146. elif len(a) < len(b):
  147. raise AssertionError("Length doesn't match (%i < %i). Extra text:\n%r" % (
  148. len(a), len(b), a[len(b):]))