Ejemplo n.º 1
0
async def test_pri(client, loop):
    """Testing prioritized reading"""
    logger.debug("START subdir")
    d = dict
    types = EtcTypes()
    t = client
    w = await t.tree(("two", ), immediate=False, static=False)
    d1 = d(pri=d(a="1", b="2", c="3", d="4", e="5"))
    await w.update(d1)
    await w.close()

    class CheckFirst(EtcString):
        async def init(self):
            assert ilen(self.parent.values()) == 1, self.parent._data

    class CheckLast(EtcString):
        async def init(self):
            assert ilen(self.parent.values()) == 5, self.parent._data

    types.register("pri", "c", cls=CheckFirst, pri=1)
    types.register("pri", "d", cls=CheckLast, pri=-1)
    types.register("pri", "b", cls=CheckLast, pri=-1)
    w = await t.tree(("two", ), immediate=None, static=False, types=types)
    wd = w['pri']
    assert isinstance(wd, EtcAwaiter)
    wd = await wd
    assert isinstance(wd, EtcDir), wd
    assert wd['a'] == "1"
    assert wd['b'] == "2"
    assert wd['c'] == "3"
    await w.close()
Ejemplo n.º 2
0
async def do_typed(client, loop, subtyped, recursed):
    # object type registration
    types = EtcTypes()
    if subtyped:

        class Sub(EtcDir):
            def __init__(self, *a, pre=None, **k):
                super().__init__(*a, **k, pre=pre)
                assert pre['my_value'].value == '10'
                self._types = EtcTypes()
                self._types.register('my_value', cls=EtcInteger)

            def subtype(self, *path, pre=None, recursive=None, **kw):
                if path == ('my_value', ):
                    if pre is None:
                        raise ReloadData
                    assert pre.value == "10", pre
                elif path == ('a', 'b', 'c'):
                    if pre is None:
                        raise ReloadData  # yes, I'm bad
                    elif not recursive:
                        raise ReloadRecursive
                elif not recursive:
                    assert len(path) < 3
                return super().subtype(*path,
                                       pre=pre,
                                       recursive=recursive,
                                       **kw)

        types.register('here', cls=Sub)
    else:
        types.register('here', 'my_value', cls=EtcInteger)

    d = dict
    t = client
    d1 = d(types=d(here=d(my_value='10', a=d(b=d(c=d(d=d(e='20')))))))
    await t._f(d1)
    w = await t.tree("/types", immediate=recursed, static=True, types=types)
    v = await w['here'].get('my_value', raw=True)
    assert v.value == 10, w['here'].get('my_value', raw=True)
    v = await w['here']
    assert v['my_value'] == 10, v.get('my_value', raw=True)
    assert (recursed is None) == (type(v['a']['b']['c']) is EtcAwaiter)
    await v['a']['b']['c']
    assert not type(v['a']['b']['c']) is EtcAwaiter
    assert (type(v['a']['b']['c']['d']) is EtcAwaiter) == (recursed is None
                                                           and not subtyped)
    await v['a']['b']['c']['d']  # no-op
    assert v['a']['b']['c']['d']['e'] == '20'
    assert isinstance(v, Sub if subtyped else EtcDir)

    await w.close()
Ejemplo n.º 3
0
 class Sub(EtcDir):
     def __init__(self,*a,pre=None,**k):
         super().__init__(*a,**k,pre=pre)
         assert pre['my_value'].value == '10'
         self._types = EtcTypes()
         self._types.register('my_value',cls=EtcInteger)
     def subtype(self,*path,pre=None,recursive=None,**kw):
         if path == ('my_value',):
             if pre is None:
                 raise ReloadData
             assert pre.value=="10",pre
         elif path == ('a','b','c'):
             if pre is None:
                 raise ReloadData # yes, I'm bad
             elif not recursive:
                 raise ReloadRecursive
         elif not recursive:
             assert len(path)<3
         return super().subtype(*path,pre=pre,recursive=recursive,**kw)
