Merge pull request #90 from gizmo98/patch-2
[libretro-ppsspp.git] / headless / Headless.cpp
blobd15a881830e7f05168655e01918d6a984785377a
1 // Headless version of PPSSPP, for testing using http://code.google.com/p/pspautotests/ .
2 // See headless.txt.
3 // To build on non-windows systems, just run CMake in the SDL directory, it will build both a normal ppsspp and the headless version.
5 #include <cstdio>
6 #include <cstdlib>
7 #include <limits>
9 #include "file/zip_read.h"
10 #include "profiler/profiler.h"
11 #include "Common/FileUtil.h"
12 #include "Core/Config.h"
13 #include "Core/Core.h"
14 #include "Core/CoreTiming.h"
15 #include "Core/System.h"
16 #include "Core/HLE/sceUtility.h"
17 #include "Core/Host.h"
18 #include "Core/SaveState.h"
19 #include "Log.h"
20 #include "LogManager.h"
21 #include "base/NativeApp.h"
22 #include "input/input_state.h"
23 #include "base/timeutil.h"
25 #include "Compare.h"
26 #include "StubHost.h"
27 #ifdef _WIN32
28 #include "Windows/OpenGLBase.h"
29 #include "WindowsHeadlessHost.h"
30 #include "WindowsHeadlessHostDx9.h"
31 #endif
33 // https://github.com/richq/android-ndk-profiler
34 #ifdef ANDROID_NDK_PROFILER
35 #include <stdlib.h>
36 #include "android/android-ndk-profiler/prof.h"
37 #endif
39 class PrintfLogger : public LogListener
41 public:
42 void Log(LogTypes::LOG_LEVELS level, const char *msg)
44 switch (level)
46 case LogTypes::LVERBOSE:
47 fprintf(stderr, "V %s", msg);
48 break;
49 case LogTypes::LDEBUG:
50 fprintf(stderr, "D %s", msg);
51 break;
52 case LogTypes::LINFO:
53 fprintf(stderr, "I %s", msg);
54 break;
55 case LogTypes::LERROR:
56 fprintf(stderr, "E %s", msg);
57 break;
58 case LogTypes::LWARNING:
59 fprintf(stderr, "W %s", msg);
60 break;
61 case LogTypes::LNOTICE:
62 default:
63 fprintf(stderr, "N %s", msg);
64 break;
69 struct InputState;
70 // Temporary hacks around annoying linking errors.
71 void D3D9_SwapBuffers() { }
72 void GL_SwapBuffers() { }
73 void NativeUpdate(InputState &input_state) { }
74 void NativeRender() { }
75 void NativeResized() { }
76 void NativeMessageReceived(const char *message, const char *value) {}
78 std::string System_GetProperty(SystemProperty prop) { return ""; }
79 int System_GetPropertyInt(SystemProperty prop) { return -1; }
80 void System_SendMessage(const char *command, const char *parameter) {}
81 bool System_InputBoxGetWString(const wchar_t *title, const std::wstring &defaultvalue, std::wstring &outvalue) { return false; }
83 #ifndef _WIN32
84 InputState input_state;
85 #endif
87 int printUsage(const char *progname, const char *reason)
89 if (reason != NULL)
90 fprintf(stderr, "Error: %s\n\n", reason);
91 fprintf(stderr, "PPSSPP Headless\n");
92 fprintf(stderr, "This is primarily meant as a non-interactive test tool.\n\n");
93 fprintf(stderr, "Usage: %s file.elf... [options]\n\n", progname);
94 fprintf(stderr, "Options:\n");
95 fprintf(stderr, " -m, --mount umd.cso mount iso on umd1:\n");
96 fprintf(stderr, " -r, --root some/path mount path on host0: (elfs must be in here)\n");
97 fprintf(stderr, " -l, --log full log output, not just emulated printfs\n");
99 #if defined(HEADLESSHOST_CLASS)
101 fprintf(stderr, " --graphics=BACKEND use the full gpu backend (slower)\n");
102 fprintf(stderr, " options: gles, software, directx9\n");
103 fprintf(stderr, " --screenshot=FILE compare against a screenshot\n");
105 #endif
106 fprintf(stderr, " --timeout=SECONDS abort test it if takes longer than SECONDS\n");
108 fprintf(stderr, " -v, --verbose show the full passed/failed result\n");
109 fprintf(stderr, " -i use the interpreter\n");
110 fprintf(stderr, " -j use jit (default)\n");
111 fprintf(stderr, " -c, --compare compare with output in file.expected\n");
112 fprintf(stderr, "\nSee headless.txt for details.\n");
114 return 1;
117 static HeadlessHost *getHost(GPUCore gpuCore) {
118 switch (gpuCore) {
119 case GPU_NULL:
120 return new HeadlessHost();
121 #ifdef _WIN32
122 case GPU_DIRECTX9:
123 return new WindowsHeadlessHostDx9();
124 #endif
125 #ifdef HEADLESSHOST_CLASS
126 default:
127 return new HEADLESSHOST_CLASS();
128 #else
129 default:
130 return new HeadlessHost();
131 #endif
135 bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, bool autoCompare, bool verbose, double timeout)
137 if (teamCityMode) {
138 // Kinda ugly, trying to guesstimate the test name from filename...
139 teamCityName = GetTestName(coreParameter.fileToStart);
142 std::string output;
143 if (autoCompare)
144 coreParameter.collectEmuLog = &output;
146 std::string error_string;
147 if (!PSP_Init(coreParameter, &error_string)) {
148 fprintf(stderr, "Failed to start %s. Error: %s\n", coreParameter.fileToStart.c_str(), error_string.c_str());
149 printf("TESTERROR\n");
150 TeamCityPrint("##teamcity[testIgnored name='%s' message='PRX/ELF missing']\n", teamCityName.c_str());
151 return false;
154 TeamCityPrint("##teamcity[testStarted name='%s' captureStandardOutput='true']\n", teamCityName.c_str());
156 host->BootDone();
158 if (autoCompare)
159 headlessHost->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter.fileToStart));
161 time_update();
162 bool passed = true;
163 // TODO: We must have some kind of stack overflow or we're not following the ABI right.
164 // This gets trashed if it's not static.
165 static double deadline;
166 deadline = time_now() + timeout;
168 coreState = CORE_RUNNING;
169 while (coreState == CORE_RUNNING)
171 int blockTicks = usToCycles(1000000 / 10);
172 PSP_RunLoopFor(blockTicks);
174 // If we were rendering, this might be a nice time to do something about it.
175 if (coreState == CORE_NEXTFRAME) {
176 coreState = CORE_RUNNING;
177 headlessHost->SwapBuffers();
179 time_update();
180 if (time_now_d() > deadline) {
181 // Don't compare, print the output at least up to this point, and bail.
182 printf("%s", output.c_str());
183 passed = false;
185 host->SendDebugOutput("TIMEOUT\n");
186 TeamCityPrint("##teamcity[testFailed name='%s' message='Test timeout']\n", teamCityName.c_str());
187 Core_Stop();
191 PSP_Shutdown();
193 headlessHost->FlushDebugOutput();
195 if (autoCompare && passed)
196 passed = CompareOutput(coreParameter.fileToStart, output, verbose);
198 TeamCityPrint("##teamcity[testFinished name='%s']\n", teamCityName.c_str());
200 return passed;
203 int main(int argc, const char* argv[])
205 PROFILE_INIT();
207 #ifdef ANDROID_NDK_PROFILER
208 setenv("CPUPROFILE_FREQUENCY", "500", 1);
209 setenv("CPUPROFILE", "/sdcard/gmon.out", 1);
210 monstartup("ppsspp_headless");
211 #endif
213 bool fullLog = false;
214 bool useJit = true;
215 bool autoCompare = false;
216 bool verbose = false;
217 const char *stateToLoad = 0;
218 GPUCore gpuCore = GPU_NULL;
220 std::vector<std::string> testFilenames;
221 const char *mountIso = 0;
222 const char *mountRoot = 0;
223 const char *screenshotFilename = 0;
224 float timeout = std::numeric_limits<float>::infinity();
226 for (int i = 1; i < argc; i++)
228 if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--mount"))
230 if (++i >= argc)
231 return printUsage(argv[0], "Missing argument after -m");
232 mountIso = argv[i];
234 else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--root"))
236 if (++i >= argc)
237 return printUsage(argv[0], "Missing argument after -r");
238 mountRoot = argv[i];
240 else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--log"))
241 fullLog = true;
242 else if (!strcmp(argv[i], "-i"))
243 useJit = false;
244 else if (!strcmp(argv[i], "-j"))
245 useJit = true;
246 else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--compare"))
247 autoCompare = true;
248 else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
249 verbose = true;
250 else if (!strncmp(argv[i], "--graphics=", strlen("--graphics=")) && strlen(argv[i]) > strlen("--graphics="))
252 const char *gpuName = argv[i] + strlen("--graphics=");
253 if (!strcasecmp(gpuName, "gles"))
254 gpuCore = GPU_GLES;
255 else if (!strcasecmp(gpuName, "software"))
256 gpuCore = GPU_SOFTWARE;
257 else if (!strcasecmp(gpuName, "directx9"))
258 gpuCore = GPU_DIRECTX9;
259 else if (!strcasecmp(gpuName, "null"))
260 gpuCore = GPU_NULL;
261 else
262 return printUsage(argv[0], "Unknown gpu backend specified after --graphics=");
264 // Default to GLES if no value selected.
265 else if (!strcmp(argv[i], "--graphics"))
266 gpuCore = GPU_GLES;
267 else if (!strncmp(argv[i], "--screenshot=", strlen("--screenshot=")) && strlen(argv[i]) > strlen("--screenshot="))
268 screenshotFilename = argv[i] + strlen("--screenshot=");
269 else if (!strncmp(argv[i], "--timeout=", strlen("--timeout=")) && strlen(argv[i]) > strlen("--timeout="))
270 timeout = strtod(argv[i] + strlen("--timeout="), NULL);
271 else if (!strcmp(argv[i], "--teamcity"))
272 teamCityMode = true;
273 else if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state="))
274 stateToLoad = argv[i] + strlen("--state=");
275 else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
276 return printUsage(argv[0], NULL);
277 else
278 testFilenames.push_back(argv[i]);
281 // TODO: Allow a filename here?
282 if (testFilenames.size() == 1 && testFilenames[0] == "@-")
284 testFilenames.clear();
285 char temp[2048];
286 temp[2047] = '\0';
288 while (scanf("%2047s", temp) == 1)
289 testFilenames.push_back(temp);
292 if (testFilenames.empty())
293 return printUsage(argv[0], argc <= 1 ? NULL : "No executables specified");
295 HeadlessHost *headlessHost = getHost(gpuCore);
296 host = headlessHost;
298 std::string error_string;
299 bool glWorking = host->InitGraphics(&error_string);
301 LogManager::Init();
302 LogManager *logman = LogManager::GetInstance();
304 PrintfLogger *printfLogger = new PrintfLogger();
306 for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; i++)
308 LogTypes::LOG_TYPE type = (LogTypes::LOG_TYPE)i;
309 logman->SetEnable(type, fullLog);
310 logman->SetLogLevel(type, LogTypes::LDEBUG);
311 logman->AddListener(type, printfLogger);
314 CoreParameter coreParameter;
315 coreParameter.cpuCore = useJit ? CPU_JIT : CPU_INTERPRETER;
316 coreParameter.gpuCore = glWorking ? gpuCore : GPU_NULL;
317 coreParameter.enableSound = false;
318 coreParameter.mountIso = mountIso ? mountIso : "";
319 coreParameter.mountRoot = mountRoot ? mountRoot : "";
320 coreParameter.startPaused = false;
321 coreParameter.printfEmuLog = !autoCompare;
322 coreParameter.headLess = true;
323 coreParameter.renderWidth = 480;
324 coreParameter.renderHeight = 272;
325 coreParameter.pixelWidth = 480;
326 coreParameter.pixelHeight = 272;
327 coreParameter.unthrottle = true;
329 g_Config.bEnableSound = false;
330 g_Config.bFirstRun = false;
331 g_Config.bIgnoreBadMemAccess = true;
332 // Never report from tests.
333 g_Config.sReportHost = "";
334 g_Config.bAutoSaveSymbolMap = false;
335 g_Config.iRenderingMode = 1;
336 g_Config.bHardwareTransform = true;
337 #ifdef USING_GLES2
338 g_Config.iAnisotropyLevel = 0;
339 #else
340 g_Config.iAnisotropyLevel = 8;
341 #endif
342 g_Config.bVertexCache = true;
343 g_Config.bTrueColor = true;
344 g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
345 g_Config.iTimeFormat = PSP_SYSTEMPARAM_TIME_FORMAT_24HR;
346 g_Config.bEncryptSave = true;
347 g_Config.sNickName = "shadow";
348 g_Config.iTimeZone = 60;
349 g_Config.iDateFormat = PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY;
350 g_Config.iButtonPreference = PSP_SYSTEMPARAM_BUTTON_CROSS;
351 g_Config.iLockParentalLevel = 9;
352 g_Config.iInternalResolution = 1;
353 g_Config.bFrameSkipUnthrottle = false;
354 g_Config.bEnableLogging = fullLog;
355 g_Config.iNumWorkerThreads = 1;
356 g_Config.bSoftwareSkinning = true;
357 g_Config.bVertexDecoderJit = true;
358 g_Config.bBlockTransferGPU = true;
359 g_Config.bSetRoundingMode = true;
361 #ifdef _WIN32
362 InitSysDirectories();
363 #endif
365 #if defined(ANDROID)
366 #elif defined(BLACKBERRY) || defined(__SYMBIAN32__)
367 #elif !defined(_WIN32)
368 g_Config.memStickDirectory = std::string(getenv("HOME")) + "/.ppsspp/";
369 #endif
371 // Try to find the flash0 directory. Often this is from a subdirectory.
372 for (int i = 0; i < 3; ++i)
374 if (!File::Exists(g_Config.flash0Directory))
375 g_Config.flash0Directory += "../../flash0/";
377 // Or else, maybe in the executable's dir.
378 if (!File::Exists(g_Config.flash0Directory))
379 g_Config.flash0Directory = File::GetExeDirectory() + "flash0/";
381 if (screenshotFilename != 0)
382 headlessHost->SetComparisonScreenshot(screenshotFilename);
384 #ifdef ANDROID
385 // For some reason the debugger installs it with this name?
386 if (File::Exists("/data/app/org.ppsspp.ppsspp-2.apk")) {
387 VFSRegister("", new ZipAssetReader("/data/app/org.ppsspp.ppsspp-2.apk", "assets/"));
389 if (File::Exists("/data/app/org.ppsspp.ppsspp.apk")) {
390 VFSRegister("", new ZipAssetReader("/data/app/org.ppsspp.ppsspp.apk", "assets/"));
392 #endif
394 if (stateToLoad != NULL)
395 SaveState::Load(stateToLoad);
397 std::vector<std::string> failedTests;
398 std::vector<std::string> passedTests;
399 for (size_t i = 0; i < testFilenames.size(); ++i)
401 coreParameter.fileToStart = testFilenames[i];
402 if (autoCompare)
403 printf("%s:\n", coreParameter.fileToStart.c_str());
404 bool passed = RunAutoTest(headlessHost, coreParameter, autoCompare, verbose, timeout);
405 if (autoCompare)
407 std::string testName = GetTestName(coreParameter.fileToStart);
408 if (passed)
410 passedTests.push_back(testName);
411 printf(" %s - passed!\n", testName.c_str());
413 else
414 failedTests.push_back(testName);
418 if (autoCompare)
420 printf("%d tests passed, %d tests failed.\n", (int)passedTests.size(), (int)failedTests.size());
421 if (!failedTests.empty())
423 printf("Failed tests:\n");
424 for (size_t i = 0; i < failedTests.size(); ++i) {
425 printf(" %s\n", failedTests[i].c_str());
430 host->ShutdownGraphics();
431 delete host;
432 host = NULL;
433 headlessHost = NULL;
435 #ifdef ANDROID_NDK_PROFILER
436 moncleanup();
437 #endif
439 return 0;