├── pytest.pdf ├── block ├── setup.py ├── test │ └── test_block.py └── block.py ├── skilift ├── setup.py ├── test │ └── test_skilift.py └── skilift.py └── README.rst /pytest.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattharrison/adv_pytest_mar_22_2018/master/pytest.pdf -------------------------------------------------------------------------------- /block/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name='block', 5 | version='0.1dev', 6 | py_modules=['block'], 7 | ) 8 | -------------------------------------------------------------------------------- /skilift/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name='skilift', 5 | version='0.1dev', 6 | py_modules=['skilift'], 7 | ) 8 | -------------------------------------------------------------------------------- /block/test/test_block.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | 4 | import block as bl 5 | 6 | 7 | import pytest 8 | 9 | 10 | @pytest.fixture 11 | def matt_node(): 12 | node = bl.Node('matt') 13 | return node 14 | 15 | def test_hash(matt_node): 16 | node = matt_node 17 | gb, hash = node.process_txns([]) 18 | print(f'Genesis block {gb.todict()}, \nHash {hash}') 19 | assert hash.startswith('0') 20 | 21 | 22 | def test_bad_difficulty(matt_node): 23 | #node = bl.Node('matt') 24 | node = matt_node 25 | with pytest.raises(TypeError): 26 | gb, hash = node.process_txns([], difficulty='1') 27 | 28 | @pytest.mark.parametrize('uuid', 29 | ['123', 'matt']) 30 | def test_node(uuid): 31 | #for uuid in ['123', 'matt']: 32 | n = bl.Node(uuid) 33 | assert n.uuid == uuid 34 | -------------------------------------------------------------------------------- /skilift/test/test_skilift.py: -------------------------------------------------------------------------------- 1 | import skilift as sk 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def line_n(request): 8 | size = request.node.get_marker( 9 | 'line_size').args[0] 10 | line = sk.Line(size) 11 | return line 12 | 13 | 14 | @pytest.fixture 15 | def BenchN(request): 16 | _size = request.node.get_marker( 17 | 'bench_size').args[0] 18 | class BSize(sk._Bench): 19 | size = _size 20 | return BSize 21 | 22 | 23 | @pytest.fixture 24 | def line_5(): 25 | line = sk.Line(5) 26 | return line 27 | 28 | @pytest.fixture 29 | def quad_10(): 30 | lift = sk.Lift(10, sk.Quad) 31 | return lift 32 | 33 | @pytest.mark.bench_size(6) 34 | def test_bench6(line_5, BenchN): 35 | lift = sk.Lift(10, BenchN) 36 | res = lift.one_bench(line_5) 37 | assert res == {'loaded': 5, 'num_benches': 1, 'unloaded': 0} 38 | 39 | @pytest.mark.line_size(6) 40 | def test_line6(line_n): 41 | res = line_n.take(7) 42 | assert res == 6 43 | 44 | 45 | 46 | def test_line_take(line_5): 47 | res = line_5.take(7) 48 | assert res == 5 49 | assert line_5.num_people == 0 50 | 51 | def test_lift_one_bench(line_5, quad_10): 52 | #lift = sk.Lift(10, sk.Quad) 53 | res = quad_10.one_bench(line_5) 54 | assert res == {'loaded': 4, 'num_benches': 1, 'unloaded': 0} 55 | 56 | 57 | @pytest.mark.parametrize('size', [[], None, '10']) 58 | def test_lines_bad(size): 59 | with pytest.raises(TypeError): 60 | line = sk.Line(size) 61 | line.take(1) 62 | 63 | @pytest.mark.parametrize('size,amt,answer', 64 | [(0, 5, 0), (5, 2, 3), (10, 0, 10)]) 65 | def test_line_sizes(size, amt, answer): 66 | line = sk.Line(size) 67 | res = line.take(amt) 68 | assert line.num_people == answer 69 | 70 | 71 | def test_half_take(monkeypatch): 72 | def half_take(self, amount): 73 | amount = int(amount/2) 74 | if amount > self.num_people: 75 | amount = self.num_people 76 | self.num_people -= amount 77 | return amount 78 | 79 | monkeypatch.setattr(sk.Line, 'take', half_take) 80 | line = sk.Line(10) 81 | res = line.take(5) 82 | assert res == 2 83 | assert line.num_people == 8 84 | 85 | 86 | -------------------------------------------------------------------------------- /skilift/skilift.py: -------------------------------------------------------------------------------- 1 | """ 2 | Code for simulating a lift at a ski resort: 3 | 4 | >>> num_benches = 10 5 | >>> bench_class = Quad 6 | >>> lift = Lift(num_benches, bench_class) 7 | >>> line = Line(num_people=10) 8 | >>> lift.simulate(line) 9 | {'loaded': 10, 'unloaded': 10, 'num_benches': 8} 10 | """ 11 | 12 | from collections import deque 13 | 14 | 15 | class Line: 16 | def __init__(self, num_people): 17 | self.num_people = num_people 18 | 19 | def take(self, amount): 20 | if amount > self.num_people: 21 | amount = self.num_people 22 | self.num_people -= amount 23 | return amount 24 | 25 | def add(self, amount): 26 | self.num_people += amount 27 | 28 | def __bool__(self): 29 | return bool(self.num_people) 30 | 31 | 32 | class _Bench: 33 | size = 1 34 | def __init__(self, id): 35 | self.count = 0 36 | self.id = id 37 | def load(self, num): 38 | self.count += num 39 | def unload(self): 40 | val = self.count 41 | self.count = 0 42 | return val 43 | 44 | 45 | class Quad(_Bench): 46 | size = 4 47 | 48 | 49 | class Lift: 50 | def __init__(self, num_benches, bench_class): 51 | half = num_benches / 2 52 | self._up = deque() 53 | self._down = deque() 54 | for i in range(num_benches): 55 | if i < half: 56 | self._up.append(bench_class(i)) 57 | else: 58 | self._down.append(bench_class(i)) 59 | self.bench_size = bench_class.size 60 | 61 | def people_riding_up(self): 62 | return any(bench.count for bench in self._up) 63 | 64 | def simulate(self, line): 65 | results = {'loaded': 0, 'unloaded':0, 'num_benches':0} 66 | while line: 67 | self.one_bench(line, results) 68 | #get them to the top 69 | while self.people_riding_up(): 70 | self.one_bench(line, results) 71 | return results 72 | 73 | def one_bench(self, line, results=None): 74 | results = results or {'loaded': 0, 'unloaded':0, 'num_benches':0} 75 | num = line.take(self.bench_size) 76 | # load bottom 77 | bench = self._down.popleft() 78 | bench.load(num) 79 | self._up.append(bench) 80 | results['loaded'] += num 81 | # unload top 82 | bench = self._up.popleft() 83 | num = bench.unload() 84 | results['unloaded'] += num 85 | self._down.append(bench) 86 | results['num_benches'] += 1 87 | return results 88 | 89 | if __name__ == '__main__': 90 | import doctest 91 | doctest.testmod() 92 | -------------------------------------------------------------------------------- /block/block.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic blockchain implementation 3 | 4 | http://bitcoin.org/bitcoin.pdf 5 | 6 | * Coin - chain of digital signatures 7 | * Chain - sequence of hashes. Hash(block of items, prev hash) 8 | * Block - header(prev hash, nonce, hash), transaction 9 | * Proof of work - Create value when hash starts with 10 | a certain number of zeros. 11 | * Nodes - Each node collects transactions, then node 12 | mines the block. Each node tries to find POW, and broadcast 13 | it. Nodes accepts the block by working on the next block. 14 | * Transaction - inputs and outputs. Normally 2 outputs. 15 | 16 | >>> a = Amount('matt', 5) 17 | >>> a.todict() 18 | {'uuid': 'matt', 'amount': 5} 19 | 20 | """ 21 | import hashlib 22 | import json 23 | 24 | # this is a comment 25 | MINING_COST = 1 26 | 27 | 28 | class Amount: 29 | def __init__(self, uuid, amount): 30 | self.uuid = uuid 31 | self.amount = amount 32 | 33 | def todict(self): 34 | return {'uuid': self.uuid, 'amount': self.amount} 35 | 36 | 37 | class Transaction: 38 | def __init__(self, inputs, outputs): 39 | self.inputs = inputs 40 | self.outputs = outputs 41 | 42 | def todict(self): 43 | return {'inputs': [i.todict() for i in self.inputs], 44 | 'outputs': [o.todict() for o in self.outputs]} 45 | 46 | 47 | class Block: 48 | def __init__(self, txns, prev_hash): 49 | self.prev_hash = prev_hash 50 | self.txns = txns 51 | self.nonce = None 52 | 53 | def todict(self, nonce=None): 54 | nonce = nonce if nonce is not None else self.nonce 55 | body = {'txns': [txns.todict() for txns in self.txns]} 56 | headers = {'prev_hash': self.prev_hash, 57 | 'body_hash': get_hash(body), 58 | 'nonce': nonce} 59 | data = {'header': headers, 60 | 'body': body} 61 | return data 62 | 63 | def get_hash(self, nonce): 64 | return get_hash(self.todict(nonce)) 65 | 66 | 67 | def get_hash(dict_data): 68 | sha = hashlib.sha256() 69 | data = json.dumps(dict_data, sort_keys=True) 70 | sha.update(data.encode('utf-8')) 71 | return sha.hexdigest() 72 | 73 | 74 | class Node: 75 | def __init__(self, uuid): 76 | self.uuid = uuid 77 | self.blocks = [] 78 | 79 | def process_txns(self, txns, difficulty=1): 80 | txns.insert(0, Transaction([], [Amount(self.uuid, 81 | MINING_COST)])) 82 | if self.blocks: 83 | prev_hash = self.blocks[-1].prev_hash 84 | else: 85 | prev_hash = '' 86 | block = Block(txns, prev_hash) 87 | nonce = 0 88 | while True: 89 | hash = block.get_hash(nonce) 90 | if hash.startswith('0'*difficulty): 91 | block.nonce = nonce 92 | self.blocks.append(block) 93 | return block, hash 94 | nonce += 1 95 | 96 | 97 | 98 | 99 | if __name__ == '__main__': 100 | import doctest 101 | doctest.testmod() 102 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Intermediate Pytest 3 | ===================== 4 | 5 | http://bit.ly/pytest-mar-22-2018 6 | 7 | These assignments are based on a Python implementation 8 | that models a lift at a ski resort. 9 | 10 | 11 | Assignment 1 12 | ============ 13 | 14 | * Install pytest in a virtual environment 15 | * Run:: 16 | 17 | pytest -h 18 | 19 | Assignment 2 20 | ============ 21 | 22 | * Create a ``test/`` directory in the ``skilift`` 23 | * Create a ``test/test_skilift.py`` file 24 | * Create a test function ``test_line_take`` that 25 | creates a ``Line`` with 5 people and takes 7. 26 | Ensure that the amount returned is 5 and the 27 | ``.num_people`` attribute is 0. 28 | * Create a test function ``test_lift_one_bench`` 29 | that creates a line of 5, and a quad lift 30 | with 10 benches. Call ``.one_bench`` and 31 | assert that the correct results are returned. 32 | 33 | Assignment 3 34 | ============ 35 | 36 | Parameterization 37 | 38 | * Create a test function ``test_line_bad``, 39 | that creates lines with ``[]``, ``None``, and 40 | ``'10'`` in them. It tries to call ``.take(1)`` 41 | on each, and checks that the appropriate error 42 | is raised. 43 | 44 | * Create a test function ``test_line_sizes`` 45 | that creates lines of size 0, 5, and 10. 46 | It takes 5, 2, and 0 from each respectively 47 | and asserts that the ``.num_people`` attribute 48 | is correct. (You should probably parameterize 49 | the result as well) 50 | 51 | * Run only the ``test_line_sizes`` test when the line 52 | is created with size 10. (Hint use ``-v`` to get an id) 53 | 54 | 55 | 56 | Assignment 4 57 | ============ 58 | 59 | * Create a fixture for a line of size 5. Use that 60 | fixture in ``test_line_take`` and ``test_lift_one_bench`` 61 | 62 | * Create a fixture for a Quad lift of size 10. Use that 63 | in ``test_lift_one_bench`` 64 | 65 | 66 | Assignment 5 67 | ============ 68 | 69 | * Create a fixture, ``line_n``, that depends on ``request``. 70 | Read off of the marker to get a line size. Create 71 | at test, ``test_line_6``, that creates a tests 72 | ``.take`` on a ``Line`` with length 6. 73 | 74 | * Create a fixture, ``BenchN``, that depends on ``request``. 75 | Read off of the marker to get a bench size. Dynamically 76 | subclass ``_Bench`` to create a subclass with the passed 77 | in size. Create a test, ``test_bench6``, like ``test_left_one_bench``, 78 | that uses the fixture to create 6 person bench and test it. 79 | 80 | 81 | Assignment 6 82 | ============== 83 | 84 | * Create a test, ``test_half_take``, that monkey patches 85 | ``Line.take`` so that only half the amount requested are 86 | returned from the line. (ie. ``line.take(4)`` would only 87 | take 2 from the line) 88 | 89 | 90 | Assignment 7 91 | ============ 92 | 93 | * Create a ``pytest.ini`` file 94 | 95 | * Add an option to run the doctests 96 | 97 | * Create a ``test/conftest.py`` file. Move the fixtures to 98 | this file 99 | 100 | 101 | Assignment 8 102 | ============ 103 | 104 | * Examine the ``setup.py`` for ``pytest-cov`` on GitHub. 105 | 106 | * What is the entry point? 107 | 108 | * What hook does the plugin implement? 109 | 110 | 111 | Assignment 9 112 | ============ 113 | 114 | * Install pytest-cov 115 | 116 | * Run coverage on the project using the plugin 117 | --------------------------------------------------------------------------------