Skip to content

Commit

Permalink
format: fix multiple class-level __init__ per one __new__ result
Browse files Browse the repository at this point in the history
+ modify tests/run_cmd to trigger such a case

Reported-by: Tomáš Jelínek" <[email protected]>
Signed-off-by: Jan Pokorný <[email protected]>
  • Loading branch information
jnpkrn committed Sep 3, 2014
1 parent 20be3b7 commit 2320b49
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 12 deletions.
8 changes: 6 additions & 2 deletions format.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ class FormatError(ClufterError):
class formats(PluginRegistry):
"""Format registry (to be used as a metaclass for formats)"""
def __init__(cls, name, bases, attrs):
# NOTE could be called multiple times, with popattr-destroyed
# form (attrs missing), so defer to attrs then
# could be called multiple times but only once per respective
# __new__ (in plugin_registry) is required, rest would be waste
# of resources if not harmful due to non-idempotent modifications
if cls._probes > 1:
return

cls._protocols, cls._validators, cls._protocol_attrs = {}, {}, set()
cls._context = set(popattr(cls, 'context_specs',
attrs.pop('context_specs', ())))
Expand Down
3 changes: 3 additions & 0 deletions plugin_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class PluginRegistry(type):
# non-API

def __new__(registry, name, bases, attrs):
assert '_probes' not in attrs, "sabotage of the meta level detected"
attrs['_probes'] = 0
if '__metaclass__' not in attrs and MetaPlugin not in bases:
# alleged end-use plugin
ret = registry.probe(name, bases, attrs)
Expand All @@ -67,6 +69,7 @@ def __new__(registry, name, bases, attrs):
ret = super(PluginRegistry, registry).__new__(registry, name,
bases, attrs)

ret._probes += 1
return ret

#
Expand Down
26 changes: 16 additions & 10 deletions tests/run_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import unittest
from os.path import dirname, exists, join
from os import unlink
#from os import unlink

import _bootstrap

Expand All @@ -20,43 +20,49 @@ class Main(unittest.TestCase):
# testcib = join(dirname(__file__), '.testcib.xml')
# testcoro = join(dirname(__file__), '.testcorosync.conf')

# files = dict(
# outputs = dict(
# cib=testcib,
# coro=testcoro,
# )
# clufter_args = type("cluster_args", (object, ), dict(
# input=testfile,
# nocheck=True,
# batch=True,
# **files)
# **outputs)
# )
# for f in files.itervalues():
# for f in outputs.itervalues():
# try:
# unlink(f)
# except OSError:
# pass
# cmd_manager = CommandManager.implicit()
# self.assertFalse(cmd_manager.commands["ccs2pcs-needle"](clufter_args))
# # just the existence of the files is enough for now...
# map(lambda f: self.assertTrue(exists(f)), files.itervalues())
# map(lambda f: self.assertTrue(exists(f)), outputs.itervalues())

def testCcs2PcsNeedleBetter(self):
testfile = join(dirname(__file__), 'filled.conf')
from clufter.formats.simpleconfig import simpleconfig
#from clufter.protocol import protocols
#protocols = protocols.plugins

files = {
"cib": {'passin': 'bytestring', },
"coro": {'passin': 'bytestring', },
outputs = {
# alternatives for posterity:
#"cib" : {'passin': protocols['bytestring']},
#"cib" : {'passin': 'bytestring'},
"cib" : {'passin': simpleconfig.BYTESTRING},
"coro": {'passin': simpleconfig.STRUCT},
}
clufter_args = type("cluster_args", (object, ), dict(
input=testfile,
nocheck=True,
batch=True,
**files)
**outputs)
)
cmd_manager = CommandManager.implicit()
self.assertFalse(cmd_manager.commands["ccs2pcs-needle"](clufter_args))
# just the existence of non-null strings is enough for now...
map(lambda fspec: self.assertTrue(fspec['passout']), files.values())
map(lambda fspec: self.assertTrue(fspec['passout']), outputs.values())


if __name__ == '__main__':
Expand Down

0 comments on commit 2320b49

Please sign in to comment.