Home | History | Annotate | Download | only in misc
      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