Test: Add fuzzing harness and move perf-main into test folder

This commit is contained in:
Jeffrey Pfau 2015-08-17 21:24:55 -07:00
parent 3b61005f2e
commit 4d79fd7324
3 changed files with 219 additions and 1 deletions

View File

@ -18,6 +18,7 @@ set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core")
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
set(BUILD_TEST OFF CACHE BOOL "Build testing harness")
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
set(BUILD_GL ON CACHE STRING "Build with OpenGL")
@ -435,7 +436,7 @@ if(BUILD_QT)
endif()
if(BUILD_PERF)
set(PERF_SRC ${CMAKE_SOURCE_DIR}/src/platform/perf-main.c)
set(PERF_SRC ${CMAKE_SOURCE_DIR}/src/platform/test/perf-main.c)
if(UNIX AND NOT APPLE)
list(APPEND PERF_LIB rt)
endif()
@ -446,6 +447,12 @@ if(BUILD_PERF)
install(FILES ${CMAKE_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}" COMPONENT ${BINARY_NAME}-perf)
endif()
if(BUILD_TEST)
add_executable(${BINARY_NAME}-fuzz ${CMAKE_SOURCE_DIR}/src/platform/test/fuzz-main.c)
target_link_libraries(${BINARY_NAME}-fuzz ${BINARY_NAME})
install(TARGETS ${BINARY_NAME}-fuzz DESTINATION bin COMPONENT ${BINARY_NAME}-test)
endif()
# Packaging
set(CPACK_PACKAGE_VERSION ${VERSION_STRING})
set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR})
@ -483,6 +490,7 @@ message(STATUS " Qt: ${BUILD_QT}")
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
message(STATUS " Libretro core: ${BUILD_LIBRETRO}")
message(STATUS " Profiling: ${BUILD_PERF}")
message(STATUS " Test harness: ${BUILD_TEST}")
message(STATUS "Library summary:")
message(STATUS " Static: ${BUILD_STATIC}")
message(STATUS " Shared: ${BUILD_SHARED}")

View File

