import math
from datetime import date
from datetime import datetime

import pytest

from yui.apps.compute.calc import BadSyntax
from yui.apps.compute.calc import Decimal as D
from yui.apps.compute.calc import Evaluator
from yui.apps.compute.calc import calculate

from ...util import FakeBot


class GetItemSpy:
    def __init__(self):
        self.queue = []

    def __getitem__(self, item):
        self.queue.append(item)


def test_decimal():
    assert -D('1') == D('-1')
    assert +D('1') == D('1')
    assert abs(D('-1')) == D('1')
    assert D('1') + 1 == D('2')
    assert 1 + D('1') == D('2')
    assert D('1') - 1 == D('0')
    assert 1 - D('1') == D('0')
    assert D('2') * 3 == D('6')
    assert 2 * D('3') == D('6')
    assert D('10') // 2 == D('5')
    assert 10 // D('2') == D('5')
    assert D('10') / 2.5 == D('4')
    assert 10 / D('2.5') == D('4')
    assert D('5') % 2 == D('1')
    assert 5 % D('2') == D('1')
    assert divmod(D('5'), 2) == (D('2'), D('1'))
    assert divmod(5, D('2')) == (D('2'), D('1'))
    assert D('3') ** 2 == D('9')
    assert 3 ** D('2') == D('9')


def test_annassign():
    e = Evaluator()

    err = 'You can not use annotation syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run('a: int = 10')

    assert 'a' not in e.symbol_table


def test_assert():
    e = Evaluator()
    err = 'You can not use assertion syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run('assert True')

    with pytest.raises(BadSyntax, match=err):
        e.run('assert False')


def test_assign():
    e = Evaluator()
    e.run('a = 1 + 2')
    assert e.symbol_table['a'] == 3
    e.run('x, y = 10, 20')
    assert e.symbol_table['x'] == 10
    assert e.symbol_table['y'] == 20

    e.symbol_table['dt'] = datetime.now()
    err = 'This assign method is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run('dt.year = 2000')

    err = 'too many values to unpack'
    with pytest.raises(ValueError, match=err):
        e.run('year, month, day = 1,')

    err = 'not enough values to unpack'
    with pytest.raises(ValueError, match=err):
        e.run('id, name = 1, "kirito", "black"')

    err = 'cannot unpack non-iterable int object'
    with pytest.raises(TypeError, match=err):
        e.run('year, month, day = 1')

    e.run('arr = [1, 2, 3]')
    assert e.symbol_table['arr'] == [1, 2, 3]

    e.run('arr[1] = 5')
    assert e.symbol_table['arr'] == [1, 5, 3]

    e.run('arr[:] = [10, 20, 30]')
    assert e.symbol_table['arr'] == [10, 20, 30]


def test_asyncfor():
    e = Evaluator()
    e.symbol_table['r'] = 0
    err = 'You can not use `async for` loop syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run(
            '''
async for x in [1, 2, 3, 4]:
    r += x

'''
        )
    assert e.symbol_table['r'] == 0


def test_asyncfunctiondef():
    e = Evaluator()
    err = 'Defining new coroutine via def syntax is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run(
            '''
async def abc():
    pass

'''
        )
    assert 'abc' not in e.symbol_table


def test_asyncwith():
    e = Evaluator()
    e.symbol_table['r'] = 0
    err = 'You can not use `async with` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run(
            '''
async with x():
    r += 100

'''
        )
    assert e.symbol_table['r'] == 0


def test_attribute():
    e = Evaluator()
    e.symbol_table['dt'] = datetime.now()
    e.run('x = dt.year')
    assert e.symbol_table['x'] == e.symbol_table['dt'].year

    err = 'You can not access `test_test_test` attribute'
    with pytest.raises(BadSyntax, match=err):
        e.run('y = dt.test_test_test')

    assert 'y' not in e.symbol_table

    err = 'You can not access `asdf` attribute'
    with pytest.raises(BadSyntax, match=err):
        e.run('z = x.asdf')

    e.symbol_table['math'] = math
    err = 'You can not access `__module__` attribute'
    with pytest.raises(BadSyntax, match=err):
        e.run('math.__module__')

    e.symbol_table['datetime'] = datetime
    err = 'You can not access `test_test` attribute'
    with pytest.raises(BadSyntax, match=err):
        e.run('datetime.test_test')


