9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-19 14:59:21 +00:00

test: improve test suite

use velocity instead of Waterfall
add some basic docs
include the default test config
This commit is contained in:
William278
2025-03-07 14:26:10 +00:00
parent b108d38598
commit fbb8ec3048
4 changed files with 283 additions and 36 deletions

1
.gitignore vendored
View File

@@ -122,4 +122,3 @@ run/
# Don't include generated test suite files # Don't include generated test suite files
/test/servers/ /test/servers/
/test/HuskSync /test/HuskSync
/test/config.yml

29
test/README.md Normal file
View File

@@ -0,0 +1,29 @@
# Testing HuskSync
This is a rudimentary Python script for running a little Proxy network of servers for quickly testing HuskSync.
Run the script to spin up a Velocity proxy and a pair of Paper servers for testing HuskSync.
* Useful for development & feature testing
* Not useful for stress or integration testing.
* Only works on Windows (as it deals with bash scripts)
* Only spins up Paper servers at the moment ()
If you don't want to do this you can also run a single-server test with the various `runServer` tasks in the Bukkit/Fabric modules.
_PRs to improve testing are welcomed with open arms & cups of tea!_
## Requirements
* Windows
* Python 3.14
* MySQL DB running locally
* Redis running locally
## How to run
1. Edit `spin_network.py` to your liking (change the MC version, add your name/UUID as a server operator)
2. Configure HuskSync to point to your local MySQL/Redis DB (edit `~/test/config.yml`)
3. Run `pip install -r requirements.txt
4. From the repository route, open terminal and run `cd ./test`, then `python3 ./spin_network.py`
## Tips
* Delete `~/test/servers` and `~/test/HuskSync` each time you want to download Paper/Velocity & re-create worlds, etc.
* Create an IntelliJ Run & Debug Python task to do this with a `Run Gradle Task before` to `clean build` the project before.

184
test/config.yml Normal file
View File

@@ -0,0 +1,184 @@
# Locale of the default language file to use.
# Docs: https://william278.net/docs/husksync/translations
language: en-gb
# Whether to automatically check for plugin updates on startup
check_for_updates: false
# Specify a common ID for grouping servers running HuskSync. Don't modify this unless you know what you're doing!
cluster_id: ''
# Enable development debug logging
debug_logging: false
# Whether to enable the Player Analytics hook.
# Docs: https://william278.net/docs/husksync/plan-hook
enable_plan_hook: true
# Whether to cancel game event packets directly when handling locked players if ProtocolLib or PacketEvents is installed
cancel_packets: true
# Add HuskSync commands to this list to prevent them from being registered (e.g. ['userdata'])
disabled_commands: []
# Database settings
database:
# Type of database to use (MYSQL, MARIADB, POSTGRES, MONGO)
type: MARIADB
# Specify credentials here for your MYSQL, MARIADB, POSTGRES OR MONGO database
credentials:
host: localhost
port: 3306
database: plugin_testing
username: root
password: ''
# Only change this if you're using MARIADB or POSTGRES
parameters: ?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8
# MYSQL, MARIADB, POSTGRES database Hikari connection pool properties. Don't modify this unless you know what you're doing!
connection_pool:
maximum_pool_size: 10
minimum_idle: 10
maximum_lifetime: 1800000
keepalive_time: 0
connection_timeout: 5000
# Advanced MongoDB settings. Don't modify unless you know what you're doing!
mongo_settings:
using_atlas: false
parameters: ?retryWrites=true&w=majority&authSource=HuskSync
# Names of tables to use on your database. Don't modify this unless you know what you're doing!
table_names:
users: husksync_users
user_data: husksync_user_data
# Whether to run the creation SQL on the database when the server starts. Don't modify this unless you know what you're doing!
create_tables: true
# Redis settings
redis:
# Specify the credentials of your Redis server here. Set "password" to '' if you don't have one
credentials:
host: localhost
port: 6379
password: ''
use_ssl: false
# Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!
sentinel:
# The master set name for the Redis sentinel.
master: ''
# List of host:port pairs
nodes: []
password: ''
# Data syncing settings
synchronization:
# The data synchronization mode to use (LOCKSTEP or DELAY). LOCKSTEP is recommended for most networks.
# Docs: https://william278.net/docs/husksync/sync-modes
mode: LOCKSTEP
# The number of data snapshot backups that should be kept at once per user
max_user_data_snapshots: 16
# Number of hours between new snapshots being saved as backups (Use "0" to backup all snapshots)
snapshot_backup_frequency: 4
# List of save cause IDs for which a snapshot will be automatically pinned (so it won't be rotated).
# Docs: https://william278.net/docs/husksync/data-rotation#save-causes
auto_pinned_save_causes:
- INVENTORY_COMMAND
- ENDERCHEST_COMMAND
- BACKUP_RESTORE
- LEGACY_MIGRATION
- MPDB_MIGRATION
# Whether to create a snapshot for users on a world when the server saves that world
save_on_world_save: true
# Configuration for how and when to sync player data when they die
save_on_death:
# Whether to create a snapshot for users when they die (containing their death drops)
enabled: false
# What items to save in death snapshots? (DROPS or ITEMS_TO_KEEP). Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server.
items_to_save: DROPS
# Should a death snapshot still be created even if the items to save on the player's death are empty?
save_empty_items: true
# Whether dead players who log out and log in to a different server should have their items saved.
sync_dead_players_changing_server: true
# Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing
compress_data: true
# Where to display sync notifications (ACTION_BAR, CHAT or NONE)
notification_display_slot: ACTION_BAR
# Persist maps locked in a Cartography Table to let them be viewed on any server
persist_locked_maps: true
# If using the DELAY sync method, how long should this server listen for Redis key data updates before pulling data from the database instead (i.e., if the user did not change servers).
network_latency_milliseconds: 500
# Which data types to synchronize.
# Docs: https://william278.net/docs/husksync/sync-features
features:
experience: true
potion_effects: true
flight_status: true
ender_chest: true
location: false
attributes: true
advancements: true
game_mode: true
persistent_data: true
inventory: true
hunger: true
statistics: true
health: true
# Commands which should be blocked before a player has finished syncing (Use * to block all commands)
blacklisted_commands_while_locked:
- '*'
# Configuration for how to sync attributes
attributes:
# Which attribute types should be saved as part of attribute syncing. Supports wildcard matching.
# (e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])
synced_attributes:
- minecraft:generic.max_health
- minecraft:max_health
- minecraft:generic.max_absorption
- minecraft:max_absorption
- minecraft:generic.luck
- minecraft:luck
- minecraft:generic.scale
- minecraft:scale
- minecraft:generic.step_height
- minecraft:step_height
- minecraft:generic.gravity
- minecraft:gravity
# Which attribute modifiers should be saved. Supports wildcard matching.
# (e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])
ignored_modifiers:
- minecraft:effect.*
- minecraft:creative_mode_*
# Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts
event_priorities:
quit_listener: LOWEST
join_listener: LOWEST
death_listener: NORMAL

View File

@@ -12,7 +12,7 @@ from tqdm import tqdm
# Parameters for starting a network of Minecraft servers # Parameters for starting a network of Minecraft servers
class Parameters: class Parameters:
root_dir = './servers/' root_dir = './servers/'
proxy_version = "1.21" proxy_version = "3.4.0-SNAPSHOT"
minecraft_version = '1.21.4' minecraft_version = '1.21.4'
eula_agreement = 'true' eula_agreement = 'true'
@@ -20,7 +20,7 @@ class Parameters:
backend_ports = [25567, 25568] backend_ports = [25567, 25568]
backend_type = 'paper' backend_type = 'paper'
backend_ram = 2048 backend_ram = 2048
backend_plugins = ['../target/HuskSync-Paper-*.jar'] backend_plugins = ['../target/HuskSync-Bukkit-*.jar']
backend_plugin_folders = ['./HuskSync'] backend_plugin_folders = ['./HuskSync']
operator_names = ['William278'] operator_names = ['William278']
operator_uuids = ['5dfb0558-e306-44f4-bb9a-f9218d4eb787'] operator_uuids = ['5dfb0558-e306-44f4-bb9a-f9218d4eb787']
@@ -28,11 +28,13 @@ class Parameters:
proxy_name = "proxy" proxy_name = "proxy"
proxy_host = "0.0.0.0" proxy_host = "0.0.0.0"
proxy_port = 25565 proxy_port = 25565
proxy_type = "waterfall" proxy_type = "velocity"
proxy_ram = 512 proxy_ram = 512
proxy_plugins = [] proxy_plugins = []
proxy_plugin_folders = [] proxy_plugin_folders = []
velocity_secret = "qUTwFSVeQqhH" # Doesn't matter that this is committed or anything, it's just for testing
just_update_plugins = False just_update_plugins = False
@@ -59,7 +61,7 @@ def main(update=False):
os.makedirs(plugin_dir) os.makedirs(plugin_dir)
# Copy plugins in # Copy plugins in
copy_plugins(parameters.backend_plugins, parameters.backend_plugin_folders, plugin_dir) copy_plugins(parameters.backend_plugins, parameters.backend_plugin_folders, plugin_dir, parameters)
# Start servers # Start servers
start_servers(parameters) start_servers(parameters)
@@ -114,14 +116,16 @@ def create_backend_server(name, port, parameters):
with open(server_dir + "/spigot.yml", "w") as file: with open(server_dir + "/spigot.yml", "w") as file:
file.write(f"# Auto-generated spigot.yml for server {name}\n") file.write(f"# Auto-generated spigot.yml for server {name}\n")
file.write(f"settings:\n") file.write(f"settings:\n")
file.write(f" bungeecord: true\n") file.write(f" bungeecord: false\n")
# Create the paper-global.yml and enable BungeeCord # Create the paper-global.yml and enable BungeeCord
with open(server_dir + "/config/paper-global.yml", "w") as file: with open(server_dir + "/config/paper-global.yml", "w") as file:
file.write(f"# Auto-generated paper-global.yml for server {name}\n") file.write(f"# Auto-generated paper-global.yml for server {name}\n")
file.write(f"proxies:\n") file.write(f"proxies:\n")
file.write(f" bungee-cord:\n") file.write(f" velocity:\n")
file.write(f" enabled: true\n")
file.write(f" online-mode: true\n") file.write(f" online-mode: true\n")
file.write(f" secret: {parameters.velocity_secret}\n")
# Create the server.properties file # Create the server.properties file
server_properties = server_dir + "/server.properties" server_properties = server_dir + "/server.properties"
@@ -153,7 +157,7 @@ def create_backend_server(name, port, parameters):
file.write("]") file.write("]")
# Copy plugins # Copy plugins
copy_plugins(parameters.backend_plugins, parameters.backend_plugin_folders, f"{server_dir}/plugins") copy_plugins(parameters.backend_plugins, parameters.backend_plugin_folders, f"{server_dir}/plugins", parameters)
# Create start scripts # Create start scripts
create_start_scripts(server_dir, create_start_scripts(server_dir,
@@ -169,44 +173,74 @@ def create_proxy_server(parameters):
if not os.path.exists(server_dir): if not os.path.exists(server_dir):
os.makedirs(server_dir) os.makedirs(server_dir)
if parameters.proxy_type == "waterfall": if parameters.proxy_type == "velocity":
# Create necessary subdirectories # Create necessary subdirectories
create_subdirectories(["plugins"], server_dir) create_subdirectories(["plugins"], server_dir)
# Download the latest paper for the version and place it in the server folder # Download the latest paper for the version and place it in the server folder
proxy_jar = "waterfall.jar" proxy_jar = "velocity.jar"
download_paper_build("waterfall", parameters.proxy_version, download_paper_build("velocity", parameters.proxy_version,
get_latest_paper_build_number("waterfall", parameters.proxy_version), get_latest_paper_build_number("velocity", parameters.proxy_version),
f"{server_dir}/{proxy_jar}") f"{server_dir}/{proxy_jar}")
# Create the config.yml # Create the forwarding.secret
with open(server_dir + "/config.yml", "w") as file: with open(server_dir + "/forwarding.secret", "w") as file:
file.write(f"# Auto-generated config.yml for proxy server {parameters.proxy_name}\n") file.write(f"{parameters.velocity_secret}\n")
# Create the velocity.toml
with open(server_dir + "/velocity.toml", "w") as file:
file.write(f"# Auto-generated velocity.toml for proxy server {parameters.proxy_name}\n")
# Write proxy settings # Write proxy settings
file.write(f"listeners:\n") file.write(f"config-version = '2.6'\n")
file.write(f"- query_port: {parameters.proxy_port}\n") file.write(f"bind = '0.0.0.0:{parameters.proxy_port}'\n")
file.write(f" motd: '{parameters.proxy_version} Proxy Server'\n") file.write(f"motd = \"Velocity Proxy Server\"\n")
file.write(f" query_enabled: false\n") file.write(f"show-max-players = 10\n")
file.write(f" proxy_protocol: false\n") file.write(f"force-key-authentication = true\n")
file.write(f" priorities:\n") file.write(f"player-info-forwarding-mode = 'modern'\n")
file.write(f" - {parameters.backend_names[0]}\n") file.write(f"prevent-client-proxy-connections = false\n")
file.write(f" bind_local_address: true\n") file.write(f"announce-forge = false\n")
file.write(f" host: {parameters.proxy_host}:{parameters.proxy_port}\n") file.write(f"kick-existing-players = false\n")
file.write(f"ip_forward: true\n") file.write(f"enable-player-address-logging = true\n")
file.write(f"online_mode: true\n") file.write(f"ping-passthrough = 'DISABLED'\n")
file.write(f"online-mode = true\n")
# Write servers # Write servers
file.write(f"servers:\n") file.write(f"\n\n[servers]\n\n")
for i in range(len(parameters.backend_names)): for i in range(len(parameters.backend_names)):
file.write(f" {parameters.backend_names[i]}:\n") file.write(f"{parameters.backend_names[i]} = \"127.0.0.1:{parameters.backend_ports[i]}\"\n")
file.write(
f" motd: '&eBackend {parameters.backend_type} {parameters.backend_names[i]} (port {parameters.backend_ports[i]})'\n") file.write(f"\n\ntry = [\n")
file.write(f" address: localhost:{parameters.backend_ports[i]}\n") for i in range(len(parameters.backend_names)):
file.write(f" restricted: false\n") file.write(f" '{parameters.backend_names[i]}',\n")
file.write(f"]\n")
file.write(f"\n\n[advanced]\n\n")
file.write(f"compression-threshold = 256\n")
file.write(f"compression-level = -1\n")
file.write(f"login-ratelimit = 0\n")
file.write(f"connection-timeout = 5000\n")
file.write(f"read-timeout = 30000\n")
file.write(f"haproxy-protocol = false\n")
file.write(f"tcp-fast-open = false\n")
file.write(f"bungee-plugin-message-channel = true\n")
file.write(f"show-ping-requests = true\n")
file.write(f"announce-proxy-commands = true\n")
file.write(f"failover-on-unexpected-server-disconnect = true\n")
file.write(f"log-command-executions = true\n")
file.write(f"log-player-connections = true\n")
file.write(f"accepts-transfers = false\n")
file.write(f"\n\n[forced-hosts]\n\n")
file.write(f"\n\n[query]\n\n")
file.write(f"enabled = false\n")
file.write(f"port = {parameters.proxy_port}\n")
file.write(f"show-plugins = false\n")
file.write(f"map = 'Test'\n")
# Copy plugins # Copy plugins
copy_plugins(parameters.proxy_plugins, parameters.proxy_plugin_folders, f"{server_dir}/plugins") copy_plugins(parameters.proxy_plugins, parameters.proxy_plugin_folders, f"{server_dir}/plugins", parameters)
# Create startup scripts # Create startup scripts
create_start_scripts(server_dir, create_start_scripts(server_dir,
@@ -252,7 +286,7 @@ def create_start_scripts(server_directory, start_arguments):
# Copies plugins and plugin folders from the source to the target # Copies plugins and plugin folders from the source to the target
def copy_plugins(plugins, plugin_folders, plugins_folder): def copy_plugins(plugins, plugin_folders, plugins_folder, parameters):
# Copy each file from the plugin list to the server/plugins folder # Copy each file from the plugin list to the server/plugins folder
for plugin in plugins: for plugin in plugins:
# Skip if the plugin does not exist # Skip if the plugin does not exist
@@ -265,7 +299,8 @@ def copy_plugins(plugins, plugin_folders, plugins_folder):
if os.path.exists(parent_directory): if os.path.exists(parent_directory):
hit = False hit = False
for file in os.listdir(parent_directory): for file in os.listdir(parent_directory):
if file.startswith(plugin_name): if (file.startswith(plugin_name) and file.endswith('mc.' + parameters.minecraft_version + '.jar')
and not file.endswith('-javadoc.jar') and not file.endswith('-sources.jar')):
shutil.copy(parent_directory + "/" + file, plugins_folder + "/" + file) shutil.copy(parent_directory + "/" + file, plugins_folder + "/" + file)
print(f"Copied plugin {file} to {plugins_folder}") print(f"Copied plugin {file} to {plugins_folder}")
hit = True hit = True