Tests & fixes for keys and os api

This commit is contained in:
neumond 2020-06-28 08:19:01 +03:00
parent b8f94449f8
commit 5438ecba58
6 changed files with 186 additions and 46 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ __pypackages__/
/build/
/*.egg-info/
/dist/
/todo.txt

View file

@ -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']

View file

@ -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))

View file

@ -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))

View file

@ -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):

View file

@ -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')