def test_augassign():
    e = Evaluator()
    e.symbol_table['a'] = 0
    e.run('a += 1')
    assert e.symbol_table['a'] == 1
    e.symbol_table['l'] = [1, 2, 3, 4]
    e.run('l[0] -= 1')
    assert e.symbol_table['l'] == [0, 2, 3, 4]

    err = 'This assign method is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run('l[2:3] += 20')

    e.symbol_table['dt'] = datetime.now()
    err = 'This assign method is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run('dt.year += 2000')


def test_await():
    e = Evaluator()
    err = 'You can not await anything'
    with pytest.raises(BadSyntax, match=err):
        e.run('r = await x()')
    assert 'r' not in e.symbol_table


def test_binop():
    e = Evaluator()
    assert e.run('1 + 2') == 1 + 2
    assert e.run('3 & 2') == 3 & 2
    assert e.run('1 | 2') == 1 | 2
    assert e.run('3 ^ 2') == 3 ^ 2
    assert e.run('3 / 2') == 3 / 2
    assert e.run('3 // 2') == 3 // 2
    assert e.run('3 << 2') == 3 << 2
    with pytest.raises(TypeError):
        e.run('2 @ 3')
    assert e.run('3 * 2') == 3 * 2
    assert e.run('33 % 4') == 33 % 4
    assert e.run('3 ** 2') == 3 ** 2
    assert e.run('100 >> 2') == 100 >> 2
    assert e.run('3 - 1') == 3 - 1


def test_boolop():
    e = Evaluator()
    assert e.run('True and False') == (True and False)
    assert e.run('True or False') == (True or False)


def test_break():
    e = Evaluator()
    e.run('break')
    assert e.current_interrupt.__class__.__name__ == 'Break'


def test_bytes():
    e = Evaluator()
    assert e.run('b"asdf"') == b'asdf'
    e.run('a = b"asdf"')
    assert e.symbol_table['a'] == b'asdf'


def test_call():
    e = Evaluator()
    e.symbol_table['date'] = date
    e.run('x = date(2019, 10, day=7)')
    assert e.symbol_table['x'] == date(2019, 10, day=7)

    e.symbol_table['math'] = math
    e.run('y = math.sqrt(121)')
    assert e.symbol_table['y'] == math.sqrt(121)

    e.symbol_table['datetime'] = datetime
    e.run('z = datetime.now().date()')
    assert e.symbol_table['z'] == datetime.now().date()


def test_classdef():
    e = Evaluator()
    err = 'Defining new class via def syntax is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run(
            '''
class ABCD:
    pass

'''
        )
    assert 'ABCD' not in e.symbol_table


def test_compare():
    e = Evaluator()
    assert e.run('1 == 2') == (1 == 2)
    assert e.run('3 > 2') == (3 > 2)
    assert e.run('3 >= 2') == (3 >= 2)
    assert e.run('"A" in "America"') == ('A' in 'America')
    assert e.run('"E" not in "America"') == ('E' not in 'America')
    assert e.run('1 is 2') == (1 is 2)  # noqa
    assert e.run('1 is not 2') == (1 is not 2)  # noqa
    assert e.run('3 < 2') == (3 < 2)
    assert e.run('3 <= 2') == (3 <= 2)


def test_continue():
    e = Evaluator()
    e.run('continue')
    assert e.current_interrupt.__class__.__name__ == 'Continue'


def test_delete():
    e = Evaluator()
    e.symbol_table['a'] = 0
    e.symbol_table['b'] = 0
    e.symbol_table['c'] = 0
    e.run('del a, b, c')
    assert 'a' not in e.symbol_table
    assert 'b' not in e.symbol_table
    assert 'c' not in e.symbol_table
    e.symbol_table['l'] = [1, 2, 3, 4]
    e.run('del l[0]')
    assert e.symbol_table['l'] == [2, 3, 4]

    err = 'This delete method is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run('del l[2:3]')

    e.symbol_table['dt'] = datetime.now()
    err = 'This delete method is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run('del dt.year')


