Skip to content

Commit 61c0620

Browse files
authored
Merge pull request #12 from cpppracticum/dev
add state_serialization tests
2 parents f83bbce + f38fca7 commit 61c0620

2 files changed

Lines changed: 195 additions & 5 deletions

File tree

tests/conftest.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
from urllib.parse import urljoin
99
from pathlib import Path
1010
from contextlib import contextmanager
11-
from typing import Set
11+
from typing import Set, Optional
1212

1313

1414
class Server:
1515

16-
def __init__(self, url: str, output: Path):
16+
def __init__(self, url: str, output: Optional[Path] = None):
1717
self.url = url
18-
self.file = open(output)
18+
if output:
19+
self.file = open(output)
1920

2021
def get_line(self):
2122
return self.file.readline()
@@ -25,7 +26,6 @@ def get_log(self):
2526

2627
def request(self, method, header, url, **kwargs):
2728
req = requests.Request(method, urljoin(self.url, url), headers=header, **kwargs).prepare()
28-
print(req.method)
2929
with requests.Session() as session:
3030
return session.send(req)
3131

@@ -36,6 +36,10 @@ def post(self, endpoint, data):
3636
return requests.post(urljoin(self.url, endpoint), data)
3737

3838

39+
def get_maps_from_config_file(config: Path):
40+
return json.loads(config.read_text())['maps']
41+
42+
3943
def pytest_generate_tests(metafunc):
4044
config_path = os.environ.get('CONFIG_PATH')
4145
if 'map_dict' in metafunc.fixturenames:
@@ -45,7 +49,7 @@ def pytest_generate_tests(metafunc):
4549
'map_dict',
4650
[
4751
pytest.param(map_dict, id=map_dict['name'])
48-
for map_dict in json.loads(config_path.read_text())['maps']
52+
for map_dict in get_maps_from_config_file(config_path)
4953
],
5054
)
5155
if 'config' in metafunc.fixturenames:
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import os
2+
import time
3+
import pytest
4+
from pathlib import Path
5+
from collections import defaultdict
6+
from dataclasses import dataclass
7+
from typing import List
8+
9+
import docker
10+
11+
from contextlib import contextmanager
12+
13+
import conftest as utils
14+
15+
client = docker.from_env()
16+
17+
18+
def get_image_name():
19+
return os.environ['IMAGE_NAME']
20+
21+
22+
def network_name():
23+
return os.environ.get('DOCKER_NETWORK')
24+
25+
26+
def volume_path():
27+
return Path(os.environ['VOLUME_PATH'])
28+
29+
30+
def remove_state_file(state):
31+
(Path(volume_path()) / state).unlink()
32+
33+
34+
@contextmanager
35+
def run_server(state, remove_state=False):
36+
server_domain = os.environ.get('SERVER_DOMAIN', '127.0.0.1')
37+
server_port = os.environ.get('SERVER_PORT', '8080')
38+
docker_network = os.environ.get('DOCKER_NETWORK')
39+
40+
entrypoint = [
41+
"/app/game_server",
42+
"--config-file", "/app/data/config.json",
43+
"--www-root", "/app/static/",
44+
"--state-file", f"/tmp/volume/{state}",
45+
"--save-state-period", "1000"
46+
]
47+
kwargs = {
48+
'detach': True,
49+
'entrypoint': entrypoint,
50+
'auto_remove': True,
51+
'ports': {f"{server_port}/tcp": server_port},
52+
'volumes': {volume_path(): {'bind': '/tmp/volume', 'mode': 'rw'}},
53+
}
54+
if docker_network:
55+
kwargs['network'] = docker_network
56+
if server_domain != '127.0.0.1':
57+
kwargs['name'] = server_domain
58+
container = client.containers.run(
59+
get_image_name(),
60+
**kwargs
61+
)
62+
63+
for i in range(2000):
64+
log = container.logs().decode('utf-8')
65+
if log.find('server started') != -1:
66+
break
67+
time.sleep(0.001)
68+
69+
server = utils.Server(f'http://{server_domain}:{server_port}/')
70+
try:
71+
yield server, container
72+
finally:
73+
try:
74+
container.stop()
75+
except docker.errors.APIError:
76+
pass
77+
if remove_state:
78+
remove_state_file(state)
79+
80+
81+
@dataclass
82+
class Cache:
83+
tokens: List[str] = None
84+
state: dict = None
85+
86+
def __post_init__(self):
87+
self.tokens = list()
88+
self.state = dict()
89+
90+
91+
@pytest.mark.parametrize('method', ['GET'])
92+
def test_stop(method):
93+
state_file = 'state'
94+
cache = defaultdict(Cache)
95+
with run_server(state_file) as (server, container):
96+
for map_dict in utils.get_maps_from_config_file(Path(os.environ['CONFIG_PATH'])):
97+
for name in ['user1', 'user2']:
98+
map_id = map_dict['id']
99+
100+
res = utils.join_to_map(server, name, map_id)
101+
token = res.json()['authToken']
102+
103+
cache[map_id].tokens.append(token)
104+
105+
for map_id, item in cache.items():
106+
token = item.tokens[0]
107+
request = 'api/v1/game/state'
108+
header = {'content-type': 'application/json', 'authorization': f'Bearer {token}'}
109+
res = server.request(method, header, request)
110+
cache[map_id].state = res.json()
111+
112+
with run_server(state_file, remove_state=True) as (server, container):
113+
for item in cache.values():
114+
for token in item.tokens:
115+
request = 'api/v1/game/state'
116+
header = {'content-type': 'application/json', 'authorization': f'Bearer {token}'}
117+
res = server.request(method, header, request)
118+
assert res.json() == item.state
119+
120+
121+
@pytest.mark.parametrize('method', ['GET'])
122+
def test_kill(method):
123+
state_file = 'state'
124+
cache = defaultdict(Cache)
125+
with run_server(state_file) as (server, container):
126+
for map_dict in utils.get_maps_from_config_file(Path(os.environ['CONFIG_PATH'])):
127+
for name in ['user1', 'user2']:
128+
map_id = map_dict['id']
129+
130+
res = utils.join_to_map(server, name, map_id)
131+
token = res.json()['authToken']
132+
133+
cache[map_id].tokens.append(token)
134+
135+
for map_id, item in cache.items():
136+
token = item.tokens[0]
137+
request = 'api/v1/game/state'
138+
header = {'content-type': 'application/json', 'authorization': f'Bearer {token}'}
139+
res = server.request(method, header, request)
140+
cache[map_id].state = res.json()
141+
142+
container.kill()
143+
144+
with run_server(state_file, remove_state=True) as (server, container):
145+
for item in cache.values():
146+
for token in item.tokens:
147+
request = 'api/v1/game/state'
148+
header = {'content-type': 'application/json', 'authorization': f'Bearer {token}'}
149+
res = server.request(method, header, request)
150+
assert res.json() != item.state
151+
152+
153+
@pytest.mark.parametrize('method', ['GET'])
154+
def test_tick(method):
155+
state_file = 'state'
156+
cache = defaultdict(Cache)
157+
with run_server(state_file) as (server, container):
158+
for map_dict in utils.get_maps_from_config_file(Path(os.environ['CONFIG_PATH'])):
159+
for name in ['user1', 'user2']:
160+
map_id = map_dict['id']
161+
162+
res = utils.join_to_map(server, name, map_id)
163+
token = res.json()['authToken']
164+
165+
cache[map_id].tokens.append(token)
166+
167+
utils.tick(server, 5000*1000)
168+
utils.tick(server, 5000*1000)
169+
utils.tick(server, 5000*1000)
170+
171+
for map_id, item in cache.items():
172+
token = item.tokens[0]
173+
request = 'api/v1/game/state'
174+
header = {'content-type': 'application/json', 'authorization': f'Bearer {token}'}
175+
res = server.request(method, header, request)
176+
cache[map_id].state = res.json()
177+
178+
container.kill()
179+
180+
with run_server(state_file, remove_state=True) as (server, container):
181+
for item in cache.values():
182+
for token in item.tokens:
183+
request = 'api/v1/game/state'
184+
header = {'content-type': 'application/json', 'authorization': f'Bearer {token}'}
185+
res = server.request(method, header, request)
186+
assert res.json() == item.state

0 commit comments

Comments
 (0)