diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ed771cdb..39e5be84 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ * minimal mypy fixes and python2 support code drop * migrate packaging to hatch * drop deprecated apis of old makegateway names +* Removed ``py`` testing dependency diff --git a/doc/example/conftest.py b/doc/example/conftest.py index 5ea2370e..5044b9eb 100644 --- a/doc/example/conftest.py +++ b/doc/example/conftest.py @@ -1,13 +1,13 @@ +import pathlib import sys -import py -# make execnet and example code importable -cand = py.path.local(__file__).dirpath().dirpath().dirpath() -if cand.join("execnet", "__init__.py").check(): +# Make execnet and example code importable. +cand = pathlib.Path(__file__).parent.parent.parent +if cand.joinpath("execnet", "__init__.py").exists(): if str(cand) not in sys.path: sys.path.insert(0, str(cand)) -cand = py.path.local(__file__).dirpath() +cand = pathlib.Path(__file__).parent if str(cand) not in sys.path: sys.path.insert(0, str(cand)) diff --git a/doc/example/svn-sync-repo.py b/doc/example/svn-sync-repo.py index 69c2dcb2..24e7bd58 100644 --- a/doc/example/svn-sync-repo.py +++ b/doc/example/svn-sync-repo.py @@ -6,10 +6,11 @@ """ import os +import pathlib +import subprocess import sys import execnet -import py def usage(): @@ -19,8 +20,8 @@ def usage(): def main(args): remote = args[0] - localrepo = py.path.local(args[1]) - if not localrepo.check(dir=1): + localrepo = pathlib.Path(args[1]) + if not localrepo.is_dir(): raise SystemExit(f"localrepo {localrepo} does not exist") if len(args) == 3: configfile = args[2] @@ -39,12 +40,18 @@ def main(args): # 4. client goes back to step 1 c = gw.remote_exec( """ - import py import os + import subprocess import time + remote_rev, repopath = channel.receive() - while 1: - rev = py.process.cmdexec('svnlook youngest "%s"' % repopath) + while True: + rev = subprocess.run( + ["svnlook", "youngest", repopath], + check=True, + capture_output=True, + text=True, + ).stdout rev = int(rev) if rev > remote_rev: revrange = (remote_rev+1, rev) @@ -103,12 +110,17 @@ def svn_load(repo, dumpchannel, maxcount=100): if count <= 0: dumpchannel.send(maxcount) count = maxcount - print >> sys.stdout + print() f.close() def get_svn_youngest(repo): - rev = py.process.cmdexec('svnlook youngest "%s"' % repo) + rev = subprocess.run( + ["svnlook", "youngest", repo], + check=True, + capture_output=True, + text=True, + ).stdout return int(rev) diff --git a/doc/example/sysinfo.py b/doc/example/sysinfo.py index 99a523b5..c88517d7 100644 --- a/doc/example/sysinfo.py +++ b/doc/example/sysinfo.py @@ -6,11 +6,11 @@ (c) Holger Krekel, MIT license """ import optparse +import pathlib import re import sys import execnet -import py parser = optparse.OptionParser(usage=__doc__) @@ -34,14 +34,15 @@ def parsehosts(path): - path = py.path.local(path) + path = pathlib.Path(path) l = [] rex = re.compile(r"Host\s*(\S+)") - for line in path.readlines(): - m = rex.match(line) - if m is not None: - (sshname,) = m.groups() - l.append(sshname) + with path.open() as f: + for line in f: + m = rex.match(line) + if m is not None: + (sshname,) = m.groups() + l.append(sshname) return l diff --git a/testing/conftest.py b/testing/conftest.py index d5e84683..4ea857a9 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,8 +1,9 @@ +import pathlib +import shutil import subprocess import sys import execnet -import py import pytest from execnet.gateway_base import get_execmodel from execnet.gateway_base import WorkerPool @@ -76,7 +77,7 @@ def getspecssh(config): xspecs = getgspecs(config) for spec in xspecs: if spec.ssh: - if not py.path.local.sysfind("ssh"): + if not shutil.which("ssh"): pytest.skip("command not found: ssh") return spec pytest.skip("need '--gx ssh=...'") @@ -113,8 +114,9 @@ def getexecutable(name, cache={}): return cache[name] except KeyError: if name == "sys.executable": - return py.path.local(sys.executable) - executable = py.path.local.sysfind(name) + return pathlib.Path(sys.executable) + path = shutil.which(name) + executable = pathlib.Path(path) if path is not None else None if executable: if name == "jython": popen = subprocess.Popen( diff --git a/testing/test_basics.py b/testing/test_basics.py index 37333910..141f7d3f 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -2,10 +2,10 @@ import os import subprocess import sys +import textwrap from io import BytesIO import execnet -import py import pytest from execnet import gateway from execnet import gateway_base @@ -29,20 +29,20 @@ def test_serializer_api(self, val): val2 = execnet.loads(dumped) assert val == val2 - def test_mmap(self, tmpdir, val): + def test_mmap(self, tmp_path, val): mmap = pytest.importorskip("mmap").mmap - p = tmpdir.join("data") + p = tmp_path / "data" with p.open("wb") as f: f.write(execnet.dumps(val)) - f = p.open("r+b") - m = mmap(f.fileno(), 0) - val2 = execnet.load(m) + with p.open("r+b") as f: + m = mmap(f.fileno(), 0) + val2 = execnet.load(m) assert val == val2 def test_bytesio(self, val): - f = py.io.BytesIO() + f = BytesIO() execnet.dump(f, val) - read = py.io.BytesIO(f.getvalue()) + read = BytesIO(f.getvalue()) val2 = execnet.load(read) assert val == val2 @@ -81,10 +81,8 @@ def receive(): return popen.stdout.readline() try: - source = py.code.Source(read_write_loop, "read_write_loop()") - repr_source = repr(str(source)) + "\n" - sendline = repr_source - send(sendline) + source = inspect.getsource(read_write_loop) + "read_write_loop()" + send(repr(source) + "\n") s = receive() assert s == "ok\n" send("hello\n") @@ -114,11 +112,11 @@ def read_write_loop(): break -def test_io_message(anypython, tmpdir, execmodel): - check = tmpdir.join("check.py") - check.write( - py.code.Source( - gateway_base, +def test_io_message(anypython, tmp_path, execmodel): + check = tmp_path / "check.py" + check.write_text( + inspect.getsource(gateway_base) + + textwrap.dedent( """ from io import BytesIO import tempfile @@ -146,24 +144,25 @@ def test_io_message(anypython, tmpdir, execmodel): ), ) ) - # out = py.process.cmdexec("%s %s" %(executable,check)) - out = anypython.sysexec(check) + out = subprocess.run( + [str(anypython), str(check)], text=True, capture_output=True, check=True + ).stdout print(out) assert "all passed" in out -def test_popen_io(anypython, tmpdir, execmodel): - check = tmpdir.join("check.py") - check.write( - py.code.Source( - gateway_base, +def test_popen_io(anypython, tmp_path, execmodel): + check = tmp_path / "check.py" + check.write_text( + inspect.getsource(gateway_base) + + textwrap.dedent( f""" io = init_popen_io(get_execmodel({execmodel.backend!r})) io.write("hello".encode('ascii')) s = io.read(1) assert s == "x".encode('ascii') - """, - ) + """ + ), ) from subprocess import Popen, PIPE @@ -191,32 +190,36 @@ def newread(numbytes): assert result == b"tes" -def test_rinfo_source(anypython, tmpdir): - check = tmpdir.join("check.py") - check.write( - py.code.Source( +def test_rinfo_source(anypython, tmp_path): + check = tmp_path / "check.py" + check.write_text( + textwrap.dedent( """ class Channel: def send(self, data): assert eval(repr(data), {}) == data channel = Channel() - """, - gateway.rinfo_source, + """ + ) + + inspect.getsource(gateway.rinfo_source) + + textwrap.dedent( """ print ('all passed') - """, + """ ) ) - out = anypython.sysexec(check) + out = subprocess.run( + [str(anypython), str(check)], text=True, capture_output=True, check=True + ).stdout print(out) assert "all passed" in out -def test_geterrortext(anypython, tmpdir): - check = tmpdir.join("check.py") - check.write( - py.code.Source( - gateway_base, +def test_geterrortext(anypython, tmp_path): + check = tmp_path / "check.py" + check.write_text( + inspect.getsource(gateway_base) + + textwrap.dedent( """ class Arg: pass @@ -230,21 +233,22 @@ class Arg: s = geterrortext(excinfo) assert "17" in s print ("all passed") - """, + """ ) ) - out = anypython.sysexec(check) + out = subprocess.run( + [str(anypython), str(check)], text=True, capture_output=True, check=True + ).stdout print(out) assert "all passed" in out -@pytest.mark.skipif("not hasattr(os, 'dup')") -def test_stdouterrin_setnull(execmodel): - cap = py.io.StdCaptureFD() +@pytest.mark.skipif(not hasattr(os, "dup"), reason="no os.dup") +def test_stdouterrin_setnull(execmodel, capfd): gateway_base.init_popen_io(execmodel) os.write(1, b"hello") os.read(0, 1) - out, err = cap.reset() + out, err = capfd.readouterr() assert not out assert not err @@ -267,7 +271,7 @@ def close(self, errortext=None): def test_exectask(execmodel): - io = py.io.BytesIO() + io = BytesIO() io.execmodel = execmodel gw = gateway_base.WorkerGateway(io, id="something") ch = PseudoChannel() @@ -278,10 +282,10 @@ def test_exectask(execmodel): class TestMessage: def test_wire_protocol(self): for i, handler in enumerate(Message._types): - one = py.io.BytesIO() + one = BytesIO() data = b"23" Message(i, 42, data).to_io(one) - two = py.io.BytesIO(one.getvalue()) + two = BytesIO(one.getvalue()) msg = Message.from_io(two) assert msg.msgcode == i assert isinstance(msg, Message) @@ -338,7 +342,7 @@ def prototype(wrong): def test_function_without_known_source_fails(self): # this one won't be able to find the source mess = {} - py.builtin.exec_("def fail(channel): pass", mess, mess) + exec("def fail(channel): pass", mess, mess) print(inspect.getsourcefile(mess["fail"])) pytest.raises(ValueError, gateway._source_of_function, mess["fail"]) @@ -361,9 +365,9 @@ def working(channel): class TestGlobalFinder: def check(self, func): - src = py.code.Source(func) - code = py.code.Code(func) - return gateway._find_non_builtin_globals(str(src), code.raw) + src = textwrap.dedent(inspect.getsource(func)) + code = func.__code__ + return gateway._find_non_builtin_globals(src, code) def test_local(self): def f(a, b, c): diff --git a/testing/test_gateway.py b/testing/test_gateway.py index dabaf34b..809a13d9 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -2,11 +2,13 @@ mostly functional tests of gateways. """ import os +import pathlib +import shutil +import signal import sys from textwrap import dedent import execnet -import py import pytest from execnet import gateway_base from execnet import gateway_io @@ -90,22 +92,22 @@ def test_gateway_status_busy(self, gw): # race condition assert status.numchannels <= numchannels - def test_remote_exec_module(self, tmpdir, gw): - p = tmpdir.join("remotetest.py") - p.write("channel.send(1)") + def test_remote_exec_module(self, tmp_path, gw): + p = tmp_path / "remotetest.py" + p.write_text("channel.send(1)") mod = type(os)("remotetest") mod.__file__ = str(p) channel = gw.remote_exec(mod) name = channel.receive() assert name == 1 - p.write("channel.send(2)") + p.write_text("channel.send(2)") channel = gw.remote_exec(mod) name = channel.receive() assert name == 2 - def test_remote_exec_module_is_removed(self, gw, tmpdir, monkeypatch): - remotetest = tmpdir.join("remote.py") - remotetest.write( + def test_remote_exec_module_is_removed(self, gw, tmp_path, monkeypatch): + remotetest = tmp_path / "remote.py" + remotetest.write_text( dedent( """ def remote(): @@ -119,13 +121,13 @@ def remote(): ) ) - monkeypatch.syspath_prepend(tmpdir) + monkeypatch.syspath_prepend(tmp_path) import remote ch = gw.remote_exec(remote) # simulate sending the code to a remote location that does not have # access to the source - tmpdir.remove() + shutil.rmtree(tmp_path) ch.send("remote()") try: result = ch.receive() @@ -134,9 +136,9 @@ def remote(): assert result is True - def test_remote_exec_module_with_traceback(self, gw, tmpdir, monkeypatch): - remotetest = tmpdir.join("remotetest.py") - remotetest.write( + def test_remote_exec_module_with_traceback(self, gw, tmp_path, monkeypatch): + remotetestpy = tmp_path / "remotetest.py" + remotetestpy.write_text( dedent( """ def run_me(channel=None): @@ -148,7 +150,7 @@ def run_me(channel=None): ) ) - monkeypatch.syspath_prepend(tmpdir) + monkeypatch.syspath_prepend(tmp_path) import remotetest ch = gw.remote_exec(remotetest) @@ -274,15 +276,13 @@ def test__rinfo(self, gw): class TestPopenGateway: gwtype = "popen" - def test_chdir_separation(self, tmpdir, makegateway): - old = tmpdir.chdir() - try: + def test_chdir_separation(self, tmp_path, makegateway): + with pytest.MonkeyPatch.context() as mp: + mp.chdir(tmp_path) gw = makegateway("popen") - finally: - waschangedir = old.chdir() c = gw.remote_exec("import os ; channel.send(os.getcwd())") x = c.receive() - assert x.lower() == str(waschangedir).lower() + assert x.lower() == str(tmp_path).lower() def test_remoteerror_readable_traceback(self, gw): with pytest.raises(gateway_base.RemoteError) as e: @@ -320,7 +320,7 @@ def test_waitclose_on_remote_killed(self, makegateway): """ ) remotepid = channel.receive() - py.process.kill(remotepid) + os.kill(remotepid, signal.SIGTERM) with pytest.raises(EOFError): channel.waitclose(TESTTIMEOUT) with pytest.raises(IOError): @@ -441,24 +441,24 @@ def test_status_with_threads(self, makegateway): class TestTracing: - def test_popen_filetracing(self, testdir, monkeypatch, makegateway): - tmpdir = testdir.tmpdir - monkeypatch.setenv("TMP", str(tmpdir)) - monkeypatch.setenv("TEMP", str(tmpdir)) # windows + def test_popen_filetracing(self, tmp_path, monkeypatch, makegateway): + monkeypatch.setenv("TMP", str(tmp_path)) + monkeypatch.setenv("TEMP", str(tmp_path)) # windows monkeypatch.setenv("EXECNET_DEBUG", "1") gw = makegateway("popen") # hack out the debuffilename fn = gw.remote_exec( "import execnet;channel.send(execnet.gateway_base.fn)" ).receive() - workerfile = py.path.local(fn) - assert workerfile.check() + workerfile = pathlib.Path(fn) + assert workerfile.exists() worker_line = "creating workergateway" - for line in workerfile.readlines(): - if worker_line in line: - break - else: - pytest.fail(f"did not find {worker_line!r} in tracefile") + with workerfile.open() as f: + for line in f: + if worker_line in line: + break + else: + pytest.fail(f"did not find {worker_line!r} in tracefile") gw.exit() @skip_win_pypy diff --git a/testing/test_multi.py b/testing/test_multi.py index 6504f3fb..79861b11 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -5,7 +5,6 @@ from time import sleep import execnet -import py import pytest from execnet import XSpec from execnet.gateway_base import Channel diff --git a/testing/test_rsync.py b/testing/test_rsync.py index 9f9d6e1a..13910d35 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -1,5 +1,8 @@ +import os +import pathlib +import sys + import execnet -import py import pytest from execnet import RSync @@ -26,23 +29,41 @@ def gw2(request, group): needssymlink = pytest.mark.skipif( - not hasattr(py.path.local, "mksymlinkto"), - reason="py.path.local has no mksymlinkto() on this platform", + not hasattr(os, "symlink"), reason="os.symlink not available" ) @pytest.fixture -def dirs(request, tmpdir): - t = tmpdir +def dirs(request, tmp_path): + t = tmp_path class dirs: - source = t.join("source") - dest1 = t.join("dest1") - dest2 = t.join("dest2") + source = t / "source" + dest1 = t / "dest1" + dest2 = t / "dest2" + + dirs.source.mkdir() + dirs.dest1.mkdir() + dirs.dest2.mkdir() return dirs +def are_paths_equal(path1, path2): + if os.path.__name__ == "ntpath": + # On Windows, os.readlink returns an extended path (\\?\) + # for absolute symlinks. However, extended does not compare + # equal to non-extended, even when they refer to the same + # path otherwise. So we have to fix it up ourselves... + is_extended1 = str(path1).startswith("\\\\?\\") + is_extended2 = str(path2).startswith("\\\\?\\") + if is_extended1 and not is_extended2: + path2 = pathlib.Path("\\\\?\\" + str(path2)) + if not is_extended1 and is_extended2: + path1 = pathlib.Path("\\\\?\\" + str(path1)) + return path1 == path2 + + class TestRSync: def test_notargets(self, dirs): rsync = RSync(dirs.source) @@ -56,55 +77,57 @@ def test_dirsync(self, dirs, gw1, gw2): source = dirs.source for s in ("content1", "content2", "content2-a-bit-longer"): - source.ensure("subdir", "file1").write(s) + subdir = source / "subdir" + subdir.mkdir(exist_ok=True) + subdir.joinpath("file1").write_text(s) rsync = RSync(dirs.source) rsync.add_target(gw1, dest) rsync.add_target(gw2, dest2) rsync.send() - assert dest.join("subdir").check(dir=1) - assert dest.join("subdir", "file1").check(file=1) - assert dest.join("subdir", "file1").read() == s - assert dest2.join("subdir").check(dir=1) - assert dest2.join("subdir", "file1").check(file=1) - assert dest2.join("subdir", "file1").read() == s + assert dest.joinpath("subdir").is_dir() + assert dest.joinpath("subdir", "file1").is_file() + assert dest.joinpath("subdir", "file1").read_text() == s + assert dest2.joinpath("subdir").is_dir() + assert dest2.joinpath("subdir", "file1").is_file() + assert dest2.joinpath("subdir", "file1").read_text() == s for x in dest, dest2: - fn = x.join("subdir", "file1") - fn.setmtime(0) + fn = x.joinpath("subdir", "file1") + os.utime(fn, (0, 0)) - source.join("subdir").remove("file1") + source.joinpath("subdir", "file1").unlink() rsync = RSync(source) rsync.add_target(gw2, dest2) rsync.add_target(gw1, dest) rsync.send() - assert dest.join("subdir", "file1").check(file=1) - assert dest2.join("subdir", "file1").check(file=1) + assert dest.joinpath("subdir", "file1").is_file() + assert dest2.joinpath("subdir", "file1").is_file() rsync = RSync(source) rsync.add_target(gw1, dest, delete=True) rsync.add_target(gw2, dest2) rsync.send() - assert not dest.join("subdir", "file1").check() - assert dest2.join("subdir", "file1").check() + assert not dest.joinpath("subdir", "file1").exists() + assert dest2.joinpath("subdir", "file1").exists() def test_dirsync_twice(self, dirs, gw1, gw2): source = dirs.source - source.ensure("hello") + source.joinpath("hello").touch() rsync = RSync(source) rsync.add_target(gw1, dirs.dest1) rsync.send() - assert dirs.dest1.join("hello").check() + assert dirs.dest1.joinpath("hello").exists() with pytest.raises(IOError): rsync.send() assert rsync.send(raises=False) is None rsync.add_target(gw1, dirs.dest2) rsync.send() - assert dirs.dest2.join("hello").check() + assert dirs.dest2.joinpath("hello").exists() with pytest.raises(IOError): rsync.send() assert rsync.send(raises=False) is None def test_rsync_default_reporting(self, capsys, dirs, gw1): source = dirs.source - source.ensure("hello") + source.joinpath("hello").touch() rsync = RSync(source) rsync.add_target(gw1, dirs.dest1) rsync.send() @@ -113,7 +136,7 @@ def test_rsync_default_reporting(self, capsys, dirs, gw1): def test_rsync_non_verbose(self, capsys, dirs, gw1): source = dirs.source - source.ensure("hello") + source.joinpath("hello").touch() rsync = RSync(source, verbose=False) rsync.add_target(gw1, dirs.dest1) rsync.send() @@ -121,47 +144,58 @@ def test_rsync_non_verbose(self, capsys, dirs, gw1): assert not out assert not err - @pytest.mark.skipif("sys.platform == 'win32' or getattr(os, '_name', '') == 'nt'") + @pytest.mark.skipif( + sys.platform == "win32" or getattr(os, "_name", "") == "nt", + reason="irrelevant on windows", + ) def test_permissions(self, dirs, gw1, gw2): source = dirs.source dest = dirs.dest1 - onedir = dirs.source.ensure("one", dir=1) + onedir = dirs.source / "one" + onedir.mkdir() onedir.chmod(448) - onefile = dirs.source.ensure("file") + onefile = dirs.source / "file" + onefile.touch() onefile.chmod(504) - onefile_mtime = onefile.stat().mtime + onefile_mtime = onefile.stat().st_mtime rsync = RSync(source) rsync.add_target(gw1, dest) rsync.send() - destdir = dirs.dest1.join(onedir.basename) - destfile = dirs.dest1.join(onefile.basename) - assert destfile.stat().mode & 511 == 504 - mode = destdir.stat().mode + destdir = dirs.dest1 / onedir.name + destfile = dirs.dest1 / onefile.name + assert destfile.stat().st_mode & 511 == 504 + mode = destdir.stat().st_mode assert mode & 511 == 448 # transfer again with changed permissions onedir.chmod(504) onefile.chmod(448) - onefile.setmtime(onefile_mtime) + os.utime(onefile, (onefile_mtime, onefile_mtime)) rsync = RSync(source) rsync.add_target(gw1, dest) rsync.send() - mode = destfile.stat().mode + mode = destfile.stat().st_mode assert mode & 511 == 448, mode - mode = destdir.stat().mode + mode = destdir.stat().st_mode assert mode & 511 == 504 - @py.test.mark.skipif("sys.platform == 'win32' or getattr(os, '_name', '') == 'nt'") + @pytest.mark.skipif( + sys.platform == "win32" or getattr(os, "_name", "") == "nt", + reason="irrelevant on windows", + ) def test_read_only_directories(self, dirs, gw1): source = dirs.source dest = dirs.dest1 - source.ensure("sub", "subsub", dir=True) - source.join("sub").chmod(0o500) - source.join("sub", "subsub").chmod(0o500) + sub = source / "sub" + sub.mkdir() + subsub = sub / "subsub" + subsub.mkdir() + sub.chmod(0o500) + subsub.chmod(0o500) # The destination directories should be created with the write # permission forced, to avoid raising an EACCES error. @@ -169,49 +203,62 @@ def test_read_only_directories(self, dirs, gw1): rsync.add_target(gw1, dest) rsync.send() - assert dest.join("sub").stat().mode & 0o700 - assert dest.join("sub").join("subsub").stat().mode & 0o700 + assert dest.joinpath("sub").stat().st_mode & 0o700 + assert dest.joinpath("sub", "subsub").stat().st_mode & 0o700 @needssymlink def test_symlink_rsync(self, dirs, gw1): source = dirs.source dest = dirs.dest1 - sourcefile = dirs.source.ensure("subdir", "existent") - source.join("rellink").mksymlinkto(sourcefile, absolute=0) - source.join("abslink").mksymlinkto(sourcefile) + subdir = dirs.source / "subdir" + subdir.mkdir() + sourcefile = subdir / "existent" + sourcefile.touch() + source.joinpath("rellink").symlink_to(sourcefile.relative_to(source)) + source.joinpath("abslink").symlink_to(sourcefile) rsync = RSync(source) rsync.add_target(gw1, dest) rsync.send() - expected = dest.join(sourcefile.relto(dirs.source)) - assert dest.join("rellink").readlink() == "subdir/existent" - assert dest.join("abslink").readlink() == expected + rellink = pathlib.Path(os.readlink(str(dest / "rellink"))) + assert rellink == pathlib.Path("subdir/existent") + + abslink = pathlib.Path(os.readlink(str(dest / "abslink"))) + expected = dest.joinpath(sourcefile.relative_to(source)) + assert are_paths_equal(abslink, expected) @needssymlink def test_symlink2_rsync(self, dirs, gw1): source = dirs.source dest = dirs.dest1 - subdir = dirs.source.ensure("subdir", dir=1) - sourcefile = subdir.ensure("somefile") - subdir.join("link1").mksymlinkto(subdir.join("link2"), absolute=0) - subdir.join("link2").mksymlinkto(sourcefile, absolute=1) - subdir.join("link3").mksymlinkto(source.dirpath(), absolute=1) + subdir = dirs.source / "subdir" + subdir.mkdir() + sourcefile = subdir / "somefile" + sourcefile.touch() + subdir.joinpath("link1").symlink_to( + subdir.joinpath("link2").relative_to(subdir) + ) + subdir.joinpath("link2").symlink_to(sourcefile) + subdir.joinpath("link3").symlink_to(source.parent) rsync = RSync(source) rsync.add_target(gw1, dest) rsync.send() - expected = dest.join(sourcefile.relto(dirs.source)) - destsub = dest.join("subdir") - assert destsub.check() - assert destsub.join("link1").readlink() == "link2" - assert destsub.join("link2").readlink() == expected - assert destsub.join("link3").readlink() == source.dirpath() + expected = dest.joinpath(sourcefile.relative_to(dirs.source)) + destsub = dest.joinpath("subdir") + assert destsub.exists() + link1 = pathlib.Path(os.readlink(str(destsub / "link1"))) + assert are_paths_equal(link1, pathlib.Path("link2")) + link2 = pathlib.Path(os.readlink(str(destsub / "link2"))) + assert are_paths_equal(link2, expected) + link3 = pathlib.Path(os.readlink(str(destsub / "link3"))) + assert are_paths_equal(link3, source.parent) def test_callback(self, dirs, gw1): dest = dirs.dest1 source = dirs.source - source.ensure("existent").write("a" * 100) - source.ensure("existant2").write("a" * 10) + source.joinpath("existent").write_text("a" * 100) + source.joinpath("existant2").write_text("a" * 10) total = {} def callback(cmd, lgt, channel): @@ -227,20 +274,20 @@ def callback(cmd, lgt, channel): def test_file_disappearing(self, dirs, gw1): dest = dirs.dest1 source = dirs.source - source.ensure("ex").write("a" * 100) - source.ensure("ex2").write("a" * 100) + source.joinpath("ex").write_text("a" * 100) + source.joinpath("ex2").write_text("a" * 100) class DRsync(RSync): def filter(self, x): assert x != source if x.endswith("ex2"): self.x = 1 - source.join("ex2").remove() + source.joinpath("ex2").unlink() return True rsync = DRsync(source) rsync.add_target(gw1, dest) rsync.send() assert rsync.x == 1 - assert len(dest.listdir()) == 1 - assert len(source.listdir()) == 1 + assert len(list(dest.iterdir())) == 1 + assert len(list(source.iterdir())) == 1 diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 62038196..f71486e7 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -1,9 +1,10 @@ +import pathlib +import shutil import subprocess import sys import tempfile import execnet -import py import pytest MINOR_VERSIONS = {"3": "543210", "2": "76"} @@ -14,16 +15,16 @@ def setup_module(mod): - mod.TEMPDIR = py.path.local(tempfile.mkdtemp()) - mod._py3_wrapper = PythonWrapper(py.path.local(sys.executable)) + mod.TEMPDIR = pathlib.Path(tempfile.mkdtemp()) + mod._py3_wrapper = PythonWrapper(pathlib.Path(sys.executable)) def teardown_module(mod): - TEMPDIR.remove(True) + shutil.rmtree(TEMPDIR) # we use the execnet folder in order to avoid triggering a missing apipkg -pyimportdir = str(py.path.local(execnet.__file__).dirpath()) +pyimportdir = str(pathlib.Path(execnet.__file__).parent) class PythonWrapper: @@ -31,8 +32,8 @@ def __init__(self, executable): self.executable = executable def dump(self, obj_rep): - script_file = TEMPDIR.join("dump.py") - script_file.write( + script_file = TEMPDIR.joinpath("dump.py") + script_file.write_text( """ import sys sys.path.insert(0, %r) @@ -52,14 +53,14 @@ def dump(self, obj_rep): stdout, stderr = popen.communicate() ret = popen.returncode if ret: - raise py.process.cmdexec.Error( - ret, ret, str(self.executable), stdout, stderr + raise Exception( + "ExecutionFailed: %d %s\n%s" % (ret, self.executable, stderr) ) return stdout def load(self, data, option_args="__class__"): - script_file = TEMPDIR.join("load.py") - script_file.write( + script_file = TEMPDIR.joinpath("load.py") + script_file.write_text( r""" import sys sys.path.insert(0, %r) @@ -81,8 +82,8 @@ def load(self, data, option_args="__class__"): stdout, stderr = popen.communicate(data) ret = popen.returncode if ret: - raise py.process.cmdexec.Error( - ret, ret, str(self.executable), stdout, stderr + raise Exception( + "ExecutionFailed: %d %s\n%s" % (ret, self.executable, stderr) ) return [s.decode("ascii") for s in stdout.splitlines()] diff --git a/testing/test_termination.py b/testing/test_termination.py index f0e484d1..0df29a76 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -1,12 +1,15 @@ +import os +import pathlib +import shutil +import signal import subprocess import sys import execnet -import py import pytest from test_gateway import TESTTIMEOUT -execnetdir = py.path.local(execnet.__file__).dirpath().dirpath() +execnetdir = pathlib.Path(execnet.__file__).parent.parent skip_win_pypy = pytest.mark.xfail( condition=hasattr(sys, "pypy_version_info") and sys.platform.startswith("win"), @@ -45,7 +48,7 @@ def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): """ ) pid = channel.receive() - py.process.kill(pid) + os.kill(pid, signal.SIGTERM) channel.setcallback(q.put, endmarker=999) val = q.get(TESTTIMEOUT) assert val == 999 @@ -55,7 +58,7 @@ def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): @skip_win_pypy def test_termination_on_remote_channel_receive(monkeypatch, makegateway): - if not py.path.local.sysfind("ps"): + if not shutil.which("ps"): pytest.skip("need 'ps' command to externally check process status") monkeypatch.setenv("EXECNET_DEBUG", "2") gw = makegateway("popen") @@ -63,9 +66,10 @@ def test_termination_on_remote_channel_receive(monkeypatch, makegateway): gw.remote_exec("channel.receive()") gw._group.terminate() command = ["ps", "-p", str(pid)] - popen = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + popen = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True + ) out, err = popen.communicate() - out = py.builtin._totext(out, "utf8") assert str(pid) not in out, out diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 2e537b57..75df627e 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -6,11 +6,11 @@ from execnet.gateway_base import WorkerPool -def test_execmodel(execmodel, tmpdir): +def test_execmodel(execmodel, tmp_path): assert execmodel.backend - p = tmpdir.join("somefile") - p.write("content") - fd = os.open(str(p), os.O_RDONLY) + p = tmp_path / "somefile" + p.write_text("content") + fd = os.open(p, os.O_RDONLY) f = execmodel.fdopen(fd, "r") assert f.read() == "content" f.close() @@ -142,9 +142,8 @@ def f(): assert pool.waitall(timeout=0.1) -@pytest.mark.skipif("not hasattr(os, 'dup')") -def test_pool_clean_shutdown(pool): - capture = py.io.StdCaptureFD() +@pytest.mark.skipif(not hasattr(os, "dup"), reason="no os.dup") +def test_pool_clean_shutdown(pool, capfd): q = pool.execmodel.queue.Queue() def f(): @@ -162,9 +161,7 @@ def wait_then_put(): pool.execmodel.start(wait_then_put) assert pool.waitall() - out, err = capture.reset() - sys.stdout.write(out + "\n") - sys.stderr.write(err + "\n") + out, err = capfd.readouterr() assert err == "" diff --git a/testing/test_xspec.py b/testing/test_xspec.py index a75e5a1c..2218e736 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -6,7 +6,6 @@ import sys import execnet -import py import pytest from execnet.gateway_io import popen_args from execnet.gateway_io import ssh_args @@ -174,17 +173,17 @@ def test_popen_explicit(self, makegateway): assert rinfo.version_info == sys.version_info @skip_win_pypy - def test_popen_chdir_absolute(self, testdir, makegateway): - gw = makegateway("popen//chdir=%s" % testdir.tmpdir) + def test_popen_chdir_absolute(self, tmp_path, makegateway): + gw = makegateway("popen//chdir=%s" % tmp_path) rinfo = gw._rinfo() - assert rinfo.cwd == str(testdir.tmpdir.realpath()) + assert rinfo.cwd == str(tmp_path.resolve()) @skip_win_pypy - def test_popen_chdir_newsub(self, testdir, makegateway): - testdir.chdir() + def test_popen_chdir_newsub(self, monkeypatch, tmp_path, makegateway): + monkeypatch.chdir(tmp_path) gw = makegateway("popen//chdir=hello") rinfo = gw._rinfo() - expected = str(testdir.tmpdir.join("hello").realpath()).lower() + expected = str(tmp_path.joinpath("hello").resolve()).lower() assert rinfo.cwd.lower() == expected def test_ssh(self, specssh, makegateway): diff --git a/tox.ini b/tox.ini index f4f785bf..131b80c9 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,6 @@ envlist=py{37,38,39,310,311,pypy37},docs,linting isolated_build = true [testenv] deps= - py pytest pytest-timeout passenv = GITHUB_ACTIONS