@ -0,0 +1,210 @@
/* Copyright (c) 2013-2015 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gba/supervisor/thread.h"
#include "gba/supervisor/config.h"
#include "gba/gba.h"
#include "gba/renderers/video-software.h"
#include "gba/serialize.h"
#include "platform/commandline.h"
#include "util/string.h"
#include "util/vfs.h"
#include <errno.h>
#include <signal.h>
#define FUZZ_OPTIONS "F:NO:S:V:"
#define FUZZ_USAGE \
"\nAdditional options:\n" \
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \
" -N Disable video rendering entirely\n" \
" -O OFFSET Offset to apply savestate overlay\n" \
" -S FILE Load a savestate when starting the test\n" \
" -V FILE Overlay a second savestate over the loaded savestate\n" \
struct FuzzOpts {
bool noVideo;
unsigned frames;
size_t overlayOffset;
char* savestate;
char* ssOverlay;
};
static void _GBAFuzzRunloop(struct GBAThread* context, unsigned frames);
static void _GBAFuzzShutdown(int signal);
static bool _parseFuzzOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg);
static void _loadSavestate(struct GBAThread* context);
static struct GBAThread* _thread;
static bool _dispatchExiting = false;
static struct VFile* _savestate = 0;
static struct VFile* _savestateOverlay = 0;
static size_t _overlayOffset;
int main(int argc, char** argv) {
signal(SIGINT, _GBAFuzzShutdown);
struct FuzzOpts fuzzOpts = { false, 0, 0, 0, 0 };
struct SubParser subparser = {
.usage = FUZZ_USAGE,
.parse = _parseFuzzOpts,
.extraOptions = FUZZ_OPTIONS,
.opts = &fuzzOpts
};
struct GBAConfig config;
GBAConfigInit(&config, "fuzz");
GBAConfigLoad(&config);
struct GBAOptions opts = {
.idleOptimization = IDLE_LOOP_DETECT
};
GBAConfigLoadDefaults(&config, &opts);
struct GBAArguments args;
bool parsed = parseArguments(&args, &config, argc, argv, &subparser);
if (!parsed || args.showHelp) {
usage(argv[0], FUZZ_USAGE);
freeArguments(&args);
GBAConfigFreeOpts(&opts);
GBAConfigDeinit(&config);
return !parsed;
}
struct GBAVideoSoftwareRenderer renderer;
renderer.outputBuffer = 0;
struct GBAThread context = {};
_thread = &context;
if (!fuzzOpts.noVideo) {
GBAVideoSoftwareRendererCreate(&renderer);
renderer.outputBuffer = malloc(256 * 256 * 4);
renderer.outputBufferStride = 256;
context.renderer = &renderer.d;
}
if (fuzzOpts.savestate) {
_savestate = VFileOpen(fuzzOpts.savestate, O_RDONLY);
free(fuzzOpts.savestate);
}
if (fuzzOpts.ssOverlay) {
_overlayOffset = fuzzOpts.overlayOffset;
if (_overlayOffset < sizeof(struct GBASerializedState)) {
_savestateOverlay = VFileOpen(fuzzOpts.ssOverlay, O_RDONLY);
}
free(fuzzOpts.ssOverlay);
}
if (_savestate) {
context.startCallback = _loadSavestate;
}
context.debugger = createDebugger(&args, &context);
context.overrides = GBAConfigGetOverrides(&config);
GBAConfigMap(&config, &opts);
opts.audioSync = false;
opts.videoSync = false;
GBAMapArgumentsToContext(&args, &context);
GBAMapOptionsToContext(&opts, &context);
int didStart = GBAThreadStart(&context);
if (!didStart) {
goto cleanup;
}
GBAThreadInterrupt(&context);
if (GBAThreadHasCrashed(&context)) {
GBAThreadJoin(&context);
goto cleanup;
}
GBAThreadContinue(&context);
_GBAFuzzRunloop(&context, fuzzOpts.frames);
GBAThreadJoin(&context);
cleanup:
if (_savestate) {
_savestate->close(_savestate);
}
if (_savestateOverlay) {
_savestateOverlay->close(_savestateOverlay);
}
GBAConfigFreeOpts(&opts);
freeArguments(&args);
GBAConfigDeinit(&config);
free(context.debugger);
if (renderer.outputBuffer) {
free(renderer.outputBuffer);
}
return !didStart || GBAThreadHasCrashed(&context);
}
static void _GBAFuzzRunloop(struct GBAThread* context, unsigned duration) {
unsigned frames = 0;
while (context->state < THREAD_EXITING) {
if (GBASyncWaitFrameStart(&context->sync, 0)) {
++frames;
}
GBASyncWaitFrameEnd(&context->sync);
if (frames >= duration) {
_GBAFuzzShutdown(0);
}
if (_dispatchExiting) {
GBAThreadEnd(context);
}
}
}
static void _GBAFuzzShutdown(int signal) {
UNUSED(signal);
// This will come in ON the GBA thread, so we have to handle it carefully
_dispatchExiting = true;
ConditionWake(&_thread->sync.videoFrameAvailableCond);
}
static bool _parseFuzzOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg) {
UNUSED(config);
struct FuzzOpts* opts = parser->opts;
errno = 0;
switch (option) {
case 'F':
opts->frames = strtoul(arg, 0, 10);
return !errno;
case 'N':
opts->noVideo = true;
return true;
case 'O':
opts->overlayOffset = strtoul(arg, 0, 10);
return !errno;
case 'S':
opts->savestate = strdup(arg);
return true;
case 'V':
opts->ssOverlay = strdup(arg);
return true;
default:
return false;
}
}
static void _loadSavestate(struct GBAThread* context) {
if (!_savestateOverlay) {
GBALoadStateNamed(context->gba, _savestate);
} else {
struct GBASerializedState* state = GBAAllocateState();
_savestate->read(_savestate, state, sizeof(*state));
_savestateOverlay->read(_savestateOverlay, (uint8_t*) state + _overlayOffset, sizeof(*state) - _overlayOffset);
GBADeserialize(context->gba, state);
GBADeallocateState(state);
_savestateOverlay->close(_savestateOverlay);
_savestateOverlay = 0;
}
_savestate->close(_savestate);
_savestate = 0;
}