Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 72 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 25 additions & 9 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,49 @@
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
rust-overlay.url = "github:oxalica/rust-overlay";
korangar-rathena.url = "github:vE5li/korangar-rathena";
};

outputs = {
self,
flake-utils,
rust-overlay,
nixpkgs,
korangar-rathena,
}:
flake-utils.lib.eachDefaultSystem (system: let
overlays = [(import rust-overlay)];
overlays = [(import rust-overlay) korangar-rathena.overlays.default];
pkgs = (import nixpkgs) {inherit system overlays;};

test-log-file = "/tmp/korangar-packet-test.log";
run-smoke-test = pkgs.writeShellScriptBin "run-smoke-test" ''
${pkgs.lib.getExe pkgs.rathena-test-20220406} > ${test-log-file} 2>&1 &
RATHENA_PID=$!

cargo build --bin smoke

while ! grep -q "test rAthena is running" ${test-log-file} 2> /dev/null; do
echo "Waiting for rAthena..."
sleep 0.5
done

echo "rAthena is ready"
sleep 5

cargo run --bin smoke

kill $RATHENA_PID
'';
in {
formatter = pkgs.alejandra;

# Provide a shell with all dependencies needed to build `koragnar::*`
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
(rust-bin.fromRustupToolchainFile ./rust-toolchain.toml)
pkg-config
];
buildInputs = with pkgs;
[libpcap nasm nixpkgs-fmt openssl shaderc shader-slang vulkan-headers vulkan-loader]
[libpcap nasm nixpkgs-fmt openssl shaderc shader-slang vulkan-headers vulkan-loader run-smoke-test]
++ lib.optional stdenv.isDarwin [
apple-sdk
moltenvk
Expand All @@ -37,6 +58,7 @@

# For any tools that need to see the rust toolchain src
RUST_SRC_PATH = pkgs.rustPlatform.rustLibSrc;

shellHook =
""
# For darwin, explicitly set our LD_LIBRARY_PATH, otherwise `cargo run` will not find paths in e.g. `/nix/store`
Expand All @@ -59,11 +81,5 @@
};
'');
};
# If we want to `nix build` and provide a derivation, we can use
# `naersk`, e.g.
# defaultPackage = naersk'.buildPackage {
# src = ./.;
# };
# app.default = { ... }
});
}
12 changes: 12 additions & 0 deletions korangar-testing/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "korangar-testing"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
korangar = { path = "../korangar" }
korangar-networking = { workspace = true }
ragnarok-packets = { workspace = true }
rust-state = { workspace = true }
winit = { workspace = true }
14 changes: 14 additions & 0 deletions korangar-testing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Korangar Testing

A harness for driving the Korangar client through scripted scenarios against a live server.
This crate exposes a `TestManager` that implements `ClientHooks` and advances through a sequence of `WorkStep`s, injecting input events and asserting on network events and client state.

## Tests

### Smoke test

To run this example you need a local rAthena server reachable on `127.0.0.1` (login on port `6900`, character on port `6121`). You can run it with

```fish
cargo run -p korangar-testing --bin smoke
```
126 changes: 126 additions & 0 deletions korangar-testing/src/bin/smoke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use korangar::Client;
use korangar::input::InputEvent;
use korangar::loaders::{ClientInfoPathExt, Service};
use korangar::state::{ClientStatePathExt, client_state};
use korangar_networking::NetworkEvent;
use korangar_testing::{TestManager, inject_input, modify_state, wait_for_network_event_or_failure_with};
use ragnarok_packets::{CharacterId, CharacterServerInformation, ServerAddress};
use winit::event_loop::{ControlFlow, EventLoop};

fn main() {
let service = Service {
display_name: Default::default(),
description: Default::default(),
balloon: Default::default(),
address: "127.0.0.1".to_owned(),
port: 6900,
// TODO: Might need to be adjusted to connect.
version: Default::default(),
language_type: Default::default(),
registration_web: Default::default(),
game_master_yellow_ids: Default::default(),
game_master_accounts: Default::default(),
loading_images: Default::default(),
packet_version: Default::default(),
};
let service_id = service.service_id();
let username = "testing_m".to_owned();
// let username = "testing".to_owned();
let password = "password".to_owned();

let character_server_information = CharacterServerInformation {
server_ip: ServerAddress([127, 0, 0, 1]),
server_port: 6121,
server_name: "rAthena".to_owned(),
user_count: Default::default(),
server_type: Default::default(),
display_new: Default::default(),
unknown: [0; 128],
};

let test_manager = TestManager::new(vec![
// Log in to login server.
modify_state(client_state().client_info().services(), vec![service]),
inject_input(InputEvent::LogIn {
service_id,
username,
password,
}),
wait_for_network_event_or_failure_with(
|network_event| matches!(network_event, NetworkEvent::LoginServerConnected { .. }),
|network_event| {
matches!(
network_event,
// `LoginServerDisconnected` means that the test server might not be running.
NetworkEvent::LoginServerDisconnected { .. } | NetworkEvent::LoginServerConnectionFailed { .. }
)
},
),
// Log in to character server.
inject_input(InputEvent::SelectServer {
character_server_information,
}),
wait_for_network_event_or_failure_with(
|network_event| matches!(network_event, NetworkEvent::CharacterServerConnected { .. }),
|network_event| {
matches!(
network_event,
// `CharacterServerDisconnected` means that the test server might not be running.
NetworkEvent::CharacterServerConnectionFailed { .. } | NetworkEvent::CharacterServerDisconnected { .. }
)
},
),
// Create test characters.
inject_input(InputEvent::CreateCharacter {
slot: 0,
name: "testing1".to_owned(),
}),
wait_for_network_event_or_failure_with(
|network_event| matches!(network_event, NetworkEvent::CharacterCreated { .. }),
|network_event| matches!(network_event, NetworkEvent::CharacterCreationFailed { .. }),
),
inject_input(InputEvent::CreateCharacter {
slot: 1,
name: "testing2".to_owned(),
}),
wait_for_network_event_or_failure_with(
|network_event| matches!(network_event, NetworkEvent::CharacterCreated { .. }),
|network_event| matches!(network_event, NetworkEvent::CharacterCreationFailed { .. }),
),
// Swap character slots.
inject_input(InputEvent::SwitchCharacterSlot {
origin_slot: 0,
destination_slot: 1,
}),
wait_for_network_event_or_failure_with(
|network_event| matches!(network_event, NetworkEvent::CharacterSlotSwitched { .. }),
|network_event| matches!(network_event, NetworkEvent::CharacterSlotSwitchFailed),
),
// Delete character.
inject_input(InputEvent::DeleteCharacter {
character_id: CharacterId(150001),
}),
wait_for_network_event_or_failure_with(
|network_event| matches!(network_event, NetworkEvent::CharacterDeleted),
|network_event| matches!(network_event, NetworkEvent::CharacterDeletionFailed { .. }),
),
// Connect to map server.
inject_input(InputEvent::SelectCharacter { slot: 1 }),
wait_for_network_event_or_failure_with(
|network_event| matches!(network_event, NetworkEvent::CharacterSelected { .. }),
|network_event| {
matches!(
network_event,
// `MapServerDisconnected` means that the test server might not be running.
NetworkEvent::CharacterSelectionFailed { .. } | NetworkEvent::MapServerDisconnected { .. }
)
},
),
]);

let mut client = Client::init(false, test_manager).unwrap();

let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
let _ = event_loop.run_app(&mut client);
}
Loading
Loading