From 92b0f466efc68651cbae80d9d05735e51499260c Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 09:47:00 +0200 Subject: [PATCH 01/16] update CI and dev configs --- .github/workflows/define-pylint.yml | 4 +-- .github/workflows/define-pytest.yml | 10 +++--- .pylintrc | 20 +++++++---- CODE_STYLE.md | 28 +++++++++++++++ MANIFEST.in | 2 -- pyproject.toml | 55 +++++++++++++++++++++-------- requirements.txt | 14 ++++---- src/test/pytest.ini | 10 ++++++ 8 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 CODE_STYLE.md delete mode 100644 MANIFEST.in create mode 100644 src/test/pytest.ini diff --git a/.github/workflows/define-pylint.yml b/.github/workflows/define-pylint.yml index 8241cd1..002addf 100644 --- a/.github/workflows/define-pylint.yml +++ b/.github/workflows/define-pylint.yml @@ -1,4 +1,4 @@ -name: Define Pylint +name: Define pylint on: workflow_call: @@ -29,7 +29,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pylint - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f requirements.txt ]; then pip install --upgrade -r requirements.txt; fi - name: Analysing the code with pylint run: | pylint $(git ls-files '*.py') diff --git a/.github/workflows/define-pytest.yml b/.github/workflows/define-pytest.yml index a2d1579..e8f7f73 100644 --- a/.github/workflows/define-pytest.yml +++ b/.github/workflows/define-pytest.yml @@ -1,4 +1,4 @@ -name: Define Pytest +name: Define pytest on: workflow_call: @@ -29,11 +29,11 @@ jobs: run: | python -m pip install --upgrade pip pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f requirements.txt ]; then pip install --upgrade -r requirements.txt; fi - name: Install node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Install mineflayer run: | npm install mineflayer @@ -49,6 +49,6 @@ jobs: touch password.txt echo $USERNAME >> password.txt echo $PASSWORD >> password.txt - - name: Run all tests with pytest + - name: Run tests with pytest run: | - pytest -rs + pytest src -rs --skip-linting diff --git a/.pylintrc b/.pylintrc index 0663811..35f1913 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,5 +1,8 @@ -[MAIN] -max-line-length=150 +[MASTER] + +init-hook="from pylint.config import find_default_config_files; import os, sys; sys.path.append(f'{os.path.dirname(next(find_default_config_files()))}/src')" + +# ignore=test [MESSAGES CONTROL] @@ -17,11 +20,16 @@ confidence= # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". disable=too-many-arguments, + invalid-name, fixme, - bare-except, duplicate-code, too-few-public-methods, - global-statement, + too-many-instance-attributes, + too-many-positional-arguments, + logging-fstring-interpolation, too-many-branches, - broad-exception-raised, - relative-beyond-top-level + too-many-locals + +[FORMAT] + +max-line-length=120 diff --git a/CODE_STYLE.md b/CODE_STYLE.md new file mode 100644 index 0000000..ee67268 --- /dev/null +++ b/CODE_STYLE.md @@ -0,0 +1,28 @@ +# CODE STYLE + +To allow for a consistent code style within this repository, some rules are enforced. + +This file documents these rules. + +Note that these are not unchangeable, if given a good reason. + +## imports + +All imports have to be absolute. This is also enforced in the pre-commit hooks. + +## sections order in code files + +The order in which imports, constants, ... are defined in python source code files needs to be unified. + +The different sections have to follow this order: +* module-level docstring (what does this module do / contain) +* standard library imports (e.g. `import os`) +* third-party imports (e.g. `import numpy as np`) +* local imports (e.g. `from abllib import get_logger`) +* optional modules (e.g. `try_import_module("pykakasi")`) +* pylint comments (which checks to ignore in this file) +* module-level logger +* module-level constants +* everything else + +Note that not all sections need to be present. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index a74be12..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include mcserverwrapper/tests/pytest.ini -recursive-include mcserverwrapper/test *.py diff --git a/pyproject.toml b/pyproject.toml index 175b0cf..2a43532 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,35 +4,62 @@ build-backend = "setuptools.build_meta" [project] name = "mcserverwrapper" -version = "0.0.3" +version = "0.0.3rc1" authors = [ { name="Ableytner", email="ableytner@gmx.at" }, ] description = "A wrapper for minecraft servers, usable as a python package or from the console." readme = "README.md" -license = { file="LICENSE" } -requires-python = ">=3.10" +license = "GPL-3.0" +license-files = [ + "LICENSE", +] +requires-python = ">=3.9" classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Operating System :: Microsoft :: Windows", + "Development Status :: 4 - Beta", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Software Development :: Libraries :: Python Modules" ] dependencies = [ - "requests", - "pexpect", - "mcstatus", + "mcstatus==12.0.5", + "pexpect==4.9.0", ] [project.optional-dependencies] dev = [ - "pytest", - "requests", - "bs4", - "pylint", - "javascript", + "bs4==0.0.2", + "javascript==1!1.2.6", + "pylint==3.3.9", + "pytest==8.4.2", + "requests==2.32.5", ] [project.urls] Repository = "https://github.com/mcserver-tools/mcserverwrapper" "Bug Tracker" = "https://github.com/mcserver-tools/mcserverwrapper/issues" + +[tool.ruff.lint] +select = ["TID252"] + +[tool.ruff.lint.flake8-tidy-imports] +# Disallow all relative imports. +ban-relative-imports = "all" + +[tool.isort] +lines_after_imports=1 + +[tool.mypy] +strict = false + +[[tool.mypy.overrides]] +module = ["dill.*"] +ignore_missing_imports = true diff --git a/requirements.txt b/requirements.txt index 681f53b..b16a56b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -requests -pexpect -mcstatus -bs4 -javascript -pytest -pylint +bs4==0.0.2 +javascript==1!1.2.6 +mcstatus==12.0.5 +pexpect==4.9.0 +pylint==3.3.9 +pytest==8.4.2 +requests==2.32.5 diff --git a/src/test/pytest.ini b/src/test/pytest.ini new file mode 100644 index 0000000..0f1738f --- /dev/null +++ b/src/test/pytest.ini @@ -0,0 +1,10 @@ +[pytest] +addopts=--tb short + --color=yes + --show-capture all +;print output to console + -s +log_format = [%(levelname)s] %(filename)s @%(lineno)d: %(message)s +;log_date_format=%Y/%m/%d %H:%M:%S +log_cli = true +log_cli_level = DEBUG From cd67447d7e3183314603051475ceb1f6de70ce8a Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 09:47:10 +0200 Subject: [PATCH 02/16] add pull request template --- .github/pull_request_template.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..78197c6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,7 @@ +## Description + +## Checklist before merging + +* [ ] applied `ready-to-merge` label +* [ ] updated documentation ([README.md](../README.md)) +* [ ] updated version number ([pyproject.toml](../pyproject.toml)) From 6c9f9d0243830a5a454cfa130d311420e05d87ac Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 10:34:30 +0200 Subject: [PATCH 03/16] update to python 3.14, deprecate 3.8 --- .github/workflows/release_package.yml | 10 +++++----- .github/workflows/run-tests.yml | 4 ++-- pyproject.toml | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release_package.yml b/.github/workflows/release_package.yml index 74bf8b7..b57af4f 100644 --- a/.github/workflows/release_package.yml +++ b/.github/workflows/release_package.yml @@ -10,7 +10,7 @@ jobs: pylint: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] uses: ./.github/workflows/define-pylint.yml with: python-version: ${{ matrix.python-version }} @@ -19,7 +19,7 @@ jobs: pytest: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] uses: ./.github/workflows/define-pytest.yml with: python-version: ${{ matrix.python-version }} @@ -29,7 +29,7 @@ jobs: runs-on: self-hosted strategy: matrix: - python-version: ["3.13"] + python-version: ["3.14"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -56,10 +56,10 @@ jobs: release-tag: ${{ steps.read-tag.outputs.release-tag }} steps: - uses: actions/checkout@v4 - - name: Set up Python 3.13 + - name: Set up Python 3.14 uses: actions/setup-python@v5 with: - python-version: "3.13" + python-version: "3.14" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f678ac0..d32b997 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: pylint: strategy: matrix: - python-version: ["3.8", "3.13"] + python-version: ["3.9", "3.14"] uses: ./.github/workflows/define-pylint.yml with: python-version: ${{ matrix.python-version }} @@ -19,7 +19,7 @@ jobs: pytest: strategy: matrix: - python-version: ["3.8", "3.13"] + python-version: ["3.9", "3.14"] needs: pylint if: | (github.event_name == 'push' && github.ref_name == 'main') || diff --git a/pyproject.toml b/pyproject.toml index 2a43532..8a930d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Libraries :: Python Modules" ] From aac110e8bc25e66ca9d5cc1bc30a445f103b10e6 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 10:42:25 +0200 Subject: [PATCH 04/16] pylint: ignore test dir --- .pylintrc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index 35f1913..98c59cd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,7 +2,7 @@ init-hook="from pylint.config import find_default_config_files; import os, sys; sys.path.append(f'{os.path.dirname(next(find_default_config_files()))}/src')" -# ignore=test +ignore=test [MESSAGES CONTROL] @@ -28,7 +28,9 @@ disable=too-many-arguments, too-many-positional-arguments, logging-fstring-interpolation, too-many-branches, - too-many-locals + too-many-locals, + broad-exception-raised, + global-statement [FORMAT] From 4c64ee490a61a940c37abfdc139025bde3783e7b Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 10:50:22 +0200 Subject: [PATCH 05/16] restructure project --- .gitignore | 22 ++++++----- .pylintrc | 5 ++- examples/example_01.py | 2 +- mcserverwrapper/src/__init__.py | 7 ---- mcserverwrapper/src/server/__init__.py | 13 ------- {mcserverwrapper => src}/main.py | 0 .../mcserverwrapper}/__init__.py | 2 +- .../src => src/mcserverwrapper}/error.py | 0 .../src => src/mcserverwrapper}/mcversion.py | 0 src/mcserverwrapper/server/__init__.py | 13 +++++++ .../mcserverwrapper}/server/base_server.py | 6 +-- .../mcserverwrapper}/server/forge_server.py | 5 ++- .../mcserverwrapper}/server/paper_server.py | 4 +- .../mcserverwrapper}/server/server_builder.py | 11 +++--- .../mcserverwrapper}/server/vanilla_server.py | 5 ++- .../server_properties_helper.py | 2 +- .../mcserverwrapper}/util/__init__.py | 2 +- .../mcserverwrapper}/util/info_getter.py | 0 .../mcserverwrapper}/util/logger.py | 0 .../src => src/mcserverwrapper}/wrapper.py | 8 ++-- {mcserverwrapper => src}/test/__init__.py | 0 {mcserverwrapper => src}/test/conftest.py | 37 +++++++++---------- {mcserverwrapper => src}/test/fixtures.py | 2 +- .../test/helpers/common_helper.py | 13 +++---- .../test/helpers/forge_helper.py | 8 ++-- .../test/helpers/vanilla_helper.py | 5 ++- .../test/integration_tests/__init__.py | 0 .../test/integration_tests/test_forge.py | 4 +- .../test/integration_tests/test_vanilla.py | 13 ++++--- .../test/module_tests/__init__.py | 0 .../test_server_properties_helper.py | 6 +-- .../test/testable_thread.py | 0 32 files changed, 96 insertions(+), 99 deletions(-) delete mode 100644 mcserverwrapper/src/__init__.py delete mode 100644 mcserverwrapper/src/server/__init__.py rename {mcserverwrapper => src}/main.py (100%) rename {mcserverwrapper => src/mcserverwrapper}/__init__.py (57%) rename {mcserverwrapper/src => src/mcserverwrapper}/error.py (100%) rename {mcserverwrapper/src => src/mcserverwrapper}/mcversion.py (100%) create mode 100644 src/mcserverwrapper/server/__init__.py rename {mcserverwrapper/src => src/mcserverwrapper}/server/base_server.py (98%) rename {mcserverwrapper/src => src/mcserverwrapper}/server/forge_server.py (94%) rename {mcserverwrapper/src => src/mcserverwrapper}/server/paper_server.py (87%) rename {mcserverwrapper/src => src/mcserverwrapper}/server/server_builder.py (93%) rename {mcserverwrapper/src => src/mcserverwrapper}/server/vanilla_server.py (95%) rename {mcserverwrapper/src => src/mcserverwrapper}/server_properties_helper.py (99%) rename {mcserverwrapper/src => src/mcserverwrapper}/util/__init__.py (58%) rename {mcserverwrapper/src => src/mcserverwrapper}/util/info_getter.py (100%) rename {mcserverwrapper/src => src/mcserverwrapper}/util/logger.py (100%) rename {mcserverwrapper/src => src/mcserverwrapper}/wrapper.py (96%) rename {mcserverwrapper => src}/test/__init__.py (100%) rename {mcserverwrapper => src}/test/conftest.py (87%) rename {mcserverwrapper => src}/test/fixtures.py (95%) rename {mcserverwrapper => src}/test/helpers/common_helper.py (98%) rename {mcserverwrapper => src}/test/helpers/forge_helper.py (93%) rename {mcserverwrapper => src}/test/helpers/vanilla_helper.py (93%) rename {mcserverwrapper => src}/test/integration_tests/__init__.py (100%) rename {mcserverwrapper => src}/test/integration_tests/test_forge.py (93%) rename {mcserverwrapper => src}/test/integration_tests/test_vanilla.py (91%) rename {mcserverwrapper => src}/test/module_tests/__init__.py (100%) rename {mcserverwrapper => src}/test/module_tests/test_server_properties_helper.py (98%) rename {mcserverwrapper => src}/test/testable_thread.py (100%) diff --git a/.gitignore b/.gitignore index 6866edb..418da71 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,14 @@ -.vscode/ -venv*/ -testdir/ -testdir_persistent/ -__pycache__/ -mcserverwrapper/test/temp/ -*.egg-info/ -.pytest_cache/ -examples/temp/ -dist/ +*\__pycache__ +*\.pytest_cache +*\.mypy_cache +*\.ruff_cache +*\.vscode +*\venv* +*\dist +*\testdir +*\testdir_persistent *.pyc +*.egg-info +src/test/temp +examples/temp password.txt diff --git a/.pylintrc b/.pylintrc index 98c59cd..9ab1247 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,7 +2,7 @@ init-hook="from pylint.config import find_default_config_files; import os, sys; sys.path.append(f'{os.path.dirname(next(find_default_config_files()))}/src')" -ignore=test +# ignore=test [MESSAGES CONTROL] @@ -30,7 +30,8 @@ disable=too-many-arguments, too-many-branches, too-many-locals, broad-exception-raised, - global-statement + global-statement, + wrong-import-order [FORMAT] diff --git a/examples/example_01.py b/examples/example_01.py index 24ac1aa..e826ce1 100644 --- a/examples/example_01.py +++ b/examples/example_01.py @@ -4,7 +4,7 @@ import pathlib import requests -from mcserverwrapper import Wrapper +from src.mcserverwrapper import Wrapper def download_server_jar(): """Download the server jar""" diff --git a/mcserverwrapper/src/__init__.py b/mcserverwrapper/src/__init__.py deleted file mode 100644 index 098d188..0000000 --- a/mcserverwrapper/src/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Export Wrapper class""" - -from .wrapper import Wrapper - -__exports__ = [ - Wrapper -] diff --git a/mcserverwrapper/src/server/__init__.py b/mcserverwrapper/src/server/__init__.py deleted file mode 100644 index 483038c..0000000 --- a/mcserverwrapper/src/server/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Export server classes""" - -from .server_builder import ServerBuilder -from .base_server import BaseServer -from .paper_server import PaperServer -from .vanilla_server import VanillaServer - -__exports__ = [ - ServerBuilder, - BaseServer, - PaperServer, - VanillaServer -] diff --git a/mcserverwrapper/main.py b/src/main.py similarity index 100% rename from mcserverwrapper/main.py rename to src/main.py diff --git a/mcserverwrapper/__init__.py b/src/mcserverwrapper/__init__.py similarity index 57% rename from mcserverwrapper/__init__.py rename to src/mcserverwrapper/__init__.py index 6d86c00..9394ebd 100644 --- a/mcserverwrapper/__init__.py +++ b/src/mcserverwrapper/__init__.py @@ -1,6 +1,6 @@ """Export Wrapper class""" -from mcserverwrapper.src import Wrapper +from mcserverwrapper.wrapper import Wrapper __exports__ = [ Wrapper diff --git a/mcserverwrapper/src/error.py b/src/mcserverwrapper/error.py similarity index 100% rename from mcserverwrapper/src/error.py rename to src/mcserverwrapper/error.py diff --git a/mcserverwrapper/src/mcversion.py b/src/mcserverwrapper/mcversion.py similarity index 100% rename from mcserverwrapper/src/mcversion.py rename to src/mcserverwrapper/mcversion.py diff --git a/src/mcserverwrapper/server/__init__.py b/src/mcserverwrapper/server/__init__.py new file mode 100644 index 0000000..63f8f39 --- /dev/null +++ b/src/mcserverwrapper/server/__init__.py @@ -0,0 +1,13 @@ +"""Export server classes""" + +from mcserverwrapper.server.base_server import BaseServer +from mcserverwrapper.server.paper_server import PaperServer +from mcserverwrapper.server.server_builder import ServerBuilder +from mcserverwrapper.server.vanilla_server import VanillaServer + +__exports__ = [ + ServerBuilder, + BaseServer, + PaperServer, + VanillaServer +] diff --git a/mcserverwrapper/src/server/base_server.py b/src/mcserverwrapper/server/base_server.py similarity index 98% rename from mcserverwrapper/src/server/base_server.py rename to src/mcserverwrapper/server/base_server.py index c4b69b7..1fc18e3 100644 --- a/mcserverwrapper/src/server/base_server.py +++ b/src/mcserverwrapper/server/base_server.py @@ -14,9 +14,9 @@ import pexpect from pexpect import popen_spawn -from ..mcversion import McVersion -from ..util import info_getter, logger -from ..error import ServerExitedError +from mcserverwrapper.error import ServerExitedError +from mcserverwrapper.mcversion import McVersion +from mcserverwrapper.util import info_getter, logger class BaseServer: """The base server, containing server type-independent functionality""" diff --git a/mcserverwrapper/src/server/forge_server.py b/src/mcserverwrapper/server/forge_server.py similarity index 94% rename from mcserverwrapper/src/server/forge_server.py rename to src/mcserverwrapper/server/forge_server.py index 01ade09..a259629 100644 --- a/mcserverwrapper/src/server/forge_server.py +++ b/src/mcserverwrapper/server/forge_server.py @@ -6,8 +6,9 @@ import os import re from zipfile import ZipFile -from .base_server import BaseServer -from ..mcversion import McVersion, McVersionType + +from mcserverwrapper.mcversion import McVersion, McVersionType +from mcserverwrapper.server.base_server import BaseServer class ForgeServer(BaseServer): """ diff --git a/mcserverwrapper/src/server/paper_server.py b/src/mcserverwrapper/server/paper_server.py similarity index 87% rename from mcserverwrapper/src/server/paper_server.py rename to src/mcserverwrapper/server/paper_server.py index 85787ae..56abdb4 100644 --- a/mcserverwrapper/src/server/paper_server.py +++ b/src/mcserverwrapper/server/paper_server.py @@ -2,8 +2,8 @@ from __future__ import annotations -from .base_server import BaseServer -from ..mcversion import McVersion, McVersionType +from mcserverwrapper.mcversion import McVersion, McVersionType +from mcserverwrapper.server.base_server import BaseServer class PaperServer(BaseServer): """ diff --git a/mcserverwrapper/src/server/server_builder.py b/src/mcserverwrapper/server/server_builder.py similarity index 93% rename from mcserverwrapper/src/server/server_builder.py rename to src/mcserverwrapper/server/server_builder.py index d0c36e7..6531d79 100644 --- a/mcserverwrapper/src/server/server_builder.py +++ b/src/mcserverwrapper/server/server_builder.py @@ -5,12 +5,11 @@ import os from pathlib import Path -from mcserverwrapper.src.util import logger - -from .base_server import BaseServer -from .vanilla_server import VanillaServer -from .forge_server import ForgeServer -from ..mcversion import McVersion, McVersionType +from mcserverwrapper.mcversion import McVersion, McVersionType +from mcserverwrapper.server.base_server import BaseServer +from mcserverwrapper.server.forge_server import ForgeServer +from mcserverwrapper.server.vanilla_server import VanillaServer +from mcserverwrapper.util import logger DEFAULT_START_CMD = "java -Xmx4G -Xms4G -jar server.jar nogui" diff --git a/mcserverwrapper/src/server/vanilla_server.py b/src/mcserverwrapper/server/vanilla_server.py similarity index 95% rename from mcserverwrapper/src/server/vanilla_server.py rename to src/mcserverwrapper/server/vanilla_server.py index 5b88810..ada41cf 100644 --- a/mcserverwrapper/src/server/vanilla_server.py +++ b/src/mcserverwrapper/server/vanilla_server.py @@ -6,8 +6,9 @@ import os import re from zipfile import ZipFile -from .base_server import BaseServer -from ..mcversion import McVersion, McVersionType + +from mcserverwrapper.mcversion import McVersion, McVersionType +from mcserverwrapper.server.base_server import BaseServer class VanillaServer(BaseServer): """Class representing a Vanilla (normal/unmodded) Minecraft server""" diff --git a/mcserverwrapper/src/server_properties_helper.py b/src/mcserverwrapper/server_properties_helper.py similarity index 99% rename from mcserverwrapper/src/server_properties_helper.py rename to src/mcserverwrapper/server_properties_helper.py index e673887..54fb267 100644 --- a/mcserverwrapper/src/server_properties_helper.py +++ b/src/mcserverwrapper/server_properties_helper.py @@ -5,7 +5,7 @@ import os from typing import Any -from .mcversion import McVersion +from mcserverwrapper.mcversion import McVersion ALL_PROPERTIES = [ "port", diff --git a/mcserverwrapper/src/util/__init__.py b/src/mcserverwrapper/util/__init__.py similarity index 58% rename from mcserverwrapper/src/util/__init__.py rename to src/mcserverwrapper/util/__init__.py index 6080966..25c8755 100644 --- a/mcserverwrapper/src/util/__init__.py +++ b/src/mcserverwrapper/util/__init__.py @@ -1,6 +1,6 @@ """Export util classes""" -from . import info_getter, logger +from mcserverwrapper.util import info_getter, logger __exports__ = [ info_getter, diff --git a/mcserverwrapper/src/util/info_getter.py b/src/mcserverwrapper/util/info_getter.py similarity index 100% rename from mcserverwrapper/src/util/info_getter.py rename to src/mcserverwrapper/util/info_getter.py diff --git a/mcserverwrapper/src/util/logger.py b/src/mcserverwrapper/util/logger.py similarity index 100% rename from mcserverwrapper/src/util/logger.py rename to src/mcserverwrapper/util/logger.py diff --git a/mcserverwrapper/src/wrapper.py b/src/mcserverwrapper/wrapper.py similarity index 96% rename from mcserverwrapper/src/wrapper.py rename to src/mcserverwrapper/wrapper.py index 31d3d60..65e1c8f 100644 --- a/mcserverwrapper/src/wrapper.py +++ b/src/mcserverwrapper/wrapper.py @@ -8,10 +8,10 @@ from threading import Thread from time import sleep -from .util import logger -from .server import ServerBuilder -from .mcversion import McVersion -from ..src import server_properties_helper +from mcserverwrapper import server_properties_helper +from mcserverwrapper.mcversion import McVersion +from mcserverwrapper.server import ServerBuilder +from mcserverwrapper.util import logger class Wrapper(): """The outer shell of the wrapper, handling inputs and outputs""" diff --git a/mcserverwrapper/test/__init__.py b/src/test/__init__.py similarity index 100% rename from mcserverwrapper/test/__init__.py rename to src/test/__init__.py diff --git a/mcserverwrapper/test/conftest.py b/src/test/conftest.py similarity index 87% rename from mcserverwrapper/test/conftest.py rename to src/test/conftest.py index 808585a..73163a2 100644 --- a/mcserverwrapper/test/conftest.py +++ b/src/test/conftest.py @@ -1,35 +1,29 @@ -# pylint: disable=unused-wildcard-import -# pylint: disable=wildcard-import -# Needed for making pytest fixtures working correctly -# pylint: disable=wrong-import-position, unused-import - -""" - Pytest configuration -""" +"""Pytest configuration""" + +# pylint: disable=wrong-import-position, wrong-import-order, missing-function-docstring + import os -import sys +# Adding source path to sys path import pathlib +import sys + +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../')) +sys.path.append(f"{pathlib.Path(__file__).parent.parent}") +sys.path.append(f"{pathlib.Path(__file__).parent}") +# pylint: enable=wrong-import-position import pytest from pytest import Metafunc, TestReport, Session, ExitCode -from .helpers.common_helper import get_mcserver_log -from .integration_tests import test_forge +from test.helpers.common_helper import get_mcserver_log, get_vanilla_urls +from test.integration_tests import test_forge -# Adding source path to sys path -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../')) -sys.path.append(f"{pathlib.Path(__file__).parent.parent}") -sys.path.append(f"{pathlib.Path(__file__).parent}") +os.environ["DEBUG"] = "True" test_files_path = os.path.join(pathlib.Path(__file__).parent.resolve(), "temp") if not os.path.isdir(test_files_path): os.mkdir(test_files_path) -from .fixtures import newest_server_jar - -from .helpers.common_helper import get_vanilla_urls -# pylint: enable=wrong-import-position - def pytest_generate_tests(metafunc: Metafunc): """Pytest hook""" @@ -89,3 +83,6 @@ def pytest_sessionfinish(session: Session, exitstatus: ExitCode): for line in item.mcserverlog.split("\n"): print(line) # pylint: enable=unused-argument + +# pylint: disable-next=unused-wildcard-import, wildcard-import, wrong-import-order +from test.fixtures import * diff --git a/mcserverwrapper/test/fixtures.py b/src/test/fixtures.py similarity index 95% rename from mcserverwrapper/test/fixtures.py rename to src/test/fixtures.py index 407962f..0a18d28 100644 --- a/mcserverwrapper/test/fixtures.py +++ b/src/test/fixtures.py @@ -6,7 +6,7 @@ import pytest import requests -from .helpers import common_helper +from test.helpers import common_helper @pytest.fixture def newest_server_jar(): diff --git a/mcserverwrapper/test/helpers/common_helper.py b/src/test/helpers/common_helper.py similarity index 98% rename from mcserverwrapper/test/helpers/common_helper.py rename to src/test/helpers/common_helper.py index c64e740..93e1f27 100644 --- a/mcserverwrapper/test/helpers/common_helper.py +++ b/src/test/helpers/common_helper.py @@ -1,22 +1,21 @@ """Common helper functions used during testing""" -from datetime import datetime, timedelta import errno -import socket import os +import re import shutil +import socket +from datetime import datetime, timedelta from threading import Thread from time import sleep -import re import pytest -from bs4 import BeautifulSoup import requests +from bs4 import BeautifulSoup +from javascript import once, require from requests.adapters import HTTPAdapter, Retry -from javascript import require, once - -from mcserverwrapper.src.util import logger +from mcserverwrapper.util import logger mineflayer = require('mineflayer') diff --git a/mcserverwrapper/test/helpers/forge_helper.py b/src/test/helpers/forge_helper.py similarity index 93% rename from mcserverwrapper/test/helpers/forge_helper.py rename to src/test/helpers/forge_helper.py index 9b7f991..8b0c29f 100644 --- a/mcserverwrapper/test/helpers/forge_helper.py +++ b/src/test/helpers/forge_helper.py @@ -1,16 +1,16 @@ """Helpers for testing Forge servers""" import os -from random import randint import re import subprocess +from random import randint import pytest from mcserverwrapper import Wrapper -from mcserverwrapper.src.error import ServerExitedError - -from .common_helper import assert_port_is_free, download_file, setup_workspace +from mcserverwrapper.error import ServerExitedError +from test.helpers.common_helper import (assert_port_is_free, download_file, + setup_workspace) def install_forge(url: str): """Install a forge server from a given installer download url""" diff --git a/mcserverwrapper/test/helpers/vanilla_helper.py b/src/test/helpers/vanilla_helper.py similarity index 93% rename from mcserverwrapper/test/helpers/vanilla_helper.py rename to src/test/helpers/vanilla_helper.py index cba3975..969ba7a 100644 --- a/mcserverwrapper/test/helpers/vanilla_helper.py +++ b/src/test/helpers/vanilla_helper.py @@ -4,8 +4,9 @@ from random import randint from mcserverwrapper import Wrapper - -from .common_helper import connect_mineflayer, setup_workspace, download_file, assert_port_is_free +from test.helpers.common_helper import (assert_port_is_free, + connect_mineflayer, download_file, + setup_workspace) def run_vanilla_test_url(url, offline_mode=False): """Run all tests for a single vanilla minecraft server url""" diff --git a/mcserverwrapper/test/integration_tests/__init__.py b/src/test/integration_tests/__init__.py similarity index 100% rename from mcserverwrapper/test/integration_tests/__init__.py rename to src/test/integration_tests/__init__.py diff --git a/mcserverwrapper/test/integration_tests/test_forge.py b/src/test/integration_tests/test_forge.py similarity index 93% rename from mcserverwrapper/test/integration_tests/test_forge.py rename to src/test/integration_tests/test_forge.py index 1f77bbe..29ebb0e 100644 --- a/mcserverwrapper/test/integration_tests/test_forge.py +++ b/src/test/integration_tests/test_forge.py @@ -1,7 +1,8 @@ """Module containing tests for Forge servers""" -from ..helpers.forge_helper import run_forge_test_url +from test.helpers.forge_helper import run_forge_test_url +# pylint: disable=line-too-long FORGE_URLS = { "1.20.4": "https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.4-49.0.31/forge-1.20.4-49.0.31-installer.jar", "1.20.3": "https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.3-49.0.2/forge-1.20.3-49.0.2-installer.jar", @@ -12,6 +13,7 @@ "1.8.9": "https://maven.minecraftforge.net/net/minecraftforge/forge/1.8.9-11.15.1.2318-1.8.9/forge-1.8.9-11.15.1.2318-1.8.9-installer.jar", "1.7.10": "https://maven.minecraftforge.net/net/minecraftforge/forge/1.7.10-10.13.4.1614-1.7.10/forge-1.7.10-10.13.4.1614-1.7.10-installer.jar" } +# pylint: enable=line-too-long def test_multiple(forge_download_url: str): """Test multiple supported Forge server versions""" diff --git a/mcserverwrapper/test/integration_tests/test_vanilla.py b/src/test/integration_tests/test_vanilla.py similarity index 91% rename from mcserverwrapper/test/integration_tests/test_vanilla.py rename to src/test/integration_tests/test_vanilla.py index d5b8478..e34832a 100644 --- a/mcserverwrapper/test/integration_tests/test_vanilla.py +++ b/src/test/integration_tests/test_vanilla.py @@ -1,17 +1,18 @@ """Module containing tests for Vanilla servers""" import os +from datetime import datetime, timedelta from random import randint from time import sleep -from datetime import datetime, timedelta import pytest -from mcserverwrapper import Wrapper -from mcserverwrapper.src import error -from ..helpers.common_helper import assert_port_is_free, download_file, connect_mineflayer, setup_workspace -from ..helpers.vanilla_helper import run_vanilla_test, run_vanilla_test_url -from ..testable_thread import TestableThread +from mcserverwrapper import Wrapper, error +from test.helpers.common_helper import (assert_port_is_free, + connect_mineflayer, download_file, + setup_workspace) +from test.helpers.vanilla_helper import run_vanilla_test, run_vanilla_test_url +from test.testable_thread import TestableThread def test_all(jar_version_tuple): """Tests all of the vanilla minecraft versions""" diff --git a/mcserverwrapper/test/module_tests/__init__.py b/src/test/module_tests/__init__.py similarity index 100% rename from mcserverwrapper/test/module_tests/__init__.py rename to src/test/module_tests/__init__.py diff --git a/mcserverwrapper/test/module_tests/test_server_properties_helper.py b/src/test/module_tests/test_server_properties_helper.py similarity index 98% rename from mcserverwrapper/test/module_tests/test_server_properties_helper.py rename to src/test/module_tests/test_server_properties_helper.py index d1c2a6a..93efee1 100644 --- a/mcserverwrapper/test/module_tests/test_server_properties_helper.py +++ b/src/test/module_tests/test_server_properties_helper.py @@ -1,10 +1,10 @@ """Test the server_properties_helper methods""" -import pathlib import os +import pathlib -from mcserverwrapper.src.mcversion import McVersion, McVersionType -from ...src import server_properties_helper as sph +from mcserverwrapper import server_properties_helper as sph +from mcserverwrapper.mcversion import McVersion, McVersionType def test_get_mixed_params(): """Tests the helper with mixed params""" diff --git a/mcserverwrapper/test/testable_thread.py b/src/test/testable_thread.py similarity index 100% rename from mcserverwrapper/test/testable_thread.py rename to src/test/testable_thread.py From 680fd1a44301cd042090922043566ef58e84afd6 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 10:51:32 +0200 Subject: [PATCH 06/16] fix import in example --- examples/example_01.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example_01.py b/examples/example_01.py index e826ce1..24ac1aa 100644 --- a/examples/example_01.py +++ b/examples/example_01.py @@ -4,7 +4,7 @@ import pathlib import requests -from src.mcserverwrapper import Wrapper +from mcserverwrapper import Wrapper def download_server_jar(): """Download the server jar""" From d039f7ae80462a5cbaeb17b0704745ee7bdbe522 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 11:04:14 +0200 Subject: [PATCH 07/16] move all test data to temp dir --- .gitignore | 6 ++--- src/test/conftest.py | 17 ++++++++++--- src/test/fixtures.py | 6 ++--- src/test/helpers/common_helper.py | 16 ++++++------- src/test/helpers/forge_helper.py | 4 ++-- src/test/helpers/vanilla_helper.py | 2 +- src/test/integration_tests/test_vanilla.py | 6 ++--- .../test_server_properties_helper.py | 24 ++++++++----------- 8 files changed, 43 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 418da71..abc0e85 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,8 @@ *\.vscode *\venv* *\dist -*\testdir -*\testdir_persistent +*\temp + *.pyc *.egg-info -src/test/temp -examples/temp password.txt diff --git a/src/test/conftest.py b/src/test/conftest.py index 73163a2..363a14c 100644 --- a/src/test/conftest.py +++ b/src/test/conftest.py @@ -20,9 +20,20 @@ os.environ["DEBUG"] = "True" -test_files_path = os.path.join(pathlib.Path(__file__).parent.resolve(), "temp") -if not os.path.isdir(test_files_path): - os.mkdir(test_files_path) +if not os.path.isdir("temp"): + os.mkdir("temp") + +def pytest_addoption(parser): + parser.addoption( + "--skip-linting", action="store_true", default=False, help="skip the pylint test" + ) + +def pytest_collection_modifyitems(config, items): + if config.getoption("--skip-linting"): + skip_pylint = pytest.mark.skip(reason="skipping code linting due to --skip-linting arg") + for item in items: + if item.name in ["test_pylint", "test_mypy"]: + item.add_marker(skip_pylint) def pytest_generate_tests(metafunc: Metafunc): """Pytest hook""" diff --git a/src/test/fixtures.py b/src/test/fixtures.py index 0a18d28..4cbb843 100644 --- a/src/test/fixtures.py +++ b/src/test/fixtures.py @@ -13,9 +13,9 @@ def newest_server_jar(): """Download the newest server jar version and return the jar file path""" url = "https://piston-data.mojang.com/v1/objects/8dd1a28015f51b1803213892b50b7b4fc76e594d/server.jar" - filename = os.path.join("testdir_persistent", "server.jar") + filename = os.path.join("temp", "testdir_persistent", "server.jar") - os.makedirs("testdir_persistent", exist_ok=True) + os.makedirs(os.path.join("temp", "testdir_persistent"), exist_ok=True) if not os.path.isfile(filename): req = requests.get(url, timeout=5) @@ -24,7 +24,7 @@ def newest_server_jar(): common_helper.setup_workspace() - testdir_filename = os.path.join("testdir", "server.jar") + testdir_filename = os.path.join("temp", "testdir", "server.jar") shutil.copyfile(filename, testdir_filename) return "server.jar" diff --git a/src/test/helpers/common_helper.py b/src/test/helpers/common_helper.py index 93e1f27..56a1e0d 100644 --- a/src/test/helpers/common_helper.py +++ b/src/test/helpers/common_helper.py @@ -23,9 +23,9 @@ def setup_workspace(): """Setup the testing folder""" try: - if os.path.isdir("testdir"): - shutil.rmtree("testdir") - os.makedirs("testdir", exist_ok=True) + if os.path.isdir(os.path.join("temp", "testdir")): + shutil.rmtree(os.path.join("temp", "testdir")) + os.makedirs(os.path.join("temp", "testdir"), exist_ok=True) except PermissionError as e: pytest.skip("cannot access testdir") @@ -35,11 +35,11 @@ def setup_workspace(): def reset_workspace(): """Delete everything inside the testing folder""" - for entry in os.listdir("testdir"): - if os.path.isfile(os.path.join("testdir", entry)): - os.remove(os.path.join("testdir", entry)) + for entry in os.listdir(os.path.join("temp", "testdir")): + if os.path.isfile(os.path.join("temp", "testdir", entry)): + os.remove(os.path.join("temp", "testdir", entry)) else: - shutil.rmtree(os.path.join("testdir", entry)) + shutil.rmtree(os.path.join("temp", "testdir", entry)) def assert_port_is_free(port: int = 25565, strict=True) -> bool: """Skips the current test if the given port is not free""" @@ -119,7 +119,7 @@ def download_file(url, counter=""): backoff_factor=0.1) s.mount("https://", HTTPAdapter(max_retries=retries)) req = s.get(url, timeout=30) - with open(os.path.join("testdir", local_filename), 'wb') as file: + with open(os.path.join("temp", "testdir", local_filename), 'wb') as file: file.write(req.content) return local_filename diff --git a/src/test/helpers/forge_helper.py b/src/test/helpers/forge_helper.py index 8b0c29f..63cd6f2 100644 --- a/src/test/helpers/forge_helper.py +++ b/src/test/helpers/forge_helper.py @@ -16,7 +16,7 @@ def install_forge(url: str): """Install a forge server from a given installer download url""" installer_jar = download_file(url) - testdir = os.path.join(os.getcwd(), "testdir") + testdir = os.path.join(os.getcwd(), "temp", "testdir") with subprocess.Popen(["java", "-jar", installer_jar, "--installServer"], stdout=subprocess.DEVNULL, @@ -65,7 +65,7 @@ def run_forge_test(jarfile, offline_mode=False): if offline_mode: server_property_args["onli"] = "false" - wrapper = Wrapper(os.path.join(os.getcwd(), "testdir", jarfile), server_start_command=start_cmd, print_output=False, + wrapper = Wrapper(os.path.join(os.getcwd(), "temp", "testdir", jarfile), server_start_command=start_cmd, print_output=False, server_property_args=server_property_args) try: diff --git a/src/test/helpers/vanilla_helper.py b/src/test/helpers/vanilla_helper.py index 969ba7a..18fac41 100644 --- a/src/test/helpers/vanilla_helper.py +++ b/src/test/helpers/vanilla_helper.py @@ -40,7 +40,7 @@ def run_vanilla_test(jarfile, offline_mode=False): if offline_mode: server_property_args["onli"] = "false" - wrapper = Wrapper(os.path.join(os.getcwd(), "testdir", jarfile), server_start_command=start_cmd, print_output=False, + wrapper = Wrapper(os.path.join(os.getcwd(), "temp", "testdir", jarfile), server_start_command=start_cmd, print_output=False, server_property_args=server_property_args) wrapper.startup() assert wrapper.server_running() diff --git a/src/test/integration_tests/test_vanilla.py b/src/test/integration_tests/test_vanilla.py index e34832a..e3a9a52 100644 --- a/src/test/integration_tests/test_vanilla.py +++ b/src/test/integration_tests/test_vanilla.py @@ -48,7 +48,7 @@ def _test_download_all_jars(jar_download_url): setup_workspace() jarfile = download_file(url) - assert os.path.isfile(os.path.join("testdir", jarfile)) + assert os.path.isfile(os.path.join("temp", "testdir", jarfile)) def _test_broken_versions(): setup_workspace() @@ -91,7 +91,7 @@ def test_mineflayer(newest_server_jar): "untp": "false" } - wrapper = Wrapper(os.path.join(os.getcwd(), "testdir", newest_server_jar), + wrapper = Wrapper(os.path.join(os.getcwd(), "temp", "testdir", newest_server_jar), server_start_command=start_cmd, server_property_args=server_params, print_output=False) @@ -124,7 +124,7 @@ def test_invalid_start_params(newest_server_jar): start_cmd = f"java -Xmx2G -jar {newest_server_jar}nogui" - wrapper = Wrapper(os.path.join(os.getcwd(), "testdir", newest_server_jar), + wrapper = Wrapper(os.path.join(os.getcwd(), "temp", "testdir", newest_server_jar), server_start_command=start_cmd, print_output=False) diff --git a/src/test/module_tests/test_server_properties_helper.py b/src/test/module_tests/test_server_properties_helper.py index 93efee1..16816dd 100644 --- a/src/test/module_tests/test_server_properties_helper.py +++ b/src/test/module_tests/test_server_properties_helper.py @@ -18,11 +18,10 @@ def test_get_mixed_params(): "use-native-transport": "false" } - props_path = os.path.join(pathlib.Path(__file__).parent.parent.resolve(), "temp") - with open(os.path.join(props_path, "server.properties"), "w+", encoding="utf8") as props_file: + with open(os.path.join("temp", "server.properties"), "w+", encoding="utf8") as props_file: props_file.write("\n".join([f"{key}={value}" for key, value in props.items()])) - result = sph.get_properties(props_path, McVersion("1.20", McVersionType.VANILLA)) + result = sph.get_properties("temp", McVersion("1.20", McVersionType.VANILLA)) assert len(result) == 5 assert result["maxp"] == props["max-players"] @@ -90,8 +89,7 @@ def test_params_with_file(): "a-final-prop=aha\n" + \ "online-mode=true\n" - props_path = os.path.join(pathlib.Path(__file__).parent.parent.resolve(), "temp") - with open(os.path.join(props_path, "server.properties"), "w+", encoding="utf8") as props_file: + with open(os.path.join("temp", "server.properties"), "w+", encoding="utf8") as props_file: props_file.write(props_test_data) props = { @@ -99,7 +97,7 @@ def test_params_with_file(): "untp": "true" } - result = sph.parse_properties_args(props_path, props, McVersion("1.20", McVersionType.VANILLA)) + result = sph.parse_properties_args("temp", props, McVersion("1.20", McVersionType.VANILLA)) # ensure that props didn't change assert len(props) == 2 @@ -176,8 +174,7 @@ def test_save_existing(): "a-final-prop=aha\n" + \ "online-mode=false" - props_path = os.path.join(pathlib.Path(__file__).parent.parent.resolve(), "temp") - with open(os.path.join(props_path, "server.properties"), "w+", encoding="utf8") as props_file: + with open(os.path.join("temp", "server.properties"), "w+", encoding="utf8") as props_file: props_file.write(props_test_data) props = { @@ -188,7 +185,7 @@ def test_save_existing(): "untp": "false" } - sph.save_properties(props_path, props) + sph.save_properties("temp", props) # ensure that props didn't change assert len(props) == 5 @@ -198,7 +195,7 @@ def test_save_existing(): assert props["levt"] == sph.DEFAULT_LEVEL_TYPE_POST_1_19 assert props["untp"] == "false" - with open(os.path.join(props_path, "server.properties"), "r", encoding="utf8") as props_file: + with open(os.path.join("temp", "server.properties"), "r", encoding="utf8") as props_file: lines = props_file.read().splitlines() assert len(lines) == 7 @@ -216,8 +213,7 @@ def test_save_new(): props_test_data = "some-other-prop=haha\n" + \ "a-final-prop=aha\n" - props_path = os.path.join(pathlib.Path(__file__).parent.parent.resolve(), "temp") - with open(os.path.join(props_path, "server.properties"), "w+", encoding="utf8") as props_file: + with open(os.path.join("temp", "server.properties"), "w+", encoding="utf8") as props_file: props_file.write(props_test_data) props = { @@ -228,7 +224,7 @@ def test_save_new(): "untp": "true" } - sph.save_properties(props_path, props) + sph.save_properties("temp", props) # ensure that props didn't change assert len(props) == 5 @@ -238,7 +234,7 @@ def test_save_new(): assert props["levt"] == "minecraft\\:normal" assert props["untp"] == "true" - with open(os.path.join(props_path, "server.properties"), "r", encoding="utf8") as props_file: + with open(os.path.join("temp", "server.properties"), "r", encoding="utf8") as props_file: lines = props_file.read().splitlines() assert len(lines) == 7 From 02f86db9235c834a072dd7575325be2f216866f8 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 11:06:17 +0200 Subject: [PATCH 08/16] pylint --- .gitignore | 1 - src/test/helpers/forge_helper.py | 4 +++- src/test/helpers/vanilla_helper.py | 4 +++- src/test/module_tests/test_server_properties_helper.py | 1 - 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index abc0e85..e605598 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ *\venv* *\dist *\temp - *.pyc *.egg-info password.txt diff --git a/src/test/helpers/forge_helper.py b/src/test/helpers/forge_helper.py index 63cd6f2..43d3bcf 100644 --- a/src/test/helpers/forge_helper.py +++ b/src/test/helpers/forge_helper.py @@ -65,7 +65,9 @@ def run_forge_test(jarfile, offline_mode=False): if offline_mode: server_property_args["onli"] = "false" - wrapper = Wrapper(os.path.join(os.getcwd(), "temp", "testdir", jarfile), server_start_command=start_cmd, print_output=False, + wrapper = Wrapper(os.path.join(os.getcwd(), "temp", "testdir", jarfile), + server_start_command=start_cmd, + print_output=False, server_property_args=server_property_args) try: diff --git a/src/test/helpers/vanilla_helper.py b/src/test/helpers/vanilla_helper.py index 18fac41..ec0fe5b 100644 --- a/src/test/helpers/vanilla_helper.py +++ b/src/test/helpers/vanilla_helper.py @@ -40,7 +40,9 @@ def run_vanilla_test(jarfile, offline_mode=False): if offline_mode: server_property_args["onli"] = "false" - wrapper = Wrapper(os.path.join(os.getcwd(), "temp", "testdir", jarfile), server_start_command=start_cmd, print_output=False, + wrapper = Wrapper(os.path.join(os.getcwd(), "temp", "testdir", jarfile), + server_start_command=start_cmd, + print_output=False, server_property_args=server_property_args) wrapper.startup() assert wrapper.server_running() diff --git a/src/test/module_tests/test_server_properties_helper.py b/src/test/module_tests/test_server_properties_helper.py index 16816dd..e6ad072 100644 --- a/src/test/module_tests/test_server_properties_helper.py +++ b/src/test/module_tests/test_server_properties_helper.py @@ -1,7 +1,6 @@ """Test the server_properties_helper methods""" import os -import pathlib from mcserverwrapper import server_properties_helper as sph from mcserverwrapper.mcversion import McVersion, McVersionType From b5820629d17a4bd900761f3cc0a8951f248c96cf Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 12:03:18 +0200 Subject: [PATCH 09/16] add optional --test-all option to pylint, make tests more robust --- .github/workflows/define-pytest.yml | 6 +- .github/workflows/release_package.yml | 1 + src/test/conftest.py | 19 +++-- src/test/helpers/common_helper.py | 4 +- src/test/helpers/forge_helper.py | 27 +++---- src/test/helpers/vanilla_helper.py | 84 ++++++++++++---------- src/test/integration_tests/test_vanilla.py | 38 +++++----- 7 files changed, 103 insertions(+), 76 deletions(-) diff --git a/.github/workflows/define-pytest.yml b/.github/workflows/define-pytest.yml index e8f7f73..da38454 100644 --- a/.github/workflows/define-pytest.yml +++ b/.github/workflows/define-pytest.yml @@ -6,6 +6,10 @@ on: python-version: required: true type: string + pytest-args: + required: false + type: string + default: "" defaults: run: @@ -51,4 +55,4 @@ jobs: echo $PASSWORD >> password.txt - name: Run tests with pytest run: | - pytest src -rs --skip-linting + pytest src -rs --skip-linting ${{ inputs.pytest-args }} diff --git a/.github/workflows/release_package.yml b/.github/workflows/release_package.yml index b57af4f..211818d 100644 --- a/.github/workflows/release_package.yml +++ b/.github/workflows/release_package.yml @@ -23,6 +23,7 @@ jobs: uses: ./.github/workflows/define-pytest.yml with: python-version: ${{ matrix.python-version }} + pytest-args: "--test-all" secrets: inherit build: diff --git a/src/test/conftest.py b/src/test/conftest.py index 363a14c..fb6f346 100644 --- a/src/test/conftest.py +++ b/src/test/conftest.py @@ -13,7 +13,6 @@ # pylint: enable=wrong-import-position import pytest -from pytest import Metafunc, TestReport, Session, ExitCode from test.helpers.common_helper import get_mcserver_log, get_vanilla_urls from test.integration_tests import test_forge @@ -23,19 +22,27 @@ if not os.path.isdir("temp"): os.mkdir("temp") -def pytest_addoption(parser): +def pytest_addoption(parser: pytest.Parser): parser.addoption( "--skip-linting", action="store_true", default=False, help="skip the pylint test" ) + parser.addoption( + "--test-all", action="store_true", default=False, help="test all supported minecraft versions" + ) -def pytest_collection_modifyitems(config, items): +def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]): if config.getoption("--skip-linting"): skip_pylint = pytest.mark.skip(reason="skipping code linting due to --skip-linting arg") for item in items: if item.name in ["test_pylint", "test_mypy"]: item.add_marker(skip_pylint) + if not config.getoption("--test-all"): + skip_pylint = pytest.mark.skip(reason="skipping testing all minecraft versions due to mising --test-all arg") + for item in items: + if item.name.startswith("test_all[") or item.name.endswith("test_multiple["): + item.add_marker(skip_pylint) -def pytest_generate_tests(metafunc: Metafunc): +def pytest_generate_tests(metafunc: pytest.Metafunc): """Pytest hook""" if "jar_version_tuple" in metafunc.fixturenames: @@ -63,7 +70,7 @@ def pytest_runtest_makereport(item, call): """Save mcserverwrapper.log to the current pytest item""" # execute all other hooks to obtain the report object - rep: TestReport = yield + rep: pytest.TestReport = yield # we only look at actual failing test calls, not setup/teardown if rep.when == "call" and rep.outcome == "failed": @@ -72,7 +79,7 @@ def pytest_runtest_makereport(item, call): return rep @pytest.hookimpl(trylast=True) -def pytest_sessionfinish(session: Session, exitstatus: ExitCode): +def pytest_sessionfinish(session: pytest.Session, exitstatus: pytest.ExitCode): """Print all saved mcserverwrapper.log after all tests finished""" if len(session.items) == 0: diff --git a/src/test/helpers/common_helper.py b/src/test/helpers/common_helper.py index 56a1e0d..7036375 100644 --- a/src/test/helpers/common_helper.py +++ b/src/test/helpers/common_helper.py @@ -98,7 +98,7 @@ def func(bot, bot_connected): while not bot_connected[0]: if (datetime.now() - start_time) > timedelta(seconds=30): - pytest.skip(f"Bot connection to {address}:{port} timed out") + raise Exception(f"Bot connection to {address}:{port} timed out") sleep(0.1) bot.chat('I spawned') @@ -168,7 +168,7 @@ def _version_valid(version): return False if vers_split[1] == 7 and (len(vers_split) < 3 or vers_split[2] < 10): return False - if ".".join([str(item) for item in vers_split]) in ["1.8"]: + if ".".join([str(item) for item in vers_split]) in ["1.8", "1.21.9"]: return False return True diff --git a/src/test/helpers/forge_helper.py b/src/test/helpers/forge_helper.py index 43d3bcf..5eafdd9 100644 --- a/src/test/helpers/forge_helper.py +++ b/src/test/helpers/forge_helper.py @@ -76,20 +76,23 @@ def run_forge_test(jarfile, offline_mode=False): # server exited because of invalid java verison pytest.skip("Server exited likely due to wrong java version") - assert wrapper.server_running() + try: + assert wrapper.server_running() - while not wrapper.output_queue.empty(): - wrapper.output_queue.get() + while not wrapper.output_queue.empty(): + wrapper.output_queue.get() - wrapper.send_command("/say Hello World") + wrapper.send_command("/say Hello World") - line = "" - while "Hello World" not in line: - line = wrapper.output_queue.get(timeout=10) + line = "" + while "Hello World" not in line: + line = wrapper.output_queue.get(timeout=10) - wrapper.send_command("/kick Developer", wait_time=1) - wrapper.server.kill() + wrapper.server.stop() - assert not wrapper.server_running() - # assert that the server process really stopped - assert wrapper.server.get_child_status(0.1) is not None + assert not wrapper.server_running() + # assert that the server process really stopped + assert wrapper.server.get_child_status(0.1) is not None + except BaseException: + wrapper.server.kill() + raise diff --git a/src/test/helpers/vanilla_helper.py b/src/test/helpers/vanilla_helper.py index ec0fe5b..5d51a92 100644 --- a/src/test/helpers/vanilla_helper.py +++ b/src/test/helpers/vanilla_helper.py @@ -45,45 +45,51 @@ def run_vanilla_test(jarfile, offline_mode=False): print_output=False, server_property_args=server_property_args) wrapper.startup() - assert wrapper.server_running() - - while not wrapper.output_queue.empty(): - wrapper.output_queue.get() - - wrapper.send_command("/say Hello World") - - line = "" - while "Hello World" not in line: - line = wrapper.output_queue.get(timeout=10) - - # Mineflayer doesn't yet support 1.20.5+ - # https://github.com/PrismarineJS/mineflayer/issues/3405 - # https://github.com/PrismarineJS/mineflayer/issues/3406 - # MineFlayer doesn't (yet) support 1.7.10 - # https://github.com/PrismarineJS/mineflayer/issues/432 - # the other versions fail because of missing protocol data - if wrapper.get_version().name not in ["1.7.10", - "1.9.1", - "1.9.2", - "1.14.2", - "1.20.5", - "1.20.6", - "1.21", - "1.21.1", - "1.21.2", - "1.21.3", - "1.21.4", - "1.21.5"]: - bot = connect_mineflayer(port=port, offline_mode=offline_mode) - assert bot is not None - line = "" - while "I spawned" not in line: - line = wrapper.output_queue.get(timeout=5) + try: + assert wrapper.server_running() + + while not wrapper.output_queue.empty(): + wrapper.output_queue.get() - wrapper.send_command("/kick Developer", wait_time=1) - wrapper.server.kill() + wrapper.send_command("/say Hello World") - assert not wrapper.server_running() - # assert that the server process really stopped - assert wrapper.server.get_child_status(0.1) is not None + line = "" + while "Hello World" not in line: + line = wrapper.output_queue.get(timeout=10) + + # Mineflayer doesn't yet support 1.20.5+ + # https://github.com/PrismarineJS/mineflayer/issues/3405 + # https://github.com/PrismarineJS/mineflayer/issues/3406 + # MineFlayer doesn't (yet) support 1.7.10 + # https://github.com/PrismarineJS/mineflayer/issues/432 + # the other versions fail because of missing protocol data + if wrapper.get_version().name not in ["1.7.10", + "1.9.1", + "1.9.2", + "1.14.2", + "1.20.5", + "1.20.6", + "1.21", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.21.5"]: + bot = connect_mineflayer(port=port, offline_mode=offline_mode) + assert bot is not None + + line = "" + while "I spawned" not in line: + line = wrapper.output_queue.get(timeout=5) + + wrapper.send_command("/kick Developer", wait_time=1) + + wrapper.server.stop() + + assert not wrapper.server_running() + # assert that the server process really stopped + assert wrapper.server.get_child_status(0.1) is not None + except BaseException: + wrapper.server.kill() + raise diff --git a/src/test/integration_tests/test_vanilla.py b/src/test/integration_tests/test_vanilla.py index e3a9a52..6e4bc9e 100644 --- a/src/test/integration_tests/test_vanilla.py +++ b/src/test/integration_tests/test_vanilla.py @@ -96,28 +96,34 @@ def test_mineflayer(newest_server_jar): server_property_args=server_params, print_output=False) wrapper.startup() - assert wrapper.server_running() - while not wrapper.output_queue.empty(): - wrapper.output_queue.get() - wrapper.send_command("/say Hello World") + try: + assert wrapper.server_running() + while not wrapper.output_queue.empty(): + wrapper.output_queue.get() + + wrapper.send_command("/say Hello World") + + line = "" + while "Hello World" not in line: + line = wrapper.output_queue.get(timeout=5) - line = "" - while "Hello World" not in line: - line = wrapper.output_queue.get(timeout=5) + bot = connect_mineflayer(port=port) + assert bot is not None - bot = connect_mineflayer(port=port) - assert bot is not None + line = "" + while "I spawned" not in line: + line = wrapper.output_queue.get(timeout=5) - line = "" - while "I spawned" not in line: - line = wrapper.output_queue.get(timeout=5) + wrapper.send_command("/kick Developer", wait_time=1) - wrapper.send_command("/kick Developer", wait_time=1) - wrapper.server.kill() + wrapper.server.stop() - # assert that the server process really stopped - assert wrapper.server.get_child_status(0.1) is not None + # assert that the server process really stopped + assert wrapper.server.get_child_status(0.1) is not None + except BaseException: + wrapper.server.kill() + raise def test_invalid_start_params(newest_server_jar): """Test a server with an invalid startup command""" From ce855007062d36bd284003ba2f4a707f178aa8ee Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 12:11:32 +0200 Subject: [PATCH 10/16] ignore logfile if it doesn't exist, run all tests on label apply --- .github/workflows/remove-merge-label.yml | 21 +++++++++++++++++ .github/workflows/run-tests-on-labeled.yml | 27 ++++++++++++++++++++++ .github/workflows/run-tests.yml | 2 +- src/test/helpers/common_helper.py | 4 ++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/remove-merge-label.yml create mode 100644 .github/workflows/run-tests-on-labeled.yml diff --git a/.github/workflows/remove-merge-label.yml b/.github/workflows/remove-merge-label.yml new file mode 100644 index 0000000..845fb23 --- /dev/null +++ b/.github/workflows/remove-merge-label.yml @@ -0,0 +1,21 @@ +name: Remove 'ready to merge' label + +on: + pull_request: + types: [closed, synchronize] + +permissions: + contents: read + id-token: write + pull-requests: write + +jobs: + remove_label: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-ecosystem/action-remove-labels@v1 + if: (github.event.action == 'synchronize') || (github.event.pull_request.merged == true) + with: + labels: 'ready to merge' + fail_on_error: false diff --git a/.github/workflows/run-tests-on-labeled.yml b/.github/workflows/run-tests-on-labeled.yml new file mode 100644 index 0000000..8ddc3b1 --- /dev/null +++ b/.github/workflows/run-tests-on-labeled.yml @@ -0,0 +1,27 @@ +name: Run all tests on label apply + +on: + pull_request: + types: [labeled] + +jobs: + pylint: + if: github.event.label.name == 'ready to merge' + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + uses: ./.github/workflows/define-pylint.yml + with: + python-version: ${{ matrix.python-version }} + secrets: inherit + + pytest: + if: github.event.label.name == 'ready to merge' + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + uses: ./.github/workflows/define-pytest.yml + with: + python-version: ${{ matrix.python-version }} + pytest-args: "--test-all" + secrets: inherit diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d32b997..7476456 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: pylint: strategy: matrix: - python-version: ["3.9", "3.14"] + python-version: ["3.14"] uses: ./.github/workflows/define-pylint.yml with: python-version: ${{ matrix.python-version }} diff --git a/src/test/helpers/common_helper.py b/src/test/helpers/common_helper.py index 7036375..b5108b1 100644 --- a/src/test/helpers/common_helper.py +++ b/src/test/helpers/common_helper.py @@ -129,6 +129,10 @@ def get_mcserver_log() -> str: if logger.logfile_path is None: print("Logger was not yet setup, cannot print logfile") return "" + + if not os.path.isfile(logger.logfile_path): + print("logfile doesn't exist, cannot print logfile") + return "" data = f"Printing out {logger.LOGFILE_NAME}:\n" with open(logger.logfile_path, "r", encoding="utf8") as f: From 6d2b75608806c8d7344a0c0cc619f3d6100648b1 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 12:13:25 +0200 Subject: [PATCH 11/16] pylint --- src/test/helpers/common_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/helpers/common_helper.py b/src/test/helpers/common_helper.py index b5108b1..9e4c0d8 100644 --- a/src/test/helpers/common_helper.py +++ b/src/test/helpers/common_helper.py @@ -129,7 +129,7 @@ def get_mcserver_log() -> str: if logger.logfile_path is None: print("Logger was not yet setup, cannot print logfile") return "" - + if not os.path.isfile(logger.logfile_path): print("logfile doesn't exist, cannot print logfile") return "" From 5e365fec433cb67ea9aaf8b7edea2d2958d0f687 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 13:18:55 +0200 Subject: [PATCH 12/16] skip tests that require online mode --- .github/workflows/release_package.yml | 1 - .github/workflows/run-tests-on-labeled.yml | 1 - .github/workflows/run-tests.yml | 1 + src/test/conftest.py | 13 ++++++++++--- src/test/integration_tests/test_forge.py | 2 +- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release_package.yml b/.github/workflows/release_package.yml index 211818d..b57af4f 100644 --- a/.github/workflows/release_package.yml +++ b/.github/workflows/release_package.yml @@ -23,7 +23,6 @@ jobs: uses: ./.github/workflows/define-pytest.yml with: python-version: ${{ matrix.python-version }} - pytest-args: "--test-all" secrets: inherit build: diff --git a/.github/workflows/run-tests-on-labeled.yml b/.github/workflows/run-tests-on-labeled.yml index 8ddc3b1..1eb0e23 100644 --- a/.github/workflows/run-tests-on-labeled.yml +++ b/.github/workflows/run-tests-on-labeled.yml @@ -23,5 +23,4 @@ jobs: uses: ./.github/workflows/define-pytest.yml with: python-version: ${{ matrix.python-version }} - pytest-args: "--test-all" secrets: inherit diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 7476456..70f1757 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -28,4 +28,5 @@ jobs: uses: ./.github/workflows/define-pytest.yml with: python-version: ${{ matrix.python-version }} + pytest-args: "--skip-test-all" secrets: inherit diff --git a/src/test/conftest.py b/src/test/conftest.py index fb6f346..be868b5 100644 --- a/src/test/conftest.py +++ b/src/test/conftest.py @@ -27,7 +27,7 @@ def pytest_addoption(parser: pytest.Parser): "--skip-linting", action="store_true", default=False, help="skip the pylint test" ) parser.addoption( - "--test-all", action="store_true", default=False, help="test all supported minecraft versions" + "--skip-test-all", action="store_true", default=False, help="skip testing all supported minecraft versions" ) def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]): @@ -36,11 +36,18 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item for item in items: if item.name in ["test_pylint", "test_mypy"]: item.add_marker(skip_pylint) - if not config.getoption("--test-all"): + + if config.getoption("--skip-test-all"): skip_pylint = pytest.mark.skip(reason="skipping testing all minecraft versions due to mising --test-all arg") for item in items: - if item.name.startswith("test_all[") or item.name.endswith("test_multiple["): + if item.name.startswith("test_all[") or item.name.startswith("test_multiple["): item.add_marker(skip_pylint) + + skip_pylint = pytest.mark.skip(reason="skipping testing in online mode as " + "https://github.com/PrismarineJS/prismarine-auth/pull/137 is not yet merged") + for item in items: + if item.name in ["test_single_online", "test_mineflayer"]: + item.add_marker(skip_pylint) def pytest_generate_tests(metafunc: pytest.Metafunc): """Pytest hook""" diff --git a/src/test/integration_tests/test_forge.py b/src/test/integration_tests/test_forge.py index 29ebb0e..765aaac 100644 --- a/src/test/integration_tests/test_forge.py +++ b/src/test/integration_tests/test_forge.py @@ -18,7 +18,7 @@ def test_multiple(forge_download_url: str): """Test multiple supported Forge server versions""" - run_forge_test_url(forge_download_url) + run_forge_test_url(forge_download_url, offline_mode=True) def test_single_online(): """Test a single Forge version in online mode""" From 919f124c2953714104dc16298a80822d4a3de1e6 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 13:20:19 +0200 Subject: [PATCH 13/16] pylint --- src/test/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/conftest.py b/src/test/conftest.py index be868b5..eb1abf3 100644 --- a/src/test/conftest.py +++ b/src/test/conftest.py @@ -42,7 +42,7 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item for item in items: if item.name.startswith("test_all[") or item.name.startswith("test_multiple["): item.add_marker(skip_pylint) - + skip_pylint = pytest.mark.skip(reason="skipping testing in online mode as " "https://github.com/PrismarineJS/prismarine-auth/pull/137 is not yet merged") for item in items: From 0332a34affcce73a3b9fe3663df2c159af500e96 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 13:44:54 +0200 Subject: [PATCH 14/16] add pre-commit hooks --- .github/workflows/release_package.yml | 6 +- .github/workflows/run-tests.yml | 2 +- .pre-commit-config.yaml | 39 ++++++++++++ README.md | 66 +++++++++++++++++--- examples/example_01.py | 1 + pyproject.toml | 1 + requirements.txt | 1 + src/mcserverwrapper/error.py | 2 +- src/mcserverwrapper/server/base_server.py | 2 +- src/mcserverwrapper/server/server_builder.py | 2 +- src/mcserverwrapper/util/info_getter.py | 17 +---- src/test/conftest.py | 6 +- src/test/fixtures.py | 5 +- src/test/helpers/common_helper.py | 6 +- src/test/helpers/forge_helper.py | 6 +- src/test/helpers/vanilla_helper.py | 4 +- src/test/integration_tests/test_vanilla.py | 10 +-- 17 files changed, 127 insertions(+), 49 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/release_package.yml b/.github/workflows/release_package.yml index b57af4f..183c99c 100644 --- a/.github/workflows/release_package.yml +++ b/.github/workflows/release_package.yml @@ -73,17 +73,17 @@ jobs: echo "release-tag=$(echo $TAG)" >> $GITHUB_OUTPUT - uses: mukunku/tag-exists-action@v1.6.0 id: check-tag - with: + with: tag: ${{ steps.read-tag.outputs.release-tag }} - name: Fail if tag exists run: | echo "Tag ${{ steps.read-tag.outputs.release-tag }} exists!" exit 1 - if: steps.check-tag.outputs.exists == 'true' + if: steps.check-tag.outputs.exists == 'true' - name: Print tag if it doesn't exist run: | echo "Tag ${{ steps.read-tag.outputs.release-tag }} doesn't yet exist and can be created" - if: steps.check-tag.outputs.exists == 'false' + if: steps.check-tag.outputs.exists == 'false' release: needs: [pylint, pytest, build, tag] diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 70f1757..bccb25f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,6 +1,6 @@ name: Run Tests -on: +on: push: branches: [main] pull_request: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..52f0036 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,39 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + files: ^.*\.(py|md|yml)$ + exclude: > + (?x)^( + src/test/fuzzy_test.py| + src/test/alg_test.py + )$ + additional_dependencies: + - tomli +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.12.10 + hooks: + - id: ruff-check + args: + - --fix + - --unsafe-fixes +- repo: https://github.com/pycqa/isort + rev: 6.0.1 + hooks: + - id: isort + name: isort (python) +- repo: https://github.com/pycqa/pylint + rev: v3.3.8 + hooks: + - id: pylint + args: + - -d import-error + - -sn diff --git a/README.md b/README.md index 6c1db55..f9c08b0 100644 --- a/README.md +++ b/README.md @@ -57,23 +57,41 @@ Finally, the server is stopped gracefully. More examples can be found in the **examples** folder. -## Run tests locally +## Development environment setup -In order to run tests locally, there are a few things that have to be setup: +If you want to contribute to this project, you need to set up your local environment. -### Add credentials for testing +### Clone the repository -To simulate a player connecting ot the server, it needs to authenticate against microsofts servers. This means, that a microsoft account which owns Minecraft is needed. Don't worry, the credentials are only saved locally. +Run the command +```bash +git clone https://github.com/mcserver-tools/mcserverwrapper +cd mcserverwrapper +``` +in your terminal. -Create a new file named *password.txt* in the repository root and add the following content (without the brackets): +### Install pip packages + +To install all optional as well as development python packages, run the following commands in the project root. + +Windows: +```bash +py -m venv venv +venv\Scripts\activate.bat +pip install -r requirements.txt ``` -(username-here) -(password-here) + +Linux: +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt ``` ### Node Node 20 is needed to use MineFlayer. +The install instructions can be found below: #### Windows @@ -98,9 +116,41 @@ To actually run a minecraft server, a Java 21 JRE needs to be installed and adde Download it from (here)[https://adoptium.net/temurin/releases/]. +### Add credentials for testing + +To simulate a player connecting to the server, it needs to authenticate against microsofts servers. This means, that a microsoft account which owns Minecraft is needed. Don't worry, the credentials are only saved locally. + +Create a new file named *password.txt* in the repository root and add the following content (without the brackets): +``` +(username-here) +(password-here) +``` + ### Run tests After installing all of the requirements, the tests can be ran using ```bash -python -m pytest +python -m pytest src -rs --skip-linting --skip-test-all +``` + +If you want to run tests for all Minecraft versions, remove the `--skip-test-all` argument from the command above. + +> **Warning** +> Beware that testing all versions can take multiple hours! + +### Git pre-commit hooks + +Pre-commit hooks are used to check and autofix formatting issues and typos before you commit your changes. +Once installed, they run automatically if you run `git commit ...`. + +Using these is optional, but encouraged. + +```bash +pip install pre-commit +pre-commit install +``` + +To verify the installation and run all checks: +```bash +pre-commit run --all-files ``` diff --git a/examples/example_01.py b/examples/example_01.py index 24ac1aa..002512b 100644 --- a/examples/example_01.py +++ b/examples/example_01.py @@ -4,6 +4,7 @@ import pathlib import requests + from mcserverwrapper import Wrapper def download_server_jar(): diff --git a/pyproject.toml b/pyproject.toml index 8a930d0..9832a2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ dev = [ "bs4==0.0.2", "javascript==1!1.2.6", + "pre-commit==4.3.0", "pylint==3.3.9", "pytest==8.4.2", "requests==2.32.5", diff --git a/requirements.txt b/requirements.txt index b16a56b..2df400b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ bs4==0.0.2 javascript==1!1.2.6 mcstatus==12.0.5 pexpect==4.9.0 +pre-commit==4.3.0 pylint==3.3.9 pytest==8.4.2 requests==2.32.5 diff --git a/src/mcserverwrapper/error.py b/src/mcserverwrapper/error.py index 48af946..401302c 100644 --- a/src/mcserverwrapper/error.py +++ b/src/mcserverwrapper/error.py @@ -4,4 +4,4 @@ class McServerWrapperError(Exception): """The base exception, can be used to catch all other McServerWrapper-related errors""" class ServerExitedError(McServerWrapperError): - """An error occuring if the minecraft server unexpectedly crashed""" + """An error occurring if the minecraft server unexpectedly crashed""" diff --git a/src/mcserverwrapper/server/base_server.py b/src/mcserverwrapper/server/base_server.py index 1fc18e3..8d6bd79 100644 --- a/src/mcserverwrapper/server/base_server.py +++ b/src/mcserverwrapper/server/base_server.py @@ -1,4 +1,4 @@ -"""A module contining the base server class""" +"""A module containing the base server class""" from __future__ import annotations diff --git a/src/mcserverwrapper/server/server_builder.py b/src/mcserverwrapper/server/server_builder.py index 6531d79..7aecc17 100644 --- a/src/mcserverwrapper/server/server_builder.py +++ b/src/mcserverwrapper/server/server_builder.py @@ -33,7 +33,7 @@ def from_jar(cls, jar_file: str) -> ServerBuilder: Args: jar_file (str): the full or relative path to the jar file to be used to start the server - + Returns: ServerBuilder: a new ServerBuilder instance """ diff --git a/src/mcserverwrapper/util/info_getter.py b/src/mcserverwrapper/util/info_getter.py index ab0f16d..57b09be 100644 --- a/src/mcserverwrapper/util/info_getter.py +++ b/src/mcserverwrapper/util/info_getter.py @@ -2,21 +2,8 @@ from __future__ import annotations -import sys - from mcstatus import JavaServer - -# version-specific code doesn't work well with pylnt -# https://github.com/pylint-dev/pylint/issues/7240 -# pylint: disable=import-error, no-name-in-module - -# python versions above 3.9 -if sys.version_info.minor > 9: - from mcstatus.responses import JavaStatusResponse -else: - from mcstatus.status_response import JavaStatusResponse - -# pylint: enable=import-error, no-name-in-module +from mcstatus.responses import JavaStatusResponse def ping_address_with_return(address, port, timeout=3) -> JavaStatusResponse | None: """Pings a given address/port combination and returns the result or None""" @@ -30,7 +17,7 @@ def ping_address_with_return(address, port, timeout=3) -> JavaStatusResponse | N return status except (TimeoutError, ConnectionAbortedError, ConnectionResetError, IOError): return None - # a bug with the library pinging the minecraft server + # a bug within mcstatus, we just retry except KeyError as keyerr: if "text" in keyerr.args or "#" in keyerr.args: print("Retrying...") diff --git a/src/test/conftest.py b/src/test/conftest.py index eb1abf3..f8cadce 100644 --- a/src/test/conftest.py +++ b/src/test/conftest.py @@ -12,11 +12,11 @@ sys.path.append(f"{pathlib.Path(__file__).parent}") # pylint: enable=wrong-import-position -import pytest - from test.helpers.common_helper import get_mcserver_log, get_vanilla_urls from test.integration_tests import test_forge +import pytest + os.environ["DEBUG"] = "True" if not os.path.isdir("temp"): @@ -38,7 +38,7 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item item.add_marker(skip_pylint) if config.getoption("--skip-test-all"): - skip_pylint = pytest.mark.skip(reason="skipping testing all minecraft versions due to mising --test-all arg") + skip_pylint = pytest.mark.skip(reason="skipping testing all minecraft versions due to --skip-test-all arg") for item in items: if item.name.startswith("test_all[") or item.name.startswith("test_multiple["): item.add_marker(skip_pylint) diff --git a/src/test/fixtures.py b/src/test/fixtures.py index 4cbb843..fec17b8 100644 --- a/src/test/fixtures.py +++ b/src/test/fixtures.py @@ -1,13 +1,12 @@ """Defines pytest fixtures""" -import shutil import os +import shutil +from test.helpers import common_helper import pytest import requests -from test.helpers import common_helper - @pytest.fixture def newest_server_jar(): """Download the newest server jar version and return the jar file path""" diff --git a/src/test/helpers/common_helper.py b/src/test/helpers/common_helper.py index 9e4c0d8..f0ec624 100644 --- a/src/test/helpers/common_helper.py +++ b/src/test/helpers/common_helper.py @@ -77,14 +77,14 @@ def connect_mineflayer(address = "127.0.0.1", port = 25565, offline_mode=False): 'auth': 'microsoft', 'username': password[0], 'password': password[1], - 'hideErrors': False + 'hideErrors': False }) else: bot = mineflayer.createBot({ 'host': address, 'port': port, 'username': "Developer", - 'hideErrors': False + 'hideErrors': False }) bot_connected = [False] @@ -108,7 +108,7 @@ def func(bot, bot_connected): def download_file(url, counter=""): """ Download the file from the given url and return its path - + Retry code from https://stackoverflow.com/a/35504626/15436169 """ diff --git a/src/test/helpers/forge_helper.py b/src/test/helpers/forge_helper.py index 5eafdd9..2563bdd 100644 --- a/src/test/helpers/forge_helper.py +++ b/src/test/helpers/forge_helper.py @@ -4,13 +4,13 @@ import re import subprocess from random import randint +from test.helpers.common_helper import (assert_port_is_free, download_file, + setup_workspace) import pytest from mcserverwrapper import Wrapper from mcserverwrapper.error import ServerExitedError -from test.helpers.common_helper import (assert_port_is_free, download_file, - setup_workspace) def install_forge(url: str): """Install a forge server from a given installer download url""" @@ -73,7 +73,7 @@ def run_forge_test(jarfile, offline_mode=False): try: wrapper.startup() except ServerExitedError: - # server exited because of invalid java verison + # server exited because of invalid java version pytest.skip("Server exited likely due to wrong java version") try: diff --git a/src/test/helpers/vanilla_helper.py b/src/test/helpers/vanilla_helper.py index 5d51a92..e852216 100644 --- a/src/test/helpers/vanilla_helper.py +++ b/src/test/helpers/vanilla_helper.py @@ -2,12 +2,12 @@ import os from random import randint - -from mcserverwrapper import Wrapper from test.helpers.common_helper import (assert_port_is_free, connect_mineflayer, download_file, setup_workspace) +from mcserverwrapper import Wrapper + def run_vanilla_test_url(url, offline_mode=False): """Run all tests for a single vanilla minecraft server url""" diff --git a/src/test/integration_tests/test_vanilla.py b/src/test/integration_tests/test_vanilla.py index 6e4bc9e..f69fef8 100644 --- a/src/test/integration_tests/test_vanilla.py +++ b/src/test/integration_tests/test_vanilla.py @@ -3,16 +3,16 @@ import os from datetime import datetime, timedelta from random import randint -from time import sleep - -import pytest - -from mcserverwrapper import Wrapper, error from test.helpers.common_helper import (assert_port_is_free, connect_mineflayer, download_file, setup_workspace) from test.helpers.vanilla_helper import run_vanilla_test, run_vanilla_test_url from test.testable_thread import TestableThread +from time import sleep + +import pytest + +from mcserverwrapper import Wrapper, error def test_all(jar_version_tuple): """Tests all of the vanilla minecraft versions""" From a2ba6c9e7b3a164e0afd436c4bc1c4c26d857e91 Mon Sep 17 00:00:00 2001 From: Ableytner Date: Wed, 8 Oct 2025 22:57:20 +0200 Subject: [PATCH 15/16] skip test if serrver jar download times out --- src/test/helpers/common_helper.py | 2 +- src/test/integration_tests/test_vanilla.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/helpers/common_helper.py b/src/test/helpers/common_helper.py index f0ec624..1f20896 100644 --- a/src/test/helpers/common_helper.py +++ b/src/test/helpers/common_helper.py @@ -97,7 +97,7 @@ def func(bot, bot_connected): start_time = datetime.now() while not bot_connected[0]: - if (datetime.now() - start_time) > timedelta(seconds=30): + if (datetime.now() - start_time) > timedelta(seconds=60): raise Exception(f"Bot connection to {address}:{port} timed out") sleep(0.1) diff --git a/src/test/integration_tests/test_vanilla.py b/src/test/integration_tests/test_vanilla.py index f69fef8..4791c19 100644 --- a/src/test/integration_tests/test_vanilla.py +++ b/src/test/integration_tests/test_vanilla.py @@ -11,6 +11,7 @@ from time import sleep import pytest +from urllib3.exceptions import ReadTimeoutError from mcserverwrapper import Wrapper, error @@ -32,6 +33,8 @@ def test_all(jar_version_tuple): raise TimeoutError("Test timed out") thread.join() + except ReadTimeoutError: + pytest.skip(f"Testing version {name} skipped: server jar download timed out") except TimeoutError as timeout_err: if "Test timed out" in timeout_err.args: pytest.fail(f"Testing version {name} timed out") From e4ee73a7db6c32d3e9e5fe2ffd87faac7a5c75bb Mon Sep 17 00:00:00 2001 From: Ableytner <56540036+Ableytner@users.noreply.github.com> Date: Thu, 9 Oct 2025 02:06:13 +0200 Subject: [PATCH 16/16] Bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9832a2b..5c7c93a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "mcserverwrapper" -version = "0.0.3rc1" +version = "0.0.4rc1" authors = [ { name="Ableytner", email="ableytner@gmx.at" }, ]