Tests & fixes for keys and os api
This commit is contained in:
parent
b8f94449f8
commit
5438ecba58
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ __pypackages__/
|
|||
/build/
|
||||
/*.egg-info/
|
||||
/dist/
|
||||
/todo.txt
|
||||
|
|
|
@ -192,7 +192,7 @@ class CCApplication(web.Application):
|
|||
asyncio.create_task(self._sender(ws, api))
|
||||
async for msg in self._json_messages(ws):
|
||||
if msg['action'] == 'event':
|
||||
for task_id in self._event_to_tids.get(msg['event'], ()):
|
||||
for task_id in api._event_to_tids.get(msg['event'], ()):
|
||||
await api._result_queues[task_id].put(msg['params'])
|
||||
elif msg['action'] == 'task_result':
|
||||
api._result_values[msg['task_id']] = msg['result']
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
from typing import Optional
|
||||
|
||||
from .base import BaseSubAPI, opt_str_return
|
||||
from .base import BaseSubAPI, lua_string, opt_int_return, opt_str_return
|
||||
|
||||
|
||||
class KeysAPI(BaseSubAPI):
|
||||
_API = 'keys'
|
||||
|
||||
async def getCode(self, name: str) -> Optional[int]:
|
||||
# replaces properties
|
||||
# keys.space → await api.keys.getCode('space')
|
||||
return opt_int_return(await self._cc._send_cmd('''
|
||||
if type({api}[{key}]) == 'number' then
|
||||
return {api}[{key}]
|
||||
end
|
||||
return nil'''.format(api=self._API, key=lua_string(name))))
|
||||
|
||||
async def getName(self, code: int) -> Optional[str]:
|
||||
return opt_str_return(await self._send('getName', code))
|
||||
|
|
|
@ -30,8 +30,8 @@ class TermMixin:
|
|||
async def getSize(self) -> Tuple[int, int]:
|
||||
return tuple(await self._send('getSize'))
|
||||
|
||||
async def scroll(self, n: int):
|
||||
return nil_return(await self._send('scroll', n))
|
||||
async def scroll(self, lines: int):
|
||||
return nil_return(await self._send('scroll', lines))
|
||||
|
||||
async def setTextColor(self, color: int):
|
||||
return nil_return(await self._send('setTextColor', color))
|
||||
|
|
|
@ -1,21 +1,15 @@
|
|||
from contextlib import asynccontextmanager
|
||||
from typing import Optional, List
|
||||
|
||||
from .base import (
|
||||
BaseSubAPI, LuaTable, LuaNum,
|
||||
nil_return, str_return, opt_str_return, number_return, int_return, bool_return
|
||||
)
|
||||
|
||||
|
||||
class CCEventQueue(BaseSubAPI):
|
||||
def __init__(self, cc, targetEvent):
|
||||
super().__init__(cc)
|
||||
self._targetEvent = targetEvent
|
||||
|
||||
async def __aenter__(self):
|
||||
self._q, self._tid = await self._cc._start_queue(self._targetEvent)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
await self._cc._stop_queue(self._tid)
|
||||
class CCEventQueue:
|
||||
def __init__(self, q):
|
||||
self._q = q
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
@ -42,13 +36,8 @@ class OSAPI(BaseSubAPI):
|
|||
async def run(self, environment: LuaTable, programPath: str, *args: List[str]):
|
||||
return bool_return(await self._send('run', environment, programPath, *args))
|
||||
|
||||
async def loadAPI(self, path: str):
|
||||
return bool_return(await self._send('loadAPI', path))
|
||||
|
||||
async def unloadAPI(self, name: str):
|
||||
return nil_return(await self._send('unloadAPI', name))
|
||||
|
||||
def registerEventQueue(self, targetEvent: str) -> CCEventQueue:
|
||||
@asynccontextmanager
|
||||
async def captureEvent(self, targetEvent: str) -> CCEventQueue:
|
||||
'''
|
||||
Use this function instead loop over pullEvent/pullEventRaw.
|
||||
|
||||
|
@ -60,41 +49,56 @@ class OSAPI(BaseSubAPI):
|
|||
There exist some dead intervals of time, while pullEvent is going to be transferred to python side.
|
||||
Lua side can receive and discard timer event since there's no consumer for it.
|
||||
|
||||
registerEventQueue gives you reliable way of receiving events without losses.
|
||||
captureEvent gives you reliable way of receiving events without losses.
|
||||
Register queue before firing a timer, start a timer, listen for messages in queue.
|
||||
|
||||
# it's significant here: start queue before starting a timer
|
||||
async with api.os.registerEventQueue('timer') as timer_queue:
|
||||
async with api.os.captureEvent('timer') as timer_queue:
|
||||
myTimer = await api.os.startTimer(3)
|
||||
async for e in timer_queue:
|
||||
if e[1] == myTimer:
|
||||
async for etid, in timer_queue:
|
||||
if etid == myTimer:
|
||||
await api.print('Timer reached')
|
||||
break
|
||||
'''
|
||||
return CCEventQueue(self._cc, targetEvent)
|
||||
q, tid = await self._cc._start_queue(targetEvent)
|
||||
try:
|
||||
yield CCEventQueue(q)
|
||||
finally:
|
||||
await self._cc._stop_queue(tid)
|
||||
|
||||
async def queueEvent(self, event: str, *params):
|
||||
return nil_return(await self._send('queueEvent', event, *params))
|
||||
return nil_return(await self._send('queueEvent', event, *params, omit_nulls=False))
|
||||
|
||||
async def clock(self) -> LuaNum:
|
||||
# number of game ticks * 0.05, roughly seconds
|
||||
return number_return(await self._send('clock'))
|
||||
|
||||
# regarding ingame parameter below:
|
||||
# python has great stdlib to deal with real current time
|
||||
# we keep here only in-game time methods and parameters
|
||||
|
||||
async def time(self) -> LuaNum:
|
||||
# in hours 0..24
|
||||
return number_return(await self._send('time', 'ingame'))
|
||||
|
||||
async def day(self) -> int:
|
||||
return int_return(await self._send('day', 'ingame'))
|
||||
|
||||
async def epoch(self) -> int:
|
||||
return int_return(await self._send('epoch', 'ingame'))
|
||||
|
||||
async def sleep(self, seconds: LuaNum):
|
||||
return nil_return(await self._send('sleep', seconds))
|
||||
|
||||
async def startTimer(self, timeout: LuaNum) -> int:
|
||||
return int_return(await self._send('startTimer', timeout))
|
||||
|
||||
async def cancelTimer(self, timerID: int):
|
||||
return nil_return(await self._send('cancelTimer', timerID))
|
||||
|
||||
async def time(self) -> LuaNum:
|
||||
return number_return(await self._send('time'))
|
||||
|
||||
async def sleep(self, seconds: LuaNum):
|
||||
return nil_return(await self._send('sleep', seconds))
|
||||
|
||||
async def day(self) -> int:
|
||||
return int_return(await self._send('day'))
|
||||
|
||||
async def setAlarm(self, time: LuaNum) -> int:
|
||||
# takes time of the day in hours 0..24
|
||||
# returns integer alarmID
|
||||
return int_return(await self._send('setAlarm', time))
|
||||
|
||||
async def cancelAlarm(self, alarmID: int):
|
||||
|
|
144
testmod.py
144
testmod.py
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
from contextlib import contextmanager
|
||||
from time import monotonic
|
||||
from types import FunctionType
|
||||
|
||||
from computercraft.errors import LuaException, CommandException
|
||||
|
@ -15,12 +16,6 @@ async def id(api):
|
|||
await api.print('Version', await api.os.version())
|
||||
|
||||
|
||||
async def parallel(api):
|
||||
# Since os.sleep is mostly waiting for events, it doesn't block execution of parallel threads
|
||||
# and this snippet takes approximately 2 seconds to complete.
|
||||
await asyncio.gather(api.os.sleep(2), api.os.sleep(2))
|
||||
|
||||
|
||||
async def move(api):
|
||||
for _ in range(4):
|
||||
await api.turtle.forward()
|
||||
|
@ -49,6 +44,15 @@ def assert_raises(etype):
|
|||
raise AssertionError(f'Exception of type {etype} was not raised')
|
||||
|
||||
|
||||
@contextmanager
|
||||
def assert_takes_time(at_least, at_most):
|
||||
t = monotonic()
|
||||
yield
|
||||
dt = monotonic() - t
|
||||
# print(at_least, '<=', dt, '<=', at_most)
|
||||
assert at_least <= dt <= at_most
|
||||
|
||||
|
||||
class AnyInstanceOf:
|
||||
def __init__(self, cls):
|
||||
self.c = cls
|
||||
|
@ -506,8 +510,130 @@ async def test_gps_command_computer(api):
|
|||
|
||||
|
||||
async def test_keys_api(api):
|
||||
assert await api.keys.getName(65) == 'a'
|
||||
assert await api.keys.getName(32) == 'space'
|
||||
assert await api.keys.getName(13) is None # wtf?
|
||||
a = await api.keys.getCode('a')
|
||||
space = await api.keys.getCode('space')
|
||||
enter = await api.keys.getCode('enter')
|
||||
assert await api.keys.getCode('doesnotexist') is None
|
||||
assert await api.keys.getCode('getName') is None
|
||||
assert isinstance(a, int)
|
||||
assert isinstance(space, int)
|
||||
assert isinstance(enter, int)
|
||||
|
||||
assert await api.keys.getName(a) == 'a'
|
||||
assert await api.keys.getName(space) == 'space'
|
||||
assert await api.keys.getName(enter) == 'enter'
|
||||
|
||||
# for i in range(255):
|
||||
# print(i, await api.keys.getName(i))
|
||||
|
||||
await api.print('Test finished successfully')
|
||||
|
||||
|
||||
async def test_help_api(api):
|
||||
assert get_class_table(api.help.__class__) \
|
||||
== await get_object_table(api, 'help')
|
||||
|
||||
await api.help.setPath('/rom/help')
|
||||
|
||||
assert await api.help.path() == '/rom/help'
|
||||
|
||||
assert await api.help.lookup('disk') == 'rom/help/disk.txt'
|
||||
assert await api.help.lookup('abracadabra') is None
|
||||
|
||||
ts = await api.help.topics()
|
||||
assert isinstance(ts, list)
|
||||
assert len(ts) > 2
|
||||
# print(ts)
|
||||
assert 'disk' in ts
|
||||
|
||||
assert await api.help.completeTopic('di') == ['sk']
|
||||
assert await api.help.completeTopic('abracadabra') == []
|
||||
|
||||
assert await api.help.setPath('/kek') is None
|
||||
assert await api.help.path() == '/kek'
|
||||
assert await api.help.topics() == ['index']
|
||||
assert await api.help.setPath('/rom/help') is None
|
||||
|
||||
await api.print('Test finished successfully')
|
||||
|
||||
|
||||
async def test_reboot(api):
|
||||
assert await api.os.reboot() is None
|
||||
await api.print('Test finished successfully')
|
||||
|
||||
|
||||
async def test_shutdown(api):
|
||||
assert await api.os.shutdown() is None
|
||||
await api.print('Test finished successfully')
|
||||
|
||||
|
||||
async def test_os_api(api):
|
||||
tbl = await get_object_table(api, 'os')
|
||||
|
||||
# use methods with get*
|
||||
del tbl['function']['computerID']
|
||||
del tbl['function']['computerLabel']
|
||||
|
||||
# use captureEvent
|
||||
del tbl['function']['pullEvent']
|
||||
del tbl['function']['pullEventRaw']
|
||||
|
||||
# we are in python world, loading lua modules is useless
|
||||
del tbl['function']['loadAPI']
|
||||
del tbl['function']['unloadAPI']
|
||||
|
||||
# remove complex date formatting function in favor of python stdlib
|
||||
del tbl['function']['date']
|
||||
|
||||
tbl['function']['captureEvent'] = True
|
||||
|
||||
assert get_class_table(api.os.__class__) == tbl
|
||||
|
||||
with assert_takes_time(1.5, 3):
|
||||
async with api.os.captureEvent('timer') as timer_queue:
|
||||
timer_id = await api.os.startTimer(2)
|
||||
async for etid, in timer_queue:
|
||||
if etid == timer_id:
|
||||
await api.print('Timer reached')
|
||||
break
|
||||
|
||||
timer_id = await api.os.startTimer(20)
|
||||
assert isinstance(timer_id, int)
|
||||
assert await api.os.cancelTimer(timer_id) is None
|
||||
assert await api.os.cancelTimer(timer_id) is None
|
||||
|
||||
alarm_id = await api.os.setAlarm(0.0)
|
||||
assert isinstance(alarm_id, int)
|
||||
assert await api.os.cancelAlarm(alarm_id) is None
|
||||
assert await api.os.cancelAlarm(alarm_id) is None
|
||||
|
||||
with assert_takes_time(1.5, 3):
|
||||
assert await api.os.sleep(2) is None
|
||||
|
||||
assert (await api.os.version()).startswith('CraftOS ')
|
||||
assert isinstance(await api.os.getComputerID(), int)
|
||||
|
||||
assert await api.os.setComputerLabel(None) is None
|
||||
assert await api.os.getComputerLabel() is None
|
||||
assert await api.os.setComputerLabel('altair') is None
|
||||
assert await api.os.getComputerLabel() == 'altair'
|
||||
assert await api.os.setComputerLabel(None) is None
|
||||
assert await api.os.getComputerLabel() is None
|
||||
|
||||
assert isinstance(await api.os.epoch(), int)
|
||||
assert isinstance(await api.os.day(), int)
|
||||
assert isinstance(await api.os.time(), (int, float))
|
||||
assert isinstance(await api.os.clock(), (int, float))
|
||||
|
||||
# TODO: run method
|
||||
|
||||
await api.print('Test finished successfully')
|
||||
|
||||
|
||||
async def test_parallel(api):
|
||||
with assert_takes_time(1.5, 3):
|
||||
# Since os.sleep is mostly waiting for events, it doesn't block execution of parallel threads
|
||||
# and this snippet takes approximately 2 seconds to complete.
|
||||
await asyncio.gather(api.os.sleep(2), api.os.sleep(2))
|
||||
|
||||
await api.print('Test finished successfully')
|
||||
|
|
Loading…
Reference in a new issue