예제 #1
0
def test_on_cleanup_with_all_kwargs(mocker):
    registry = OperatorRegistry()

    @kopf.on.cleanup(id='id',
                     registry=registry,
                     errors=ErrorsMode.PERMANENT,
                     timeout=123,
                     retries=456,
                     backoff=78)
    def fn(**_):
        pass

    handlers = registry.get_activity_handlers(activity=Activity.CLEANUP)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].activity == Activity.CLEANUP
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
예제 #2
0
def test_requires_finalizer_no_deletion_handler(cause_factory):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, body=OBJECT_BODY)

    @kopf.on.create('group', 'version', 'plural', registry=registry)
    def fn1(**_):
        pass

    requires_finalizer = registry.resource_changing_handlers.requires_finalizer(
        cause)
    assert requires_finalizer is False
예제 #3
0
def test_on_probe_with_all_kwargs(mocker):
    registry = OperatorRegistry()

    @kopf.on.probe(id='id',
                   registry=registry,
                   errors=ErrorsMode.PERMANENT,
                   timeout=123,
                   retries=456,
                   cooldown=78)
    def fn(**_):
        pass

    handlers = registry.get_activity_handlers(activity=Activity.PROBE)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].activity == Activity.PROBE
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].cooldown == 78
예제 #4
0
def test_requires_finalizer_multiple_handlers(optional, expected):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')

    @kopf.on.create('group', 'version', 'plural', registry=registry)
    def fn1(**_):
        pass

    @kopf.on.delete('group',
                    'version',
                    'plural',
                    registry=registry,
                    optional=optional)
    def fn2(**_):
        pass

    with pytest.deprecated_call(
            match=r"use registry.resource_changing_handlers"):
        requires_finalizer = registry.requires_finalizer(resource=resource,
                                                         cause=CAUSE)
    assert requires_finalizer == expected
예제 #5
0
async def test_errors_are_raised_aggregated(activity):
    def sample_fn1(**_):
        raise PermanentError("boo!123")

    def sample_fn2(**_):
        raise PermanentError("boo!456")

    registry = OperatorRegistry()
    registry.register_activity_handler(fn=sample_fn1,
                                       id='id1',
                                       activity=activity)
    registry.register_activity_handler(fn=sample_fn2,
                                       id='id2',
                                       activity=activity)

    with pytest.raises(ActivityError) as e:
        await run_activity(
            registry=registry,
            activity=activity,
            lifecycle=all_at_once,
        )

    assert set(e.value.outcomes.keys()) == {'id1', 'id2'}
    assert e.value.outcomes['id1'].final
    assert e.value.outcomes['id1'].delay is None
    assert e.value.outcomes['id1'].result is None
    assert e.value.outcomes['id1'].exception is not None
    assert e.value.outcomes['id2'].final
    assert e.value.outcomes['id2'].delay is None
    assert e.value.outcomes['id2'].result is None
    assert e.value.outcomes['id2'].exception is not None
    assert str(e.value.outcomes['id1'].exception) == "boo!123"
    assert str(e.value.outcomes['id2'].exception) == "boo!456"
예제 #6
0
def test_on_delete_with_all_kwargs(mocker, optional):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource, reason=Reason.DELETE)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.delete('group',
                    'version',
                    'plural',
                    id='id',
                    registry=registry,
                    errors=ErrorsMode.PERMANENT,
                    timeout=123,
                    retries=456,
                    backoff=78,
                    optional=optional,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'},
                    when=when)
    def fn(**_):
        pass

    with pytest.deprecated_call(
            match=r"use registry.resource_changing_handlers"):
        handlers = registry.get_resource_changing_handlers(cause)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.DELETE
    assert handlers[0].field is None
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
예제 #7
0
def test_on_field_with_all_kwargs(mocker):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    diff = [('op', ('field', 'subfield'), 'old', 'new')]
    cause = mocker.MagicMock(resource=resource,
                             reason=Reason.UPDATE,
                             diff=diff)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.field('group',
                   'version',
                   'plural',
                   'field.subfield',
                   id='id',
                   registry=registry,
                   errors=ErrorsMode.PERMANENT,
                   timeout=123,
                   retries=456,
                   backoff=78,
                   labels={'somelabel': 'somevalue'},
                   annotations={'someanno': 'somevalue'},
                   when=when)
    def fn(**_):
        pass

    handlers = registry.get_resource_changing_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].id == 'id/field.subfield'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
