1 """Helpers for writing unit tests.""" 2 3 from __future__ import (print_function, division, absolute_import, 4 unicode_literals) 5 try: 6 from collections.abc import Iterable 7 except ImportError: # python < 3.3 8 from collections import Iterable 9 import os 10 import shutil 11 import sys 12 import tempfile 13 from unittest import TestCase as _TestCase 14 from fontTools.misc.py23 import * 15 from fontTools.misc.xmlWriter import XMLWriter 16 17 18 def parseXML(xmlSnippet): 19 """Parses a snippet of XML. 20 21 Input can be either a single string (unicode or UTF-8 bytes), or a 22 a sequence of strings. 23 24 The result is in the same format that would be returned by 25 XMLReader, but the parser imposes no constraints on the root 26 element so it can be called on small snippets of TTX files. 27 """ 28 # To support snippets with multiple elements, we add a fake root. 29 reader = TestXMLReader_() 30 xml = b"<root>" 31 if isinstance(xmlSnippet, bytes): 32 xml += xmlSnippet 33 elif isinstance(xmlSnippet, unicode): 34 xml += tobytes(xmlSnippet, 'utf-8') 35 elif isinstance(xmlSnippet, Iterable): 36 xml += b"".join(tobytes(s, 'utf-8') for s in xmlSnippet) 37 else: 38 raise TypeError("expected string or sequence of strings; found %r" 39 % type(xmlSnippet).__name__) 40 xml += b"</root>" 41 reader.parser.Parse(xml, 0) 42 return reader.root[2] 43 44 45 class FakeFont: 46 def __init__(self, glyphs): 47 self.glyphOrder_ = glyphs 48 self.reverseGlyphOrderDict_ = {g: i for i, g in enumerate(glyphs)} 49 self.lazy = False 50 self.tables = {} 51 52 def __getitem__(self, tag): 53 return self.tables[tag] 54 55 def __setitem__(self, tag, table): 56 self.tables[tag] = table 57 58 def get(self, tag, default=None): 59 return self.tables.get(tag, default) 60 61 def getGlyphID(self, name): 62 return self.reverseGlyphOrderDict_[name] 63 64 def getGlyphName(self, glyphID): 65 if glyphID < len(self.glyphOrder_): 66 return self.glyphOrder_[glyphID] 67 else: 68 return "glyph%.5d" % glyphID 69 70 def getGlyphOrder(self): 71 return self.glyphOrder_ 72 73 def getReverseGlyphMap(self): 74 return self.reverseGlyphOrderDict_ 75 76 77 class TestXMLReader_(object): 78 def __init__(self): 79 from xml.parsers.expat import ParserCreate 80 self.parser = ParserCreate() 81 self.parser.StartElementHandler = self.startElement_ 82 self.parser.EndElementHandler = self.endElement_ 83 self.parser.CharacterDataHandler = self.addCharacterData_ 84 self.root = None 85 self.stack = [] 86 87 def startElement_(self, name, attrs): 88 element = (name, attrs, []) 89 if self.stack: 90 self.stack[-1][2].append(element) 91 else: 92 self.root = element 93 self.stack.append(element) 94 95 def endElement_(self, name): 96 self.stack.pop() 97 98 def addCharacterData_(self, data): 99 self.stack[-1][2].append(data) 100 101 102 def makeXMLWriter(newlinestr='\n'): 103 # don't write OS-specific new lines 104 writer = XMLWriter(BytesIO(), newlinestr=newlinestr) 105 # erase XML declaration 106 writer.file.seek(0) 107 writer.file.truncate() 108 return writer 109 110 111 def getXML(func, ttFont=None): 112 """Call the passed toXML function and return the written content as a 113 list of lines (unicode strings). 114 Result is stripped of XML declaration and OS-specific newline characters. 115 """ 116 writer = makeXMLWriter() 117 func(writer, ttFont) 118 xml = writer.file.getvalue().decode("utf-8") 119 # toXML methods must always end with a writer.newline() 120 assert xml.endswith("\n") 121 return xml.splitlines() 122 123 124 class MockFont(object): 125 """A font-like object that automatically adds any looked up glyphname 126 to its glyphOrder.""" 127 128 def __init__(self): 129 self._glyphOrder = ['.notdef'] 130 131 class AllocatingDict(dict): 132 def __missing__(reverseDict, key): 133 self._glyphOrder.append(key) 134 gid = len(reverseDict) 135 reverseDict[key] = gid 136 return gid 137 self._reverseGlyphOrder = AllocatingDict({'.notdef': 0}) 138 self.lazy = False 139 140 def getGlyphID(self, glyph, requireReal=None): 141 gid = self._reverseGlyphOrder[glyph] 142 return gid 143 144 def getReverseGlyphMap(self): 145 return self._reverseGlyphOrder 146 147 def getGlyphName(self, gid): 148 return self._glyphOrder[gid] 149 150 def getGlyphOrder(self): 151 return self._glyphOrder 152 153 154 class TestCase(_TestCase): 155 156 def __init__(self, methodName): 157 _TestCase.__init__(self, methodName) 158 # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, 159 # and fires deprecation warnings if a program uses the old name. 160 if not hasattr(self, "assertRaisesRegex"): 161 self.assertRaisesRegex = self.assertRaisesRegexp 162 163 164 class DataFilesHandler(TestCase): 165 166 def setUp(self): 167 self.tempdir = None 168 self.num_tempfiles = 0 169 170 def tearDown(self): 171 if self.tempdir: 172 shutil.rmtree(self.tempdir) 173 174 def getpath(self, testfile): 175 folder = os.path.dirname(sys.modules[self.__module__].__file__) 176 return os.path.join(folder, "data", testfile) 177 178 def temp_dir(self): 179 if not self.tempdir: 180 self.tempdir = tempfile.mkdtemp() 181 182 def temp_font(self, font_path, file_name): 183 self.temp_dir() 184 temppath = os.path.join(self.tempdir, file_name) 185 shutil.copy2(font_path, temppath) 186 return temppath 187