updated contents from Atlas repo
This commit is contained in:
19
interfaces/bingo.py
Normal file
19
interfaces/bingo.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import random
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class BingoCage(Tombola): # <1>
|
||||
|
||||
def __init__(self, items):
|
||||
self._balls = list(items) # <2>
|
||||
|
||||
def load(self, items):
|
||||
self._balls.extend(items)
|
||||
|
||||
def pop(self):
|
||||
try:
|
||||
position = random.randrange(len(self._balls)) # <4>
|
||||
except ValueError:
|
||||
raise LookupError('pop from empty BingoCage')
|
||||
return self._balls.pop(position)
|
||||
17
interfaces/drum.py
Normal file
17
interfaces/drum.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from random import shuffle
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class TumblingDrum(Tombola):
|
||||
|
||||
def __init__(self, iterable):
|
||||
self._balls = []
|
||||
self.load(iterable)
|
||||
|
||||
def load(self, iterable):
|
||||
self._balls.extend(iterable)
|
||||
shuffle(self._balls)
|
||||
|
||||
def pop(self):
|
||||
return self._balls.pop()
|
||||
61
interfaces/exceptions-tree.txt
Normal file
61
interfaces/exceptions-tree.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
BaseException
|
||||
├── SystemExit
|
||||
├── KeyboardInterrupt
|
||||
├── GeneratorExit
|
||||
└── Exception
|
||||
├── StopIteration
|
||||
├── ArithmeticError
|
||||
│ ├── FloatingPointError
|
||||
│ ├── OverflowError
|
||||
│ └── ZeroDivisionError
|
||||
├── AssertionError
|
||||
├── AttributeError
|
||||
├── BufferError
|
||||
├── EOFError
|
||||
├── ImportError
|
||||
├── LookupError
|
||||
│ ├── IndexError
|
||||
│ └── KeyError
|
||||
├── MemoryError
|
||||
├── NameError
|
||||
│ └── UnboundLocalError
|
||||
├── OSError
|
||||
│ ├── BlockingIOError
|
||||
│ ├── ChildProcessError
|
||||
│ ├── ConnectionError
|
||||
│ │ ├── BrokenPipeError
|
||||
│ │ ├── ConnectionAbortedError
|
||||
│ │ ├── ConnectionRefusedError
|
||||
│ │ └── ConnectionResetError
|
||||
│ ├── FileExistsError
|
||||
│ ├── FileNotFoundError
|
||||
│ ├── InterruptedError
|
||||
│ ├── IsADirectoryError
|
||||
│ ├── NotADirectoryError
|
||||
│ ├── PermissionError
|
||||
│ ├── ProcessLookupError
|
||||
│ └── TimeoutError
|
||||
├── ReferenceError
|
||||
├── RuntimeError
|
||||
│ └── NotImplementedError
|
||||
├── SyntaxError
|
||||
│ └── IndentationError
|
||||
│ └── TabError
|
||||
├── SystemError
|
||||
├── TypeError
|
||||
├── ValueError
|
||||
│ └── UnicodeError
|
||||
│ ├── UnicodeDecodeError
|
||||
│ ├── UnicodeEncodeError
|
||||
│ └── UnicodeTranslateError
|
||||
└── Warning
|
||||
├── DeprecationWarning
|
||||
├── PendingDeprecationWarning
|
||||
├── RuntimeWarning
|
||||
├── SyntaxWarning
|
||||
├── UserWarning
|
||||
├── FutureWarning
|
||||
├── ImportWarning
|
||||
├── UnicodeWarning
|
||||
├── BytesWarning
|
||||
└── ResourceWarning
|
||||
24
interfaces/lotto.py
Normal file
24
interfaces/lotto.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import random
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class LotteryBlower(Tombola):
|
||||
|
||||
def __init__(self, iterable):
|
||||
self.randomizer = random.SystemRandom() # <1>
|
||||
self.clear()
|
||||
self.load(iterable)
|
||||
|
||||
def clear(self):
|
||||
self._balls = []
|
||||
|
||||
def load(self, iterable):
|
||||
self._balls.extend(iterable)
|
||||
self.randomizer.shuffle(self._balls) # <2>
|
||||
|
||||
def pop(self):
|
||||
return self._balls.pop() # <3>
|
||||
|
||||
def loaded(self): # <4>
|
||||
return len(self._balls) > 0
|
||||
25
interfaces/tombola.py
Normal file
25
interfaces/tombola.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class Tombola(metaclass=ABCMeta): # <1>
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, iterable): # <2>
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def load(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def pop(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def loaded(self): # <3>
|
||||
try:
|
||||
item = self.pop()
|
||||
except LookupError:
|
||||
return False
|
||||
else:
|
||||
self.load([item]) # put it back
|
||||
return True
|
||||
52
interfaces/tombola_runner.py
Normal file
52
interfaces/tombola_runner.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import sys
|
||||
import importlib
|
||||
import doctest
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
TESTS = 'tombola_tests.rst'
|
||||
|
||||
MODULES = 'bingo lotto tombolist drum'.split()
|
||||
|
||||
|
||||
def test_module(module_name, verbose=False):
|
||||
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
tombola_class = None
|
||||
for name in dir(module):
|
||||
obj = getattr(module, name)
|
||||
if (isinstance(obj, type) and
|
||||
issubclass(obj, Tombola) and
|
||||
obj.__module__ == module_name):
|
||||
tombola_class = obj
|
||||
break # stop at first matching class
|
||||
|
||||
if tombola_class is None:
|
||||
print('ERROR: no Tombola subclass found in', module_name)
|
||||
sys.exit(1)
|
||||
|
||||
res = doctest.testfile(TESTS,
|
||||
globs={'TombolaUnderTest': tombola_class},
|
||||
verbose=verbose,
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
|
||||
print('{:10} {}'.format(module_name, res))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
args = sys.argv[:]
|
||||
if '-v' in args:
|
||||
args.remove('-v')
|
||||
verbose = True
|
||||
else:
|
||||
verbose = False
|
||||
|
||||
if len(args) == 2:
|
||||
module_names = [args[1]]
|
||||
else:
|
||||
module_names = MODULES
|
||||
|
||||
for name in module_names:
|
||||
print('*' * 60, name)
|
||||
test_module(name, verbose)
|
||||
70
interfaces/tombola_tests.rst
Normal file
70
interfaces/tombola_tests.rst
Normal file
@@ -0,0 +1,70 @@
|
||||
==============
|
||||
Tombola tests
|
||||
==============
|
||||
|
||||
Every concrete subclass of Tombola should pass these tests.
|
||||
|
||||
|
||||
Create and load instance from iterable::
|
||||
|
||||
>>> balls = list(range(3))
|
||||
>>> globe = TombolaUnderTest(balls)
|
||||
>>> globe.loaded()
|
||||
True
|
||||
|
||||
|
||||
Pop and collect balls::
|
||||
|
||||
>>> picks = []
|
||||
>>> picks.append(globe.pop())
|
||||
>>> picks.append(globe.pop())
|
||||
>>> picks.append(globe.pop())
|
||||
|
||||
|
||||
Check state and results::
|
||||
|
||||
>>> globe.loaded()
|
||||
False
|
||||
>>> sorted(picks) == balls
|
||||
True
|
||||
|
||||
|
||||
Reload::
|
||||
|
||||
>>> globe.load(balls)
|
||||
>>> globe.loaded()
|
||||
True
|
||||
>>> picks = [globe.pop() for i in balls]
|
||||
>>> globe.loaded()
|
||||
False
|
||||
|
||||
|
||||
Load and pop 20 balls to verify that the order has changed::
|
||||
|
||||
>>> balls = list(range(20))
|
||||
>>> globe = TombolaUnderTest(balls)
|
||||
>>> picks = []
|
||||
>>> while globe.loaded():
|
||||
... picks.append(globe.pop())
|
||||
>>> len(picks) == len(balls)
|
||||
True
|
||||
>>> picks != balls
|
||||
True
|
||||
|
||||
|
||||
Also check that the order is not simply reversed either::
|
||||
|
||||
>>> picks[::-1] != balls
|
||||
True
|
||||
|
||||
Note: last 2 tests each have 1 chance in 20! (factorial) of failing even if the implementation is OK. 1/20!, or approximately 4.11e-19, is the probability of the 20 balls coming out, by chance, in the exact order the were loaded.
|
||||
|
||||
Check that `LookupError` (or a subclass) is the exception thrown when the device is empty::
|
||||
|
||||
>>> globe = TombolaUnderTest([])
|
||||
>>> try:
|
||||
... globe.pop()
|
||||
... except LookupError as exc:
|
||||
... print('OK')
|
||||
OK
|
||||
|
||||
19
interfaces/tombolist.py
Normal file
19
interfaces/tombolist.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from random import randrange
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class TomboList(list): # <1>
|
||||
|
||||
def pop(self):
|
||||
if self: # <2>
|
||||
return super().pop(randrange(len(self))) # <3>
|
||||
else:
|
||||
raise LookupError('pop from empty TomboList')
|
||||
|
||||
def load(self, iterable): self.extend(iterable) # <4>
|
||||
|
||||
def loaded(self): return bool(self) # <5>
|
||||
|
||||
|
||||
Tombola.register(TomboList) # <6>
|
||||
Reference in New Issue
Block a user