tests: add login smoke json and failure modes
This commit is contained in:
@@ -218,12 +218,141 @@ struct PacketGCChannel
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct SmokeOptions
|
||||
{
|
||||
bool json = false;
|
||||
std::string create_character_name;
|
||||
std::string client_version = "1215955205";
|
||||
std::string expect_auth_failure;
|
||||
std::string expect_channel_failure;
|
||||
};
|
||||
|
||||
struct SmokeResult
|
||||
{
|
||||
bool ok = false;
|
||||
std::string result = "unexpected_failure";
|
||||
std::string stage = "startup";
|
||||
std::string error;
|
||||
std::string failure_status;
|
||||
uint32_t login_key = 0;
|
||||
int empire = -1;
|
||||
int character_index = -1;
|
||||
std::string character_name;
|
||||
bool character_created = false;
|
||||
int game_channel = -1;
|
||||
int64_t auth_handshake_ms = -1;
|
||||
int64_t auth_login_ms = -1;
|
||||
int64_t channel_handshake_ms = -1;
|
||||
int64_t channel_login_ms = -1;
|
||||
int64_t character_create_ms = -1;
|
||||
int64_t character_select_ms = -1;
|
||||
int64_t entergame_ms = -1;
|
||||
std::vector<std::string> events;
|
||||
};
|
||||
|
||||
struct AuthResponse
|
||||
{
|
||||
bool success = false;
|
||||
uint32_t login_key = 0;
|
||||
std::string failure_status;
|
||||
};
|
||||
|
||||
struct ChannelLoginResponse
|
||||
{
|
||||
bool success = false;
|
||||
int empire = -1;
|
||||
std::string failure_status;
|
||||
PacketGCLoginSuccess4 login_success {};
|
||||
};
|
||||
|
||||
void Expect(bool condition, std::string_view message)
|
||||
{
|
||||
if (!condition)
|
||||
throw std::runtime_error(std::string(message));
|
||||
}
|
||||
|
||||
std::string JsonEscape(std::string_view input)
|
||||
{
|
||||
std::string escaped;
|
||||
escaped.reserve(input.size() + 8);
|
||||
|
||||
for (const char c : input)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\\':
|
||||
escaped += "\\\\";
|
||||
break;
|
||||
case '"':
|
||||
escaped += "\\\"";
|
||||
break;
|
||||
case '\n':
|
||||
escaped += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
escaped += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
escaped += "\\t";
|
||||
break;
|
||||
default:
|
||||
escaped.push_back(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
void EmitEvent(SmokeResult& result, bool json, const std::string& line)
|
||||
{
|
||||
result.events.push_back(line);
|
||||
if (!json)
|
||||
std::cout << line << "\n";
|
||||
}
|
||||
|
||||
int64_t ElapsedMs(std::chrono::steady_clock::time_point started_at)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - started_at)
|
||||
.count();
|
||||
}
|
||||
|
||||
void PrintJson(const SmokeResult& result)
|
||||
{
|
||||
std::cout
|
||||
<< "{"
|
||||
<< "\"ok\":" << (result.ok ? "true" : "false")
|
||||
<< ",\"result\":\"" << JsonEscape(result.result) << "\""
|
||||
<< ",\"stage\":\"" << JsonEscape(result.stage) << "\""
|
||||
<< ",\"error\":\"" << JsonEscape(result.error) << "\""
|
||||
<< ",\"failure_status\":\"" << JsonEscape(result.failure_status) << "\""
|
||||
<< ",\"login_key\":" << result.login_key
|
||||
<< ",\"empire\":" << result.empire
|
||||
<< ",\"character_index\":" << result.character_index
|
||||
<< ",\"character_name\":\"" << JsonEscape(result.character_name) << "\""
|
||||
<< ",\"character_created\":" << (result.character_created ? "true" : "false")
|
||||
<< ",\"game_channel\":" << result.game_channel
|
||||
<< ",\"timings_ms\":{"
|
||||
<< "\"auth_handshake\":" << result.auth_handshake_ms
|
||||
<< ",\"auth_login\":" << result.auth_login_ms
|
||||
<< ",\"channel_handshake\":" << result.channel_handshake_ms
|
||||
<< ",\"channel_login\":" << result.channel_login_ms
|
||||
<< ",\"character_create\":" << result.character_create_ms
|
||||
<< ",\"character_select\":" << result.character_select_ms
|
||||
<< ",\"entergame\":" << result.entergame_ms
|
||||
<< "},\"events\":[";
|
||||
|
||||
for (size_t i = 0; i < result.events.size(); ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
std::cout << ",";
|
||||
std::cout << "\"" << JsonEscape(result.events[i]) << "\"";
|
||||
}
|
||||
|
||||
std::cout << "]}\n";
|
||||
}
|
||||
|
||||
void WriteExact(int fd, const void* data, size_t length, std::string_view context)
|
||||
{
|
||||
const uint8_t* cursor = static_cast<const uint8_t*>(data);
|
||||
@@ -423,11 +552,8 @@ uint8_t FindFirstPlayableCharacterIndex(const PacketGCLoginSuccess4& success)
|
||||
throw std::runtime_error("account has no playable characters");
|
||||
}
|
||||
|
||||
uint32_t Authenticate(const std::string& host, uint16_t auth_port, const std::string& login, const std::string& password)
|
||||
AuthResponse Authenticate(EncryptedClient& auth_client, const std::string& login, const std::string& password, SmokeResult& result, bool json)
|
||||
{
|
||||
EncryptedClient auth_client(host, auth_port);
|
||||
auth_client.Handshake();
|
||||
|
||||
PacketCGLogin3 login_packet {};
|
||||
login_packet.header = CG::LOGIN3;
|
||||
login_packet.length = sizeof(login_packet);
|
||||
@@ -448,7 +574,8 @@ uint32_t Authenticate(const std::string& host, uint16_t auth_port, const std::st
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected login failure size");
|
||||
const auto* failure = reinterpret_cast<const PacketGCLoginFailure*>(frame.data());
|
||||
throw std::runtime_error(std::string("auth login failed: ") + failure->status);
|
||||
EmitEvent(result, json, "auth_failure status=" + std::string(failure->status));
|
||||
return AuthResponse {false, 0, failure->status};
|
||||
}
|
||||
|
||||
if (header == GC::AUTH_SUCCESS)
|
||||
@@ -456,15 +583,15 @@ uint32_t Authenticate(const std::string& host, uint16_t auth_port, const std::st
|
||||
Expect(frame.size() == sizeof(PacketGCAuthSuccess), "unexpected auth success size");
|
||||
const auto* success = reinterpret_cast<const PacketGCAuthSuccess*>(frame.data());
|
||||
Expect(success->result != 0, "auth result returned failure");
|
||||
std::cout << "auth_success login_key=" << success->login_key << "\n";
|
||||
return success->login_key;
|
||||
EmitEvent(result, json, "auth_success login_key=" + std::to_string(success->login_key));
|
||||
return AuthResponse {true, success->login_key, {}};
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("did not receive AUTH_SUCCESS");
|
||||
}
|
||||
|
||||
PacketGCLoginSuccess4 LoginToChannel(EncryptedClient& channel_client, const std::string& login, uint32_t login_key)
|
||||
ChannelLoginResponse LoginToChannel(EncryptedClient& channel_client, const std::string& login, uint32_t login_key, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGLogin2 login_packet {};
|
||||
login_packet.header = CG::LOGIN2;
|
||||
@@ -492,7 +619,8 @@ PacketGCLoginSuccess4 LoginToChannel(EncryptedClient& channel_client, const std:
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected channel login failure size");
|
||||
const auto* failure = reinterpret_cast<const PacketGCLoginFailure*>(frame.data());
|
||||
throw std::runtime_error(std::string("channel login failed: ") + failure->status);
|
||||
EmitEvent(result, json, "channel_failure status=" + std::string(failure->status));
|
||||
return ChannelLoginResponse {false, -1, failure->status, {}};
|
||||
}
|
||||
|
||||
if (header == GC::EMPIRE)
|
||||
@@ -501,7 +629,7 @@ PacketGCLoginSuccess4 LoginToChannel(EncryptedClient& channel_client, const std:
|
||||
const auto* empire_packet = reinterpret_cast<const PacketGCEmpire*>(frame.data());
|
||||
saw_empire = true;
|
||||
empire = empire_packet->empire;
|
||||
std::cout << "channel_empire empire=" << static_cast<int>(empire) << "\n";
|
||||
EmitEvent(result, json, "channel_empire empire=" + std::to_string(static_cast<int>(empire)));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -510,17 +638,17 @@ PacketGCLoginSuccess4 LoginToChannel(EncryptedClient& channel_client, const std:
|
||||
Expect(frame.size() == sizeof(PacketGCLoginSuccess4), "unexpected channel login success size");
|
||||
std::memcpy(&login_success, frame.data(), sizeof(login_success));
|
||||
saw_login_success = true;
|
||||
std::cout << "channel_login_success length=" << length << "\n";
|
||||
EmitEvent(result, json, "channel_login_success length=" + std::to_string(length));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Expect(saw_empire, "did not receive EMPIRE");
|
||||
Expect(saw_login_success, "did not receive LOGIN_SUCCESS4");
|
||||
return login_success;
|
||||
return ChannelLoginResponse {true, empire, {}, login_success};
|
||||
}
|
||||
|
||||
uint8_t CreateCharacter(EncryptedClient& channel_client, std::string_view create_name)
|
||||
uint8_t CreateCharacter(EncryptedClient& channel_client, std::string_view create_name, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGPlayerCreate create_packet {};
|
||||
create_packet.header = CG::CHARACTER_CREATE;
|
||||
@@ -559,8 +687,10 @@ uint8_t CreateCharacter(EncryptedClient& channel_client, std::string_view create
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCPlayerCreateSuccess), "unexpected player create success size");
|
||||
const auto* success = reinterpret_cast<const PacketGCPlayerCreateSuccess*>(frame.data());
|
||||
std::cout << "character_created index=" << static_cast<int>(success->account_character_index)
|
||||
<< " name=" << success->player.name << "\n";
|
||||
EmitEvent(result, json, "character_created index=" + std::to_string(static_cast<int>(success->account_character_index)) +
|
||||
" name=" + success->player.name);
|
||||
result.character_created = true;
|
||||
result.character_name = success->player.name;
|
||||
return success->account_character_index;
|
||||
}
|
||||
}
|
||||
@@ -568,7 +698,7 @@ uint8_t CreateCharacter(EncryptedClient& channel_client, std::string_view create
|
||||
throw std::runtime_error("did not receive PLAYER_CREATE_SUCCESS");
|
||||
}
|
||||
|
||||
void SelectCharacter(EncryptedClient& channel_client, uint8_t character_index)
|
||||
void SelectCharacter(EncryptedClient& channel_client, uint8_t character_index, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGPlayerSelect select_packet {};
|
||||
select_packet.header = CG::CHARACTER_SELECT;
|
||||
@@ -602,7 +732,7 @@ void SelectCharacter(EncryptedClient& channel_client, uint8_t character_index)
|
||||
if (phase->phase == PHASE_LOADING)
|
||||
{
|
||||
saw_phase_loading = true;
|
||||
std::cout << "phase_loading\n";
|
||||
EmitEvent(result, json, "phase_loading");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -613,7 +743,8 @@ void SelectCharacter(EncryptedClient& channel_client, uint8_t character_index)
|
||||
const auto* main_character = reinterpret_cast<const PacketGCMainCharacter*>(frame.data());
|
||||
Expect(main_character->name[0] != '\0', "main character name is empty");
|
||||
saw_main_character = true;
|
||||
std::cout << "main_character name=" << main_character->name << "\n";
|
||||
result.character_name = main_character->name;
|
||||
EmitEvent(result, json, "main_character name=" + std::string(main_character->name));
|
||||
}
|
||||
|
||||
if (saw_phase_loading && saw_main_character)
|
||||
@@ -621,7 +752,7 @@ void SelectCharacter(EncryptedClient& channel_client, uint8_t character_index)
|
||||
}
|
||||
}
|
||||
|
||||
void SendClientVersion(EncryptedClient& channel_client, std::string_view client_version)
|
||||
void SendClientVersion(EncryptedClient& channel_client, std::string_view client_version, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGClientVersion version_packet {};
|
||||
version_packet.header = CG::CLIENT_VERSION;
|
||||
@@ -629,10 +760,10 @@ void SendClientVersion(EncryptedClient& channel_client, std::string_view client_
|
||||
std::strncpy(version_packet.filename, "Metin2_Debug.exe", sizeof(version_packet.filename) - 1);
|
||||
std::strncpy(version_packet.timestamp, client_version.data(), sizeof(version_packet.timestamp) - 1);
|
||||
channel_client.SendEncryptedPacket(version_packet);
|
||||
std::cout << "client_version " << version_packet.timestamp << "\n";
|
||||
EmitEvent(result, json, "client_version " + std::string(version_packet.timestamp));
|
||||
}
|
||||
|
||||
void EnterGame(EncryptedClient& channel_client)
|
||||
void EnterGame(EncryptedClient& channel_client, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGEnterGame enter_game {};
|
||||
enter_game.header = CG::ENTERGAME;
|
||||
@@ -666,7 +797,7 @@ void EnterGame(EncryptedClient& channel_client)
|
||||
if (phase->phase == PHASE_GAME)
|
||||
{
|
||||
saw_phase_game = true;
|
||||
std::cout << "phase_game\n";
|
||||
EmitEvent(result, json, "phase_game");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -683,7 +814,8 @@ void EnterGame(EncryptedClient& channel_client)
|
||||
Expect(frame.size() == sizeof(PacketGCChannel), "unexpected channel packet size");
|
||||
const auto* channel = reinterpret_cast<const PacketGCChannel*>(frame.data());
|
||||
saw_channel = true;
|
||||
std::cout << "game_channel channel=" << static_cast<int>(channel->channel) << "\n";
|
||||
result.game_channel = channel->channel;
|
||||
EmitEvent(result, json, "game_channel channel=" + std::to_string(static_cast<int>(channel->channel)));
|
||||
}
|
||||
|
||||
if (saw_phase_game && saw_time && saw_channel)
|
||||
@@ -694,20 +826,22 @@ void EnterGame(EncryptedClient& channel_client)
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
SmokeOptions options;
|
||||
SmokeResult result;
|
||||
|
||||
try
|
||||
{
|
||||
Expect(argc >= 6 && argc <= 8,
|
||||
Expect(argc >= 6,
|
||||
"usage: metin_login_smoke <host> <auth_port> <channel_port> <login> <password>\n"
|
||||
" or: metin_login_smoke <host> <auth_port> <channel_port> <login> --password-env=ENV_NAME\n"
|
||||
" optional: --create-character-name=NAME [--client-version=VERSION]");
|
||||
" optional: --create-character-name=NAME [--client-version=VERSION] [--json] "
|
||||
"[--expect-auth-failure=STATUS] [--expect-channel-failure=STATUS]");
|
||||
|
||||
const std::string host = argv[1];
|
||||
const uint16_t auth_port = static_cast<uint16_t>(std::stoi(argv[2]));
|
||||
const uint16_t channel_port = static_cast<uint16_t>(std::stoi(argv[3]));
|
||||
const std::string login = argv[4];
|
||||
std::string password;
|
||||
std::string create_character_name;
|
||||
std::string client_version = "1215955205";
|
||||
|
||||
const std::string password_arg = argv[5];
|
||||
const std::string prefix = "--password-env=";
|
||||
@@ -727,54 +861,174 @@ int main(int argc, char** argv)
|
||||
const std::string create_arg = argv[i];
|
||||
const std::string create_prefix = "--create-character-name=";
|
||||
const std::string version_prefix = "--client-version=";
|
||||
const std::string auth_failure_prefix = "--expect-auth-failure=";
|
||||
const std::string channel_failure_prefix = "--expect-channel-failure=";
|
||||
if (create_arg.rfind(create_prefix, 0) == 0)
|
||||
{
|
||||
create_character_name = create_arg.substr(create_prefix.size());
|
||||
Expect(!create_character_name.empty(), "create character name is empty");
|
||||
Expect(create_character_name.size() <= CHARACTER_NAME_MAX_LEN_LOCAL, "create character name too long");
|
||||
options.create_character_name = create_arg.substr(create_prefix.size());
|
||||
Expect(!options.create_character_name.empty(), "create character name is empty");
|
||||
Expect(options.create_character_name.size() <= CHARACTER_NAME_MAX_LEN_LOCAL, "create character name too long");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg.rfind(version_prefix, 0) == 0)
|
||||
{
|
||||
client_version = create_arg.substr(version_prefix.size());
|
||||
Expect(!client_version.empty(), "client version is empty");
|
||||
options.client_version = create_arg.substr(version_prefix.size());
|
||||
Expect(!options.client_version.empty(), "client version is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
throw std::runtime_error("unknown optional argument");
|
||||
if (create_arg.rfind(auth_failure_prefix, 0) == 0)
|
||||
{
|
||||
options.expect_auth_failure = create_arg.substr(auth_failure_prefix.size());
|
||||
Expect(!options.expect_auth_failure.empty(), "expected auth failure status is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg.rfind(channel_failure_prefix, 0) == 0)
|
||||
{
|
||||
options.expect_channel_failure = create_arg.substr(channel_failure_prefix.size());
|
||||
Expect(!options.expect_channel_failure.empty(), "expected channel failure status is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg == "--json")
|
||||
{
|
||||
options.json = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw std::runtime_error("unknown optional argument: " + create_arg);
|
||||
}
|
||||
|
||||
Expect(login.size() <= LOGIN_MAX_LEN_LOCAL, "login too long");
|
||||
Expect(password.size() <= PASSWD_MAX_LEN_LOCAL, "password too long");
|
||||
|
||||
const uint32_t login_key = Authenticate(host, auth_port, login, password);
|
||||
result.stage = "auth_handshake";
|
||||
auto started_at = std::chrono::steady_clock::now();
|
||||
EncryptedClient auth_client(host, auth_port);
|
||||
auth_client.Handshake();
|
||||
result.auth_handshake_ms = ElapsedMs(started_at);
|
||||
|
||||
result.stage = "auth_login";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
const AuthResponse auth = Authenticate(auth_client, login, password, result, options.json);
|
||||
result.auth_login_ms = ElapsedMs(started_at);
|
||||
result.failure_status.clear();
|
||||
|
||||
if (!auth.success)
|
||||
{
|
||||
result.failure_status = auth.failure_status;
|
||||
if (!options.expect_auth_failure.empty())
|
||||
{
|
||||
Expect(auth.failure_status == options.expect_auth_failure,
|
||||
"expected auth failure '" + options.expect_auth_failure + "' but got '" + auth.failure_status + "'");
|
||||
result.ok = true;
|
||||
result.result = "expected_auth_failure";
|
||||
result.stage = "auth_login";
|
||||
EmitEvent(result, options.json, "expected_auth_failure status=" + auth.failure_status);
|
||||
if (options.json)
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
throw std::runtime_error("auth login failed: " + auth.failure_status);
|
||||
}
|
||||
|
||||
Expect(options.expect_auth_failure.empty(),
|
||||
"auth login succeeded but expected failure '" + options.expect_auth_failure + "'");
|
||||
|
||||
const uint32_t login_key = auth.login_key;
|
||||
result.login_key = login_key;
|
||||
|
||||
result.stage = "channel_handshake";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
EncryptedClient channel_client(host, channel_port);
|
||||
channel_client.Handshake();
|
||||
const PacketGCLoginSuccess4 login_success = LoginToChannel(channel_client, login, login_key);
|
||||
result.channel_handshake_ms = ElapsedMs(started_at);
|
||||
|
||||
result.stage = "channel_login";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
const ChannelLoginResponse channel = LoginToChannel(channel_client, login, login_key, result, options.json);
|
||||
result.channel_login_ms = ElapsedMs(started_at);
|
||||
|
||||
if (!channel.success)
|
||||
{
|
||||
result.failure_status = channel.failure_status;
|
||||
if (!options.expect_channel_failure.empty())
|
||||
{
|
||||
Expect(channel.failure_status == options.expect_channel_failure,
|
||||
"expected channel failure '" + options.expect_channel_failure + "' but got '" + channel.failure_status + "'");
|
||||
result.ok = true;
|
||||
result.result = "expected_channel_failure";
|
||||
result.stage = "channel_login";
|
||||
EmitEvent(result, options.json, "expected_channel_failure status=" + channel.failure_status);
|
||||
if (options.json)
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
throw std::runtime_error("channel login failed: " + channel.failure_status);
|
||||
}
|
||||
|
||||
Expect(options.expect_channel_failure.empty(),
|
||||
"channel login succeeded but expected failure '" + options.expect_channel_failure + "'");
|
||||
|
||||
const PacketGCLoginSuccess4& login_success = channel.login_success;
|
||||
result.empire = channel.empire;
|
||||
|
||||
uint8_t character_index = 0;
|
||||
try
|
||||
{
|
||||
character_index = FindFirstPlayableCharacterIndex(login_success);
|
||||
std::cout << "selected_character index=" << static_cast<int>(character_index) << "\n";
|
||||
result.character_index = character_index;
|
||||
result.character_name = login_success.players[character_index].name;
|
||||
EmitEvent(result, options.json, "selected_character index=" + std::to_string(static_cast<int>(character_index)));
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
Expect(!create_character_name.empty(), "account has no playable characters and no create-character name was provided");
|
||||
character_index = CreateCharacter(channel_client, create_character_name);
|
||||
Expect(!options.create_character_name.empty(), "account has no playable characters and no create-character name was provided");
|
||||
result.stage = "character_create";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
character_index = CreateCharacter(channel_client, options.create_character_name, result, options.json);
|
||||
result.character_create_ms = ElapsedMs(started_at);
|
||||
result.character_index = character_index;
|
||||
}
|
||||
|
||||
SelectCharacter(channel_client, character_index);
|
||||
SendClientVersion(channel_client, client_version);
|
||||
EnterGame(channel_client);
|
||||
result.stage = "character_select";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
SelectCharacter(channel_client, character_index, result, options.json);
|
||||
result.character_select_ms = ElapsedMs(started_at);
|
||||
|
||||
std::cout << "full_login_success\n";
|
||||
result.stage = "client_version";
|
||||
SendClientVersion(channel_client, options.client_version, result, options.json);
|
||||
|
||||
result.stage = "entergame";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
EnterGame(channel_client, result, options.json);
|
||||
result.entergame_ms = ElapsedMs(started_at);
|
||||
|
||||
result.ok = true;
|
||||
result.result = "success";
|
||||
result.stage = "complete";
|
||||
EmitEvent(result, options.json, "full_login_success");
|
||||
if (options.json)
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
result.ok = false;
|
||||
result.result = "unexpected_failure";
|
||||
result.error = e.what();
|
||||
if (options.json)
|
||||
{
|
||||
PrintJson(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "metin_login_smoke failed: " << e.what() << "\n";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user