Skip to content

Commit 250c9e9

Browse files
committed
Drop testing on Python 3.7 and add testing on Python 3.13
With cryptography version 44.0 including deprecation warnings for Python 3.7 with plans to drop support in its next release, this commit updates the test matrix to drop Python 3.7 and add Python 3.13. This commit also fixes a few issues that caused failures on Windows with Python 3.8 and later. With these changes, Windows tests should be able to run on all currently supported versions of Python. This commit also updates the supported versions in the project metadata.
1 parent 63191ed commit 250c9e9

File tree

8 files changed

+44
-36
lines changed

8 files changed

+44
-36
lines changed

.github/workflows/run_tests.yml

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
fail-fast: false
99
matrix:
1010
os: [ubuntu-latest, macos-latest, windows-latest]
11-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
11+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
1212
include:
1313
- os: macos-latest
1414
python-version: "3.10"
@@ -19,22 +19,9 @@ jobs:
1919
- os: macos-latest
2020
python-version: "3.12"
2121
openssl-version: "3"
22-
exclude:
23-
# having trouble with arch arm64 on macos-ltest on Python 3.7
2422
- os: macos-latest
25-
python-version: "3.7"
26-
27-
# test hangs on these combination
28-
- os: windows-latest
29-
python-version: "3.8"
30-
- os: windows-latest
31-
python-version: "3.9"
32-
- os: windows-latest
33-
python-version: "3.10"
34-
- os: windows-latest
35-
python-version: "3.11"
36-
- os: windows-latest
37-
python-version: "3.12"
23+
python-version: "3.13"
24+
openssl-version: "3"
3825

3926
runs-on: ${{ matrix.os }}
4027
env:

