11# -*- coding: utf-8 -*-
22
33from ..syntax import macros , test , test_raises # noqa: F401
4- from .fixtures import session , testset
4+ from .fixtures import session , testset , returns_normally
55
66import threading
77from queue import Queue
8+ import gc
89
910from ..dynassign import dyn , make_dynvar
11+ from ..misc import slurp
1012
1113def runtests ():
14+ # various parts of unpythonic use dynvars, so get what's there before we insert anything for testing
15+ implicits = [k for k in dyn ]
16+ def noimplicits (kvs ):
17+ return tuple (sorted ((k , v ) for k , v in kvs if k not in implicits ))
18+ def noimplicits_keys (keys ):
19+ return tuple (sorted (k for k in keys if k not in implicits ))
20+
21+ # some test data
22+ D = {"a" : 1 , "b" : 2 }
23+ D2 = {"c" : 3 , "d" : 4 }
24+
1225 def f ():
1326 test [dyn .a == 2 ] # no a in lexical scope
1427
15- def runtest ():
28+ def basictests ():
1629 with testset ("basic usage" ):
1730 with dyn .let (a = 2 , b = "foo" ):
1831 test [dyn .a == 2 ]
@@ -46,14 +59,9 @@ def threadtest(q):
4659 t2 .join ()
4760 err = comm .get ()
4861 test [err is not None ]
49- runtest ()
62+ basictests ()
5063
5164 with testset ("syntactic sugar" ):
52- # various parts of unpythonic use dynvars, so get what's there before we insert anything for testing
53- implicits = [k for k in dyn ]
54- def noimplicits (kvs ):
55- return tuple (sorted ((k , v ) for k , v in kvs if k not in implicits ))
56- D = {"a" : 1 , "b" : 2 }
5765 with dyn .let (** D ):
5866 # membership test
5967 test ["a" in dyn ]
@@ -66,7 +74,6 @@ def noimplicits(kvs):
6674 test [noimplicits (dyn .items ()) == ()]
6775
6876 with testset ("update existing bindings" ):
69- D2 = {"c" : 3 , "d" : 4 }
7077 with dyn .let (** D ):
7178 with dyn .let (** D2 ):
7279 test [noimplicits (dyn .items ()) == (("a" , 1 ), ("b" , 2 ), ("c" , 3 ), ("d" , 4 ))]
@@ -76,7 +83,12 @@ def noimplicits(kvs):
7683 test [noimplicits (dyn .items ()) == (("a" , 42 ), ("b" , 2 ), ("c" , 23 ), ("d" , 4 ))]
7784 with test_raises (AttributeError , "should not be able to update unbound dynamic variable" ):
7885 dyn .e = 5
79- test [noimplicits (dyn .items ()) == (("a" , 42 ), ("b" , 2 ))]
86+
87+ # subscript notation also works for updating
88+ with test :
89+ dyn ["a" ] = 9001
90+ test [dyn .a == 9001 ]
91+ test [noimplicits (dyn .items ()) == (("a" , 9001 ), ("b" , 2 ))]
8092 test [noimplicits (dyn .items ()) == ()]
8193
8294 with testset ("update in presence of name shadowing" ):
@@ -100,8 +112,43 @@ def noimplicits(kvs):
100112 test [noimplicits (dyn .items ()) == (("a" , 10 ), ("b" , 20 ))]
101113 test [noimplicits (dyn .items ()) == ()]
102114
115+ with testset ("mass update with multithreading" ):
116+ comm = Queue ()
117+ def worker ():
118+ # test[] itself is thread-safe, but the worker threads don't have a
119+ # surrounding testset to catch failures, since we don't want to print in them.
120+ try :
121+ local_successes = 0
122+ with dyn .let (** D ):
123+ with dyn .let (** D2 ):
124+ if noimplicits (dyn .items ()) == (("a" , 1 ), ("b" , 2 ), ("c" , 3 ), ("d" , 4 )):
125+ local_successes += 1
126+ dyn .update (a = - 1 , b = - 2 , c = - 3 , d = - 4 )
127+ if noimplicits (dyn .items ()) == (("a" , - 1 ), ("b" , - 2 ), ("c" , - 3 ), ("d" , - 4 )):
128+ local_successes += 1
129+ if noimplicits (dyn .items ()) == (("a" , - 1 ), ("b" , - 2 )):
130+ local_successes += 1
131+ dyn .update (a = 10 , b = 20 )
132+ if noimplicits (dyn .items ()) == (("a" , 10 ), ("b" , 20 )):
133+ local_successes += 1
134+ if noimplicits (dyn .items ()) == ():
135+ local_successes += 1
136+ if local_successes == 5 :
137+ comm .put (1 )
138+ except Exception : # pragma: no cover, only happens if the test fails.
139+ pass
140+ n = 100
141+ threads = [threading .Thread (target = worker ) for _ in range (n )]
142+ for t in threads :
143+ t .start ()
144+ for t in threads :
145+ t .join ()
146+ successes = sum (slurp (comm ))
147+ test [successes == n ]
148+
103149 with testset ("make_dynvar (default values)" ):
104150 make_dynvar (im_always_there = True )
151+ test [dyn .im_always_there is True ]
105152 with dyn .let (a = 1 , b = 2 ):
106153 test [noimplicits (dyn .items ()) == (("a" , 1 ), ("b" , 2 ),
107154 ("im_always_there" , True ))]
@@ -114,6 +161,8 @@ def noimplicits(kvs):
114161 with dyn .let (a = 1 , b = 2 ):
115162 test [noimplicits (view .items ()) == (("a" , 1 ), ("b" , 2 ),
116163 ("im_always_there" , True ))]
164+ del view
165+ gc .collect ()
117166
118167 # as does dyn.items() (it's an abbreviation for dyn.asdict().items())
119168 items = dyn .items ()
@@ -122,6 +171,22 @@ def noimplicits(kvs):
122171 test [noimplicits (items ) == (("a" , 1 ), ("b" , 2 ),
123172 ("im_always_there" , True ))]
124173
174+ # the rest of the Mapping API
175+ keys = dyn .keys () # live!
176+ with dyn .let (a = 1 , b = 2 ):
177+ test [noimplicits_keys (keys ) == ("a" , "b" , "im_always_there" )]
178+
179+ test [dyn .get ("a" ) == 1 ]
180+ test [dyn .get ("c" ) is None ] # default
181+
182+ d = dict (items )
183+ test [dyn == d ]
184+
185+ # Not much we can do with the output so let's just check these don't crash.
186+ test [returns_normally (dyn .values ())]
187+ test [returns_normally (len (dyn ))]
188+
189+
125190if __name__ == '__main__' : # pragma: no cover
126191 with session (__file__ ):
127192 runtests ()
0 commit comments