ppsspp/headless/Compare.cpp
Unknown W. Brackets 57b636816b Add an internal comparison algorithm to headless.
This is slightly smarter than the test.py version, and should be
completely compatible.  It doesn't handle timeouts though.
2013-09-16 08:30:02 -07:00

237 lines
5.4 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "headless/Compare.h"
#include "Common/FileUtil.h"
#include "Core/Host.h"
#include <cmath>
#include <cstdarg>
#include <iostream>
#include <fstream>
bool teamCityMode = false;
std::string teamCityName = "";
void TeamCityPrint(const char *fmt, ...)
{
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\n", expect_filename.c_str());
TeamCityPrint("##teamcity[testIgnored name='%s' message='Expects file missing']\n", teamCityName.c_str());
return false;
}
}
inline int ComparePixel(u32 pix1, u32 pix2)
{
// For now, if they're different at all except alpha, it's an error.
if ((pix1 & 0xFFFFFF) != (pix2 & 0xFFFFFF))
return 1;
return 0;
}
double CompareScreenshot(const u8 *pixels, int w, int h, int stride, const std::string screenshotFilename, std::string &error)
{
u32 *pixels32 = (u32 *) pixels;
// We assume the bitmap is the specified size, not including whatever stride.
u32 *reference = (u32 *) calloc(w * h, sizeof(u32));
FILE *bmp = fopen(screenshotFilename.c_str(), "rb");
if (bmp)
{
// The bitmap header is 14 + 40 bytes. We could validate it but the test would fail either way.
fseek(bmp, 14 + 40, SEEK_SET);
fread(reference, sizeof(u32), w * h, bmp);
fclose(bmp);
}
else
{
error = "Unable to read screenshot: " + screenshotFilename;
free(reference);
return -1.0f;
}
u32 errors = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
errors += ComparePixel(pixels32[y * stride + x], reference[y * w + x]);
}
free(reference);
return (double) errors / (double) (w * h);
}