def test_dict():
    e = Evaluator()
    assert e.run('{1: 111, 2: 222}') == {1: 111, 2: 222}
    e.run('a = {1: 111, 2: 222}')
    assert e.symbol_table['a'] == {1: 111, 2: 222}


def test_dictcomp():
    e = Evaluator()
    assert e.run('{k+1: v**2 for k, v in {1: 1, 2: 11, 3: 111}.items()}') == {
        2: 1,
        3: 121,
        4: 12321,
    }
    assert 'k' not in e.symbol_table
    assert 'v' not in e.symbol_table
    e.run('a = {k+1: v**2 for k, v in {1: 1, 2: 11, 3: 111}.items()}')
    assert e.symbol_table['a'] == {
        2: 1,
        3: 121,
        4: 12321,
    }
    assert 'k' not in e.symbol_table
    assert 'v' not in e.symbol_table


def test_ellipsis():
    e = Evaluator()
    assert e.run('...') == Ellipsis


def test_expr():
    e = Evaluator()
    assert e.run('True') is True
    assert e.run('False') is False
    assert e.run('None') is None
    assert e.run('123') == 123
    assert e.run('"abc"') == 'abc'
    assert e.run('[1, 2, 3]') == [1, 2, 3]
    assert e.run('(1, 2, 3, 3)') == (1, 2, 3, 3)
    assert e.run('{1, 2, 3, 3}') == {1, 2, 3}
    assert e.run('{1: 111, 2: 222}') == {1: 111, 2: 222}


def test_extslice():
    e = Evaluator()
    e.symbol_table['obj'] = GetItemSpy()
    e.run('obj[1,2:3,4]')
    es = e.symbol_table['obj'].queue.pop()
    assert isinstance(es, tuple)
    assert len(es) == 3
    assert es[0] == 1
    assert isinstance(es[1], slice)
    assert es[1].start == 2
    assert es[1].stop == 3
    assert es[1].step is None
    assert es[2] == 4


def test_functiondef():
    e = Evaluator()
    err = 'Defining new function via def syntax is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run(
            '''
def abc():
    pass

'''
        )
    assert 'abc' not in e.symbol_table


def test_for():
    total = 0
    for x in [1, 2, 3, 4, 5, 6]:
        total = total + x
        if total > 10:
            continue
        total = total * 2
    else:
        total = total + 10000
    e = Evaluator()
    e.run(
        '''
total = 0
for x in [1, 2, 3, 4, 5, 6]:
    total = total + x
    if total > 10:
        continue
    total = total * 2
else:
    total = total + 10000
'''
    )
    assert e.symbol_table['total'] == total

    total2 = 0
    for x in [1, 2, 3, 4, 5, 6]:
        total2 = total2 + x
        if total2 > 10:
            break
        total2 = total2 * 2
    else:
        total2 = total2 + 10000

    e.run(
        '''
total2 = 0
for x in [1, 2, 3, 4, 5, 6]:
    total2 = total2 + x
    if total2 > 10:
        break
    total2 = total2 * 2
else:
    total2 = total2 + 10000
'''
    )
    assert e.symbol_table['total2'] == total2


def test_formattedvalue():
    e = Evaluator()
    e.symbol_table['before'] = 123456
    e.run('after = f"change {before} to {before:,}!"')
    assert e.symbol_table['after'] == 'change 123456 to 123,456!'


def test_generator_exp():
    e = Evaluator()
    e.symbol_table['r'] = [1, 2, 3]
    err = 'Defining new generator expression is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run('x = (i ** 2 for i in r)')
    assert 'x' not in e.symbol_table


def test_global():
    e = Evaluator()
    err = 'You can not use `global` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run('global x')