예제 #8
0
async def test_errors_are_raised_aggregated(settings, activity):
    def sample_fn1(**_):
        raise PermanentError("boo!123")

    def sample_fn2(**_):
        raise PermanentError("boo!456")

    registry = OperatorRegistry()
    registry._activities.append(
        ActivityHandler(
            fn=sample_fn1,
            id='id1',
            activity=activity,
            param=None,
            errors=None,
            timeout=None,
            retries=None,
            backoff=None,
        ))
    registry._activities.append(
        ActivityHandler(
            fn=sample_fn2,
            id='id2',
            activity=activity,
            param=None,
            errors=None,
            timeout=None,
            retries=None,
            backoff=None,
        ))

    with pytest.raises(ActivityError) as e:
        await run_activity(
            registry=registry,
            settings=settings,
            activity=activity,
            lifecycle=all_at_once,
            indices=OperatorIndexers().indices,
            memo=Memo(),
        )

    assert set(e.value.outcomes.keys()) == {'id1', 'id2'}
    assert e.value.outcomes['id1'].final
    assert e.value.outcomes['id1'].delay is None
    assert e.value.outcomes['id1'].result is None
    assert e.value.outcomes['id1'].exception is not None
    assert e.value.outcomes['id2'].final
    assert e.value.outcomes['id2'].delay is None
    assert e.value.outcomes['id2'].result is None
    assert e.value.outcomes['id2'].exception is not None
    assert str(e.value.outcomes['id1'].exception) == "boo!123"
    assert str(e.value.outcomes['id2'].exception) == "boo!456"
예제 #9
0
async def activity_trigger(
    *,
    lifecycle: lifecycles.LifeCycleFn,
    registry: registries.OperatorRegistry,
    activity: causation.Activity,
) -> Mapping[registries.HandlerId, registries.HandlerResult]:
    """
    Execute a handling cycle until succeeded or permanently failed.

    This mimics the behaviour of patching-watching in Kubernetes, but in-memory.
    """
    logger = logging.getLogger(f'kopf.activities.{activity.value}')

    # For the activity handlers, we have neither bodies, nor patches, just the state.
    cause = causation.ActivityCause(logger=logger, activity=activity)
    handlers = registry.get_activity_handlers(activity=activity)
    state = states.State.from_scratch(handlers=handlers)
    latest_outcomes: MutableMapping[registries.HandlerId,
                                    states.HandlerOutcome] = {}
    while not state.done:
        outcomes = await _execute_handlers(
            lifecycle=lifecycle,
            handlers=handlers,
            cause=cause,
            state=state,
        )
        latest_outcomes.update(outcomes)
        state = state.with_outcomes(outcomes)
        delay = state.delay
        if delay:
            await sleeping.sleep_or_wait(
                min(delay, WAITING_KEEPALIVE_INTERVAL), asyncio.Event())

    # Activities assume that all handlers must eventually succeed.
    # We raise from the 1st exception only: just to have something real in the tracebacks.
    # For multiple handlers' errors, the logs should be investigated instead.
    exceptions = [
        outcome.exception for outcome in latest_outcomes.values()
        if outcome.exception is not None
    ]
    if exceptions:
        raise ActivityError("One or more handlers failed.", outcomes=latest_outcomes) \
            from exceptions[0]

    # If nothing has failed, we return identifiable results. The outcomes/states are internal.
    # The order of results is not guaranteed (the handlers can succeed on one of the retries).
    results = {
        handler_id: outcome.result
        for handler_id, outcome in latest_outcomes.items()
        if outcome.result is not None
    }
    return results
