aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--base/plugins.cpp3
-rwxr-xr-xconfigure1
-rwxr-xr-xdists/engine-data/create-testbed-data.sh52
-rwxr-xr-xdists/engine-data/testbed-audiocd-files/track01.mp3bin0 -> 15300 bytes
-rwxr-xr-xdists/engine-data/testbed-audiocd-files/track02.mp3bin0 -> 15927 bytes
-rwxr-xr-xdists/engine-data/testbed-audiocd-files/track03.mp3bin0 -> 11389 bytes
-rwxr-xr-xdists/engine-data/testbed-audiocd-files/track04.mp3bin0 -> 16136 bytes
-rw-r--r--engines/engines.mk5
-rw-r--r--engines/testbed/config.cpp292
-rw-r--r--engines/testbed/config.h135
-rw-r--r--engines/testbed/detection.cpp92
-rw-r--r--engines/testbed/events.cpp294
-rw-r--r--engines/testbed/events.h67
-rw-r--r--engines/testbed/fs.cpp169
-rw-r--r--engines/testbed/fs.h74
-rw-r--r--engines/testbed/graphics.cpp1102
-rw-r--r--engines/testbed/graphics.h94
-rw-r--r--engines/testbed/misc.cpp172
-rw-r--r--engines/testbed/misc.h80
-rw-r--r--engines/testbed/module.mk24
-rw-r--r--engines/testbed/savegame.cpp199
-rw-r--r--engines/testbed/savegame.h69
-rw-r--r--engines/testbed/sound.cpp264
-rw-r--r--engines/testbed/sound.h80
-rw-r--r--engines/testbed/template.h67
-rw-r--r--engines/testbed/testbed.cpp169
-rw-r--r--engines/testbed/testbed.h85
-rw-r--r--engines/testbed/testsuite.cpp373
-rw-r--r--engines/testbed/testsuite.h214
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
diff --git a/configure b/configure
index f380b94ff4..5f6148c597 100755
--- a/configure
+++ b/configure
@@ -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
new file mode 100755
index 0000000000..53d057ee96
--- /dev/null
+++ b/dists/engine-data/testbed-audiocd-files/track01.mp3
Binary files differ
diff --git a/dists/engine-data/testbed-audiocd-files/track02.mp3 b/dists/engine-data/testbed-audiocd-files/track02.mp3
new file mode 100755
index 0000000000..daf8e4860d
--- /dev/null
+++ b/dists/engine-data/testbed-audiocd-files/track02.mp3
Binary files differ
diff --git a/dists/engine-data/testbed-audiocd-files/track03.mp3 b/dists/engine-data/testbed-audiocd-files/track03.mp3
new file mode 100755
index 0000000000..1ef385d640
--- /dev/null
+++ b/dists/engine-data/testbed-audiocd-files/track03.mp3
Binary files differ
diff --git a/dists/engine-data/testbed-audiocd-files/track04.mp3 b/dists/engine-data/testbed-audiocd-files/track04.mp3
new file mode 100755
index 0000000000..7607087f08
--- /dev/null
+++ b/dists/engine-data/testbed-audiocd-files/track04.mp3
Binary files differ
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