diff options
author | Eugene Sandulenko | 2010-09-12 08:08:37 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2010-09-12 08:08:37 +0000 |
commit | 74118a70878f0b6efcba73f8410a7f661949bcf6 (patch) | |
tree | a4c22ffcbb86fbad22b50c5b4a6405a60920be0b | |
parent | 6bc9340df55d05ad7aa6b14348a567304ff41cc7 (diff) | |
parent | a4a28eb16e3f2c9d1f4e79120b359821d731eecf (diff) | |
download | scummvm-rg350-74118a70878f0b6efcba73f8410a7f661949bcf6.tar.gz scummvm-rg350-74118a70878f0b6efcba73f8410a7f661949bcf6.tar.bz2 scummvm-rg350-74118a70878f0b6efcba73f8410a7f661949bcf6.zip |
TESTBED: Merge gsoc2010-testbed branch
svn-id: r52681
29 files changed, 4176 insertions, 0 deletions
diff --git a/base/plugins.cpp b/base/plugins.cpp index b1273b2d21..f5e51f3228 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -157,6 +157,9 @@ public: #if PLUGIN_ENABLED_STATIC(TEENAGENT) LINK_PLUGIN(TEENAGENT) #endif + #if PLUGIN_ENABLED_STATIC(TESTBED) + LINK_PLUGIN(TESTBED) + #endif #if PLUGIN_ENABLED_STATIC(TINSEL) LINK_PLUGIN(TINSEL) #endif @@ -106,6 +106,7 @@ add_engine sky "Beneath a Steel Sky" yes add_engine sword1 "Broken Sword" yes add_engine sword2 "Broken Sword II" yes add_engine teenagent "Teen Agent" yes +add_engine testbed "TestBed: the Testing framework" no add_engine tinsel "Tinsel" yes add_engine touche "Touche: The Adventures of the Fifth Musketeer" yes add_engine tucker "Bud Tucker in Double Trouble" yes diff --git a/dists/engine-data/create-testbed-data.sh b/dists/engine-data/create-testbed-data.sh new file mode 100755 index 0000000000..0200803caa --- /dev/null +++ b/dists/engine-data/create-testbed-data.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +# Create the directory structure +# Avoided bash shortcuts / file-seperators in interest of portability + +if [ -e testbed ]; then + echo "Game-data already present as testbed/" + echo "To regenerate, remove and rerun" + exit 0 +fi + +mkdir testbed + +cd testbed + +# For game detection +echo "ScummVM rocks!" > TESTBED + +mkdir test1 +mkdir Test2 +mkdir TEST3 +mkdir tEST4 +mkdir test5 + + +cd test1 +echo "It works!" > file.txt +cd .. + +cd Test2 +echo "It works!" > File.txt +cd .. + +cd TEST3 +echo "It works!" > FILE.txt +cd .. + +cd tEST4 +echo "It works!" > fILe.txt +cd .. + +cd test5 +echo "It works!" > file. +cd .. + +# back to the top +cd .. + +# move the audiocd data to newly created directory +cp -r testbed-audiocd-files testbed/audiocd-files + +echo "Game data created" diff --git a/dists/engine-data/testbed-audiocd-files/track01.mp3 b/dists/engine-data/testbed-audiocd-files/track01.mp3 Binary files differnew file mode 100755 index 0000000000..53d057ee96 --- /dev/null +++ b/dists/engine-data/testbed-audiocd-files/track01.mp3 diff --git a/dists/engine-data/testbed-audiocd-files/track02.mp3 b/dists/engine-data/testbed-audiocd-files/track02.mp3 Binary files differnew file mode 100755 index 0000000000..daf8e4860d --- /dev/null +++ b/dists/engine-data/testbed-audiocd-files/track02.mp3 diff --git a/dists/engine-data/testbed-audiocd-files/track03.mp3 b/dists/engine-data/testbed-audiocd-files/track03.mp3 Binary files differnew file mode 100755 index 0000000000..1ef385d640 --- /dev/null +++ b/dists/engine-data/testbed-audiocd-files/track03.mp3 diff --git a/dists/engine-data/testbed-audiocd-files/track04.mp3 b/dists/engine-data/testbed-audiocd-files/track04.mp3 Binary files differnew file mode 100755 index 0000000000..7607087f08 --- /dev/null +++ b/dists/engine-data/testbed-audiocd-files/track04.mp3 diff --git a/engines/engines.mk b/engines/engines.mk index e542ffd933..f20d46a8f3 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -141,6 +141,11 @@ DEFINES += -DENABLE_SWORD2=$(ENABLE_SWORD2) MODULES += engines/sword2 endif +ifdef ENABLE_TESTBED +DEFINES += -DENABLE_TESTBED=$(ENABLE_TESTBED) +MODULES += engines/testbed +endif + ifdef ENABLE_TEENAGENT DEFINES += -DENABLE_TEENAGENT=$(ENABLE_TEENAGENT) MODULES += engines/teenagent diff --git a/engines/testbed/config.cpp b/engines/testbed/config.cpp new file mode 100644 index 0000000000..1cfb1bc395 --- /dev/null +++ b/engines/testbed/config.cpp @@ -0,0 +1,292 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/fs.h" +#include "common/stream.h" +#include "common/config-manager.h" +#include "engines/engine.h" +#include "testbed/config.h" + +namespace Testbed { + +TestbedOptionsDialog::TestbedOptionsDialog(Common::Array<Testsuite *> &tsList, TestbedConfigManager *tsConfMan) : GUI::Dialog("Browser"), _testbedConfMan(tsConfMan) { + + new GUI::StaticTextWidget(this, "Browser.Headline", "Select Testsuites to Execute"); + new GUI::StaticTextWidget(this, "Browser.Path", "Use Doubleclick to select/deselect"); + + // Construct a String Array + Common::Array<Testsuite *>::const_iterator iter; + Common::String description; + uint selected = 0; + + for (iter = tsList.begin(); iter != tsList.end(); iter++) { + _testSuiteArray.push_back(*iter); + description = (*iter)->getDescription(); + if ((*iter)->isEnabled()) { + _testSuiteDescArray.push_back(description + "(selected)"); + selected++; + _colors.push_back(GUI::ThemeEngine::kFontColorNormal); + } else { + _testSuiteDescArray.push_back(description); + _colors.push_back(GUI::ThemeEngine::kFontColorAlternate); + } + } + + _testListDisplay = new TestbedListWidget(this, "Browser.List", _testSuiteArray); + _testListDisplay->setNumberingMode(GUI::kListNumberingOff); + _testListDisplay->setList(_testSuiteDescArray, &_colors); + + // This list shouldn't be editable + _testListDisplay->setEditable(false); + + if (selected > (tsList.size() - selected)) { + _selectButton = new GUI::ButtonWidget(this, "Browser.Up", "Deselect All", 0, kTestbedDeselectAll, 0); + } else { + _selectButton = new GUI::ButtonWidget(this, "Browser.Up", "Select All", 0, kTestbedSelectAll, 0); + } + new GUI::ButtonWidget(this, "Browser.Cancel", "Run tests", 0, GUI::kCloseCmd); + new GUI::ButtonWidget(this, "Browser.Choose", "Exit Testbed", 0, kTestbedQuitCmd); +} + +TestbedOptionsDialog::~TestbedOptionsDialog() {} + +void TestbedOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { + Testsuite *ts; + Common::WriteStream *ws; + switch (cmd) { + case GUI::kListItemDoubleClickedCmd: + ts = _testSuiteArray[_testListDisplay->getSelected()]; + if (ts) { + if (ts->isEnabled()) { + ts->enable(false); + _testListDisplay->markAsDeselected(_testListDisplay->getSelected()); + } else { + ts->enable(true); + _testListDisplay->markAsSelected(_testListDisplay->getSelected()); + } + } + break; + + case kTestbedQuitCmd: + Engine::quitGame(); + close(); + break; + + case kTestbedDeselectAll: + _selectButton->setLabel("Select All"); + _selectButton->setCmd(kTestbedSelectAll); + for (uint i = 0; i < _testSuiteArray.size(); i++) { + _testListDisplay->markAsDeselected(i); + ts = _testSuiteArray[i]; + if (ts) { + ts->enable(false); + } + } + break; + + case kTestbedSelectAll: + _selectButton->setLabel("Deselect All"); + _selectButton->setCmd(kTestbedDeselectAll); + for (uint i = 0; i < _testSuiteArray.size(); i++) { + _testListDisplay->markAsSelected(i); + ts = _testSuiteArray[i]; + if (ts) { + ts->enable(true); + } + } + break; + case GUI::kCloseCmd: + // This is final selected state, write it to config file. + ws = _testbedConfMan->getConfigWriteStream(); + _testbedConfMan->writeTestbedConfigToStream(ws); + delete ws; + default: + GUI::Dialog::handleCommand(sender, cmd, data); + + } +} + +void TestbedInteractionDialog::addText(uint w, uint h, const Common::String text, Graphics::TextAlign textAlign, uint xOffset, uint yPadding) { + if (!xOffset) { + xOffset = _xOffset; + } + _yOffset += yPadding; + new GUI::StaticTextWidget(this, xOffset, _yOffset, w, h, text, textAlign); + _yOffset += h; +} + +void TestbedInteractionDialog::addButton(uint w, uint h, const Common::String name, uint32 cmd, uint xOffset, uint yPadding) { + if (!xOffset) { + xOffset = _xOffset; + } + _yOffset += yPadding; + _buttonArray.push_back(new GUI::ButtonWidget(this, xOffset, _yOffset, w, h, name, 0, cmd)); + _yOffset += h; +} + +void TestbedInteractionDialog::addList(uint x, uint y, uint w, uint h, Common::Array<Common::String> &strArray, uint yPadding) { + _yOffset += yPadding; + GUI::ListWidget *list = new GUI::ListWidget(this, x, y, w, h); + list->setEditable(false); + list->setNumberingMode(GUI::kListNumberingOff); + list->setList(strArray); + _yOffset += h; +} + +void TestbedInteractionDialog::addButtonXY(uint x, uint y, uint w, uint h, const Common::String name, uint32 cmd) { + _buttonArray.push_back(new GUI::ButtonWidget(this, x, _yOffset, w, h, name, 0, cmd)); +} + +void TestbedInteractionDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { + switch (cmd) { + default: + GUI::Dialog::handleCommand(sender, cmd, data); + } +} + +void TestbedConfigManager::initDefaultConfiguration() { + // Default Configuration + // Add Global configuration Parameters here. + _configFileInterface.setKey("isSessionInteractive", "Global", "true"); +} + +void TestbedConfigManager::writeTestbedConfigToStream(Common::WriteStream *ws) { + Common::String wStr; + for (Common::Array<Testsuite *>::const_iterator i = _testsuiteList.begin(); i < _testsuiteList.end(); i++) { + _configFileInterface.setKey("this", (*i)->getName(), boolToString((*i)->isEnabled())); + const Common::Array<Test *> &testList = (*i)->getTestList(); + for (Common::Array<Test *>::const_iterator j = testList.begin(); j != testList.end(); j++) { + _configFileInterface.setKey((*j)->featureName, (*i)->getName(), boolToString((*j)->enabled)); + } + } + _configFileInterface.saveToStream(*ws); + _configFileInterface.clear(); + ws->flush(); +} + +Common::SeekableReadStream *TestbedConfigManager::getConfigReadStream() { + // Look for config file in game-path + const Common::String &path = ConfMan.get("path"); + Common::FSDirectory gameRoot(path); + Common::SeekableReadStream *rs = gameRoot.createReadStreamForMember(_configFileName); + return rs; +} + +Common::WriteStream *TestbedConfigManager::getConfigWriteStream() { + // Look for config file in game-path + const Common::String &path = ConfMan.get("path"); + Common::WriteStream *ws; + Common::FSNode gameRoot(path); + Common::FSNode config = gameRoot.getChild(_configFileName); + ws = config.createWriteStream(); + return ws; +} + +void TestbedConfigManager::parseConfigFile() { + Common::SeekableReadStream *rs = getConfigReadStream(); + + if (!rs) { + Testsuite::logPrintf("Info! No config file found, using default configuration.\n"); + initDefaultConfiguration(); + return; + } + _configFileInterface.loadFromStream(*rs); + Common::ConfigFile::SectionList sections = _configFileInterface.getSections(); + Testsuite *currTS = 0; + + for (Common::ConfigFile::SectionList::const_iterator i = sections.begin(); i != sections.end(); i++) { + if (i->name.equalsIgnoreCase("Global")) { + // Global params may be directly queried, ignore them + } else { + // A testsuite, process it. + currTS = getTestsuiteByName(i->name); + Common::ConfigFile::SectionKeyList kList = i->getKeys(); + if (!currTS) { + Testsuite::logPrintf("Warning! Error in config: Testsuite %s not found\n", i->name.c_str()); + } + + for (Common::ConfigFile::SectionKeyList::const_iterator j = kList.begin(); j != kList.end(); j++) { + if (j->key.equalsIgnoreCase("this")) { + currTS->enable(stringToBool(j->value)); + } else { + if (!currTS->enableTest(j->key, stringToBool(j->value))) { + Testsuite::logPrintf("Warning! Error in config: Test %s not found\n", j->key.c_str()); + } + } + } + } + } + delete rs; +} + +int TestbedConfigManager::getNumSuitesEnabled() { + int count = 0; + for (uint i = 0; i < _testsuiteList.size(); i++) { + if (_testsuiteList[i]->isEnabled()) { + count++; + } + } + return count; +} + +Testsuite *TestbedConfigManager::getTestsuiteByName(const Common::String &name) { + for (uint i = 0; i < _testsuiteList.size(); i++) { + if (name.equalsIgnoreCase(_testsuiteList[i]->getName())) { + return _testsuiteList[i]; + } + } + return 0; +} + +void TestbedConfigManager::selectTestsuites() { + + parseConfigFile(); + + if (_configFileInterface.hasKey("isSessionInteractive", "Global")) { + Common::String in; + _configFileInterface.getKey("isSessionInteractive", "Global", in); + Testsuite::isSessionInteractive = stringToBool(in); + } + + if (!Testsuite::isSessionInteractive) { + // Non interactive sessions don't need to go beyond + return; + } + + // XXX: disabling these as of now for fastly testing other tests + // Testsuite::isSessionInteractive = false; + Common::String prompt("Welcome to the ScummVM testbed!\n" + "It is a framework to test the various ScummVM subsystems namely GFX, Sound, FS, events etc.\n" + "If you see this, it means interactive tests would run on this system :)"); + + Testsuite::logPrintf("Info! : Interactive tests are also being executed.\n"); + + if (Testsuite::handleInteractiveInput(prompt, "Proceed?", "Customize", kOptionRight)) { + // Select testsuites using checkboxes + TestbedOptionsDialog tbd(_testsuiteList, this); + tbd.runModal(); + } +} + +} // End of namespace Testbed diff --git a/engines/testbed/config.h b/engines/testbed/config.h new file mode 100644 index 0000000000..0c734c95fb --- /dev/null +++ b/engines/testbed/config.h @@ -0,0 +1,135 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_CONFIG_H +#define TESTBED_CONFIG_H + + +#include "common/array.h" +#include "common/config-file.h" +#include "common/str-array.h" +#include "common/tokenizer.h" + +#include "gui/ListWidget.h" +#include "gui/options.h" +#include "gui/ThemeEngine.h" + +#include "testbed/testsuite.h" + +namespace Testbed { + +enum { + kTestbedQuitCmd = 'Quit', + kTestbedSelectAll = 'sAll', + kTestbedDeselectAll = 'dAll' +}; + + + +class TestbedConfigManager { +public: + TestbedConfigManager(Common::Array<Testsuite *> &tList, const Common::String fName) : _testsuiteList(tList), _configFileName(fName) {} + ~TestbedConfigManager() {} + void selectTestsuites(); + void setConfigFile(const Common::String fName) { _configFileName = fName; } + Common::SeekableReadStream *getConfigReadStream(); + Common::WriteStream *getConfigWriteStream(); + void writeTestbedConfigToStream(Common::WriteStream *ws); + Testsuite *getTestsuiteByName(const Common::String &name); + bool stringToBool(const Common::String str) { return str.equalsIgnoreCase("true") ? true : false; } + Common::String boolToString(bool val) { return val ? "true" : "false"; } + void initDefaultConfiguration(); + int getNumSuitesEnabled(); + +private: + Common::Array<Testsuite *> &_testsuiteList; + Common::String _configFileName; + Common::ConfigFile _configFileInterface; + void parseConfigFile(); +}; + +class TestbedListWidget : public GUI::ListWidget { +public: + TestbedListWidget(GUI::Dialog *boss, const Common::String &name, Common::Array<Testsuite *> tsArray) : GUI::ListWidget(boss, name), _testSuiteArray(tsArray) {} + + void markAsSelected(int i) { + if (!_list[i].contains("selected")) { + _list[i] += " (selected)"; + } + _listColors[i] = GUI::ThemeEngine::kFontColorNormal; + draw(); + } + + void markAsDeselected(int i) { + if (_list[i].contains("selected")) { + _list[i] = _testSuiteArray[i]->getDescription(); + } + _listColors[i] = GUI::ThemeEngine::kFontColorAlternate; + draw(); + } + + void setColor(uint32 indx, GUI::ThemeEngine::FontColor color) { + assert(indx < _listColors.size()); + _listColors[indx] = color; + draw(); + } + +private: + Common::Array<Testsuite *> _testSuiteArray; +}; + +class TestbedOptionsDialog : public GUI::Dialog { +public: + TestbedOptionsDialog(Common::Array<Testsuite *> &tsList, TestbedConfigManager *tsConfMan); + ~TestbedOptionsDialog(); + void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); + +private: + GUI::ListWidget::ColorList _colors; + GUI::ButtonWidget *_selectButton; + Common::Array<Testsuite *> _testSuiteArray; + Common::StringArray _testSuiteDescArray; + TestbedListWidget *_testListDisplay; + TestbedConfigManager *_testbedConfMan; +}; + +class TestbedInteractionDialog : public GUI::Dialog { +public: + TestbedInteractionDialog(uint x, uint y, uint w, uint h) : GUI::Dialog(x, y, w, h) {} + ~TestbedInteractionDialog() {} + virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); + void addButton(uint w, uint h, const Common::String name, uint32 cmd, uint xOffset = 0, uint yPadding = 8); + void addButtonXY(uint x, uint y, uint w, uint h, const Common::String name, uint32 cmd); + void addText(uint w, uint h, const Common::String text, Graphics::TextAlign textAlign, uint xOffset, uint yPadding = 8); + void addList(uint x, uint y, uint w, uint h, Common::Array<Common::String> &strArray, uint yPadding = 8); +protected: + Common::Array<GUI::ButtonWidget *> _buttonArray; + uint _xOffset; + uint _yOffset; + +}; + +} // End of namespace Testbed + +#endif // TESTBED_CONFIG_H diff --git a/engines/testbed/detection.cpp b/engines/testbed/detection.cpp new file mode 100644 index 0000000000..bb633e9812 --- /dev/null +++ b/engines/testbed/detection.cpp @@ -0,0 +1,92 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + + * 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 for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/config-manager.h" +#include "engines/advancedDetector.h" +#include "common/system.h" +#include "common/fs.h" + +#include "base/plugins.h" + +#include "testbed/testbed.h" + +static const PlainGameDescriptor testbed_setting[] = { + { "testbed", "Testbed: The backend testing framework" }, + { 0, 0 } +}; + +static const ADGameDescription testbedDescriptions[] = { + { + "testbed", + "", + AD_ENTRY1(NULL, 0), // No data files required + Common::EN_ANY, + Common::kPlatformPC, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + AD_TABLE_END_MARKER +}; + +static const ADParams detectionParams = { + (const byte *)testbedDescriptions, + sizeof(ADGameDescription), + 512, + testbed_setting, + 0, + "testbed", + 0, + ADGF_NO_FLAGS, + Common::GUIO_NONE, + 1, + 0 +}; + +class TestbedMetaEngine : public AdvancedMetaEngine { +public: + TestbedMetaEngine() : AdvancedMetaEngine(detectionParams) { + } + + virtual const char *getName() const { + return "TestBed: The backend testing framework"; + } + + virtual const char *getOriginalCopyright() const { + return "Copyright (C) ScummVM"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + // Instantiate Engine even if the game data is not found. + *engine = new Testbed::TestbedEngine(syst); + return true; + } + +}; + +#if PLUGIN_ENABLED_DYNAMIC(TESTBED) + REGISTER_PLUGIN_DYNAMIC(TESTBED, PLUGIN_TYPE_ENGINE, TestbedMetaEngine); +#else + REGISTER_PLUGIN_STATIC(TESTBED, PLUGIN_TYPE_ENGINE, TestbedMetaEngine); +#endif diff --git a/engines/testbed/events.cpp b/engines/testbed/events.cpp new file mode 100644 index 0000000000..a8e2816266 --- /dev/null +++ b/engines/testbed/events.cpp @@ -0,0 +1,294 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/events.h" +#include "common/keyboard.h" + +#include "engines/engine.h" + +#include "graphics/cursorman.h" + +#include "testbed/events.h" +#include "testbed/graphics.h" + +namespace Testbed { + +struct keycodeToChar { + Common::KeyCode code; + char value; +} keyCodeLUT[] = { + {Common::KEYCODE_a, 'a'}, + {Common::KEYCODE_b, 'b'}, + {Common::KEYCODE_c, 'c'}, + {Common::KEYCODE_d, 'd'}, + {Common::KEYCODE_e, 'e'}, + {Common::KEYCODE_f, 'f'}, + {Common::KEYCODE_g, 'g'}, + {Common::KEYCODE_h, 'h'}, + {Common::KEYCODE_i, 'i'}, + {Common::KEYCODE_j, 'j'}, + {Common::KEYCODE_k, 'k'}, + {Common::KEYCODE_l, 'l'}, + {Common::KEYCODE_m, 'm'}, + {Common::KEYCODE_n, 'n'}, + {Common::KEYCODE_o, 'o'}, + {Common::KEYCODE_p, 'p'}, + {Common::KEYCODE_q, 'q'}, + {Common::KEYCODE_r, 'r'}, + {Common::KEYCODE_s, 's'}, + {Common::KEYCODE_t, 't'}, + {Common::KEYCODE_u, 'u'}, + {Common::KEYCODE_v, 'v'}, + {Common::KEYCODE_w, 'w'}, + {Common::KEYCODE_x, 'x'}, + {Common::KEYCODE_y, 'y'}, + {Common::KEYCODE_z, 'z'}, + {Common::KEYCODE_SPACE, ' '} +}; + +char EventTests::keystrokeToChar() { + Common::EventManager *eventMan = g_system->getEventManager(); + bool quitLoop = false; + Common::Event event; + + // handle all keybd events + while (!quitLoop) { + while (eventMan->pollEvent(event)) { + // Quit if explicitly requested! + if (Engine::shouldQuit()) { + return 0; + } + + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_ESCAPE) { + return 0; + } + for (int i = 0; i < ARRAYSIZE(keyCodeLUT); i++) { + if (event.kbd.keycode == keyCodeLUT[i].code) { + return keyCodeLUT[i].value; + } + } + break; + default: + break; // Ignore other events + } + } + } + + return 0; +} + +Common::Rect EventTests::drawFinishZone() { + Graphics::Surface *screen = g_system->lockScreen(); + const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont)); + int width = 35; + int height = 20; + int right = g_system->getWidth(); + Common::Rect rect(0, 0, right, height); + Common::Rect rect2(0, 0, right - width, height); + screen->fillRect(rect, kColorSpecial); + screen->fillRect(rect2, kColorBlack); + g_system->unlockScreen(); + font.drawString(screen, "Close", rect.left, rect.top, screen->w, kColorBlack, Graphics::kTextAlignRight); + g_system->updateScreen(); + return Common::Rect(right - width, 0, right, height); +} + +bool EventTests::mouseEvents() { + + Testsuite::clearScreen(); + Common::String info = "Testing Mouse events.\n " + "Any movement/click generated by L/R/M mouse buttons or the mouse wheel should be detected.\n" + "Press X to exit"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : keyboard events\n"); + return true; + } + + Common::EventManager *eventMan = g_system->getEventManager(); + + Common::Point pt(0, 100); + Common::Rect rect = Testsuite::writeOnScreen("Generate mouse events make L/R/M button clicks", pt); + pt.y = 120; + Testsuite::writeOnScreen("Testbed should be able to detect them, Press X to exit", pt); + + // Init Mouse Palette + GFXtests::initMousePalette(); + Common::Rect finishZone = drawFinishZone(); + + bool quitLoop = false; + bool passed = true; + // handle all mouse events + Common::Event event; + while (!quitLoop) { + // Show mouse + CursorMan.showMouse(true); + g_system->updateScreen(); + + while (eventMan->pollEvent(event)) { + // Quit if explicitly requested + if (Engine::shouldQuit()) { + return passed; + } + switch (event.type) { + case Common::EVENT_MOUSEMOVE: + // Movements havee already been tested in GFX + break; + case Common::EVENT_LBUTTONDOWN: + Testsuite::clearScreen(rect); + Testsuite::writeOnScreen("Mouse left-button pressed", pt); + break; + case Common::EVENT_RBUTTONDOWN: + Testsuite::clearScreen(rect); + Testsuite::writeOnScreen("Mouse right-button pressed", pt); + break; + case Common::EVENT_WHEELDOWN: + Testsuite::clearScreen(rect); + Testsuite::writeOnScreen("Mouse wheel moved down", pt); + break; + case Common::EVENT_MBUTTONDOWN: + Testsuite::clearScreen(rect); + Testsuite::writeOnScreen("Mouse middle-button pressed ", pt); + break; + case Common::EVENT_LBUTTONUP: + Testsuite::clearScreen(rect); + if (finishZone.contains(eventMan->getMousePos())) { + quitLoop = true; + } + Testsuite::writeOnScreen("Mouse left-button released", pt); + break; + case Common::EVENT_RBUTTONUP: + Testsuite::clearScreen(rect); + Testsuite::writeOnScreen("Mouse right-button released", pt); + break; + case Common::EVENT_WHEELUP: + Testsuite::clearScreen(rect); + Testsuite::writeOnScreen("Mouse wheel moved up", pt); + break; + case Common::EVENT_MBUTTONUP: + Testsuite::clearScreen(rect); + Testsuite::writeOnScreen("Mouse middle-button released ", pt); + break; + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_x) { + Testsuite::clearScreen(rect); + Testsuite::writeOnScreen("Exit requested", pt); + quitLoop = true; + } + break; + default: + break; + } + + } + } + + CursorMan.showMouse(false); + + // Verify results now! + if (Testsuite::handleInteractiveInput("Were mouse clicks L/R/M buttons identfied?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Mouse clicks (L/R/M buttons) failed"); + passed = false; + } + if (Testsuite::handleInteractiveInput("Were mouse wheel movements identified?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Mouse wheel movements failed"); + passed = false; + } + + return passed; +} + +bool EventTests::kbdEvents() { + + Testsuite::clearScreen(); + Common::String info = "Testing keyboard events.\n " + "Testbed should be able to figure out any alphanumeric keystrokes made by the user and display them back.\n" + "Press ESC key when done of the input."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : keyboard events\n"); + return true; + } + + + // Make user type some word and display the output on screen + Common::String text = "You Entered : "; + Common::Point pt(0, 100); + Testsuite::clearScreen(); + Testsuite::writeOnScreen("Enter your word, press ESC when done, it will be echoed back", pt); + pt.y += 20; + Common::Rect rect = Testsuite::writeOnScreen(text, pt); + char letter; + while ((letter = keystrokeToChar()) != 0) { + Testsuite::clearScreen(rect); + text += letter; + rect = Testsuite::writeOnScreen(text, pt); + } + + bool passed = true; + + if (Testsuite::handleInteractiveInput("Was the word you entered same as that displayed on screen?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Keyboard Events failed"); + passed = false; + } + + Testsuite::clearScreen(); + return passed; +} + +bool EventTests::showMainMenu() { + + Testsuite::clearScreen(); + Common::String info = "Testing Main Menu events.\n " + "Main Menu event is normally trigerred by user pressing (Ctrl + f5).\n" + "Click 'resume' to continue testbed."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Main Menu\n"); + return true; + } + Common::EventManager *eventMan = g_system->getEventManager(); + Common::Event mainMenuEvent; + mainMenuEvent.type = Common::EVENT_MAINMENU; + eventMan->pushEvent(mainMenuEvent); + + bool passed = true; + + if (Testsuite::handleInteractiveInput("Were you able to see a main menu widget?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Event MAINMENU failed"); + passed = false; + } + + return passed; +} + +EventTestSuite::EventTestSuite() { + addTest("MouseEvents", &EventTests::mouseEvents); + addTest("KeyboardEvents", &EventTests::kbdEvents); + addTest("MainmenuEvent", &EventTests::showMainMenu); +} + +} // End of namespace Testbed diff --git a/engines/testbed/events.h b/engines/testbed/events.h new file mode 100644 index 0000000000..e857deec25 --- /dev/null +++ b/engines/testbed/events.h @@ -0,0 +1,67 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_EVENTS_H +#define TESTBED_EVENTS_H + +#include "testbed/testsuite.h" + +namespace Testbed { + +namespace EventTests { + +// Helper functions for Event tests +char keystrokeToChar(); +Common::Rect drawFinishZone(); +// will contain function declarations for Event tests +bool mouseEvents(); +bool kbdEvents(); +bool showMainMenu(); +// add more here + +} // End of namespace EventTests + +class EventTestSuite : public Testsuite { +public: + /** + * The constructor for the EventTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + EventTestSuite(); + ~EventTestSuite() {} + const char *getName() const { + return "Events"; + } + const char *getDescription() const { + return "Events : Keyboard/Mouse/RTL"; + } +}; + +} // End of namespace Testbed + +#endif // TESTBED_EVENTS_H diff --git a/engines/testbed/fs.cpp b/engines/testbed/fs.cpp new file mode 100644 index 0000000000..6bd67022ee --- /dev/null +++ b/engines/testbed/fs.cpp @@ -0,0 +1,169 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/config-manager.h" +#include "common/stream.h" +#include "common/util.h" + +#include "testbed/fs.h" + +namespace Testbed { + +/** + * This test does the following: + * 1) acquires the game-data path + * 2) In the game-root it navigates to "directory" and opens the file "file" + * + * The code accesses the appropriate file using the fileSystem API, creates a read stream of it and + * compares the message contained in it, with what it expects. + * + */ +bool FStests::readDataFromFile(Common::FSDirectory *directory, const char *file) { + + Common::SeekableReadStream *readStream = directory->createReadStreamForMember(file); + + if (!readStream) { + Testsuite::logDetailedPrintf("Can't open game file for reading\n"); + return false; + } + + Common::String msg = readStream->readLine(); + delete readStream; + + Testsuite::logDetailedPrintf("Message Extracted from %s/%s : %s\n", directory->getFSNode().getName().c_str(), file, msg.c_str()); + + Common::String expectedMsg = "It works!"; + + if (!msg.equals(expectedMsg)) { + Testsuite::logDetailedPrintf("Can't read Correct data from file\n"); + return false; + } + + return true; +} + +bool FStests::testReadFile() { + const Common::String &path = ConfMan.get("path"); + Common::FSDirectory gameRoot(path); + int numFailed = 0; + + if (!gameRoot.getFSNode().isDirectory()) { + Testsuite::logDetailedPrintf("game Path should be a directory"); + return false; + } + + const char *dirList[] = {"test1" ,"Test2", "TEST3" , "tEST4", "test5"}; + const char *file[] = {"file.txt", "File.txt", "FILE.txt", "fILe.txt", "file"}; + + for (unsigned int i = 0; i < ARRAYSIZE(dirList); i++) { + Common::String dirName = dirList[i]; + Common::String fileName = file[i]; + Common::FSDirectory *directory = gameRoot.getSubDirectory(dirName); + + if (!readDataFromFile(directory, fileName.c_str())) { + Testsuite::logDetailedPrintf("Reading from %s/%s failed\n", dirName.c_str(), fileName.c_str()); + numFailed++; + } + + dirName.toLowercase(); + fileName.toLowercase(); + delete directory; + directory = gameRoot.getSubDirectory(dirName); + + if (!readDataFromFile(directory, fileName.c_str())) { + Testsuite::logDetailedPrintf("Reading from %s/%s failed\n", dirName.c_str(), fileName.c_str()); + numFailed++; + } + + dirName.toUppercase(); + fileName.toUppercase(); + delete directory; + directory = gameRoot.getSubDirectory(dirName); + + if (!readDataFromFile(directory, fileName.c_str())) { + Testsuite::logDetailedPrintf("Reading from %s/%s failed\n", dirName.c_str(), fileName.c_str()); + numFailed++; + } + delete directory; + } + + Testsuite::logDetailedPrintf("Failed %d out of 15\n", numFailed); + return false; +} + +/** + * This test creates a file testbed.out, writes a sample data and confirms if + * it is same by reading the file again. + */ +bool FStests::testWriteFile() { + const Common::String &path = ConfMan.get("path"); + Common::FSNode gameRoot(path); + + Common::FSNode fileToWrite = gameRoot.getChild("testbed.out"); + + Common::WriteStream *ws = fileToWrite.createWriteStream(); + + if (!ws) { + Testsuite::logDetailedPrintf("Can't open writable file in game data dir\n"); + return false; + } + + ws->writeString("ScummVM Rocks!"); + ws->flush(); + delete ws; + + Common::SeekableReadStream *rs = fileToWrite.createReadStream(); + Common::String readFromFile = rs->readLine(); + delete rs; + + if (readFromFile.equals("ScummVM Rocks!")) { + // All good + Testsuite::logDetailedPrintf("Data written and read correctly\n"); + return true; + } + + return false; +} + + + +FSTestSuite::FSTestSuite() { + addTest("ReadingFile", &FStests::testReadFile, false); + addTest("WritingFile", &FStests::testWriteFile, false); +} + +void FSTestSuite::enable(bool flag) { + const Common::String &path = ConfMan.get("path"); + Common::FSNode gameRoot(path); + + Common::FSNode gameIdentificationFile = gameRoot.getChild("TESTBED"); + if (!gameIdentificationFile.exists()) { + logPrintf("WARNING! : Game Data not found. Skipping FS tests\n"); + Testsuite::enable(false); + return; + } + Testsuite::enable(flag); +} + +} // End of namespace Testbed diff --git a/engines/testbed/fs.h b/engines/testbed/fs.h new file mode 100644 index 0000000000..a5e79c10ce --- /dev/null +++ b/engines/testbed/fs.h @@ -0,0 +1,74 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_FS_H +#define TESTBED_FS_H + +#include "common/fs.h" + +#include "testbed/testsuite.h" + +namespace Testbed { + +namespace FStests { + +// Note: These tests require a game-data directory +// So would work if game-path is set in the launcher or invoked as ./scummvm --path="path-to-testbed-data" testbed +// from commandline + +// Helper functions for FS tests +bool readDataFromFile(Common::FSDirectory *directory, const char *file); + +// will contain function declarations for FS tests +bool testReadFile(); +bool testWriteFile(); +bool testOpeningSaveFile(); +// add more here + +} // End of namespace FStests + +class FSTestSuite : public Testsuite { +public: + /** + * The constructor for the FSTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + FSTestSuite(); + ~FSTestSuite() {} + const char *getName() const { + return "FS"; + } + const char *getDescription() const { + return "File system tests (Navigation, Read/Write)"; + } + void enable(bool flag); +}; + +} // End of namespace Testbed + +#endif // TESTBED_FS_H diff --git a/engines/testbed/graphics.cpp b/engines/testbed/graphics.cpp new file mode 100644 index 0000000000..38f5daf35d --- /dev/null +++ b/engines/testbed/graphics.cpp @@ -0,0 +1,1102 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/events.h" +#include "common/list.h" +#include "common/random.h" + +#include "engines/engine.h" + +#include "testbed/graphics.h" +#include "testbed/testsuite.h" + +#include "graphics/cursorman.h" +#include "graphics/fontman.h" +#include "graphics/surface.h" +#include "graphics/VectorRendererSpec.h" + +namespace Testbed { + +byte GFXTestSuite::_palette[256 * 4] = {0, 0, 0, 0, 255, 255, 255, 0, 255, 255, 255, 0}; + +GFXTestSuite::GFXTestSuite() { + // Initialize color palettes + // The fourth field is for alpha channel which is unused + // Assuming 8bpp as of now + g_system->setPalette(_palette, 0, 3); + + // Init Mouse Palette (White-black-yellow) + GFXtests::initMousePalette(); + + // Add tests here + + // Blitting buffer on screen + addTest("BlitBitmaps", &GFXtests::copyRectToScreen); + + // GFX Transcations + addTest("FullScreenMode", &GFXtests::fullScreenMode); + addTest("AspectRatio", &GFXtests::aspectRatio); + addTest("IconifyingWindow", &GFXtests::iconifyWindow); + + // Mouse Layer tests (Palettes and movements) + addTest("PalettizedCursors", &GFXtests::palettizedCursors); + addTest("MouseMovements", &GFXtests::mouseMovements); + // FIXME: Scaled cursor crsh with odd dimmensions + addTest("ScaledCursors", &GFXtests::scaledCursors); + + // Effects + addTest("shakingEffect", &GFXtests::shakingEffect); + // addTest("focusRectangle", &GFXtests::focusRectangle); + + // Overlay + addTest("Overlays", &GFXtests::overlayGraphics); + + // Specific Tests: + addTest("PaletteRotation", &GFXtests::paletteRotation); + addTest("cursorTrailsInGUI", &GFXtests::cursorTrails); + //addTest("Pixel Formats", &GFXtests::pixelFormats); +} + +void GFXTestSuite::setCustomColor(uint r, uint g, uint b) { + _palette[8] = r; + _palette[9] = g; + _palette[10] = b; + + // Set colorNum kColorSpecial with a special color. + int absIndx = kColorSpecial * 4; + _palette[absIndx + 1] = 173; + _palette[absIndx + 2] = 255; + _palette[absIndx + 3] = 47; + g_system->setPalette(_palette, 0, 256); +} + +// Helper functions used by GFX tests + +void GFXtests::initMousePalette() { + byte palette[3 * 4]; // Black, white and yellow + + palette[0] = palette[1] = palette[2] = 0; + palette[4] = palette[5] = palette[6] = 255; + palette[8] = palette[9] = 255; + palette[10] = 0; + + CursorMan.replaceCursorPalette(palette, 0, 3); +} + +Common::Rect GFXtests::computeSize(Common::Rect &cursorRect, int scalingFactor, int cursorTargetScale) { + if (cursorTargetScale == 1 || scalingFactor == 1) { + // Game data and cursor would be scaled equally. + // so dimensions would be same. + return Common::Rect(cursorRect.width(), cursorRect.height()); + } + + if (scalingFactor == 2) { + // Game data is scaled by 2, cursor is said to be scaled by 2 or 3. so it wud not be scaled any further + // So a w/2 x h/2 rectangle when scaled would match the cursor + return Common::Rect(cursorRect.width() / 2, cursorRect.height() / 2); + } + + if (scalingFactor == 3) { + // Cursor traget scale is 2 or 3. + return Common::Rect((cursorRect.width() / cursorTargetScale), (cursorRect.height() / cursorTargetScale)); + } else { + Testsuite::logPrintf("Unsupported scaler %dx\n", scalingFactor); + return Common::Rect(); + } +} + +void GFXtests::HSVtoRGB(int &rComp, int &gComp, int &bComp, int hue, int sat, int val) { + float r = rComp; + float g = gComp; + float b = bComp; + + float h = hue * (360 / 256.0); // All colors are tried + float s = sat; + float v = val; + + int i; + float f, p, q, t; + + if (s == 0) { + r = g = b = v * 255; + return; + } + + h /= 60; + i = (int)h; + f = h - i; + p = v * (1 - s); + q = v * (1 - s * f); + t = v * (1 - s * (1 - f)); + + switch (i) { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + default: + r = v; + g = p; + b = q; + break; + } + + rComp = (int)(r * 255); + gComp = (int)(g * 255); + bComp = (int)(b * 255); +} + +Common::Rect GFXtests::drawCursor(bool cursorPaletteDisabled, const char *gfxModeName, int cursorTargetScale) { + // Buffer initialized with yellow color + byte buffer[500]; + memset(buffer, 2, sizeof(buffer)); + + int cursorWidth = 12; + int cursorHeight = 12; + + /* Disable HotSpot highlighting as of now + + // Paint the cursor with yellow, except the hotspot + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + if (i != j && i != 15 - j) { + buffer[i * 16 + j] = 2; + } + } + } + + */ + + // Uncommenting the next line and commenting the line after that would reproduce the crash + // CursorMan.replaceCursor(buffer, 11, 11, 0, 0, 255, cursorTargetScale); + CursorMan.replaceCursor(buffer, 12, 12, 0, 0, 255, cursorTargetScale); + CursorMan.showMouse(true); + + if (cursorPaletteDisabled) { + CursorMan.disableCursorPalette(true); + } else { + initMousePalette(); + CursorMan.disableCursorPalette(false); + } + + g_system->updateScreen(); + return Common::Rect(0, 0, cursorWidth, cursorHeight); +} + +void rotatePalette(byte *palette, int size) { + // Rotate the colors starting from address palette "size" times + + // take a temporary palette color + byte tColor[4] = {0}; + // save first color in it. + memcpy(tColor, &palette[0], 4 * sizeof(byte)); + + // Move each color upward by 1 + for (int i = 0; i < size - 1; i++) { + memcpy(&palette[i * 4], &palette[(i + 1) * 4], 4 * sizeof(byte)); + } + // Assign last color to tcolor + memcpy(&palette[(size - 1) * 4], tColor, 4 * sizeof(byte)); +} + +/** + * Sets up mouse loop, exits when user clicks any of the mouse button + */ +void GFXtests::setupMouseLoop(bool disableCursorPalette, const char *gfxModeName, int cursorTargetScale) { + bool isFeaturePresent; + isFeaturePresent = g_system->hasFeature(OSystem::kFeatureCursorHasPalette); + Common::Rect cursorRect; + + if (isFeaturePresent) { + + cursorRect = GFXtests::drawCursor(disableCursorPalette, gfxModeName, cursorTargetScale); + + Common::EventManager *eventMan = g_system->getEventManager(); + Common::Event event; + Common::Point pt(0, 100); + + bool quitLoop = false; + uint32 lastRedraw = 0; + const uint32 waitTime = 1000 / 45; + + Testsuite::clearScreen(); + Common::String info = disableCursorPalette ? "Using Game Palette" : "Using cursor palette"; + info += " to render the cursor, Click to finish"; + + Common::String gfxScalarMode(gfxModeName); + + if (!gfxScalarMode.equals("")) { + info = "The cursor size (yellow) should match the red rectangle."; + } + + Testsuite::writeOnScreen(info, pt); + + info = "GFX Mode"; + info += gfxModeName; + info += " "; + + char cScale = cursorTargetScale + '0'; + info += "Cursor scale: "; + info += cScale; + + Common::Rect estimatedCursorRect; + + if (!gfxScalarMode.equals("")) { + + if (gfxScalarMode.contains("1x")) { + estimatedCursorRect = computeSize(cursorRect, 1, cursorTargetScale); + } else if (gfxScalarMode.contains("2x")) { + estimatedCursorRect = computeSize(cursorRect, 2, cursorTargetScale); + } else if (gfxScalarMode.contains("3x")) { + estimatedCursorRect = computeSize(cursorRect, 3, cursorTargetScale); + } else { + // If unable to detect scaler, default to 2 + Testsuite::writeOnScreen("Unable to detect scaling factor, assuming 2x", Common::Point(0, 5)); + estimatedCursorRect = computeSize(cursorRect, 2, cursorTargetScale); + } + Testsuite::writeOnScreen(info, Common::Point(0, 120)); + + // Move cursor to (20, 20) + g_system->warpMouse(20, 20); + // Move estimated rect to (20, 20) + estimatedCursorRect.moveTo(20, 20); + + Graphics::Surface *screen = g_system->lockScreen(); + GFXTestSuite::setCustomColor(255, 0, 0); + screen->fillRect(estimatedCursorRect, 2); + g_system->unlockScreen(); + g_system->updateScreen(); + } + + while (!quitLoop) { + while (eventMan->pollEvent(event)) { + if (Engine::shouldQuit()) { + // Quit directly + return; + } + if (lastRedraw + waitTime < g_system->getMillis()) { + g_system->updateScreen(); + lastRedraw = g_system->getMillis(); + } + + switch (event.type) { + case Common::EVENT_MOUSEMOVE: + break; + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_RBUTTONDOWN: + quitLoop = true; + Testsuite::clearScreen(); + Testsuite::writeOnScreen("Mouse clicked", pt); + g_system->delayMillis(1000); + break; + default: + break;// Ignore handling any other event + + } + } + } + } else { + Testsuite::displayMessage("feature not supported"); + } +} + +/** + * Used by aspectRatio() + */ +void GFXtests::drawEllipse(int cx, int cy, int a, int b) { + + // Take a buffer of screen size + int width = g_system->getWidth(); + int height = Testsuite::getDisplayRegionCoordinates().y; + byte *buffer = new byte[height * width]; + float theta; + int x, y, x1, y1; + + memset(buffer, 0, sizeof(byte) * width * height); + // Illuminate the center + buffer[cx * width + cy] = 1; + + // Illuminate the points lying on ellipse + + for (theta = 0; theta <= PI / 2; theta += PI / 360) { + x = (int)(b * sin(theta) + 0.5); + y = (int)(a * cos(theta) + 0.5); + + // This gives us four points + + x1 = x + cx; + y1 = y + cy; + + buffer[x1 * width + y1] = 1; + + x1 = (-1) * x + cx; + y1 = y + cy; + + buffer[x1 * width + y1] = 1; + + x1 = x + cx; + y1 = (-1) * y + cy; + + buffer[x1 * width + y1] = 1; + + x1 = (-1) * x + cx; + y1 = (-1) * y + cy; + + buffer[x1 * width + y1] = 1; + } + + g_system->copyRectToScreen(buffer, width, 0, 0, width, height); + g_system->updateScreen(); + delete[] buffer; +} + +// GFXtests go here + +/** + * Tests the fullscreen mode by: toggling between fullscreen and windowed mode + */ +bool GFXtests::fullScreenMode() { + Testsuite::clearScreen(); + Common::String info = "Fullscreen test. Here you should expect a toggle between windowed and fullscreen states depending " + "upon your initial state."; + + Common::Point pt(0, 100); + Common::Rect rect = Testsuite::writeOnScreen("Testing fullscreen mode", pt); + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : FullScreenMode\n"); + return true; + } + + bool isFeaturePresent; + bool isFeatureEnabled; + bool passed = true; + Common::String prompt; + OptionSelected shouldSelect; + + isFeaturePresent = g_system->hasFeature(OSystem::kFeatureFullscreenMode); + + if (isFeaturePresent) { + // Toggle + isFeatureEnabled = g_system->getFeatureState(OSystem::kFeatureFullscreenMode); + shouldSelect = isFeatureEnabled ? kOptionLeft : kOptionRight; + + g_system->delayMillis(1000); + + if (isFeatureEnabled) { + Testsuite::logDetailedPrintf("Current Mode is Fullsecreen\n"); + } else { + Testsuite::logDetailedPrintf("Current Mode is Windowed\n"); + } + + prompt = " Which mode do you see currently ? "; + + if (!Testsuite::handleInteractiveInput(prompt, "Fullscreen", "Windowed", shouldSelect)) { + // User selected incorrect current state + passed = false; + Testsuite::logDetailedPrintf("g_system->getFeatureState() failed\n"); + } + + g_system->beginGFXTransaction(); + g_system->setFeatureState(OSystem::kFeatureFullscreenMode, !isFeatureEnabled); + g_system->endGFXTransaction(); + + // Current state should be now !isFeatureEnabled + isFeatureEnabled = g_system->getFeatureState(OSystem::kFeatureFullscreenMode); + shouldSelect = isFeatureEnabled ? kOptionLeft : kOptionRight; + + g_system->delayMillis(1000); + + prompt = " Which screen mode do you see now ? "; + + if (!Testsuite::handleInteractiveInput(prompt, "Fullscreen", "Windowed", shouldSelect)) { + // User selected incorrect mode + passed = false; + Testsuite::logDetailedPrintf("g_system->setFeatureState() failed\n"); + } + + g_system->beginGFXTransaction(); + g_system->setFeatureState(OSystem::kFeatureFullscreenMode, !isFeatureEnabled); + g_system->endGFXTransaction(); + + g_system->delayMillis(1000); + + prompt = "This should be your initial state. Is it?"; + + if (!Testsuite::handleInteractiveInput(prompt, "Yes, it is", "Nopes", shouldSelect)) { + // User selected incorrect mode + Testsuite::logDetailedPrintf("switching back to initial state failed\n"); + passed = false; + } + + } else { + Testsuite::displayMessage("feature not supported"); + } + + return passed; +} + +/** + * Tests the aspect ratio correction by: drawing an ellipse, when corrected the ellipse should render to a circle + */ +bool GFXtests::aspectRatio() { + + Testsuite::clearScreen(); + Common::String info = "Aspect Ratio Correction test. If aspect ratio correction is enabled you should expect a circle on screen," + " an ellipse otherwise."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Aspect Ratio\n"); + return true; + } + // Draw an ellipse on the screen + drawEllipse(80, 160, 72, 60); + + bool isFeaturePresent; + bool isFeatureEnabled; + bool passed; + Common::String prompt; + OptionSelected shouldSelect; + + isFeaturePresent = g_system->hasFeature(OSystem::kFeatureAspectRatioCorrection); + isFeatureEnabled = g_system->getFeatureState(OSystem::kFeatureAspectRatioCorrection); + g_system->delayMillis(1000); + + if (isFeaturePresent) { + // Toggle + shouldSelect = isFeatureEnabled ? kOptionLeft : kOptionRight; + prompt = " What does the curve on screen appears to you ?"; + if (!Testsuite::handleInteractiveInput(prompt, "Circle", "Ellipse", shouldSelect)) { + // User selected incorrect option + passed = false; + Testsuite::logDetailedPrintf("Aspect Ratio Correction failed\n"); + } + + g_system->beginGFXTransaction(); + g_system->setFeatureState(OSystem::kFeatureAspectRatioCorrection, !isFeatureEnabled); + g_system->endGFXTransaction(); + + g_system->delayMillis(1000); + + shouldSelect = !isFeatureEnabled ? kOptionLeft : kOptionRight; + prompt = " What does the curve on screen appears to you ?"; + if (!Testsuite::handleInteractiveInput(prompt, "Circle", "Ellipse", shouldSelect)) { + // User selected incorrect option + passed = false; + Testsuite::logDetailedPrintf("Aspect Ratio Correction failed\n"); + } + + g_system->beginGFXTransaction(); + g_system->setFeatureState(OSystem::kFeatureAspectRatioCorrection, isFeatureEnabled); + g_system->endGFXTransaction(); + } else { + Testsuite::displayMessage("feature not supported"); + } + + g_system->delayMillis(500); + + if (Testsuite::handleInteractiveInput("This should definetely be your initial state?", "Yes, it is", "Nopes", kOptionRight)) { + // User selected incorrect mode + Testsuite::logDetailedPrintf("Switching back to initial state failed\n"); + passed = false; + } + + return passed; +} + +/** + * Tests Palettized cursors. + * Method: Create a yellow colored cursor, should be able to move it. Once you click test terminates + */ +bool GFXtests::palettizedCursors() { + + Testsuite::clearScreen(); + Common::String info = "Palettized Cursors test.\n " + "Here you should expect to see a yellow mouse cursor rendered with mouse graphics.\n" + "You would be able to move the cursor. Later we use game graphics to render the cursor.\n" + "For cursor palette it should be yellow and will be red if rendered by the game palette.\n" + "The test finishes when mouse (L/R) is clicked."; + + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Palettized Cursors\n"); + return true; + } + + bool passed = true; + + // Testing with cursor Palette + setupMouseLoop(); + + if (Testsuite::handleInteractiveInput("Which color did the cursor appeared to you?", "Yellow", "Any other", kOptionRight)) { + Testsuite::logDetailedPrintf("Couldn't use cursor palette for rendering cursor\n"); + passed = false; + } + + // Testing with game Palette + GFXTestSuite::setCustomColor(255, 0, 0); + setupMouseLoop(true); + + if (Testsuite::handleInteractiveInput("Which color did the cursor appeared to you?", "Red", "Any other", kOptionRight)) { + Testsuite::logDetailedPrintf("Couldn't use Game palette for rendering cursor\n"); + passed = false; + } + + if (!Testsuite::handleInteractiveInput("Did test run as was described?")) { + passed = false; + } + + // re-enable cursor palette + CursorMan.disableCursorPalette(false); + // Done with cursors, make them invisible, any other test the could simply make it visible + CursorMan.showMouse(false); + return passed; +} + +/** + * Tests automated mouse movements. "Warp" functionality provided by the backend. + */ + +bool GFXtests::mouseMovements() { + Testsuite::clearScreen(); + // Make mouse visible + CursorMan.showMouse(true); + + Common::String info = "Testing Automated Mouse movements.\n" + "You should expect cursor hotspot(top-left corner) to automatically move from (0, 0) to (100, 100).\n" + "There we have a rectangle drawn, finally the cursor would lie centred in that rectangle."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Mouse Movements\n"); + return true; + } + + // Draw Rectangle + Graphics::Surface *screen = g_system->lockScreen(); + screen->fillRect(Common::Rect::center(106, 106, 14, 14), 2); + g_system->unlockScreen(); + + // Testing Mouse Movements now! + Common::Point pt(0, 10); + Testsuite::writeOnScreen("Moving mouse hotspot automatically from (0, 0) to (100, 100)", pt); + g_system->warpMouse(0, 0); + g_system->updateScreen(); + g_system->delayMillis(1000); + + for (int i = 0; i <= 100; i++) { + g_system->delayMillis(20); + g_system->warpMouse(i, i); + g_system->updateScreen(); + } + + Testsuite::writeOnScreen("Mouse hotspot Moved to (100, 100)", pt); + g_system->delayMillis(1500); + CursorMan.showMouse(false); + + if (Testsuite::handleInteractiveInput("Was the cursor symmetrically contained in the rectangle at (100, 100)?", "Yes", "No", kOptionRight)) { + return false; + } + + return true; +} + + + +/** + * This basically blits the screen by the contents of its buffer. + * + */ +bool GFXtests::copyRectToScreen() { + + Testsuite::clearScreen(); + Common::String info = "Testing Blitting a Bitmap to screen.\n" + "You should expect to see a 20x40 yellow horizontal rectangle centred at the screen."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Blitting Bitmap\n"); + return true; + } + + GFXTestSuite::setCustomColor(255, 255, 0); + byte buffer[20 * 40]; + memset(buffer, 2, 20 * 40); + + uint x = g_system->getWidth() / 2 - 20; + uint y = g_system->getHeight() / 2 - 10; + + g_system->copyRectToScreen(buffer, 40, x, y, 40, 20); + g_system->updateScreen(); + g_system->delayMillis(1000); + + if (Testsuite::handleInteractiveInput("Did you see yellow rectangle?", "Yes", "No", kOptionRight)) { + return false; + } + + return true; +} + +/** + * Testing feature : Iconifying window + * It is expected the screen minimizes when this feature is enabled + */ +bool GFXtests::iconifyWindow() { + + Testsuite::clearScreen(); + Common::String info = "Testing Iconify Window mode.\n If the feature is supported by the backend, " + "you should expect the window to be minimized.\n However you would manually need to de-iconify."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Iconifying window\n"); + return true; + } + + Common::Point pt(0, 100); + Common::Rect rect = Testsuite::writeOnScreen("Testing Iconifying window", pt); + + bool isFeaturePresent; + bool isFeatureEnabled; + + isFeaturePresent = g_system->hasFeature(OSystem::kFeatureIconifyWindow); + isFeatureEnabled = g_system->getFeatureState(OSystem::kFeatureIconifyWindow); + g_system->delayMillis(1000); + + if (isFeaturePresent) { + // Toggle + + g_system->beginGFXTransaction(); + g_system->setFeatureState(OSystem::kFeatureIconifyWindow, !isFeatureEnabled); + g_system->endGFXTransaction(); + + g_system->delayMillis(1000); + + g_system->beginGFXTransaction(); + g_system->setFeatureState(OSystem::kFeatureIconifyWindow, isFeatureEnabled); + g_system->endGFXTransaction(); + } else { + Testsuite::displayMessage("feature not supported"); + } + + if (Testsuite::handleInteractiveInput("Did you see window minimized?", "Yes", "No", kOptionRight)) { + return false; + } + + return true; +} + +/** + * Testing feature: Scaled cursors + */ +bool GFXtests::scaledCursors() { + + Testsuite::clearScreen(); + Common::String info = "Testing : Scaled cursors\n" + "Here every graphics mode is tried with a cursorTargetScale of 1, 2 and 3.\n" + "The expected cursor size is drawn as a rectangle, the cursor should entirely cover that rectangle.\n" + "This may take time, You may skip the later scalers and just examine the first three i.e 1x, 2x and 3x"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Scaled Cursors\n"); + return true; + } + + int maxLimit = 1000; + if (!Testsuite::handleInteractiveInput("Do You want to restrict scalers to 1x, 2x and 3x only?", "Yes", "No", kOptionRight)) { + maxLimit = 3; + } + + const int currGFXMode = g_system->getGraphicsMode(); + const OSystem::GraphicsMode *gfxMode = g_system->getSupportedGraphicsModes(); + + while (gfxMode->name && maxLimit > 0) { + // for every graphics mode display cursors for cursorTargetScale 1, 2 and 3 + // Switch Graphics mode + // FIXME: Crashes with "3x" mode now.: + g_system->beginGFXTransaction(); + + bool isGFXModeSet = g_system->setGraphicsMode(gfxMode->id); + g_system->initSize(320, 200); + + OSystem::TransactionError gfxError = g_system->endGFXTransaction(); + + if (gfxError == OSystem::kTransactionSuccess && isGFXModeSet) { + setupMouseLoop(false, gfxMode->name, 1); + Testsuite::clearScreen(); + + setupMouseLoop(false, gfxMode->name, 2); + Testsuite::clearScreen(); + + setupMouseLoop(false, gfxMode->name, 3); + Testsuite::clearScreen(); + } else { + Testsuite::logDetailedPrintf("Switching to graphics mode %s failed\n", gfxMode->name); + return false; + } + gfxMode++; + maxLimit--; + } + + // Restore Original State + g_system->beginGFXTransaction(); + bool isGFXModeSet = g_system->setGraphicsMode(currGFXMode); + g_system->initSize(320, 200); + OSystem::TransactionError gfxError = g_system->endGFXTransaction(); + + if (gfxError != OSystem::kTransactionSuccess || !isGFXModeSet) { + Testsuite::logDetailedPrintf("Switcing to initial state failed\n"); + return false; + } + + // Done with cursors, Make them invisible, any other test may enable and use it. + CursorMan.showMouse(false); + return true; +} + +bool GFXtests::shakingEffect() { + + Testsuite::clearScreen(); + Common::String info = "Shaking test. You should expect the graphics(text/bars etc) drawn on the screen to shake!"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Shaking Effect\n"); + return true; + } + + Common::Point pt(0, 100); + Testsuite::writeOnScreen("If Shaking Effect works, this should shake!", pt); + int times = 35; + while (times--) { + g_system->setShakePos(25); + g_system->updateScreen(); + g_system->setShakePos(0); + g_system->updateScreen(); + } + g_system->delayMillis(1500); + + if (Testsuite::handleInteractiveInput("Did the Shaking test worked as you were expecting?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Shaking Effect didn't worked"); + return false; + } + return true; +} + +bool GFXtests::focusRectangle() { + + Testsuite::clearScreen(); + Common::String info = "Testing : Setting and hiding Focus \n" + "If this feature is implemented, the focus should be toggled between the two rectangles on the corners"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : focus Rectangle\n"); + return true; + } + + const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont)); + + Graphics::Surface *screen = g_system->lockScreen(); + int screenHeight = g_system->getHeight(); + int screenWidth = g_system->getWidth(); + + int height = font.getFontHeight(); + int width = screenWidth / 2; + + Common::Rect rectLeft(0, 0, width, height * 2); + screen->fillRect(rectLeft, kColorWhite); + font.drawString(screen, "Focus 1", rectLeft.left, rectLeft.top, width, kColorBlack, Graphics::kTextAlignLeft); + + Common::Rect rectRight(screenWidth - width, screenHeight - height * 2 , screenWidth, screenHeight); + screen->fillRect(rectRight, kColorWhite); + font.drawString(screen, "Focus 2", rectRight.left, rectRight.top, width, kColorBlack, Graphics::kTextAlignRight); + g_system->unlockScreen(); + g_system->updateScreen(); + + g_system->clearFocusRectangle(); + + g_system->setFocusRectangle(rectLeft); + g_system->updateScreen(); + + g_system->delayMillis(1000); + + g_system->setFocusRectangle(rectRight); + g_system->updateScreen(); + + g_system->clearFocusRectangle(); + + if (Testsuite::handleInteractiveInput("Did you noticed a variation in focus?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Focus Rectangle feature doesn't works. Check platform.\n"); + } + + return true; +} + +bool GFXtests::overlayGraphics() { + Testsuite::clearScreen(); + Common::String info = "Overlay Graphics. You should expect to see a green colored rectangle on the screen"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Overlay Graphics\n"); + return true; + } + + Graphics::PixelFormat pf = g_system->getOverlayFormat(); + + OverlayColor buffer[50 * 100]; + OverlayColor value = pf.RGBToColor(0, 255, 0); + + for (int i = 0; i < 50 * 100; i++) { + buffer[i] = value; + } + + g_system->showOverlay(); + g_system->copyRectToOverlay(buffer, 100, 270, 175, 100, 50); + g_system->updateScreen(); + + g_system->delayMillis(1000); + + g_system->hideOverlay(); + g_system->updateScreen(); + + if (Testsuite::handleInteractiveInput("Did you see a green overlayed rectangle?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Overlay Rectangle feature doesn't works\n"); + return false; + } + + return true; +} + +bool GFXtests::paletteRotation() { + + Common::String info = "Palette rotation. Here we draw a full 256 colored rainbow and then rotate it.\n" + "Note that the screen graphics change without having to draw anything.\n" + "The palette should appear to rotate, Click the mouse button to exit."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : palette Rotation\n"); + return true; + } + Common::Point pt(0, 10); + Testsuite::clearEntireScreen(); + + // Use 256 colors + byte palette[256 * 4] = {0}; + + int r, g, b; + int colIndx; + + for (int i = 0; i < 256; i++) { + HSVtoRGB(r, g, b, i, 1, 1); + colIndx = i * 4; + palette[colIndx] = r; + palette[colIndx + 1] = g; + palette[colIndx + 2] = b; + } + + // Initialize this palette. + g_system->setPalette(palette, 0, 256); + + // Draw 256 Rectangles, each 1 pixel wide and 10 pixels long + // one for 0-255 color range other for 0-127-255 range + byte buffer[256 * 30] = {0}; + + for (int i = 0; i < 30; i++) { + for (int j = 0; j < 256; j++) { + if (i < 10) { + buffer[i * 256 + j] = j + 2; + } else if (i < 20) { + buffer[i * 256 + j] = 0; + } else { + buffer[i * 256 + j] = ((j + 127) % 256) + 2; + } + } + } + + g_system->copyRectToScreen(buffer, 256, 22, 50, 256, 30); + + // Show mouse + CursorMan.showMouse(true); + g_system->updateScreen(); + + + bool toRotate = true; + Common::Event event; + + while (toRotate) { + while (g_system->getEventManager()->pollEvent(event)) { + if (event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_RBUTTONDOWN) { + toRotate = false; + } + } + + rotatePalette(palette, 256); + + g_system->delayMillis(10); + g_system->setPalette(palette, 0, 256); + g_system->updateScreen(); + } + + CursorMan.showMouse(false); + // Reset initial palettes + GFXTestSuite::setCustomColor(255, 0, 0); + Testsuite::clearScreen(); + + if(Testsuite::handleInteractiveInput("Did you saw a rotation in colors of rectangles displayed on screen?", "Yes", "No", kOptionRight)) { + return false; + } + + return true; +} + +bool GFXtests::cursorTrails() { + Common::String info = "With some shake offset the cursor was known to leave trails in the GUI\n" + "Here we set some offset and ask user to check for mouse trails, \n" + "the test is passed when there are no trails"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Cursor Trails\n"); + return true; + } + bool passed = false; + g_system->setShakePos(25); + g_system->updateScreen(); + if (Testsuite::handleInteractiveInput("Does the cursor leaves trails while moving?", "Yes", "No", kOptionRight)) { + passed = true; + } + g_system->setShakePos(0); + g_system->updateScreen(); + return passed; +} + +bool GFXtests::pixelFormats() { + Testsuite::clearScreen(); + Common::String info = "Testing pixel formats. Here we iterate over all the supported pixel formats and display some colors using them\n" + "This may take long, especially if the backend supports many pixel formats"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : focus Rectangle\n"); + return true; + } + + Common::List<Graphics::PixelFormat> pfList = g_system->getSupportedFormats(); + Common::List<Graphics::PixelFormat>::const_iterator iter = pfList.begin(); + + int numFormatsTested = 0; + int numPassed = 0; + bool numFailed = 0; + + Testsuite::logDetailedPrintf("Testing Pixel Formats. Size of list : %d\n", pfList.size()); + + for (iter = pfList.begin(); iter != pfList.end(); iter++) { + numFormatsTested++; + if (iter->bytesPerPixel == 1) { + // Palettes already tested + continue; + } else if (iter->bytesPerPixel > 2) { + Testsuite::logDetailedPrintf("Can't test pixels with bpp > 2\n"); + continue; + } + + // Switch to that pixel Format + g_system->beginGFXTransaction(); + g_system->initSize(320, 200, &(*iter)); + g_system->endGFXTransaction(); + Testsuite::clearScreen(true); + + // Draw some nice gradients + // Pick up some colors + uint colors[6]; + + colors[0] = iter->RGBToColor(255, 255, 255); + colors[1] = iter->RGBToColor(135, 48, 21); + colors[2] = iter->RGBToColor(205, 190, 87); + colors[3] = iter->RGBToColor(0, 32, 64); + colors[4] = iter->RGBToColor(181, 126, 145); + colors[5] = iter->RGBToColor(47, 78, 36); + + Common::Point pt(0, 170); + Common::String msg; + // XXX: Can use snprintf? + msg = Common::String::printf("Testing Pixel Formats, %d of %d", numFormatsTested, pfList.size()); + Testsuite::writeOnScreen(msg, pt, true); + + // CopyRectToScreen could have been used, but that may involve writing code which + // already resides in graphics/surface.h + // So using Graphics::Surface + + Graphics::Surface *screen = g_system->lockScreen(); + + // Draw 6 rectangles centred at (50, 160), piled over one another + // each with color in colors[] + for (int i = 0; i < 6; i++) { + screen->fillRect(Common::Rect::center(160, 20 + i * 10, 100, 10), colors[i]); + } + + g_system->unlockScreen(); + g_system->updateScreen(); + g_system->delayMillis(500); + + if(Testsuite::handleInteractiveInput("Were you able to notice the colored rectangles on the screen for this format?", "Yes", "No", kOptionLeft)) { + numPassed++; + } else { + numFailed++; + Testsuite::logDetailedPrintf("Testing pixel format failed for format #%d on the list\n", numFormatsTested); + } + } + + // Revert back to 8bpp + g_system->beginGFXTransaction(); + g_system->initSize(320, 200); + g_system->endGFXTransaction(); + GFXTestSuite::setCustomColor(255, 0, 0); + initMousePalette(); + Testsuite::clearScreen(); + + if (numFailed) { + Testsuite::logDetailedPrintf("Pixel Format test: Failed : %d, Passed : %d, Ignored %d\n",numFailed, numPassed, numFormatsTested - (numPassed + numFailed)); + return false; + } + + return true; +} + +} // End of namespace Testbed diff --git a/engines/testbed/graphics.h b/engines/testbed/graphics.h new file mode 100644 index 0000000000..42ab5df59e --- /dev/null +++ b/engines/testbed/graphics.h @@ -0,0 +1,94 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_GRAPHICS_H +#define TESTBED_GRAPHICS_H + +#include "testbed/testsuite.h" + +namespace Testbed { + +namespace GFXtests { + +// Helper functions for GFX tests +void drawEllipse(int x, int y, int a, int b); +void setupMouseLoop(bool disableCursorPalette = false, const char *gfxModeName = "", int cursorTargetScale = 1); +void initMousePalette(); +Common::Rect computeSize(Common::Rect &cursorRect, int scalingFactor, int cursorTargetScale); +void HSVtoRGB(int &rComp, int &gComp, int &bComp, int hue, int sat, int val); +Common::Rect drawCursor(bool cursorPaletteDisabled = false, const char *gfxModeName = "", int cursorTargetScale = 1); + +// will contain function declarations for GFX tests +bool cursorTrails(); +bool fullScreenMode(); +bool aspectRatio(); +bool palettizedCursors(); +bool mouseMovements(); +bool copyRectToScreen(); +bool iconifyWindow(); +bool scaledCursors(); +bool shakingEffect(); +bool focusRectangle(); +bool overlayGraphics(); +bool paletteRotation(); +bool pixelFormats(); +// add more here + +} // End of namespace GFXtests + +class GFXTestSuite : public Testsuite { +public: + /** + * The constructor for the GFXTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + GFXTestSuite(); + ~GFXTestSuite() {} + const char *getName() const { + return "GFX"; + } + const char *getDescription() const { + return "Graphics Subsystem"; + } + static void setCustomColor(uint r, uint g, uint b); + +private: + /** + * A Palette consists of 4 components RGBA. + * As of now we only take 3 colors + * 0 (R:0, G:0, B:0) Black (kColorBlack) + * 1 (R:255, G:255, B:255) White (kColorWhite) + * 2 (R:255, G:255, B:255) your customized color (by default white) (kColorCustom) + * The remaining values are zero + */ + static byte _palette[256 * 4]; +}; + +} // End of namespace Testbed + +#endif // TESTBED_GRAPHICS_H diff --git a/engines/testbed/misc.cpp b/engines/testbed/misc.cpp new file mode 100644 index 0000000000..74f0af2948 --- /dev/null +++ b/engines/testbed/misc.cpp @@ -0,0 +1,172 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "testbed/misc.h" +#include "common/timer.h" + +namespace Testbed { + +Common::String MiscTests::getHumanReadableFormat(TimeDate &td) { + return Common::String::printf("%d:%d:%d on %d/%d/%d (dd/mm/yyyy)", td.tm_hour, td.tm_min, td.tm_sec, td.tm_mday, td.tm_mon + 1, td.tm_year + 1900); +} + +void MiscTests::timerCallback(void *arg) { + // Increment arg which actually points to an int + // arg must point to a static data, threads otherwise have their own stack + int &valToModify = *((int *) arg); + valToModify = 999; // some arbitrary value +} + +void MiscTests::criticalSection(void *arg) { + SharedVars &sv = *((SharedVars *)arg); + + Testsuite::logDetailedPrintf("Before critical section: %d %d\n", sv.first, sv.second); + g_system->lockMutex(sv.mutex); + + // In any case, the two vars must be equal at entry, if mutex works fine. + // verify this here. + if (sv.first != sv.second) { + sv.resultSoFar = false; + } + + sv.first++; + g_system->delayMillis(1000); + + // This should bring no change as well in the difference between vars + // verify this too. + if (sv.second + 1 != sv.first) { + sv.resultSoFar = false; + } + + sv.second *= sv.first; + Testsuite::logDetailedPrintf("After critical section: %d %d\n", sv.first, sv.second); + g_system->unlockMutex(sv.mutex); + + g_system->getTimerManager()->removeTimerProc(criticalSection); +} + +bool MiscTests::testDateTime() { + + if (Testsuite::isSessionInteractive) { + if (Testsuite::handleInteractiveInput("Testing the date time API implementation", "Continue", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Date time tests skipped by the user.\n"); + return true; + } + + Testsuite::writeOnScreen("Verifying Date-Time...", Common::Point(0, 100)); + } + + TimeDate t1, t2; + g_system->getTimeAndDate(t1); + Testsuite::logDetailedPrintf("Current Time and Date: "); + Common::String dateTimeNow; + dateTimeNow = getHumanReadableFormat(t1); + + if (Testsuite::isSessionInteractive) { + // Directly verify date + dateTimeNow = "We expect the current date time to be " + dateTimeNow; + if (Testsuite::handleInteractiveInput(dateTimeNow, "Correct!", "Wrong", kOptionRight)) { + return false; + } + } + + g_system->getTimeAndDate(t1); + dateTimeNow = getHumanReadableFormat(t1); + Testsuite::logDetailedPrintf("%s\n", dateTimeNow.c_str()); + // Now, Put some delay + g_system->delayMillis(2000); + g_system->getTimeAndDate(t2); + Testsuite::logDetailedPrintf("Time and Date 2s later: "); + dateTimeNow = getHumanReadableFormat(t2); + Testsuite::logDetailedPrintf("%s\n", dateTimeNow.c_str()); + + if (t1.tm_year == t2.tm_year && t1.tm_mon == t2.tm_mon && t1.tm_mday == t2.tm_mday) { + if (t1.tm_mon == t2.tm_mon && t1.tm_year == t2.tm_year) { + // Ignore lag due to processing time + if (t1.tm_sec + 2 == t2.tm_sec) { + return true; + } + } + } + return false; +} + +bool MiscTests::testTimers() { + static int valToModify = 0; + if (g_system->getTimerManager()->installTimerProc(timerCallback, 100000, &valToModify)) { + g_system->delayMillis(150); + g_system->getTimerManager()->removeTimerProc(timerCallback); + + if (999 == valToModify) { + return true; + } + } + return false; +} + +bool MiscTests::testMutexes() { + + if (Testsuite::isSessionInteractive) { + if (Testsuite::handleInteractiveInput("Testing the Mutual Exclusion API implementation", "Continue", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Mutex tests skipped by the user.\n"); + return true; + } + Testsuite::writeOnScreen("Installing mutex", Common::Point(0, 100)); + } + + static SharedVars sv = {1, 1, true, g_system->createMutex()}; + + if (g_system->getTimerManager()->installTimerProc(criticalSection, 100000, &sv)) { + g_system->delayMillis(150); + } + + g_system->lockMutex(sv.mutex); + sv.first++; + g_system->delayMillis(1000); + sv.second *= sv.first; + g_system->unlockMutex(sv.mutex); + + // wait till timed process exits + if (Testsuite::isSessionInteractive) { + Testsuite::writeOnScreen("Waiting for 3s so that timed processes finish", Common::Point(0, 100)); + } + g_system->delayMillis(3000); + + Testsuite::logDetailedPrintf("Final Value: %d %d\n", sv.first, sv.second); + g_system->deleteMutex(sv.mutex); + + if (sv.resultSoFar && 6 == sv.second) { + return true; + } + + return false; +} + +MiscTestSuite::MiscTestSuite() { + addTest("Datetime", &MiscTests::testDateTime, false); + addTest("Timers", &MiscTests::testTimers, false); + addTest("Mutexes", &MiscTests::testMutexes, false); +} + +} // End of namespace Testbed diff --git a/engines/testbed/misc.h b/engines/testbed/misc.h new file mode 100644 index 0000000000..1ca01224e5 --- /dev/null +++ b/engines/testbed/misc.h @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_MISC_H +#define TESTBED_MISC_H + +#include "testbed/testsuite.h" + + +namespace Testbed { + +// Shared variables used in mutex handling test +struct SharedVars { + int first; + int second; + bool resultSoFar; + OSystem::MutexRef mutex; +}; + +namespace MiscTests { + +// Miscellaneous tests include testing datetime, timers and mutexes + +// Helper functions for Misc tests +Common::String getHumanReadableFormat(TimeDate &td); +void timerCallback(void *arg); +void criticalSection(void *arg); + +// will contain function declarations for Misc tests +bool testDateTime(); +bool testTimers(); +bool testMutexes(); +// add more here + +} // End of namespace MiscTests + +class MiscTestSuite : public Testsuite { +public: + /** + * The constructor for the MiscTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + MiscTestSuite(); + ~MiscTestSuite() {} + const char *getName() const { + return "Misc"; + } + const char *getDescription() const { + return "Miscellaneous: Timers/Mutexes/Datetime"; + } +}; + +} // End of namespace Testbed + +#endif // TESTBED_MISC_H diff --git a/engines/testbed/module.mk b/engines/testbed/module.mk new file mode 100644 index 0000000000..4c530e412a --- /dev/null +++ b/engines/testbed/module.mk @@ -0,0 +1,24 @@ +MODULE := engines/testbed + +MODULE_OBJS := \ + config.o \ + detection.o \ + events.o \ + fs.o \ + graphics.o \ + misc.o \ + savegame.o \ + sound.o \ + testbed.o \ + testsuite.o + +MODULE_DIRS += \ + engines/testbed + +# This module can be built as a plugin +ifeq ($(ENABLE_TESTBED), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/testbed/savegame.cpp b/engines/testbed/savegame.cpp new file mode 100644 index 0000000000..80c83725d2 --- /dev/null +++ b/engines/testbed/savegame.cpp @@ -0,0 +1,199 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/savefile.h" + +#include "testbed/savegame.h" + +namespace Testbed { + +/** + * This test creates a savefile for the given testbed-state and could be reloaded using the saveFile API. + * It is intended to test saving and loading from savefiles. + */ +bool SaveGametests::writeDataToFile(const char *fileName, const char *msg) { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::OutSaveFile *saveFile = saveFileMan->openForSaving(fileName); + + if (!saveFile) { + Testsuite::logDetailedPrintf("Can't open saveFile %s\n", fileName); + return false; + } + + saveFile->writeString(msg); + saveFile->finalize(); + delete saveFile; + + return true; +} + +bool SaveGametests::readAndVerifyData(const char *fileName, const char *expected) { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::InSaveFile *loadFile = saveFileMan->openForLoading(fileName); + + if (!loadFile) { + Testsuite::logDetailedPrintf("Can't open save File to load\n"); + return false; + } + + Common::String lineToRead = loadFile->readLine(); + delete loadFile; + + if (lineToRead.equals(expected)) { + return true; + } + + return false; +} + +bool SaveGametests::testSaveLoadState() { + // create a savefile with "ScummVM Rocks!" written on it + if (!writeDataToFile("tBedSavefile.0", "ScummVM Rocks!")) { + Testsuite::logDetailedPrintf("Writing data to savefile failed\n"); + return false; + } + + // Verify if it contains the same data + if (!readAndVerifyData("tBedSavefile.0", "ScummVM Rocks!")) { + Testsuite::logDetailedPrintf("Reading data from savefile failed\n"); + return false; + } + + return true; +} + +bool SaveGametests::testRemovingSavefile() { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + + // Create a dummy savefile + if (!writeDataToFile("tBedSavefileToRemove.0", "Dummy Savefile!")) { + Testsuite::logDetailedPrintf("Writing data to savefile failed\n"); + return false; + } + + // Remove it + saveFileMan->removeSavefile("tBedSavefileToRemove.0"); + + // Try opening it Now + Common::InSaveFile *loadFile = saveFileMan->openForLoading("saveFile.0"); + if (loadFile) { + // Removing failed + Testsuite::logDetailedPrintf("Removing savefile failed\n"); + return false; + } + + return true; +} + +bool SaveGametests::testRenamingSavefile() { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + // Open a file for renaming + if (!writeDataToFile("tBedSomeWeirdName.0", "Rename me!")) { + Testsuite::logDetailedPrintf("Writing data to savefile failed\n"); + return false; + } + + // Rename it + saveFileMan->renameSavefile("tBedSomeWeirdName.0", "tBedSomeCoolName.0"); + + // Verify if it contains the same data + if (!readAndVerifyData("tBedSomeCoolName.0", "Rename me!")) { + Testsuite::logDetailedPrintf("Renaming savefile failed\n"); + return false; + } + + return true; +} + +bool SaveGametests::testListingSavefile() { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + saveFileMan->clearError(); + + // create some savefiles + const char *savefileName[] = {"tBedSavefileToList.0", "tBedSavefileToList.1", "tBedSavefileToList.2"}; + writeDataToFile("tBedSavefileToList.0", "Save me!"); + writeDataToFile("tBedSavefileToList.1", "Save me!"); + writeDataToFile("tBedSavefileToList.2", "Save me!"); + + Common::Error error = saveFileMan->getError(); + + if (error != Common::kNoError) { + // Abort. Some Error in writing files + Testsuite::logDetailedPrintf("Error while creating savefiles: %s\n", Common::errorToString(error)); + return false; + } + + Common::StringArray savefileList = saveFileMan->listSavefiles("tBedSavefileToList.?"); + if (savefileList.size() == ARRAYSIZE(savefileName)) { + // Match them exactly + // As the order of savefileList may be platform specific, match them exhaustively + for (uint i = 0; i < ARRAYSIZE(savefileName); i++) { + for (uint j = 0; j < savefileList.size(); j++) { + if (savefileList[j].equals(savefileName[i])) { + break; + } + if (savefileList.size() == j) { + // A match for this name not found + Testsuite::logDetailedPrintf("Listed Names don't match\n"); + return false; + } + } + } + return true; + } else { + Testsuite::logDetailedPrintf("listing Savefiles failed!\n"); + return false; + } + + return false; +} + +bool SaveGametests::testErrorMessages() { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + saveFileMan->clearError(); + + // Try opening a non existing file + readAndVerifyData("tBedSomeNonExistentSaveFile.0", "File doesn't exists!"); + + Common::Error error = saveFileMan->getError(); + if (error == Common::kNoError) { + // blunder! how come? + Testsuite::logDetailedPrintf("SaveFileMan.getError() failed\n"); + return false; + } + // Can't actually predict whether which error, kInvalidPath or kPathDoesNotExist or some other? + // So just return true if some error + Testsuite::logDetailedPrintf("getError returned : %s\n", saveFileMan->getErrorDesc().c_str()); + return true; +} + +SaveGameTestSuite::SaveGameTestSuite() { + addTest("OpeningSaveFile", &SaveGametests::testSaveLoadState, false); + addTest("RemovingSaveFile", &SaveGametests::testRemovingSavefile, false); + addTest("RenamingSaveFile", &SaveGametests::testRenamingSavefile, false); + addTest("ListingSaveFile", &SaveGametests::testListingSavefile, false); + addTest("VerifyErrorMessages", &SaveGametests::testErrorMessages, false); +} + +} // End of namespace Testbed diff --git a/engines/testbed/savegame.h b/engines/testbed/savegame.h new file mode 100644 index 0000000000..98d630d237 --- /dev/null +++ b/engines/testbed/savegame.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_SAVEGAME_H +#define TESTBED_SAVEGAME_H + +#include "testbed/testsuite.h" + +namespace Testbed { + +namespace SaveGametests { + +// Helper functions for SaveGame tests +bool writeDataToFile(const char *fileName, const char *msg); +bool readAndVerifyData(const char *fileName, const char *expected); +// will contain function declarations for SaveGame tests +bool testSaveLoadState(); +bool testRemovingSavefile(); +bool testRenamingSavefile(); +bool testListingSavefile(); +bool testErrorMessages(); +// add more here + +} // End of namespace SaveGametests + +class SaveGameTestSuite : public Testsuite { +public: + /** + * The constructor for the SaveGameTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + SaveGameTestSuite(); + ~SaveGameTestSuite() {} + const char *getName() const { + return "SaveGames"; + } + const char *getDescription() const { + return "Saving Game state tests"; + } +}; + +} // End of namespace Testbed + +#endif // TESTBED_SAVEGAME_H diff --git a/engines/testbed/sound.cpp b/engines/testbed/sound.cpp new file mode 100644 index 0000000000..89aede7ef8 --- /dev/null +++ b/engines/testbed/sound.cpp @@ -0,0 +1,264 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "sound/audiocd.h" +#include "sound/softsynth/pcspk.h" + +#include "testbed/sound.h" + +namespace Testbed { + +enum { + kPlayChannel1 = 'pch1', + kPlayChannel2 = 'pch2', + kPlayChannel3 = 'pch3', + kPauseChannel1 = 'pac1', + kPauseChannel2 = 'pac2', + kPauseChannel3 = 'pac3' +}; + +SoundSubsystemDialog::SoundSubsystemDialog() : TestbedInteractionDialog(80, 60, 400, 170) { + _xOffset = 25; + _yOffset = 0; + Common::String text = "Sound Subsystem Tests: Test Mixing of Audio Streams."; + addText(350, 20, text, Graphics::kTextAlignCenter, _xOffset, 15); + addButton(200, 20, "Play Channel #1", kPlayChannel1); + addButton(200, 20, "Play Channel #2", kPlayChannel2); + addButton(200, 20, "Play Channel #3", kPlayChannel3); + addButton(50, 20, "Close", GUI::kCloseCmd, 160, 15); + + _mixer = g_system->getMixer(); + + // the three streams to be mixed + Audio::PCSpeaker *s1 = new Audio::PCSpeaker(); + Audio::PCSpeaker *s2 = new Audio::PCSpeaker(); + Audio::PCSpeaker *s3 = new Audio::PCSpeaker(); + + s1->play(Audio::PCSpeaker::kWaveFormSine, 1000, -1); + s2->play(Audio::PCSpeaker::kWaveFormSine, 1200, -1); + s3->play(Audio::PCSpeaker::kWaveFormSine, 1400, -1); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_h1, s1); + _mixer->pauseHandle(_h1, true); + + _mixer->playStream(Audio::Mixer::kSpeechSoundType, &_h2, s2); + _mixer->pauseHandle(_h2, true); + + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_h3, s3); + _mixer->pauseHandle(_h3, true); + +} + + +void SoundSubsystemDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { + + switch (cmd) { + case kPlayChannel1: + _buttonArray[0]->setLabel("Pause Channel #1"); + _buttonArray[0]->setCmd(kPauseChannel1); + _mixer->pauseHandle(_h1, false); + break; + case kPlayChannel2: + _buttonArray[1]->setLabel("Pause Channel #2"); + _buttonArray[1]->setCmd(kPauseChannel2); + _mixer->pauseHandle(_h2, false); + break; + case kPlayChannel3: + _buttonArray[2]->setLabel("Pause Channel #3"); + _buttonArray[2]->setCmd(kPauseChannel3); + _mixer->pauseHandle(_h3, false); + break; + case kPauseChannel1: + _buttonArray[0]->setLabel("Play Channel #1"); + _buttonArray[0]->setCmd(kPlayChannel1); + _mixer->pauseHandle(_h1, true); + break; + case kPauseChannel2: + _buttonArray[1]->setLabel("Play Channel #2"); + _buttonArray[1]->setCmd(kPlayChannel2); + _mixer->pauseHandle(_h2, true); + break; + case kPauseChannel3: + _buttonArray[2]->setLabel("Play Channel #3"); + _buttonArray[2]->setCmd(kPlayChannel3); + _mixer->pauseHandle(_h3, true); + break; + default: + _mixer->stopAll(); + GUI::Dialog::handleCommand(sender, cmd, data); + } +} + +bool SoundSubsystem::playBeeps() { + Testsuite::clearScreen(); + bool passed = true; + Common::String info = "Testing Sound Output by generating beeps\n" + "You should hear a left beep followed by a right beep\n"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Play Beeps\n"); + return true; + } + + Audio::PCSpeaker *speaker = new Audio::PCSpeaker(); + Audio::Mixer *mixer = g_system->getMixer(); + Audio::SoundHandle handle; + mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, speaker); + + // Left Beep + Testsuite::writeOnScreen("Left Beep", Common::Point(0, 100)); + mixer->setChannelBalance(handle, -127); + speaker->play(Audio::PCSpeaker::kWaveFormSine, 1000, -1); + g_system->delayMillis(500); + mixer->pauseHandle(handle, true); + + if (Testsuite::handleInteractiveInput("Were you able to hear the left beep?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Left Beep couldn't be detected : Error with Mixer::setChannelBalance()\n"); + passed = false; + } + + // Right Beep + Testsuite::writeOnScreen("Right Beep", Common::Point(0, 100)); + mixer->setChannelBalance(handle, 127); + mixer->pauseHandle(handle, false); + g_system->delayMillis(500); + mixer->stopAll(); + + if (Testsuite::handleInteractiveInput("Were you able to hear the right beep?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Right Beep couldn't be detected : Error with Mixer::setChannelBalance()\n"); + passed = false; + } + return passed; +} + +bool SoundSubsystem::mixSounds() { + Testsuite::clearScreen(); + bool passed = true; + Common::String info = "Testing Mixer Output by generating multichannel sound output using PC speaker emulator.\n" + "The mixer should be able to play them simultaneously\n"; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : Mix Sounds\n"); + return true; + } + + SoundSubsystemDialog sDialog; + sDialog.runModal(); + if (Testsuite::handleInteractiveInput("Was the mixer able to simultaneously play multiple channels?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Multiple channels couldn't be played : Error with Mixer Class\n"); + passed = false; + } + return passed; +} + +bool SoundSubsystem::audiocdOutput() { + Testsuite::clearScreen(); + bool passed = true; + Common::String info = "Testing AudioCD API implementation.\n" + "Here we have four tracks, we play them in order i.e 1-2-3-last.\n" + "The user should verify if the tracks were run in correct order or not."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : AudioCD API\n"); + return true; + } + + Common::Point pt(0, 100); + Testsuite::writeOnScreen("Playing the tracks of testCD in order i.e 1-2-3-last", pt); + + // Make audio-files discoverable + Common::FSNode gameRoot(ConfMan.get("path")); + SearchMan.addSubDirectoryMatching(gameRoot, "audiocd-files"); + + // Play all tracks + for (int i = 1; i < 5; i++) { + AudioCD.play(i, 1, 0, 0); + while (AudioCD.isPlaying()) { + g_system->delayMillis(500); + Testsuite::writeOnScreen(Common::String::printf("Playing Now: track%02d", i), pt); + } + g_system->delayMillis(500); + } + + Testsuite::clearScreen(); + if (Testsuite::handleInteractiveInput("Were all the tracks played in order i.e 1-2-3-last ?", "Yes", "No", kOptionRight)) { + Testsuite::logPrintf("Error! Error in AudioCD.play() or probably sound files were not detected, try -d1 (debuglevel 1)\n"); + passed = false; + } + + return passed; +} + +bool SoundSubsystem::sampleRates() { + bool passed = true; + Audio::Mixer *mixer = g_system->getMixer(); + + Audio::PCSpeaker *s1 = new Audio::PCSpeaker(); + // Stream at half sampling rate + Audio::PCSpeaker *s2 = new Audio::PCSpeaker(s1->getRate() - 10000); + // Stream at twice sampling rate + Audio::PCSpeaker *s3 = new Audio::PCSpeaker(s1->getRate() + 10000); + + s1->play(Audio::PCSpeaker::kWaveFormSine, 1000, -1); + s2->play(Audio::PCSpeaker::kWaveFormSine, 1000, -1); + s3->play(Audio::PCSpeaker::kWaveFormSine, 1000, -1); + + Audio::SoundHandle handle; + Common::Point pt(0, 100); + + mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, s1); + Testsuite::writeOnScreen(Common::String::printf("Playing at smaple rate: %d", s1->getRate()), pt); + g_system->delayMillis(1000); + mixer->stopHandle(handle); + g_system->delayMillis(1000); + + mixer->playStream(Audio::Mixer::kSpeechSoundType, &handle, s2); + Testsuite::writeOnScreen(Common::String::printf("Playing at sample rate : %d", s2->getRate()), pt); + g_system->delayMillis(1000); + mixer->stopHandle(handle); + g_system->delayMillis(1000); + + mixer->playStream(Audio::Mixer::kSFXSoundType, &handle, s3); + Testsuite::writeOnScreen(Common::String::printf("Playing at sample rate : %d", s3->getRate()), pt); + g_system->delayMillis(1000); + mixer->stopHandle(handle); + g_system->delayMillis(1000); + + Testsuite::clearScreen(); + if (Testsuite::handleInteractiveInput("Was the mixer able to play beeps with variable sample rates?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Error with variable sample rates\n"); + passed = false; + } + + return passed; +} + +SoundSubsystemTestSuite::SoundSubsystemTestSuite() { + addTest("SimpleBeeps", &SoundSubsystem::playBeeps, true); + addTest("MixSounds", &SoundSubsystem::mixSounds, true); + addTest("AudiocdOutput", &SoundSubsystem::audiocdOutput, true); + addTest("SampleRates", &SoundSubsystem::sampleRates, true); +} + +} // End of namespace Testbed diff --git a/engines/testbed/sound.h b/engines/testbed/sound.h new file mode 100644 index 0000000000..53ff96cedb --- /dev/null +++ b/engines/testbed/sound.h @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_SOUND_H +#define TESTBED_SOUND_H + +#include "gui/dialog.h" +#include "sound/mixer.h" +#include "testbed/config.h" +#include "testbed/testsuite.h" + +namespace Testbed { + +class SoundSubsystemDialog : public TestbedInteractionDialog { +public: + SoundSubsystemDialog(); + ~SoundSubsystemDialog() {} + void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); + Audio::Mixer *_mixer; + Audio::SoundHandle _h1, _h2, _h3; +}; + +namespace SoundSubsystem { + +// Helper functions for SoundSubsystem tests + +// will contain function declarations for SoundSubsystem tests +bool playBeeps(); +bool mixSounds(); +bool audiocdOutput(); +bool sampleRates(); +} + +class SoundSubsystemTestSuite : public Testsuite { +public: + /** + * The constructor for the SoundSubsystemTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + SoundSubsystemTestSuite(); + ~SoundSubsystemTestSuite() {} + + const char *getName() const { + return "SoundSubsystem"; + } + + const char *getDescription() const { + return "Sound Subsystem"; + } + +}; + +} // End of namespace Testbed + +#endif // TESTBED_SOUND_H diff --git a/engines/testbed/template.h b/engines/testbed/template.h new file mode 100644 index 0000000000..a2efca1157 --- /dev/null +++ b/engines/testbed/template.h @@ -0,0 +1,67 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_TEMPLATE_H +#define TESTBED_TEMPLATE_H + +#include "testbed/testsuite.h" + +// This file can be used as template for header files of other newer testsuites. + +namespace Testbed { + +namespace XXXtests { + +// Helper functions for XXX tests + +// will contain function declarations for XXX tests +// add more here + +} // End of namespace XXXtests + +class XXXTestSuite : public Testsuite { +public: + /** + * The constructor for the XXXTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + XXXTestSuite(); + ~XXXTestSuite() {} + const char *getName() const { + return "Dummy Template"; + } + + const char *getDescription() const { + return "Some Arbit description"; + } + +}; + +} // End of namespace Testbed + +#endif // TESTBED_TEMPLATE_H diff --git a/engines/testbed/testbed.cpp b/engines/testbed/testbed.cpp new file mode 100644 index 0000000000..ef61cd7c13 --- /dev/null +++ b/engines/testbed/testbed.cpp @@ -0,0 +1,169 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/debug-channels.h" +#include "common/scummsys.h" +#include "common/system.h" + +#include "engines/util.h" + +#include "testbed/events.h" +#include "testbed/fs.h" +#include "testbed/graphics.h" +#include "testbed/misc.h" +#include "testbed/savegame.h" +#include "testbed/sound.h" +#include "testbed/testbed.h" + +namespace Testbed { + +void TestbedExitDialog::init() { + _xOffset = 25; + _yOffset = 0; + Common::String text = "Thank you for using ScummVM testbed! Here are yor summarized results:"; + addText(450, 20, text, Graphics::kTextAlignCenter, _xOffset, 15); + Common::Array<Common::String> strArray; + + for (Common::Array<Testsuite *>::const_iterator i = _testsuiteList.begin(); i != _testsuiteList.end(); ++i) { + strArray.push_back(Common::String::printf("%s (%d/%d tests failed)", (*i)->getName(), (*i)->getNumTestsFailed(), + (*i)->getNumTestsEnabled())); + } + + addList(0, _yOffset, 500, 200, strArray); + text = "More Details can be viewed in the Log file : " + Testsuite::getLogFile(); + addText(450, 20, text, Graphics::kTextAlignLeft, 0, 0); + text = "Directory : " + Testsuite::getLogDir(); + addText(500, 20, text, Graphics::kTextAlignLeft, 0, 0); + _yOffset += 5; + addButtonXY(_xOffset + 80, _yOffset, 120, 20, "Rerun Tests", kCmdRerunTestbed); + addButtonXY(_xOffset + 240, _yOffset, 60, 20, "Close", GUI::kCloseCmd); +} + +void TestbedExitDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { + switch (cmd) { + case kCmdRerunTestbed : + _rerun = true; + GUI::Dialog::close(); + default: + GUI::Dialog::handleCommand(sender, cmd, data); + } +} + +bool TestbedEngine::hasFeature(EngineFeature f) const { + return (f == kSupportsRTL) ? true : false; +} + +TestbedEngine::TestbedEngine(OSystem *syst) + : Engine(syst) { + // Put your engine in a sane state, but do nothing big yet; + // in particular, do not load data from files; rather, if you + // need to do such things, do them from init(). + + // Do not initialize graphics here + + // However this is the place to specify all default directories + + DebugMan.addDebugChannel(kTestbedLogOutput, "LOG", "Log of test results generated by testbed"); + DebugMan.addDebugChannel(kTestbedEngineDebug, "Debug", "Engine-specific debug statements"); + DebugMan.enableDebugChannel("LOG"); + + // Initialize testsuites here + // GFX + Testsuite *ts = new GFXTestSuite(); + _testsuiteList.push_back(ts); + // FS + ts = new FSTestSuite(); + _testsuiteList.push_back(ts); + // Savegames + ts = new SaveGameTestSuite(); + _testsuiteList.push_back(ts); + // Misc. + ts = new MiscTestSuite(); + _testsuiteList.push_back(ts); + // Events + ts = new EventTestSuite(); + _testsuiteList.push_back(ts); + // Sound + ts = new SoundSubsystemTestSuite(); + _testsuiteList.push_back(ts); +} + +TestbedEngine::~TestbedEngine() { + Testsuite::deleteWriteStream(); + // Remove all of our debug levels here + DebugMan.clearAllDebugChannels(); + + for (Common::Array<Testsuite *>::const_iterator i = _testsuiteList.begin(); i != _testsuiteList.end(); ++i) { + delete (*i); + } +} + +void TestbedEngine::invokeTestsuites(TestbedConfigManager &cfMan) { + Common::Array<Testsuite *>::const_iterator iter; + uint count = 1; + Common::Point pt = Testsuite::getDisplayRegionCoordinates(); + int numSuitesEnabled = cfMan.getNumSuitesEnabled(); + + for (iter = _testsuiteList.begin(); iter != _testsuiteList.end(); iter++) { + (*iter)->reset(); + if ((*iter)->isEnabled()) { + Testsuite::updateStats("Testsuite", (*iter)->getName(), count++, numSuitesEnabled, pt); + (*iter)->execute(); + } + } +} + +Common::Error TestbedEngine::run() { + // Initialize graphics using following: + initGraphics(320, 200, false); + + // As of now we are using GUI::MessageDialog for interaction, Test if it works. + // interactive mode could also be modified by a config parameter "non-interactive=1" + // TODO: Implement that + + TestbedConfigManager cfMan(_testsuiteList, "testbed.config"); + + // Keep running if rerun requested + TestbedExitDialog tbDialog(_testsuiteList); + + do { + Testsuite::clearEntireScreen(); + cfMan.selectTestsuites(); + // Init logging + Testsuite::initLogging(true); + // Check if user wanted to exit. + if (Engine::shouldQuit()) { + return Common::kNoError; + } + + invokeTestsuites(cfMan); + tbDialog.init(); + tbDialog.run(); + + } while (tbDialog.rerunRequired()); + + return Common::kNoError; +} + +} // End of namespace Testbed diff --git a/engines/testbed/testbed.h b/engines/testbed/testbed.h new file mode 100644 index 0000000000..3959865cfd --- /dev/null +++ b/engines/testbed/testbed.h @@ -0,0 +1,85 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_H +#define TESTBED_H + +#include "engines/engine.h" + +#include "gui/options.h" + +#include "testbed/config.h" +#include "testbed/testsuite.h" + +namespace Testbed { + +class TestbedConfigManager; + +enum { + kTestbedLogOutput = 1 << 0, + kTestbedEngineDebug = 1 << 2, + kCmdRerunTestbed = 'crtb' +}; + +class TestbedEngine : public Engine { +public: + TestbedEngine(OSystem *syst); + ~TestbedEngine(); + + virtual Common::Error run(); + + /** + * Invokes configured testsuites. + */ + void invokeTestsuites(TestbedConfigManager &cfMan); + + bool hasFeature(EngineFeature f) const; + +private: + Common::Array<Testsuite *> _testsuiteList; +}; + +class TestbedExitDialog : public TestbedInteractionDialog { +public: + TestbedExitDialog(Common::Array<Testsuite *> &testsuiteList) : TestbedInteractionDialog(80, 60, 500, 320), _rerun(false), + _testsuiteList(testsuiteList) {} + ~TestbedExitDialog() {} + void init(); + void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); + void run() { runModal(); } + bool rerunRequired() { + if (_rerun) { + _rerun = false; + return true; + } + return false; + } +private: + bool _rerun; + Common::Array<Testsuite *> &_testsuiteList; +}; + +} // End of namespace Testbed + +#endif // TESTBED_H diff --git a/engines/testbed/testsuite.cpp b/engines/testbed/testsuite.cpp new file mode 100644 index 0000000000..efd31c051d --- /dev/null +++ b/engines/testbed/testsuite.cpp @@ -0,0 +1,373 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/config-manager.h" +#include "common/events.h" +#include "common/stream.h" + +#include "graphics/fontman.h" +#include "graphics/surface.h" + +#include "gui/message.h" + +#include "testbed/graphics.h" +#include "testbed/testbed.h" +#include "testbed/testsuite.h" + +namespace Testbed { + +// Static public variable of Testsuite +bool Testsuite::isSessionInteractive = true; + +// Static private variable of Testsuite +Common::String Testsuite::_logDirectory = ""; +Common::String Testsuite::_logFilename = ""; +Graphics::FontManager::FontUsage Testsuite::_displayFont = Graphics::FontManager::kGUIFont; +Common::WriteStream *Testsuite::_ws = 0; +uint Testsuite::toQuit = kLoopNormal; + +void Testsuite::setLogDir(const char *dirname) { + _logDirectory = dirname; +} + +void Testsuite::setLogFile(const char *filename) { + _logFilename = filename; +} + +void Testsuite::deleteWriteStream() { + if (_ws) { + delete _ws; + } +} + +void Testsuite::initLogging(const char *logdir, const char *filename, bool enable) { + setLogDir(logdir); + setLogFile(filename); + + if (enable) { + _ws = Common::FSNode(_logDirectory).getChild(_logFilename).createWriteStream(); + } else { + _ws = 0; + } +} + +void Testsuite::initLogging(bool enable) { + setLogDir(ConfMan.get("path").c_str()); + setLogFile("testbed.log"); + + if (enable) { + _ws = Common::FSNode(_logDirectory).getChild(_logFilename).createWriteStream(); + } else { + _ws = 0; + } +} + +void Testsuite::logPrintf(const char *fmt, ...) { + // Assuming log message size to be not greater than STRINGBUFLEN i.e 256 + char buffer[STRINGBUFLEN]; + va_list vl; + va_start(vl, fmt); + vsnprintf(buffer, STRINGBUFLEN, fmt, vl); + va_end(vl); + + if (_ws) { + _ws->writeString(buffer); + debugCN(kTestbedLogOutput, "%s", buffer); + } else { + debugCN(kTestbedLogOutput, "%s", buffer); + } +} + +void Testsuite::logDetailedPrintf(const char *fmt, ...) { + // Assuming log message size to be not greater than STRINGBUFLEN i.e 256 + // Messages with this function would only be displayed if -d1 is specified on command line + char buffer[STRINGBUFLEN]; + va_list vl; + va_start(vl, fmt); + vsnprintf(buffer, STRINGBUFLEN, fmt, vl); + va_end(vl); + + if (_ws) { + _ws->writeString(buffer); + debugCN(1, kTestbedLogOutput, "%s", buffer); + } else { + debugCN(1, kTestbedLogOutput, "%s", buffer); + } +} + +Testsuite::Testsuite() { + _numTestsPassed = 0; + _numTestsExecuted = 0; + // Initially all testsuites are enabled, disable them by calling enableTestSuite(name, false) + _isTsEnabled = true; + // Set custom color for progress bar + GFXTestSuite::setCustomColor(0, 0, 0); +} + +Testsuite::~Testsuite() { + for (Common::Array<Test *>::iterator i = _testsToExecute.begin(); i != _testsToExecute.end(); ++i) { + delete (*i); + } +} + +void Testsuite::reset() { + _numTestsPassed = 0; + _numTestsExecuted = 0; + toQuit = kLoopNormal; + for (Common::Array<Test *>::iterator i = _testsToExecute.begin(); i != _testsToExecute.end(); ++i) { + (*i)->passed = false; + } +} + +void Testsuite::genReport() const { + logPrintf("\n"); + logPrintf("Consolidating results...\n"); + logPrintf("Subsystem: %s ", getName()); + logPrintf("(Tests Executed: %d)\n", _numTestsExecuted); + logPrintf("Passed: %d ", _numTestsPassed); + logPrintf("Failed: %d\n", getNumTestsFailed()); + logPrintf("\n"); +} + +bool Testsuite::handleInteractiveInput(const Common::String &textToDisplay, const char *opt1, const char *opt2, OptionSelected result) { + GUI::MessageDialog prompt(textToDisplay, opt1, opt2); + return prompt.runModal() == result ? true : false; +} + +void Testsuite::displayMessage(const Common::String &textToDisplay, const char *defaultButton, const char *altButton) { + GUI::MessageDialog prompt(textToDisplay, defaultButton); + prompt.runModal(); +} + +Common::Rect Testsuite::writeOnScreen(const Common::String &textToDisplay, const Common::Point &pt, bool flag) { + const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont)); + uint fillColor = kColorBlack; + uint textColor = kColorWhite; + + Graphics::Surface *screen = g_system->lockScreen(); + + int height = font.getFontHeight(); + int width = screen->w; + + Common::Rect rect(pt.x, pt.y, pt.x + width, pt.y + height); + + if (flag) { + Graphics::PixelFormat pf = g_system->getScreenFormat(); + fillColor = pf.RGBToColor(0, 0, 0); + textColor = pf.RGBToColor(255, 255, 255); + } + + screen->fillRect(rect, fillColor); + font.drawString(screen, textToDisplay, rect.left, rect.top, screen->w, textColor, Graphics::kTextAlignCenter); + + g_system->unlockScreen(); + g_system->updateScreen(); + + return rect; +} + +void Testsuite::clearScreen(const Common::Rect &rect) { + Graphics::Surface *screen = g_system->lockScreen(); + + screen->fillRect(rect, kColorBlack); + + g_system->unlockScreen(); + g_system->updateScreen(); +} + +void Testsuite::clearScreen() { + int numBytesPerLine = g_system->getWidth() * g_system->getScreenFormat().bytesPerPixel; + int height = getDisplayRegionCoordinates().y; + + // Don't clear test info display region + int size = height * numBytesPerLine; + byte *buffer = new byte[size]; + memset(buffer, 0, size); + g_system->copyRectToScreen(buffer, numBytesPerLine, 0, 0, g_system->getWidth(), height); + g_system->updateScreen(); + delete[] buffer; +} + +void Testsuite::clearScreen(bool flag) { + Graphics::Surface *screen = g_system->lockScreen(); + uint fillColor = kColorBlack; + + if (flag) { + fillColor = g_system->getScreenFormat().RGBToColor(0, 0, 0); + } + + screen->fillRect(Common::Rect(0, 0, g_system->getWidth(), g_system->getHeight()), fillColor); + + g_system->unlockScreen(); + g_system->updateScreen(); +} + +void Testsuite::addTest(const Common::String &name, InvokingFunction f, bool isInteractive) { + Test *featureTest = new Test(name, f, isInteractive); + _testsToExecute.push_back(featureTest); +} + +int Testsuite::getNumTestsEnabled() { + int count = 0; + Common::Array<Test *>::const_iterator iter; + + if (!isEnabled()) { + return 0; + } + + for (iter = _testsToExecute.begin(); iter != _testsToExecute.end(); iter++) { + if ((*iter)->enabled) { + count++; + } + } + return count; +} + +uint Testsuite::parseEvents() { + uint startTime = g_system->getMillis(); + uint end = startTime + kEventHandlingTime; + do { + Common::Event ev; + while (g_system->getEventManager()->pollEvent(ev)) { + switch (ev.type) { + case Common::EVENT_KEYDOWN: + if (ev.kbd.keycode == Common::KEYCODE_ESCAPE) { + return kSkipNext; + } + break; + case Common::EVENT_QUIT: + case Common::EVENT_RTL: + return kEngineQuit; + break; + default: + break; + } + } + g_system->delayMillis(10); + startTime = g_system->getMillis(); + } while (startTime <= end); + + return kLoopNormal; +} + +void Testsuite::updateStats(const char *prefix, const char *info, uint testNum, uint numTests, Common::Point pt) { + Common::String text = Common::String::printf(" Running %s: %s (%d of %d) ", prefix, info, testNum, numTests); + writeOnScreen(text, pt); + uint barColor = kColorSpecial; + // below the text a rectangle denoting the progress in the testsuite can be drawn. + int separation = getLineSeparation(); + pt.y += separation; + int wRect = 200; + int lRect = 7; + pt.x = g_system->getWidth() / 2 - 100; + byte *buffer = new byte[lRect * wRect]; + memset(buffer, 0, sizeof(byte) * lRect * wRect); + + int wShaded = (int) (wRect * (((float)testNum) / numTests)); + + // draw the boundary + memset(buffer, barColor, sizeof(byte) * wRect); + memset(buffer + (wRect * (lRect - 1)) , barColor, sizeof(byte) * wRect); + + for (int i = 0; i < lRect; i++) { + for (int j = 0; j < wRect; j++) { + if (j < wShaded) { + buffer[i * wRect + j] = barColor; + } + } + buffer[i * wRect + 0] = barColor; + buffer[i * wRect + wRect - 1] = barColor; + } + g_system->copyRectToScreen(buffer, wRect, pt.x, pt.y, wRect, lRect); + g_system->updateScreen(); + delete[] buffer; +} + +bool Testsuite::enableTest(const Common::String &testName, bool toEnable) { + for (uint i = 0; i < _testsToExecute.size(); i++) { + if (_testsToExecute[i]->featureName.equalsIgnoreCase(testName)) { + _testsToExecute[i]->enabled = toEnable; + return true; + } + } + return false; +} + + +void Testsuite::execute() { + // Main Loop for a testsuite + + // Do nothing if meant to exit + if (toQuit == kEngineQuit) { + return; + } + + uint count = 0; + Common::Point pt = getDisplayRegionCoordinates(); + pt.y += getLineSeparation(); + int numEnabledTests = getNumTestsEnabled(); + + for (Common::Array<Test *>::iterator i = _testsToExecute.begin(); i != _testsToExecute.end(); ++i) { + if (!(*i)->enabled) { + logPrintf("Info! Skipping Test: %s, Skipped by configuration.\n", ((*i)->featureName).c_str()); + continue; + } + + if (toQuit == kSkipNext) { + logPrintf("Info! Skipping Test: %s, Skipped by user.\n", ((*i)->featureName).c_str()); + toQuit = kLoopNormal; + continue; + } + + if((*i)->isInteractive && !isSessionInteractive) { + logPrintf("Info! Skipping Test: %s, non-interactive environment is selected\n", ((*i)->featureName).c_str()); + continue; + } + + logPrintf("Info! Executing Test: %s\n", ((*i)->featureName).c_str()); + updateStats("Test", ((*i)->featureName).c_str(), count++, numEnabledTests, pt); + _numTestsExecuted++; + if ((*i)->driver()) { + logPrintf("Result: Passed\n"); + _numTestsPassed++; + } else { + logPrintf("Result: Failed\n"); + } + updateStats("Test", ((*i)->featureName).c_str(), count, numEnabledTests, pt); + // TODO: Display a screen here to user with details of upcoming test, he can skip it or Quit or RTL + // Check if user wants to quit/RTL/Skip next test by parsing events. + // Quit directly if explicitly requested + + if (Engine::shouldQuit()) { + toQuit = kEngineQuit; + genReport(); + return; + } + + toQuit = parseEvents(); + } + genReport(); +} + +} // End of namespace Testebed diff --git a/engines/testbed/testsuite.h b/engines/testbed/testsuite.h new file mode 100644 index 0000000000..bd05f36e87 --- /dev/null +++ b/engines/testbed/testsuite.h @@ -0,0 +1,214 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef TESTBED_TESTSUITE_H +#define TESTBED_TESTSUITE_H + +#include "common/system.h" +#include "common/str.h" +#include "common/array.h" + +#include "graphics/fontman.h" + +namespace Testbed { + +enum { + kColorBlack = 0, + kColorWhite = 1, + kColorCustom = 2, + kColorSpecial = 5 ///< some random number +}; + +enum OptionSelected { + kOptionLeft = 1, + kOptionRight = 0 +}; + +enum { + kEngineQuit = 0, + kSkipNext = 1, + kLoopNormal = 2, + // Event handling time,(in ms) used in parseEvent() + kEventHandlingTime = 50 +}; + +typedef bool (*InvokingFunction)(); + +/** + * This represents a feature to be tested + */ + +struct Test { + Test(Common::String name, InvokingFunction f, bool interactive) : featureName(name) { + driver = f; + enabled = true; + passed = false; + isInteractive = interactive; + } + const Common::String featureName; ///< Name of feature to be tested + InvokingFunction driver; ///< Pointer to the function that will invoke this feature test + bool enabled; ///< Decides whether or not this test is to be executed + bool passed; ///< Collects and stores result of this feature test + bool isInteractive; ///< Decides if the test is interactive or not, An interactive testsuite may have non-interactive tests, hence this change. +}; + + +/** + * The basic Testsuite class + * All the other testsuites would inherit it and override its virtual methods + */ + +class Testsuite { +public: + Testsuite(); + virtual ~Testsuite(); + int getNumTests() const { return _testsToExecute.size(); } + int getNumTestsPassed() const { return _numTestsPassed; } + int getNumTestsFailed() const { return _numTestsExecuted - _numTestsPassed; } + void genReport() const; + bool isEnabled() const { return _isTsEnabled; } + virtual void enable(bool flag) { + _isTsEnabled = flag; + } + bool enableTest(const Common::String &testName, bool enable); + void reset(); + + /** + * Prompts for User Input in form of "Yes" or "No" for interactive tests + * e.g: "Is this like you expect?" "Yes" or "No" + * + * @param textToDisplay Display text + * @return true if "Yes" false otherwise + */ + static bool handleInteractiveInput(const Common::String &textToDisplay, const char *opt1 = "Yes", const char *opt2 = "No", OptionSelected result = kOptionLeft); + + static void displayMessage(const Common::String &textToDisplay, const char *defaultButton = "OK", const char *altButton = 0); + static Common::Rect writeOnScreen(const Common::String &textToDisplay, const Common::Point &pt, bool flag = false); + static void clearScreen(const Common::Rect &rect); + static void clearEntireScreen() { + const int width = g_system->getWidth(); + const int height = g_system->getHeight(); + Common::Rect r(0, 0, width, height); + clearScreen(r); + } + static void clearScreen(); + static void clearScreen(bool flag); + + /** + * Adds a test to the list of tests to be executed + * + * @param name the string description of the test, for display purposes + * @param f pointer to the function that invokes this test + * @param isInteractive decides if the test is to be executed in interactive mode/ default true + */ + void addTest(const Common::String &name, InvokingFunction f, bool isInteractive = true); + + /** + * The driver function for the testsuite + * All code should go in here. + */ + virtual void execute(); + static uint parseEvents(); + + virtual const char *getName() const = 0; + virtual const char *getDescription() const = 0; + + static void logPrintf(const char *s, ...) GCC_PRINTF(1, 2); + static void logDetailedPrintf(const char *s, ...) GCC_PRINTF(1, 2); + /** + * Note: To enable logging, this function must be called once first. + */ + static void initLogging(const char *dirname, const char *filename, bool enable = true); + static void initLogging(bool enable = true); + static void setLogDir(const char *dirname); + static void setLogFile(const char *filename); + static Common::String getLogDir() { return _logDirectory; } + static Common::String getLogFile() { return _logFilename; } + + static void deleteWriteStream(); + + // Progress bar (Information Display) related methods. + /** + * Display region is in the bottom. Probably 1/4th of the game screen. + * It contains: + * 1) Information about executing testsuite. + * 2) Total progress within this testsuite. + * 3) Total overall progress in the number of testsuites + */ + + static Common::Point getDisplayRegionCoordinates() { + Common::Point pt(0, 0); + // start from bottom + pt.y = g_system->getHeight(); + // Will Contain 3 lines + pt.y -= (FontMan.getFontByUsage(_displayFont)->getFontHeight() * 3 + 15); // Buffer of 5 pixels per line + return pt; + } + + static uint getLineSeparation() { + return FontMan.getFontByUsage(_displayFont)->getFontHeight() + 5; + } + static Graphics::FontManager::FontUsage getCurrentFontUsageType() { return _displayFont; } + static void setCurrentFontUsageType(Graphics::FontManager::FontUsage f) { _displayFont = f; } + + static void updateStats(const char *prefix, const char *info, uint numTests, uint testNum, Common::Point pt); + const Common::Array<Test *>& getTestList() { return _testsToExecute; } + int getNumTestsEnabled(); + +protected: + Common::Array<Test *> _testsToExecute; ///< List of tests to be executed + int _numTestsPassed; ///< Number of tests passed + int _numTestsExecuted; ///< Number of tests executed + bool _isTsEnabled; + +public: + + /** + * Static variable of this class that determines if the user initiated testing session is interactive or not. + * Used by various tests to respond accordingly + */ + static bool isSessionInteractive; + + /** + * Used from the code to decide if the engine needs to exit + */ + static uint toQuit; + +private: + /** + * Private variables related to logging files + */ + static Common::String _logDirectory; + static Common::String _logFilename; + static Common::WriteStream *_ws; + + /** + * Private variable used for font + */ + static Graphics::FontManager::FontUsage _displayFont; +}; + +} // End of namespace Testbed + +#endif // TESTBED_TESTSUITE_H |