aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdi Feschiyan <553226+refresher@users.noreply.github.com>2020-06-18 13:12:31 +0300
committerEdi Feschiyan <553226+refresher@users.noreply.github.com>2020-06-18 13:12:31 +0300
commit80159d8789fe2ea0b36d84b83348813f67e18652 (patch)
treea997c5013257bdb7d02518d86c21595c15a33ced
parent8a8b87e65c0b3d579f8ea420e23a9cd07528dfe1 (diff)
Rearranging files for distribution, setup.py modifications
-rw-r--r--.gitignore1
-rw-r--r--pyagl/services/audiomixer.py (renamed from services/audiomixer.py)10
-rw-r--r--pyagl/services/base.py (renamed from services/base.py)2
-rw-r--r--pyagl/services/bluetooth-map.py (renamed from services/bluetooth-map.py)0
-rw-r--r--pyagl/services/bluetooth-pbap.py (renamed from services/bluetooth-pbap.py)0
-rw-r--r--pyagl/services/bluetooth.py (renamed from services/bluetooth.py)8
-rw-r--r--pyagl/services/geoclue.py (renamed from services/geoclue.py)10
-rw-r--r--pyagl/services/gps.py (renamed from services/gps.py)10
-rw-r--r--pyagl/services/mediaplayer.py (renamed from services/mediaplayer.py)36
-rw-r--r--pyagl/services/network.py (renamed from services/network.py)0
-rw-r--r--pyagl/services/nfc.py (renamed from services/nfc.py)10
-rw-r--r--pyagl/services/taskmanager.py (renamed from services/taskmanager.py)0
-rw-r--r--pyagl/services/weather.py (renamed from services/weather.py)10
-rw-r--r--pyagl/tests/test_bluetooth.py105
-rw-r--r--pyagl/tests/test_gps.py73
-rw-r--r--pyagl/tests/test_weather.py (renamed from templates/test/{{cookiecutter.file_name}}.py)0
-rw-r--r--setup.py26
-rw-r--r--templates/cookiecutter.json8
-rw-r--r--templates/service/cookiecutter.json6
-rw-r--r--templates/service/{{cookiecutter.file_name}}.py9
-rw-r--r--templates/{{cookiecutter.services_dir}}/{{cookiecutter.service_slug}}.py17
-rw-r--r--templates/{{cookiecutter.tests_dir}}/test_{{cookiecutter.service_slug}}.py23
22 files changed, 298 insertions, 66 deletions
diff --git a/.gitignore b/.gitignore
index 67ad1bc..a98ea7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
.idea/*
-scratch
__pycache__
downloads/
diff --git a/services/audiomixer.py b/pyagl/services/audiomixer.py
index e594471..3f35bc8 100644
--- a/services/audiomixer.py
+++ b/pyagl/services/audiomixer.py
@@ -1,4 +1,4 @@
-from aglbaseservice import AGLBaseService, AFBResponse
+from pyagl.services.base import AGLBaseService, AFBResponse
import asyncio
import os
@@ -73,15 +73,15 @@ async def main():
if args.subscribe:
for event in args.subscribe:
- id = await ams.subscribe(event)
- print(f'Subscribing to {event} with id {id}')
+ msgid = await ams.subscribe(event)
+ print(f'Subscribing to {event} with id {msgid}')
r = AFBResponse(await ams.response())
print(r)
if args.unsubscribe:
for event in args.unsubscribe:
- id = await ams.unsubscribe(event)
- print(f'Unsubscribing from {event} with id {id}')
+ msgid = await ams.unsubscribe(event)
+ print(f'Unsubscribing from {event} with id {msgid}')
r = AFBResponse(await ams.response())
print(r)
diff --git a/services/base.py b/pyagl/services/base.py
index 03d4793..ce5c8c7 100644
--- a/services/base.py
+++ b/pyagl/services/base.py
@@ -80,8 +80,6 @@ class AFBResponse:
self.msgid = int(data[1])
self.status = data[2]['request']['status']
self.info = data[2]['request']['info']
- # raise ValueError(f'AFB returned erroneous response {data}')
- # if 'request' not in data[2] or 'response' not in data[2]:
if 'response' in data[2]:
self.data = data[2]['response']
diff --git a/services/bluetooth-map.py b/pyagl/services/bluetooth-map.py
index e69de29..e69de29 100644
--- a/services/bluetooth-map.py
+++ b/pyagl/services/bluetooth-map.py
diff --git a/services/bluetooth-pbap.py b/pyagl/services/bluetooth-pbap.py
index e69de29..e69de29 100644
--- a/services/bluetooth-pbap.py
+++ b/pyagl/services/bluetooth-pbap.py
diff --git a/services/bluetooth.py b/pyagl/services/bluetooth.py
index b73f8a8..d111d09 100644
--- a/services/bluetooth.py
+++ b/pyagl/services/bluetooth.py
@@ -1,4 +1,4 @@
-from aglbaseservice import AGLBaseService, AFBResponse
+from pyagl.services.base import AGLBaseService, AFBResponse
import asyncio
import os
@@ -23,8 +23,6 @@ class BluetoothService(AGLBaseService):
parser.add_argument('--confirm_pairing', metavar='pincode')
parser.add_argument('--remove_device', metavar='dev_88_0F_10_96_D3_20', help='Remove paired device')
-
-
def __init__(self, ip, port=None, service='agl-service-bluetooth'):
super().__init__(api='Bluetooth-Manager', ip=ip, port=port, service=service)
@@ -75,8 +73,8 @@ async def main(loop):
bts = await BluetoothService(ip=args.ipaddr, port=args.port)
if args.default_adapter:
- id = await bts.default_adapter()
- print(f'Requesting default adapter with id {id}')
+ msgid = await bts.default_adapter()
+ print(f'Requesting default adapter with id {msgid}')
r = AFBResponse(await bts.response())
print(r)
diff --git a/services/geoclue.py b/pyagl/services/geoclue.py
index df1afd6..c14c6db 100644
--- a/services/geoclue.py
+++ b/pyagl/services/geoclue.py
@@ -1,4 +1,4 @@
-from aglbaseservice import AGLBaseService, AFBResponse
+from pyagl.services.base import AGLBaseService, AFBResponse
import asyncio
import os
@@ -26,14 +26,14 @@ async def main(loop):
gcs = await GeoClueService(args.ipaddr)
if args.location:
- id = await gcs.location()
- print(f'Sent location request with messageid {id}')
+ msgid = await gcs.location()
+ print(f'Sent location request with messageid {msgid}')
print(AFBResponse(await gcs.response()))
if args.subscribe:
for event in args.subscribe:
- id = await gcs.subscribe(event)
- print(f"Subscribed for {event} with messageid {id}")
+ msgid = await gcs.subscribe(event)
+ print(f"Subscribed for {event} with messageid {msgid}")
print(AFBResponse(await gcs.response()))
if args.listener:
async for response in gcs.listener():
diff --git a/services/gps.py b/pyagl/services/gps.py
index 1905e0f..69c253e 100644
--- a/services/gps.py
+++ b/pyagl/services/gps.py
@@ -1,4 +1,4 @@
-from aglbaseservice import AGLBaseService, AFBResponse
+from pyagl.services.base import AGLBaseService, AFBResponse
import asyncio
import os
@@ -33,8 +33,8 @@ async def main(loop):
gpss.logger.setLevel(args.loglevel)
if args.record:
- id = await gpss.record(args.record)
- print(f'Sent gps record request with value {args.record} with messageid {id}')
+ msgid = await gpss.record(args.record)
+ print(f'Sent gps record request with value {args.record} with messageid {msgid}')
print(AFBResponse(await gpss.response()))
if args.location:
@@ -43,8 +43,8 @@ async def main(loop):
if args.subscribe:
for event in args.subscribe:
- id = await gpss.subscribe(event)
- print(f'Subscribed for event {event} with messageid {id}')
+ msgid = await gpss.subscribe(event)
+ print(f'Subscribed for event {event} with messageid {msgid}')
print(AFBResponse(await gpss.response()))
if args.listener:
diff --git a/services/mediaplayer.py b/pyagl/services/mediaplayer.py
index 9291981..4472531 100644
--- a/services/mediaplayer.py
+++ b/pyagl/services/mediaplayer.py
@@ -1,4 +1,4 @@
-from aglbaseservice import AGLBaseService, AFBResponse
+from pyagl.services.base import AGLBaseService, AFBResponse
from typing import Union
import logging
import asyncio
@@ -91,49 +91,49 @@ async def main(loop):
MPS = await MediaPlayerService(ip=args.ipaddr)
if args.playlist:
- id = await MPS.playlist()
+ msgid = await MPS.playlist()
r = AFBResponse(await MPS.response())
for l in r.data['list']: print(l)
if args.control:
- id = await MPS.control(args.control)
- print(f'Sent {args.control} request with messageid {id}')
+ msgid = await MPS.control(args.control)
+ print(f'Sent {args.control} request with messageid {msgid}')
r = AFBResponse(await MPS.response())
print(r)
if args.seek:
- id = await MPS.control('seek', args.seek)
- print(f'Sent seek request to {args.seek} msec with messageid {id}')
+ msgid = await MPS.control('seek', args.seek)
+ print(f'Sent seek request to {args.seek} msec with messageid {msgid}')
r = AFBResponse(await MPS.response())
print(r)
if args.fastforward:
- id = await MPS.control('fast-forward', args.fastforward)
- print(f'Sent fast-forward request for {args.fastforward} msec with messageid {id}')
+ msgid = await MPS.control('fast-forward', args.fastforward)
+ print(f'Sent fast-forward request for {args.fastforward} msec with messageid {msgid}')
r = AFBResponse(await MPS.response())
print(r)
if args.rewind:
- id = await MPS.control('rewind', -args.rewind)
- print(f'Sent rewind request for {args.rewind} msec with messageid {id}')
+ msgid = await MPS.control('rewind', -args.rewind)
+ print(f'Sent rewind request for {args.rewind} msec with messageid {msgid}')
r = AFBResponse(await MPS.response())
print(r)
if args.picktrack:
- id = await MPS.control('pick-track', args.picktrack)
- print(f'Sent pick-track request with index {args.rewind} with messageid {id}')
+ msgid = await MPS.control('pick-track', args.picktrack)
+ print(f'Sent pick-track request with index {args.rewind} with messageid {msgid}')
r = AFBResponse(await MPS.response())
print(r)
if args.volume:
- id = await MPS.control('volume', int(args.volume))
- print(f'Sent volume request: {args.rewind} with messageid {id}')
+ msgid = await MPS.control('volume', int(args.volume))
+ print(f'Sent volume request: {args.rewind} with messageid {msgid}')
r = AFBResponse(await MPS.response())
print(r)
if args.loop:
- id = await MPS.control('loop', args.loop)
- print(f'Sent loop-state request: {args.loop} with messageid {id}')
+ msgid = await MPS.control('loop', args.loop)
+ print(f'Sent loop-state request: {args.loop} with messageid {msgid}')
r = AFBResponse(await MPS.response())
print(r)
@@ -145,8 +145,8 @@ async def main(loop):
if args.subscribe:
for event in args.subscribe:
- id = await MPS.subscribe(event)
- print(f"Subscribed for event {event} with messageid {id}")
+ msgid = await MPS.subscribe(event)
+ print(f"Subscribed for event {event} with messageid {msgid}")
r = await MPS.response()
print(r)
diff --git a/services/network.py b/pyagl/services/network.py
index e69de29..e69de29 100644
--- a/services/network.py
+++ b/pyagl/services/network.py
diff --git a/services/nfc.py b/pyagl/services/nfc.py
index c966c4a..78f9b6e 100644
--- a/services/nfc.py
+++ b/pyagl/services/nfc.py
@@ -1,4 +1,4 @@
-from aglbaseservice import AGLBaseService, AFBResponse
+from pyagl.services.base import AGLBaseService, AFBResponse
import asyncio
class NFCService(AGLBaseService):
@@ -21,15 +21,15 @@ async def main(loop):
if args.subscribe:
for event in args.subscribe:
- id = await nfcs.subscribe(event)
- print(f"Subscribing for event {event} with messageid {id}")
+ msgid = await nfcs.subscribe(event)
+ print(f"Subscribing for event {event} with messageid {msgid}")
r = AFBResponse(await nfcs.response())
print(r)
if args.unsubscribe:
for event in args.unsubscribe:
- id = await nfcs.unsubscribe(event)
- print(f"Unsubscribing for event {event} with messageid {id}")
+ msgid = await nfcs.unsubscribe(event)
+ print(f"Unsubscribing for event {event} with messageid {msgid}")
r = AFBResponse(await nfcs.response())
print(r)
diff --git a/services/taskmanager.py b/pyagl/services/taskmanager.py
index e69de29..e69de29 100644
--- a/services/taskmanager.py
+++ b/pyagl/services/taskmanager.py
diff --git a/services/weather.py b/pyagl/services/weather.py
index f3c02d0..389be70 100644
--- a/services/weather.py
+++ b/pyagl/services/weather.py
@@ -1,6 +1,6 @@
+from pyagl.services.base import AGLBaseService, AFBResponse
import asyncio
import json
-from aglbaseservice import AGLBaseService, AFBResponse
class WeatherService(AGLBaseService):
@@ -23,19 +23,19 @@ async def main():
args = WeatherService.parser.parse_args()
aws = await WeatherService(ip=args.ipaddr, port=args.port)
if args.current:
- id = await aws.current_weather()
+ msgid = await aws.current_weather()
resp = AFBResponse(await aws.response())
print(json.dumps(resp.data, indent=2))
if args.apikey:
- id = await aws.apikey()
+ msgid = await aws.apikey()
resp = AFBResponse(await aws.response())
print(resp.data['api_key'])
if args.subscribe:
for event in args.subscribe:
- id = await aws.subscribe(event)
- print(f'Subscribed for event {event} with messageid {id}')
+ msgid = await aws.subscribe(event)
+ print(f'Subscribed for event {event} with messageid {msgid}')
resp = AFBResponse(await aws.response())
print(resp)
diff --git a/pyagl/tests/test_bluetooth.py b/pyagl/tests/test_bluetooth.py
new file mode 100644
index 0000000..0670f5e
--- /dev/null
+++ b/pyagl/tests/test_bluetooth.py
@@ -0,0 +1,105 @@
+import asyncio
+import os
+import pytest
+
+from pyagl.services.base import AFBResponse, AFBT
+from pyagl.services.bluetooth import BluetoothService as BTS
+import logging
+
+logger = logging.getLogger(f'pytest-{BTS.service}')
+logger.setLevel(logging.DEBUG)
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.fixture(scope='module')
+def event_loop():
+ loop = asyncio.get_event_loop()
+ yield loop
+ loop.close()
+
+
+@pytest.fixture(scope='module')
+async def service():
+ address = os.environ.get('AGL_TGT_IP', 'localhost')
+ port = os.environ.get('AGL_TGT_PORT', None)
+
+ bts = await BTS(ip=address, port=port)
+ yield bts
+ await bts.websocket.close()
+
+
+@pytest.mark.xfail
+@pytest.fixture(scope='module')
+def btaddr():
+ bthtestaddr = os.environ.get('AGL_TEST_BT_ADDR', None)
+ if not bthtestaddr:
+ pytest.xfail('No test bluetooth test address set in environment variables')
+
+ return bthtestaddr
+
+
+@pytest.mark.dependency
+async def test_default_adapter(event_loop, service: BTS):
+ msgid = await service.default_adapter()
+ resp = AFBResponse(await service.response())
+ assert resp.status == 'success', resp
+ assert 'adapter' in resp.data.keys()
+ assert resp.data['adapter'] == 'hci0'
+
+
+@pytest.mark.dependency(depends=['test_default_adapter'])
+async def test_managed_objects(event_loop, service: BTS):
+ msgid = await service.managed_objects()
+ resp = AFBResponse(await service.response())
+ assert resp.status == 'success', str(resp)
+
+
+@pytest.mark.dependency(depends=['test_default_adapter'])
+async def test_has_single_adapter(event_loop, service: BTS):
+ msgid = await service.managed_objects()
+ resp = AFBResponse(await service.response())
+ assert len(resp.data['adapters']) == 1, \
+ f'Detected {len(resp.data["adapters"])} adapters. Multiple adapters may also break testing'
+
+
+@pytest.mark.dependency(depends=['test_default_adapter'])
+async def test_adapter_state(event_loop, service: BTS):
+ msgid = await service.adapter_state('hci0')
+ resp = AFBResponse(await service.response())
+ assert resp.status == 'success', 'adapter state verb failed'
+
+
+async def test_pairing_verb(event_loop, service: BTS, btaddr):
+ msgid = await service.pair(btaddr)
+ resp = await service.afbresponse()
+ assert msgid == resp.msgid
+ assert resp.status == 'success', f'pair verb failed - {resp.info}'
+
+
+async def test_connect_verb(event_loop, service: BTS, btaddr):
+ msgid = await service.connect(btaddr)
+ resp = await service.afbresponse()
+ assert msgid == resp.msgid
+ assert resp.status == 'success', f'connect verb failed - {resp.info}'
+
+
+async def test_disconnect_verb(event_loop, service: BTS, btaddr):
+ msgid = await service.disconnect(btaddr)
+ resp = await service.afbresponse()
+ assert msgid == resp.msgid
+ assert resp.status == 'success', f'disconnect verb failed - {resp.info}'
+
+
+async def test_remove_pairing_verb(event_loop, service: BTS, btaddr):
+ msgid = await service.remove_device(btaddr)
+ resp = await service.afbresponse()
+ assert msgid == resp.msgid
+ assert resp.status == 'success'
+
+
+@pytest.mark.xfail(reason='This is expected to fail because there has to be an ongoing pairing attempt')
+async def test_confirm_pairing_verb(event_loop, service: BTS, btaddr):
+ msgid = await service.confirm_pairing(pincode='123456')
+ resp = await service.afbresponse()
+ assert msgid == resp.msgid
+ assert resp.status == 'success', f'confirm_pairing verb failed - {resp.info}'
diff --git a/pyagl/tests/test_gps.py b/pyagl/tests/test_gps.py
new file mode 100644
index 0000000..7e56d1e
--- /dev/null
+++ b/pyagl/tests/test_gps.py
@@ -0,0 +1,73 @@
+import asyncio
+import os
+import pytest
+import logging
+from pyagl.services.base import AFBResponse, AFBT
+from pyagl.services.gps import GPSService as GPS
+from concurrent.futures import TimeoutError
+
+pytestmark = pytest.mark.asyncio
+
+@pytest.fixture(scope='module')
+def event_loop():
+ loop = asyncio.get_event_loop()
+ yield loop
+ loop.close()
+
+@pytest.fixture(scope='module')
+async def service():
+ address = os.environ.get('AGL_TGT_IP', 'localhost')
+ port = os.environ.get('AGL_TGT_PORT', None)
+ gpss = await GPS(ip=address, port=port)
+ yield gpss
+ await gpss.websocket.close()
+
+# @pytest.fixture(scope='module')
+# async def response(event_loop, service):
+# async for _response in service.listener():
+# yield _response
+
+async def test_location_verb(event_loop, service: GPS):
+ msgid = await service.location()
+ resp = AFBResponse(await service.response())
+ assert resp.msgid == msgid
+
+@pytest.mark.xfail # expecting this to fail because of "No 3D GNSS fix" and GPS is unavailable
+async def test_location_result(event_loop, service: GPS):
+ msgid = await service.location()
+ resp = AFBResponse(await service.response())
+ assert resp.status == 'success'
+
+async def test_subscribe_verb(event_loop, service: GPS):
+ msgid = await service.subscribe("")
+ resp = AFBResponse(await service.response())
+ assert resp.msgid == msgid
+ assert resp.status == 'success'
+
+async def test_subscribe_location(event_loop, service: GPS):
+ msgid = await service.subscribe('location')
+ resp = AFBResponse(await service.response())
+ assert resp.msgid == msgid
+ assert resp.status == 'success'
+
+async def test_unsubscribe(event_loop, service: GPS):
+ msgid = await service.unsubscribe('location')
+ resp = AFBResponse(await service.response())
+ assert resp.msgid == msgid
+ assert resp.status == 'success'
+
+@pytest.mark.xfail # expecting this to fail because of "No 3D GNSS fix" and GPS is unavailable
+async def test_location_events(event_loop, service: GPS):
+ msgid = await service.subscribe('location')
+ resp = AFBResponse(await service.response())
+ assert resp.msgid == msgid
+ assert resp.status == 'success' # successful subscription
+ try:
+ resp = await asyncio.wait_for(service.afbresponse(), 10)
+ resp = AFBResponse(resp)
+ assert resp.type == AFBT.EVENT, f'Expected EVENT response, got {resp.type.name} instead'
+ # TODO one more assert for the actual received event, haven't received a location event yet
+ except TimeoutError:
+ pytest.xfail("Did not receive location event")
+
+
diff --git a/templates/test/{{cookiecutter.file_name}}.py b/pyagl/tests/test_weather.py
index e69de29..e69de29 100644
--- a/templates/test/{{cookiecutter.file_name}}.py
+++ b/pyagl/tests/test_weather.py
diff --git a/setup.py b/setup.py
index e69de29..afb45ab 100644
--- a/setup.py
+++ b/setup.py
@@ -0,0 +1,26 @@
+import setuptools
+
+with open("README.md", "r") as fh:
+ long_description = fh.read()
+
+setuptools.setup(
+ name="pyagl",
+ version="0.0.1",
+ author="Edi Feschiyan",
+ author_email="edi.feschiyan@konsulko.com",
+ description="Python bindings and tests for Automotive Grade Linux services",
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ url="https://github.com/refresher/pyagl",
+ packages=setuptools.find_packages(),
+ license="Apache 2.0",
+ install_requires=['websockets', 'parse', 'asyncssh', 'pytest', 'pytest-asyncio'],
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: Apache Software License",
+ "Operating System :: POSIX :: Linux",
+ ],
+ python_requires='>=3.6',
+)
diff --git a/templates/cookiecutter.json b/templates/cookiecutter.json
new file mode 100644
index 0000000..c4afdd6
--- /dev/null
+++ b/templates/cookiecutter.json
@@ -0,0 +1,8 @@
+{
+ "services_dir": "services",
+ "tests_dir": "tests",
+ "service_slug": "service",
+ "aglsystemdservice": "agl-service-something",
+ "classname": "NewService",
+ "api": "serviceapi"
+}
diff --git a/templates/service/cookiecutter.json b/templates/service/cookiecutter.json
deleted file mode 100644
index f37ea1f..0000000
--- a/templates/service/cookiecutter.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "aglsystemdservice": "agl-service-something",
- "classname": "NewService",
- "api": "serviceapi",
- "verblist": ["subscribe", "unsubscribe"]
-} \ No newline at end of file
diff --git a/templates/service/{{cookiecutter.file_name}}.py b/templates/service/{{cookiecutter.file_name}}.py
deleted file mode 100644
index 6defd28..0000000
--- a/templates/service/{{cookiecutter.file_name}}.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from aglbaseservice import AGLBaseService, AFBResponse
-import asyncio
-import os
-
-class {{cookiecutter.classname}}(AGLBaseService):
-
- def __init__(self, ip, port=None, service='{{cookiecutter.aglsystemdservice}}'):
- super().__init__(api='{{cookiecutter.api}}', ip=ip, port=port, service=service)
-
diff --git a/templates/{{cookiecutter.services_dir}}/{{cookiecutter.service_slug}}.py b/templates/{{cookiecutter.services_dir}}/{{cookiecutter.service_slug}}.py
new file mode 100644
index 0000000..dab3a68
--- /dev/null
+++ b/templates/{{cookiecutter.services_dir}}/{{cookiecutter.service_slug}}.py
@@ -0,0 +1,17 @@
+from pyagl.services.base import AGLBaseService, AFBResponse
+import asyncio
+import os
+
+
+class {{cookiecutter.classname}}(AGLBaseService):
+ service = '{{cookiecutter.aglsystemdservice}}'
+ parser = AGLBaseService.getparser()
+
+ def __init__(self, ip, port=None, service='{{cookiecutter.aglsystemdservice}}'):
+ super().__init__(api='{{cookiecutter.api}}', ip=ip, port=port, service=service)
+ # more init stuff specific to the new service
+
+async def main(loop):
+ args = {{cookiecutter.classname}}.parser.parse_args()
+ svc = {{cookiecutter.classname}}(args.ipaddr)
+
diff --git a/templates/{{cookiecutter.tests_dir}}/test_{{cookiecutter.service_slug}}.py b/templates/{{cookiecutter.tests_dir}}/test_{{cookiecutter.service_slug}}.py
new file mode 100644
index 0000000..e193205
--- /dev/null
+++ b/templates/{{cookiecutter.tests_dir}}/test_{{cookiecutter.service_slug}}.py
@@ -0,0 +1,23 @@
+import asyncio
+import os
+import pytest
+import logging
+from pyagl.services.base import AFBResponse, AFBT
+from concurrent.futures import TimeoutError
+
+from pyagl.services.{{cookiecutter.service_slug}} import {{cookiecutter.classname}}
+pytestmark = pytest.mark.asyncio
+
+@pytest.fixture(scope='module')
+def event_loop():
+ loop = asyncio.get_event_loop()
+ yield loop
+ loop.close()
+
+@pytest.fixture(scope='module')
+async def service():
+ address = os.environ.get('AGL_TGT_IP', 'localhost')
+ port = os.environ.get('AGL_TGT_PORT', None)
+ svc = await {{cookiecutter.classname}}(ip=address, port=port)
+ yield svc
+ await svc.websocket.close()