def test_if():
    e = Evaluator()
    e.symbol_table['a'] = 1
    e.run(
        '''
if a == 1:
    a = 2
    b = 3
'''
    )
    assert e.symbol_table['a'] == 2
    assert e.symbol_table['b'] == 3

    e.run(
        '''
if a == 1:
    a = 2
    b = 3
    z = 1
else:
    a = 3
    b = 4
    c = 5
'''
    )
    assert e.symbol_table['a'] == 3
    assert e.symbol_table['b'] == 4
    assert e.symbol_table['c'] == 5
    assert 'z' not in e.symbol_table

    e.run(
        '''
if a == 1:
    a = 2
    b = 3
    z = 1
elif a == 3:
    d = 4
    e = 5
    f = 6
else:
    a = 3
    b = 4
    c = 5
    y = 7
'''
    )
    assert e.symbol_table['a'] == 3
    assert e.symbol_table['b'] == 4
    assert e.symbol_table['c'] == 5
    assert e.symbol_table['d'] == 4
    assert e.symbol_table['e'] == 5
    assert e.symbol_table['f'] == 6
    assert 'y' not in e.symbol_table
    assert 'z' not in e.symbol_table


def test_ifexp():
    e = Evaluator()
    assert e.run('100 if 1 == 1 else 200') == 100
    assert e.run('100 if 1 == 2 else 200') == 200


def test_import():
    e = Evaluator()
    err = 'You can not import anything'
    with pytest.raises(BadSyntax, match=err):
        e.run('import sys')
    assert 'sys' not in e.symbol_table


def test_importfrom():
    e = Evaluator()
    err = 'You can not import anything'
    with pytest.raises(BadSyntax, match=err):
        e.run('from os import path')
    assert 'path' not in e.symbol_table


def test_index():
    e = Evaluator()
    e.symbol_table['obj'] = GetItemSpy()
    e.run('obj[10]')
    index = e.symbol_table['obj'].queue.pop()
    assert index == 10
    e.run('obj["asdf"]')
    index = e.symbol_table['obj'].queue.pop()
    assert index == 'asdf'


def test_lambda():
    e = Evaluator()
    err = 'Defining new function via lambda syntax is not allowed'
    with pytest.raises(BadSyntax, match=err):
        e.run('lambda x: x*2')


def test_list():
    e = Evaluator()
    assert e.run('[1, 2, 3]') == [1, 2, 3]
    e.run('a = [1, 2, 3]')
    assert e.symbol_table['a'] == [1, 2, 3]


def test_listcomp():
    e = Evaluator()
    assert e.run('[x ** 2 for x in [1, 2, 3]]') == [1, 4, 9]
    assert 'x' not in e.symbol_table
    assert e.run('[x ** 2 + y for x in [1, 2, 3] for y in [10, 20, 30]]') == (
        [x ** 2 + y for x in [1, 2, 3] for y in [10, 20, 30]]
    )
    assert 'x' not in e.symbol_table
    assert 'y' not in e.symbol_table
    assert e.run('[y ** 2 for x in [1, 2, 3] for y in [x+1, x+3, x+5]]') == (
        [y ** 2 for x in [1, 2, 3] for y in [x + 1, x + 3, x + 5]]
    )
    assert 'x' not in e.symbol_table
    assert 'y' not in e.symbol_table


def test_nameconstant():
    e = Evaluator()
    assert e.run('True') is True
    assert e.run('False') is False
    assert e.run('None') is None
    e.run('x = True')
    e.run('y = False')
    e.run('z = None')
    assert e.symbol_table['x'] is True
    assert e.symbol_table['y'] is False
    assert e.symbol_table['z'] is None


def test_nonlocal():
    e = Evaluator()
    err = 'You can not use `nonlocal` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run('nonlocal x')


def test_num():
    e = Evaluator()
    assert e.run('123') == 123
    e.run('a = 123')
    assert e.symbol_table['a'] == 123


def test_pass():
    e = Evaluator()
    e.run('pass')


def test_raise():
    e = Evaluator()
    err = 'You can not use `raise` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run('raise NameError')


def test_return():
    e = Evaluator()
    err = 'You can not use `return` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run('return True')


