1 // Headless version of PPSSPP, for testing using http://code.google.com/p/pspautotests/ .
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.
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"
20 #include "LogManager.h"
21 #include "base/NativeApp.h"
22 #include "input/input_state.h"
23 #include "base/timeutil.h"
28 #include "Windows/OpenGLBase.h"
29 #include "WindowsHeadlessHost.h"
30 #include "WindowsHeadlessHostDx9.h"
33 // https://github.com/richq/android-ndk-profiler
34 #ifdef ANDROID_NDK_PROFILER
36 #include "android/android-ndk-profiler/prof.h"
39 class PrintfLogger
: public LogListener
42 void Log(LogTypes::LOG_LEVELS level
, const char *msg
)
46 case LogTypes::LVERBOSE
:
47 fprintf(stderr
, "V %s", msg
);
49 case LogTypes::LDEBUG
:
50 fprintf(stderr
, "D %s", msg
);
53 fprintf(stderr
, "I %s", msg
);
55 case LogTypes::LERROR
:
56 fprintf(stderr
, "E %s", msg
);
58 case LogTypes::LWARNING
:
59 fprintf(stderr
, "W %s", msg
);
61 case LogTypes::LNOTICE
:
63 fprintf(stderr
, "N %s", msg
);
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; }
84 InputState input_state
;
87 int printUsage(const char *progname
, const char *reason
)
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");
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");
117 static HeadlessHost
*getHost(GPUCore gpuCore
) {
120 return new HeadlessHost();
123 return new WindowsHeadlessHostDx9();
125 #ifdef HEADLESSHOST_CLASS
127 return new HEADLESSHOST_CLASS();
130 return new HeadlessHost();
135 bool RunAutoTest(HeadlessHost
*headlessHost
, CoreParameter
&coreParameter
, bool autoCompare
, bool verbose
, double timeout
)
138 // Kinda ugly, trying to guesstimate the test name from filename...
139 teamCityName
= GetTestName(coreParameter
.fileToStart
);
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());
154 TeamCityPrint("##teamcity[testStarted name='%s' captureStandardOutput='true']\n", teamCityName
.c_str());
159 headlessHost
->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter
.fileToStart
));
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();
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());
185 host
->SendDebugOutput("TIMEOUT\n");
186 TeamCityPrint("##teamcity[testFailed name='%s' message='Test timeout']\n", teamCityName
.c_str());
193 headlessHost
->FlushDebugOutput();
195 if (autoCompare
&& passed
)
196 passed
= CompareOutput(coreParameter
.fileToStart
, output
, verbose
);
198 TeamCityPrint("##teamcity[testFinished name='%s']\n", teamCityName
.c_str());
203 int main(int argc
, const char* argv
[])
207 #ifdef ANDROID_NDK_PROFILER
208 setenv("CPUPROFILE_FREQUENCY", "500", 1);
209 setenv("CPUPROFILE", "/sdcard/gmon.out", 1);
210 monstartup("ppsspp_headless");
213 bool fullLog
= false;
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"))
231 return printUsage(argv
[0], "Missing argument after -m");
234 else if (!strcmp(argv
[i
], "-r") || !strcmp(argv
[i
], "--root"))
237 return printUsage(argv
[0], "Missing argument after -r");
240 else if (!strcmp(argv
[i
], "-l") || !strcmp(argv
[i
], "--log"))
242 else if (!strcmp(argv
[i
], "-i"))
244 else if (!strcmp(argv
[i
], "-j"))
246 else if (!strcmp(argv
[i
], "-c") || !strcmp(argv
[i
], "--compare"))
248 else if (!strcmp(argv
[i
], "-v") || !strcmp(argv
[i
], "--verbose"))
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"))
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"))
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"))
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"))
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
);
278 testFilenames
.push_back(argv
[i
]);
281 // TODO: Allow a filename here?
282 if (testFilenames
.size() == 1 && testFilenames
[0] == "@-")
284 testFilenames
.clear();
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
);
298 std::string error_string
;
299 bool glWorking
= host
->InitGraphics(&error_string
);
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;
338 g_Config
.iAnisotropyLevel
= 0;
340 g_Config
.iAnisotropyLevel
= 8;
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;
362 InitSysDirectories();
366 #elif defined(BLACKBERRY) || defined(__SYMBIAN32__)
367 #elif !defined(_WIN32)
368 g_Config
.memStickDirectory
= std::string(getenv("HOME")) + "/.ppsspp/";
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
);
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/"));
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
];
403 printf("%s:\n", coreParameter
.fileToStart
.c_str());
404 bool passed
= RunAutoTest(headlessHost
, coreParameter
, autoCompare
, verbose
, timeout
);
407 std::string testName
= GetTestName(coreParameter
.fileToStart
);
410 passedTests
.push_back(testName
);
411 printf(" %s - passed!\n", testName
.c_str());
414 failedTests
.push_back(testName
);
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();
435 #ifdef ANDROID_NDK_PROFILER