Skip to content

Commit

Permalink
Merge pull request #430 from PerchunPak/transform-fraction-seconds-to…
Browse files Browse the repository at this point in the history
…-milliseconds

Transform fraction seconds to milliseconds
  • Loading branch information
kevinkjt2000 authored Oct 27, 2022
2 parents 149a5a8 + db6f8c2 commit 21c6d04
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 7 deletions.
15 changes: 12 additions & 3 deletions mcstatus/bedrock_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,28 @@ def parse_response(data: bytes, latency: float) -> "BedrockStatusResponse":

def read_status(self) -> BedrockStatusResponse:
start = perf_counter()
data = self._read_status()
end = perf_counter()
return self.parse_response(data, (end - start) * 1000)

def _read_status(self) -> bytes:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(self.timeout)

s.sendto(self.request_status_data, self.address)
data, _ = s.recvfrom(2048)

return self.parse_response(data, (perf_counter() - start))
return data

async def read_status_async(self) -> BedrockStatusResponse:
start = perf_counter()
stream = None
data = await self._read_status_async()
end = perf_counter()

return self.parse_response(data, (end - start) * 1000)

async def _read_status_async(self) -> bytes:
stream = None
try:
conn = asyncio_dgram.connect(self.address)
stream = await asyncio.wait_for(conn, timeout=self.timeout)
Expand All @@ -69,7 +78,7 @@ async def read_status_async(self) -> BedrockStatusResponse:
if stream is not None:
stream.close()

return self.parse_response(data, (perf_counter() - start))
return data


class BedrockStatusResponse:
Expand Down
8 changes: 4 additions & 4 deletions mcstatus/pinger.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def read_status(self) -> "PingResponse":
except ValueError:
raise IOError("Received invalid JSON")
try:
return PingResponse(raw, latency=(received - start))
return PingResponse(raw, latency=(received - start) * 1000)
except ValueError as e:
raise IOError(f"Received invalid status response: {e}")

Expand All @@ -141,7 +141,7 @@ def test_ping(self) -> float:
f"Received mangled ping response packet (expected token {self.ping_token}, received {received_token})"
)

return received - sent
return (received - sent) * 1000


class AsyncServerPinger(ServerPinger):
Expand Down Expand Up @@ -171,7 +171,7 @@ async def read_status(self) -> "PingResponse":
except ValueError:
raise IOError("Received invalid JSON")
try:
return PingResponse(raw, latency=(received - start))
return PingResponse(raw, latency=(received - start) * 1000)
except ValueError as e:
raise IOError(f"Received invalid status response: {e}")

Expand All @@ -192,7 +192,7 @@ async def test_ping(self) -> float:
f"Received mangled ping response packet (expected token {self.ping_token}, received {received_token})"
)

return received - sent
return (received - sent) * 1000


class PingResponse:
Expand Down
59 changes: 59 additions & 0 deletions tests/test_async_pinger.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import asyncio
import time
from unittest import mock

import pytest

Expand Down Expand Up @@ -91,3 +93,60 @@ def test_test_ping_wrong_token(self):

with pytest.raises(IOError):
async_decorator(self.pinger.test_ping)()

@pytest.mark.asyncio
async def test_latency_is_real_number(self):
"""`time.perf_counter` returns fractional seconds, we must convert it to milliseconds."""

def mocked_read_buffer():
time.sleep(0.001)
return mock.DEFAULT

with mock.patch.object(FakeAsyncConnection, "read_buffer") as mocked:
mocked.side_effect = mocked_read_buffer
mocked.return_value.read_varint = lambda: 0 # overwrite `async` here
mocked.return_value.read_utf = (
lambda: """
{
"description": "A Minecraft Server",
"players": {"max": 20, "online": 0},
"version": {"name": "1.8-pre1", "protocol": 44}
}
"""
) # overwrite `async` here
pinger = AsyncServerPinger(
FakeAsyncConnection(), # type: ignore[arg-type]
address=Address("localhost", 25565),
version=44,
)

pinger.connection.receive(
bytearray.fromhex(
"7200707B226465736372697074696F6E223A2241204D696E65637261667420536572766572222C22706C6179657273223A"
"7B226D6178223A32302C226F6E6C696E65223A307D2C2276657273696F6E223A7B226E616D65223A22312E382D70726531"
"222C2270726F746F636F6C223A34347D7D"
)
)
# we slept 1ms, so this should be always ~1.
assert (await pinger.read_status()).latency >= 1

@pytest.mark.asyncio
async def test_test_ping_is_in_milliseconds(self):
"""`time.perf_counter` returns fractional seconds, we must convert it to milliseconds."""

def mocked_read_buffer():
time.sleep(0.001)
return mock.DEFAULT