def test_set():
    e = Evaluator()
    assert e.run('{1, 1, 2, 3, 3}') == {1, 2, 3}
    e.run('a = {1, 1, 2, 3, 3}')
    assert e.symbol_table['a'] == {1, 2, 3}


def test_setcomp():
    e = Evaluator()
    assert e.run('{x ** 2 for x in [1, 2, 3, 3]}') == {1, 4, 9}
    assert 'x' not in e.symbol_table
    assert e.run('{x ** 2 + y for x in [1, 2, 3] for y in [10, 20, 30]}') == (
        {x ** 2 + y for x in [1, 2, 3] for y in [10, 20, 30]}
    )
    assert 'x' not in e.symbol_table
    assert 'y' not in e.symbol_table
    assert e.run('{y ** 2 for x in [1, 2, 3] for y in [x+1, x+3, x+5]}') == (
        {y ** 2 for x in [1, 2, 3] for y in [x + 1, x + 3, x + 5]}
    )
    assert 'x' not in e.symbol_table
    assert 'y' not in e.symbol_table


def test_slice():
    e = Evaluator()
    e.symbol_table['obj'] = GetItemSpy()
    e.run('obj[10:20:3]')
    s = e.symbol_table['obj'].queue.pop()
    assert isinstance(s, slice)
    assert s.start == 10
    assert s.stop == 20
    assert s.step == 3


def test_str():
    e = Evaluator()
    assert e.run('"asdf"') == 'asdf'
    e.run('a = "asdf"')
    assert e.symbol_table['a'] == 'asdf'


def test_subscript():
    e = Evaluator()
    assert e.run('[10, 20, 30][0]') == 10
    assert e.run('(100, 200, 300)[0]') == 100
    assert e.run('{"a": 1000, "b": 2000, "c": 3000}["a"]') == 1000
    e.run('a = [10, 20, 30][0]')
    e.run('b = (100, 200, 300)[0]')
    e.run('c = {"a": 1000, "b": 2000, "c": 3000}["a"]')
    assert e.symbol_table['a'] == 10
    assert e.symbol_table['b'] == 100
    assert e.symbol_table['c'] == 1000
    e.symbol_table['l'] = [11, 22, 33]
    assert e.run('l[2]') == 33
    e.run('l[2] = 44')
    assert e.symbol_table['l'] == [11, 22, 44]


def test_try():
    e = Evaluator()
    err = 'You can not use `try` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run(
            '''
try:
    x = 1
except:
    pass
'''
        )
    assert 'x' not in e.symbol_table


def test_tuple():
    e = Evaluator()
    assert e.run('(1, 1, 2, 3, 3)') == (1, 1, 2, 3, 3)
    e.run('a = (1, 1, 2, 3, 3)')
    assert e.symbol_table['a'] == (1, 1, 2, 3, 3)


def test_unaryop():
    e = Evaluator()
    assert e.run('~100') == ~100
    assert e.run('not 100') == (not 100)
    assert e.run('+100') == +100
    assert e.run('-100') == -100


def test_while():
    total = 0
    i = 1
    while total > 100:
        total += i
        i += i
        if i % 10 == 0:
            i += 1
    else:
        total = total + 10000
    e = Evaluator()
    e.run(
        '''
total = 0
i = 1
while total > 100:
    total += i
    i += i
    if i % 10 == 0:
        i += 1
else:
    total = total + 10000
'''
    )
    assert e.symbol_table['total'] == total

    r = 0
    while True:
        break
    else:
        r += 10

    e.run(
        '''
r = 0
while True:
    break
else:
    r += 10
'''
    )
    assert e.symbol_table['r'] == 0


def test_with():
    e = Evaluator()
    err = 'You can not use `with` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run(
            '''
with some:
    x = 1
'''
        )
    assert 'x' not in e.symbol_table


def test_yield():
    e = Evaluator()
    err = 'You can not use `yield` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run('x = yield f()')
    assert 'x' not in e.symbol_table