asyncssh/sftp.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7534,6 +7534,11 @@ def readlink(self, path: bytes) -> MaybeAwait[bytes]:
75347534
"""
75357535

75367536
path = os.readlink(_to_local_path(self.map_path(path)))
7537+
7538+
if sys.platform == 'win32' and \
7539+
path.startswith('\\\\?\\'): # pragma: no cover
7540+
path = path[4:]
7541+
75377542
return self.reverse_map_path(_from_local_path(path))
75387543

75397544
def symlink(self, oldpath: bytes, newpath: bytes) -> MaybeAwait[None]:
@@ -7799,7 +7804,13 @@ async def mkdir(self, path: bytes) -> None:
77997804
async def readlink(self, path: bytes) -> bytes:
78007805
"""Return the target of a local symbolic link"""
78017806

7802-
return _from_local_path(os.readlink(_to_local_path(path)))
7807+
path = os.readlink(_to_local_path(path))
7808+
7809+
if sys.platform == 'win32' and \
7810+
path.startswith('\\\\?\\'): # pragma: no cover
7811+
path = path[4:]
7812+
7813+
return _from_local_path(path)
78037814

78047815
async def symlink(self, oldpath: bytes, newpath: bytes) -> None:
78057816
"""Create a local symbolic link"""

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ classifiers = [
1515
'License :: OSI Approved',
1616
'Operating System :: MacOS :: MacOS X',
1717
'Operating System :: POSIX',
18-
'Programming Language :: Python :: 3.7',
1918
'Programming Language :: Python :: 3.8',
2019
'Programming Language :: Python :: 3.9',
2120
'Programming Language :: Python :: 3.10',
2221
'Programming Language :: Python :: 3.11',
2322
'Programming Language :: Python :: 3.12',
23+
'Programming Language :: Python :: 3.13',
2424
'Topic :: Internet',
2525
'Topic :: Security :: Cryptography',
2626
'Topic :: Software Development :: Libraries :: Python Modules',

tests/server.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import signal
2727
import socket
2828
import subprocess
29+
import sys
2930

3031
import asyncssh
3132
from asyncssh.misc import async_context_manager
@@ -269,17 +270,20 @@ async def asyncSetUpClass(cls):
269270
if 'XAUTHORITY' in os.environ: # pragma: no cover
270271
del os.environ['XAUTHORITY']
271272

272-
try:
273-
output = run('ssh-agent -a agent 2>/dev/null')
274-
except subprocess.CalledProcessError: # pragma: no cover
275-
cls._agent_pid = None
276-
else:
277-
cls._agent_pid = int(output.splitlines()[2].split()[3][:-1])
273+
if sys.platform != 'win32':
274+
try:
275+
output = run('ssh-agent -a agent 2>/dev/null')
276+
except subprocess.CalledProcessError: # pragma: no cover
277+
cls._agent_pid = None
278+
else:
279+
cls._agent_pid = int(output.splitlines()[2].split()[3][:-1])
278280

279-
os.environ['SSH_AUTH_SOCK'] = 'agent'
281+
os.environ['SSH_AUTH_SOCK'] = 'agent'
280282

281-
async with asyncssh.connect_agent() as agent:
282-
await agent.add_keys([ckey_ecdsa, (ckey, ckey_cert)])
283+
async with asyncssh.connect_agent() as agent:
284+
await agent.add_keys([ckey_ecdsa, (ckey, ckey_cert)])
285+
else: # pragma: no cover
286+
cls._agent_pid = None
283287

284288
with open('ssh-keysign', 'wb'):
285289
pass
@@ -288,14 +292,14 @@ async def asyncSetUpClass(cls):
288292
async def asyncTearDownClass(cls):
289293
"""Shut down test server and agent"""
290294

295+
cls._server.close()
296+
await cls._server.wait_closed()
297+
291298
tasks = all_tasks()
292299
tasks.remove(current_task())
293300

294301
await asyncio.gather(*tasks, return_exceptions=True)
295302

296-
cls._server.close()
297-
await cls._server.wait_closed()
298-
299303
if cls._agent_pid: # pragma: no branch
300304
os.kill(cls._agent_pid, signal.SIGTERM)
301305

tests/test_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ def test_uid_percent_expansion_unavailable(self):
502502
def mock_hasattr(obj, attr):
503503
if obj == os and attr == 'getuid':
504504
return False
505-
else:
505+
else: # pragma: no cover
506506
return orig_hasattr(obj, attr)
507507

508508
with self.assertRaises(asyncssh.ConfigParseError):

tests/test_connection_auth.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,8 @@ async def test_agent_auth_failure(self):
12801280
async def test_agent_auth_unset(self):
12811281
"""Test connecting with no local keys and no ssh-agent configured"""
12821282

1283-
with patch.dict(os.environ, HOME='xxx', SSH_AUTH_SOCK=''):
1283+
with patch.dict(os.environ, HOME='xxx', USERPROFILE='xxx',
1284+
SSH_AUTH_SOCK=''):
12841285
with self.assertRaises(asyncssh.PermissionDenied):
12851286
await self.connect(username='ckey',
12861287
known_hosts='.ssh/known_hosts')

tests/test_sftp.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,12 @@ def _check_stat_v4(self, sftp_stat, local_stat):
704704
def _check_link(self, link, target):
705705
"""Check if a symlink points to the right target"""
706706

707-
self.assertEqual(os.readlink(link), target)
707+
link = os.readlink(link)
708+
709+
if link.startswith('\\\\?\\'): # pragma: no cover
710+
link = link[4:]
711+
712+
self.assertEqual(link, target)
708713

709714

710715
class _TestSFTP(_CheckSFTP):

tox.ini

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[tox]
2-
minversion = 3.7
2+
minversion = 3.8
33
skip_missing_interpreters = True
44
envlist =
55
clean
66
report
7-
py3{7,8,9,10,11,12}-{linux,darwin,windows}
7+
py3{8,9,10,11,12,13}-{linux,darwin,windows}
88

99
[testenv]
1010
deps =
@@ -53,7 +53,7 @@ commands =
5353
coverage html
5454
coverage xml
5555
depends =
56-
py3{7,8,9,10,11,12}-{linux,darwin,windows}
56+
py3{8,9,10,11,12,13}-{linux,darwin,windows}
5757

5858
[pytest]
5959
testpaths = tests

0 commit comments

Comments
 (0)