예제 #10
0
async def test_empty_registry_produces_no_credentials():
    vault = Vault()
    registry = OperatorRegistry()

    await authenticate(
        registry=registry,
        vault=vault,
    )

    assert not vault
    with pytest.raises(LoginError):
        async for _, _ in vault:
            pass
예제 #11
0
def test_requires_finalizer_multiple_handlers(optional, expected,
                                              cause_factory):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, body=OBJECT_BODY)

    @kopf.on.create('group', 'version', 'plural', registry=registry)
    def fn1(**_):
        pass

    @kopf.on.delete('group',
                    'version',
                    'plural',
                    registry=registry,
                    optional=optional)
    def fn2(**_):
        pass

    with pytest.deprecated_call(match=r"cease using the internal registries"):
        requires_finalizer = registry.requires_finalizer(resource=resource,
                                                         cause=cause)
    assert requires_finalizer == expected
예제 #12
0
def test_registry_and_settings_are_propagated(mocker):
    operator_mock = mocker.patch('kopf.reactor.running.operator')
    registry = OperatorRegistry()
    settings = OperatorSettings()
    with KopfRunner(['run', '--standalone'],
                    registry=registry,
                    settings=settings) as runner:
        pass
    assert runner.exit_code == 0
    assert runner.exception is None
    assert operator_mock.called
    assert operator_mock.call_args[1]['registry'] is registry
    assert operator_mock.call_args[1]['settings'] is settings
예제 #13
0
async def test_retries_are_simulated(activity, mocker):
    mock = mocker.MagicMock()

    def sample_fn(**_):
        mock()
        raise TemporaryError('to be retried', delay=0)

    registry = OperatorRegistry()
    registry.register_activity_handler(fn=sample_fn,
                                       id='id',
                                       activity=activity,
                                       retries=3)

    with pytest.raises(ActivityError) as e:
        await run_activity(
            registry=registry,
            activity=activity,
            lifecycle=all_at_once,
        )

    assert isinstance(e.value.outcomes['id'].exception, PermanentError)
    assert mock.call_count == 3
예제 #14
0
def test_on_resume_with_all_kwargs(mocker, reason):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = mocker.MagicMock(resource=resource,
                             reason=reason,
                             initial=True,
                             deleted=False)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    @kopf.on.resume('group',
                    'version',
                    'plural',
                    id='id',
                    registry=registry,
                    errors=ErrorsMode.PERMANENT,
                    timeout=123,
                    retries=456,
                    backoff=78,
                    deleted=True,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'})
    def fn(**_):
        pass

    handlers = registry.get_resource_changing_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].field is None
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].cooldown == 78  # deprecated alias
    assert handlers[0].deleted == True
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
예제 #15
0
def test_on_startup_with_all_kwargs(mocker):
    registry = OperatorRegistry()

    @kopf.on.startup(id='id',
                     registry=registry,
                     errors=ErrorsMode.PERMANENT,
                     timeout=123,
                     retries=456,
                     backoff=78)
    def fn(**_):
        pass

    with pytest.deprecated_call(match=r"cease using the internal registries"):
        handlers = registry.get_activity_handlers(activity=Activity.STARTUP)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].activity == Activity.STARTUP
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
예제 #16
0
async def test_noreturn_handler_produces_no_credentials():
    vault = Vault()
    registry = OperatorRegistry()

    def login_fn(**_):
        pass

    registry.register_activity_handler(
        fn=login_fn,
        id=
        'login_fn',  # auto-detection does not work, as it is local to the test function.
        activity=Activity.AUTHENTICATION,
    )

    await authenticate(
        registry=registry,
        vault=vault,
    )

    assert not vault
    with pytest.raises(LoginError):
        async for _, _ in vault:
            pass
