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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
# SPDX-FileCopyrightText: Christian Amsüss and the aiocoap contributors
#
# SPDX-License-Identifier: MIT
import asyncio
import aiocoap
import errno
from .test_server import WithTestServer, WithClient, no_warnings, asynctest
class TestClientWithSetHost(WithTestServer, WithClient):
set_uri_host = True
@no_warnings
@asynctest
async def test_uri_parser(self):
request = aiocoap.Message(code=aiocoap.GET)
request_uri = "coap://" + self.servernetloc + "/empty?query=a&query=b"
request.set_request_uri(request_uri, set_uri_host=self.set_uri_host)
self.assertEqual(
request.get_request_uri(),
request_uri,
"Request URL does not round-trip in request",
)
response = await self.client.request(request).response
self.assertEqual(
response.get_request_uri(),
request_uri,
"Request URL does not round-trip in response",
)
self.assertEqual(response.code, aiocoap.CONTENT, "Request URL building failed")
request = aiocoap.Message(code=aiocoap.GET)
request.set_request_uri(
"coap://" + self.servernamealias + "/empty", set_uri_host=self.set_uri_host
)
self.assertEqual(
request.get_request_uri(), "coap://" + self.servernamealias + "/empty"
)
response = await self.client.request(request).response
self.assertEqual(
response.code,
aiocoap.CONTENT,
"Resolving WithTestServer.servernamealias failed",
)
if self.set_uri_host:
self.assertEqual(
response.get_request_uri(),
"coap://" + self.servernamealias + "/empty",
"Host name did not get round-tripped",
)
else:
# The simple6 transport misreports remotes to which a socket was
# opened with a name.
if "simple6" not in list(
aiocoap.defaults.get_default_clienttransports(loop=self.loop)
):
self.assertEqual(
response.get_request_uri(),
"coap://" + self.servernetloc + "/empty",
"Response's request URI is not numeric in hostname-less query",
)
@no_warnings
@asynctest
async def test_uri_parser2(self):
"""A difficult test because it is prone to keeping the transport
around, bothering later tests"""
request = aiocoap.Message(code=aiocoap.GET)
request.set_request_uri(
"coap://" + self.servernetloc + ":9999/empty",
set_uri_host=self.set_uri_host,
)
resp = self.client.request(request).response
try:
# give the request some time to finish getaddrinfo
result = await asyncio.as_completed([resp], timeout=0.1).__next__()
except aiocoap.error.NetworkError as e:
# This is a bit stricter than what the API indicates, but hey, we
# can still relax the tests.
self.assertTrue(isinstance(e.__cause__, OSError))
# ECONNREFUSED: linux; ECONNRESET: win32
self.assertTrue(e.__cause__.errno in (errno.ECONNREFUSED, errno.ECONNRESET))
except asyncio.TimeoutError:
self.fail(
"Request to non-opened port did not come back with 'Connection Refused' immediately"
)
else:
self.fail(
"Request to non-opened port did not come back with 'Connection Refused', but another result: %s"
% (result,)
)
self.assertTrue(
request.remote.hostinfo.endswith(":9999"), "Remote port was not parsed"
)
resp.cancel()
class TestClientWithHostlessMessages(TestClientWithSetHost):
set_uri_host = False
class TestClientOther(WithTestServer, WithClient):
@no_warnings
# can't do @asynctest because of assertRaises
def test_raising(self):
"""This test obtains results via the response_raising property of a
Request."""
yieldfrom = self.loop.run_until_complete
request = aiocoap.Message(
code=aiocoap.GET, uri="coap://" + self.servernetloc + "/empty"
)
response = yieldfrom(self.client.request(request).response_raising)
self.assertEqual(
response.code,
aiocoap.CONTENT,
"Response access via response_raising failed",
)
request = aiocoap.Message(
code=aiocoap.GET, uri="coap://" + self.servernetloc + "/nonexistent"
)
## @FIXME i'd like to assertRaises(NotFound), see docstring of
# :class:`ResponseWrappingError`
self.assertRaises(
aiocoap.error.ResponseWrappingError,
yieldfrom,
self.client.request(request).response_raising,
)
@no_warnings
@asynctest
async def test_nonraising(self):
"""This test obtains results via the response_nonraising property of a
Request."""
request = aiocoap.Message(
code=aiocoap.GET, uri="coap://" + self.servernetloc + "/empty"
)
response = await self.client.request(request).response_nonraising
self.assertEqual(
response.code,
aiocoap.CONTENT,
"Response access via response_nonraising failed",
)
request = aiocoap.Message(
code=aiocoap.GET, uri="coap://cant.resolve.this.example./empty"
)
response = await self.client.request(request).response_nonraising
self.assertEqual(response.code, aiocoap.INTERNAL_SERVER_ERROR)
@no_warnings
@asynctest
async def test_freeoncancel(self):
# As there's no programmatic feedback about what actually gets sent,
# looking at the logs is the easiest option, even though it will
# require occasional adjustment when logged messages change.
#
# FIXME Currently, this *only* checks for whether later responses are
# rejected, it does *not* check for whether the response runner is
# freed as well (primarily because that'd need _del_to_be_sure to be
# useable in an async context).
# With immediate cancellation, nothing is sent. Note that we don't
# ensure this per documentation, but still it's good to see when this
# changes.
loglength = len(self.handler.list)
request = aiocoap.Message(
code=aiocoap.GET, uri="coap://" + self.servernetloc + "/empty"
)
self.resp = self.client.request(request).response
self.resp.cancel()
self.assertEqual(
loglength,
len(self.handler.list),
"Something was logged during request creation and immediate cancellation: %r"
% (self.handler.list[loglength:],),
)
# FIXME: What is slightly weird here is that on uvloop, something
# *does* get sent -- but it appears that that happens even after the
# cancellation. That behavior oly gets apparent when test_freeoncancel
# and test_freeoncancel_non were run in a single test, because then the
# ACK Content from the prior test also got counted.
@no_warnings
@asynctest
async def test_freeoncancel_non(self):
# With a NON, the response should take long. (Not trying to race the
# "I'm taking too long"-ACK by making the sleep short enough).
# Note that the resource implementation deliberately sends responses as CON,
# as to allow us to peek into the internals of aiocoap by
# looking at wehter it returns a RST or an ACK.
request = aiocoap.Message(
code=aiocoap.GET,
uri="coap://" + self.servernetloc + "/slow",
mtype=aiocoap.NON,
)
self.resp = self.client.request(request).response
# Wait for the request to actually be sent
while not any("Sending request" in l.getMessage() for l in self.handler.list):
await asyncio.sleep(0.001)
# Now the request was sent, let's look at what happens during and after the cancellation
loglength = len(self.handler.list)
self.resp.cancel()
await asyncio.sleep(0.4) # server takes 0.2 to respond
logmsgs = self.handler.list[loglength:]
unmatched_msgs = [
l
for l in logmsgs
if "could not be matched to any request" in l.getMessage()
]
self.assertEqual(
len(unmatched_msgs), 1, "The incoming response was not treated as unmatched"
)
|