Ejemplo n.º 4
0
async def do_typed(client,loop, subtyped,recursed):
    # object type registration
    types = EtcTypes()
    if subtyped:
        class Sub(EtcDir):
            def __init__(self,*a,pre=None,**k):
                super().__init__(*a,**k,pre=pre)
                assert pre['my_value'].value == '10'
                self._types = EtcTypes()
                self._types.register('my_value',cls=EtcInteger)
            def subtype(self,*path,pre=None,recursive=None,**kw):
                if path == ('my_value',):
                    if pre is None:
                        raise ReloadData
                    assert pre.value=="10",pre
                elif path == ('a','b','c'):
                    if pre is None:
                        raise ReloadData # yes, I'm bad
                    elif not recursive:
                        raise ReloadRecursive
                elif not recursive:
                    assert len(path)<3
                return super().subtype(*path,pre=pre,recursive=recursive,**kw)
        types.register('here',cls=Sub)
    else:
        types.register('here','my_value',cls=EtcInteger)

    d=dict
    t = client
    d1=d(types=d(here=d(my_value='10',a=d(b=d(c=d(d=d(e='20')))))))
    await t._f(d1)
    w = await t.tree("/types", immediate=recursed, static=True, types=types)
    v = await w['here']._get('my_value')
    assert v.value == 10,w['here']._get('my_value')
    v = await w['here']
    assert v['my_value'] == 10, v._get('my_value')
    assert (recursed is None) == (type(v['a']['b']['c']) is EtcAwaiter)
    await v['a']['b']['c']
    assert not type(v['a']['b']['c']) is EtcAwaiter
    assert (type(v['a']['b']['c']['d']) is EtcAwaiter) == (recursed is None and not subtyped)
    await v['a']['b']['c']['d'] # no-op
    assert v['a']['b']['c']['d']['e'] == '20'
    assert isinstance(v, Sub if subtyped else EtcDir)
Ejemplo n.º 5
0
        class Sub(EtcDir):
            def __init__(self, *a, pre=None, **k):
                super().__init__(*a, **k, pre=pre)
                assert pre['my_value'].value == '10'
                self._types = EtcTypes()
                self._types.register('my_value', cls=EtcInteger)

            def subtype(self, *path, pre=None, recursive=None, **kw):
                if path == ('my_value', ):
                    if pre is None:
                        raise ReloadData
                    assert pre.value == "10", pre
                elif path == ('a', 'b', 'c'):
                    if pre is None:
                        raise ReloadData  # yes, I'm bad
                    elif not recursive:
                        raise ReloadRecursive
                elif not recursive:
                    assert len(path) < 3
                return super().subtype(*path,
                                       pre=pre,
                                       recursive=recursive,
                                       **kw)
