Merge pull request #3804 from unknownbrackets/headless

Implement --compare in headless
This commit is contained in:
Henrik Rydgård 2013-09-16 14:36:26 -07:00
commit 39c640d3ab
10 changed files with 307 additions and 40 deletions

View File

@ -24,6 +24,7 @@
#include "Core/CoreTiming.h"
#include "Core/Reporting.h"
#include "Core/Config.h"
#include "Core/HW/MediaEngine.h"
#include "Common/ChunkFile.h"
#include "sceKernel.h"
@ -1089,6 +1090,8 @@ int __AtracSetContext(Atrac *atrac)
}
#ifdef USE_FFMPEG
InitFFmpeg();
u8* tempbuf = (u8*)av_malloc(atrac->atracBufSize);
atrac->pFormatCtx = avformat_alloc_context();

View File

@ -20,7 +20,7 @@
#include "Core/HLE/sceMp3.h"
#include "Core/HW/MediaEngine.h"
#include "Core/Reporting.h"
#include "../HW/MediaEngine.h"
#include "Core/HW/MediaEngine.h"
#ifdef USE_FFMPEG
#ifndef PRId64
@ -337,6 +337,8 @@ int sceMp3Init(u32 mp3) {
ctx->mp3Version = ((header >> 19) & 0x3);
#ifdef USE_FFMPEG
InitFFmpeg();
u8* avio_buffer = static_cast<u8*>(av_malloc(ctx->mp3BufSize));
ctx->avio_context = avio_alloc_context(avio_buffer, ctx->mp3BufSize, 0, ctx, readFunc, NULL, NULL);
ctx->avformat_context = avformat_alloc_context();

View File

@ -83,6 +83,23 @@ static int getPixelFormatBytes(int pspFormat)
}
}
void ffmpeg_logger(void *, int, const char *format, va_list va_args) {
char tmp[1024];
vsprintf(tmp, format, va_args);
INFO_LOG(ME, "%s", tmp);
}
bool InitFFmpeg() {
#ifdef _DEBUG
av_log_set_level(AV_LOG_VERBOSE);
#else
av_log_set_level(AV_LOG_ERROR);
#endif
av_log_set_callback(&ffmpeg_logger);
return true;
}
MediaEngine::MediaEngine(): m_pdata(0) {
m_pFormatCtx = 0;
m_pCodecCtx = 0;
@ -192,21 +209,10 @@ int _MpegReadbuffer(void *opaque, uint8_t *buf, int buf_size)
return size;
}
#ifdef _DEBUG
void ffmpeg_logger(void *, int, const char *format, va_list va_args) {
char tmp[1024];
vsprintf(tmp, format, va_args);
INFO_LOG(ME, "%s", tmp);
}
#endif
bool MediaEngine::openContext() {
#ifdef USE_FFMPEG
InitFFmpeg();
#ifdef _DEBUG
av_log_set_level(AV_LOG_VERBOSE);
av_log_set_callback(&ffmpeg_logger);
#endif
if (m_pFormatCtx || !m_pdata)
return false;
m_mpegheaderReadPos = 0;

View File

@ -41,6 +41,8 @@ inline s64 getMpegTimeStamp(u8* buf) {
| ((s64)buf[1] << 32) | ((s64)buf[0] << 36);
}
bool InitFFmpeg();
class MediaEngine
{
public:

View File

@ -15,22 +15,182 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Compare.h"
#include "FileUtil.h"
#include "headless/Compare.h"
#include "Common/FileUtil.h"
#include "Core/Host.h"
#include <math.h>
#include <cmath>
#include <cstdarg>
#include <iostream>
#include <fstream>
bool CompareOutput(const std::string bootFilename)
bool teamCityMode = false;
std::string teamCityName = "";
void TeamCityPrint(const char *fmt, ...)
{
std::string expect_filename = bootFilename.substr(bootFilename.length() - 4) + ".expected";
if (File::Exists(expect_filename))
{
// TODO: Do the compare here
if (!teamCityMode)
return;
const int TEMP_BUFFER_SIZE = 32768;
char temp[TEMP_BUFFER_SIZE];
va_list args;
va_start(args, fmt);
vsnprintf(temp, TEMP_BUFFER_SIZE - 1, fmt, args);
temp[TEMP_BUFFER_SIZE - 1] = '\0';
va_end(args);
printf("%s", temp);
}
struct BufferedLineReader {
const static int MAX_BUFFER = 5;
const static int TEMP_BUFFER_SIZE = 32768;
BufferedLineReader(const std::string &data) : valid_(0), data_(data), pos_(0) {
}
void Fill() {
while (valid_ < MAX_BUFFER && HasLines()) {
buffer_[valid_++] = ReadLine();
}
}
const std::string Peek(int pos) {
if (pos >= valid_) {
Fill();
}
if (pos >= valid_) {
return "";
}
return buffer_[pos];
}
void Skip(int count) {
if (count > valid_) {
count = valid_;
}
valid_ -= count;
for (int i = 0; i < valid_; ++i) {
buffer_[i] = buffer_[i + count];
}
Fill();
}
const std::string Consume() {
const std::string result = Peek(0);
Skip(1);
return result;
}
virtual bool HasLines() {
return pos_ != data_.npos;
}
bool Compare(BufferedLineReader &other) {
if (Peek(0) != other.Peek(0)) {
return false;
}
Skip(1);
other.Skip(1);
return true;
}
protected:
BufferedLineReader() : valid_(0) {
}
virtual std::string ReadLine() {
size_t next = data_.find('\n', pos_);
if (next == data_.npos) {
std::string result = data_.substr(pos_);
pos_ = next;
return result;
} else {
std::string result = data_.substr(pos_, next - pos_);
pos_ = next + 1;
return result;
}
}
int valid_;
std::string buffer_[MAX_BUFFER];
const std::string data_;
size_t pos_;
};
struct BufferedLineReaderFile : public BufferedLineReader {
BufferedLineReaderFile(std::ifstream &in) : BufferedLineReader(), in_(in) {
}
virtual bool HasLines() {
return !in_.eof();
}
protected:
virtual std::string ReadLine() {
char temp[TEMP_BUFFER_SIZE];
in_.getline(temp, TEMP_BUFFER_SIZE);
return temp;
}
std::ifstream &in_;
};
bool CompareOutput(const std::string &bootFilename, const std::string &output)
{
std::string expect_filename = bootFilename.substr(0, bootFilename.length() - 4) + ".expected";
std::ifstream in;
in.open(expect_filename.c_str(), std::ios::in);
if (!in.fail())
{
BufferedLineReaderFile expected(in);
BufferedLineReader actual(output);
bool failed = false;
while (expected.HasLines())
{
if (expected.Compare(actual))
continue;
if (!failed)
{
TeamCityPrint("##teamcity[testFailed name='%s' message='Output different from expected file']\n", teamCityName.c_str());
failed = true;
}
// This is a really dirt simple comparing algorithm.
// Perhaps it was an extra line?
if (expected.Peek(0) == actual.Peek(1))
printf("+ %s\n", actual.Consume().c_str());
// A single missing line?
else if (expected.Peek(1) == actual.Peek(0))
printf("- %s\n", expected.Consume().c_str());
else
{
printf("O %s\n", actual.Consume().c_str());
printf("E %s\n", expected.Consume().c_str());
}
}
while (actual.HasLines())
{
// If it's a blank line, this will pass.
if (actual.Compare(expected))
continue;
printf("+ %s\n", actual.Consume().c_str());
}
return failed;
}
else
{
fprintf(stderr, "Expectation file %s not found", expect_filename.c_str());
fprintf(stderr, "Expectation file %s not found\n", expect_filename.c_str());
TeamCityPrint("##teamcity[testIgnored name='%s' message='Expects file missing']\n", teamCityName.c_str());
return false;
}
}

View File

@ -19,5 +19,9 @@
#include "Globals.h"
bool CompareOutput(std::string bootFilename);
extern bool teamCityMode;
extern std::string teamCityName;
void TeamCityPrint(const char *fmt, ...);
bool CompareOutput(const std::string &bootFilename, const std::string &output);
double CompareScreenshot(const u8 *pixels, int w, int h, int stride, const std::string screenshotFilename, std::string &error);

View File

@ -2,7 +2,9 @@
// See headless.txt.
// To build on non-windows systems, just run CMake in the SDL directory, it will build both a normal ppsspp and the headless version.
#include <stdio.h>
#include <cstdio>
#include <cstdlib>
#include <limits>
#include "Core/Config.h"
#include "Core/Core.h"
@ -14,6 +16,7 @@
#include "LogManager.h"
#include "base/NativeApp.h"
#include "input/input_state.h"
#include "base/timeutil.h"
#include "Compare.h"
#include "StubHost.h"
@ -84,6 +87,7 @@ void printUsage(const char *progname, const char *reason)
fprintf(stderr, " options: gles, software, directx9\n");
fprintf(stderr, " --screenshot=FILE compare against a screenshot\n");
}
fprintf(stderr, " --timeout=SECONDS abort test it if takes longer than SECONDS\n");
fprintf(stderr, " -i use the interpreter\n");
fprintf(stderr, " -j use jit (default)\n");
@ -102,6 +106,27 @@ static HeadlessHost * getHost(GPUCore gpuCore) {
}
}
static std::string ChopFront(std::string s, std::string front)
{
if (s.size() >= front.size())
{
if (s.substr(0, front.size()) == front)
return s.substr(front.size());
}
return s;
}
static std::string ChopEnd(std::string s, std::string end)
{
if (s.size() >= end.size())
{
size_t endpos = s.size() - end.size();
if (s.substr(endpos) == end)
return s.substr(0, endpos);
}
return s;
}
int main(int argc, const char* argv[])
{
bool fullLog = false;
@ -113,6 +138,7 @@ int main(int argc, const char* argv[])
const char *mountIso = 0;
const char *screenshotFilename = 0;
bool readMount = false;
double timeout = -1.0;
for (int i = 1; i < argc; i++)
{
@ -154,6 +180,10 @@ int main(int argc, const char* argv[])
gpuCore = GPU_GLES;
else if (!strncmp(argv[i], "--screenshot=", strlen("--screenshot=")) && strlen(argv[i]) > strlen("--screenshot="))
screenshotFilename = argv[i] + strlen("--screenshot=");
else if (!strncmp(argv[i], "--timeout=", strlen("--timeout=")) && strlen(argv[i]) > strlen("--timeout="))
timeout = strtod(argv[i] + strlen("--timeout="), NULL);
else if (!strcmp(argv[i], "--teamcity"))
teamCityMode = true;
else if (bootFilename == 0)
bootFilename = argv[i];
else
@ -199,6 +229,8 @@ int main(int argc, const char* argv[])
logman->AddListener(type, printfLogger);
}
std::string output;
CoreParameter coreParameter;
coreParameter.cpuCore = useJit ? CPU_JIT : CPU_INTERPRETER;
coreParameter.gpuCore = glWorking ? gpuCore : GPU_NULL;
@ -207,7 +239,9 @@ int main(int argc, const char* argv[])
coreParameter.mountIso = mountIso ? mountIso : "";
coreParameter.startPaused = false;
coreParameter.enableDebugging = false;
coreParameter.printfEmuLog = true;
coreParameter.printfEmuLog = !autoCompare;
if (autoCompare)
coreParameter.collectEmuLog = &output;
coreParameter.headLess = true;
coreParameter.renderWidth = 480;
coreParameter.renderHeight = 272;
@ -249,17 +283,29 @@ int main(int argc, const char* argv[])
g_Config.flashDirectory = g_Config.memCardDirectory+"/flash/";
#endif
if (teamCityMode) {
// Kinda ugly, trying to guesstimate the test name from filename...
teamCityName = ChopEnd(ChopFront(ChopFront(bootFilename, "tests/"), "pspautotests/tests/"), ".prx");
}
if (!PSP_Init(coreParameter, &error_string)) {
fprintf(stderr, "Failed to start %s. Error: %s\n", coreParameter.fileToStart.c_str(), error_string.c_str());
printf("TESTERROR\n");
TeamCityPrint("##teamcity[testIgnored name='%s' message='PRX/ELF missing']\n", teamCityName.c_str());
return 1;
}
TeamCityPrint("##teamcity[testStarted name='%s' captureStandardOutput='true']\n", teamCityName.c_str());
host->BootDone();
if (screenshotFilename != 0)
headlessHost->SetComparisonScreenshot(screenshotFilename);
time_update();
bool doCompare = true;
double deadline = timeout < 0.0 ? std::numeric_limits<float>::infinity() : time_now() + timeout;
coreState = CORE_RUNNING;
while (coreState == CORE_RUNNING)
{
@ -271,17 +317,31 @@ int main(int argc, const char* argv[])
coreState = CORE_RUNNING;
headlessHost->SwapBuffers();
}
time_update();
if (time_now() > deadline) {
// Don't compare, print the output at least up to this point, and bail.
printf("%s", output.c_str());
doCompare = false;
host->SendDebugOutput("TIMEOUT\n");
TeamCityPrint("##teamcity[testFailed name='%s' message='Test timeout']\n", teamCityName.c_str());
Core_Stop();
}
}
host->ShutdownGL();
PSP_Shutdown();
headlessHost->FlushDebugOutput();
delete host;
host = NULL;
headlessHost = NULL;
if (autoCompare)
CompareOutput(bootFilename);
if (autoCompare && doCompare)
CompareOutput(bootFilename, output);
TeamCityPrint("##teamcity[testFinished name='%s']\n", teamCityName.c_str());
return 0;
}