with mock.patch.object(FakeAsyncConnection, "read_buffer") as mocked:
mocked.side_effect = mocked_read_buffer
mocked.return_value.read_varint = lambda: 1 # overwrite `async` here
mocked.return_value.read_long = lambda: 123456789 # overwrite `async` here
pinger = AsyncServerPinger(
FakeAsyncConnection(), # type: ignore[arg-type]
address=Address("localhost", 25565),
version=44,
ping_token=123456789,
)
# we slept 1ms, so this should be always ~1.
assert await pinger.test_ping() >= 1
45 changes: 45 additions & 0 deletions tests/test_bedrock_status.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import time
from unittest import mock

import pytest

from mcstatus.address import Address
from mcstatus.bedrock_status import BedrockServerStatus, BedrockStatusResponse


Expand All @@ -19,3 +25,42 @@ def test_bedrock_response_contains_expected_fields():
assert "version" in parsed.__dict__
assert "brand" in parsed.version.__dict__
assert "protocol" in parsed.version.__dict__


def test_latency_is_real_number():
"""`time.perf_counter` returns fractional seconds, we must convert it to milliseconds."""

def mocked_read_status():
time.sleep(0.001)
return mock.DEFAULT

pinger = BedrockServerStatus(Address("localhost", 25565))
with mock.patch.object(pinger, "_read_status") as mocked_read, mock.patch.object(
pinger, "parse_response"
) as mocked_parse_response:
mocked_read.side_effect = mocked_read_status

pinger.read_status()

# we slept 1ms, so this should be always ~1.
assert mocked_parse_response.call_args[0][1] >= 1


@pytest.mark.asyncio
async def test_async_latency_is_real_number():
"""`time.perf_counter` returns fractional seconds, we must convert it to milliseconds."""

def mocked_read_status():
time.sleep(0.001)
return mock.DEFAULT

pinger = BedrockServerStatus(Address("localhost", 25565))
with mock.patch.object(pinger, "_read_status_async") as mocked_read, mock.patch.object(
pinger, "parse_response"
) as mocked_parse_response:
mocked_read.side_effect = mocked_read_status

await pinger.read_status_async()

# we slept 1ms, so this should be always ~1.
assert mocked_parse_response.call_args[0][1] >= 1
64 changes: 64 additions & 0 deletions tests/test_pinger.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import time
from unittest import mock

import pytest

from mcstatus.address import Address
Expand Down Expand Up @@ -78,6 +81,67 @@ def test_test_ping_wrong_token(self):
with pytest.raises(IOError):
self.pinger.test_ping()

def test_latency_is_real_number(self):
"""`time.perf_counter` returns fractional seconds, we must convert it to milliseconds."""

def mocked_read_buffer():
time.sleep(0.001)
return mock.DEFAULT

with mock.patch.object(Connection, "read_buffer") as mocked:
mocked.side_effect = mocked_read_buffer
mocked.return_value.read_varint.return_value = 0
mocked.return_value.read_utf.return_value = """
{
"description": "A Minecraft Server",
"players": {"max": 20, "online": 0},
"version": {"name": "1.8-pre1", "protocol": 44}
}
"""
pinger = ServerPinger(
Connection(), # type: ignore[arg-type]
address=Address("localhost", 25565),
version=44,
)

pinger.connection.receive(
bytearray.fromhex(
"7200707B226465736372697074696F6E223A2241204D696E65637261667420536572766572222C22706C6179657273223A"
"7B226D6178223A32302C226F6E6C696E65223A307D2C2276657273696F6E223A7B226E616D65223A22312E382D70726531"
"222C2270726F746F636F6C223A34347D7D"
)
)
# we slept 1ms, so this should be always ~1.
assert pinger.read_status().latency >= 1

def test_test_ping_is_in_milliseconds(self):
"""`time.perf_counter` returns fractional seconds, we must convert it to milliseconds."""

def mocked_read_buffer():
time.sleep(0.001)
return mock.DEFAULT

with mock.patch.object(Connection, "read_buffer") as mocked:
mocked.side_effect = mocked_read_buffer
mocked.return_value.read_varint.return_value = 1
mocked.return_value.read_long.return_value = 123456789
pinger = ServerPinger(
Connection(), # type: ignore[arg-type]
address=Address("localhost", 25565),
version=44,
ping_token=123456789,
)

pinger.connection.receive(
bytearray.fromhex(
"7200707B226465736372697074696F6E223A2241204D696E65637261667420536572766572222C22706C6179657273223A"
"7B226D6178223A32302C226F6E6C696E65223A307D2C2276657273696F6E223A7B226E616D65223A22312E382D70726531"
"222C2270726F746F636F6C223A34347D7D"
)
)
# we slept 1ms, so this should be always ~1.
assert pinger.test_ping() >= 1


class TestPingResponse:
def test_raw(self):
Expand Down

0 comments on commit 21c6d04

Please sign in to comment.