Ejemplo n.º 6
0
async def test_basic_watch(client, loop):
    """Watches which don't actually watch"""
    # object type registration
    types = EtcTypes()
    twotypes = EtcTypes()

    @twotypes.register()
    class rTwo(EtcDir):
        pass

    class rDie(EtcValue):
        async def has_update(self):
            raise RuntimeError("RIP")

    @twotypes.register("die")
    class rPreDie(EtcValue):
        @classmethod
        async def this_obj(cls, recursive=None, **kw):
            return rDie(**kw)

    # reg funcion shall return the right thing
    types.step('two', dest=twotypes)
    assert types[('two', 'die')] is rPreDie
    assert types.lookup(('two', 'die'), dir=False) is rPreDie
    assert types.lookup('two', 'die', dir=False) is rPreDie
    assert types.lookup('two/die', dir=False) is rPreDie
    assert types.lookup('two', dir=True, raw=True).lookup('die',
                                                          dir=False) is rPreDie
    assert types.lookup('two/die', dir=False,
                        raw=True).lookup(dir=False) is rPreDie
    i = types.register("two", "vier", cls=EtcBoolean)
    assert i is EtcBoolean
    i = types.register("*/vierixx")(EtcInteger)
    assert i is EtcInteger
    types['what/ever'] = EtcFloat
    types['what/ever'] = rTwo
    assert types.lookup('what', 'ever', dir=False) is EtcFloat
    assert types.lookup('what', 'ever', dir=True) is rTwo
    assert types['what/ever'] is EtcFloat
    with pytest.raises(AssertionError):
        types['/what/ever']
    with pytest.raises(AssertionError):
        types['what/ever/']
    with pytest.raises(AssertionError):
        types['what//ever']
    types['something/else'] = EtcInteger
    assert types['two/vier'] is EtcBoolean
    assert types['something/else'] is EtcInteger
    assert types['not/not'] is None

    d = dict
    t = client
    d1 = d(one="eins",
           two=d(zwei=d(und="drei", a=d(b=d(c='d'))), vier="true"),
           x="y")
    await t._f(d1)

    # basic access, each directory separately
    class xRoot(EtcRoot):
        pass

    types.register(cls=xRoot)

    @xRoot.register("zwei", "und")
    class xUnd(EtcString):
        pass

    w = await t.tree("/two", immediate=False, static=True, types=types)
    w.env.foobar = "Foo Bar"
    assert sorted(dict(
        (a, b) for a, b, c in w.registrations()).items()) == sorted([
            (('.', ), [None, xRoot]),
            (('.', 'something', 'else'), [EtcInteger, None]),
            (('.', '*', 'vierixx'), [EtcInteger, None]),
            (('.', 'what', 'ever'), [EtcFloat, rTwo]),
            (('.', 'two'), [None, rTwo]),
            (('.', 'two', 'die'), [rPreDie, None]),
            (('.', 'two', 'vier'), [EtcBoolean, None]),
            (('zwei', 'und'), [xUnd, None]),
        ]), list(w.registrations())
    assert isinstance(w, xRoot)
    assert w.env.foobar == "Foo Bar"
    assert w.env.barbaz is None
    assert w['zwei'].env is w.env
    assert w['zwei']['a']['b'].env is w.env

    assert w['zwei']['und'] == "drei"
    assert type(w['zwei'].get('und', raw=True)) is xUnd
    assert w['vier'] == "true"
    with pytest.raises(KeyError):
        w['x']
    # basic access, read it all at once
    w2 = await t.tree("/two", immediate=True, static=True, types=types)
    assert w2['zwei']['und'] == "drei"
    assert w['vier'] == "true"
    assert w == w2

    # basic access, read it on demand
    w5 = await t.tree("/two", immediate=None, types=types)

    def wx(x):
        assert x.added == {'und', 'a'}
        x.test_called = 1

    mx = w5['zwei'].add_monitor(wx)
    assert isinstance(w5['zwei']['und'], EtcAwaiter)
    assert (await w5['zwei']['und']).value == "drei"
    assert w5['vier'] == "true"
    await w5['zwei'].force_updated()
    assert w5['zwei'].test_called

    # use typed subtrees
    w4 = await t.tree((), types=types)
    await w4.set('two', d(sechs="sieben"))
    w3 = await t.tree("/", static=True, types=types)
    assert w3['two']['vier'] is True
    assert w3['two']['sechs'] == "sieben"
    ##assert not w3['two'] == w2
    # which are different, but not because of the tree types
    assert not w3 is w4
    assert w3 == w4

    # check basic node iterators
    res = set()
    for v in w3['two']['zwei'].values():
        assert not isinstance(v, EtcValue)
        if not isinstance(v, EtcDir):
            res.add(v)
    assert res == {"drei"}

    res = set()
    for k in w3['two'].keys():
        res.add(k)
    assert res == {"zwei", "vier", "sechs"}

    res = set()
    for k, v in w3['two'].items():
        res.add(k)
        assert v == w3['two'][k]
    assert res == {"zwei", "vier", "sechs"}

    # check what happens if an updater dies on us
    await w4['two'].set('hello', 'one')
    await w4['two'].set('die', '42')
    await asyncio.sleep(1.5, loop=loop)
    with pytest.raises(RuntimeError):
        await w4['two'].set('hello', 'two')

    await w.close()
    await w2.close()
    await w3.close()
    await w4.close()
    await w5.close()
