1 from minecraft
import (
2 SUPPORTED_MINECRAFT_VERSIONS
, SUPPORTED_PROTOCOL_VERSIONS
,
3 PROTOCOL_VERSION_INDICES
,
5 from minecraft
.networking
.packets
import clientbound
, serverbound
6 from minecraft
.networking
.connection
import Connection
7 from minecraft
.exceptions
import (
8 VersionMismatch
, LoginDisconnect
, InvalidState
, IgnorePacket
11 from . import fake_server
18 class ConnectTest(fake_server
._FakeServerTest
):
19 def test_connect(self
):
22 class client_handler_type(fake_server
.FakeClientHandler
):
23 def handle_play_start(self
):
24 super(ConnectTest
.client_handler_type
, self
).handle_play_start()
25 self
.write_packet(clientbound
.play
.KeepAlivePacket(
26 keep_alive_id
=1223334444))
28 def handle_play_packet(self
, packet
):
29 super(ConnectTest
.client_handler_type
, self
) \
30 .handle_play_packet(packet
)
31 if isinstance(packet
, serverbound
.play
.KeepAlivePacket
):
32 assert packet
.keep_alive_id
== 1223334444
33 raise fake_server
.FakeServerDisconnect
36 class ReconnectTest(ConnectTest
):
39 def _start_client(self
, client
):
40 def handle_login_disconnect(packet
):
41 if 'Please reconnect' in packet
.json_data
:
42 # Override the default behaviour of raising a fatal exception.
46 client
.register_packet_listener(
47 handle_login_disconnect
, clientbound
.login
.DisconnectPacket
,
50 def handle_play_disconnect(packet
):
51 if 'Please reconnect' in packet
.json_data
:
53 elif 'Test successful' in packet
.json_data
:
54 raise fake_server
.FakeServerTestSuccess
55 client
.register_packet_listener(
56 handle_play_disconnect
, clientbound
.play
.DisconnectPacket
)
60 class client_handler_type(fake_server
.FakeClientHandler
):
61 def handle_login(self
, packet
):
62 if self
.server
.test_case
.phase
== 0:
63 self
.server
.test_case
.phase
= 1
64 raise fake_server
.FakeServerDisconnect('Please reconnect (0).')
65 super(ReconnectTest
.client_handler_type
, self
).handle_login(packet
)
67 def handle_play_start(self
):
68 if self
.server
.test_case
.phase
== 1:
69 self
.server
.test_case
.phase
= 2
70 raise fake_server
.FakeServerDisconnect('Please reconnect (1).')
72 assert self
.server
.test_case
.phase
== 2
73 raise fake_server
.FakeServerDisconnect('Test successful (2).')
76 class PingTest(ConnectTest
):
77 def _start_client(self
, client
):
78 def handle_ping(latency_ms
):
79 assert 0 <= latency_ms
< 60000
80 raise fake_server
.FakeServerTestSuccess
81 client
.status(handle_status
=False, handle_ping
=handle_ping
)
84 class StatusTest(ConnectTest
):
85 def _start_client(self
, client
):
86 def handle_status(status_dict
):
87 assert status_dict
['description'] == {'text': 'FakeServer'}
88 raise fake_server
.FakeServerTestSuccess
89 client
.status(handle_status
=handle_status
, handle_ping
=False)
92 class DefaultStatusTest(ConnectTest
):
94 class FakeStdOut(io
.BytesIO
):
95 def write(self
, data
):
96 if isinstance(data
, str):
97 data
= data
.encode('utf8')
98 super(FakeStdOut
, self
).write(data
)
99 sys
.stdout
, self
.old_stdout
= FakeStdOut(), sys
.stdout
102 sys
.stdout
, self
.old_stdout
= self
.old_stdout
, None
104 def _start_client(self
, client
):
106 output
= sys
.stdout
.getvalue()
107 assert re
.match(b
'{.*}\\nPing: \\d+ ms\\n$', output
), \
108 'Invalid stdout contents: %r.' % output
109 raise fake_server
.FakeServerTestSuccess
110 client
.handle_exit
= handle_exit
112 client
.status(handle_status
=None, handle_ping
=None)
115 class ConnectCompressionLowTest(ConnectTest
):
116 compression_threshold
= 0
119 class ConnectCompressionHighTest(ConnectTest
):
120 compression_threshold
= 256
123 class AllowedVersionsTest(fake_server
._FakeServerTest
):
124 versions
= list(SUPPORTED_MINECRAFT_VERSIONS
.items())
125 test_indices
= (0, len(versions
) // 2, len(versions
) - 1)
127 client_handler_type
= ConnectTest
.client_handler_type
129 def test_with_version_names(self
):
130 for index
in self
.test_indices
:
132 server_version
=self
.versions
[index
][0],
133 client_versions
={v
[0] for v
in self
.versions
[:index
+1]})
135 def test_with_protocol_numbers(self
):
136 for index
in self
.test_indices
:
138 server_version
=self
.versions
[index
][0],
139 client_versions
={v
[1] for v
in self
.versions
[:index
+1]})
142 class LoginDisconnectTest(fake_server
._FakeServerTest
):
143 def test_login_disconnect(self
):
144 with self
.assertRaisesRegexp(LoginDisconnect
, r
'You are banned'):
147 class client_handler_type(fake_server
.FakeClientHandler
):
148 def handle_login(self
, login_start_packet
):
149 raise fake_server
.FakeServerDisconnect('You are banned.')
152 class ConnectTwiceTest(fake_server
._FakeServerTest
):
153 def test_connect(self
):
154 with self
.assertRaisesRegexp(InvalidState
, 'existing connection'):
157 class client_handler_type(fake_server
.FakeClientHandler
):
158 def handle_play_start(self
):
159 super(ConnectTwiceTest
.client_handler_type
, self
) \
161 raise fake_server
.FakeServerDisconnect('Test complete.')
163 def _start_client(self
, client
):
168 class ConnectStatusTest(ConnectTwiceTest
):
169 def _start_client(self
, client
):
174 class LoginPluginTest(fake_server
._FakeServerTest
):
175 class client_handler_type(fake_server
.FakeClientHandler
):
176 def handle_login(self
, login_start_packet
):
177 request
= clientbound
.login
.PluginRequestPacket(
178 message_id
=1, channel
='pyCraft:tests/fail', data
=b
'ignored')
179 self
.write_packet(request
)
180 response
= self
.read_packet()
181 assert isinstance(response
, serverbound
.login
.PluginResponsePacket
)
182 assert response
.message_id
== request
.message_id
183 assert response
.successful
is False
184 assert response
.data
is None
186 request
= clientbound
.login
.PluginRequestPacket(
187 message_id
=2, channel
='pyCraft:tests/echo', data
=b
'hello')
188 self
.write_packet(request
)
189 response
= self
.read_packet()
190 assert isinstance(response
, serverbound
.login
.PluginResponsePacket
)
191 assert response
.message_id
== request
.message_id
192 assert response
.successful
is True
193 assert response
.data
== request
.data
195 super(LoginPluginTest
.client_handler_type
, self
) \
196 .handle_login(login_start_packet
)
198 def handle_play_start(self
):
199 super(LoginPluginTest
.client_handler_type
, self
) \
201 raise fake_server
.FakeServerDisconnect
203 def _start_client(self
, client
):
204 def handle_plugin_request(packet
):
205 if packet
.channel
== 'pyCraft:tests/echo':
206 client
.write_packet(serverbound
.login
.PluginResponsePacket(
207 message_id
=packet
.message_id
, data
=packet
.data
))
209 client
.register_packet_listener(
210 handle_plugin_request
, clientbound
.login
.PluginRequestPacket
,
213 super(LoginPluginTest
, self
)._start
_client
(client
)
215 def test_login_plugin_messages(self
):
219 class EarlyPacketListenerTest(ConnectTest
):
220 """ Early packet listeners should be called before ordinary ones, even when
221 the early packet listener is registered afterwards.
223 def _start_client(self
, client
):
224 @client.listener(clientbound
.play
.JoinGamePacket
)
225 def handle_join(packet
):
226 assert early_handle_join
.called
, \
227 'Ordinary listener called before early listener.'
228 handle_join
.called
= True
229 handle_join
.called
= False
231 @client.listener(clientbound
.play
.JoinGamePacket
, early
=True)
232 def early_handle_join(packet
):
233 early_handle_join
.called
= True
234 early_handle_join
.called
= False
236 @client.listener(clientbound
.play
.DisconnectPacket
)
237 def handle_disconnect(packet
):
238 assert early_handle_join
.called
, 'Early listener not called.'
239 assert handle_join
.called
, 'Ordinary listener not called.'
240 raise fake_server
.FakeServerTestSuccess
245 class IgnorePacketTest(ConnectTest
):
246 """ Raising 'minecraft.networking.connection.IgnorePacket' from within a
247 packet listener should prevent any subsequent packet listeners from
248 being called, and, if the listener is early, should prevent the default
249 behaviour from being triggered.
252 def _start_client(self
, client
):
253 keep_alive_ids_incoming
= []
254 keep_alive_ids_outgoing
= []
256 def handle_keep_alive_1(packet
):
257 keep_alive_ids_incoming
.append(packet
.keep_alive_id
)
258 if packet
.keep_alive_id
== 1:
260 client
.register_packet_listener(
261 handle_keep_alive_1
, clientbound
.play
.KeepAlivePacket
, early
=True)
263 def handle_keep_alive_2(packet
):
264 keep_alive_ids_incoming
.append(packet
.keep_alive_id
)
265 assert packet
.keep_alive_id
> 1
266 if packet
.keep_alive_id
== 2:
268 client
.register_packet_listener(
269 handle_keep_alive_2
, clientbound
.play
.KeepAlivePacket
)
271 def handle_keep_alive_3(packet
):
272 keep_alive_ids_incoming
.append(packet
.keep_alive_id
)
273 assert packet
.keep_alive_id
== 3
274 client
.register_packet_listener(
275 handle_keep_alive_3
, clientbound
.play
.KeepAlivePacket
)
277 def handle_outgoing_keep_alive_2(packet
):
278 keep_alive_ids_outgoing
.append(packet
.keep_alive_id
)
279 assert 2 <= packet
.keep_alive_id
<= 3
280 if packet
.keep_alive_id
== 2:
282 client
.register_packet_listener(
283 handle_outgoing_keep_alive_2
, serverbound
.play
.KeepAlivePacket
,
284 outgoing
=True, early
=True)
286 def handle_outgoing_keep_alive_3(packet
):
287 keep_alive_ids_outgoing
.append(packet
.keep_alive_id
)
288 assert packet
.keep_alive_id
== 3
290 client
.register_packet_listener(
291 handle_outgoing_keep_alive_3
, serverbound
.play
.KeepAlivePacket
,
294 def handle_outgoing_keep_alive_none(packet
):
295 keep_alive_ids_outgoing
.append(packet
.keep_alive_id
)
297 client
.register_packet_listener(
298 handle_outgoing_keep_alive_none
, serverbound
.play
.KeepAlivePacket
,
301 def handle_disconnect(packet
):
302 assert keep_alive_ids_incoming
== [1, 2, 2, 3, 3, 3], \
303 'Incoming keep-alive IDs %r != %r' % \
304 (keep_alive_ids_incoming
, [1, 2, 2, 3, 3, 3])
305 assert keep_alive_ids_outgoing
== [2, 3, 3], \
306 'Outgoing keep-alive IDs %r != %r' % \
307 (keep_alive_ids_incoming
, [2, 3, 3])
308 client
.register_packet_listener(
309 handle_disconnect
, clientbound
.play
.DisconnectPacket
)
313 class client_handler_type(fake_server
.FakeClientHandler
):
314 __slots__
= '_keep_alive_ids_returned'
316 def __init__(self
, *args
, **kwds
):
317 super(IgnorePacketTest
.client_handler_type
, self
).__init
__(
319 self
._keep
_alive
_ids
_returned
= []
321 def handle_play_start(self
):
322 super(IgnorePacketTest
.client_handler_type
, self
)\
324 self
.write_packet(clientbound
.play
.KeepAlivePacket(
326 self
.write_packet(clientbound
.play
.KeepAlivePacket(
328 self
.write_packet(clientbound
.play
.KeepAlivePacket(
330 self
.handle_play_server_disconnect('Test complete.')
332 def handle_play_packet(self
, packet
):
333 super(IgnorePacketTest
.client_handler_type
, self
) \
334 .handle_play_packet(packet
)
335 if isinstance(packet
, serverbound
.play
.KeepAlivePacket
):
336 self
._keep
_alive
_ids
_returned
.append(packet
.keep_alive_id
)
338 def handle_play_client_disconnect(self
):
339 assert self
._keep
_alive
_ids
_returned
== [3], \
340 'Returned keep-alive IDs %r != %r' % \
341 (self
._keep
_alive
_ids
_returned
, [3])
342 raise fake_server
.FakeServerTestSuccess
345 class HandleExceptionTest(ConnectTest
):
346 ignore_extra_exceptions
= True
348 def _start_client(self
, client
):
349 message
= 'Min skoldpadda ar inte snabb, men den ar en skoldpadda.'
351 @client.listener(clientbound
.login
.LoginSuccessPacket
)
352 def handle_login_success(_packet
):
353 raise Exception(message
)
355 @client.exception_handler(early
=True)
356 def handle_exception(exc
, _exc_info
):
357 assert isinstance(exc
, Exception) and exc
.args
== (message
,)
358 raise fake_server
.FakeServerTestSuccess
363 class ExceptionReconnectTest(ConnectTest
):
364 class CustomException(Exception):
370 def _start_client(self
, client
):
371 @client.listener(clientbound
.play
.JoinGamePacket
)
372 def handle_join_game(packet
):
375 raise self
.CustomException
377 raise fake_server
.FakeServerTestSuccess
379 @client.exception_handler(self
.CustomException
, early
=True)
380 def handle_custom_exception(exc
, exc_info
):
381 client
.disconnect(immediate
=True)
386 class client_handler_type(ConnectTest
.client_handler_type
):
387 def handle_abnormal_disconnect(self
, exc
):
391 class VersionNegotiationEdgeCases(fake_server
._FakeServerTest
):
392 earliest_version
= SUPPORTED_PROTOCOL_VERSIONS
[0]
393 latest_version
= SUPPORTED_PROTOCOL_VERSIONS
[-1]
395 fake_version
= max(PROTOCOL_VERSION_INDICES
.keys()) + 1
396 fake_version_index
= max(PROTOCOL_VERSION_INDICES
.values()) + 1
399 PROTOCOL_VERSION_INDICES
[self
.fake_version
] = self
.fake_version_index
400 super(VersionNegotiationEdgeCases
, self
).setUp()
403 super(VersionNegotiationEdgeCases
, self
).tearDown()
404 del PROTOCOL_VERSION_INDICES
[self
.fake_version
]
406 def test_client_protocol_unsupported(self
):
407 self
._test
_client
_protocol
(version
=self
.fake_version
)
409 def test_client_protocol_unknown(self
):
410 self
._test
_client
_protocol
(version
='surprise me!')
412 def test_client_protocol_invalid(self
):
413 self
._test
_client
_protocol
(version
=object())
415 def _test_client_protocol(self
, version
):
416 with self
.assertRaisesRegexp(ValueError, 'Unsupported version'):
417 self
._test
_connect
(client_versions
={version}
)
419 def test_server_protocol_unsupported(self
, client_versions
=None):
420 with self
.assertRaisesRegexp(VersionMismatch
, 'not supported'):
421 self
._test
_connect
(client_versions
=client_versions
,
422 server_version
=self
.fake_version
)
424 def test_server_protocol_unsupported_direct(self
):
425 self
.test_server_protocol_unsupported({self
.latest_version
})
427 def test_server_protocol_disallowed(self
, client_versions
=None):
428 if client_versions
is None:
429 client_versions
= set(SUPPORTED_PROTOCOL_VERSIONS
) \
430 - {self
.latest_version
}
431 with self
.assertRaisesRegexp(VersionMismatch
, 'not allowed'):
432 self
._test
_connect
(client_versions
={self
.earliest_version
},
433 server_version
=self
.latest_version
)
435 def test_server_protocol_disallowed_direct(self
):
436 self
.test_server_protocol_disallowed({self
.earliest_version
})
438 def test_default_protocol_version(self
, status_response
=None):
439 if status_response
is None:
440 status_response
= '{"description": {"text": "FakeServer"}}'
442 class ClientHandler(fake_server
.FakeClientHandler
):
443 def handle_status(self
, request_packet
):
444 packet
= clientbound
.status
.ResponsePacket()
445 packet
.json_response
= status_response
446 self
.write_packet(packet
)
448 def handle_play_start(self
):
449 super(ClientHandler
, self
).handle_play_start()
450 raise fake_server
.FakeServerDisconnect('Test complete.')
452 def make_connection(*args
, **kwds
):
453 kwds
['initial_version'] = self
.earliest_version
454 return Connection(*args
, **kwds
)
456 self
._test
_connect
(server_version
=self
.earliest_version
,
457 client_handler_type
=ClientHandler
,
458 connection_type
=make_connection
)
460 def test_default_protocol_version_empty(self
):
461 with self
.assertRaisesRegexp(IOError, 'Invalid server status'):
462 self
.test_default_protocol_version(status_response
='{}')
464 def test_default_protocol_version_eof(self
):
465 class ClientHandler(fake_server
.FakeClientHandler
):
466 def handle_status(self
, request_packet
):
467 raise fake_server
.FakeServerDisconnect(
468 'Refusing to handle status request, for test purposes.')
470 def handle_play_start(self
):
471 super(ClientHandler
, self
).handle_play_start()
472 raise fake_server
.FakeServerDisconnect('Test complete.')
474 def make_connection(*args
, **kwds
):
475 kwds
['initial_version'] = self
.earliest_version
476 return Connection(*args
, **kwds
)
478 self
._test
_connect
(server_version
=self
.earliest_version
,
479 client_handler_type
=ClientHandler
,
480 connection_type
=make_connection
)