switch from context manager to base class

Replaced temp_config context manager with TempConfigTestCase base class. This
lets us clean up even when a test fails.
This commit is contained in:
Adrian Sampson 2012-12-15 13:15:10 -08:00
parent 3e5ef375c8
commit 39cd1f6c63
7 changed files with 181 additions and 183 deletions

View file

@ -609,7 +609,7 @@ class SubcommandsOptionParser(optparse.OptionParser):
# The root parser and its main function.
def _raw_main(args, configfh):
def _raw_main(args):
"""A helper function for `main` without top-level exception
handling.
"""
@ -671,12 +671,12 @@ def _raw_main(args, configfh):
# Invoke the subcommand.
subcommand.func(lib, suboptions, subargs)
def main(args=None, configfh=None):
def main(args=None):
"""Run the main command-line interface for beets. Includes top-level
exception handlers that print friendly error messages.
"""
try:
_raw_main(args, configfh)
_raw_main(args)
except UserError as exc:
message = exc.args[0] if exc.args else None
log.error(u'error: {0}'.format(message))

View file

@ -81,17 +81,18 @@ def import_session(lib=None, logfile=None, paths=[], query=[], cli=False):
return cls(lib, logfile, paths, query)
# Temporary config modifications.
@contextlib.contextmanager
def temp_config():
"""A context manager that saves and restores beets' global
class TempConfigTestCase(unittest.TestCase):
"""A TestCase subclass that saves and restores beets' global
configuration. This allows tests to make temporary modifications
that will then be automatically removed when the context exits.
that will then be automatically removed when the test completes.
"""
old_sources = copy.deepcopy(beets.config.sources)
old_overlay = copy.deepcopy(beets.config.overlay)
yield
beets.config.sources = old_sources
beets.config.overlay = old_overlay
def setUp(self):
self.old_sources = copy.deepcopy(beets.config.sources)
self.old_overlay = copy.deepcopy(beets.config.overlay)
def tearDown(self):
beets.config.sources = self.old_sources
beets.config.overlay = self.old_overlay
# Mock timing.

View file

@ -179,8 +179,10 @@ class AAOTest(unittest.TestCase):
res = fetchart.aao_art('x')
self.assertEqual(res, None)
class ArtImporterTest(unittest.TestCase, _common.ExtraAsserts):
class ArtImporterTest(_common.TempConfigTestCase, _common.ExtraAsserts):
def setUp(self):
super(ArtImporterTest, self).setUp()
# Mock the album art fetcher to always return our test file.
self.art_file = os.path.join(_common.RSRC, 'tmpcover.jpg')
_common.touch(self.art_file)
@ -221,6 +223,8 @@ class ArtImporterTest(unittest.TestCase, _common.ExtraAsserts):
self.task.set_choice(AlbumMatch(0, info, {}, set(), set()))
def tearDown(self):
super(ArtImporterTest, self).tearDown()
fetchart.art_for_album = self.old_afa
if os.path.exists(self.art_file):
os.remove(self.art_file)
@ -265,15 +269,13 @@ class ArtImporterTest(unittest.TestCase, _common.ExtraAsserts):
self.assertExists(self.art_file)
def test_delete_original_file(self):
with _common.temp_config():
config['import']['delete'] = True
self._fetch_art(True)
config['import']['delete'] = True
self._fetch_art(True)
self.assertNotExists(self.art_file)
def test_move_original_file(self):
with _common.temp_config():
config['import']['move'] = True
self._fetch_art(True)
config['import']['move'] = True
self._fetch_art(True)
self.assertNotExists(self.art_file)
def test_do_not_delete_original_if_already_in_place(self):

View file

@ -500,12 +500,13 @@ class ApplyTestUtil(object):
mapping = {}
for i, t in zip(self.items, info.tracks):
mapping[i] = t
with _common.temp_config():
config['per_disc_numbering'] = per_disc_numbering
autotag.apply_metadata(info, mapping)
config['per_disc_numbering'] = per_disc_numbering
autotag.apply_metadata(info, mapping)
class ApplyTest(unittest.TestCase, ApplyTestUtil):
class ApplyTest(_common.TempConfigTestCase, ApplyTestUtil):
def setUp(self):
super(ApplyTest, self).setUp()
self.items = []
self.items.append(Item({}))
self.items.append(Item({}))
@ -616,8 +617,10 @@ class ApplyTest(unittest.TestCase, ApplyTestUtil):
self.assertEqual(self.items[1].albumartist_sort, 'albumArtistSort')
self.assertEqual(self.items[1].artist_sort, 'albumArtistSort')
class ApplyCompilationTest(unittest.TestCase, ApplyTestUtil):
class ApplyCompilationTest(_common.TempConfigTestCase, ApplyTestUtil):
def setUp(self):
super(ApplyCompilationTest, self).setUp()
self.items = []
self.items.append(Item({}))
self.items.append(Item({}))

View file

@ -27,8 +27,10 @@ from beets.autotag import AlbumInfo, TrackInfo, AlbumMatch, TrackMatch
from beets import config
TEST_TITLES = ('The Opener', 'The Second Track', 'The Last Track')
class NonAutotaggedImportTest(unittest.TestCase):
class NonAutotaggedImportTest(_common.TempConfigTestCase):
def setUp(self):
super(NonAutotaggedImportTest, self).setUp()
self.io = _common.DummyIO()
self.io.install()
@ -43,6 +45,8 @@ class NonAutotaggedImportTest(unittest.TestCase):
self.srcdir = os.path.join(_common.RSRC, 'testsrcdir')
def tearDown(self):
super(NonAutotaggedImportTest, self).tearDown()
self.io.restore()
if os.path.exists(self.libdb):
os.remove(self.libdb)
@ -84,17 +88,16 @@ class NonAutotaggedImportTest(unittest.TestCase):
}))
# Run the UI "beet import" command!
with _common.temp_config():
config['import']['delete'] = delete
config['import']['threaded'] = threaded
config['import']['singletons'] = singletons
config['import']['move'] = move
config['import']['autotag'] = False
session = importer.ImportSession(self.lib,
logfile=None,
paths=[os.path.dirname(paths[0])],
query=None)
session.run()
config['import']['delete'] = delete
config['import']['threaded'] = threaded
config['import']['singletons'] = singletons
config['import']['move'] = move
config['import']['autotag'] = False
session = importer.ImportSession(self.lib,
logfile=None,
paths=[os.path.dirname(paths[0])],
query=None)
session.run()
return paths
@ -180,8 +183,10 @@ def _call_stages(session, items, choice_or_info,
return task
class ImportApplyTest(unittest.TestCase, _common.ExtraAsserts):
class ImportApplyTest(_common.TempConfigTestCase, _common.ExtraAsserts):
def setUp(self):
super(ImportApplyTest, self).setUp()
self.libdir = os.path.join(_common.RSRC, 'testlibdir')
os.mkdir(self.libdir)
self.libpath = os.path.join(_common.RSRC, 'testlib.blb')
@ -214,6 +219,8 @@ class ImportApplyTest(unittest.TestCase, _common.ExtraAsserts):
)
def tearDown(self):
super(ImportApplyTest, self).tearDown()
shutil.rmtree(self.libdir)
if os.path.exists(self.srcdir):
shutil.rmtree(self.srcdir)
@ -221,22 +228,19 @@ class ImportApplyTest(unittest.TestCase, _common.ExtraAsserts):
os.unlink(self.libpath)
def test_finalize_no_delete(self):
with _common.temp_config():
config['import']['delete'] = False
_call_stages(self.session, [self.i], self.info)
config['import']['delete'] = False
_call_stages(self.session, [self.i], self.info)
self.assertExists(self.srcpath)
def test_finalize_with_delete(self):
with _common.temp_config():
config['import']['delete'] = True
_call_stages(self.session, [self.i], self.info)
config['import']['delete'] = True
_call_stages(self.session, [self.i], self.info)
self.assertNotExists(self.srcpath)
def test_finalize_with_delete_prunes_directory_empty(self):
with _common.temp_config():
config['import']['delete'] = True
_call_stages(self.session, [self.i], self.info,
toppath=self.srcdir)
config['import']['delete'] = True
_call_stages(self.session, [self.i], self.info,
toppath=self.srcdir)
self.assertNotExists(os.path.dirname(self.srcpath))
def test_apply_asis_uses_album_path(self):
@ -318,16 +322,14 @@ class ImportApplyTest(unittest.TestCase, _common.ExtraAsserts):
self.assertEqual(task.old_paths, [self.srcpath])
def test_apply_with_move(self):
with _common.temp_config():
config['import']['move'] = True
_call_stages(self.session, [self.i], self.info)
config['import']['move'] = True
_call_stages(self.session, [self.i], self.info)
self.assertExists(list(self.lib.items())[0].path)
self.assertNotExists(self.srcpath)
def test_apply_with_move_prunes_empty_directory(self):
with _common.temp_config():
config['import']['move'] = True
_call_stages(self.session, [self.i], self.info, toppath=self.srcdir)
config['import']['move'] = True
_call_stages(self.session, [self.i], self.info, toppath=self.srcdir)
self.assertNotExists(os.path.dirname(self.srcpath))
def test_manipulate_files_with_null_move(self):
@ -335,10 +337,9 @@ class ImportApplyTest(unittest.TestCase, _common.ExtraAsserts):
already at the destination.
"""
self.lib.move(self.i) # Already at destination.
with _common.temp_config():
config['import']['move'] = True
_call_stages(self.session, [self.i], self.info, toppath=self.srcdir,
stages=[importer.manipulate_files])
config['import']['move'] = True
_call_stages(self.session, [self.i], self.info, toppath=self.srcdir,
stages=[importer.manipulate_files])
self.assertExists(self.i.path)
class AsIsApplyTest(unittest.TestCase):
@ -386,8 +387,10 @@ class AsIsApplyTest(unittest.TestCase):
self.assertFalse(alb.comp)
self.assertEqual(alb.albumartist, self.items[2].artist)
class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
class ApplyExistingItemsTest(_common.TempConfigTestCase, _common.ExtraAsserts):
def setUp(self):
super(ApplyExistingItemsTest, self).setUp()
self.libdir = os.path.join(_common.RSRC, 'testlibdir')
os.mkdir(self.libdir)
@ -398,8 +401,6 @@ class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
]
self.session = _common.import_session(self.lib)
self.config_ctx = _common.temp_config()
self.config_ctx.__enter__()
config['import']['write'] = False
config['import']['copy'] = False
@ -409,7 +410,8 @@ class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
self.i.comp = False
def tearDown(self):
self.config_ctx.__exit__(None, None, None)
super(ApplyExistingItemsTest, self).tearDown()
os.remove(self.dbpath)
shutil.rmtree(self.libdir)
@ -452,17 +454,16 @@ class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
def test_apply_existing_item_new_metadata_does_not_duplicate(self):
# We want to copy the item to a new location.
with _common.temp_config():
config['import']['copy'] = True
config['import']['copy'] = True
# Import with existing metadata.
self._apply_asis([self.i])
# Import with existing metadata.
self._apply_asis([self.i])
# Import again with new metadata.
item = self.lib.items().next()
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
# Import again with new metadata.
item = self.lib.items().next()
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
# Should not be duplicated.
self.assertEqual(len(list(self.lib.items())), 1)
@ -470,14 +471,13 @@ class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
def test_apply_existing_item_new_metadata_moves_files(self):
# As above, import with old metadata and then reimport with new.
with _common.temp_config():
config['import']['copy'] = True
config['import']['copy'] = True
self._apply_asis([self.i])
item = self.lib.items().next()
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
self._apply_asis([self.i])
item = self.lib.items().next()
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
item = self.lib.items().next()
self.assertTrue('differentTitle' in item.path)
@ -485,29 +485,27 @@ class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
def test_apply_existing_item_new_metadata_copy_disabled(self):
# Import *without* copying to ensure that the path does *not* change.
with _common.temp_config():
config['import']['copy'] = False
config['import']['copy'] = False
self._apply_asis([self.i])
item = self.lib.items().next()
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
self._apply_asis([self.i])
item = self.lib.items().next()
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
item = self.lib.items().next()
self.assertFalse('differentTitle' in item.path)
self.assertExists(item.path)
def test_apply_existing_item_new_metadata_removes_old_files(self):
with _common.temp_config():
config['import']['copy'] = True
config['import']['copy'] = True
self._apply_asis([self.i])
item = self.lib.items().next()
oldpath = item.path
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
self._apply_asis([self.i])
item = self.lib.items().next()
oldpath = item.path
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
item = self.lib.items().next()
self.assertNotExists(oldpath)
@ -515,16 +513,15 @@ class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
def test_apply_existing_item_new_metadata_delete_enabled(self):
# The "delete" flag should be ignored -- only the "copy" flag
# controls whether files move.
with _common.temp_config():
config['import']['copy'] = True
config['import']['delete'] = True # !
config['import']['copy'] = True
config['import']['delete'] = True # !
self._apply_asis([self.i])
item = self.lib.items().next()
oldpath = item.path
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
self._apply_asis([self.i])
item = self.lib.items().next()
oldpath = item.path
new_item = library.Item.from_path(item.path)
new_item.title = 'differentTitle'
self._apply_asis([new_item])
item = self.lib.items().next()
self.assertNotExists(oldpath)
@ -533,14 +530,13 @@ class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
def test_apply_existing_item_preserves_file(self):
# With copying enabled, import the item twice with same metadata.
with _common.temp_config():
config['import']['copy'] = True
config['import']['copy'] = True
self._apply_asis([self.i])
item = self.lib.items().next()
oldpath = item.path
new_item = library.Item.from_path(item.path)
self._apply_asis([new_item])
self._apply_asis([self.i])
item = self.lib.items().next()
oldpath = item.path
new_item = library.Item.from_path(item.path)
self._apply_asis([new_item])
self.assertEqual(len(list(self.lib.items())), 1)
item = self.lib.items().next()
@ -548,14 +544,13 @@ class ApplyExistingItemsTest(unittest.TestCase, _common.ExtraAsserts):
self.assertExists(oldpath)
def test_apply_existing_item_preserves_file_delete_enabled(self):
with _common.temp_config():
config['import']['copy'] = True
config['import']['delete'] = True # !
config['import']['copy'] = True
config['import']['delete'] = True # !
self._apply_asis([self.i])
item = self.lib.items().next()
new_item = library.Item.from_path(item.path)
self._apply_asis([new_item])
self._apply_asis([self.i])
item = self.lib.items().next()
new_item = library.Item.from_path(item.path)
self._apply_asis([new_item])
self.assertEqual(len(list(self.lib.items())), 1)
item = self.lib.items().next()

