Merge remote-tracking branch 'upstream/master' into merge-master-into-features
This commit is contained in:
commit
557cb6cffe
26
.travis.yml
26
.travis.yml
|
@ -1,5 +1,6 @@
|
|||
sudo: false
|
||||
language: python
|
||||
dist: xenial
|
||||
stages:
|
||||
- baseline
|
||||
- name: test
|
||||
|
@ -7,7 +8,7 @@ stages:
|
|||
- name: deploy
|
||||
if: repo = pytest-dev/pytest AND tag IS present
|
||||
python:
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
install:
|
||||
- pip install --upgrade --pre tox
|
||||
env:
|
||||
|
@ -17,24 +18,21 @@ env:
|
|||
- TOXENV=py27-nobyte
|
||||
- TOXENV=py27-xdist
|
||||
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
# Specialized factors for py36.
|
||||
- TOXENV=py36-pexpect,py36-trial,py36-numpy
|
||||
- TOXENV=py36-xdist
|
||||
- TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
# Specialized factors for py37.
|
||||
- TOXENV=py37-pexpect,py37-trial,py37-numpy
|
||||
- TOXENV=py37-xdist
|
||||
- TOXENV=py37-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
- TOXENV=py37-freeze PYTEST_NO_COVERAGE=1
|
||||
|
||||
jobs:
|
||||
include:
|
||||
# Coverage tracking is slow with pypy, skip it.
|
||||
- env: TOXENV=pypy PYTEST_NO_COVERAGE=1
|
||||
python: 'pypy-5.4'
|
||||
dist: trusty
|
||||
- env: TOXENV=py35
|
||||
python: '3.5'
|
||||
- env: TOXENV=py36-freeze PYTEST_NO_COVERAGE=1
|
||||
python: '3.6'
|
||||
- env: TOXENV=py37
|
||||
python: '3.7'
|
||||
sudo: required
|
||||
dist: xenial
|
||||
- &test-macos
|
||||
language: generic
|
||||
os: osx
|
||||
|
@ -54,8 +52,11 @@ jobs:
|
|||
- stage: baseline
|
||||
env: TOXENV=py27
|
||||
- env: TOXENV=py34
|
||||
python: '3.4'
|
||||
- env: TOXENV=py36
|
||||
- env: TOXENV=linting,docs,doctesting PYTEST_NO_COVERAGE=1
|
||||
python: '3.6'
|
||||
- env: TOXENV=linting,docs,doctesting
|
||||
python: '3.7'
|
||||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
|
@ -88,7 +89,8 @@ after_success:
|
|||
- |
|
||||
if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then
|
||||
set -e
|
||||
pip install coverage
|
||||
# Add last TOXENV to $PATH.
|
||||
PATH="$PWD/.tox/${TOXENV##*,}/bin:$PATH"
|
||||
coverage combine
|
||||
coverage xml --ignore-errors
|
||||
coverage report -m --ignore-errors
|
||||
|
|
|
@ -18,6 +18,40 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 3.10.1 (2018-11-11)
|
||||
==========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#4287 <https://github.com/pytest-dev/pytest/issues/4287>`_: Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``.
|
||||
|
||||
|
||||
- `#4304 <https://github.com/pytest-dev/pytest/issues/4304>`_: Block the ``stepwise`` plugin if ``cacheprovider`` is also blocked, as one depends on the other.
|
||||
|
||||
|
||||
- `#4306 <https://github.com/pytest-dev/pytest/issues/4306>`_: Parse ``minversion`` as an actual version and not as dot-separated strings.
|
||||
|
||||
|
||||
- `#4310 <https://github.com/pytest-dev/pytest/issues/4310>`_: Fix duplicate collection due to multiple args matching the same packages.
|
||||
|
||||
|
||||
- `#4321 <https://github.com/pytest-dev/pytest/issues/4321>`_: Fix ``item.nodeid`` with resolved symlinks.
|
||||
|
||||
|
||||
- `#4325 <https://github.com/pytest-dev/pytest/issues/4325>`_: Fix collection of direct symlinked files, where the target does not match ``python_files``.
|
||||
|
||||
|
||||
- `#4329 <https://github.com/pytest-dev/pytest/issues/4329>`_: Fix TypeError in report_collect with _collect_report_last_write.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4305 <https://github.com/pytest-dev/pytest/issues/4305>`_: Replace byte/unicode helpers in test_capture with python level syntax.
|
||||
|
||||
|
||||
pytest 3.10.0 (2018-11-03)
|
||||
==========================
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ Short version
|
|||
#. Follow **PEP-8** for naming and `black <https://github.com/ambv/black>`_ for formatting.
|
||||
#. Tests are run using ``tox``::
|
||||
|
||||
tox -e linting,py27,py36
|
||||
tox -e linting,py27,py37
|
||||
|
||||
The test environments above are usually enough to cover most cases locally.
|
||||
|
||||
|
@ -237,12 +237,12 @@ Here is a simple overview, with pytest-specific bits:
|
|||
|
||||
#. Run all the tests
|
||||
|
||||
You need to have Python 2.7 and 3.6 available in your system. Now
|
||||
You need to have Python 2.7 and 3.7 available in your system. Now
|
||||
running tests is as simple as issuing this command::
|
||||
|
||||
$ tox -e linting,py27,py36
|
||||
$ tox -e linting,py27,py37
|
||||
|
||||
This command will run tests via the "tox" tool against Python 2.7 and 3.6
|
||||
This command will run tests via the "tox" tool against Python 2.7 and 3.7
|
||||
and also perform "lint" coding-style checks.
|
||||
|
||||
#. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 for naming.
|
||||
|
@ -252,9 +252,9 @@ Here is a simple overview, with pytest-specific bits:
|
|||
|
||||
$ tox -e py27 -- --pdb
|
||||
|
||||
Or to only run tests in a particular test module on Python 3.6::
|
||||
Or to only run tests in a particular test module on Python 3.7::
|
||||
|
||||
$ tox -e py36 -- testing/test_config.py
|
||||
$ tox -e py37 -- testing/test_config.py
|
||||
|
||||
|
||||
When committing, ``pre-commit`` will re-format the files if necessary.
|
||||
|
|
11
appveyor.yml
11
appveyor.yml
|
@ -2,7 +2,6 @@ environment:
|
|||
matrix:
|
||||
- TOXENV: "py27"
|
||||
- TOXENV: "py37"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "linting,docs,doctesting"
|
||||
- TOXENV: "py36"
|
||||
- TOXENV: "py35"
|
||||
|
@ -14,13 +13,13 @@ environment:
|
|||
- TOXENV: "py27-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py27-xdist"
|
||||
# Specialized factors for py36.
|
||||
- TOXENV: "py36-trial,py36-numpy"
|
||||
- TOXENV: "py36-pluggymaster"
|
||||
# Specialized factors for py37.
|
||||
- TOXENV: "py37-trial,py37-numpy"
|
||||
- TOXENV: "py37-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py36-freeze"
|
||||
- TOXENV: "py37-freeze"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py36-xdist"
|
||||
- TOXENV: "py37-xdist"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``.
|
|
@ -1 +0,0 @@
|
|||
Block the ``stepwise`` plugin if ``cacheprovider`` is also blocked, as one depends on the other.
|
|
@ -1 +0,0 @@
|
|||
Replace byte/unicode helpers in test_capture with python level syntax.
|
|
@ -1 +0,0 @@
|
|||
Parse ``minversion`` as an actual version and not as dot-separated strings.
|
|
@ -1 +0,0 @@
|
|||
Fix duplicate collection due to multiple args matching the same packages.
|
|
@ -1 +0,0 @@
|
|||
Fix TypeError in report_collect with _collect_report_last_write.
|
|
@ -6,6 +6,7 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-3.10.1
|
||||
release-3.10.0
|
||||
release-3.9.3
|
||||
release-3.9.2
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
pytest-3.10.1
|
||||
=======================================
|
||||
|
||||
pytest 3.10.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Anthony Sottile
|
||||
* Boris Feld
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Fabien ZARIFIAN
|
||||
* Jon Dufresne
|
||||
* Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -85,9 +85,8 @@ interesting to just look at the collection tree::
|
|||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
<Package '$REGENDOC_TMPDIR/nonpython'>
|
||||
<Package '$REGENDOC_TMPDIR/nonpython'>
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'hello'>
|
||||
<YamlItem 'ok'>
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'hello'>
|
||||
<YamlItem 'ok'>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
|
|
@ -398,6 +398,7 @@ class Session(nodes.FSCollector):
|
|||
# Keep track of any collected nodes in here, so we don't duplicate fixtures
|
||||
self._node_cache = {}
|
||||
self._bestrelpathcache = _bestrelpath_cache(config.rootdir)
|
||||
# Dirnames of pkgs with dunder-init files.
|
||||
self._pkg_roots = {}
|
||||
|
||||
self.config.pluginmanager.register(self, name="session")
|
||||
|
@ -504,7 +505,7 @@ class Session(nodes.FSCollector):
|
|||
from _pytest.python import Package
|
||||
|
||||
names = self._parsearg(arg)
|
||||
argpath = names.pop(0).realpath()
|
||||
argpath = names.pop(0)
|
||||
|
||||
# Start with a Session root, and delve to argpath item (dir or file)
|
||||
# and stack all Packages found on the way.
|
||||
|
@ -551,8 +552,7 @@ class Session(nodes.FSCollector):
|
|||
seen_dirs.add(dirpath)
|
||||
pkginit = dirpath.join("__init__.py")
|
||||
if pkginit.exists():
|
||||
collect_root = self._pkg_roots.get(dirpath, self)
|
||||
for x in collect_root._collectfile(pkginit):
|
||||
for x in self._collectfile(pkginit):
|
||||
yield x
|
||||
if isinstance(x, Package):
|
||||
self._pkg_roots[dirpath] = x
|
||||
|
@ -652,7 +652,7 @@ class Session(nodes.FSCollector):
|
|||
"file or package not found: " + arg + " (missing __init__.py?)"
|
||||
)
|
||||
raise UsageError("file not found: " + arg)
|
||||
parts[0] = path
|
||||
parts[0] = path.realpath()
|
||||
return parts
|
||||
|
||||
def matchnodes(self, matching, names):
|
||||
|
|
|
@ -449,7 +449,7 @@ class Collector(Node):
|
|||
def _check_initialpaths_for_relpath(session, fspath):
|
||||
for initial_path in session._initialpaths:
|
||||
if fspath.common(initial_path) == initial_path:
|
||||
return fspath.relto(initial_path.dirname)
|
||||
return fspath.relto(initial_path)
|
||||
|
||||
|
||||
class FSCollector(Collector):
|
||||
|
|
|
@ -666,11 +666,11 @@ class TestInvocationVariants(object):
|
|||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*test_hello.py::test_hello*PASSED*",
|
||||
"*test_hello.py::test_other*PASSED*",
|
||||
"*test_world.py::test_world*PASSED*",
|
||||
"*test_world.py::test_other*PASSED*",
|
||||
"*4 passed*",
|
||||
"test_hello.py::test_hello*PASSED*",
|
||||
"test_hello.py::test_other*PASSED*",
|
||||
"ns_pkg/world/test_world.py::test_world*PASSED*",
|
||||
"ns_pkg/world/test_world.py::test_other*PASSED*",
|
||||
"*4 passed in*",
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ def equal_with_bash(prefix, ffc, fc, out=None):
|
|||
res_bash = set(fc(prefix))
|
||||
retval = set(res) == res_bash
|
||||
if out:
|
||||
out.write("equal_with_bash {} {}\n".format(retval, res))
|
||||
out.write("equal_with_bash({}) {} {}\n".format(prefix, retval, res))
|
||||
if not retval:
|
||||
out.write(" python - bash: %s\n" % (set(res) - res_bash))
|
||||
out.write(" bash - python: %s\n" % (res_bash - set(res)))
|
||||
|
@ -91,13 +91,19 @@ class FilesCompleter(object):
|
|||
|
||||
class TestArgComplete(object):
|
||||
@pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
|
||||
def test_compare_with_compgen(self):
|
||||
def test_compare_with_compgen(self, tmpdir):
|
||||
from _pytest._argcomplete import FastFilesCompleter
|
||||
|
||||
ffc = FastFilesCompleter()
|
||||
fc = FilesCompleter()
|
||||
for x in ["/", "/d", "/data", "qqq", ""]:
|
||||
assert equal_with_bash(x, ffc, fc, out=sys.stdout)
|
||||
|
||||
with tmpdir.as_cwd():
|
||||
assert equal_with_bash("", ffc, fc, out=sys.stdout)
|
||||
|
||||
tmpdir.ensure("data")
|
||||
|
||||
for x in ["d", "data", "doesnotexist", ""]:
|
||||
assert equal_with_bash(x, ffc, fc, out=sys.stdout)
|
||||
|
||||
@pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
|
||||
def test_remove_dir_prefix(self):
|
||||
|
|
|
@ -6,6 +6,8 @@ import pprint
|
|||
import sys
|
||||
import textwrap
|
||||
|
||||
import py
|
||||
|
||||
import pytest
|
||||
from _pytest.main import _in_venv
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
|
@ -1082,4 +1084,55 @@ def test_collect_with_chdir_during_import(testdir):
|
|||
with testdir.tmpdir.as_cwd():
|
||||
result = testdir.runpytest("--collect-only")
|
||||
result.stdout.fnmatch_lines(["collected 1 item"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not hasattr(py.path.local, "mksymlinkto"),
|
||||
reason="symlink not available on this platform",
|
||||
)
|
||||
def test_collect_symlink_file_arg(testdir):
|
||||
"""Test that collecting a direct symlink, where the target does not match python_files works (#4325)."""
|
||||
real = testdir.makepyfile(
|
||||
real="""
|
||||
def test_nodeid(request):
|
||||
assert request.node.nodeid == "real.py::test_nodeid"
|
||||
"""
|
||||
)
|
||||
symlink = testdir.tmpdir.join("symlink.py")
|
||||
symlink.mksymlinkto(real)
|
||||
result = testdir.runpytest("-v", symlink)
|
||||
result.stdout.fnmatch_lines(["real.py::test_nodeid PASSED*", "*1 passed in*"])
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not hasattr(py.path.local, "mksymlinkto"),
|
||||
reason="symlink not available on this platform",
|
||||
)
|
||||
def test_collect_symlink_out_of_tree(testdir):
|
||||
"""Test collection of symlink via out-of-tree rootdir."""
|
||||
sub = testdir.tmpdir.join("sub")
|
||||
real = sub.join("test_real.py")
|
||||
real.write(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
def test_nodeid(request):
|
||||
# Should not contain sub/ prefix.
|
||||
assert request.node.nodeid == "test_real.py::test_nodeid"
|
||||
"""
|
||||
),
|
||||
ensure=True,
|
||||
)
|
||||
|
||||
out_of_tree = testdir.tmpdir.join("out_of_tree").ensure(dir=True)
|
||||
symlink_to_sub = out_of_tree.join("symlink_to_sub")
|
||||
symlink_to_sub.mksymlinkto(sub)
|
||||
sub.chdir()
|
||||
result = testdir.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub)
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
# Should not contain "sub/"!
|
||||
"test_real.py::test_nodeid PASSED"
|
||||
]
|
||||
)
|
||||
assert result.ret == 0
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import py
|
||||
|
||||
import pytest
|
||||
from _pytest import nodes
|
||||
|
||||
|
@ -29,3 +31,23 @@ def test_std_warn_not_pytestwarning(testdir):
|
|||
)
|
||||
with pytest.raises(ValueError, match=".*instance of PytestWarning.*"):
|
||||
items[0].warn(UserWarning("some warning"))
|
||||
|
||||
|
||||
def test__check_initialpaths_for_relpath():
|
||||
"""Ensure that it handles dirs, and does not always use dirname."""
|
||||
cwd = py.path.local()
|
||||
|
||||
class FakeSession:
|
||||
_initialpaths = [cwd]
|
||||
|
||||
assert nodes._check_initialpaths_for_relpath(FakeSession, cwd) == ""
|
||||
|
||||
sub = cwd.join("file")
|
||||
|
||||
class FakeSession:
|
||||
_initialpaths = [cwd]
|
||||
|
||||
assert nodes._check_initialpaths_for_relpath(FakeSession, sub) == "file"
|
||||
|
||||
outside = py.path.local("/outside")
|
||||
assert nodes._check_initialpaths_for_relpath(FakeSession, outside) is None
|
||||
|
|
|
@ -333,7 +333,11 @@ def test_rootdir_option_arg(testdir, monkeypatch, path):
|
|||
|
||||
result = testdir.runpytest("--rootdir={}".format(path))
|
||||
result.stdout.fnmatch_lines(
|
||||
["*rootdir: {}/root, inifile:*".format(testdir.tmpdir), "*1 passed*"]
|
||||
[
|
||||
"*rootdir: {}/root, inifile:*".format(testdir.tmpdir),
|
||||
"root/test_rootdir_option_arg.py *",
|
||||
"*1 passed*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
|
20
tox.ini
20
tox.ini
|
@ -10,10 +10,10 @@ envlist =
|
|||
py36
|
||||
py37
|
||||
pypy
|
||||
{py27,py36}-{pexpect,xdist,trial,numpy,pluggymaster}
|
||||
{py27,py37}-{pexpect,xdist,trial,numpy,pluggymaster}
|
||||
py27-nobyte
|
||||
doctesting
|
||||
py36-freeze
|
||||
py37-freeze
|
||||
docs
|
||||
|
||||
[testenv]
|
||||
|
@ -23,7 +23,7 @@ commands =
|
|||
coverage: coverage report
|
||||
passenv = USER USERNAME COVERAGE_* TRAVIS
|
||||
setenv =
|
||||
# configuration if a user runs tox with a "coverage" factor, for example "tox -e py36-coverage"
|
||||
# configuration if a user runs tox with a "coverage" factor, for example "tox -e py37-coverage"
|
||||
coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m
|
||||
coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
|
||||
coverage: COVERAGE_FILE={toxinidir}/.coverage
|
||||
|
@ -46,7 +46,7 @@ commands =
|
|||
|
||||
[testenv:linting]
|
||||
skip_install = True
|
||||
basepython = python3.6
|
||||
basepython = python3
|
||||
deps = pre-commit>=1.11.0
|
||||
commands = pre-commit run --all-files --show-diff-on-failure
|
||||
|
||||
|
@ -60,7 +60,7 @@ deps =
|
|||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs}
|
||||
|
||||
[testenv:py36-xdist]
|
||||
[testenv:py37-xdist]
|
||||
# NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706.
|
||||
deps =
|
||||
pytest-xdist>=1.13
|
||||
|
@ -78,7 +78,7 @@ deps =
|
|||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py {posargs}
|
||||
|
||||
[testenv:py36-pexpect]
|
||||
[testenv:py37-pexpect]
|
||||
platform = {[testenv:py27-pexpect]platform}
|
||||
deps = {[testenv:py27-pexpect]deps}
|
||||
commands = {[testenv:py27-pexpect]commands}
|
||||
|
@ -103,7 +103,7 @@ deps =
|
|||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_unittest.py}
|
||||
|
||||
[testenv:py36-trial]
|
||||
[testenv:py37-trial]
|
||||
deps = {[testenv:py27-trial]deps}
|
||||
commands = {[testenv:py27-trial]commands}
|
||||
|
||||
|
@ -114,7 +114,7 @@ deps =
|
|||
commands=
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/python/approx.py}
|
||||
|
||||
[testenv:py36-numpy]
|
||||
[testenv:py37-numpy]
|
||||
deps = {[testenv:py27-numpy]deps}
|
||||
commands = {[testenv:py27-numpy]commands}
|
||||
|
||||
|
@ -125,7 +125,7 @@ setenv=
|
|||
# NOTE: using env instead of "{[testenv]deps}", because of https://github.com/tox-dev/tox/issues/706.
|
||||
_PYTEST_TOX_EXTRA_DEP=git+https://github.com/pytest-dev/pluggy.git@master
|
||||
|
||||
[testenv:py36-pluggymaster]
|
||||
[testenv:py37-pluggymaster]
|
||||
setenv = {[testenv:py27-pluggymaster]setenv}
|
||||
|
||||
[testenv:docs]
|
||||
|
@ -170,7 +170,7 @@ changedir = testing
|
|||
commands =
|
||||
{envpython} {envbindir}/py.test-jython {posargs}
|
||||
|
||||
[testenv:py36-freeze]
|
||||
[testenv:py37-freeze]
|
||||
changedir = testing/freeze
|
||||
deps =
|
||||
pyinstaller
|
||||
|
|
Loading…
Reference in New Issue