예제 #17
0
def test_on_probe_with_all_kwargs(mocker):
    registry = OperatorRegistry()

    @kopf.on.probe(id='id',
                   registry=registry,
                   errors=ErrorsMode.PERMANENT,
                   timeout=123,
                   retries=456,
                   backoff=78)
    def fn(**_):
        pass

    with pytest.deprecated_call(match=r"use registry.activity_handlers"):
        handlers = registry.get_activity_handlers(activity=Activity.PROBE)

    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].activity == Activity.PROBE
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
예제 #18
0
def test_requires_finalizer_deletion_handler(optional, expected):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')

    @kopf.on.delete('group',
                    'version',
                    'plural',
                    registry=registry,
                    optional=optional)
    def fn(**_):
        pass

    requires_finalizer = registry.resource_changing_handlers[
        resource].requires_finalizer(CAUSE)
    assert requires_finalizer == expected
예제 #19
0
async def test_empty_registry_produces_no_credentials(settings):
    vault = Vault()
    registry = OperatorRegistry()

    await authenticate(
        registry=registry,
        settings=settings,
        vault=vault,
        memo=Memo(),
        indices=OperatorIndexers().indices,
    )

    assert not vault
    with pytest.raises(LoginError):
        async for _, _ in vault:
            pass
예제 #20
0
def test_on_field_with_most_kwargs(mocker, cause_factory):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    old = {'field': {'subfield': 'old'}}
    new = {'field': {'subfield': 'new'}}
    cause = cause_factory(resource=resource,
                          reason=Reason.UPDATE,
                          old=old,
                          new=new,
                          body=new)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.field('group',
                   'version',
                   'plural',
                   field='field.subfield',
                   id='id',
                   registry=registry,
                   errors=ErrorsMode.PERMANENT,
                   timeout=123,
                   retries=456,
                   backoff=78,
                   labels={'somelabel': 'somevalue'},
                   annotations={'someanno': 'somevalue'},
                   when=when)
    def fn(**_):
        pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(
        cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].id == 'id/field.subfield'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].value is None
    assert handlers[0].old is None
    assert handlers[0].new is None
예제 #21
0
def test_requires_finalizer_deletion_handler(optional, expected,
                                             cause_factory):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, body=OBJECT_BODY)

    @kopf.on.delete('group',
                    'version',
                    'plural',
                    registry=registry,
                    optional=optional)
    def fn(**_):
        pass

    requires_finalizer = registry.resource_changing_handlers.requires_finalizer(
        cause)
    assert requires_finalizer == expected
예제 #22
0
async def test_results_are_returned_on_success(settings, activity):
    def sample_fn1(**_):
        return 123

    def sample_fn2(**_):
        return 456

    registry = OperatorRegistry()
    registry._activities.append(
        ActivityHandler(
            fn=sample_fn1,
            id='id1',
            activity=activity,
            param=None,
            errors=None,
            timeout=None,
            retries=None,
            backoff=None,
        ))
    registry._activities.append(
        ActivityHandler(
            fn=sample_fn2,
            id='id2',
            activity=activity,
            param=None,
            errors=None,
            timeout=None,
            retries=None,
            backoff=None,
        ))

    results = await run_activity(
        registry=registry,
        settings=settings,
        activity=activity,
        lifecycle=all_at_once,
        indices=OperatorIndexers().indices,
        memo=Memo(),
    )

    assert set(results.keys()) == {'id1', 'id2'}
    assert results['id1'] == 123
    assert results['id2'] == 456
예제 #23
0
def test_resources():
    handler = Mock()

    resource1 = Resource('group1', 'version1', 'plural1')
    resource2 = Resource('group2', 'version2', 'plural2')

    registry = OperatorRegistry()
    registry.resource_watching_handlers[resource1].append(handler)
    registry.resource_changing_handlers[resource2].append(handler)
    registry.resource_watching_handlers[resource2].append(handler)
    registry.resource_changing_handlers[resource1].append(handler)

    resources = registry.resources

    assert isinstance(resources, collections.abc.Collection)
    assert len(resources) == 2

    assert resource1 in resources
    assert resource2 in resources
