async def __aexit__(s, exc_type, exc, tb): if exc: return False if res is asyncio.CancelledError: with assertRaises(res): await V.task elif type(res) is PhotonsAppError: with assertRaises(PhotonsAppError, res.message): await V.task else: assert (await V.task) == res
async def check_result(expect=None): if expect is not None: assert (await result) == expect elif already_done is False: assert not result.done() elif already_done is True: assert (await result) == existing_result elif already_done == "exception": with assertRaises(ValueError, "Nope"): await result elif already_done == "cancelled": assert result.cancelled()
for packet in packets: got = await V.session.determine_needed_transport(packet, services) assert got == [Services.UDP] describe "choose_transport": async it "complains if we can't determined need transport", V: determine_needed_transport = pytest.helpers.AsyncMock(name="determine_needed_transport") determine_needed_transport.return_value = [] packet = mock.Mock(name="packet", protocol=9001, pkt_type=89) services = mock.Mock(name="services") msg = "Unable to determine what service to send packet to" kwargs = {"protocol": 9001, "pkt_type": 89} with assertRaises(NoDesiredService, msg, **kwargs): with mock.patch.object( V.session, "determine_needed_transport", determine_needed_transport ): await V.session.choose_transport(packet, services) determine_needed_transport.assert_awaited_once_with(packet, services) async it "returns the desired service or complains if can't be found", V: need = [Services.UDP] determine_needed_transport = pytest.helpers.AsyncMock(name="determine_needed_transport") determine_needed_transport.return_value = need udpservice = mock.Mock(name="udpservice") packet = mock.NonCallableMock(name="packet", spec=[])
light1: [DeviceMessages.GetPower(), DeviceMessages.SetPower(level=65535)], light2: [DeviceMessages.GetPower(), DeviceMessages.SetPower(level=0)], light3: [DeviceMessages.GetPower(), DeviceMessages.SetPower(level=65535)], } await self.assertScript(sender, gen, expected=expected) async it "does not ignore exception in generator", sender: error = Exception("NOPE") async def gen(reference, sender, **kwargs): raise error yield DeviceMessages.GetPower() expected = {light1: [], light2: [], light3: []} with assertRaises(BadRun, _errors=[error]): await self.assertScript(sender, gen, expected=expected) async it "adds exception from generator to error_catcher", sender: got = [] def err(e): got.append(e) error = Exception("NOPE") async def gen(reference, sender, **kwargs): raise error yield DeviceMessages.GetPower() expected = {light1: [], light2: [], light3: []}
" --> one = 1", " || pkt = SetPower(ack=True,res=True,source=<NOT_SPECIFIED>,sequence=<NOT_SPECIFIED>,target=None)", " ^^ level: 65535", " :: other = [1, 2]", " :: more = True", ) describe "Events": it "is a EventsHolder": assert isinstance(Events, EventsHolder) it "can register events": events = EventsHolder() assert events.events == {} with assertRaises(AttributeError): events.AMAZE_EVENT @events.register("AMAZE_EVENT") class Amaze: pass assert events.AMAZE_EVENT is Amaze it "can get a name for an event": events = EventsHolder() @events.register("AMAZE_EVENT") class Amaze: pass
describe "adding a resolver": it "adds and overrides", register: typ = mock.Mock(name="typ") resolver = mock.Mock(name="resolver") register.add(typ, resolver) assert register.resolvers[typ] is resolver resolver2 = mock.Mock(name="resolver2") register.add(typ, resolver2) assert register.resolvers[typ] is resolver2 describe "resolving": it "complains if the typ isn't registered", register: typ = mock.Mock(name="typ") with assertRaises(ResolverNotFound, wanted=typ): register.resolve(typ, "blah") it "uses registered resolver", register: ret = mock.Mock(name="ret") typ = mock.Mock(name="typ") resolver = mock.Mock(name="resolver", return_value=ret) register.add(typ, resolver) assert register.resolve(typ, "blah") is ret resolver.assert_called_once_with("blah") describe "getting a reference object": it "returns SpecialReference objects as is", register: class Reference(SpecialReference):
pkt2 = msg(one=False, two="there") assert pkt1.Key == (1024, 52, '{"one": true, "two": "hello"}') assert pkt2.Key == (1024, 52, '{"one": false, "two": "there"}') # For efficiency, the Key is cached, so if you change the payload # The key stays the same, but we can delete the key for it to be recreated pkt1.two = "tree" assert pkt1.Key == (1024, 52, '{"one": true, "two": "hello"}') del pkt1.Key assert pkt1.Key == (1024, 52, '{"one": true, "two": "tree"}') describe "MultiOptions": it "complains if we don't give it two functions": for a, b in [(None, None), (lambda: 1, None), (None, lambda: 1), (1, 2)]: with assertRaises(ProgrammerError, "Multi Options expects two callables"): MultiOptions(a, b) it "sets the two callables": determine_res_packet = mock.Mock(name="determine_res_packet") adjust_expected_number = mock.Mock(name="adjust_expected_number") options = MultiOptions(determine_res_packet, adjust_expected_number) assert options.determine_res_packet is determine_res_packet assert options.adjust_expected_number is adjust_expected_number it "has a Max helper": num = MultiOptions.Max(5) assert num([1]) == -1 assert num([0, 1, 2, 3]) == -1
it "passes through booleans": spec = boolean() meta = Meta.empty() assert spec.normalise(meta, False) is False assert spec.normalise(meta, [False]) is False assert spec.normalise(meta, True) is True assert spec.normalise(meta, [True]) is True it "complains about anything else": class Wat: pass for thing in ([], [1, 2], [True, False], {}, {1: 2}, lambda: 1, Wat, Wat()): with assertRaises(BadSpecValue): boolean().normalise(Meta.empty(), thing) describe "str_ranges": it "converts comma separated pairs into list of tuples": wanted = "1-2,3-5.5,5,6.7-9,10.1-45.6" got = str_ranges().normalise(Meta.empty(), wanted) assert got == [(1.0, 2.0), (3.0, 5.5), (5.0, 5.0), (6.7, 9.0), (10.1, 45.6)] it "can take in list of tuples": wanted = [(1.0, 2.0), (3.0, 5.5), (5.0, 5.0), (6.7, 9.0), (10.1, 45.6)] got = str_ranges().normalise(Meta.empty(), wanted) assert wanted == got it "can take in list of strings": provided = ["1.0-2.0", "3.0-5.5", "5.0", "6.7-9.0", "10.1-45.6"]
from photons_protocol.packets import PacketSpecMetaKls, dictobj, Initial from photons_protocol.types import Type as T from photons_app.errors import ProgrammerError from photons_app import helpers as hp from delfick_project.errors_pytest import assertRaises from delfick_project.norms import sb from unittest import mock import pytest import uuid describe "PacketSpecMetaKls": it "complains if we have fields that are already attributes": with assertRaises( ProgrammerError, r"Can't override attributes with fields\talready_attributes=\['items', 'values'\]", ): class Together(dictobj.PacketSpec): fields = [("items", T.Bool), ("values", T.Bool), ("one", T.Bool)] with assertRaises( ProgrammerError, r"Can't override attributes with fields\talready_attributes=\['one'\]" ): class Together2(dictobj.PacketSpec): fields = [("one", T.Bool)] def one(self): pass
d.start.assert_called_once_with() assert len(d.finish.mock_calls) == 0 for d in devices: d.finish.assert_called_once_with() async it "finishes even on error": device1 = mock.Mock(name="device1") device2 = mock.Mock(name="device2") devices = [device1, device2] for d in devices: d.start = pytest.helpers.AsyncMock(name="start") d.finish = pytest.helpers.AsyncMock(name="finish") with assertRaises(ValueError, "NOPE"): async with WithDevices(devices): for d in devices: d.start.assert_called_once_with() assert len(d.finish.mock_calls) == 0 raise ValueError("NOPE") for d in devices: d.finish.assert_called_once_with() describe "pktkeys": it "can get us deduped keys to represent the packets": msg1 = DeviceMessages.SetPower(level=65535, source=1, sequence=2, target="d073d501") msg2 = DeviceMessages.SetLabel(label="bob", source=3, sequence=4, target="d073d502") msg3 = DeviceMessages.SetLabel(label="bob", source=5, sequence=6, target="d073d503") keys = pktkeys([msg1, msg2, msg3])
assert thing == {"one": 2, "two": "yeap", "three": wat} it "complains if you try to reuse a command": store = Store() @store.command("thing") class Thing(store.Command): pass # Can use a child kls @store.command("another_path") class Other(Thing): pass # Can't use the same class though with assertRaises(CantReuseCommands): try: store.command("another_path")(Thing) except CantReuseCommands as error: assert error.reusing is Thing raise it "works": store = Store() @store.command("thing", path="/v1") class Thing(store.Command): one = dictobj.Field(sb.integer_spec) two = dictobj.Field(sb.string_spec) class Spec1:
async it "has a final future as a child of that passed in": final_future = hp.create_future() streamer = hp.ResultStreamer(final_future) streamer.final_future.cancel() assert not final_future.done() final_future = hp.create_future() streamer = hp.ResultStreamer(final_future) final_future.set_result(True) assert streamer.final_future.cancelled() final_future = hp.create_future() streamer = hp.ResultStreamer(final_future) final_future.set_exception(Exception("NOPE")) with assertRaises(Exception, "NOPE"): await streamer.final_future final_future = hp.create_future() streamer = hp.ResultStreamer(final_future) final_future.cancel() assert streamer.final_future.cancelled() describe "add generator": @pytest.fixture() async def V(self): class V: final_future = hp.create_future() error_catcher = mock.Mock(name="error_catcher")
assert found != otherfound otherfound["d073d5000001"] = 1 otherfound["d073d5000002"] = 2 otherfound["d073d5000003"] = 3 otherfound["d073d5000004"] = 4 assert found == otherfound otherfound["d073d5000004"] = 5 assert found != otherfound found["d073d5000005"] = 6 assert len(found) == 5 it "has getitem", found: with assertRaises(KeyError): found["d073d5000001"] services = mock.Mock(name="services") found["d073d5000001"] = services assert found["d073d5000001"] is services assert found["d073d500000111"] is services assert found[binascii.unhexlify("d073d5000001")] is services assert found[binascii.unhexlify("d073d500000122")] is services it "has setitem", found: found["d073d5000001"] = 1 assert found["d073d5000001"] == 1 found["d073d500000122"] = 2
called.append("c2b") collector.photons_app.cleaners.extend([cleanup1, cleanup2]) called.append(2) try: raise PhotonsAppError("YO") except: fut = hp.create_future() fut.set_result(True) await fut called.append(3) raise finally: called.append(4) with assertRaises(PhotonsAppError, "YO"): T.create(collector).run_loop(collector=collector) assert called == [1, 2, 3, 4, "c1a", "c1b", "c2a", "c2b"] it "cleans up after we finish task if it's cancelled outside", collector: called = [] class T(Task): async def execute_task(self, collector, **kwargs): called.append(1) async def cleanup1(): called.append("c1a") fut = hp.create_future()
result = list( database.execute("SELECT * FROM thingmodel WHERE one=:one", {"one": "one"}) ) assert result == [(1, "one", 1)] describe "queries": it "has methods for doing stuff with the database", database, ThingModel: one = database.queries.create_thing_model(one="one", two=True) database.add(one) database.commit() two, made = database.queries.get_or_create_thing_model(one="one", two=True) assert made == False assert one.id == two.id three, made = database.queries.get_or_create_thing_model(one="two", two=True) assert made == True database.add(three) database.commit() made = database.queries.get_thing_models().order_by(ThingModel.one.asc()).all() assert [t.as_dict() for t in made] == [t.as_dict() for t in (one, three)] one_got = database.queries.get_thing_model(one="one") assert one_got.as_dict() == one.as_dict() assert one_got.id == one.id with assertRaises(sqlalchemy.orm.exc.MultipleResultsFound): database.queries.get_one_thing_model(two=True)
{"start_index": 10 + 23, "end_index": 10 + 23}, {"start_index": 10 + 24, "end_index": 10 + 24}, {"start_index": 10 + 25, "end_index": 10 + 25}, {"start_index": 10 + 26, "end_index": 10 + 26}, {"start_index": 10 + 27, "end_index": 10 + 27}, {"start_index": 10 + 28, "end_index": 10 + 28}, ] for e, o in zip(expected_old, plan.set_color_old): for k, v in e.items(): assert v == o[k] assert plan.set_color_new.zone_index == 10 async it "complains if we have more than 82 colors": with assertRaises(PhotonsAppError, "colors can only go up to 82 colors", got=87): SetZonesPlan([["red", 80], ["blue", 7]]) async it "complains if we have no colors": with assertRaises(PhotonsAppError, "No colors were specified"): SetZonesPlan([]) async it "can create messages to send back", specifier: plan = SetZonesPlan(specifier) def make(device, options): return type("Capability", (Capability,), options)(device.cap.product) instance1 = plan.Instance( light1.serial, plan,
it "returns as is if the value is already a Services", meta, spec: val = Services.UDP assert spec.normalise(meta, val) is val it "returns the enum value if it matches", meta, spec: val = "UDP" assert spec.normalise(meta, val) is Services.UDP it "complains if we don't have a match", meta, spec: msg = "Unknown service type" services = sorted([s.name for s in Services if not s.name.startswith("RESERVED")]) assert len(services) > 0 kwargs = dict(want="WAT", available=services) with assertRaises(BadSpecValue, msg, **kwargs): spec.normalise(meta, "WAT") describe "hardcoded_discovery_spec": @pytest.fixture() def spec(self): return do.hardcoded_discovery_spec() @pytest.fixture() def ret(self): return mock.Mock(name="ret") @pytest.fixture() def fake_spec(self, ret): fake_spec = mock.Mock(name="fake_spec")
assert thing.progress_cb is progress_cb assert thing.request_handler is request_handler assert thing.path == "/v1" assert thing.store is store assert thing.request_future.done() async it "injected fields complain if they don't exist": store2 = store.clone() progress_cb = mock.Mock(name="progress_cb") request_handler = mock.Mock(name="request_handler") commander = Commander(store2) with assertRaises( BadOptionFormat, "Can't find key in options", chain=["body.args.notexisting"], key="notexisting", ): await commander.executor(progress_cb, request_handler).execute( "/v1", {"command": "fields_are_required"} ) assert False, "expected an error" async it "injected fields complain if they don't match format_into option": store2 = store.clone() progress_cb = mock.Mock(name="progress_cb") request_handler = mock.Mock(name="request_handler") commander = Commander(store2) try: await commander.executor(progress_cb, request_handler, option="asdf").execute(
async it "returns the result of waiting on the coroutine": val = str(uuid.uuid1()) async def func(): return val res = await hp.async_with_timeout(func(), timeout=10) assert res == val async it "cancels the coroutine if it doesn't respond": async def func(): await asyncio.sleep(2) start = time.time() with assertRaises(asyncio.CancelledError): await hp.async_with_timeout(func(), timeout=0.1) assert time.time() - start < 0.5 async it "cancels the coroutine and raises timeout_error": error = PhotonsAppError("Blah") async def func(): try: await asyncio.sleep(2) except asyncio.CancelledError: assert False, "Expected it to just raise the error rather than cancelling first" start = time.time() with assertRaises(PhotonsAppError, "Blah"): await hp.async_with_timeout(func(), timeout=0.1, timeout_error=error)
assert val_to_bitarray("d073d5", "test") == expected it "creates bitarray from bytes": expected = bitarray(endian="little") bts = binascii.unhexlify("d073d5") expected.frombytes(bts) assert val_to_bitarray(bts, "test") == expected it "creates bitarray from sb.NotSpecified": expected = bitarray(endian="little") assert val_to_bitarray(sb.NotSpecified, "test") == expected it "complains otherwise": doing = mock.Mock(name="doing") for val in (0, 1, None, True, False, [], [1], {1: 2}, lambda: 1): with assertRaises(BadConversion, "Couldn't get bitarray from a value", doing=doing): val_to_bitarray(val, doing) describe "BitarraySlice": @pytest.fixture() def slce(self): name = mock.Mock(name="name") typ = mock.Mock(name="typ") val = mock.Mock(name="val") size_bits = mock.Mock(name="size_bits") group = mock.Mock(name="group") return BitarraySlice(name, typ, val, size_bits, group) it "takes in things": name = mock.Mock(name="name")
es = [] def ec(e): es.append(e) with catch_errors(ec): pass assert es == [] with catch_errors(es): pass assert es == [] it "throws the error if just one": with assertRaises(ValueError, "NOPE"): with catch_errors(): raise ValueError("NOPE") it "merges multiple of the same error together": e1 = PhotonsAppError("yeap", a=1) e2 = PhotonsAppError("yeap", a=1) with assertRaises(PhotonsAppError, "yeap", a=1): with catch_errors() as ec: hp.add_error(ec, e1) raise e2 with assertRaises(PhotonsAppError, "yeap", a=1): with catch_errors() as ec: hp.add_error(ec, e1)
it "can match nested lists": for val in ([[]], ["asdf", [1]], [["asdf", True], [1]]): assert json_spec.normalise(Meta.empty(), val) == val it "can match dictionaries": for val in ({}, {"1": "2", "2": 2, "3": False}): assert json_spec.normalise(Meta.empty(), val) == val it "can match nested dictionaries": val = {"asdf": {"adf": {"asdf": 2, "adf": False, "eieu": None}}} assert json_spec.normalise(Meta.empty(), val) == val it "complains about things that aren't json like objects, callables and non string keys": for val in (type("adf", (object,), {}), any, json, lambda: 1): with assertRaises(BadSpecValue): json_spec.normalise(Meta.empty(), val) try: json_spec.normalise(Meta.empty(), {"one": {1: 2}}) assert False, "Expected an error" except BadSpecValue as error: assert error.errors[0].errors[0].message == "Expected a string" describe "Type": it "takes in struct_format and conversion": struct_format = mock.Mock(name="struct_format") conversion = mock.Mock(name="conversion") t = Type(struct_format, conversion) assert t.struct_format is struct_format assert t.conversion is conversion
loop = photons_app.loop assert loop.get_debug() describe "final_future": it "belongs to the loop": photons_app = self.make_photons_app() final_future = photons_app.final_future assert final_future._loop is photons_app.loop describe "extra_as_json": it "converts extra into json dictionary": photons_app = self.make_photons_app(extra='{"one": 2, "two": "three"}') assert photons_app.extra_as_json == {"one": 2, "two": "three"} it "complains if extra is not valid json": with assertRaises(BadOption, "The options after -- wasn't valid json"): self.make_photons_app(extra="{").extra_as_json it "can read json from a file": with hp.a_temp_file() as fle: fle.write(b'{"power": "off"}') fle.flush() assert self.make_photons_app(extra=f"file://{fle.name}").extra_as_json == { "power": "off" } path = os.path.relpath(fle.name, os.getcwd()) assert not path.startswith("/") assert self.make_photons_app(extra=f"file://{path}").extra_as_json == { "power": "off" }
cap3 = cap2(firmware_major=3, firmware_minor=4) assert cap2 != cap3 product2 = mock.Mock(name="product") cap4 = base.Capability(product2) assert cap != cap4 it "returns no items by default": product = mock.Mock(name="product") cap = base.Capability(product) assert list(cap.items()) == [] describe "Product": it "complains about not having a cap": with assertRaises( IncompleteProduct, "Product doesn't have a capability specified", name="P" ): class P(base.Product): pass it "complains about attributes not implemented": with assertRaises( IncompleteProduct, "Attribute wasn't overridden", attr="family", name="P" ): class P(base.Product): class cap(base.Capability): pass it "sets the cap as an instance of the cap class":
describe "CommandSplitter": it "can format argv": command = "{@:2}:{@:1} {@:3:}" result = CommandSplitter( {"argv": ["my_script", "one", "two", "three", "four"]}, command ).split() assert result == ["two:one", "three", "four"] command = "{@:2:4}:{@:1} {@:4:}" result = CommandSplitter( {"argv": ["my_script", "one", "two", "three", "four", "five"]}, command ).split() assert result == ["two", "three:one", "four", "five"] it "can complain about an env specifier without a name": with assertRaises(Exception, "env specifier used without saying what variable is needed"): command = "{:env}" CommandSplitter({"argv": ["my_script"]}, command).split() it "can complain if an environment variable is needed but doesn't exist": with pytest.helpers.modified_env(THING=None): with assertRaises( SystemExit, "This script requires you have a 'THING' variable in your environment" ): command = "{THING:env}" CommandSplitter({"argv": ["my_script"]}, command).split() it "doesn't complain if the environment variable exists but is empty": with pytest.helpers.modified_env(THING=""): command = "thing={THING:env}" assert CommandSplitter({"argv": ["my_script"]}, command).split() == ["thing="]
await runner.database.request(do_set) async def do_get(session, query): return (await query.get_one_thing()).as_dict() got = await runner.database.request(do_get) assert got == {"one": "one", "two": True} async it "retries on OperationalError", runner: tries = [True, True] async def do_error(session, query): tries.pop(0) raise sqlalchemy.exc.OperationalError("select", {}, "") with assertRaises(sqlalchemy.exc.OperationalError): await runner.database.request(do_error) assert tries == [] async it "can work after the first OperationalError", runner: tries = [True, True] async def do_error(session, query): tries.pop(0) if len(tries) == 1: raise sqlalchemy.exc.OperationalError("select", {}, "") else: one = await query.create_thing(one="one", two=True) session.add(one)
it "can get a path object", attrs: path = attrs.attrs_path("one", "two", 3, "four") assert isinstance(path, Path) assert path.attrs is attrs assert path.parts == ["one", "two", 3, "four"] async it "says if something is inside _attrs", attrs: assert "one" not in attrs await attrs.attrs_apply(attrs.attrs_path("one").changer_to(3), event=None) assert "one" in attrs assert attrs.one == 3 assert attrs["one"] == 3 it "complains if you try to set things on the attrs with item or attr syntax", attrs: assert "nup" not in attrs with assertRaises(TypeError, "'Attrs' object does not support item assignment"): attrs["nup"] = 3 assert "nup" not in attrs with assertRaises(AttributeError): attrs.nup = 3 assert "nup" not in attrs async it "can get attributes with item and attr syntax", attrs: with assertRaises(AttributeError, "No such attribute thing"): attrs.thing with assertRaises(KeyError, "thing"): attrs["thing"] await attrs.attrs_apply( attrs.attrs_path("thing").changer_to(3),
fut.set_exception(TypeError("NOPE")) assert fut.done() fut.reset() assert not fut.done() fut.cancel() assert fut.done() fut.reset() assert not fut.done() async it "can get and set a result": fut = hp.ResettableFuture() fut.set_result(True) assert fut.result() is True with assertRaises(hp.InvalidStateError): fut.set_result(False) assert await fut is True fut.reset() assert not fut.done() fut.set_result(False) assert fut.result() is False assert await fut is False async it "can get and set an exception": fut = hp.ResettableFuture() error = ValueError("NOPE") fut.set_exception(error)
else: assert filtr[field] is sb.NotSpecified it "defaults everything to NotSpecified": filtr = Filter.empty() assert len(filtr.fields) == 16 self.assertFltrMatches(filtr, {}) describe "from_json_str": it "treats the string as a json object": want = {"label": "kitchen", "location_name": ["one", "two"], "hue": "20-50"} expect = {"label": ["kitchen"], "location_name": ["one", "two"], "hue": [(20, 50)]} self.assertFltrMatches(Filter.from_json_str(json.dumps(want)), expect) it "complains if the string is not valid json": with assertRaises(InvalidJson): Filter.from_json_str("{") it "complains if the string is not a dictionary": for s in ('"wat"', "[]", "1"): # Make sure it's valid json json.dumps(s) # And make sure it complains it's not a dictionary with assertRaises(InvalidJson, "Expected a dictionary"): Filter.from_json_str(s) describe "from_key_value_str": it "treats the key,value as dictionary items": want = "label=bathroom,hallway location_id=identifier1 saturation=0.7,0.8-1" expect = {
assert attrs.wat is val device.validate_attr.reset_mock() val2 = mock.Mock(name="val") attrs.wat2 = val2 device.validate_attr.assert_called_once_with("wat2", val2) assert attrs.wat is val assert attrs.wat2 is val2 it "doesn't set key if validate_attr raises an error", device, attrs: assert attrs._attrs == {} attrs.wat = 2 attrs["things"] = 3 expected = {"wat": 2, "things": 3} assert attrs._attrs == expected device.validate_attr.side_effect = ValueError("NOPE") with assertRaises(ValueError, "NOPE"): attrs.nope = 2 with assertRaises(AttributeError): attrs.nope with assertRaises(ValueError, "NOPE"): attrs["hello"] = 3 with assertRaises(KeyError): attrs["hello"]