def test_yield_from():
    e = Evaluator()
    err = 'You can not use `yield from` syntax'
    with pytest.raises(BadSyntax, match=err):
        e.run('x = yield from f()')
    assert 'x' not in e.symbol_table


@pytest.mark.asyncio
@pytest.mark.parametrize(
    (
        'expr, expected_decimal_result, expected_num_result,'
        'expected_decimal_local, expected_num_local'
    ),
    [
        ('1', D('1'), 1, {}, {}),
        ('1+2', D('3'), 3, {}, {}),
        (
            '0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1',
            D('1'),
            0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1,
            {},
            {},
        ),
        ('1-2', D('-1'), -1, {}, {}),
        ('4*5', D('20'), 20, {}, {}),
        ('1/2', D('0.5'), 0.5, {}, {}),
        ('10%3', D('1'), 1, {}, {}),
        ('2**3', D('8'), 8, {}, {}),
        ('(1+2)**3', D('27'), 27, {}, {}),
        ('max(1,2,3,4,5)', D('5'), 5, {}, {}),
        ('math.floor(3.2)', D('3'), 3, {}, {}),
        ('1+math.e', D(math.e) + D('1'), math.e + 1, {}, {}),
        ('[1,2,3]', [D('1'), D('2'), D('3')], [1, 2, 3], {}, {}),
        (
            '[x*10 for x in [0,1,2]]',
            [D('0'), D('10'), D('20')],
            [0, 10, 20],
            {},
            {},
        ),
        ('(1,2,3)', (D('1'), D('2'), D('3')), (1, 2, 3), {}, {}),
        ('{3,2,10}', {D('2'), D('3'), D('10')}, {2, 3, 10}, {}, {}),
        ('{x%2 for x in [1,2,3,4]}', {D('0'), D('1')}, {0, 1}, {}, {}),
        ('{"ab": 123}', {'ab': D('123')}, {'ab': 123}, {}, {}),
        (
            '{"k"+str(x): x-1 for x in [1,2,3]}',
            {'k1': D('0'), 'k2': D('1'), 'k3': D('2')},
            {'k1': 0, 'k2': 1, 'k3': 2},
            {},
            {},
        ),
        ('3 in [1,2,3]', True, True, {}, {}),
        ('[1,2,3,12,3].count(3)', 2, 2, {}, {}),
        ('{1,2} & {2,3}', {D('2')}, {2}, {}, {}),
        ('"item4"', 'item4', 'item4', {}, {}),
        ('"{}4".format("item")', 'item4', 'item4', {}, {}),
        ('money = 1000', None, None, {'money': D('1000')}, {'money': 1000}),
        (
            'money = 1000; money * 2',
            D('2000'),
            2000,
            {'money': D('1000')},
            {'money': 1000},
        ),
        (
            'money = 1000; f"{money}원"',
            '1000원',
            '1000원',
            {'money': D('1000')},
            {'money': 1000},
        ),
        (
            'a = 11;\nif a > 10:\n    a += 100\na',
            D('111'),
            111,
            {'a': D(111)},
            {'a': 111},
        ),
    ],
)
async def test_calculate_fine(
    expr: str,
    expected_decimal_result,
    expected_num_result,
    expected_decimal_local: dict,
    expected_num_local: dict,
):
    bot = FakeBot()

    decimal_result, decimal_local = await bot.run_in_other_process(
        calculate, expr, decimal_mode=True,
    )

    num_result, num_local = await bot.run_in_other_process(
        calculate, expr, decimal_mode=False,
    )

    assert expected_decimal_result == decimal_result
    assert expected_decimal_local.keys() == decimal_local.keys()

    for key in decimal_local.keys():
        expected = expected_decimal_local[key]
        local = decimal_local[key]

        assert type(expected) == type(local)

        if callable(expected):
            assert expected(1) == local(1)
        else:
            assert expected == local

    assert expected_num_result == num_result
    assert expected_num_local.keys() == num_local.keys()

    for key in num_local.keys():
        expected = expected_num_local[key]
        local = num_local[key]

        assert type(expected) == type(local)

        assert expected == local