- Add --exec flag to execute multiple scripts before main program - Scripts are executed in order and share Python interpreter state - Implement full PyAutoGUI-compatible automation API in McRFPy_Automation - Add screenshot, mouse control, keyboard input capabilities - Fix Python initialization issues when multiple scripts are loaded - Update CommandLineParser to handle --exec with proper sys.argv management - Add comprehensive examples and documentation This enables automation testing by allowing test scripts to run alongside games using the same Python environment. The automation API provides event injection into the SFML render loop for UI testing. Closes #32 partially (Python interpreter emulation) References automation testing requirements
189 lines
No EOL
6 KiB
C++
189 lines
No EOL
6 KiB
C++
// Example implementation of --exec flag for McRogueFace
|
|
// This shows the minimal changes needed to support multiple script execution
|
|
|
|
// === In McRogueFaceConfig.h ===
|
|
struct McRogueFaceConfig {
|
|
// ... existing fields ...
|
|
|
|
// Scripts to execute after main script (McRogueFace style)
|
|
std::vector<std::filesystem::path> exec_scripts;
|
|
};
|
|
|
|
// === In CommandLineParser.cpp ===
|
|
CommandLineParser::ParseResult CommandLineParser::parse(McRogueFaceConfig& config) {
|
|
// ... existing parsing code ...
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
std::string arg = argv[i];
|
|
|
|
// ... existing flag handling ...
|
|
|
|
else if (arg == "--exec") {
|
|
// Add script to exec list
|
|
if (i + 1 < argc) {
|
|
config.exec_scripts.push_back(argv[++i]);
|
|
} else {
|
|
std::cerr << "Error: --exec requires a script path\n";
|
|
return {true, 1};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// === In GameEngine.cpp ===
|
|
GameEngine::GameEngine(const McRogueFaceConfig& cfg) : config(cfg) {
|
|
// ... existing initialization ...
|
|
|
|
// Only load game.py if no custom script/command/module is specified
|
|
bool should_load_game = config.script_path.empty() &&
|
|
config.python_command.empty() &&
|
|
config.python_module.empty() &&
|
|
!config.interactive_mode &&
|
|
!config.python_mode &&
|
|
config.exec_scripts.empty(); // Add this check
|
|
|
|
if (should_load_game) {
|
|
if (!Py_IsInitialized()) {
|
|
McRFPy_API::api_init();
|
|
}
|
|
McRFPy_API::executePyString("import mcrfpy");
|
|
McRFPy_API::executeScript("scripts/game.py");
|
|
}
|
|
|
|
// Execute any --exec scripts
|
|
for (const auto& exec_script : config.exec_scripts) {
|
|
std::cout << "Executing script: " << exec_script << std::endl;
|
|
McRFPy_API::executeScript(exec_script.string());
|
|
}
|
|
}
|
|
|
|
// === Usage Examples ===
|
|
|
|
// Example 1: Run game with automation
|
|
// ./mcrogueface game.py --exec automation.py
|
|
|
|
// Example 2: Run game with multiple automation scripts
|
|
// ./mcrogueface game.py --exec test_suite.py --exec monitor.py --exec logger.py
|
|
|
|
// Example 3: Run only automation (no game)
|
|
// ./mcrogueface --exec standalone_test.py
|
|
|
|
// Example 4: Headless automation
|
|
// ./mcrogueface --headless game.py --exec automation.py
|
|
|
|
// === Python Script Example (automation.py) ===
|
|
/*
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
|
|
def periodic_test():
|
|
"""Run automated tests every 5 seconds"""
|
|
# Take screenshot
|
|
automation.screenshot(f"test_{mcrfpy.getFrame()}.png")
|
|
|
|
# Check game state
|
|
scene = mcrfpy.currentScene()
|
|
if scene == "main_menu":
|
|
# Click start button
|
|
automation.click(400, 300)
|
|
elif scene == "game":
|
|
# Perform game tests
|
|
automation.hotkey("i") # Open inventory
|
|
|
|
print(f"Test completed at frame {mcrfpy.getFrame()}")
|
|
|
|
# Register timer for periodic testing
|
|
mcrfpy.setTimer("automation_test", periodic_test, 5000)
|
|
|
|
print("Automation script loaded - tests will run every 5 seconds")
|
|
|
|
# Script returns here - giving control back to C++
|
|
*/
|
|
|
|
// === Advanced Example: Event-Driven Automation ===
|
|
/*
|
|
# automation_advanced.py
|
|
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import json
|
|
|
|
class AutomationFramework:
|
|
def __init__(self):
|
|
self.test_queue = []
|
|
self.results = []
|
|
self.load_test_suite()
|
|
|
|
def load_test_suite(self):
|
|
"""Load test definitions from JSON"""
|
|
with open("test_suite.json") as f:
|
|
self.test_queue = json.load(f)["tests"]
|
|
|
|
def run_next_test(self):
|
|
"""Execute next test in queue"""
|
|
if not self.test_queue:
|
|
self.finish_testing()
|
|
return
|
|
|
|
test = self.test_queue.pop(0)
|
|
|
|
try:
|
|
if test["type"] == "click":
|
|
automation.click(test["x"], test["y"])
|
|
elif test["type"] == "key":
|
|
automation.keyDown(test["key"])
|
|
automation.keyUp(test["key"])
|
|
elif test["type"] == "screenshot":
|
|
automation.screenshot(test["filename"])
|
|
elif test["type"] == "wait":
|
|
# Re-queue this test for later
|
|
self.test_queue.insert(0, test)
|
|
return
|
|
|
|
self.results.append({"test": test, "status": "pass"})
|
|
except Exception as e:
|
|
self.results.append({"test": test, "status": "fail", "error": str(e)})
|
|
|
|
def finish_testing(self):
|
|
"""Save test results and cleanup"""
|
|
with open("test_results.json", "w") as f:
|
|
json.dump(self.results, f, indent=2)
|
|
print(f"Testing complete: {len(self.results)} tests executed")
|
|
mcrfpy.delTimer("automation_framework")
|
|
|
|
# Create and start automation
|
|
framework = AutomationFramework()
|
|
mcrfpy.setTimer("automation_framework", framework.run_next_test, 100)
|
|
*/
|
|
|
|
// === Thread Safety Considerations ===
|
|
|
|
// The --exec approach requires NO thread safety changes because:
|
|
// 1. All scripts run in the same Python interpreter
|
|
// 2. Scripts execute sequentially during initialization
|
|
// 3. After initialization, only callbacks run (timer/input based)
|
|
// 4. C++ maintains control of the render loop
|
|
|
|
// This is the "honor system" - scripts must:
|
|
// - Set up their callbacks/timers
|
|
// - Return control to C++
|
|
// - Not block or run infinite loops
|
|
// - Use timers for periodic tasks
|
|
|
|
// === Future Extensions ===
|
|
|
|
// 1. Script communication via shared Python modules
|
|
// game.py:
|
|
// import mcrfpy
|
|
// mcrfpy.game_state = {"level": 1, "score": 0}
|
|
//
|
|
// automation.py:
|
|
// import mcrfpy
|
|
// if mcrfpy.game_state["level"] == 1:
|
|
// # Test level 1 specific features
|
|
|
|
// 2. Priority-based script execution
|
|
// ./mcrogueface game.py --exec-priority high:critical.py --exec-priority low:logging.py
|
|
|
|
// 3. Conditional execution
|
|
// ./mcrogueface game.py --exec-if-scene menu:menu_test.py --exec-if-scene game:game_test.py
|