Commit MetaInfo

Revision0602b98481f568aadce84b8ff7c3ea581f202378 (tree)
Time2022-11-03 03:56:15
AuthorAntoon Pardon <aej@pard...>
CommiterAntoon Pardon

Log Message

Threaders module, comm2 iterator and copy_defaults correction merged in.

Change Summary

Incremental Difference

diff -r 2f551a155f34 -r 0602b98481f5 cleanall
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cleanall Wed Nov 02 19:56:15 2022 +0100
@@ -0,0 +1,4 @@
1+#!/bin/bash
2+
3+pushd tests > /dev/null; make clean; popd > /dev/null
4+pushd source > /dev/null; make clean; popd > /dev/null
diff -r 2f551a155f34 -r 0602b98481f5 source/decorators.py
--- a/source/decorators.py Sun Jul 24 10:21:49 2022 +0200
+++ b/source/decorators.py Wed Nov 02 19:56:15 2022 +0100
@@ -1,47 +1,58 @@
11
22 from inspect import signature as signature_of, Parameter
33 from functools import wraps
4-from copy import copy
4+from copy import deepcopy
5+
56 default = object()
6-'\nThis value kan be passed as an argument, where the functions provides a default value.\nIf you used the pass_defaults decorator, the function will use its default for that\nparameter\n'
7+'''
8+This value kan be passed as an argument, where the functions provides a default value.
9+If you used the pass_defaults decorator, the function will use its default for that
10+parameter
11+'''
712
813 def copy_defaults(func):
9- '\n\tThis decorator makes that defaults values are copied on a call.\n\t'
10- signature = signature_of(func)
11- parameter_items = list(signature.parameters.items())
14+ '''
15+ This decorator makes that defaults values are copied on a call.
16+ '''
17+ signature = signature_of(func)
18+ parameter_items = list(signature.parameters.items())
1219
13- @wraps(func)
14- def wrapper(*args, **kwds):
15- newargs = list(args)
16- tail_parameters = parameter_items[len(args):]
17- for (name, parameter) in tail_parameters:
18- try:
19- newargs.append(kwds[name])
20- del kwds[name]
21- except KeyError:
22- if (parameter.default is not Parameter.empty):
23- newargs.append(copy(parameter.default))
24- else:
25- break
26- return func(*newargs, **kwds)
27- return wrapper
20+ @wraps(func)
21+ def wrapper(*args, **kwds):
22+ newargs = list(args)
23+ tail_parameters = parameter_items[len(args):]
24+ for name, parameter in tail_parameters:
25+ try:
26+ newargs.append(kwds[name])
27+ del kwds[name]
28+ except KeyError:
29+ if parameter.default is not Parameter.empty:
30+ newargs.append(deepcopy(parameter.default))
31+ else:
32+ #return func(*newargs, **kwds)
33+ break
34+ return func(*newargs, **kwds)
35+
36+ return wrapper
2837
2938 def pass_defaults(func):
30- '\n\tThis decorator allows to pass a specific "default" value, to\n\thave the function use the default value\n\t'
31- signature = signature_of(func)
32- parameter_items = list(signature.parameters.items())
39+ '''
40+ This decorator allows to pass a specific "default" value, to
41+ have the function use the default value
42+ '''
43+ signature = signature_of(func)
44+ parameter_items = list(signature.parameters.items())
3345
34- @wraps(func)
35- def wrapper(*args, **kwds):
36- newargs = []
37- for (arg, item) in zip(args, parameter_items):
38- parameter = item[1]
39- if (arg is not default):
40- newargs.append(arg)
41- elif (parameter.default is not Parameter.empty):
42- newargs.append(parameter.default)
43- else:
44- raise ValueError('"default" used as argument when no default is available')
45- return func(*newargs, **kwds)
46- return wrapper
47-
46+ @wraps(func)
47+ def wrapper(*args, **kwds):
48+ newargs = []
49+ for (arg, item) in zip(args, parameter_items):
50+ parameter = item[1]
51+ if (arg is not default):
52+ newargs.append(arg)
53+ elif (parameter.default is not Parameter.empty):
54+ newargs.append(parameter.default)
55+ else:
56+ raise ValueError('"default" used as argument when no default is available')
57+ return func(*newargs, **kwds)
58+ return wrapper
diff -r 2f551a155f34 -r 0602b98481f5 source/iterators.py
--- a/source/iterators.py Sun Jul 24 10:21:49 2022 +0200
+++ b/source/iterators.py Wed Nov 02 19:56:15 2022 +0100
@@ -1,69 +1,148 @@
1+# Copyright © 2017-2022 Antoon Pardon
2+#
3+# Permission is hereby granted, free of charge, to any person obtaining a
4+# copy of this software and associated documentation files (the "Software"),
5+# to deal in the Software without restriction, including without limitation
6+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+# and/or sell copies of the Software, and to permit persons to whom the
8+# Software is furnished to do so, subject to the following conditions:
9+#
10+# The above copyright notice and this permission notice shall be included
11+# in all copies or substantial portions of the Software.
12+#
13+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19+#
20+'''\
21+This module contains a number of useful iterators.
122
2-"This module contains a number of useful iterators.\n\nThe first family of\niterators are the classes of pipe iterators. These can be strung together\nin a pipe line, just as with the unix '|' operator.\n"
23+The first family of
24+iterators are the classes of pipe iterators. These can be strung together
25+in a pipe line, just as with the unix '|' operator.
26+'''
327 __pdoc__ = {}
428 from functools import partial
529 from itertools import chain, filterfalse
630 _INVALID = object()
731
832 class PipeIterator():
9- "\n\tBase class for constructing iterators that work like a pipe in the\n\tunix shell and work with the '|' operator. The '>>' operator in\n\tcorrespondece with C++ can also be used to get the same effect.\n\n\tThe use is mainly in the ease with which these iterators can be combined\n\n\tThis class can also be used as a decorator to turn a function/generator\n\tinto a pipe iterator.\n\t"
33+ """
34+ Base class for constructing iterators that work like a pipe in the
35+ unix shell and work with the '|' operator. The '>>' operator in
36+ correspondece with C++ can also be used to get the same effect.
1037
11- def __init__(self, function):
12- self.apply = function
38+ The use is mainly in the ease with which these iterators can be combined
1339
14- def _pipe(self, iterator):
15- return self.apply(iterator)
16- __ror__ = _pipe
17- __rrshift__ = _pipe
40+ This class can also be used as a decorator to turn a function/generator
41+ into a pipe iterator.
42+ """
43+
44+ def __init__(self, function):
45+ self.apply = function
46+
47+ def _pipe(self, iterator):
48+ return self.apply(iterator)
49+ __ror__ = _pipe
50+ __rrshift__ = _pipe
51+
52+
1853 Apply = PipeIterator
1954 __pdoc__['Apply'] = False
2055
2156 class Map(PipeIterator):
22- '\n\tThis class produces a pipe iterator that will turn an iterator into a new\n\titerator where the new items are the result of applying the function.\n\n\tdouble = partial(operator.__mul__, 2) \n\t[3, 5, 8] | Map(double) --> 6, 10, 16\n\t'
57+ '''
58+ This class produces a pipe iterator that will turn an iterator into a new
59+ iterator where the new items are the result of applying the function.
2360
24- def __init__(self, function):
25- super().__init__(partial(map, function))
61+ double = partial(operator.__mul__, 2)
62+ [3, 5, 8] | Map(double) --> 6, 10, 16
63+ '''
64+
65+ def __init__(self, function):
66+ super().__init__(partial(map, function))
2667
2768 class SubMap(Map):
28- '\n\tThis class produces a pipe iterator that will turn an iterable of iterables\n\tinto a new iterables of iterables where the new items are the result of applying the function.\n\n\tdouble = partial(operator.__mul__, 2) \n\t[[3, 5, 8], [7, 4], [2]] | SubMap(double) --> [6, 10, 16], [14, 8], [4]\n\t'
69+ '''
70+ This class produces a pipe iterator that will turn an iterable of iterables
71+ into a new iterables of iterables where the new items are the result of applying the function.\n
72+ double = partial(operator.__mul__, 2)
73+ [[3, 5, 8], [7, 4], [2]] | SubMap(double) --> [6, 10, 16], [14, 8], [4]
74+ '''
2975
30- def __init__(self, function):
31- super().__init__(partial(map, function))
76+ def __init__(self, function):
77+ super().__init__(partial(map, function))
3278 concat = Apply(chain.from_iterable)
3379
3480 class Select(PipeIterator):
35- '\n\tThis class produces a pipe iterator that only lets items through that are\n\tselected through the condition.\n\n\t[21, 13, 8, 5, 3, 2] >> Select(is_odd) --> 21 13 5 3\n\t'
81+ '''
82+ This class produces a pipe iterator that only lets items through that are
83+ selected through the condition.\n
84+ [21, 13, 8, 5, 3, 2] >> Select(is_odd) --> 21 13 5 3
85+ '''
3686
37- def __init__(self, condition):
38- super().__init__(partial(filter, condition))
87+ def __init__(self, condition):
88+ super().__init__(partial(filter, condition))
3989
4090 class Discard(PipeIterator):
41- '\n\tThis class produces a pipe iterator that removes items that are\n\tfiltered out through the condition.\n\n\t[21, 13, 8, 5, 3, 2] >> Discard(is_odd) --> 8 2\n\t'
91+ '''
92+ This class produces a pipe iterator that removes items that are
93+ filtered out through the condition.\n
94+ [21, 13, 8, 5, 3, 2] >> Discard(is_odd) --> 8 2
95+ '''
4296
43- def __init__(self, condition):
44- super().__init__(partial(filterfalse, condition))
97+ def __init__(self, condition):
98+ super().__init__(partial(filterfalse, condition))
4599
46100 class Peeker():
47- '\n\tThis class allows to peek ahead in an iterator\n\t'
48-
49- def __init__(self, iterable):
50- self.iterator = iter(iterable)
51- self.next = _INVALID
52-
53- def __iter__(self):
54- return self
101+ '''
102+ This class allows to peek ahead in an iterator
103+ '''
55104
56- def __next__(self):
57- if (self.next is _INVALID):
58- next_item = next(self.iterator)
59- else:
60- next_item = self.next
61- self.next = _INVALID
62- return next_item
105+ def __init__(self, iterable):
106+ self.iterator = iter(iterable)
107+ self.next = _INVALID
63108
64- def peek(self):
65- '\n\t\tThis method looks one item ahead in the iterator\n\t\t'
66- if (self.next is _INVALID):
67- self.next = next(self.iterator)
68- return self.next
109+ def __iter__(self):
110+ return self
69111
112+ def __next__(self):
113+ if (self.next is _INVALID):
114+ next_item = next(self.iterator)
115+ else:
116+ next_item = self.next
117+ self.next = _INVALID
118+ return next_item
119+
120+ def peek(self):
121+ """
122+ This method looks one item ahead in the iterator
123+ """
124+ if self.next is _INVALID:
125+ self.next = next(self.iterator)
126+ return self.next
127+
128+def comm2(it1, it2):
129+ """
130+ This function behave a bit like the unix command comm.
131+ It takes two sorted iterators and produces an iterator of tuples.
132+ """
133+ top = 1,None
134+ it1 = ((0, item) for item in it1)
135+ it2 = ((0, item) for item in it2)
136+ item1 = next(it1, top)
137+ item2 = next(it2, top)
138+ while (item1, item2) != (top, top):
139+ if item1 < item2:
140+ yield item1[1], None
141+ item1 = next(it1, top)
142+ elif item1 > item2:
143+ yield None, item2[1]
144+ item2 = next(it2, top)
145+ else:
146+ yield item1[1], item2[1]
147+ item1 = next(it1, top)
148+ item2 = next(it2, top)
diff -r 2f551a155f34 -r 0602b98481f5 source/release.cfg
--- a/source/release.cfg Sun Jul 24 10:21:49 2022 +0200
+++ b/source/release.cfg Wed Nov 02 19:56:15 2022 +0100
@@ -1,3 +1,3 @@
1-version = '0.01.12a-untyped'
2-modified = '2022-07-23 @ 17:16:36'
1+version = '0.01.14a-untyped'
2+modified = '2022-10-25 @ 19:07:31'
33 installed = '*********************'
diff -r 2f551a155f34 -r 0602b98481f5 source/threaders.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/source/threaders.py Wed Nov 02 19:56:15 2022 +0100
@@ -0,0 +1,71 @@
1+"""
2+ This module provides an alternative for the threading module in
3+ the standard library
4+"""
5+from _thread import start_new_thread, get_ident, allocate_lock
6+from typing import Callable, Set, Any
7+from itertools import count
8+
9+from enum import Enum
10+
11+class TaskState (Enum): # pylint: disable=missing-class-docstring
12+ Ready = 1
13+ Active = 2
14+ Done = 3
15+
16+_TaskCounter = count()
17+
18+class Task:
19+ """
20+ This class initiates a Task that can be started later for
21+ assynchronized work
22+ """
23+ # pylint: disable=too-many-instance-attributes
24+ def __init__(self, func: Callable, *args, **kwds):
25+ self.func = func
26+ self.args = args
27+ self.kwds = kwds
28+ self.state = TaskState.Ready
29+ self.tid = None
30+ self.iid = next(_TaskCounter)
31+ self.held_resources : Set[Any] = set()
32+ self.joiner = None
33+
34+ def start(self): # pylint: disable=missing-function-docstring
35+ self.state = TaskState.Active
36+ self.joiner = allocate_lock()
37+ self.joiner.acquire()
38+ start_new_thread(self._startup, ())
39+
40+ def throw(self, exception, *args): # pylint: disable=missing-function-docstring
41+ pass
42+
43+ def join(self):
44+ """
45+ This function allows one task to synchronise with an other.
46+ The task calling this method, will be blocked until the
47+ task whose join method was called is terminated
48+ """
49+ if self.state is TaskState.Ready:
50+ raise RuntimeError("Can't join a Task that hasn't started yet")
51+ if self.state is TaskState.Done:
52+ return
53+ joiner = self.joiner
54+ joiner.acquire()
55+ joiner.release()
56+
57+ def _startup(self):
58+ # pylint: disable=broad-except
59+ try:
60+ self.tid = get_ident()
61+ self.func(*self.args, **self.kwds)
62+ except Exception:
63+ print("Something went wrong")
64+ finally:
65+ self.state = TaskState.Done
66+ self.tid = None
67+ self.joiner.release()
68+ for resource in self.held_resources:
69+ for attrname in ['close', 'release']:
70+ if (release := getattr(resource, attrname, None)) is not None:
71+ release()
diff -r 2f551a155f34 -r 0602b98481f5 tests/iterators.py
--- a/tests/iterators.py Sun Jul 24 10:21:49 2022 +0200
+++ b/tests/iterators.py Wed Nov 02 19:56:15 2022 +0100
@@ -1,137 +1,192 @@
11
2-from unittest import TestCase, main as unitmain
3-from random import shuffle
2+# pylint: disable=missing-function-docstring,invalid-name,missing-module-docstring
3+
4+from unittest import TestCase, main as unitmain #, skip
5+from random import shuffle, randint
46 from itertools import product
7+from operator import itemgetter
8+
9+#from typing import List
10+
11+# pylint: disable=unused-import
512 from AGPlib.iterators import PipeIterator, Apply, Map, concat, SubMap, Select, Discard, Peeker
13+from AGPlib.iterators import comm2
14+# pylint: enable=unused-import
15+
616 SIZE_LIMIT = 101
717
818 def shuffled_range_list(*args):
9- step = 1
10- start = 0
11- if (len(args) == 1):
12- stop = args[0]
13- elif (len(args) == 2):
14- (start, stop) = args
15- else:
16- (start, stop, step) = args
17- lst = list(range(start, stop, step))
18- shuffle(lst)
19- return lst
19+ step = 1
20+ start = 0
21+ if (len(args) == 1):
22+ stop = args[0]
23+ elif (len(args) == 2):
24+ (start, stop) = args
25+ else:
26+ (start, stop, step) = args
27+ lst = list(range(start, stop, step))
28+ shuffle(lst)
29+ return lst
2030
2131 def cube(nr):
22- return ((nr * nr) * nr)
32+ return ((nr * nr) * nr)
2333
2434 def square(nr):
25- return (nr * nr)
35+ return (nr * nr)
2636
2737 def is_odd(nr):
28- return bool((nr % 2))
38+ return bool((nr % 2))
2939
3040 def is_larger_than_50(nr):
31- return (nr > 50)
41+ return (nr > 50)
3242
3343 def collatz(nr):
34- return (((3 * nr) + 1) if is_odd(nr) else (nr // 2))
44+ return (((3 * nr) + 1) if is_odd(nr) else (nr // 2))
3545
3646 def concater(iterable):
37- for sub_iterable in iterable:
38- (yield from sub_iterable)
47+ for sub_iterable in iterable:
48+ yield from sub_iterable
49+
50+def rndrange(*args):
51+ for i in range(*args):
52+ if randint(0, 1) == 1:
53+ yield i
54+
55+# pylint: disable=no-self-argument
56+
3957 List = Apply(list)
4058
4159 class PipeCase(TestCase):
42- '\n\t\tclass for testing the PipeIterators\n\t'
43-
44- def test_apply(s):
45- for size in range(SIZE_LIMIT):
46- source = shuffled_range_list(size)
47- for function in (list, sum):
48- Function = Apply(function)
49- result1 = function(source)
50- result2 = (source | Function)
51- result3 = (source >> Function)
52- s.assertEqual(result1, result2)
53- s.assertEqual(result1, result3)
54-
55- def test_map(s):
56- for size in range(SIZE_LIMIT):
57- source = shuffled_range_list(size)
58- for function in (square, cube, is_odd, collatz):
59- result0 = list(map(function, source))
60- result1 = ((source | Map(function)) | Apply(list))
61- s.assertEqual(result0, result1)
62- result2 = ((source >> Map(function)) >> Apply(list))
63- s.assertEqual(result0, result2)
60+ '''
61+ class for testing the PipeIterators
62+ '''
6463
65- def test_concat(s):
66- for (list_size, sub_size) in product(range(0, SIZE_LIMIT, 5), range(0, SIZE_LIMIT, 4)):
67- source_list = [shuffled_range_list(sub_size) for _ in range(list_size)]
68- result0 = list(concater(source_list))
69- result1 = ((source_list | concat) | List)
70- s.assertEqual(result0, result1)
71- result2 = ((source_list >> concat) >> List)
72- s.assertEqual(result0, result2)
73-
74- def test_submap(s):
75- for (list_size, sub_size) in product(range(0, SIZE_LIMIT, 5), range(0, SIZE_LIMIT, 4)):
76- source_list = [shuffled_range_list(sub_size) for _ in range(list_size)]
77- for function in (square, cube, is_odd, collatz):
78- result0 = [list(map(function, lst)) for lst in source_list]
79- result1 = (((source_list | SubMap(function)) | Map(list)) | List)
80- result2 = (((source_list >> SubMap(function)) >> Map(list)) >> List)
81- s.assertEqual(result0, result1)
82- s.assertEqual(result0, result2)
64+ def test_apply(s):
65+ for size in range(SIZE_LIMIT):
66+ source = shuffled_range_list(size)
67+ for function in (list, sum):
68+ Function = Apply(function)
69+ result1 = function(source)
70+ result2 = (source | Function)
71+ result3 = (source >> Function)
72+ s.assertEqual(result1, result2)
73+ s.assertEqual(result1, result3)
8374
84- def test_select(s):
85- for size in range(SIZE_LIMIT):
86- source = shuffled_range_list(size)
87- for condition in [is_odd, is_larger_than_50]:
88- result0 = [nr for nr in source if condition(nr)]
89- result1 = ((source | Select(condition)) | List)
90- result2 = ((source >> Select(condition)) >> List)
91- s.assertEqual(result0, result1)
92- s.assertEqual(result0, result2)
75+ def test_map(s):
76+ for size in range(SIZE_LIMIT):
77+ source = shuffled_range_list(size)
78+ for function in (square, cube, is_odd, collatz):
79+ result0 = list(map(function, source))
80+ result1 = ((source | Map(function)) | Apply(list))
81+ s.assertEqual(result0, result1)
82+ result2 = ((source >> Map(function)) >> Apply(list))
83+ s.assertEqual(result0, result2)
9384
94- def test_discard(s):
95- for size in range(SIZE_LIMIT):
96- source = shuffled_range_list(size)
97- for condition in [is_odd, is_larger_than_50]:
98- result0 = [nr for nr in source if (not condition(nr))]
99- result1 = ((source | Discard(condition)) | List)
100- result2 = ((source >> Discard(condition)) >> List)
101- s.assertEqual(result0, result1)
102- s.assertEqual(result0, result2)
85+ def test_concat(s):
86+ for (list_size, sub_size) in product(range(0, SIZE_LIMIT, 5), range(0, SIZE_LIMIT, 4)):
87+ source_list = [shuffled_range_list(sub_size) for _ in range(list_size)]
88+ result0 = list(concater(source_list))
89+ result1 = ((source_list | concat) | List)
90+ s.assertEqual(result0, result1)
91+ result2 = ((source_list >> concat) >> List)
92+ s.assertEqual(result0, result2)
93+
94+ def test_submap(s):
95+ for (list_size, sub_size) in product(range(0, SIZE_LIMIT, 5), range(0, SIZE_LIMIT, 4)):
96+ source_list = [shuffled_range_list(sub_size) for _ in range(list_size)]
97+ for function in (square, cube, is_odd, collatz):
98+ result0 = [list(map(function, lst)) for lst in source_list]
99+ result1 = (((source_list | SubMap(function)) | Map(list)) | List)
100+ result2 = (((source_list >> SubMap(function)) >> Map(list)) >> List)
101+ s.assertEqual(result0, result1)
102+ s.assertEqual(result0, result2)
103+
104+ def test_select(s):
105+ for size in range(SIZE_LIMIT):
106+ source = shuffled_range_list(size)
107+ for condition in [is_odd, is_larger_than_50]:
108+ result0 = [nr for nr in source if condition(nr)]
109+ result1 = ((source | Select(condition)) | List)
110+ result2 = ((source >> Select(condition)) >> List)
111+ s.assertEqual(result0, result1)
112+ s.assertEqual(result0, result2)
113+
114+ def test_discard(s):
115+ for size in range(SIZE_LIMIT):
116+ source = shuffled_range_list(size)
117+ for condition in [is_odd, is_larger_than_50]:
118+ result0 = [nr for nr in source if (not condition(nr))]
119+ result1 = ((source | Discard(condition)) | List)
120+ result2 = ((source >> Discard(condition)) >> List)
121+ s.assertEqual(result0, result1)
122+ s.assertEqual(result0, result2)
103123
104124 class IterCase(TestCase):
105- '\n\tTestCase for the other iterators\n\t'
106-
107- def test_empty_peeker(s):
108- with s.assertRaises(StopIteration):
109- Peeker([]).peek()
110-
111- def test_peeker_is_iterator(s):
112- for size in range(SIZE_LIMIT):
113- source = shuffled_range_list(size)
114- peeker = Peeker(source)
115- s.assertIs(peeker, iter(peeker))
125+ '''
126+ TestCase for the other iterators
127+ '''
116128
117- def test_peeker_iterates_over_its_source(s):
118- for size in range(SIZE_LIMIT):
119- source = shuffled_range_list(size)
120- peeker = Peeker(source)
121- for (item, peeked) in zip(source, peeker):
122- s.assertEqual(item, peeked)
129+ def test_empty_peeker(s):
130+ with s.assertRaises(StopIteration):
131+ Peeker([]).peek()
123132
124- def test_peek_ahead(s):
125- for size in range(1, SIZE_LIMIT):
126- source = shuffled_range_list(size)
127- peeker = Peeker(source)
128- peeked = peeker.peek()
129- with s.assertRaises(StopIteration):
130- Peeker([]).peek()
131- index = 0
132- for (index, item) in enumerate(peeker):
133- s.assertEqual(item, peeked)
134- peeked = peeker.peek()
135- s.assertEqual((index + 1), size)
133+ def test_peeker_is_iterator(s):
134+ for size in range(SIZE_LIMIT):
135+ source = shuffled_range_list(size)
136+ peeker = Peeker(source)
137+ s.assertIs(peeker, iter(peeker))
138+
139+ def test_peeker_iterates_over_its_source(s):
140+ for size in range(SIZE_LIMIT):
141+ source = shuffled_range_list(size)
142+ peeker = Peeker(source)
143+ for (item, peeked) in zip(source, peeker):
144+ s.assertEqual(item, peeked)
145+
146+ def test_peek_ahead(s):
147+ for size in range(1, SIZE_LIMIT):
148+ source = shuffled_range_list(size)
149+ peeker = Peeker(source)
150+ peeked = peeker.peek()
151+ with s.assertRaises(StopIteration):
152+ Peeker([]).peek()
153+ index = 0
154+ for (index, item) in enumerate(peeker):
155+ s.assertEqual(item, peeked)
156+ peeked = peeker.peek()
157+ s.assertEqual((index + 1), size)
158+
159+ def test_comm2_double_empty(s):
160+ comm_lst = list(comm2([], []))
161+ s.assertEqual(comm_lst, [])
162+
163+ def test_comm2_one_empty(s):
164+ lst = list(rndrange(200))
165+ length = len(lst)
166+ Nones = length * [None]
167+ comm_lst1 = list(comm2(lst, []))
168+ projected0 = list(map(itemgetter(0), comm_lst1))
169+ projected1 = list(map(itemgetter(1), comm_lst1))
170+ s.assertEqual(lst, projected0)
171+ s.assertEqual(Nones, projected1)
172+ comm_lst2 = list(comm2([], lst))
173+ projected0 = list(map(itemgetter(0), comm_lst2))
174+ projected1 = list(map(itemgetter(1), comm_lst2))
175+ s.assertEqual(Nones, projected0)
176+ s.assertEqual(lst, projected1)
177+
178+ def test_comm2_double_filled(s):
179+ itr1 = rndrange(1, 4096)
180+ itr2 = rndrange(1, 4096)
181+ prv = 0
182+ for item in comm2(itr1, itr2):
183+ if item[0] is None:
184+ nxt = item[1]
185+ elif item[1] is None:
186+ nxt = item[0]
187+ else:
188+ s.assertEqual(*item)
189+ nxt = item[1]
190+ s.assertLess(prv, nxt)
191+
136192 unitmain()
137-
diff -r 2f551a155f34 -r 0602b98481f5 tests/threaders.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/threaders.py Wed Nov 02 19:56:15 2022 +0100
@@ -0,0 +1,63 @@
1+
2+# pylint: disable=missing-function-docstring,invalid-name,missing-module-docstring
3+
4+from unittest import TestCase, main as unitmain #, skip
5+from time import sleep
6+
7+# pylint: disable=unused-import
8+from AGPlib.threaders import Task, TaskState
9+# pylint: enable=unused-import
10+
11+class TaskTest(TestCase):
12+ '''Testing tasks'''
13+ def test_starttask(self):
14+ test_task = Task(None)
15+ self.assertIs(test_task.state, TaskState.Ready)
16+
17+ def test_premature_join(self):
18+ with self.assertRaises(RuntimeError):
19+ Task(None).join()
20+
21+ def test_asynchrone(self):
22+ def appender(lst, appendant):
23+ for item in appendant:
24+ sleep(0.001)
25+ lst.append(item)
26+ #end appender
27+
28+ lst = []
29+ task1 = Task(appender, lst, range(1000))
30+ task2 = Task(appender, lst, range(2000, 3000))
31+ task1.start()
32+ task2.start()
33+ task1.join()
34+ task2.join()
35+ sorted_lst = sorted(lst)
36+ test_lst = []
37+ test_lst.extend(range(1000))
38+ test_lst.extend(range(2000, 3000))
39+ self.assertNotEqual(lst, sorted_lst)
40+ self.assertEqual(test_lst, sorted_lst)
41+
42+ def test_synchrone(self):
43+ def appender(lst, appendant):
44+ for item in appendant:
45+ sleep(0.001)
46+ lst.append(item)
47+ #end appender
48+
49+ lst = []
50+ task1 = Task(appender, lst, range(1000))
51+ task2 = Task(appender, lst, range(2000, 3000))
52+ task1.start()
53+ task1.join()
54+ task2.start()
55+ task2.join()
56+ sorted_lst = sorted(lst)
57+ self.assertEqual(lst, sorted_lst)
58+ test_lst = []
59+ test_lst.extend(range(1000))
60+ test_lst.extend(range(2000, 3000))
61+ self.assertEqual(test_lst, sorted_lst)
62+
63+unitmain()
Show on old repository browser