예제 #24
0
def test_on_resume_with_most_kwargs(mocker, reason, cause_factory):
    registry = OperatorRegistry()
    resource = Resource('group', 'version', 'plural')
    cause = cause_factory(resource=resource, reason=reason, initial=True)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.resume('group',
                    'version',
                    'plural',
                    id='id',
                    registry=registry,
                    errors=ErrorsMode.PERMANENT,
                    timeout=123,
                    retries=456,
                    backoff=78,
                    deleted=True,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'},
                    when=when)
    def fn(**_):
        pass

    handlers = registry.resource_changing_handlers[resource].get_handlers(
        cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason is None
    assert handlers[0].id == 'id'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].deleted == True
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
    assert handlers[0].field is None
    assert handlers[0].value is None
    assert handlers[0].old is None
    assert handlers[0].new is None
예제 #25
0
def test_resources():
    resource1 = Resource('group1', 'version1', 'plural1')
    resource2 = Resource('group2', 'version2', 'plural2')
    selector1 = Selector('group1', 'version1', 'plural1')
    selector2 = Selector('group2', 'version2', 'plural2')
    handler1 = Mock(selector=selector1)
    handler2 = Mock(selector=selector2)

    registry = OperatorRegistry()
    registry.resource_watching_handlers.append(handler1)
    registry.resource_changing_handlers.append(handler2)
    registry.resource_watching_handlers.append(handler2)
    registry.resource_changing_handlers.append(handler1)

    resources = registry.resources

    assert isinstance(resources, collections.abc.Collection)
    assert len(resources) == 2

    assert resource1 in resources
    assert resource2 in resources
예제 #26
0
async def test_errors_are_cascaded_from_one_of_the_originals(activity):

    def sample_fn(**_):
        raise PermanentError("boo!")

    registry = OperatorRegistry()
    registry.activity_handlers.append(ActivityHandler(
        fn=sample_fn, id='id', activity=activity,
        errors=None, timeout=None, retries=None, backoff=None, cooldown=None,
    ))

    with pytest.raises(ActivityError) as e:
        await run_activity(
            registry=registry,
            activity=activity,
            lifecycle=all_at_once,
        )

    assert e.value.__cause__
    assert type(e.value.__cause__) is PermanentError
    assert str(e.value.__cause__) == "boo!"
예제 #27
0
def test_resources():
    registry = OperatorRegistry()
    registry.register_resource_changing_handler('group1', 'version1',
                                                'plural1', some_fn)
    registry.register_resource_changing_handler('group2', 'version2',
                                                'plural2', some_fn)

    resources = registry.resources

    assert isinstance(resources, collections.abc.Collection)
    assert len(resources) == 2

    resource1 = Resource('group1', 'version1', 'plural1')
    resource2 = Resource('group2', 'version2', 'plural2')
    assert resource1 in resources
    assert resource2 in resources
예제 #28
0
def test_on_delete_with_most_kwargs(mocker, cause_factory, optional, resource):
    registry = OperatorRegistry()
    cause = cause_factory(resource=resource, reason=Reason.DELETE)
    mocker.patch('kopf.reactor.registries.match', return_value=True)

    when = lambda **_: False

    @kopf.on.delete(*resource,
                    id='id',
                    registry=registry,
                    errors=ErrorsMode.PERMANENT,
                    timeout=123,
                    retries=456,
                    backoff=78,
                    optional=optional,
                    field='field.subfield',
                    value=999,
                    labels={'somelabel': 'somevalue'},
                    annotations={'someanno': 'somevalue'},
                    when=when)
    def fn(**_):
        pass

    handlers = registry._resource_changing.get_handlers(cause)
    assert len(handlers) == 1
    assert handlers[0].fn is fn
    assert handlers[0].reason == Reason.DELETE
    assert handlers[0].id == 'id/field.subfield'
    assert handlers[0].errors == ErrorsMode.PERMANENT
    assert handlers[0].timeout == 123
    assert handlers[0].retries == 456
    assert handlers[0].backoff == 78
    assert handlers[0].labels == {'somelabel': 'somevalue'}
    assert handlers[0].annotations == {'someanno': 'somevalue'}
    assert handlers[0].when == when
    assert handlers[0].field == ('field', 'subfield')
    assert handlers[0].value == 999
    assert handlers[0].old is None
    assert handlers[0].new is None