This repository has been archived by the owner on Oct 1, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 17
/
test_django_transactions.py
185 lines (132 loc) · 4.76 KB
/
test_django_transactions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
from aiohttp import WSMsgType
from aiohttp.web import Application, AppRunner, TCPSite
from aiohttp_json_rpc import JsonRpc, RpcInvalidParamsError
import aiohttp
import asyncio
import json
import pytest
import logging
logger = logging.getLogger('test')
pytestmark = pytest.mark.django(reason='Depends on Django')
async def watchdog(futures):
logger.info('Watchdog: started')
await asyncio.sleep(2)
for future in futures:
if not future.done():
future.cancel()
logger.error(
'Watchdog: killed Client #{}'.format(future.client_id))
return 0
async def client(id, url, numbers, sleep=0):
def encode_message(method, params=None, id=None):
return json.dumps({
'jsonrpc': '2.0',
'id': id,
'method': method,
'params': params,
})
logger.info('Client #{}: started'.format(id))
if sleep:
logger.info('Client #{}: sleeps for {}s'.format(id, sleep))
await asyncio.sleep(sleep)
session = aiohttp.ClientSession()
async with session.ws_connect(url) as ws:
msg = encode_message('add', id=0, params={
'client_id': id,
'numbers': numbers,
})
logger.debug('Client #{}: > {}'.format(id, msg))
await ws.send_str(msg)
try:
async for msg in ws:
if msg.type == WSMsgType.text:
logger.debug('Client #{}: < {}'.format(id, msg.data))
msg_data = json.loads(msg.data)
assert msg_data['id'] == 0
return msg_data['result']
finally:
await ws.close()
await session.close()
return 1
async def add(request):
from django.db import transaction
from django_project.models import Item
try:
client_id = request.params['client_id']
numbers = request.params['numbers']
except KeyError:
raise RpcInvalidParamsError
try:
logger.debug(
'Client #{}: try open transaction'.format(client_id))
with transaction.atomic():
logger.debug(
'Client #{}: transaction opened'.format(client_id))
for number in numbers:
item = Item.objects.create(number=number, client_id=client_id)
logger.debug(
'Client #{}: created {}'.format(client_id, repr(item)))
await asyncio.sleep(0.1)
logger.debug(
'Client #{}: transaction commited'.format(client_id))
except Exception as e:
logger.error(
'Client #{}: transaction aborted because of {}({})'.format(
client_id, e.__class__.__name__, e))
return 1
return 0
@pytest.mark.django_db(transaction=True)
@pytest.mark.asyncio
async def test_cuncurrent_transactions(event_loop, unused_tcp_port):
"""
This test tests aiohttp_json_rpc.django.patch_db_connections.
The test sets up a JsonRpc and starts two concurrent clients.
Both clients try to open a Django transaction.
The test is successful if only the first client is able to commit its
database changes.
Note:
The transactions are slowed down artificial and Client #2 sleeps 200ms
on start using asyncio.sleep to make the test log more readable and
ensure the transactions are concurrent.
Note:
The test starts an watchdog to make sure the test does not hang if the
rpc communication is broken or hanging.
"""
from django_project.models import Item
from aiohttp_json_rpc.django import patch_db_connections
def create_client(client_id, *args, **kwargs):
url = 'http://localhost:{}'.format(unused_tcp_port)
future = asyncio.ensure_future(client(client_id, url, *args, **kwargs))
future.client_id = client_id
return future
patch_db_connections()
# just to be sure
assert Item.objects.count() == 0
# setup rpc
app = Application()
rpc = JsonRpc()
rpc.add_methods(
('', add),
)
app.router.add_route('*', '/', rpc.handle_request)
runner = AppRunner(app)
await runner.setup()
site = TCPSite(runner, 'localhost', unused_tcp_port)
await site.start()
# setup clients and watchdog
tasks = [
create_client(1, list(range(0, 10))),
create_client(2, list(range(2, 5)), sleep=0.2),
]
tasks = [
*tasks,
asyncio.ensure_future(watchdog(tasks)),
]
# run
await asyncio.gather(*tasks)
# checks
assert Item.objects.filter(client_id=1).count() == 10
assert not Item.objects.filter(client_id=2).exists()
assert tasks[0].result() == 0 # client #1
assert tasks[1].result() == 1 # client #2
assert tasks[2].result() == 0 # watchdog