Ejemplo n.º 7
0
async def test_update_watch(client, loop):
    """Testing auto-update, both ways"""
    logger.debug("START update_watch")
    d = dict
    types = EtcTypes()
    t = client
    w = await t.tree(("two", ), immediate=False, static=False)
    d1 = d(zwei=d(und="drei", oder={}),
           vier="fünf",
           sechs="sieben",
           acht=d(neun="zehn"))
    await w.update(d1)

    m1, m2 = Mock(), Mock()
    f = asyncio.Future(loop=loop)

    def wake(x):
        f.set_result(x)

    def mx(x):
        s = getattr(x, 'test_step', 0)
        x.test_step = s + 1
        if s == 0:
            assert x.added == {'und', 'oder'}
            assert x.deleted == {'oder'}
        elif s == 1:
            assert x.added == {':zehn'}
            assert not x.deleted
        else:
            assert 0, s
        pass

    i0 = w.add_monitor(wake)
    i1 = w['zwei'].add_monitor(m1)
    ix = w['zwei'].add_monitor(mx)
    i2 = w['zwei'].get('und', raw=True).add_monitor(m2)

    assert w['sechs'] == "sieben"
    acht = w['acht']
    assert acht['neun'] == "zehn"
    d2 = d(two=d(zwei=d(und="mehr"), vier=d(auch="xxy", oder="fünfe")))
    mod = await t._f(d2, delete=True)
    await w.wait(mod=mod, tasks=True)
    assert w['zwei']['und'] == "mehr"
    assert w['vier']['oder'] == "fünfe"
    assert w['vier']['auch'] == "xxy"
    assert "oder" in w['vier']
    assert "oderr" not in w['vier']

    # Directly insert "deep" entries
    await t.client.write(client._extkey('/two/three/four/five/six/seven'),
                         value=None,
                         dir=True)
    mod = (await t.client.write(client._extkey('/two/three/four/fiver'),
                                "what")).modifiedIndex
    await w.wait(mod, tasks=True)
    # and check that they're here
    assert w['three']['four']['fiver'] == "what"
    assert isinstance(w['three']['four']['five']['six']['seven'], EtcDir)

    logger.debug("Waiting for _update 1")
    await f
    assert w['zwei'].test_step == 1
    f = asyncio.Future(loop=loop)
    assert m1.call_count  # may be >1
    assert m2.call_count
    mc1 = m1.call_count
    mc2 = m2.call_count
    w['zwei'].remove_monitor(i1)

    # The ones deleted by _f(…,delete=True) should not be
    with pytest.raises(KeyError):
        w['sechs']
    with pytest.raises(KeyError):
        logger.debug("CHECK acht")
        w['acht']
    # deleting a whole subtree is not yet implemented
    with pytest.raises((etcd.EtcdDirNotEmpty, etcd.EtcdNotFile)):
        del w['vier']
        await w.wait(tasks=True)
    del w['vier']['oder']
    await w.wait(tasks=True)
    w['vier']
    s = w['vier'].get('auch', raw=True)._cseq
    with pytest.raises(KeyError):
        w['vier']['oder']
    m = await w['vier'].get('auch', raw=True).delete()
    await w.wait(m, tasks=True)
    with pytest.raises(KeyError):
        w['vier']['auch']

    # Now test that adding a node does the right thing
    m = await w['vier'].set('auch', "ja2")
    logger.debug("Set :zehn")
    w['zwei'][':zehn'] = d(zwanzig=30, vierzig=d(fuenfzig=60))
    w['zwei']['und'] = "weniger"
    logger.debug("WAIT FOR ME")
    await w['zwei'].wait(m, tasks=True)
    assert s != w['vier'].get('auch', raw=True)._cseq

    from etcd_tree import client as rclient
    from .util import cfgpath
    tt = await rclient(cfgpath, loop=loop)
    w1 = await tt.tree("/two", immediate=True, types=types)
    assert w is not w1
    assert w == w1
    # wx = await tt.tree("/two", immediate=True)
    # assert wx is w1 ## no caching
    w2 = await t.tree("/two", static=True)
    assert w1 is not w2
    assert w1['zwei']['und'] == "weniger"
    assert w1['zwei'].get('und') == "weniger"
    assert w1['zwei'].get('und', raw=True).value == "weniger"
    assert w1['zwei'].get('und', 'nix') == "weniger"
    assert w1['zwei'].get('und', 'nix', raw=True).value == "weniger"
    assert w1['zwei'].get('huhuhu', 'nixi') == "nixi"
    assert w1['zwei'].get('huhuhu', 'nixo', raw=True) == "nixo"
    with pytest.raises(KeyError):
        w1['zwei'].get('huhuhu')
    with pytest.raises(KeyError):
        w1['zwei'].get('huhuhu', raw=True)
    assert w2['zwei']['und'] == "weniger"
    assert w1['zwei'][':zehn']['zwanzig'] == "30"
    assert w2['zwei'][':zehn']['zwanzig'] == "30"
    assert w1['vier']['auch'] == "ja2"
    assert w2['vier']['auch'] == "ja2"
    w1['zwei'] = d(und='noch weniger')
    await w1.wait(tasks=True)
    assert w1['zwei']['und'] == "noch weniger"
    assert w1['zwei'].get('und') == "noch weniger"

    logger.debug("Waiting for _update 2")
    await f
    assert w['zwei'].test_step == 2
    assert m1.call_count == mc1
    assert m2.call_count == mc2 + 1

    # three ways to skin a cat
    del i0
    # w['zwei'].remove_monitor(i1) ## happened above
    w['zwei'].remove_monitor(ix)
    i2.cancel()
    assert not w._later_mon
    assert not w['zwei']._later_mon
    assert not w['zwei'].get('und', raw=True)._later_mon

    types.register("**", "new_a", cls=IntObj)
    types.register(("**", "new_b"), cls=EtcInteger)
    mod = await t._f(d2, delete=True)
    await w1.wait(mod, tasks=True)
    w1['vier']['auch'] = "nein"
    #assert w1.vier.auch == "ja" ## should be, but too dependent on timing
    w1['vier']['new_a'] = 4242
    await w1.wait(tasks=True)
    assert w1['vier']['auch'] == "nein"
    with pytest.raises(KeyError):
        assert w1['vier']['dud']
    assert w1['vier']['new_a'].value == 4242

    with pytest.raises(ValueError):
        await w1['vier'].set('new_a', "x123", ext=True)
    await w1['vier'].set('new_a', "123", ext=True)
    assert w1['vier']['new_a'].value == 123

    d1 = d(two=d(vier=d(a="b", c="d")))
    mod = await t._f(d1)
    await w1.wait(mod, tasks=True)
    assert w1['vier']['a'] == "b"
    with pytest.raises(KeyError):
        w1['vier']['new_b']

    d1 = d(two=d(vier=d(c="x", d="y", new_b=123)))
    mod = await t._f(d1)
    await w1.wait(mod, tasks=True)
    assert w1['vier']['c'] == "x"
    assert w1['vier']['d'] == "y"
    assert w1['vier']['new_b'] == 123
    await w.wait(mod, tasks=True)

    assert len(w['vier']) == 7, list(w['vier'])
    s = set(w['vier'])
    assert 'a' in s
    assert 'auch' in s
    assert 'auck' not in s

    # now delete the thing
    await w['vier'].delete('a')
    await w['vier'].delete('auch')
    await w['vier'].delete('oder')
    await w['vier'].delete('c')
    await w['vier'].delete('d')
    await w['vier'].delete('new_a')
    await w['vier'].delete('new_b')
    m = await w.delete('vier', recursive=False)
    await w.wait(m, tasks=True)
    with pytest.raises(KeyError):
        w['vier']
    with pytest.raises(RuntimeError):
        await w.delete()

    assert w.running
    assert not w.stopped.done()
    await t.delete("/two", recursive=True)
    await asyncio.sleep(0.3, loop=loop)
    assert not w.running
    assert w.stopped.done()

    await w.close()
    await w1.close()
    await w2.close()
