|
function CreateTestPrints(output, tag, timestamp) |
|
tag = tag or "" |
|
local err_tag = "GT_ERROR " .. tag |
|
|
|
GameTestsPrint = CreatePrint { tag, output = output } |
|
GameTestsPrintf = CreatePrint { tag, output = output, format = string.format } |
|
GameTestsError = CreatePrint { err_tag, output = output, timestamp = timestamp } |
|
GameTestsErrorf = CreatePrint { err_tag, output = output, timestamp = timestamp, format = string.format } |
|
end |
|
|
|
if FirstLoad then |
|
GameTestsRunning = false |
|
GameTestsPrint = false |
|
GameTestsPrintf = false |
|
GameTestsError = false |
|
GameTestsErrorf = false |
|
GameTestsErrorsFilename = "svnAssets/Logs/GameTestsErrors.log" |
|
GameTestsFlushErrors = empty_func |
|
|
|
CreateTestPrints() |
|
end |
|
|
|
function RunGameTests(time_start_up, game_tests_name, ...) |
|
time_start_up = os.time() - (time_start_up or os.time()) |
|
game_tests_name = game_tests_name or "GameTests" |
|
|
|
CreateRealTimeThread( function(...) |
|
AsyncFileDelete(GameTestsErrorsFilename) |
|
local game_tests_errors_file, error_msg = io.open(GameTestsErrorsFilename, "w+") |
|
if not game_tests_errors_file then |
|
print("Failed to open GameTestsErrors.log:", error_msg) |
|
end |
|
|
|
local function GameTestOutput(s) |
|
ConsolePrintNoLog(s) |
|
if game_tests_errors_file then |
|
game_tests_errors_file:write(s, "\n") |
|
end |
|
end |
|
|
|
CreateTestPrints(GameTestOutput) |
|
GameTestsPrintf("Lua rev: %d, Assets rev: %d", LuaRevision, AssetsRevision) |
|
|
|
LoadBinAssets("") |
|
|
|
GameTestsRunning = true |
|
Msg("GameTestsBegin", true) |
|
SetAllDevDlcs(true) |
|
|
|
table.change(config, "GameTests", { |
|
Backtrace = false, |
|
SilentVMEStack = true, |
|
}) |
|
UpdateThreadDebugHook() |
|
|
|
local game_tests_table = _G[game_tests_name] |
|
local tests_to_run = {...} |
|
if #tests_to_run == 0 then |
|
tests_to_run = table.keys2(game_tests_table, "sorted") |
|
end |
|
local log_lines_processed = 0 |
|
|
|
local any_failed |
|
local lua_error_prefix = "[LUA ERROR] " |
|
GameTestsFlushErrors = function() |
|
FlushLogFile() |
|
local err, log_file = AsyncFileToString(GetLogFile(), false, false, "lines") |
|
if not err then |
|
for i = log_lines_processed+1, #log_file do |
|
local line = log_file[i] |
|
if line:starts_with(lua_error_prefix) then |
|
GameTestsErrorf("%s", string.sub(line, #lua_error_prefix+1)) |
|
any_failed = true |
|
elseif line:match("%)%: ASSERT.*failed") then |
|
GameTestsErrorf("once", "%s", line) |
|
any_failed = true |
|
elseif line:match(".*%.lua%(%d*%): ") then |
|
GameTestsErrorf("%s", line) |
|
any_failed = true |
|
elseif line:match("COMPILE!.*fx") then |
|
GameTestsPrint("once", line) |
|
end |
|
end |
|
log_lines_processed = #log_file |
|
else |
|
GameTestsPrint("Failed to load log file from game " .. GetLogFile() .. " : " .. err) |
|
end |
|
if game_tests_errors_file then |
|
game_tests_errors_file:flush() |
|
end |
|
end |
|
|
|
GameTestsFlushErrors() |
|
|
|
local all_tests_start_time = GetPreciseTicks() |
|
for _, test in ipairs(tests_to_run) do |
|
if game_tests_table[test] then |
|
CreateTestPrints(GameTestOutput, test, "gametime") |
|
|
|
GameTestsPrint("Start...") |
|
local time = GetPreciseTicks() |
|
Msg("GameTestBegin", test) |
|
|
|
local success = sprocall(game_tests_table[test], time_start_up, game_tests_name) |
|
if not success then |
|
any_failed = true |
|
end |
|
|
|
Msg("GameTestEnd", test) |
|
GameTestsFlushErrors() |
|
GameTestsPrint(string.format("...end. Duration %i ms. Since start %i sec.", GetPreciseTicks() - time , (GetPreciseTicks() - all_tests_start_time) / 1000)) |
|
else |
|
GameTestsError("GameTest not found:", test) |
|
end |
|
end |
|
|
|
if any_failed then |
|
FlushLogFile() |
|
local err, log_file = AsyncFileToString(GetLogFile(), false, false, "lines") |
|
if not err then |
|
CreateTestPrints(GameTestOutput, "GT_LOG") |
|
GameTestsPrint("Complete log file from run follows:") |
|
GameTestsPrint(string.rep("-", 80)) |
|
for _, line in ipairs(log_file) do |
|
GameTestsPrint(line) |
|
end |
|
end |
|
end |
|
|
|
if game_tests_errors_file then |
|
game_tests_errors_file:close() |
|
end |
|
|
|
GameTestsRunning = false |
|
Msg("GameTestsEnd", true) |
|
|
|
table.restore(config, "GameTests", true) |
|
UpdateThreadDebugHook() |
|
|
|
CreateTestPrints() |
|
|
|
quit() |
|
end, ...) |
|
end |
|
|
|
function DbgRunGameTests(game_tests_table, names) |
|
if not IsRealTimeThread() then |
|
return CreateRealTimeThread(DbgRunGameTests, game_tests_table, names) |
|
end |
|
GameTestsRunning = true |
|
Msg("GameTestsBegin") |
|
local old = LocalStorage.DisableDLC |
|
SetAllDevDlcs(true) |
|
game_tests_table = game_tests_table or GameTests |
|
names = names or table.keys(game_tests_table, true) |
|
local times = {} |
|
local st = GetPreciseTicks() |
|
for _, name in ipairs(names) do |
|
local func = game_tests_table[name] |
|
if not func then |
|
printf("No such test", name) |
|
else |
|
CreateTestPrints(print, name) |
|
Msg("GameTestBegin", name) |
|
print("Testing", name) |
|
CloseMenuDialogs() |
|
local time = GetPreciseTicks() |
|
sprocall(func) |
|
time = GetPreciseTicks() - time |
|
Msg("GameTestEnd", name) |
|
printf("Done testing %s in %d ms", name, time) |
|
times[name] = time |
|
end |
|
end |
|
if #names > 1 then |
|
printf("Done testing all in %d ms", GetPreciseTicks() - st) |
|
for _, name in ipairs(names) do |
|
printf("\t%s: %d ms", name, times[name]) |
|
end |
|
print() |
|
end |
|
LocalStorage.DisableDLC = old |
|
SaveLocalStorage() |
|
CreateTestPrints() |
|
GameTestsRunning = false |
|
Msg("GameTestsEnd") |
|
end |
|
|
|
function DbgRunGameTest(name, game_tests_table) |
|
return DbgRunGameTests(game_tests_table, {name}) |
|
end |
|
|
|
GameTests = {} |
|
GameTestsNightly = {} |
|
|
|
|
|
g_UIAutoTestButtonsMap = false |
|
g_UIGameChangeMap = ChangeMap |
|
g_UIGetContentTop = function() return GetInGameInterface() end |
|
g_UIGetBuildingsList = false |
|
g_UISpecialToggleButton = {match = false} |
|
g_UIBlacklistButton = {match = false} |
|
g_UIPrepareTest = false |
|
|
|
local function IsSpecialToggleButton(button, id) |
|
if g_UISpecialToggleButton[id] then return true end |
|
local match = g_UISpecialToggleButton.match |
|
return match and match(button) |
|
end |
|
|
|
local function IsBlacklistedButton(button, id) |
|
local id = rawget(button, "Id") |
|
if id and g_UIBlacklistButton[id] then |
|
return true |
|
end |
|
local match = g_UIBlacklistButton.match |
|
return match and match(button) |
|
end |
|
|
|
local function GetContentSnapshot(content) |
|
content = content or g_UIGetContentTop() |
|
|
|
local snapshot, used = {}, {} |
|
for idx, window in ipairs(content) do |
|
if not used[window] then |
|
used[window] = true |
|
snapshot[idx] = GetContentSnapshot(window) |
|
end |
|
end |
|
|
|
return snapshot, used |
|
end |
|
|
|
local function DetectNewWindows(snapshot, used) |
|
local new_snapshot, new_used = GetContentSnapshot() |
|
local windows = setmetatable({}, weak_keys_meta) |
|
for window in pairs(new_used) do |
|
if not used[window] then |
|
table.insert(windows, window) |
|
end |
|
end |
|
|
|
return windows |
|
end |
|
|
|
local function GetButtons(windows, buttons) |
|
buttons = buttons or {} |
|
|
|
for _, control in ipairs(windows) do |
|
if control:IsKindOf("XButton") then |
|
if not IsBlacklistedButton(control) then |
|
table.insert(buttons, control) |
|
end |
|
else |
|
GetButtons(control, buttons) |
|
end |
|
end |
|
|
|
return buttons |
|
end |
|
|
|
local function FilterWindowsWithButtons(windows) |
|
local windows_with_buttons = {} |
|
for _, window in ipairs(windows) do |
|
local buttons = GetButtons(window) |
|
if #buttons > 0 then |
|
table.insert(windows_with_buttons, {window = window, buttons = buttons}) |
|
end |
|
end |
|
|
|
return windows_with_buttons |
|
end |
|
|
|
local function GetSelectObjContainer(obj) |
|
local snapshot, used = GetContentSnapshot() |
|
SelectObj(obj) |
|
WaitMsg("SelectionChange", 1000) |
|
local windows = DetectNewWindows(snapshot, used) |
|
local windows_with_buttons = FilterWindowsWithButtons(windows) |
|
assert(#windows_with_buttons <= 1) |
|
|
|
return #windows_with_buttons == 1 and windows_with_buttons[1] |
|
end |
|
|
|
local function GetButtonPressContainer(button) |
|
local snapshot, used = GetContentSnapshot() |
|
button:Press() |
|
local windows = DetectNewWindows(snapshot, used) |
|
local windows_with_buttons = FilterWindowsWithButtons(windows) |
|
assert(#windows_with_buttons <= 1) |
|
|
|
return #windows_with_buttons == 1 and windows_with_buttons[1] |
|
end |
|
|
|
local function GetButtonId(button, idx) |
|
return button.Id or string.format("idChild_%d", idx) |
|
end |
|
|
|
function FindButton(container, id) |
|
for _, control in ipairs(container) do |
|
if control:IsKindOf("XButton") then |
|
if GetButtonId(control) == id then |
|
return control |
|
end |
|
else |
|
local button = FindButton(control, id) |
|
if button then |
|
return button |
|
end |
|
end |
|
end |
|
end |
|
|
|
local function ExpandGraph(node, buttons) |
|
node.children = node.children or {} |
|
for idx, button in ipairs(buttons) do |
|
local id = GetButtonId(button, idx) |
|
table.insert(node.children, {processed = {}, children = {}, parent = node, id = id, expanded = false}) |
|
end |
|
node.expanded = true |
|
end |
|
|
|
local function MarkNodeProcessed(node) |
|
node.parent.processed[node.id] = true |
|
end |
|
|
|
local function GenNodePath(node, nodes) |
|
for idx, child in ipairs(node.children) do |
|
if not node.processed[child.id] then |
|
table.insert(nodes, child) |
|
if child.expanded then |
|
local old_len = #buttons |
|
GenButtonSequence(child, nodes) |
|
if #nodes > old_len then |
|
return |
|
end |
|
else |
|
return |
|
end |
|
table.remove(nodes) |
|
node.processed[child.id] = true |
|
end |
|
end |
|
end |
|
|
|
local function FindButtonSequence(root) |
|
local nodes = {} |
|
GenNodePath(root, nodes) |
|
if #nodes > 0 then |
|
local node = nodes[#nodes] |
|
local buttons = {} |
|
for i = 1, #nodes - 1 do |
|
buttons[i] = nodes[i].id |
|
end |
|
|
|
return buttons, node |
|
end |
|
end |
|
|
|
function GetSingleBuildingClassList(list) |
|
local buildings, class_taken = {}, {} |
|
for _, bld in ipairs(list) do |
|
if not class_taken[bld.class] then |
|
table.insert(buildings, bld) |
|
class_taken[bld.class] = true |
|
end |
|
end |
|
|
|
return buildings |
|
end |
|
|
|
function GameTests.BuildingButtons() |
|
if not g_UIAutoTestButtonsMap then return end |
|
|
|
local time_started = GetPreciseTicks() |
|
|
|
if GetMapName() ~= g_UIAutoTestButtonsMap then g_UIGameChangeMap(g_UIAutoTestButtonsMap) end |
|
local list, content |
|
while not (list and content) do |
|
list = g_UIGetBuildingsList() |
|
content = g_UIGetContentTop() |
|
Sleep(50) |
|
end |
|
if g_UIPrepareTest then |
|
g_UIPrepareTest() |
|
end |
|
|
|
|
|
local clicks = 0 |
|
SelectObj(false) |
|
for bld_idx, bld in ipairs(list) do |
|
local container = IsValid(bld) and GetSelectObjContainer(bld) |
|
if container then |
|
local root = {processed = {}, children = {}, expanded = false} |
|
ExpandGraph(root, container.buttons) |
|
local buttons, node = FindButtonSequence(root) |
|
while container and buttons do |
|
for _, button in ipairs(buttons) do |
|
|
|
button:Press() |
|
clicks = clicks + 1 |
|
end |
|
local button = FindButton(container.window, node.id) |
|
if button and button:GetVisible() and button:GetEnabled() and not IsBlacklistedButton(button) then |
|
|
|
local new_container = GetButtonPressContainer(button) |
|
if new_container then |
|
ExpandGraph(node, new_container.buttons) |
|
end |
|
if IsSpecialToggleButton(button, node.id) then |
|
|
|
button:Press() |
|
clicks = clicks + 1 |
|
end |
|
end |
|
MarkNodeProcessed(node) |
|
SelectObj(false) |
|
container = GetSelectObjContainer(bld) |
|
|
|
buttons, node = FindButtonSequence(root) |
|
end |
|
end |
|
SelectObj(false) |
|
end |
|
GameTestsPrintf("Testing %d building for %d UI buttons clicks finished: %ds.", #list, clicks, (GetPreciseTicks() - time_started) / 1000) |
|
end |
|
|
|
function GameTestAddReferenceValue(type, name, value, comment, tolerance_mul, tolerance_div) |
|
if not type then return end |
|
local results_file = "AppData/Benchmarks/GameTestReferenceValues.lua" |
|
local _, str_result = AsyncFileToString(results_file) |
|
local _, referenceValues = LuaCodeToTuple(str_result) |
|
|
|
referenceValues = referenceValues or {} |
|
|
|
local avg_previous, avg_items = 0, 0 |
|
|
|
local maxResults = 5 |
|
|
|
referenceValues[type] = referenceValues[type] or {} |
|
local benchmark_results = table.copy(referenceValues[type]) |
|
|
|
benchmark_results[name] = benchmark_results[name] or {} |
|
|
|
for oldInd, oldCamera in pairs(benchmark_results[name]) do |
|
if oldCamera.comment == comment then |
|
avg_previous = avg_previous + oldCamera.value |
|
avg_items = avg_items + 1 |
|
else |
|
table.remove(benchmark_results[name], oldInd) |
|
GameTestsPrintf("Old %s not matching, deleting results for %s data!", name, type) |
|
end |
|
end |
|
table.insert(benchmark_results[name], {comment = comment, value = value}) |
|
referenceValues[type] = benchmark_results |
|
while true do |
|
if #benchmark_results[name] > maxResults then |
|
table.remove(benchmark_results[name], 1) |
|
else |
|
break |
|
end |
|
end |
|
|
|
if avg_items == 0 then |
|
GameTestsPrintf("No previous results to compare to for %s: %s. New results saved.",type, name) |
|
else |
|
avg_previous = avg_previous/avg_items |
|
end |
|
|
|
if avg_previous ~= 0 then |
|
if abs( 100.0 - ( ( (value*1.0)* 100.0) / (avg_previous*1.0) ) ) <= (tolerance_mul*1.0)/(tolerance_div*1.0) * 100.0 then |
|
GameTestsPrintf("Reference value %s: %s is %s, avg of previous is %s", type, name, value, avg_previous) |
|
else |
|
GameTestsErrorf("Reference value %s: %s is %s, avg of previous is %s", type, name, value, avg_previous) |
|
GameTestsPrintf("Camera properties: "..tostring(comment)) |
|
end |
|
end |
|
|
|
AsyncCreatePath("AppData/Benchmarks") |
|
local err = AsyncStringToFile(results_file, ValueToLuaCode(referenceValues)) |
|
if err then |
|
GameTestsError("Failed to create file with reference values", results_file, err) |
|
end |
|
end |
|
|
|
function GameTestsNightly.ReferenceImages() |
|
|
|
if not config.RenderingTestsMap then |
|
GameTestsPrint("config.RenderingTestsMap map not specified, skipping the test.") |
|
return |
|
end |
|
if not MapData[config.RenderingTestsMap] then |
|
GameTestsError(config.RenderingTestsMap, "map not found, could not complete test.") |
|
return |
|
end |
|
ChangeMap(config.RenderingTestsMap) |
|
SetMouseDeltaMode(true) |
|
|
|
ChangeVideoMode(512, 512, 0, false, false) |
|
SetLightmodel(0, LightmodelPresets.ArtPreview, 0) |
|
WaitNextFrame(10) |
|
|
|
local allowedDifference = 80 |
|
|
|
|
|
|
|
local cameras = Presets.Camera["reference"] |
|
if not cameras or #cameras == 0 then |
|
GameTestsPrint("No recorded 'reference' Cameras, could not complete test.") |
|
return |
|
end |
|
|
|
local ostime = os.time() |
|
local results = {} |
|
for i, cam in ipairs(cameras) do |
|
local logs_gt_src = "svnAssets/Logs/"..cam.id..".png" |
|
local logs_ref_src = "svnAssets/Logs/"..cam.id.."_"..ostime.."_reference.png" |
|
local logs_diff_src = "svnAssets/Logs/"..cam.id.."_"..ostime.."_diffResult.png" |
|
|
|
cam:ApplyProperties() |
|
cam:beginFunc() |
|
camera.Lock() |
|
Sleep(3500) |
|
|
|
AsyncCreatePath("svnAssets/Logs") |
|
local ref_img_path = "svnAssets/Tests/ReferenceImages/" |
|
local name = ref_img_path .. cam.id .. ".png" |
|
local err = AsyncCopyFile(name, logs_gt_src, "raw") |
|
if err then |
|
err = AsyncExec(string.format("svn update %s --set-depth infinity", ConvertToOSPath(ref_img_path)), true, true) |
|
if err then |
|
GameTestsErrorf("Reference images folder '%s' could not be updated. Reason: %s!", ConvertToOSPath(ref_img_path), err) |
|
return |
|
end |
|
err = AsyncExec(string.format("svn update %s --depth infinity", ConvertToOSPath(ref_img_path)), true, true) |
|
if err then |
|
GameTestsErrorf("Reference images folder '%s' could not be updated. Reason: %s!", ConvertToOSPath(ref_img_path), err) |
|
return |
|
end |
|
err = AsyncCopyFile(name, logs_gt_src, "raw") |
|
if err then |
|
GameTestsErrorf("Reference images could not be copied from Tests folder for '%s' --> '%s'. Reason: %s. Try increasing SVN update depth manually!", ConvertToOSPath(name), ConvertToOSPath(logs_gt_src), err) |
|
return |
|
end |
|
end |
|
|
|
AsyncFileDelete(logs_ref_src) |
|
WriteScreenshot(logs_ref_src, 512, 512) |
|
Sleep(300) |
|
|
|
local err, img_err = CompareImages( logs_gt_src, logs_ref_src, logs_diff_src, 4) |
|
if img_err then if img_err < allowedDifference then |
|
GameTestsErrorf("Image taken from "..cam.id.." is too different from reference image!") |
|
end end |
|
cam:endFunc() |
|
WaitNextFrame(1) |
|
table.insert(results, {id = cam.id, img_err = img_err}) |
|
end |
|
|
|
local newHTMLTable = {"<!doctype html>", |
|
"<head><style> table, th, td {border: 1px solid black;} </style>", |
|
"<title> Image report for Reference Cameras </title>", |
|
"<style type=\"text/css\">"} |
|
|
|
for i, img in ipairs(results) do |
|
local img_gt = string.format('"%s.png"',tostring(img.id)) |
|
local img_ref = string.format('"%s_%s_reference.png"',tostring(img.id), tostring(ostime)) |
|
|
|
table.iappend(newHTMLTable, |
|
{".class_", img.id, " {width: 512px; height: 512px;", |
|
"background: url(", img_gt, ") no-repeat;}", |
|
".class_", img.id, ":active {width: 512px; height: 512px;", |
|
"background: url(", img_ref, ") no-repeat;}", |
|
".class_", img.id, "_ref {width: 512px; height: 512px;", |
|
"background: url(", img_ref, ") no-repeat;}", |
|
".class_", img.id, "_ref:active {width: 512px; height: 512px;", |
|
"background: url(", img_gt, ") no-repeat;}", |
|
}) |
|
end |
|
|
|
table.iappend(newHTMLTable, |
|
{"</style> </head> <body> <table>", |
|
"<tr><th>Camera ID</th>", |
|
"<th>Image error metric</th>", |
|
"<th>Ground Truth</th>", |
|
"<th>Difference</th> ", |
|
"<th>New Image</th></tr>"}) |
|
|
|
for i,img in ipairs(results) do |
|
local str_for_color = " style=\"background-color:"..(img.img_err < allowedDifference and "#f76e59;\"" or "#92ed78;\"") |
|
local img_diff = string.format('"%s_%s_diffResult.png"',tostring(img.id), tostring(ostime)) |
|
table.iappend(newHTMLTable,{ |
|
"<tr><td><b>",img.id,"</b></td><td ",str_for_color,">", img.img_err, |
|
"</td><td><div class=\"class_", img.id,"\"></div></td>", |
|
"<td><img src=",img_diff, " alt=\" Difference image missing.\"></td>", |
|
"<td><div class=\"class_",img.id, "_ref\"> </div> </tr>"}) |
|
end |
|
table.insert(newHTMLTable,"</body></html>") |
|
|
|
AsyncCreatePath("svnAssets/Logs") |
|
local report_name = os.date("%Y-%m-%d_%H-%M-%S", os.time()) |
|
local err = AsyncStringToFile("svnAssets/Logs/reference_images_"..report_name..".html", table.concat(newHTMLTable)) |
|
|
|
GameTestsPrint("RULE(reference_images_" .. report_name .. ")") |
|
|
|
|
|
ChangeVideoMode(1680, 940, 0, false, false) |
|
SetMouseDeltaMode(false) |
|
camera.Unlock() |
|
end |
|
|
|
function GameTestsNightly.RenderingBenchmark() |
|
if not config.RenderingTestsMap then |
|
GameTestsPrint("config.RenderingTestsMap map not specified, skipping the test.") |
|
return |
|
end |
|
if not MapData[config.RenderingTestsMap] then |
|
GameTestsError(config.RenderingTestsMap, "map not found, could not complete test.") |
|
return |
|
end |
|
ChangeMap(config.RenderingTestsMap) |
|
ChangeVideoMode(1920, 1080, 0, false, false) |
|
WaitNextFrame(5) |
|
|
|
local num_shaders = GetNumShaders() |
|
GameTestAddReferenceValue("TotalNumberOfShaders", 0, num_shaders, "", 20, 100) |
|
|
|
local cameras = Presets.Camera["benchmark"] |
|
if not cameras or #cameras == 0 then |
|
GameTestsPrint("No recorded 'benchmark' Cameras, could not complete test.") |
|
return |
|
end |
|
|
|
local results = {} |
|
table.change(hr, "rendering_benchmark", { RenderStatsSmoothing = 30 }) |
|
for i, cam in pairs(cameras) do |
|
cam:ApplyProperties() |
|
Sleep(3000) |
|
|
|
local gpu_time = hr.RenderStatsFrameTimeGPU |
|
local cpu_time = hr.RenderStatsFrameTimeCPU |
|
local result = { |
|
time = os.time(), |
|
id = cam.id, |
|
gpu_time = gpu_time, |
|
cpu_time = cpu_time |
|
} |
|
table.insert (results, result) |
|
end |
|
table.restore(hr, "rendering_benchmark") |
|
|
|
for _, cameraResult in ipairs(results) do |
|
GameTestAddReferenceValue("RenderingBenchmarkCPU", cameraResult.id, cameraResult.cpu_time, "", 50, 1000) |
|
GameTestAddReferenceValue("RenderingBenchmarkGPU", cameraResult.id, cameraResult.gpu_time, "", 50, 1000) |
|
end |
|
end |
|
|
|
function TestNonInferedShaders(time, seed, verbose) |
|
if not config.RenderingTestsMap then |
|
GameTestsPrint("config.RenderingTestsMap not specified, skipping the test.") |
|
return |
|
end |
|
if not MapData[config.RenderingTestsMap] then |
|
GameTestsError(config.RenderingTestsMap, "map not found, could not complete test.") |
|
return |
|
end |
|
ChangeMap(config.RenderingTestsMap) |
|
WaitNextFrame(5) |
|
|
|
time = time or 5 * 60 * 1000 |
|
seed = seed or AsyncRand() |
|
GameTestsPrintf("TestNonInferedShaders: time %d, seed %d", time, seed) |
|
|
|
local options = {} |
|
for option, descr in pairs(OptionsData.Options) do |
|
if descr[1] and descr[1].hr then |
|
options[#options + 1] = descr |
|
end |
|
end |
|
|
|
local real_time_start = RealTime() |
|
local precise_time_start = GetPreciseTicks() |
|
local test = 0 |
|
local rand = BraidRandomCreate(seed) |
|
local orig_hr = {} |
|
while RealTime() - real_time_start < time do |
|
test = test + 1 |
|
GameTestsPrintf("Changing hr. options test #%d", test) |
|
local change_time = RealTime() |
|
local changed |
|
while not changed do |
|
for _, option_set in ipairs(options) do |
|
local entry = table.rand(option_set, rand()) |
|
for hr_key, hr_param in sorted_pairs(entry.hr) do |
|
if hr[hr_key] ~= hr_param then |
|
if verbose then |
|
GameTestsPrintf(" hr['%s'] = %s -- was %s", hr_key, hr_param, hr[hr_key]) |
|
end |
|
orig_hr[hr_key] = orig_hr[hr_key] or hr[hr_key] |
|
hr[hr_key] = hr_param |
|
changed = true |
|
end |
|
end |
|
end |
|
end |
|
WaitNextFrame(3) |
|
if verbose then |
|
GameTestsPrintf("done for %dms.", RealTime() - change_time) |
|
end |
|
end |
|
|
|
GameTestsPrintf("Restoring initial hr...") |
|
for hr_key, hr_param in sorted_pairs(orig_hr) do |
|
if verbose then |
|
GameTestsPrintf(" hr['%s'] = %s", hr_key, hr_param) |
|
end |
|
hr[hr_key] = hr_param |
|
end |
|
|
|
if verbose then |
|
GameTestsPrintf("Changing hr. options for %d mins finished.", time / (60 * 1000)) |
|
end |
|
end |
|
|
|
function GameTestsNightly.NonInferedShaders() |
|
TestNonInferedShaders() |
|
end |
|
|
|
function GameTests.TestDoesMapSavingGenerateFakeDeltas() |
|
if not config.AutoTestSaveMap then return end |
|
ChangeMap(config.AutoTestSaveMap) |
|
if GetMapName() ~= config.AutoTestSaveMap then |
|
GameTestsError("Failed to change map to " .. config.AutoTestSaveMap .. "! ") |
|
return |
|
end |
|
|
|
local p = "svnAssets/Source/Maps/" .. config.AutoTestSaveMap .. "/objects.lua" |
|
|
|
if not IsEditorActive() then |
|
EditorActivate() |
|
end |
|
SaveMap("no backup") |
|
EditorDeactivate() |
|
|
|
local _, str = SVNDiff(p) |
|
local diff = {} |
|
for s in str:gmatch("[^\r\n]+") do |
|
diff[#diff+1] = s |
|
if #diff == 20 then break end |
|
end |
|
if #diff > 0 then |
|
GameTestsError("Resaving " .. config.AutoTestSaveMap .. " produced differences!") |
|
GameTestsPrint(table.concat(diff, "\n")) |
|
end |
|
end |
|
|
|
|
|
function GameTests_LoadAnyMap() |
|
if GetMap() ~= "" then return end |
|
if not config.VideoSettingsMap then |
|
GameTestsError("Configure config.GameTestsMap to test presets - some preset validation tests may only run on a map") |
|
return |
|
end |
|
if GetMap() ~= config.VideoSettingsMap then |
|
CloseMenuDialogs() |
|
ChangeMap(config.VideoSettingsMap) |
|
WaitNextFrame() |
|
end |
|
end |
|
|
|
function GameTests.z8_ValidatePresetDataIntegrity() |
|
GameTests_LoadAnyMap() |
|
|
|
local orig_pairs = pairs |
|
pairs = g_old_pairs |
|
ValidatePresetDataIntegrity("validate_all", "game_tests", "verbose") |
|
pairs = orig_pairs |
|
end |
|
|
|
function GameTests.InGameEditors() |
|
if not config.EditorsToTest then return end |
|
|
|
PauseInfiniteLoopDetection("GameTests.InGameEditors") |
|
|
|
local time_started = GetPreciseTicks() |
|
local project = GetAppName() |
|
local function Test(editor_class) |
|
local waiting = CurrentThread() |
|
local worker = CreateRealTimeThread(function() |
|
local ged = OpenPresetEditor(editor_class) |
|
if ged then |
|
local err = ged:Send("rfnApp", "SaveAll", true) |
|
if err then |
|
GameTestsErrorf("%s:%s In-Game editor SaveAll(true) failed: %s", project, editor_class, tostring(err)) |
|
end |
|
err = ged:Send("rfnClose") |
|
else |
|
GameTestsErrorf("%s:%s In-Game editor opening failed", project, editor_class) |
|
end |
|
Wakeup(waiting) |
|
end) |
|
if not WaitWakeup(10000) then |
|
GameTestsErrorf("%s:%s In-Game editor test timeout", project, editor_class) |
|
DeleteThread(worker) |
|
end |
|
end |
|
if not config.EditorsToTestThrottle then |
|
parallel_foreach(config.EditorsToTest, Test, nil, 8) |
|
else |
|
for _, editor_class in ipairs(config.EditorsToTest) do |
|
Test(editor_class) |
|
Sleep(config.EditorsToTestThrottle) |
|
end |
|
end |
|
GameTestsPrintf("%s In-Game editors tests finished: %ds.", project, (GetPreciseTicks() - time_started) / 1000) |
|
|
|
ResumeInfiniteLoopDetection("GameTests.InGameEditors") |
|
end |
|
|
|
function ChangeVideoSettings_ViewPositions() end |
|
function GameTests.ChangeVideoSettings() |
|
if not config.VideoSettingsMap then return end |
|
local presets = {"Low", "Medium", "High", "Ultra"} |
|
if GetMap() ~= config.VideoSettingsMap then |
|
CloseMenuDialogs() |
|
ChangeMap(config.VideoSettingsMap) |
|
WaitNextFrame() |
|
end |
|
local orig = OptionsCreateAndLoad() |
|
for _, p in ipairs(presets) do |
|
GameTestsPrint("Video preset", p) |
|
ApplyVideoPreset(p) |
|
WaitNextFrame() |
|
ChangeVideoSettings_ViewPositions() |
|
end |
|
if orig then |
|
GameTestsPrint("Returning to the original preset", orig.VideoPreset) |
|
ApplyOptionsObj(orig) |
|
WaitNextFrame() |
|
end |
|
end |
|
|
|
function GameTests.EntityStatesMissingAnimations() |
|
if not g_AllEntities then |
|
GameTests_LoadAnyMap() |
|
end |
|
for entity_name in sorted_pairs(g_AllEntities) do |
|
local entity_spec = GetEntitySpec(entity_name, "expect_missing") |
|
if entity_spec then |
|
local entity_states = GetStates(entity_name) |
|
local state_specs = entity_spec:GetSpecSubitems("StateSpec", not "inherit") |
|
for _, state_name in pairs(entity_states) do |
|
local state_spec = state_specs[state_name] |
|
if state_spec and state_name:sub(1, 1) ~= "_" then |
|
local mesh_spec = entity_spec:GetMeshSpec(state_spec.mesh) |
|
local anim_name = GetEntityAnimName(entity_name, state_spec.name) |
|
if mesh_spec.animated and (not anim_name or anim_name == "") then |
|
GameTestsPrintf("State %s/%s is animated but has no exported animation!", entity_name, state_spec.name) |
|
end |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
function GameTests.EntityBillboards() |
|
GetBillboardEntities(GameTestsErrorf) |
|
end |
|
|
|
function GameTests.ValidateSounds() |
|
GenerateSoundMetadata("svnAssets/tmp/sndmeta-autotest.dat") |
|
end |
|
|
|
function CheckEntitySpots(entity) |
|
local meshes = {} |
|
for k, state in pairs( EnumValidStates(entity) ) do |
|
local mesh = GetStateMeshFile(entity, state) |
|
if mesh and not meshes[mesh] then |
|
meshes[mesh] = state |
|
end |
|
end |
|
for mesh, state in sorted_pairs(meshes) do |
|
local spbeg, spend = GetAllSpots(entity, state) |
|
local pos_map, pos_spots = {}, {} |
|
local pos_list = { GetEntitySpotPos(entity, state, 0, spbeg, spend) } |
|
for idx=spbeg, spend do |
|
local pos = pos_list[idx - spbeg + 1] |
|
local pos_hash = point_pack(pos) |
|
local spot_name = GetSpotName(entity, idx) |
|
local annotation = GetSpotAnnotation(entity, idx) or "" |
|
if annotation ~= "" then |
|
spot_name = spot_name .. " [" .. annotation .. "]" |
|
end |
|
local spot_names = pos_spots[pos_hash] or {} |
|
pos_spots[pos_hash] = spot_names |
|
if pos_map[pos_hash] and spot_names[spot_name] then |
|
table.insert(spot_names[spot_name], idx) |
|
else |
|
pos_map[pos_hash] = pos |
|
spot_names[spot_name] = {idx} |
|
end |
|
end |
|
for pos_hash, spot_names in sorted_pairs(pos_spots) do |
|
local pos = pos_map[pos_hash] |
|
for spot_name, spot_index_list in sorted_pairs(spot_names) do |
|
if #spot_index_list > 1 then |
|
GameTestsErrorf("%d duplicated spots %s.%s (%s) %s: %s", #spot_index_list, entity, spot_name, mesh, tostring(pos), table.concat(spot_index_list, ",")) |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
function GameTests.CheckSpots() |
|
if not g_AllEntities then |
|
GameTests_LoadAnyMap() |
|
end |
|
PauseInfiniteLoopDetection("CheckSpots") |
|
for entity in sorted_pairs(g_AllEntities) do |
|
CheckEntitySpots(entity) |
|
end |
|
ResumeInfiniteLoopDetection("CheckSpots") |
|
end |
|
|
|
function GameTests.z9_ResaveAllPresetsTest() |
|
ResetInteractionRand() |
|
ResaveAllPresetsTest("game_tests") |
|
end |
|
|