View file

@ -6,7 +6,7 @@ from beets import config
from beetsplug.the import ThePlugin, PATTERN_A, PATTERN_THE, FORMAT
class ThePluginTest(unittest.TestCase):
class ThePluginTest(_common.TempConfigTestCase):
def test_unthe_with_default_patterns(self):
self.assertEqual(ThePlugin().unthe('', PATTERN_THE), '')
@ -34,11 +34,10 @@ class ThePluginTest(unittest.TestCase):
'the An Arse')
def test_unthe_with_strip(self):
with _common.temp_config():
config['the']['strip'] = True
self.assertEqual(ThePlugin().unthe('The Something', PATTERN_THE),
'Something')
self.assertEqual(ThePlugin().unthe('An A', PATTERN_A), 'A')
config['the']['strip'] = True
self.assertEqual(ThePlugin().unthe('The Something', PATTERN_THE),
'Something')
self.assertEqual(ThePlugin().unthe('An A', PATTERN_A), 'A')
def test_template_function_with_defaults(self):
ThePlugin().patterns = [PATTERN_THE, PATTERN_A]
@ -47,17 +46,15 @@ class ThePluginTest(unittest.TestCase):
self.assertEqual(ThePlugin().the_template_func('An A'), 'A, An')
def test_custom_pattern(self):
with _common.temp_config():
config['the']['patterns'] = [u'^test\s']
config['the']['format'] = FORMAT
self.assertEqual(ThePlugin().the_template_func('test passed'),
'passed, test')
config['the']['patterns'] = [u'^test\s']
config['the']['format'] = FORMAT
self.assertEqual(ThePlugin().the_template_func('test passed'),
'passed, test')
def test_custom_format(self):
with _common.temp_config():
config['the']['patterns'] = [PATTERN_THE, PATTERN_A]
config['the']['format'] = u'{1} ({0})'
self.assertEqual(ThePlugin().the_template_func('The A'), 'The (A)')
config['the']['patterns'] = [PATTERN_THE, PATTERN_A]
config['the']['format'] = u'{1} ({0})'
self.assertEqual(ThePlugin().the_template_func('The A'), 'The (A)')
def suite():

