feat: Enhance Assert RPC with thread-safe state management and improved error handling
This commit is contained in:
@@ -115,10 +115,10 @@ run_test "Ping (Health Check)" "Ping" '{"message":"test"}'
|
|||||||
run_test "Click (Open Overworld Editor)" "Click" '{"target":"menuitem: Overworld Editor","type":"LEFT"}'
|
run_test "Click (Open Overworld Editor)" "Click" '{"target":"menuitem: Overworld Editor","type":"LEFT"}'
|
||||||
|
|
||||||
# 3. Wait - Window Visible (Overworld Editor should open)
|
# 3. Wait - Window Visible (Overworld Editor should open)
|
||||||
run_test "Wait (Overworld Editor Window)" "Wait" '{"condition":"window_visible:Overworld Editor","timeout_ms":5000,"poll_interval_ms":100}'
|
run_test "Wait (Overworld Editor Window)" "Wait" '{"condition":"window_visible:Overworld","timeout_ms":15000,"poll_interval_ms":100}'
|
||||||
|
|
||||||
# 4. Assert - Window Visible (Overworld Editor should be open)
|
# 4. Assert - Window Visible (Overworld Editor should be open)
|
||||||
run_test "Assert (Overworld Editor Visible)" "Assert" '{"condition":"visible:Overworld Editor"}'
|
run_test "Assert (Overworld Editor Visible)" "Assert" '{"condition":"visible:Overworld"}'
|
||||||
|
|
||||||
# 5. Click - Another menu item (Dungeon Editor)
|
# 5. Click - Another menu item (Dungeon Editor)
|
||||||
run_test "Click (Open Dungeon Editor)" "Click" '{"target":"menuitem: Dungeon Editor","type":"LEFT"}'
|
run_test "Click (Open Dungeon Editor)" "Click" '{"target":"menuitem: Dungeon Editor","type":"LEFT"}'
|
||||||
|
|||||||
@@ -52,6 +52,33 @@ struct WaitState {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Thread-safe state for Assert RPC communication
|
||||||
|
struct AssertState {
|
||||||
|
std::atomic<bool> assertion_passed{false};
|
||||||
|
std::mutex data_mutex;
|
||||||
|
std::string message;
|
||||||
|
std::string actual_value;
|
||||||
|
std::string expected_value;
|
||||||
|
|
||||||
|
void SetResult(bool passed, const std::string& msg,
|
||||||
|
const std::string& actual, const std::string& expected) {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex);
|
||||||
|
assertion_passed.store(passed);
|
||||||
|
message = msg;
|
||||||
|
actual_value = actual;
|
||||||
|
expected_value = expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetResult(bool& passed, std::string& msg,
|
||||||
|
std::string& actual, std::string& expected) {
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex);
|
||||||
|
passed = assertion_passed.load();
|
||||||
|
msg = message;
|
||||||
|
actual = actual_value;
|
||||||
|
expected = expected_value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -619,24 +646,24 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request,
|
|||||||
std::string assertion_type = condition.substr(0, colon_pos);
|
std::string assertion_type = condition.substr(0, colon_pos);
|
||||||
std::string assertion_target = condition.substr(colon_pos + 1);
|
std::string assertion_target = condition.substr(colon_pos + 1);
|
||||||
|
|
||||||
// Create a dynamic test to check the assertion
|
// Create thread-safe shared state for communication
|
||||||
bool assertion_passed = false;
|
auto assert_state = std::make_shared<AssertState>();
|
||||||
std::string message;
|
|
||||||
std::string actual_value;
|
|
||||||
std::string expected_value;
|
|
||||||
|
|
||||||
auto test_data = std::make_shared<DynamicTestData>();
|
auto test_data = std::make_shared<DynamicTestData>();
|
||||||
test_data->test_func = [=, &assertion_passed, &message, &actual_value, &expected_value](ImGuiTestContext* ctx) {
|
test_data->test_func = [assert_state, assertion_type, assertion_target](ImGuiTestContext* ctx) {
|
||||||
try {
|
try {
|
||||||
|
bool passed = false;
|
||||||
|
std::string msg, actual, expected;
|
||||||
|
|
||||||
if (assertion_type == "visible") {
|
if (assertion_type == "visible") {
|
||||||
// Check if window is visible
|
// Check if window is visible
|
||||||
ImGuiWindow* window = ImGui::FindWindowByName(assertion_target.c_str());
|
ImGuiWindow* window = ImGui::FindWindowByName(assertion_target.c_str());
|
||||||
bool is_visible = (window != nullptr && !window->Hidden);
|
bool is_visible = (window != nullptr && !window->Hidden);
|
||||||
|
|
||||||
assertion_passed = is_visible;
|
passed = is_visible;
|
||||||
actual_value = is_visible ? "visible" : "hidden";
|
actual = is_visible ? "visible" : "hidden";
|
||||||
expected_value = "visible";
|
expected = "visible";
|
||||||
message = assertion_passed
|
msg = passed
|
||||||
? absl::StrFormat("'%s' is visible", assertion_target)
|
? absl::StrFormat("'%s' is visible", assertion_target)
|
||||||
: absl::StrFormat("'%s' is not visible", assertion_target);
|
: absl::StrFormat("'%s' is not visible", assertion_target);
|
||||||
|
|
||||||
@@ -645,10 +672,10 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request,
|
|||||||
ImGuiTestItemInfo item = ctx->ItemInfo(assertion_target.c_str());
|
ImGuiTestItemInfo item = ctx->ItemInfo(assertion_target.c_str());
|
||||||
bool is_enabled = (item.ID != 0 && !(item.ItemFlags & ImGuiItemFlags_Disabled));
|
bool is_enabled = (item.ID != 0 && !(item.ItemFlags & ImGuiItemFlags_Disabled));
|
||||||
|
|
||||||
assertion_passed = is_enabled;
|
passed = is_enabled;
|
||||||
actual_value = is_enabled ? "enabled" : "disabled";
|
actual = is_enabled ? "enabled" : "disabled";
|
||||||
expected_value = "enabled";
|
expected = "enabled";
|
||||||
message = assertion_passed
|
msg = passed
|
||||||
? absl::StrFormat("'%s' is enabled", assertion_target)
|
? absl::StrFormat("'%s' is enabled", assertion_target)
|
||||||
: absl::StrFormat("'%s' is not enabled", assertion_target);
|
: absl::StrFormat("'%s' is not enabled", assertion_target);
|
||||||
|
|
||||||
@@ -657,10 +684,10 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request,
|
|||||||
ImGuiTestItemInfo item = ctx->ItemInfo(assertion_target.c_str());
|
ImGuiTestItemInfo item = ctx->ItemInfo(assertion_target.c_str());
|
||||||
bool exists = (item.ID != 0);
|
bool exists = (item.ID != 0);
|
||||||
|
|
||||||
assertion_passed = exists;
|
passed = exists;
|
||||||
actual_value = exists ? "exists" : "not found";
|
actual = exists ? "exists" : "not found";
|
||||||
expected_value = "exists";
|
expected = "exists";
|
||||||
message = assertion_passed
|
msg = passed
|
||||||
? absl::StrFormat("'%s' exists", assertion_target)
|
? absl::StrFormat("'%s' exists", assertion_target)
|
||||||
: absl::StrFormat("'%s' not found", assertion_target);
|
: absl::StrFormat("'%s' not found", assertion_target);
|
||||||
|
|
||||||
@@ -669,10 +696,11 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request,
|
|||||||
// Format: "text_contains:MyInput:ExpectedText"
|
// Format: "text_contains:MyInput:ExpectedText"
|
||||||
size_t second_colon = assertion_target.find(':');
|
size_t second_colon = assertion_target.find(':');
|
||||||
if (second_colon == std::string::npos) {
|
if (second_colon == std::string::npos) {
|
||||||
assertion_passed = false;
|
passed = false;
|
||||||
message = "text_contains requires format 'text_contains:target:expected_text'";
|
msg = "text_contains requires format 'text_contains:target:expected_text'";
|
||||||
actual_value = "N/A";
|
actual = "N/A";
|
||||||
expected_value = "N/A";
|
expected = "N/A";
|
||||||
|
assert_state->SetResult(passed, msg, actual, expected);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,32 +712,34 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request,
|
|||||||
// Note: Text retrieval is simplified - actual implementation may need widget-specific handling
|
// Note: Text retrieval is simplified - actual implementation may need widget-specific handling
|
||||||
std::string actual_text = "(text_retrieval_not_fully_implemented)";
|
std::string actual_text = "(text_retrieval_not_fully_implemented)";
|
||||||
|
|
||||||
assertion_passed = (actual_text.find(expected_text) != std::string::npos);
|
passed = (actual_text.find(expected_text) != std::string::npos);
|
||||||
actual_value = actual_text;
|
actual = actual_text;
|
||||||
expected_value = absl::StrFormat("contains '%s'", expected_text);
|
expected = absl::StrFormat("contains '%s'", expected_text);
|
||||||
message = assertion_passed
|
msg = passed
|
||||||
? absl::StrFormat("'%s' contains '%s'", input_target, expected_text)
|
? absl::StrFormat("'%s' contains '%s'", input_target, expected_text)
|
||||||
: absl::StrFormat("'%s' does not contain '%s' (actual: '%s')",
|
: absl::StrFormat("'%s' does not contain '%s' (actual: '%s')",
|
||||||
input_target, expected_text, actual_text);
|
input_target, expected_text, actual_text);
|
||||||
} else {
|
} else {
|
||||||
assertion_passed = false;
|
passed = false;
|
||||||
message = absl::StrFormat("Input '%s' not found", input_target);
|
msg = absl::StrFormat("Input '%s' not found", input_target);
|
||||||
actual_value = "not found";
|
actual = "not found";
|
||||||
expected_value = expected_text;
|
expected = expected_text;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
assertion_passed = false;
|
passed = false;
|
||||||
message = absl::StrFormat("Unknown assertion type: %s", assertion_type);
|
msg = absl::StrFormat("Unknown assertion type: %s", assertion_type);
|
||||||
actual_value = "N/A";
|
actual = "N/A";
|
||||||
expected_value = "N/A";
|
expected = "N/A";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store result in thread-safe state
|
||||||
|
assert_state->SetResult(passed, msg, actual, expected);
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
assertion_passed = false;
|
assert_state->SetResult(false,
|
||||||
message = absl::StrFormat("Assertion failed: %s", e.what());
|
absl::StrFormat("Assertion failed: %s", e.what()),
|
||||||
actual_value = "exception";
|
"exception", "N/A");
|
||||||
expected_value = "N/A";
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -729,25 +759,30 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request,
|
|||||||
auto wait_start = std::chrono::steady_clock::now();
|
auto wait_start = std::chrono::steady_clock::now();
|
||||||
while (!IsTestCompleted(test)) {
|
while (!IsTestCompleted(test)) {
|
||||||
if (std::chrono::steady_clock::now() - wait_start > timeout) {
|
if (std::chrono::steady_clock::now() - wait_start > timeout) {
|
||||||
assertion_passed = false;
|
assert_state->SetResult(false, "Test timeout - assertion check timed out",
|
||||||
message = "Test timeout - assertion check timed out";
|
"timeout", "N/A");
|
||||||
actual_value = "timeout";
|
|
||||||
expected_value = "N/A";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Yield to allow ImGui event processing
|
// Yield to allow ImGui event processing
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read final state from thread-safe shared state
|
||||||
|
bool assertion_passed;
|
||||||
|
std::string message, actual_value, expected_value;
|
||||||
|
assert_state->GetResult(assertion_passed, message, actual_value, expected_value);
|
||||||
|
|
||||||
// Check final test status
|
// Check final test status
|
||||||
if (IsTestCompleted(test)) {
|
if (IsTestCompleted(test)) {
|
||||||
if (test->Output.Status == ImGuiTestStatus_Success) {
|
if (test->Output.Status == ImGuiTestStatus_Success) {
|
||||||
// Status already set by test function
|
// Status already set by test function
|
||||||
} else {
|
} else {
|
||||||
assertion_passed = false;
|
|
||||||
if (message.empty()) {
|
if (message.empty()) {
|
||||||
message = absl::StrFormat("Test failed with status: %d",
|
assert_state->SetResult(false,
|
||||||
test->Output.Status);
|
absl::StrFormat("Test failed with status: %d",
|
||||||
|
test->Output.Status),
|
||||||
|
"error", "N/A");
|
||||||
|
assert_state->GetResult(assertion_passed, message, actual_value, expected_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user