Ejemplo n.º 8
0
async def test_basic_watch(client,loop):
    """Watches which don't actually watch"""
    # object type registration
    types = EtcTypes()
    twotypes = EtcTypes()
    @twotypes.register()
    class rTwo(EtcDir):
        pass
    class rDie(EtcValue):
        def has_update(self):
            raise RuntimeError("RIP")
    @twotypes.register("die")
    class rPreDie(EtcValue):
        @classmethod
        async def this_obj(cls,recursive=None,**kw):
            return rDie(**kw)
    # reg funcion shall return the right thing
    types.step('two',dest=twotypes)
    assert types[('two','die')] is rPreDie
    assert types.lookup(('two','die'),dir=False) is rPreDie
    assert types.lookup('two','die',dir=False) is rPreDie
    assert types.lookup('two/die',dir=False) is rPreDie
    assert types.lookup('two',dir=True,raw=True).lookup('die',dir=False) is rPreDie
    assert types.lookup('two/die',dir=False,raw=True).lookup(dir=False) is rPreDie
    i = types.register("two","vier", cls=EtcInteger)
    assert i is EtcInteger
    i = types.register("*/vierixx")(EtcInteger)
    assert i is EtcInteger
    types['what/ever'] = EtcFloat
    assert types.lookup('what','ever', dir=False) is EtcFloat
    assert types['what/ever'] is EtcFloat
    with pytest.raises(AssertionError):
        types['/what/ever']
    with pytest.raises(AssertionError):
        types['what/ever/']
    with pytest.raises(AssertionError):
        types['what//ever']
    types['something/else'] = EtcInteger
    assert types['two/vier'] is EtcInteger
    assert types['something/else'] is EtcInteger
    assert types['not/not'] is None

    d=dict
    t = client
    d1=d(one="eins",two=d(zwei=d(und="drei",a=d(b=d(c='d'))),vier="5"),x="y")
    await t._f(d1)
    # basic access, each directory separately
    class xRoot(EtcRoot):
        pass
    types.register(cls=xRoot)
    @xRoot.register("zwei","und")
    class xUnd(EtcString):
        pass
    w = await t.tree("/two", immediate=False, static=True, types=types)
    w.env.foobar="Foo Bar"
    assert isinstance(w,xRoot)
    assert w.env.foobar == "Foo Bar"
    assert w.env.barbaz is None
    assert w['zwei'].env is w.env
    assert w['zwei']['a']['b'].env is w.env

    assert w['zwei']['und'] == "drei"
    assert type(w['zwei']._get('und')) is xUnd
    assert w['vier'] == "5"
    with pytest.raises(KeyError):
        w['x']
    # basic access, read it all at once
    w2 = await t.tree("/two", immediate=True, static=True, types=types)
    assert w2['zwei']['und'] == "drei"
    assert w['vier'] == "5"
    assert w == w2

    # basic access, read it on demand
    w5 = await t.tree("/two", immediate=None, types=types)
    def wx(x):
        assert x.added == {'und','a'}
        x.test_called = 1
    mx = w5['zwei'].add_monitor(wx)
    assert isinstance(w5['zwei']['und'],EtcAwaiter)
    assert (await w5['zwei']['und']).value == "drei"
    assert w5['vier'] == "5"
    w5['zwei'].force_updated()
    assert w5['zwei'].test_called

    # use typed subtrees
    w4 = await t.tree((), types=types)
    await w4.set('two',d(sechs="sieben"))
    w3 = await t.tree("/", static=True, types=types)
    assert w3['two']['vier'] == 5
    assert w3['two']['sechs'] == "sieben"
    assert not w3['two'] == w2
    # which are different, but not because of the tree types
    assert not w3 is w4
    assert w3 == w4

    # check basic node iterators
    res=set()
    for v in w3['two']['zwei'].values():
        assert not isinstance(v,EtcValue)
        if not isinstance(v,EtcDir):
            res.add(v)
    assert res == {"drei"}

    res=set()
    for k in w3['two'].keys():
        res.add(k)
    assert res == {"zwei","vier","sechs"}

    res=set()
    for k,v in w3['two'].items():
        res.add(k)
        assert v == w3['two'][k]
    assert res == {"zwei","vier","sechs"}

    # check what happens if an updater dies on us
    await w4['two'].set('hello','one')
    await w4['two'].set('die',42)
    await asyncio.sleep(1.5, loop=loop)
    with pytest.raises(WatchStopped):
        await w4['two'].set('hello','two')
    
    await w2.close()
    await w3.close()
    await w4.close()