View file

@ -19,8 +19,8 @@ import shutil
import textwrap
import logging
import re
import yaml
from StringIO import StringIO
import ConfigParser
import _common
from _common import unittest
@ -31,6 +31,7 @@ from beets import autotag
from beets import importer
from beets.mediafile import MediaFile
from beets import config
from beets.util import confit
class ListTest(unittest.TestCase):
def setUp(self):
@ -467,13 +468,12 @@ class AutotagTest(unittest.TestCase):
self.io.addinput('u')
self._no_candidates_test(importer.action.ASIS)
class ImportTest(unittest.TestCase):
class ImportTest(_common.TempConfigTestCase):
def test_quiet_timid_disallowed(self):
with _common.temp_config():
config['import']['quiet'] = True
config['import']['timid'] = True
self.assertRaises(ui.UserError, commands.import_files, None, [],
None)
config['import']['quiet'] = True
config['import']['timid'] = True
self.assertRaises(ui.UserError, commands.import_files, None, [],
None)
class InputTest(unittest.TestCase):
def setUp(self):
@ -489,36 +489,44 @@ class InputTest(unittest.TestCase):
self.assertEqual(artist, u'\xc2me')
self.assertEqual(album, u'\xc2me')
class ConfigTest(unittest.TestCase):
class ConfigTest(_common.TempConfigTestCase):
def setUp(self):
super(ConfigTest, self).setUp()
self.io = _common.DummyIO()
self.io.install()
self.test_cmd = ui.Subcommand('test', help='test')
commands.default_commands.append(self.test_cmd)
def tearDown(self):
super(ConfigTest, self).tearDown()
self.io.restore()
commands.default_commands.pop()
def _run_main(self, args, config, func):
def _run_main(self, args, config_yaml, func):
self.test_cmd.func = func
ui._raw_main(args + ['test'], StringIO(config))
config_yaml = textwrap.dedent(config_yaml).strip()
if config_yaml:
config_data = yaml.load(config_yaml, Loader=confit.Loader)
config.sources.insert(0, config_data)
ui._raw_main(args + ['test'])
def test_paths_section_respected(self):
def func(lib, opts, args):
key, template = lib.path_formats[0]
self.assertEqual(key, 'x')
self.assertEqual(template.original, 'y')
self._run_main([], textwrap.dedent("""
[paths]
x=y"""), func)
self._run_main([], """
paths:
- x: y
""", func)
def test_default_paths_preserved(self):
default_formats = ui.get_path_formats()
def func(lib, opts, args):
self.assertEqual(lib.path_formats[1:],
default_formats)
self._run_main([], textwrap.dedent("""
[paths]
x=y"""), func)
self._run_main([], """
paths:
- x: y
""", func)
def test_nonexistant_config_file(self):
os.environ['BEETSCONFIG'] = '/xxxxx'
@ -528,34 +536,24 @@ class ConfigTest(unittest.TestCase):
def func(lib, opts, args):
pass
with self.assertRaises(ui.UserError):
self._run_main([], textwrap.dedent("""
[beets]
self._run_main([], """
library: /xxx/yyy/not/a/real/path
"""), func)
""", func)
def test_replacements_parsed(self):
def func(lib, opts, args):
replacements = lib.replacements
self.assertEqual(replacements, [(re.compile(ur'[xy]'), u'z')])
self._run_main([], textwrap.dedent("""
[beets]
replace=[xy] z"""), func)
def test_replacements_parsed_unicode(self):
def func(lib, opts, args):
replacements = lib.replacements
self.assertEqual(replacements, [(re.compile(ur'\u2019'), u'z')])
self._run_main([], textwrap.dedent(u"""
[beets]
replace=\u2019 z"""), func)
self._run_main([], """
replace:
- '[xy]': z
""", func)
def test_empty_replacements_produce_none(self):
def func(lib, opts, args):
replacements = lib.replacements
self.assertFalse(replacements)
self._run_main([], textwrap.dedent("""
[beets]
"""), func)
self._run_main([], "", func)
def test_multiple_replacements_parsed(self):
def func(lib, opts, args):
@ -564,16 +562,19 @@ class ConfigTest(unittest.TestCase):
(re.compile(ur'[xy]'), u'z'),
(re.compile(ur'foo'), u'bar'),
])
self._run_main([], textwrap.dedent("""
[beets]
replace=[xy] z
foo bar"""), func)
self._run_main([], """
replace:
- '[xy]': z
- foo: bar
""", func)
class ShowdiffTest(unittest.TestCase):
class ShowdiffTest(_common.TempConfigTestCase):
def setUp(self):
super(ShowdiffTest, self).setUp()
self.io = _common.DummyIO()
self.io.install()
def tearDown(self):
super(ShowdiffTest, self).tearDown()
self.io.restore()
def test_showdiff_strings(self):
@ -592,9 +593,8 @@ class ShowdiffTest(unittest.TestCase):
self.assertTrue('field' in out)
def test_showdiff_ints_no_color(self):
with _common.temp_config():
config['color'] = False
commands._showdiff('field', 2, 3)
config['color'] = False
commands._showdiff('field', 2, 3)
out = self.io.getoutput()
self.assertTrue('field' in out)
@ -647,8 +647,9 @@ class ManualIDTest(unittest.TestCase):
out = commands.manual_id(False)
self.assertEqual(out, AN_ID)
class ShowChangeTest(unittest.TestCase):
class ShowChangeTest(_common.TempConfigTestCase):
def setUp(self):
super(ShowChangeTest, self).setUp()
self.io = _common.DummyIO()
self.io.install()
@ -661,6 +662,7 @@ class ShowChangeTest(unittest.TestCase):
])
def tearDown(self):
super(ShowChangeTest, self).tearDown()
self.io.restore()
def _show_change(self, items=None, info=None,
@ -669,13 +671,12 @@ class ShowChangeTest(unittest.TestCase):
items = items or self.items
info = info or self.info
mapping = dict(zip(items, info.tracks))
with _common.temp_config():
config['color'] = False
commands.show_change(
cur_artist,
cur_album,
autotag.AlbumMatch(0.1, info, mapping, set(), set()),
)
config['color'] = False
commands.show_change(
cur_artist,
cur_album,
autotag.AlbumMatch(0.1, info, mapping, set(), set()),
)
return self.io.getoutput().lower()
def test_null_change(self):
@ -715,13 +716,12 @@ class ShowChangeTest(unittest.TestCase):
self.assertTrue(u'caf\xe9.mp3 -> the title' in msg
or u'caf.mp3 ->' in msg)
class PathFormatTest(unittest.TestCase):
class PathFormatTest(_common.TempConfigTestCase):
def test_custom_paths_prepend(self):
default_formats = ui.get_path_formats()
with _common.temp_config():
config['paths'] = [('foo', 'bar')]
pf = ui.get_path_formats()
config['paths'] = [('foo', 'bar')]
pf = ui.get_path_formats()
key, tmpl = pf[0]
self.assertEqual(key, 'foo')
self.assertEqual(tmpl.original, 'bar')