View File

@ -46,11 +46,29 @@ public:
virtual bool IsDebuggingEnabled() {return false;}
virtual bool AttemptLoadSymbolMap() {return false;}
virtual void SendDebugOutput(const std::string &output) { fwrite(output.data(), sizeof(char), output.length(), stdout); }
virtual void SendDebugOutput(const std::string &output) {
if (output.find('\n') != output.npos) {
DoFlushDebugOutput();
fwrite(output.data(), sizeof(char), output.length(), stdout);
} else {
debugOutputBuffer_ += output;
}
}
virtual void FlushDebugOutput() {
DoFlushDebugOutput();
}
inline void DoFlushDebugOutput() {
if (!debugOutputBuffer_.empty()) {
fwrite(debugOutputBuffer_.data(), sizeof(char), debugOutputBuffer_.length(), stdout);
debugOutputBuffer_.clear();
}
}
virtual void SetComparisonScreenshot(const std::string &filename) {}
// Unique for HeadlessHost
virtual void SwapBuffers() {}
protected:
std::string debugOutputBuffer_;
};

View File

@ -22,6 +22,9 @@
#include "Common/CommonWindows.h"
#include <io.h>
#include "Core/CoreParameter.h"
#include "Core/System.h"
#include "base/logging.h"
#include "gfx_es2/gl_state.h"
#include "gfx/gl_common.h"
@ -72,10 +75,6 @@ void SetVSync(int value)
void WindowsHeadlessHost::LoadNativeAssets()
{
// Native is kinda talkative, but that's annoying in our case.
out = _fdopen(_dup(_fileno(stdout)), "wt");
freopen("NUL", "wt", stdout);
VFSRegister("", new DirectoryAssetReader("assets/"));
VFSRegister("", new DirectoryAssetReader(""));
VFSRegister("", new DirectoryAssetReader("../"));
@ -83,16 +82,24 @@ void WindowsHeadlessHost::LoadNativeAssets()
VFSRegister("", new DirectoryAssetReader("../Windows/"));
gl_lost_manager_init();
// See SendDebugOutput() for how things get back on track.
}
void WindowsHeadlessHost::SendDebugOutput(const std::string &output)
{
fwrite(output.data(), sizeof(char), output.length(), out);
fwrite(output.data(), sizeof(char), output.length(), stdout);
OutputDebugStringUTF8(output.c_str());
}
void WindowsHeadlessHost::SendOrCollectDebugOutput(const std::string &data)
{
if (PSP_CoreParameter().printfEmuLog)
SendDebugOutput(data);
else if (PSP_CoreParameter().collectEmuLog)
*PSP_CoreParameter().collectEmuLog += data;
else
DEBUG_LOG(COMMON, "%s", data.c_str());
}
void WindowsHeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h)
{
// We ignore the current framebuffer parameters and just grab the full screen.
@ -100,19 +107,24 @@ void WindowsHeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h)
const static int FRAME_HEIGHT = 272;
u8 *pixels = new u8[FRAME_WIDTH * FRAME_HEIGHT * 4];
// TODO: Maybe this code should be moved into GLES_GPU.
// TODO: Maybe this code should be moved into GLES_GPU to support DirectX/etc.
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, FRAME_WIDTH, FRAME_HEIGHT, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
std::string error;
double errors = CompareScreenshot(pixels, FRAME_WIDTH, FRAME_HEIGHT, FRAME_WIDTH, comparisonScreenshot, error);
if (errors < 0)
fprintf_s(out, "%s\n", error.c_str());
SendOrCollectDebugOutput(error);
if (errors > 0)
{
fprintf_s(out, "Screenshot error: %f%%\n", errors * 100.0f);
char temp[256];
sprintf_s(temp, "Screenshot error: %f%%\n", errors * 100.0f);
SendOrCollectDebugOutput(temp);
}
if (errors > 0 && !teamCityMode)
{
// Lazy, just read in the original header to output the failed screenshot.
u8 header[14 + 40] = {0};
FILE *bmp = fopen(comparisonScreenshot.c_str(), "rb");
@ -129,7 +141,7 @@ void WindowsHeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h)
fwrite(pixels, sizeof(u32), FRAME_WIDTH * FRAME_HEIGHT, saved);
fclose(saved);
fprintf_s(out, "Actual output written to: __testfailure.bmp\n");
SendOrCollectDebugOutput("Actual output written to: __testfailure.bmp\n");
}
}

View File

@ -40,10 +40,10 @@ public:
private:
bool ResizeGL();
void LoadNativeAssets();
void SendOrCollectDebugOutput(const std::string &output);
HWND hWnd;
HDC hDC;
HGLRC hRC;
FILE *out;
std::string comparisonScreenshot;
};