Ejemplo n.º 9
0
async def test_update_watch(client, loop):
    """Testing auto-update, both ways"""
    logger.debug("START update_watch")
    d=dict
    types = EtcTypes()
    t = client
    w = await t.tree(("two",), immediate=False, static=False)
    d1=d(zwei=d(und="drei",oder={}),vier="fünf",sechs="sieben",acht=d(neun="zehn"))
    await w.update(d1)

    m1,m2 = Mock(),Mock()
    f = asyncio.Future(loop=loop)
    def wake(x):
        f.set_result(x)
    def mx(x):
        s = getattr(x,'test_step',0)
        x.test_step = s+1
        if s == 0:
            assert x.added == {'und','oder'}
            assert x.deleted == {'oder'}
        elif s == 1:
            assert x.added == {'zehn'}
            assert not x.deleted
        else:
            assert 0,s
        pass
    i0 = w.add_monitor(wake)
    i1 = w['zwei'].add_monitor(m1)
    ix = w['zwei'].add_monitor(mx)
    i2 = w['zwei']._get('und').add_monitor(m2)

    assert w['sechs'] == "sieben"
    acht = w['acht']
    assert acht['neun'] =="zehn"
    d2=d(two=d(zwei=d(und="mehr"),vier=d(auch="xxy",oder="fünfe")))
    mod = await t._f(d2,delete=True)
    await w.wait(mod=mod)
    assert w['zwei']['und']=="mehr"
    assert w['vier']['oder']=="fünfe"
    assert w['vier']['auch']=="xxy"
    assert "oder" in w['vier']
    assert "oderr" not in w['vier']

    # Directly insert "deep" entries
    await t.client.write(client._extkey('/two/three/four/five/six/seven'),value=None,dir=True)
    mod = (await t.client.write(client._extkey('/two/three/four/fiver'),"what")).modifiedIndex
    await w.wait(mod)
    # and check that they're here
    assert w['three']['four']['fiver'] == "what"
    assert isinstance(w['three']['four']['five']['six']['seven'], EtcDir)

    logger.debug("Waiting for _update 1")
    await f
    f = asyncio.Future(loop=loop)
    assert m1.call_count # may be >1
    assert m2.call_count
    mc1 = m1.call_count
    mc2 = m2.call_count
    w['zwei'].remove_monitor(i1)

    # The ones deleted by _f(…,delete=True) should not be
    with pytest.raises(KeyError):
        w['sechs']
    with pytest.raises(KeyError):
        logger.debug("CHECK acht")
        w['acht']
    # deleting a whole subtree is not yet implemented
    with pytest.raises((etcd.EtcdDirNotEmpty,etcd.EtcdNotFile)):
        del w['vier']
        await w.wait()
    del w['vier']['oder']
    await w.wait()
    w['vier']
    s = w['vier']._get('auch')._cseq
    with pytest.raises(KeyError):
        w['vier']['oder']
    m = await w['vier']._get('auch').delete()
    await w.wait(m)
    with pytest.raises(KeyError):
        w['vier']['auch']

    # Now test that adding a node does the right thing
    m = await w['vier'].set('auch',"ja2")
    w['zwei']['zehn'] = d(zwanzig=30,vierzig=d(fuenfzig=60))
    w['zwei']['und'] = "weniger"
    logger.debug("WAIT FOR ME")
    await w['zwei'].wait(m)
    assert s != w['vier']._get('auch')._cseq

    from etcd_tree import client as rclient
    from .util import cfgpath
    tt = await rclient(cfgpath, loop=loop)
    w1 = await tt.tree("/two", immediate=True, types=types)
    assert w is not w1
    assert w == w1
    # wx = await tt.tree("/two", immediate=True)
    # assert wx is w1 ## no caching
    w2 = await t.tree("/two", static=True)
    assert w1 is not w2
    assert w1['zwei']['und'] == "weniger"
    assert w1['zwei'].get('und') == "weniger"
    assert w1['zwei']._get('und').value == "weniger"
    assert w1['zwei'].get('und','nix') == "weniger"
    assert w1['zwei']._get('und','nix').value == "weniger"
    assert w1['zwei'].get('huhuhu','nixi') == "nixi"
    assert w1['zwei']._get('huhuhu','nixo') == "nixo"
    with pytest.raises(KeyError):
        w1['zwei'].get('huhuhu')
    with pytest.raises(KeyError):
        w1['zwei']._get('huhuhu')
    assert w2['zwei']['und'] == "weniger"
    assert w1['zwei']['zehn']['zwanzig'] == "30"
    assert w2['zwei']['zehn']['zwanzig'] == "30"
    assert w1['vier']['auch'] == "ja2"
    assert w2['vier']['auch'] == "ja2"
    w1['zwei']=d(und='noch weniger')
    await w1.wait()
    assert w1['zwei']['und'] == "noch weniger"
    assert w1['zwei'].get('und') == "noch weniger"

    logger.debug("Waiting for _update 2")
    await f
    assert m1.call_count == mc1
    assert m2.call_count == mc2+1

    # three ways to skin a cat
    del i0
    # w['zwei'].remove_monitor(i1) ## happened above
    w['zwei'].remove_monitor(ix)
    i2.cancel()
    assert not w._later_mon
    assert not w['zwei']._later_mon
    assert not w['zwei']._get('und')._later_mon

    types.register("**","new_a", cls=IntObj)
    types.register(("**","new_b"), cls=EtcInteger)
    mod = await t._f(d2,delete=True)
    await w1.wait(mod)
    w1['vier']['auch'] = "nein"
    #assert w1.vier.auch == "ja" ## should be, but too dependent on timing
    w1['vier']['new_a'] = 4242
    await w1.wait()
    assert w1['vier']['auch'] == "nein"
    with pytest.raises(KeyError):
        assert w1['vier']['dud']
    assert w1['vier']['new_a'].value == 4242

    d1=d(two=d(vier=d(a="b",c="d")))
    mod = await t._f(d1)
    await w1.wait(mod)
    assert w1['vier']['a'] == "b"
    with pytest.raises(KeyError):
        w1['vier']['new_b']

    d1=d(two=d(vier=d(c="x",d="y",new_b=123)))
    mod = await t._f(d1)
    await w1.wait(mod)
    assert w1['vier']['c'] == "x"
    assert w1['vier']['d'] == "y"
    assert w1['vier']['new_b'] == 123
    await w.wait(mod)

    assert len(w['vier']) == 7,list(w['vier'])
    s=set(w['vier'])
    assert 'a' in s
    assert 'auch' in s
    assert 'auck' not in s

    # now delete the thing
    await w['vier'].delete('a')
    await w['vier'].delete('auch')
    await w['vier'].delete('oder')
    await w['vier'].delete('c')
    await w['vier'].delete('d')
    await w['vier'].delete('new_a')
    await w['vier'].delete('new_b')
    m = await w.delete('vier',recursive=False)
    await w.wait(m)
    with pytest.raises(KeyError):
        w['vier']
    with pytest.raises(RuntimeError):
        await w.delete()

    assert w.running
    assert not w.stopped.done()
    await t.delete("/two",recursive=True)
    await asyncio.sleep(0.3,loop=loop)
    assert not w.running
    assert w.stopped.done()

    await w.close()
    await w1.close()
    await w2.close()