Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

1
engines/testbed/POTFILES Normal file
View File

@@ -0,0 +1 @@
engines/testbed/metaengine.cpp

568
engines/testbed/cloud.cpp Normal file
View File

@@ -0,0 +1,568 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/stream.h"
#include "common/util.h"
#include "testbed/fs.h"
#include "testbed/cloud.h"
#include "backends/cloud/cloudmanager.h"
namespace Testbed {
CloudTestSuite::CloudTestSuite() {
// Cloud tests depend on CloudMan.
// If there is no Storage connected to it, disable this test suite.
if (CloudMan.getCurrentStorage() == nullptr) {
logPrintf("WARNING! : No Storage connected to CloudMan found. Skipping Cloud tests\n");
Testsuite::enable(false);
}
addTest("UserInfo", &CloudTests::testInfo, true);
addTest("ListDirectory", &CloudTests::testDirectoryListing, true);
addTest("CreateDirectory", &CloudTests::testDirectoryCreating, true);
addTest("FileUpload", &CloudTests::testUploading, true);
addTest("FileDownload", &CloudTests::testDownloading, true);
addTest("FolderDownload", &CloudTests::testFolderDownloading, true);
addTest("SyncSaves", &CloudTests::testSavesSync, true);
}
/*
void CloudTestSuite::enable(bool flag) {
Testsuite::enable(ConfParams.isGameDataFound() ? flag : false);
}
*/
///// TESTS GO HERE /////
bool CloudTests::waitForCallback() {
const int TIMEOUT = 30;
Common::Point pt;
pt.x = 10; pt.y = 10;
Testsuite::writeOnScreen("Waiting for callback...", pt);
int left = TIMEOUT;
while (--left) {
if (ConfParams.isCloudTestCallbackCalled()) return true;
if (ConfParams.isCloudTestErrorCallbackCalled()) return true;
g_system->delayMillis(1000);
}
return false;
}
bool CloudTests::waitForCallbackMore() {
while (!waitForCallback()) {
Common::String info = "It takes more time than expected. Do you want to skip the test or wait more?";
if (Testsuite::handleInteractiveInput(info, "Wait", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : info()\n");
return false;
}
}
return true;
}
const char *CloudTests::getRemoteTestPath() {
if (CloudMan.getStorageIndex() == Cloud::kStorageDropboxId)
return "/testbed";
return "testbed";
}
void CloudTests::infoCallback(const Cloud::Storage::StorageInfoResponse &response) {
ConfParams.setCloudTestCallbackCalled(true);
Testsuite::logPrintf("Info! User's ID: %s\n", response.value.uid().c_str());
Testsuite::logPrintf("Info! User's email: %s\n", response.value.email().c_str());
Testsuite::logPrintf("Info! User's name: %s\n", response.value.name().c_str());
Testsuite::logPrintf("Info! User's quota: %lu bytes used / %lu bytes available\n",
static_cast<unsigned long>(response.value.used()),
static_cast<unsigned long>(response.value.available()));
}
void CloudTests::directoryListedCallback(const Cloud::Storage::FileArrayResponse &response) {
ConfParams.setCloudTestCallbackCalled(true);
if (response.value.size() == 0) {
Testsuite::logPrintf("Warning! Directory is empty!\n");
return;
}
Common::String directory, file;
uint32 directories = 0, files = 0;
for (uint32 i = 0; i < response.value.size(); ++i) {
if (response.value[i].isDirectory()) {
if (++directories == 1) directory = response.value[i].path();
} else {
if (++files == 1) file = response.value[i].name();
}
}
if (directories == 0) {
Testsuite::logPrintf("Info! %u files listed, first one is '%s'\n", files, file.c_str());
} else if (files == 0) {
Testsuite::logPrintf("Info! %u directories listed, first one is '%s'\n", directories, directory.c_str());
} else {
Testsuite::logPrintf("Info! %u directories and %u files listed\n", directories, files);
Testsuite::logPrintf("Info! First directory is '%s' and first file is '%s'\n", directory.c_str(), file.c_str());
}
}
void CloudTests::directoryCreatedCallback(const Cloud::Storage::BoolResponse &response) {
ConfParams.setCloudTestCallbackCalled(true);
if (response.value) {
Testsuite::logPrintf("Info! Directory created!\n");
} else {
Testsuite::logPrintf("Info! Such directory already exists!\n");
}
}
void CloudTests::fileUploadedCallback(const Cloud::Storage::UploadResponse &response) {
ConfParams.setCloudTestCallbackCalled(true);
Testsuite::logPrintf("Info! Uploaded file into '%s'\n", response.value.path().c_str());
Testsuite::logPrintf("Info! It's id = '%s' and size = '%u'\n", response.value.id().c_str(), response.value.size());
}
void CloudTests::fileDownloadedCallback(const Cloud::Storage::BoolResponse &response) {
ConfParams.setCloudTestCallbackCalled(true);
if (response.value) {
Testsuite::logPrintf("Info! File downloaded!\n");
} else {
Testsuite::logPrintf("Info! Failed to download the file!\n");
}
}
void CloudTests::directoryDownloadedCallback(const Cloud::Storage::FileArrayResponse &response) {
ConfParams.setCloudTestCallbackCalled(true);
if (response.value.size() == 0) {
Testsuite::logPrintf("Info! Directory is downloaded successfully!\n");
} else {
Testsuite::logPrintf("Warning! %u files were not downloaded during folder downloading!\n", response.value.size());
}
}
void CloudTests::savesSyncedCallback(const Cloud::Storage::BoolResponse &response) {
ConfParams.setCloudTestCallbackCalled(true);
if (response.value) {
Testsuite::logPrintf("Info! Saves are synced successfully!\n");
} else {
Testsuite::logPrintf("Warning! Saves were not synced!\n");
}
}
void CloudTests::errorCallback(const Networking::ErrorResponse &response) {
ConfParams.setCloudTestErrorCallbackCalled(true);
Testsuite::logPrintf("Info! Error Callback was called\n");
Testsuite::logPrintf("Info! code = %ld, message = %s\n", response.httpResponseCode, response.response.c_str());
}
/** This test calls Storage::info(). */
TestExitStatus CloudTests::testInfo() {
ConfParams.setCloudTestCallbackCalled(false);
ConfParams.setCloudTestErrorCallbackCalled(false);
if (CloudMan.getCurrentStorage() == nullptr) {
Testsuite::logPrintf("Couldn't find connected Storage\n");
return kTestFailed;
}
Common::String info = Common::String::format(
"Welcome to the Cloud test suite!\n"
"We're going to use the %s cloud storage which is connected right now.\n\n"
"Testing Cloud Storage API info() method.\n"
"In this test we'll try to list user information.",
CloudMan.getCurrentStorage()->name().c_str()
);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : info()\n");
return kTestSkipped;
}
if (CloudMan.info(
new Common::GlobalFunctionCallback<const Cloud::Storage::StorageInfoResponse &>(&infoCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Info was displayed\n");
return kTestPassed;
}
TestExitStatus CloudTests::testDirectoryListing() {
ConfParams.setCloudTestCallbackCalled(false);
ConfParams.setCloudTestErrorCallbackCalled(false);
if (CloudMan.getCurrentStorage() == nullptr) {
Testsuite::logPrintf("Couldn't find connected Storage\n");
return kTestFailed;
}
Common::String info = "Testing Cloud Storage API listDirectory() method.\n"
"In this test we'll try to list root directory.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : listDirectory()\n");
return kTestSkipped;
}
if (CloudMan.listDirectory(
"",
new Common::GlobalFunctionCallback<const Cloud::Storage::FileArrayResponse &>(&directoryListedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Directory was listed\n");
return kTestPassed;
}
TestExitStatus CloudTests::testDirectoryCreating() {
ConfParams.setCloudTestCallbackCalled(false);
ConfParams.setCloudTestErrorCallbackCalled(false);
if (CloudMan.getCurrentStorage() == nullptr) {
Testsuite::logPrintf("Couldn't find connected Storage\n");
return kTestFailed;
}
Common::String info = "Testing Cloud Storage API createDirectory() method.\n"
"In this test we'll try to create a 'testbed' directory.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : createDirectory()\n");
return kTestSkipped;
}
Common::String info2 = "We'd list the root directory, create the directory and the list it again.\n"
"If all goes smoothly, you'd notice that there are more directories now.";
Testsuite::displayMessage(info2);
// list root directory
if (CloudMan.listDirectory(
"",
new Common::GlobalFunctionCallback<const Cloud::Storage::FileArrayResponse &>(&directoryListedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
ConfParams.setCloudTestCallbackCalled(false);
// create 'testbed'
if (CloudMan.getCurrentStorage()->createDirectory(
getRemoteTestPath(),
new Common::GlobalFunctionCallback<const Cloud::Storage::BoolResponse &>(&directoryCreatedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
ConfParams.setCloudTestCallbackCalled(false);
// list it again
if (CloudMan.listDirectory(
"",
new Common::GlobalFunctionCallback<const Cloud::Storage::FileArrayResponse &>(&directoryListedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
if (Testsuite::handleInteractiveInput("Was the CloudMan able to create a 'testbed' directory?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Directory was not created!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Directory was created\n");
return kTestPassed;
}
TestExitStatus CloudTests::testUploading() {
ConfParams.setCloudTestCallbackCalled(false);
ConfParams.setCloudTestErrorCallbackCalled(false);
if (CloudMan.getCurrentStorage() == nullptr) {
Testsuite::logPrintf("Couldn't find connected Storage\n");
return kTestFailed;
}
Common::String info = "Testing Cloud Storage API upload() method.\n"
"In this test we'll try to upload a 'test1/file.txt' file.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : upload()\n");
return kTestSkipped;
}
if (!ConfParams.isGameDataFound()) {
Testsuite::logPrintf("Info! Couldn't find the game data, so skipping test : upload()\n");
return kTestSkipped;
}
const Common::Path &path = ConfMan.getPath("path");
Common::FSDirectory gameRoot(path);
Common::FSDirectory *directory = gameRoot.getSubDirectory("test1");
Common::FSNode node = directory->getFSNode().getChild("file.txt");
delete directory;
if (CloudMan.getCurrentStorage()->uploadStreamSupported()) {
if (CloudMan.getCurrentStorage()->upload(
Common::String(getRemoteTestPath()) + "/testfile.txt",
node.createReadStream(),
new Common::GlobalFunctionCallback<const Cloud::Storage::UploadResponse &>(&fileUploadedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
} else {
Common::Path filepath = node.getPath();
if (CloudMan.getCurrentStorage()->upload(
Common::String(getRemoteTestPath()) + "/testfile.txt",
filepath,
new Common::GlobalFunctionCallback<const Cloud::Storage::UploadResponse &>(&fileUploadedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
Common::String info2 = "upload() is finished. Do you want to list '/testbed' directory?";
if (!Testsuite::handleInteractiveInput(info2, "Yes", "No", kOptionRight)) {
ConfParams.setCloudTestCallbackCalled(false);
if (CloudMan.listDirectory(
getRemoteTestPath(),
new Common::GlobalFunctionCallback<const Cloud::Storage::FileArrayResponse &>(&directoryListedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
}
if (Testsuite::handleInteractiveInput("Was the CloudMan able to upload into 'testbed/testfile.txt' file?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! File was not uploaded!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("File was uploaded\n");
return kTestPassed;
}
TestExitStatus CloudTests::testDownloading() {
ConfParams.setCloudTestCallbackCalled(false);
ConfParams.setCloudTestErrorCallbackCalled(false);
if (CloudMan.getCurrentStorage() == nullptr) {
Testsuite::logPrintf("Couldn't find connected Storage\n");
return kTestFailed;
}
Common::String info = "Testing Cloud Storage API download() method.\n"
"In this test we'll try to download that 'testbed/testfile.txt' file.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : download()\n");
return kTestSkipped;
}
Common::FSNode testDirectory(ConfMan.getPath("path"));
if (!testDirectory.isWritable()) {
// redirect to savepath if game-data directory is not writable.
testDirectory = Common::FSNode(ConfMan.getPath("savepath"));
}
Common::FSNode node = testDirectory.getChild("downloaded_file.txt");
Common::Path filepath = node.getPath();
if (CloudMan.getCurrentStorage()->download(
Common::String(getRemoteTestPath()) + "/testfile.txt",
filepath,
new Common::GlobalFunctionCallback<const Cloud::Storage::BoolResponse &>(&fileDownloadedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
if (Testsuite::handleInteractiveInput("Was the CloudMan able to download into 'testbed/downloaded_file.txt' file?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! File was not downloaded!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("File was downloaded\n");
return kTestPassed;
}
TestExitStatus CloudTests::testFolderDownloading() {
ConfParams.setCloudTestCallbackCalled(false);
ConfParams.setCloudTestErrorCallbackCalled(false);
if (CloudMan.getCurrentStorage() == nullptr) {
Testsuite::logPrintf("Couldn't find connected Storage\n");
return kTestFailed;
}
Common::String info = "Testing Cloud Storage API downloadFolder() method.\n"
"In this test we'll try to download remote 'testbed/' directory.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : downloadFolder()\n");
return kTestSkipped;
}
Common::FSNode testDirectory(ConfMan.getPath("path"));
if (!testDirectory.isWritable()) {
// redirect to savepath if game-data directory is not writable.
testDirectory = Common::FSNode(ConfMan.getPath("savepath"));
}
Common::FSNode node = testDirectory.getChild("downloaded_directory");
Common::Path filepath = node.getPath();
if (CloudMan.downloadFolder(
getRemoteTestPath(),
filepath,
new Common::GlobalFunctionCallback<const Cloud::Storage::FileArrayResponse &>(&directoryDownloadedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
if (Testsuite::handleInteractiveInput("Was the CloudMan able to download into 'testbed/downloaded_directory'?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Directory was not downloaded!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Directory was downloaded\n");
return kTestPassed;
}
TestExitStatus CloudTests::testSavesSync() {
ConfParams.setCloudTestCallbackCalled(false);
ConfParams.setCloudTestErrorCallbackCalled(false);
if (CloudMan.getCurrentStorage() == nullptr) {
Testsuite::logPrintf("Couldn't find connected Storage\n");
return kTestFailed;
}
Common::String info = "Testing Cloud Storage API syncSaves() method.\n"
"In this test we'll try to sync your saves.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : syncSaves()\n");
return kTestSkipped;
}
if (CloudMan.syncSaves(
new Common::GlobalFunctionCallback<const Cloud::Storage::BoolResponse &>(&savesSyncedCallback),
new Common::GlobalFunctionCallback<const Networking::ErrorResponse &>(&errorCallback)
) == nullptr) {
Testsuite::logPrintf("Warning! No Request is returned!\n");
}
if (!waitForCallbackMore()) return kTestSkipped;
Testsuite::clearScreen();
if (ConfParams.isCloudTestErrorCallbackCalled()) {
Testsuite::logPrintf("Error callback was called\n");
return kTestFailed;
}
if (Testsuite::handleInteractiveInput("Was the CloudMan able to sync saves?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Saves were not synced!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Saves were synced successfully\n");
return kTestPassed;
}
} // End of namespace Testbed

82
engines/testbed/cloud.h Normal file
View File

@@ -0,0 +1,82 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_CLOUD_H
#define TESTBED_CLOUD_H
#include "testbed/testsuite.h"
#include "backends/cloud/storage.h"
// This file can be used as template for header files of other newer testsuites.
namespace Testbed {
namespace CloudTests {
// Helper functions for Cloud tests
bool waitForCallback();
bool waitForCallbackMore();
const char *getRemoteTestPath();
void infoCallback(const Cloud::Storage::StorageInfoResponse &response);
void directoryListedCallback(const Cloud::Storage::FileArrayResponse &response);
void directoryCreatedCallback(const Cloud::Storage::BoolResponse &response);
void fileUploadedCallback(const Cloud::Storage::UploadResponse &response);
void fileDownloadedCallback(const Cloud::Storage::BoolResponse &response);
void directoryDownloadedCallback(const Cloud::Storage::FileArrayResponse &response);
void savesSyncedCallback(const Cloud::Storage::BoolResponse &response);
void errorCallback(const Networking::ErrorResponse &response);
TestExitStatus testInfo();
TestExitStatus testDirectoryListing();
TestExitStatus testDirectoryCreating();
TestExitStatus testUploading();
TestExitStatus testDownloading();
TestExitStatus testFolderDownloading();
TestExitStatus testSavesSync();
} // End of namespace CloudTests
class CloudTestSuite : public Testsuite {
public:
/**
* The constructor for the CloudTestSuite
* 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()
*/
CloudTestSuite();
~CloudTestSuite() override {}
const char *getName() const override {
return "Cloud";
}
const char *getDescription() const override {
return "CloudMan, Storage API tests";
}
};
} // End of namespace Testbed
#endif // TESTBED_TEMPLATE_H

View File

@@ -0,0 +1,82 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/fs.h"
#include "testbed/config-params.h"
namespace Common {
DECLARE_SINGLETON(Testbed::ConfigParams);
}
namespace Testbed {
ConfigParams::ConfigParams() {
_logDirectory = "";
_logFilename = "";
_ws = 0;
_displayFont = Graphics::FontManager::kGUIFont;
_isInteractive = true;
_isGameDataFound = true;
_rerunTests = false;
_testbedConfMan = 0;
}
void ConfigParams::initLogging(const Common::Path &dirname, const char *filename, bool enable) {
setLogDirectory(dirname);
setLogFilename(filename);
if (enable) {
_ws = Common::FSNode(_logDirectory).getChild(_logFilename).createWriteStream(false);
} else {
_ws = 0;
}
}
void ConfigParams::initLogging(bool enable) {
Common::Path logPath = ConfMan.getPath("path");
Common::FSNode logDir(logPath);
if (logDir.isWritable()) {
// Default Log Directory is game-data directory and filename is 'testbed.log'.
initLogging(ConfMan.getPath("path"), "testbed.log", enable);
} else {
// redirect to savepath if game-data directory is not writable.
initLogging(ConfMan.getPath("savepath"), "testbed.log", enable);
}
}
bool ConfigParams::isRerunRequired() {
if (_rerunTests) {
_rerunTests = false;
return true;
}
return false;
}
void ConfigParams::deleteWriteStream() {
if (_ws) {
delete _ws;
_ws = 0;
}
}
} // End of namespace Testbed

View File

@@ -0,0 +1,108 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_CONFIG_PARAMS_H
#define TESTBED_CONFIG_PARAMS_H
#include "common/singleton.h"
#include "common/stream.h"
#include "graphics/fontman.h"
class TestbedConfigManager;
namespace Testbed {
class ConfigParams : public Common::Singleton<ConfigParams> {
private:
friend class Common::Singleton<SingletonBaseType>;
ConfigParams();
/**
* Private variables related to log files.
*/
Common::Path _logDirectory;
Common::String _logFilename;
Common::WriteStream *_ws;
/**
* Private variable used for font.
*/
Graphics::FontManager::FontUsage _displayFont;
/**
* Determines if the user initiated testing session is interactive or not.
* Used by various tests to respond accordingly.
*/
bool _isInteractive;
bool _isGameDataFound;
#ifdef USE_CLOUD
bool _isCloudTestCallbackCalled;
bool _isCloudTestErrorCallbackCalled;
#endif
bool _rerunTests;
TestbedConfigManager *_testbedConfMan;
public:
bool isRerunRequired();
void setRerunFlag(bool flag) { _rerunTests = flag; }
bool isSessionInteractive() { return _isInteractive; }
void setSessionAsInteractive(bool status) { _isInteractive = status; }
bool isGameDataFound() { return _isGameDataFound; }
void setGameDataFound(bool status) { _isGameDataFound = status; }
#ifdef USE_CLOUD
bool isCloudTestCallbackCalled() const { return _isCloudTestCallbackCalled; }
void setCloudTestCallbackCalled(bool status) { _isCloudTestCallbackCalled = status; }
bool isCloudTestErrorCallbackCalled() const { return _isCloudTestErrorCallbackCalled; }
void setCloudTestErrorCallbackCalled(bool status) { _isCloudTestErrorCallbackCalled = status; }
#endif
TestbedConfigManager *getTestbedConfigManager() { return _testbedConfMan; }
void setTestbedConfigManager(TestbedConfigManager* confMan) { _testbedConfMan = confMan; }
Common::Path &getLogDirectory() { return _logDirectory; }
void setLogDirectory(const Common::Path &dirname) { _logDirectory = dirname; }
Common::String &getLogFilename() { return _logFilename; }
void setLogFilename(const Common::String &filename) { _logFilename = filename; }
Common::WriteStream *getLogWriteStream() { return _ws; }
Graphics::FontManager::FontUsage getCurrentFontUsageType() { return _displayFont; }
void setCurrentFontUsageType(Graphics::FontManager::FontUsage f) { _displayFont = f; }
/**
* Note: To enable logging, this function must be called once first.
*/
void initLogging(const Common::Path &dirname, const char *filename, bool enable = true);
void initLogging(bool enable = true);
void deleteWriteStream();
};
/** Shortcut for accessing ConfigParams */
#define ConfParams ConfigParams::instance()
} // End of Namespace Testbed
#endif

345
engines/testbed/config.cpp Normal file
View File

@@ -0,0 +1,345 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/stream.h"
#include "common/config-manager.h"
#include "engines/engine.h"
#include "testbed/config.h"
#include "testbed/fs.h"
#include "gui/widgets/scrollcontainer.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
namespace Testbed {
TestbedOptionsDialog::TestbedOptionsDialog(Common::Array<Testsuite *> &tsList, TestbedConfigManager *tsConfMan) :
GUI::Dialog(0, 0, 0, 0),
_testbedConfMan(tsConfMan) {
_testContainerDisplay = new GUI::ScrollContainerWidget(this, 0, 0, 0, 0);
_testContainerDisplay->setBackgroundType(GUI::ThemeEngine::kWidgetBackgroundNo);
// Construct a String Array
Common::Array<Testsuite *>::const_iterator iter;
Common::U32String description;
uint selected = 0;
for (iter = tsList.begin(); iter != tsList.end(); iter++) {
_testSuiteArray.push_back(*iter);
description = (*iter)->getDescription();
GUI::CheckboxWidget *checkbox;
checkbox = new GUI::CheckboxWidget(_testContainerDisplay, 0, 0, 0, 0, description, Common::U32String());
if ((*iter)->isEnabled()) {
checkbox->setState(true);
selected++;
} else {
checkbox->setState(false);
}
_testSuiteCheckboxArray.push_back(checkbox);
}
_messageText = new GUI::StaticTextWidget(this, 0, 0, 0, 0, Common::U32String("Select Testsuites to Execute"), Graphics::kTextAlignLeft);
if (selected > (tsList.size() - selected)) {
_selectButton = new GUI::ButtonWidget(this, 0, 0, 0, 0, Common::U32String("Deselect All"), Common::U32String(), kTestbedDeselectAll, 0);
} else {
_selectButton = new GUI::ButtonWidget(this, 0, 0, 0, 0, Common::U32String("Select All"), Common::U32String(), kTestbedSelectAll, 0);
}
_runTestButton = new GUI::ButtonWidget(this, 0, 0, 0, 0, Common::U32String("Run tests"), Common::U32String(), GUI::kCloseCmd);
_quitButton = new GUI::ButtonWidget(this, 0, 0, 0, 0, Common::U32String("Exit Testbed"), Common::U32String(), kTestbedQuitCmd);
reflowLayout();
}
TestbedOptionsDialog::~TestbedOptionsDialog() {}
void TestbedOptionsDialog::reflowLayout() {
// Calculate all sizes for widgets
uint16 lineHeight = g_gui.xmlEval()->getVar("Globals.Line.Height");
int16 overlayWidth = g_system->getOverlayWidth();
int16 overlayHeight = g_system->getOverlayHeight();
uint16 xPos = lineHeight;
uint16 padding = 32;
// handle the low res
if (overlayHeight < 500) {
xPos = xPos / 2;
padding = 16;
}
uint16 buttonHeight = lineHeight * 2;
uint16 buttonWidth = lineHeight * 5;
uint16 dialogWidth = overlayWidth - padding * 2;
uint16 dialogHeight = overlayHeight - padding * 2;
uint16 buttonPosY = dialogHeight - buttonHeight - lineHeight;
uint16 containerWidth = dialogWidth - padding;
uint16 containerHeight = dialogHeight - padding - lineHeight * 4 - buttonHeight;
this->resize(padding, padding, dialogWidth, dialogHeight, false);
_testContainerDisplay->setSize(containerWidth, containerHeight);
_testContainerDisplay->setPos(0, lineHeight * 3);
for (auto &iter : _testSuiteCheckboxArray) {
iter->setPos(xPos, (&iter - _testSuiteCheckboxArray.begin()) * lineHeight * 2);
iter->setSize(containerWidth - padding - xPos, lineHeight * 1.5f);
}
_messageText->setPos(xPos, lineHeight);
_messageText->setSize(containerWidth - padding - xPos, lineHeight);
_selectButton->setPos(xPos, buttonPosY);
_selectButton->setSize(buttonWidth, buttonHeight);
_runTestButton->setPos(dialogWidth - padding * 2 - buttonWidth * 2, buttonPosY);
_runTestButton->setSize(buttonWidth, buttonHeight);
_quitButton->setPos(dialogWidth - padding - buttonWidth, buttonPosY);
_quitButton->setSize(buttonWidth, buttonHeight);
Dialog::reflowLayout();
}
void TestbedOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
Testsuite *ts;
Common::WriteStream *ws;
switch (cmd) {
case kTestbedQuitCmd:
Engine::quitGame();
close();
break;
case kTestbedDeselectAll:
_selectButton->setLabel("Select All");
_selectButton->setCmd(kTestbedSelectAll);
for (uint i = 0; i < _testSuiteArray.size(); i++) {
_testSuiteCheckboxArray[i]->setState(false);
}
break;
case kTestbedSelectAll:
_selectButton->setLabel("Deselect All");
_selectButton->setCmd(kTestbedDeselectAll);
for (uint i = 0; i < _testSuiteArray.size(); i++) {
_testSuiteCheckboxArray[i]->setState(true);
}
break;
case GUI::kCloseCmd:
// This is final selected state, write it to config file.
for (uint i = 0; i < _testSuiteCheckboxArray.size(); i++) {
ts = _testSuiteArray[i];
if (_testSuiteCheckboxArray[i]->getState()) {
if (ts) {
ts->enable(true);
}
} else {
if (ts) {
ts->enable(false);
}
}
}
ws = _testbedConfMan->getConfigWriteStream();
if (ws) {
_testbedConfMan->writeTestbedConfigToStream(ws);
delete ws;
}
break;
default:
break;
}
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, true, 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, true, name, Common::U32String(), cmd));
_yOffset += h;
}
void TestbedInteractionDialog::addList(uint x, uint y, uint w, uint h, const Common::Array<Common::U32String> &strArray, uint yPadding) {
_yOffset += yPadding;
GUI::ListWidget *list = new GUI::ListWidget(this, x, y, w, h, true);
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, true, name, Common::U32String(), cmd));
}
void TestbedInteractionDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
GUI::Dialog::handleCommand(sender, cmd, data);
}
void TestbedConfigManager::writeTestbedConfigToStream(Common::WriteStream *ws) {
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);
ws->flush();
}
Common::SeekableReadStream *TestbedConfigManager::getConfigReadStream() const {
// Look for config file using SearchMan
Common::SeekableReadStream *rs = SearchMan.createReadStreamForMember(Common::Path(_configFileName));
return rs;
}
Common::WriteStream *TestbedConfigManager::getConfigWriteStream() const {
// Look for config file in game-path
const Common::Path &path = ConfMan.getPath("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();
// get interactive mode from global config
ConfParams.setSessionAsInteractive(ConfMan.getBool("interactive-mode"));
_configFileInterface.setKey("isSessionInteractive", "Global", ConfParams.isSessionInteractive() ? "true" : "false");
if (!rs) {
Testsuite::logPrintf("Info! No config file found, using default configuration.\n");
return;
}
if (!ConfParams.isSessionInteractive()) {
// Non-interactive sessions don't need to go beyond (they run all tests)
Testsuite::logPrintf("Info! Non interactive session, skipping config parsing.\n");
return;
}
_configFileInterface.loadFromStream(*rs);
Common::INIFile::SectionList sections = _configFileInterface.getSections();
Testsuite *currTS = 0;
for (Common::INIFile::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::INIFile::SectionKeyList kList = i->getKeys();
if (!currTS) {
Testsuite::logPrintf("Warning! Error in config: Testsuite %s not found\n", i->name.c_str());
continue;
}
for (Common::INIFile::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);
ConfParams.setSessionAsInteractive(stringToBool(in));
}
if (!ConfParams.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 :)\n");
if (!ConfParams.isGameDataFound()) {
prompt += "\nSeems like Game data files are not configured properly.\n"
"Create Game data files using script ./create-testbed-data.sh in dists/engine-data\n"
"Next, Configure the game path in launcher / command-line.\n"
"Currently a few testsuites namely FS/AudioCD/MIDI would be disabled\n";
}
Testsuite::logPrintf("Info! : Interactive tests are also being executed.\n");
if (Testsuite::handleInteractiveInput(prompt, "Proceed?", "Customize", kOptionRight)) {
if (Engine::shouldQuit()) {
return;
}
// Select testsuites using checkboxes
TestbedOptionsDialog tbd(_testsuiteList, this);
tbd.runModal();
}
// Clear it to remove entries before next rerun
_configFileInterface.clear();
}
} // End of namespace Testbed

107
engines/testbed/config.h Normal file
View File

@@ -0,0 +1,107 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_CONFIG_H
#define TESTBED_CONFIG_H
#include "common/array.h"
#include "common/formats/ini-file.h"
#include "common/str-array.h"
#include "common/tokenizer.h"
#include "gui/widgets/list.h"
#include "gui/dialog.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() const;
Common::WriteStream *getConfigWriteStream() const;
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"; }
int getNumSuitesEnabled();
private:
Common::Array<Testsuite *> &_testsuiteList;
Common::String _configFileName;
Common::INIFile _configFileInterface;
void parseConfigFile();
};
class TestbedOptionsDialog : public GUI::Dialog {
public:
TestbedOptionsDialog(Common::Array<Testsuite *> &tsList, TestbedConfigManager *tsConfMan);
~TestbedOptionsDialog() override;
void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
void reflowLayout() override;
private:
GUI::ButtonWidget *_selectButton;
GUI::ButtonWidget *_runTestButton;
GUI::ButtonWidget *_quitButton;
GUI::StaticTextWidget *_messageText;
GUI::ScrollContainerWidget *_testContainerDisplay;
Common::Array<GUI::CheckboxWidget *> _testSuiteCheckboxArray;
Common::Array<Testsuite *> _testSuiteArray;
Common::U32StringArray _testSuiteDescArray;
TestbedConfigManager *_testbedConfMan;
};
class TestbedInteractionDialog : public GUI::Dialog {
public:
TestbedInteractionDialog(uint x, uint y, uint w, uint h) : GUI::Dialog(x, y, w, h, true), _xOffset(0), _yOffset(0) {}
~TestbedInteractionDialog() override {}
void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
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, const Common::Array<Common::U32String> &strArray, uint yPadding = 8);
protected:
Common::Array<GUI::ButtonWidget *> _buttonArray;
uint _xOffset;
uint _yOffset;
};
} // End of namespace Testbed
#endif // TESTBED_CONFIG_H

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine testbed "TestBed: the Testing framework" no "" "" "" "imgui midi universaltracker cdtoons indeo3 indeo45 vpx mpc hnm jyv1 mpeg2 qdm2 svq1 truemotion1 xan tinygl"

View File

@@ -0,0 +1,76 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/advancedDetector.h"
#include "base/plugins.h"
#include "testbed/testbed.h"
#include "testbed/detection.h"
static const PlainGameDescriptor testbed_setting[] = {
{ "testbed", "Testbed: The Backend Testing Framework" },
{ 0, 0 }
};
static const DebugChannelDef debugFlagList[] = {
{Testbed::kTestbedLogOutput, "LOG", "Log of test results generated by testbed"},
{Testbed::kTestbedEngineDebug, "Debug", "Engine-specific debug statements"},
DEBUG_CHANNEL_END
};
static const ADGameDescription testbedDescriptions[] = {
{
"testbed",
"",
AD_ENTRY1("TESTBED", 0), // Game-data file for detection
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NOLAUNCHLOAD)
},
AD_TABLE_END_MARKER
};
class TestbedMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
public:
TestbedMetaEngineDetection() : AdvancedMetaEngineDetection(testbedDescriptions, testbed_setting) {
_md5Bytes = 512;
_guiOptions = GUIO1(GAMEOPTION_INTERACTIVE_MODE);
}
const char *getName() const override {
return "testbed";
}
const char *getEngineName() const override {
return "TestBed: The Backend Testing Framework";
}
const char *getOriginalCopyright() const override {
return "Copyright (C) ScummVM";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
REGISTER_PLUGIN_STATIC(TESTBED_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, TestbedMetaEngineDetection);

View File

@@ -0,0 +1,27 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_DETECTION_H
#define TESTBED_DETECTION_H
#define GAMEOPTION_INTERACTIVE_MODE GUIO_GAMEOPTIONS1
#endif

380
engines/testbed/events.cpp Normal file
View File

@@ -0,0 +1,380 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/events.h"
#include "common/keyboard.h"
#include "common/rect.h"
#include "common/str.h"
#include "common/system.h"
#include "common/util.h"
#include "engines/engine.h"
#include "graphics/cursorman.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "graphics/surface.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_0, '0'},
{Common::KEYCODE_1, '1'},
{Common::KEYCODE_2, '2'},
{Common::KEYCODE_3, '3'},
{Common::KEYCODE_4, '4'},
{Common::KEYCODE_5, '5'},
{Common::KEYCODE_6, '6'},
{Common::KEYCODE_7, '7'},
{Common::KEYCODE_8, '8'},
{Common::KEYCODE_9, '9'},
{Common::KEYCODE_SPACE, ' '}
};
char EventTests::keystrokeToChar() {
Common::EventManager *eventMan = g_system->getEventManager();
Common::Event event;
// handle all keybd events
while (true) {
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
}
}
}
}
Common::Rect EventTests::drawFinishZone() {
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);
g_system->fillScreen(rect, kColorSpecial);
g_system->fillScreen(rect2, kColorBlack);
Graphics::Surface *screen = g_system->lockScreen();
font.drawString(screen, "Close", rect.left, rect.top, screen->w, kColorBlack, Graphics::kTextAlignRight);
g_system->unlockScreen();
g_system->updateScreen();
return Common::Rect(right - width, 0, right, height);
}
TestExitStatus 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 kTestSkipped;
}
Common::EventManager *eventMan = g_system->getEventManager();
Common::Point pt(0, 25);
Common::Rect rectInfo = Testsuite::writeOnScreen("Generate mouse events make L/R/M button clicks, move wheel", pt);
pt.y += 15;
Testsuite::writeOnScreen("Press X to exit", pt);
pt.y = 55;
Common::Rect rectLB = Testsuite::writeOnScreen("Left-button click : Not tested", pt);
pt.y += 15;
Common::Rect rectRB = Testsuite::writeOnScreen("Right-button click : Not tested", pt);
pt.y += 15;
Common::Rect rectMB = Testsuite::writeOnScreen("Middle-button click : Not tested", pt);
pt.y += 15;
Common::Rect rectX1B = Testsuite::writeOnScreen("X1-button click : Not tested", pt);
pt.y += 15;
Common::Rect rectX2B = Testsuite::writeOnScreen("X2-button click : Not tested", pt);
pt.y += 15;
Common::Rect rectWheel = Testsuite::writeOnScreen("Wheel Movements : Not tested", pt);
// Init Mouse Palette
GFXtests::initMousePalette();
Common::Rect finishZone = drawFinishZone();
bool quitLoop = false;
TestExitStatus passed = kTestPassed;
// 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(rectInfo);
Testsuite::writeOnScreen("Mouse left-button pressed", Common::Point(rectInfo.left, rectInfo.top));
break;
case Common::EVENT_RBUTTONDOWN:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse right-button pressed", Common::Point(rectInfo.left, rectInfo.top));
break;
case Common::EVENT_WHEELDOWN:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse wheel moved down", Common::Point(rectInfo.left, rectInfo.top));
Testsuite::writeOnScreen("Wheel Movements : Done!", Common::Point(rectWheel.left, rectWheel.top));
break;
case Common::EVENT_MBUTTONDOWN:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse middle-button pressed ", Common::Point(rectInfo.left, rectInfo.top));
break;
case Common::EVENT_X1BUTTONDOWN:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse X1-button pressed ", Common::Point(rectInfo.left, rectInfo.top));
break;
case Common::EVENT_X2BUTTONDOWN:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse X2-button pressed ", Common::Point(rectInfo.left, rectInfo.top));
break;
case Common::EVENT_LBUTTONUP:
Testsuite::clearScreen(rectInfo);
if (finishZone.contains(eventMan->getMousePos())) {
quitLoop = true;
}
Testsuite::writeOnScreen("Mouse left-button released", Common::Point(rectInfo.left, rectInfo.top));
Testsuite::writeOnScreen("Left-button clicks : Done!", Common::Point(rectLB.left, rectLB.top));
break;
case Common::EVENT_RBUTTONUP:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse right-button released", Common::Point(rectInfo.left, rectInfo.top));
Testsuite::writeOnScreen("Right-button clicks : Done!", Common::Point(rectRB.left, rectRB.top));
break;
case Common::EVENT_WHEELUP:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse wheel moved up", Common::Point(rectInfo.left, rectInfo.top));
Testsuite::writeOnScreen("Wheel Movements : Done!", Common::Point(rectWheel.left, rectWheel.top));
break;
case Common::EVENT_MBUTTONUP:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse middle-button released ", Common::Point(rectInfo.left, rectInfo.top));
Testsuite::writeOnScreen("Middle-button clicks : Done!", Common::Point(rectMB.left, rectMB.top));
break;
case Common::EVENT_X1BUTTONUP:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse X1-button released ", Common::Point(rectInfo.left, rectInfo.top));
Testsuite::writeOnScreen("X1-button clicks : Done!", Common::Point(rectX1B.left, rectX1B.top));
break;
case Common::EVENT_X2BUTTONUP:
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Mouse X2-button released ", Common::Point(rectInfo.left, rectInfo.top));
Testsuite::writeOnScreen("X2-button clicks : Done!", Common::Point(rectX2B.left, rectX2B.top));
break;
case Common::EVENT_KEYDOWN:
if (event.kbd.keycode == Common::KEYCODE_x) {
Testsuite::clearScreen(rectInfo);
Testsuite::writeOnScreen("Exit requested", Common::Point(rectInfo.left, rectInfo.top));
quitLoop = true;
}
break;
default:
break;
}
}
}
CursorMan.showMouse(false);
// Verify results now!
if (Testsuite::handleInteractiveInput("Were mouse clicks (L/R/M buttons) and wheel movements identfied ?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Mouse clicks (L/R/M buttons) and wheel movements failed");
passed = kTestFailed;
}
return passed;
}
TestExitStatus EventTests::doubleClickTime() {
Testsuite::clearScreen();
uint32 dclickTime = g_system->getDoubleClickTime();
if (dclickTime == 0) {
if (Testsuite::handleInteractiveInput("Double-click time returned 0, meaning it isn't configurable on this operating system.\nIs that correct ?", "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("Unsupported double-click time check failed");
return kTestFailed;
}
}
Common::String info = "Testing double click time detection.\n "
"This should report the correct double-click time for the system";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : double click time\n");
return kTestSkipped;
}
info = Common::String::format("Double-click time was reported as: %u msec\nDoes this seem correct?", static_cast<unsigned int>(dclickTime));
if (Testsuite::handleInteractiveInput(info, "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Double-click time failed");
return kTestFailed;
}
if (Testsuite::handleInteractiveInput("Do you want to test for detecting configuration changes?\nIf so, change the OS double-click time now, then click 'Yes'", "Yes", "No", kOptionLeft)) {
dclickTime = g_system->getDoubleClickTime();
info = Common::String::format("Double-click time was reported as: %u msec\nDoes this seem correct?", static_cast<unsigned int>(dclickTime));
if (Testsuite::handleInteractiveInput(info, "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Double-click time reconfiguration failed");
return kTestFailed;
}
}
return kTestPassed;
}
TestExitStatus 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 kTestSkipped;
}
// 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);
}
TestExitStatus passed = kTestPassed;
if (Testsuite::handleInteractiveInput("Was the word you entered same as that displayed on screen?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Keyboard Events failed");
passed = kTestFailed;
}
Testsuite::clearScreen();
return passed;
}
TestExitStatus 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'(the topmost button) to continue testbed.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : Main Menu\n");
return kTestSkipped;
}
Common::EventManager *eventMan = g_system->getEventManager();
Common::Event mainMenuEvent;
mainMenuEvent.type = Common::EVENT_MAINMENU;
eventMan->pushEvent(mainMenuEvent);
TestExitStatus passed = kTestPassed;
if (Testsuite::handleInteractiveInput("Were you able to see a main menu widget?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Event MAINMENU failed");
passed = kTestFailed;
}
return passed;
}
EventTestSuite::EventTestSuite() {
addTest("MouseEvents", &EventTests::mouseEvents);
addTest("DoubleClickTime", &EventTests::doubleClickTime);
addTest("KeyboardEvents", &EventTests::kbdEvents);
addTest("MainmenuEvent", &EventTests::showMainMenu);
}
} // End of namespace Testbed

65
engines/testbed/events.h Normal file
View File

@@ -0,0 +1,65 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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
TestExitStatus mouseEvents();
TestExitStatus doubleClickTime();
TestExitStatus kbdEvents();
TestExitStatus 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() override {}
const char *getName() const override {
return "Events";
}
const char *getDescription() const override {
return "Events : Keyboard/Mouse/Return to Launcher";
}
};
} // End of namespace Testbed
#endif // TESTBED_EVENTS_H

240
engines/testbed/fs.cpp Normal file
View File

@@ -0,0 +1,240 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/file.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) {
if (!SearchMan.isPathDirectory(directory->getFSNode().getPath())) {
SearchMan.addDirectory(directory->getFSNode().getPath(), 0, 1, false);
}
Common::ScopedPtr<Common::File> f(new Common::File());
if (!f->open(file)) {
Testsuite::logDetailedPrintf("Can't open game file\n");
return false;
}
Common::SeekableReadStream *readStream = f.release();
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;
}
TestExitStatus FStests::testReadFile() {
const Common::Path &path = ConfMan.getPath("path");
Common::FSDirectory gameRoot(path);
int numFailed = 0;
if (!gameRoot.getFSNode().exists() || !gameRoot.getFSNode().isDirectory()) {
Testsuite::logDetailedPrintf("game Path should be an existing directory");
return kTestFailed;
}
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(Common::Path(dirName));
if (!directory) {
Testsuite::logDetailedPrintf("Failed to open directory %s during FS tests\n", dirName.c_str());
return kTestFailed;
}
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(Common::Path(dirName));
if (!directory) {
Testsuite::logDetailedPrintf("Failed to open directory %s during FS tests\n", dirName.c_str());
return kTestFailed;
}
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(Common::Path(dirName));
if (!directory) {
Testsuite::logDetailedPrintf("Failed to open directory %s during FS tests\n", dirName.c_str());
return kTestFailed;
}
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);
if (numFailed) {
return kTestFailed;
} else {
return kTestPassed;
}
}
/**
* This test creates a file testbed.out, writes a sample data and confirms if
* it is same by reading the file again.
*/
TestExitStatus FStests::testWriteFile() {
Common::FSNode testDirectory(ConfMan.getPath("path"));
if (!testDirectory.isWritable()) {
// redirect to savepath if game-data directory is not writable.
testDirectory = Common::FSNode(ConfMan.getPath("savepath"));
}
if (!testDirectory.exists()) {
Testsuite::logPrintf("Couldn't open the game data directory %s",
testDirectory.getPath().toString(Common::Path::kNativeSeparator).c_str());
return kTestFailed;
}
Common::FSNode fileToWrite = testDirectory.getChild("testbed.out");
Common::WriteStream *ws = fileToWrite.createWriteStream();
if (!ws) {
Testsuite::logDetailedPrintf("Can't open writable file in game data dir\n");
return kTestFailed;
}
ws->writeString("ScummVM Rocks!");
ws->flush();
delete ws;
Common::SeekableReadStream *rs = fileToWrite.createReadStream();
if (!rs) {
Testsuite::logDetailedPrintf("Can't open recently written file testbed.out in game data dir\n");
return kTestFailed;
}
Common::String readFromFile = rs->readLine();
delete rs;
if (readFromFile.equals("ScummVM Rocks!")) {
// All good
Testsuite::logDetailedPrintf("Data written and read correctly\n");
return kTestPassed;
}
return kTestFailed;
}
/**
* This test creates a directory testbed.dir, and confirms if the directory is created successfully
*/
TestExitStatus FStests::testCreateDir() {
Common::FSNode testDirectory(ConfMan.getPath("path"));
if (!testDirectory.isWritable()) {
// redirect to savepath if game-data directory is not writable.
testDirectory = Common::FSNode(ConfMan.getPath("savepath"));
}
if (!testDirectory.exists()) {
Testsuite::logPrintf("Couldn't open the game data directory %s",
testDirectory.getPath().toString(Common::Path::kNativeSeparator).c_str());
return kTestFailed;
}
Common::FSNode dirToCreate = testDirectory.getChild("testbed.dir");
// TODO: Delete the directory after creating it
if (dirToCreate.exists()) {
Testsuite::logDetailedPrintf("Directory already exists in game data dir\n");
return kTestSkipped;
}
if (!dirToCreate.createDirectory()) {
Testsuite::logDetailedPrintf("Can't create directory in game data dir\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Directory created successfully\n");
return kTestPassed;
}
FSTestSuite::FSTestSuite() {
// FS tests depend on Game Data files.
// If those are not found. Disable this testsuite.
const Common::Path &path = ConfMan.getPath("path");
Common::FSNode gameRoot(path);
Common::FSNode gameIdentificationFile = gameRoot.getChild("TESTBED");
if (!gameIdentificationFile.exists()) {
logPrintf("WARNING! : Game Data not found. Skipping FS tests\n");
ConfParams.setGameDataFound(false);
Testsuite::enable(false);
}
addTest("ReadingFile", &FStests::testReadFile, false);
addTest("WritingFile", &FStests::testWriteFile, false);
addTest("CreateDir", &FStests::testCreateDir, false);
}
void FSTestSuite::enable(bool flag) {
Testsuite::enable(ConfParams.isGameDataFound() ? flag : false);
}
} // End of namespace Testbed

72
engines/testbed/fs.h Normal file
View File

@@ -0,0 +1,72 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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
TestExitStatus testReadFile();
TestExitStatus testWriteFile();
TestExitStatus testCreateDir();
TestExitStatus 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() override {}
const char *getName() const override {
return "FS";
}
const char *getDescription() const override {
return "File system tests (Navigation, Read/Write)";
}
void enable(bool flag) override;
};
} // End of namespace Testbed
#endif // TESTBED_FS_H

1925
engines/testbed/graphics.cpp Normal file

File diff suppressed because it is too large Load Diff

100
engines/testbed/graphics.h Normal file
View File

@@ -0,0 +1,100 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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();
void initMouseCursor();
Common::Rect computeSize(const 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, int cursorTargetScale = 1);
TestExitStatus pixelFormats(Common::List<Graphics::PixelFormat> &pfList);
void showPixelFormat(const Graphics::PixelFormat &pf, uint aLoss);
// will contain function declarations for GFX tests
TestExitStatus cursorTrails();
TestExitStatus fullScreenMode();
TestExitStatus filteringMode();
TestExitStatus aspectRatio();
TestExitStatus palettizedCursors();
TestExitStatus alphaCursors();
TestExitStatus maskedCursors();
TestExitStatus mouseMovements();
TestExitStatus copyRectToScreen();
TestExitStatus iconifyWindow();
TestExitStatus scaledCursors();
TestExitStatus shakingEffect();
TestExitStatus focusRectangle();
TestExitStatus overlayGraphics();
TestExitStatus paletteRotation();
TestExitStatus pixelFormatsSupported();
TestExitStatus pixelFormatsRequired();
TestExitStatus shaderCompatibility();
// 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() override {}
const char *getName() const override {
return "GFX";
}
const char *getDescription() const override {
return "Graphics Subsystem";
}
void prepare() override;
static void setCustomColor(uint r, uint g, uint b);
private:
/**
* A Palette consists of 3 components RGB.
* 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 * 3];
};
} // End of namespace Testbed
#endif // TESTBED_GRAPHICS_H

375
engines/testbed/image.cpp Normal file
View File

@@ -0,0 +1,375 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "graphics/managed_surface.h"
#include "graphics/paletteman.h"
#include "graphics/pm5544.h"
#include "graphics/screen.h"
#include "image/bmp.h"
#include "image/gif.h"
#include "image/iff.h"
#include "image/jpeg.h"
#include "image/pcx.h"
#include "image/pict.h"
#include "image/png.h"
#include "image/tga.h"
#include "image/xbm.h"
#include "testbed/image.h"
#include "testbed/testsuite.h"
namespace Testbed {
namespace ImageTests {
TestExitStatus testRenderPM5544();
TestExitStatus testBitmapDecoder();
TestExitStatus testJPEGDecoder();
TestExitStatus testGIFDecoder();
TestExitStatus testPCXDecoder();
TestExitStatus testPICTDecoder();
TestExitStatus testPNGDecoder();
TestExitStatus testTGADecoder();
TestExitStatus testXBMDecoder();
bool testImageDecoder(Common::Path &filepath, Image::ImageDecoder &decoder);
}
ImageTestSuite::ImageTestSuite() {
// Add tests here
// Render base image
addTest("testRenderPM5544", &ImageTests::testRenderPM5544);
// Test image decoders
addTest("testBitmapDecoder", &ImageTests::testBitmapDecoder);
addTest("testJPEGDecoder", &ImageTests::testJPEGDecoder);
addTest("testGIFDecoder", &ImageTests::testGIFDecoder);
addTest("testPCXDecoder", &ImageTests::testPCXDecoder);
addTest("testPICTDecoder", &ImageTests::testPICTDecoder);
addTest("testPNGDecoder", &ImageTests::testPNGDecoder);
addTest("testTGADecoder", &ImageTests::testTGADecoder);
// External XBM files are not yet supported
//addTest("testXBMDecoder", &ImageTests::testXBMDecoder);
}
TestExitStatus ImageTests::testRenderPM5544() {
Common::String info = "Render test pattern?\n"
"This test pattern is the comparison reference for the other image decoders.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testRenderPM5544()\n");
return kTestSkipped;
}
int xres = 320, yres = 240;
g_system->beginGFXTransaction();
g_system->initSize(xres, yres);
g_system->endGFXTransaction();
Graphics::ManagedSurface *pm5544 = Graphics::renderPM5544(xres, yres);
// Clear the version string
pm5544->fillRect(Common::Rect(112, 176, 208, 192), 0);
byte palette[768];
pm5544->grabPalette(palette, 0, 256);
// Common::Path filename("image/pm5544.bmp");
// Common::DumpFile dumpFile;
// bool result = dumpFile.open(filename);
// if (result) {
// result = Image::writeBMP(dumpFile, pm5544->rawSurface(), palette);
// }
g_system->getPaletteManager()->setPalette(palette, 0, 256);
g_system->copyRectToScreen(pm5544->surfacePtr()->getPixels(), pm5544->surfacePtr()->pitch, 0, 0, xres, yres);
g_system->updateScreen();
delete pm5544;
return kTestPassed;
}
TestExitStatus ImageTests::testBitmapDecoder() {
if (Testsuite::handleInteractiveInput("Test bitmap decoder?", "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testBitmapDecoder()\n");
return kTestSkipped;
}
Common::Path filepath;
Common::SharedPtr<Image::ImageDecoder> decoder;
filepath = "image/pm5544-24bpp.bmp";
decoder.reset(new Image::BitmapDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp.bmp";
decoder.reset(new Image::BitmapDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
Testsuite::logDetailedPrintf("Bitmap decoder is OK\n");
return kTestPassed;
}
TestExitStatus ImageTests::testJPEGDecoder() {
if (Testsuite::handleInteractiveInput("Test JPEG decoder?", "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testJPEGDecoder()\n");
return kTestSkipped;
}
Common::Path filepath;
Common::SharedPtr<Image::ImageDecoder> decoder;
filepath = "image/pm5544-24bpp.jpg";
decoder.reset(new Image::JPEGDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
Testsuite::logDetailedPrintf("JPEG decoder is OK\n");
return kTestPassed;
}
TestExitStatus ImageTests::testGIFDecoder() {
#ifdef USE_GIF
if (Testsuite::handleInteractiveInput("Test GIF decoder?", "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testGIFDecoder()\n");
return kTestSkipped;
}
Common::Path filepath;
Common::SharedPtr<Image::ImageDecoder> decoder;
filepath = "image/pm5544-8bpp.gif";
decoder.reset(new Image::GIFDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
Testsuite::logDetailedPrintf("GIF decoder is OK\n");
return kTestPassed;
#else
Testsuite::logDetailedPrintf("Info! Skipping test: GIF decoder is disabled.\n");
return kTestSkipped;
#endif
}
TestExitStatus ImageTests::testPCXDecoder() {
if (Testsuite::handleInteractiveInput("Test PCX decoder?", "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testPCXDecoder()\n");
return kTestSkipped;
}
Common::Path filepath;
Common::SharedPtr<Image::ImageDecoder> decoder;
filepath = "image/pm5544-24bpp.pcx";
decoder.reset(new Image::PCXDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp.pcx";
decoder.reset(new Image::PCXDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
Testsuite::logDetailedPrintf("PCX decoder is OK\n");
return kTestPassed;
}
TestExitStatus ImageTests::testPICTDecoder() {
if (Testsuite::handleInteractiveInput("Test PICT decoder?", "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testPICTDecoder()\n");
return kTestSkipped;
}
Common::Path filepath;
Common::SharedPtr<Image::ImageDecoder> decoder;
filepath = "image/pm5544-24bpp.pict";
decoder.reset(new Image::PICTDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp.pict";
decoder.reset(new Image::PICTDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
Testsuite::logDetailedPrintf("PICT decoder is OK\n");
return kTestPassed;
}
TestExitStatus ImageTests::testPNGDecoder() {
if (Testsuite::handleInteractiveInput("Test PNG decoder?", "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testPNGDecoder()\n");
return kTestSkipped;
}
Common::Path filepath;
Common::SharedPtr<Image::ImageDecoder> decoder;
filepath = "image/pm5544-24bpp.png";
decoder.reset(new Image::PNGDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp.png";
decoder.reset(new Image::PNGDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp-grey.png";
decoder.reset(new Image::PNGDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
Testsuite::logDetailedPrintf("PNG decoder is OK\n");
return kTestPassed;
}
TestExitStatus ImageTests::testTGADecoder() {
if (Testsuite::handleInteractiveInput("Test TGA decoder?", "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testTGADecoder()\n");
return kTestSkipped;
}
Common::Path filepath;
Common::SharedPtr<Image::ImageDecoder> decoder;
filepath = "image/pm5544-24bpp.tga";
decoder.reset(new Image::TGADecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-24bpp-rle.tga";
decoder.reset(new Image::TGADecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp.tga";
decoder.reset(new Image::TGADecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp-rle.tga";
decoder.reset(new Image::TGADecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp-grey.tga";
decoder.reset(new Image::TGADecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
filepath = "image/pm5544-8bpp-grey-rle.tga";
decoder.reset(new Image::TGADecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
Testsuite::logDetailedPrintf("TGA decoder is OK\n");
return kTestPassed;
}
TestExitStatus ImageTests::testXBMDecoder() {
if (Testsuite::handleInteractiveInput("Test XBM decoder?", "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test: testXBMDecoder()\n");
return kTestSkipped;
}
Common::Path filepath;
Common::SharedPtr<Image::ImageDecoder> decoder;
filepath = "image/pm5544-1bpp.xbm";
decoder.reset(new Image::XBMDecoder());
if (!testImageDecoder(filepath, *decoder)) {
return kTestFailed;
}
Testsuite::logDetailedPrintf("XBM decoder is OK\n");
return kTestPassed;
}
bool ImageTests::testImageDecoder(Common::Path &filepath, Image::ImageDecoder &decoder) {
Common::File f;
if (!f.open(filepath)) {
Testsuite::logDetailedPrintf("Error! File could not be opened: %s\n", filepath.toString().c_str());
return false;
}
if (!decoder.loadStream(f)) {
Testsuite::logDetailedPrintf("Error! Image decoder failed: %s\n", filepath.toString().c_str());
return false;
}
int oldW = g_system->getWidth();
int oldH = g_system->getHeight();
Graphics::PixelFormat oldFormat = g_system->getScreenFormat();
const Graphics::Surface *pSurface = decoder.getSurface();
Graphics::PixelFormat pf = g_system->getOverlayFormat();
g_system->beginGFXTransaction();
g_system->initSize(pSurface->w, pSurface->h, &pf);
g_system->endGFXTransaction();
Graphics::Screen screen;
if (decoder.hasPalette()) {
screen.simpleBlitFrom(*pSurface, Graphics::FLIP_NONE, false, 255, &decoder.getPalette());
} else {
screen.simpleBlitFrom(*pSurface);
}
screen.update();
g_system->delayMillis(1000);
bool result = true;
Common::String info = "Did the image \"" + filepath.baseName() + "\" display as expected?";
if (Testsuite::handleInteractiveInput(info, "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Image did not display as expected: %s\n", filepath.toString().c_str());
result = false;
}
// Return to previous state
g_system->beginGFXTransaction();
g_system->initSize(oldW, oldH, &oldFormat);
g_system->endGFXTransaction();
return result;
}
} // End of namespace Testbed

54
engines/testbed/image.h Normal file
View File

@@ -0,0 +1,54 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_IMAGE_H
#define TESTBED_IMAGE_H
#include "testbed/testsuite.h"
namespace Testbed {
class ImageTestSuite : 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()
*/
ImageTestSuite();
~ImageTestSuite() override {}
const char *getName() const override {
return "Image";
}
const char *getDescription() const override {
return "Image decoders";
}
};
} // End of namespace Testbed
#endif // TESTBED_IMAGE_H

104
engines/testbed/imgui.cpp Normal file
View File

@@ -0,0 +1,104 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/system.h"
#include "common/events.h"
#include "engines/engine.h"
#include "engines/testbed/imgui.h"
#include "backends/imgui/imgui.h"
#include "backends/imgui/imgui_fonts.h"
namespace Testbed {
static bool p_supported = false;
static bool p_open = false;
static void onImGuiInit() {
p_supported = true;
}
void onImGuiRender() {
if (p_open) {
ImGui::GetIO().ConfigFlags &= ~(ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
ImGui::ShowDemoWindow(&p_open);
} else {
ImGui::GetIO().ConfigFlags |= (ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
}
}
static void onImGuiCleanup() {
}
TestExitStatus Imguitests::testImGui() {
Testsuite::clearScreen();
Common::String info = "ImGui test. You should expect an ImGui dialog to appear on screen.\"";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testImGui\n");
return kTestSkipped;
}
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing ImGui", pt);
p_open = true;
ImGuiCallbacks callbacks;
callbacks.init = onImGuiInit;
callbacks.render = onImGuiRender;
callbacks.cleanup = onImGuiCleanup;
g_system->setImGuiCallbacks(callbacks);
// Update the screen once so that it has the chance to initialise.
g_system->updateScreen();
if (!p_supported) {
Testsuite::logDetailedPrintf("ImGui failed\n");
return kTestFailed;
}
Common::EventManager *eventMan = g_system->getEventManager();
Common::Event event;
while (p_open) {
while (eventMan->pollEvent(event)) {
// Quit if explicitly requested
if (Engine::shouldQuit()) {
return kTestPassed;
}
}
g_system->updateScreen();
}
Common::String prompt = "Did you see an ImGui dialog?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("ImGui failed\n");
return kTestFailed;
}
return kTestPassed;
}
ImGuiTestSuite::ImGuiTestSuite() {
_isTsEnabled = true;
addTest("testImGui", &Imguitests::testImGui, true);
}
} // End of namespace Testbed

65
engines/testbed/imgui.h Normal file
View File

@@ -0,0 +1,65 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_IMGUI_H
#define TESTBED_IMGUI_H
#include "testbed/testsuite.h"
namespace Testbed {
namespace Imguitests {
// Helper functions for ImGui tests
// will contain function declarations for ImGui tests
// add more here
TestExitStatus testImGui();
} // End of namespace Imguitests
class ImGuiTestSuite : 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()
*/
ImGuiTestSuite();
~ImGuiTestSuite() override {}
const char *getName() const override {
return "ImGui";
}
const char *getDescription() const override {
return "ImGui Subsystem";
}
};
} // End of namespace Testbed
#endif // TESTBED_IMGUI_H

View File

@@ -0,0 +1,81 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "base/plugins.h"
#include "common/system.h"
#include "engines/advancedDetector.h"
#include "common/translation.h"
#include "testbed/testbed.h"
#include "testbed/detection.h"
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_INTERACTIVE_MODE,
{
// I18N: Runs tests in interactive mode
_s("Run in interactive mode"),
_s("Run in interactive mode"),
"interactive-mode",
true,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
class TestbedMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override {
return "testbed";
}
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return optionsList;
}
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription * /* desc */) const override {
*engine = new Testbed::TestbedEngine(syst);
return Common::kNoError;
}
const Common::AchievementsInfo getAchievementsInfo(const Common::String &target) const override {
Common::AchievementsInfo result;
result.platform = Common::UNK_ACHIEVEMENTS;
result.appId = "testbed";
return result;
}
bool hasFeature(MetaEngineFeature f) const override {
return false;
}
};
#if PLUGIN_ENABLED_DYNAMIC(TESTBED)
REGISTER_PLUGIN_DYNAMIC(TESTBED, PLUGIN_TYPE_ENGINE, TestbedMetaEngine);
#else
REGISTER_PLUGIN_STATIC(TESTBED, PLUGIN_TYPE_ENGINE, TestbedMetaEngine);
#endif

159
engines/testbed/midi.cpp Normal file
View File

@@ -0,0 +1,159 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/archive.h"
#include "common/events.h"
#include "common/memstream.h"
#include "graphics/cursorman.h"
#include "audio/mididrv.h"
#include "audio/midiparser.h"
#include "testbed/midi.h"
#include "testbed/testbed.h"
namespace Testbed {
bool MidiTests::loadMusicInMemory(Common::WriteStream *ws) {
Common::SeekableReadStream *midiFile = SearchMan.createReadStreamForMember("music.mid");
if (!midiFile) {
Testsuite::logPrintf("Error! Can't open Midi music file, check game data directory for file music.mid\n");
return false;
}
while (!midiFile->eos()) {
byte data = midiFile->readByte();
ws->writeByte(data);
}
return true;
}
void MidiTests::waitForMusicToPlay(MidiParser *parser) {
Common::EventManager *eventMan = g_system->getEventManager();
bool quitLoop = false;
Common::Event event;
CursorMan.showMouse(true);
while (!quitLoop) {
g_system->delayMillis(10);
while (eventMan->pollEvent(event)) {
// Quit if explicitly requested!
if (Engine::shouldQuit()) {
return;
}
if (event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_RBUTTONDOWN) {
quitLoop = true;
} else {
Testsuite::writeOnScreen("Playing Midi Music, Click to end", Common::Point(0, 100));
if (!parser->isPlaying()) {
quitLoop = true;
}
}
}
}
CursorMan.showMouse(false);
return;
}
TestExitStatus MidiTests::playMidiMusic() {
Testsuite::clearScreen();
Common::String info = "Testing Midi Sound output.\n"
"Here, We generate some Music by using the Midi Driver selected in the GUI.\n"
"You should expect to hear that. The initialization may take some time.\n";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : Play Midi Music\n");
return kTestSkipped;
}
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB);
// Create a driver instance
MidiDriver *driver = MidiDriver::createMidi(dev);
// Create a SMF parser
MidiParser *smfParser = MidiParser::createParser_SMF();
// Open the driver
int errCode = driver->open();
if (errCode) {
Common::String errMsg = MidiDriver::getErrorName(errCode);
Testsuite::writeOnScreen(errMsg, Common::Point(0, 100));
Testsuite::logPrintf("Error! %s", errMsg.c_str());
delete smfParser;
delete driver;
return kTestFailed;
}
Testsuite::logDetailedPrintf("Info! Midi: Successfully opened the driver\n");
Common::MemoryWriteStreamDynamic ws(DisposeAfterUse::YES);
loadMusicInMemory(&ws);
// start playing
if (smfParser->loadMusic(ws.getData(), ws.size())) {
smfParser->setTrack(0);
smfParser->setMidiDriver(driver);
smfParser->setTimerRate(driver->getBaseTempo());
driver->setTimerCallback(smfParser, MidiParser::timerCallback);
Testsuite::logDetailedPrintf("Info! Midi: Parser Successfully loaded Music data.\n");
if (smfParser->isPlaying()) {
Testsuite::writeOnScreen("Playing Midi Music, Click to end.", Common::Point(0, 100));
Testsuite::logDetailedPrintf("Info! Midi: Playing music!\n");
}
}
// Play until track ends or an exit is requested.
waitForMusicToPlay(smfParser);
// Done. Clean up.
smfParser->unloadMusic();
driver->setTimerCallback(NULL, NULL);
driver->close();
delete smfParser;
delete driver;
if (Testsuite::handleInteractiveInput("Were you able to hear the music as described?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Midi: Can't play Music\n");
return kTestFailed;
}
return kTestPassed;
}
MidiTestSuite::MidiTestSuite() {
addTest("MidiTests", &MidiTests::playMidiMusic);
_isMidiDataFound = true;
if (!SearchMan.hasFile("music.mid")) {
// add some fallback test if filesystem loading failed
Testsuite::logPrintf("Warning! Midi: Sound data file music.mid not found\n");
_isMidiDataFound = false;
MidiTestSuite::enable(false);
}
}
void MidiTestSuite::enable(bool flag) {
Testsuite::enable(_isMidiDataFound ? flag : false);
}
}

78
engines/testbed/midi.h Normal file
View File

@@ -0,0 +1,78 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_MIDI_H
#define TESTBED_MIDI_H
#include "testbed/testsuite.h"
// This file can be used as template for header files of other newer testsuites.
class MidiParser;
namespace Common {
class WriteStream;
}
namespace Testbed {
namespace MidiTests {
// Helper functions for MIDI tests
bool loadMusicInMemory(Common::WriteStream *ws);
void waitForMusicToPlay(MidiParser *parser);
// will contain function declarations for MIDI tests
// add more here
TestExitStatus playMidiMusic();
} // End of namespace MIDItests
class MidiTestSuite : 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()
*/
MidiTestSuite();
~MidiTestSuite() override {}
const char *getName() const override {
return "MIDI";
}
const char *getDescription() const override {
return "Midi Music";
}
void enable(bool flag) override;
private:
bool _isMidiDataFound;
};
} // End of namespace Testbed
#endif // TESTBED_MIDI_H

328
engines/testbed/misc.cpp Normal file
View File

@@ -0,0 +1,328 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "testbed/misc.h"
#include "common/timer.h"
#include "common/file.h"
#include "graphics/palette.h"
#include "gui/dialog.h"
#include "gui/imagealbum-dialog.h"
#include "image/jpeg.h"
namespace Testbed {
Common::String MiscTests::getHumanReadableFormat(const TimeDate &td) {
return Common::String::format("%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
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);
sv.mutex->lock();
// 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);
sv.mutex->unlock();
g_system->getTimerManager()->removeTimerProc(criticalSection);
}
TestExitStatus MiscTests::testDateTime() {
if (ConfParams.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 kTestSkipped;
}
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 (ConfParams.isSessionInteractive()) {
// Directly verify date
dateTimeNow = "We expect the current date time to be " + dateTimeNow;
if (Testsuite::handleInteractiveInput(dateTimeNow, "Correct!", "Wrong", kOptionRight)) {
return kTestFailed;
}
}
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 kTestPassed;
}
}
}
return kTestFailed;
}
TestExitStatus MiscTests::testTimers() {
int valToModify = 0;
if (g_system->getTimerManager()->installTimerProc(timerCallback, 100000, &valToModify, "testbedTimer")) {
g_system->delayMillis(150);
g_system->getTimerManager()->removeTimerProc(timerCallback);
if (999 == valToModify) {
return kTestPassed;
}
}
return kTestFailed;
}
TestExitStatus MiscTests::testMutexes() {
if (ConfParams.isSessionInteractive()) {
if (Testsuite::handleInteractiveInput("Testing the Mutual Exclusion API implementation", "Continue", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Mutex tests skipped by the user.\n");
return kTestSkipped;
}
Testsuite::writeOnScreen("Installing mutex", Common::Point(0, 100));
}
SharedVars sv = {1, 1, true, new Common::Mutex()};
if (g_system->getTimerManager()->installTimerProc(criticalSection, 100000, &sv, "testbedMutex")) {
g_system->delayMillis(150);
}
sv.mutex->lock();
sv.first++;
g_system->delayMillis(1000);
sv.second *= sv.first;
sv.mutex->unlock();
// wait till timed process exits
if (ConfParams.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);
delete sv.mutex;
if (sv.resultSoFar && 6 == sv.second) {
return kTestPassed;
}
return kTestFailed;
}
TestExitStatus MiscTests::testOpenUrl() {
Common::String info = "Testing openUrl() method.\n"
"In this test we'll try to open scummvm.org in your default browser.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : openUrl()\n");
return kTestSkipped;
}
if (!g_system->openUrl("https://scummvm.org/")) {
Testsuite::logPrintf("Info! openUrl() says it couldn't open the url (probably not supported on this platform)\n");
return kTestFailed;
}
if (Testsuite::handleInteractiveInput("Was ScummVM able to open 'https://scummvm.org/' in your default browser?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! openUrl() is not working!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("openUrl() is OK\n");
return kTestPassed;
}
class ImageAlbumImageSupplier : public GUI::ImageAlbumImageSupplier {
public:
void addFile(const Common::Path &path, Common::FormatInfo::FormatID format, bool dontReportFormat);
bool loadImageSlot(uint slot, const Graphics::Surface *&outSurface, bool &outHasPalette, Graphics::Palette &outPalette, GUI::ImageAlbumImageMetadata &outMetadata) override;
void releaseImageSlot(uint slot) override;
bool getFileFormatForImageSlot(uint slot, Common::FormatInfo::FormatID &outFormat) const override;
Common::SeekableReadStream *createReadStreamForSlot(uint slot) override;
uint getNumSlots() const override;
Common::U32String getDefaultFileNameForSlot(uint slot) const override;
private:
struct FileInfo {
FileInfo(const Common::Path &path, Common::FormatInfo::FormatID format, bool dontReportFormat);
Common::Path _path;
Common::FormatInfo::FormatID _format;
bool _dontReportFormat;
Common::SharedPtr<Image::ImageDecoder> _decoder;
};
Common::Array<FileInfo> _slots;
};
void ImageAlbumImageSupplier::addFile(const Common::Path &path, Common::FormatInfo::FormatID format, bool dontReportFormat) {
_slots.push_back(FileInfo(path, format, dontReportFormat));
}
bool ImageAlbumImageSupplier::loadImageSlot(uint slot, const Graphics::Surface *&outSurface, bool &outHasPalette, Graphics::Palette &outPalette, GUI::ImageAlbumImageMetadata &outMetadata) {
FileInfo &fi = _slots[slot];
switch (fi._format) {
case Common::FormatInfo::kBMP:
fi._decoder.reset(new Image::BitmapDecoder());
break;
#ifdef USE_JPEG
case Common::FormatInfo::kJPEG:
fi._decoder.reset(new Image::JPEGDecoder());
break;
#endif
default:
return false;
}
Common::ScopedPtr<Common::SeekableReadStream> readStream(createReadStreamForSlot(slot));
if (!readStream)
return false;
if (!fi._decoder->loadStream(*readStream))
return false;
outSurface = fi._decoder->getSurface();
outHasPalette = fi._decoder->hasPalette();
if (fi._decoder->hasPalette())
outPalette.set(fi._decoder->getPalette(), 0, fi._decoder->getPalette().size());
outMetadata = GUI::ImageAlbumImageMetadata();
return true;
}
void ImageAlbumImageSupplier::releaseImageSlot(uint slot) {
_slots[slot]._decoder.reset();
}
bool ImageAlbumImageSupplier::getFileFormatForImageSlot(uint slot, Common::FormatInfo::FormatID &outFormat) const {
if (_slots[slot]._dontReportFormat)
return false;
outFormat = _slots[slot]._format;
return true;
}
Common::SeekableReadStream *ImageAlbumImageSupplier::createReadStreamForSlot(uint slot) {
Common::ScopedPtr<Common::File> f(new Common::File());
if (!f->open(_slots[slot]._path))
return nullptr;
return f.release();
}
uint ImageAlbumImageSupplier::getNumSlots() const {
return _slots.size();
}
Common::U32String ImageAlbumImageSupplier::getDefaultFileNameForSlot(uint slot) const {
return Common::U32String(_slots[slot]._path.baseName());
}
ImageAlbumImageSupplier::FileInfo::FileInfo(const Common::Path &path, Common::FormatInfo::FormatID format, bool dontReportFormat) {
_path = path;
_format = format;
_dontReportFormat = dontReportFormat;
}
TestExitStatus MiscTests::testImageAlbum() {
Common::String info = "Testing ImageAlbum method.\n"
"In this test we'll try to display some images,\n"
"and you should be able to save them if the backend supports it.\n"
"The second image will not report a file format to the backend, the third (if it exists) will be JPEG.\n";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : ImageAlbum()\n");
return kTestSkipped;
}
ImageAlbumImageSupplier imageSupplier;
imageSupplier.addFile("imagealbum/image1.bmp", Common::FormatInfo::kBMP, false);
imageSupplier.addFile("imagealbum/image2.bmp", Common::FormatInfo::kBMP, true);
#ifdef USE_JPEG
imageSupplier.addFile("imagealbum/image3.jpg", Common::FormatInfo::kJPEG, false);
#endif
GUI::Dialog *dialog = GUI::createImageAlbumDialog(Common::U32String("Image Album"), &imageSupplier, 0);
dialog->runModal();
delete dialog;
if (Testsuite::handleInteractiveInput("Did the image album work as expected?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! ImageAlbum is not working!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("ImageAlbum is OK\n");
return kTestPassed;
}
MiscTestSuite::MiscTestSuite() {
addTest("Datetime", &MiscTests::testDateTime, false);
addTest("Timers", &MiscTests::testTimers, false);
addTest("Mutexes", &MiscTests::testMutexes, false);
addTest("openUrl", &MiscTests::testOpenUrl, true);
addTest("ImageAlbum", &MiscTests::testImageAlbum, true);
}
} // End of namespace Testbed

81
engines/testbed/misc.h Normal file
View File

@@ -0,0 +1,81 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_MISC_H
#define TESTBED_MISC_H
#include "testbed/testsuite.h"
#include "common/mutex.h"
namespace Testbed {
// Shared variables used in mutex handling test
struct SharedVars {
int first;
int second;
bool resultSoFar;
Common::Mutex *mutex;
};
namespace MiscTests {
// Miscellaneous tests include testing datetime, timers and mutexes
// Helper functions for Misc tests
Common::String getHumanReadableFormat(const TimeDate &td);
void timerCallback(void *arg);
void criticalSection(void *arg);
// will contain function declarations for Misc tests
TestExitStatus testDateTime();
TestExitStatus testTimers();
TestExitStatus testMutexes();
TestExitStatus testOpenUrl();
TestExitStatus testImageAlbum();
// 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() override {}
const char *getName() const override {
return "Misc";
}
const char *getDescription() const override {
return "Miscellaneous: Timers/Mutexes/Datetime/openUrl/ImageAlbum";
}
};
} // End of namespace Testbed
#endif // TESTBED_MISC_H

59
engines/testbed/module.mk Normal file
View File

@@ -0,0 +1,59 @@
MODULE := engines/testbed
MODULE_OBJS := \
config.o \
config-params.o \
events.o \
fs.o \
graphics.o \
image.o \
metaengine.o \
midi.o \
misc.o \
networking.o \
savegame.o \
sound.o \
testbed.o \
testsuite.o \
video.o
ifdef USE_CLOUD
MODULE_OBJS += \
cloud.o
endif
ifdef USE_SDL_NET
MODULE_OBJS += \
webserver.o
endif
ifdef USE_TTS
MODULE_OBJS += \
speech.o
endif
ifdef USE_IMGUI
MODULE_OBJS += \
imgui.o
endif
ifdef USE_TINYGL
ifdef USE_OPENGL_GAME # Currently only direct comparisons are implemented
MODULE_OBJS += \
tinygl.o
endif
endif
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
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

View File

@@ -0,0 +1,151 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "testbed/networking.h"
#ifdef USE_BASIC_NET
#include "backends/networking/basic/socket.h"
#include "backends/networking/basic/url.h"
#endif
namespace Testbed {
NetworkingTestSuite::NetworkingTestSuite() {
addTest("IsConnectionLimited", Networkingtests::testConnectionLimit, true);
#ifdef USE_BASIC_NET
addTest("URL", Networkingtests::testURL, true);
addTest("Socket", Networkingtests::testSocket, true);
#endif
}
TestExitStatus Networkingtests::testConnectionLimit() {
if (ConfParams.isSessionInteractive()) {
if (Testsuite::handleInteractiveInput("Testing the IsConnectionLimited API implementation", "Continue", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! IsConnectionLimited test skipped by the user.\n");
return kTestSkipped;
}
bool isLimited = g_system->isConnectionLimited();
if (ConfParams.isSessionInteractive()) {
Common::String status = "We expect the internet connection to be ";
status += (isLimited ? "limited." : "unlimited.");
if (Testsuite::handleInteractiveInput(status, "Correct!", "Wrong", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! isConnectionLimited failed\n");
return kTestFailed;
}
}
Testsuite::logDetailedPrintf("isConnectionLimited worked\n");
return kTestPassed;
}
return TestExitStatus();
}
#ifdef USE_BASIC_NET
TestExitStatus Networkingtests::testURL() {
if (ConfParams.isSessionInteractive()) {
if (Testsuite::handleInteractiveInput("Testing the URL API implementation", "Continue", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! URL test skipped by the user.\n");
return kTestSkipped;
}
Common::String lobbyUrl = "https://multiplayer.scummvm.org:9130";
Networking::URL *url = Networking::URL::parseURL(lobbyUrl);
if (!url) {
Testsuite::logDetailedPrintf("Error! URL parsing failed\n");
return kTestFailed;
}
if (url->getScheme() != "https") {
Testsuite::logDetailedPrintf("Error! URL scheme was unexpected\n");
return kTestFailed;
}
if (url->getHost() != "multiplayer.scummvm.org") {
Testsuite::logDetailedPrintf("Error! URL host was unexpected\n");
return kTestFailed;
}
if (url->getPort() != 9130) {
Testsuite::logDetailedPrintf("Error! URL port was unexpected\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("URL test worked\n");
return kTestPassed;
}
return TestExitStatus();
}
TestExitStatus Networkingtests::testSocket() {
if (ConfParams.isSessionInteractive()) {
if (Testsuite::handleInteractiveInput("Testing the Socket API implementation", "Continue", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Socket test skipped by the user.\n");
return kTestSkipped;
}
Common::String lobbyUrl = "https://multiplayer.scummvm.org:9130";
Networking::Socket *socket = Networking::Socket::connect(lobbyUrl);
if (!socket) {
Testsuite::logDetailedPrintf("Error! Socket connection failed\n");
return kTestFailed;
}
const char *send = "{\"cmd\": \"get_population\", \"area\": 8}\n";
size_t length = strlen(send);
if (socket->send(send, length) != length) {
Testsuite::logDetailedPrintf("Error! Socket send failed\n");
return kTestFailed;
}
int i;
for (i = 0; i < 10; i++) {
if (socket->ready()) {
break;
}
g_system->delayMillis(1000);
}
if (i == 10) {
Testsuite::logDetailedPrintf("Error! Socket ready check failed\n");
return kTestFailed;
}
char reply[1024];
length = socket->recv(reply, sizeof(reply));
if (length == 0) {
Testsuite::logDetailedPrintf("Error! Socket receive failed\n");
return kTestFailed;
}
reply[length] = 0;
debug("Socket received: %s", reply);
Testsuite::logDetailedPrintf("URL test worked\n");
return kTestPassed;
}
return TestExitStatus();
}
#endif
} // namespace Testbed

View File

@@ -0,0 +1,60 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_NETWORKING_H
#define TESTBED_NETWORKING_H
#include "testbed/testsuite.h"
// This file can be used as template for header files of other newer testsuites.
namespace Testbed {
namespace Networkingtests {
// Helper functions for Networking tests
TestExitStatus testConnectionLimit();
#ifdef USE_BASIC_NET
TestExitStatus testSocket();
TestExitStatus testURL();
#endif
} // End of namespace Networkingtests
class NetworkingTestSuite : public Testsuite {
public:
NetworkingTestSuite();
~NetworkingTestSuite() {}
const char *getName() const {
return "Networking";
}
const char *getDescription() const {
return "Network and internet subsystems";
}
};
} // End of namespace Testbed
#endif // TESTBED_NETWORKING_H

View File

@@ -0,0 +1,194 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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;
}
TestExitStatus 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 kTestFailed;
}
// Verify if it contains the same data
if (!readAndVerifyData("tBedSavefile.0", "ScummVM Rocks!")) {
Testsuite::logDetailedPrintf("Reading data from savefile failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus 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 kTestFailed;
}
// 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 kTestFailed;
}
return kTestPassed;
}
TestExitStatus 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 kTestFailed;
}
// 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 kTestFailed;
}
return kTestPassed;
}
TestExitStatus 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 err = saveFileMan->getError();
if (err.getCode() != Common::kNoError) {
// Abort. Some Error in writing files
Testsuite::logDetailedPrintf("Error while creating savefiles: %s\n", err.getDesc().c_str());
return kTestFailed;
}
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 kTestFailed;
}
}
}
return kTestPassed;
}
Testsuite::logDetailedPrintf("listing Savefiles failed!\n");
return kTestFailed;
}
TestExitStatus 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 err = saveFileMan->getError();
if (err.getCode() == Common::kNoError) {
// blunder! how come?
Testsuite::logDetailedPrintf("SaveFileMan.getError() failed\n");
return kTestFailed;
}
// Can't actually predict whether which error, kInvalidPath or kPathDoesNotExist or some other?
// So just return kTestPassed if some error
Testsuite::logDetailedPrintf("getError returned : %s\n", saveFileMan->getErrorDesc().c_str());
return kTestPassed;
}
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

View File

@@ -0,0 +1,66 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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
TestExitStatus testSaveLoadState();
TestExitStatus testRemovingSavefile();
TestExitStatus testRenamingSavefile();
TestExitStatus testListingSavefile();
TestExitStatus 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() override {}
const char *getName() const override {
return "SaveGames";
}
const char *getDescription() const override {
return "Saving Game state tests";
}
};
} // End of namespace Testbed
#endif // TESTBED_SAVEGAME_H

375
engines/testbed/sound.cpp Normal file
View File

@@ -0,0 +1,375 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/softsynth/pcspk.h"
#include "audio/mods/mod_xm_s3m.h"
#include "audio/mods/universaltracker.h"
#include "backends/audiocd/audiocd.h"
#include "common/config-manager.h"
#include "common/events.h"
#include "common/file.h"
#include "testbed/testbed.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::PCSpeakerStream *s1 = new Audio::PCSpeakerStream();
Audio::PCSpeakerStream *s2 = new Audio::PCSpeakerStream();
Audio::PCSpeakerStream *s3 = new Audio::PCSpeakerStream();
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);
}
}
TestExitStatus SoundSubsystem::playBeeps() {
Testsuite::clearScreen();
TestExitStatus passed = kTestPassed;
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 kTestSkipped;
}
Audio::PCSpeakerStream *speaker = new Audio::PCSpeakerStream();
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 = kTestFailed;
}
// 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 = kTestFailed;
}
return passed;
}
TestExitStatus SoundSubsystem::mixSounds() {
Testsuite::clearScreen();
TestExitStatus passed = kTestPassed;
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 kTestSkipped;
}
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 = kTestFailed;
}
return passed;
}
const char *music[] = {
"music0167.xm",
"music0360.xm",
"music0038.mo3",
"music0077.it",
"music0078.it",
0
};
TestExitStatus SoundSubsystem::modPlayback() {
Testsuite::clearScreen();
TestExitStatus passed = kTestPassed;
Common::String info = "Testing Module Playback\n"
"You should hear 5 melodies\n";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : Mod Playback\n");
return kTestSkipped;
}
Common::FSNode gameRoot(ConfMan.getPath("path"));
SearchMan.addSubDirectoryMatching(gameRoot, "audiocd-files");
Common::File f;
Audio::Mixer *mixer = g_system->getMixer();
Common::Point pt(0, 100);
Common::Point pt2(0, 110);
for (int i = 0; music[i]; i++) {
f.open(music[i]);
if (!f.isOpen())
continue;
Audio::RewindableAudioStream *mod = nullptr;
if (Audio::probeModXmS3m(&f))
mod = Audio::makeModXmS3mStream(&f, DisposeAfterUse::NO);
if (!mod) {
mod = Audio::makeUniversalTrackerStream(&f, DisposeAfterUse::NO);
}
if (!mod) {
Testsuite::displayMessage(Common::String::format("Could not load MOD file '%s'", music[i]));
f.close();
continue;
}
Audio::SoundHandle handle;
mixer->playStream(Audio::Mixer::kMusicSoundType, &handle, mod);
Common::EventManager *eventMan = g_system->getEventManager();
Common::Event event;
while (mixer->isSoundHandleActive(handle)) {
g_system->delayMillis(10);
Testsuite::writeOnScreen(Common::String::format("Playing Now: %s", music[i]), pt);
Testsuite::writeOnScreen("Click to stop.", pt2);
if (eventMan->pollEvent(event)) {
// Quit if explicitly requested!
if (Engine::shouldQuit()) {
break;
}
if (event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_RBUTTONDOWN)
break;
}
}
if (Engine::shouldQuit()) {
break;
}
g_system->delayMillis(10);
mixer->stopAll();
f.close();
}
mixer->stopAll();
if (Testsuite::handleInteractiveInput("Were you able to hear the music?", "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! No MOD playback\n");
passed = kTestFailed;
}
return passed;
}
TestExitStatus SoundSubsystem::audiocdOutput() {
Testsuite::clearScreen();
TestExitStatus passed = kTestPassed;
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 kTestSkipped;
}
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Playing the tracks of testCD in order i.e 1-2-3-last", pt);
// Play all tracks
for (int i = 1; i < 5; i++) {
g_system->getAudioCDManager()->play(i, 1, 0, 0);
while (g_system->getAudioCDManager()->isPlaying()) {
g_system->delayMillis(500);
Testsuite::writeOnScreen(Common::String::format("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 _system->getAudioCDManager()->play() or probably sound files were not detected, try -d1 (debuglevel 1)\n");
passed = kTestFailed;
}
return passed;
}
TestExitStatus SoundSubsystem::sampleRates() {
Common::String info = "Testing Multiple Sample Rates.\n"
"Here we try to play sounds at three different sample rates.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : Sample Rates\n");
return kTestSkipped;
}
TestExitStatus passed = kTestPassed;
Audio::Mixer *mixer = g_system->getMixer();
Audio::PCSpeakerStream *s1 = new Audio::PCSpeakerStream();
// Stream at half sampling rate
Audio::PCSpeakerStream *s2 = new Audio::PCSpeakerStream(s1->getRate() - 10000);
// Stream at twice sampling rate
Audio::PCSpeakerStream *s3 = new Audio::PCSpeakerStream(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::format("Playing at sample 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::format("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::format("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 = kTestFailed;
}
return passed;
}
SoundSubsystemTestSuite::SoundSubsystemTestSuite() {
addTest("SimpleBeeps", &SoundSubsystem::playBeeps, true);
addTest("MixSounds", &SoundSubsystem::mixSounds, true);
addTest("MODPlayback", &SoundSubsystem::modPlayback, true);
// Make audio-files discoverable
Common::FSNode gameRoot(ConfMan.getPath("path"));
if (gameRoot.exists()) {
SearchMan.addSubDirectoryMatching(gameRoot, "audiocd-files", 0, 2, false);
if (SearchMan.hasFile("track01.mp3") && SearchMan.hasFile("track02.mp3") && SearchMan.hasFile("track03.mp3") && SearchMan.hasFile("track04.mp3")) {
addTest("AudiocdOutput", &SoundSubsystem::audiocdOutput, true);
} else {
Testsuite::logPrintf("Warning! Skipping test AudioCD: Required data files missing, check game-dir/audiocd-files\n");
}
}
addTest("SampleRates", &SoundSubsystem::sampleRates, true);
}
} // End of namespace Testbed

76
engines/testbed/sound.h Normal file
View File

@@ -0,0 +1,76 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_SOUND_H
#define TESTBED_SOUND_H
#include "audio/mixer.h"
#include "testbed/config.h"
#include "testbed/testsuite.h"
namespace Testbed {
class SoundSubsystemDialog : public TestbedInteractionDialog {
public:
SoundSubsystemDialog();
~SoundSubsystemDialog() override {}
void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
Audio::Mixer *_mixer;
Audio::SoundHandle _h1, _h2, _h3;
};
namespace SoundSubsystem {
// Helper functions for SoundSubsystem tests
// will contain function declarations for SoundSubsystem tests
TestExitStatus playBeeps();
TestExitStatus mixSounds();
TestExitStatus modPlayback();
TestExitStatus audiocdOutput();
TestExitStatus 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() override {}
const char *getName() const override {
return "SoundSubsystem";
}
const char *getDescription() const override {
return "Sound Subsystem";
}
};
} // End of namespace Testbed
#endif // TESTBED_SOUND_H

605
engines/testbed/speech.cpp Normal file
View File

@@ -0,0 +1,605 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/text-to-speech.h"
#include "common/system.h"
#include "common/events.h"
#include "common/array.h"
#include "engines/testbed/speech.h"
namespace Testbed {
void Speechtests::waitForSpeechEnd(Common::TextToSpeechManager *ttsMan) {
Common::Event event;
while (ttsMan->isSpeaking()) {
g_system->delayMillis(100);
g_system->getEventManager()->pollEvent(event);
}
}
void Speechtests::delaySeconds(int sec) {
Common::Event event;
int loop = sec * 10;
while (loop) {
g_system->delayMillis(100);
g_system->getEventManager()->pollEvent(event);
--loop;
}
}
TestExitStatus Speechtests::testMale() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
Testsuite::clearScreen();
Common::String info = "Male voice test. You should expect a male voice to say \"Testing text to speech with male voice.\"";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing male TTS voice", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testMale\n");
return kTestSkipped;
}
Common::Array<int> maleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
if (maleVoices.size() == 0) {
Testsuite::displayMessage("No male voice available");
return kTestFailed;
}
ttsMan->setVoice(maleVoices[0]);
ttsMan->say("Testing text to speech with male voice.");
if (!ttsMan->isSpeaking()) {
Testsuite::logDetailedPrintf("Male TTS failed\n");
return kTestFailed;
}
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear male voice saying: \"Testing text to speech with male voice.\" ?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("Male TTS failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testFemale() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
Testsuite::clearScreen();
Common::String info = "Female voice test. You should expect a female voice to say \"Testing text to speech with female voice.\"";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing female TTS voice", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testFemale\n");
return kTestSkipped;
}
Common::Array<int> femaleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
if (femaleVoices.size() == 0) {
Testsuite::logDetailedPrintf("Female TTS failed\n");
return kTestFailed;
}
ttsMan->setVoice(femaleVoices[0]);
ttsMan->say("Testing text to speech with female voice.");
if (!ttsMan->isSpeaking()) {
Testsuite::logDetailedPrintf("Female TTS failed\n");
return kTestFailed;
}
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear female voice saying: \"Testing text to speech with female voice.\" ?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("Female TTS failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testStop() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech stop test. You should expect a voice to start speaking and after approximately a second it should stop the speech";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS stop", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testStop\n");
return kTestSkipped;
}
ttsMan->say("Testing text to speech, the speech should stop after approximately a second after it started, so it shouldn't have the time to read this.");
delaySeconds(1);
ttsMan->stop();
// It is allright if the voice isn't available right away, but a second should be
// enough for the TTS to recover and get ready.
delaySeconds(1);
if (!ttsMan->isReady()) {
Testsuite::logDetailedPrintf("TTS stop failed\n");
return kTestFailed;
}
Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should stop after approximately a second after it started, so it shouldn't have the time to read this.\" but stopping in the middle?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS stop failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testStopAndSpeak() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech stop and speak test. You should expect a voice to start speaking and after approximately a second it should stop the speech and start another sentence.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS stop and speak", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testStop\n");
return kTestSkipped;
}
ttsMan->say("Testing text to speech, the speech should stop after approximately a second after it started, so it shouldn't have the time to read this.");
delaySeconds(1);
ttsMan->stop();
ttsMan->say("Now starting the second sentence.", Common::TextToSpeechManager::QUEUE);
ttsMan->say("You should hear that one in totality.", Common::TextToSpeechManager::QUEUE);
if (!ttsMan->isSpeaking()) {
Testsuite::logDetailedPrintf("Male TTS failed\n");
return kTestFailed;
}
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should stop after approximately a second after it started, so it shouldn't have the time to read this.\" but stopping in the middle, and then saying \"Now starting the second sentence. You should hear that one in totality.\"?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS stop failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testPauseResume() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech pause test. You should expect a voice to start speaking, then after approximately a second of speech, it should pause and then continue from where it left.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS pause", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testPauseResume\n");
return kTestSkipped;
}
ttsMan->say("Testing text to speech, the speech should pause after a second");
delaySeconds(1);
ttsMan->pause();
if (!ttsMan->isPaused()) {
Testsuite::logDetailedPrintf("TTS pause failed\n");
return kTestFailed;
}
ttsMan->say("and then resume again", Common::TextToSpeechManager::QUEUE);
delaySeconds(3);
if (!ttsMan->isPaused()) {
Testsuite::logDetailedPrintf("TTS pause failed\n");
return kTestFailed;
}
ttsMan->resume();
if (!ttsMan->isSpeaking()) {
Testsuite::logDetailedPrintf("TTS pause failed\n");
return kTestFailed;
}
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should pause after a second and then resume again.\" but with a pause in the middle?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS pauseResume failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testRate() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech rate test. You should expect a voice to say: \"Text to speech slow rate.\" really slowly and then \"Text to speech fast rate\" really fast";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS rate", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testRate\n");
return kTestSkipped;
}
ttsMan->setRate(-100);
ttsMan->say("Text to speech slow rate.");
waitForSpeechEnd(ttsMan);
ttsMan->setRate(100);
ttsMan->say("Text to speech fast rate.");
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying: \"Text to speech slow rate.\" slowly and then \"Text to speech fast rate.\" fast?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS rate failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testVolume() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech volume test. You should expect a voice to say: \"Text to speech low volume.\" quietly and then \"Text to speech max volume\" at a higher volume";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS volume", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testVolume\n");
return kTestSkipped;
}
ttsMan->setVolume(20);
ttsMan->say("Text to speech low volume.");
waitForSpeechEnd(ttsMan);
ttsMan->setVolume(100);
ttsMan->say("Text to speech max volume.");
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying: \"Text to speech low volume.\" quietly and then \"Text to speech max volume.\" at a higher volume?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS volume failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testPitch() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech pitch test. You should expect a high pitched voice to say: \"Text to speech high pitch.\" and then a low pitched voice: \"Text to speech low pitch\"";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS pitch", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testPitch\n");
return kTestSkipped;
}
ttsMan->setPitch(100);
ttsMan->say("Text to speech high pitch.");
waitForSpeechEnd(ttsMan);
ttsMan->setPitch(-100);
ttsMan->say("Text to speech low pitch.");
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a high pitched voice saying: \"Text to speech high pitch.\" and then a low pitched voice: \"Text to speech low pitch.\" ?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS pitch failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testStateStacking() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech state stacking test. You should expect a speech from three different voices (different pitch, gender, volume and speech rate), each voice will say: \"Voice number X is speaking.\", the voices will speak in this order: 1, 2, 3, 2, 1. A voice with the same number should sound the same every time";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS state stacking", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testStateStacking\n");
return kTestSkipped;
}
ttsMan->say("Voice number 1 is speaking");
waitForSpeechEnd(ttsMan);
ttsMan->pushState();
Common::Array<int> femaleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
Common::Array<Common::TTSVoice> allVoices = ttsMan->getVoicesArray();
if (femaleVoices.size() == 0)
ttsMan->setVoice(1 % allVoices.size());
else
ttsMan->setVoice(femaleVoices[0]);
ttsMan->setVolume(80);
ttsMan->setPitch(40);
ttsMan->setRate(-30);
ttsMan->say("Voice number 2 is speaking");
waitForSpeechEnd(ttsMan);
ttsMan->pushState();
ttsMan->setVoice(2 % allVoices.size());
ttsMan->setVolume(90);
ttsMan->setPitch(-80);
ttsMan->setRate(-50);
ttsMan->say("Voice number 3 is speaking");
waitForSpeechEnd(ttsMan);
ttsMan->popState();
ttsMan->say("Voice number 2 is speaking");
waitForSpeechEnd(ttsMan);
ttsMan->popState();
ttsMan->say("Voice number 1 is speaking");
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear three different voices speaking in this order: 1, 2, 3, 2, 1 and each time the same voice spoke, it sounded the same?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS state stacking\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testQueueing() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech queue test. You should expect a voice to say: \"This is first speech. This is second speech\"";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS queue", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testQueueing\n");
return kTestSkipped;
}
ttsMan->say("This is first speech.");
ttsMan->say("This is second speech.", Common::TextToSpeechManager::QUEUE);
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying: \"This is first speech. This is second speech\" ?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS queue failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testInterrupting() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech interrupt test. You should expect a voice to start saying english alphabet and after about a second it should get interrupted and say: \"Speech interrupted\" instead.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS interrupt", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testInterrupting\n");
return kTestSkipped;
}
ttsMan->say("A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z");
delaySeconds(1);
ttsMan->say("Speech interrupted", Common::TextToSpeechManager::INTERRUPT);
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying the engilsh alphabet, but it got interrupted and said: \"Speech interrupted\" instead?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS interrupt failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testDroping() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech drop test. You should expect a voice to start say:\"Today is a really nice weather, perfect day to use ScummVM, don't you think?\" and nothing else.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS drop", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testDroping\n");
return kTestSkipped;
}
ttsMan->say("Today is a really nice weather, perfect day to use ScummVM, don't you think?");
ttsMan->say("Speech interrupted, fail", Common::TextToSpeechManager::DROP);
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice say: \"Today is a really nice weather, perfect day to use ScummVM, don't you think?\" and nothing else?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS drop failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testInterruptNoRepeat() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech interrupt no repeat test. You should expect a voice to start saying:\"This is the first sentence, this should get interrupted\", but the speech gets interrupted and \"This is the second sentence, it should play only once\" is said instead.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS Interrupt No Repeat", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testInterruptNoRepeat\n");
return kTestSkipped;
}
ttsMan->say("This is the first sentence, this should get interrupted");
ttsMan->say("Failure", Common::TextToSpeechManager::QUEUE);
delaySeconds(1);
ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
ttsMan->say("Failure", Common::TextToSpeechManager::QUEUE);
delaySeconds(1);
ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
ttsMan->say("Failure", Common::TextToSpeechManager::QUEUE);
delaySeconds(1);
ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice say: \"This is the first sentence, this should get interrupted\", but it got interrupted and \"This is the second sentence, it should play only once.\" got said instead?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS interruptNoRepeat failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testQueueNoRepeat() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech queue no repeat test. You should expect a voice to start say:\"This is the first sentence. This is the second sentence\" and nothing else";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS Queue No Repeat", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testQueueNoRepeat\n");
return kTestSkipped;
}
ttsMan->say("This is the first sentence.");
ttsMan->say("This is the first sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
delaySeconds(1);
ttsMan->say("This is the first sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
ttsMan->say("This is the second sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
ttsMan->say("This is the second sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
delaySeconds(1);
ttsMan->say("This is the second sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice say: \"This is the first sentence. This the second sentence\" and nothing else?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS QueueNoRepeat failed\n");
return kTestFailed;
}
return kTestPassed;
}
TestExitStatus Speechtests::testQueueEmptyString() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
ttsMan->setVolume(100);
ttsMan->setRate(0);
ttsMan->setPitch(0);
ttsMan->setVoice(ttsMan->getDefaultVoice());
Testsuite::clearScreen();
Common::String info = "Text to speech queue empty test. You should expect a voice to start say:\"This is the first sentence. This is the third sentence\"";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS Queue No Repeat", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testQueueNoRepeat\n");
return kTestSkipped;
}
ttsMan->say("This is the first sentence.");
ttsMan->say("", Common::TextToSpeechManager::QUEUE);
ttsMan->say("This is the third sentence.", Common::TextToSpeechManager::QUEUE);
waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice say: \"This is the first sentence. This the third sentence\"?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS QueueEmptyText failed\n");
return kTestFailed;
}
return kTestPassed;
}
SpeechTestSuite::SpeechTestSuite() {
_isTsEnabled = true;
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan)
ttsMan->enable(true);
else
_isTsEnabled = false;
addTest("testMale", &Speechtests::testMale, true);
addTest("testFemale", &Speechtests::testFemale, true);
addTest("testStop", &Speechtests::testStop, true);
addTest("testStopAndSpeak", &Speechtests::testStopAndSpeak, true);
addTest("testPauseResume", &Speechtests::testPauseResume, true);
addTest("testRate", &Speechtests::testRate, true);
addTest("testVolume", &Speechtests::testVolume, true);
addTest("testPitch", &Speechtests::testPitch, true);
addTest("testStateStacking", &Speechtests::testStateStacking, true);
addTest("testQueueing", &Speechtests::testQueueing, true);
addTest("testInterrupting", &Speechtests::testInterrupting, true);
addTest("testDroping", &Speechtests::testDroping, true);
addTest("testInterruptNoRepeat", &Speechtests::testInterruptNoRepeat, true);
addTest("testQueueNoRepeat", &Speechtests::testQueueNoRepeat, true);
addTest("testQueueEmptyString", &Speechtests::testQueueEmptyString, true);
}
} // End of namespace Testbed

84
engines/testbed/speech.h Normal file
View File

@@ -0,0 +1,84 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_SPEECH_H
#define TESTBED_SPEECH_H
#include "testbed/testsuite.h"
#include "common/text-to-speech.h"
namespace Testbed {
namespace Speechtests {
// Helper functions for Speech tests
// will contain function declarations for Speech tests
// add more here
TestExitStatus testMale();
TestExitStatus testFemale();
TestExitStatus testStop();
TestExitStatus testStopAndSpeak();
TestExitStatus testPauseResume();
TestExitStatus testRate();
TestExitStatus testVolume();
TestExitStatus testPitch();
TestExitStatus testStateStacking();
TestExitStatus testQueueing();
TestExitStatus testInterrupting();
TestExitStatus testDroping();
TestExitStatus testInterruptNoRepeat();
TestExitStatus testQueueNoRepeat();
TestExitStatus testQueueEmptyString();
// Utility function to avoid dupplicated code
void waitForSpeechEnd(Common::TextToSpeechManager *);
void delaySeconds(int);
} // End of namespace Speechtests
class SpeechTestSuite : 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()
*/
SpeechTestSuite();
~SpeechTestSuite() override {}
const char *getName() const override {
return "Speech";
}
const char *getDescription() const override {
return "Speech Subsystem";
}
};
} // End of namespace Testbed
#endif // TESTBED_SPEECH_H

View File

@@ -0,0 +1,64 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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

290
engines/testbed/testbed.cpp Normal file
View File

@@ -0,0 +1,290 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug-channels.h"
#include "common/scummsys.h"
#include "common/archive.h"
#include "common/config-manager.h"
#include "common/error.h"
#include "common/fs.h"
#include "common/rect.h"
#include "common/str.h"
#include "engines/achievements.h"
#include "engines/util.h"
#include "gui/textviewer.h"
#include "gui/gui-manager.h"
#include "testbed/events.h"
#include "testbed/fs.h"
#include "testbed/graphics.h"
#include "testbed/image.h"
#include "testbed/midi.h"
#include "testbed/misc.h"
#include "testbed/networking.h"
#include "testbed/savegame.h"
#include "testbed/sound.h"
#include "testbed/testbed.h"
#include "testbed/video.h"
#ifdef USE_CLOUD
#include "testbed/cloud.h"
#endif
#ifdef USE_SDL_NET
#include "testbed/webserver.h"
#endif
#ifdef USE_TTS
#include "testbed/speech.h"
#endif
#ifdef USE_IMGUI
#include "testbed/imgui.h"
#endif
#if defined USE_TINYGL && defined USE_OPENGL_GAME
#include "testbed/tinygl.h"
#endif
namespace Testbed {
void TestbedExitDialog::init() {
resize(80, 40, 500, 330);
_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::U32String> strArray;
Common::U32String color;
for (Common::Array<Testsuite *>::const_iterator i = _testsuiteList.begin(); i != _testsuiteList.end(); ++i) {
color = GUI::ListWidget::getThemeColor(GUI::ThemeEngine::kFontColorNormal);
strArray.push_back(color + Common::U32String::format("%s :", (*i)->getDescription()));
color = GUI::ListWidget::getThemeColor(GUI::ThemeEngine::kFontColorAlternate);
if ((*i)->isEnabled()) {
strArray.push_back(color + Common::U32String::format("Passed: %d Failed: %d Skipped: %d", (*i)->getNumTestsPassed(), (*i)->getNumTestsFailed(), (*i)->getNumTestsSkipped()));
} else {
strArray.push_back(color + Common::U32String("Skipped"));
}
}
addList(0, _yOffset, 500, 200, strArray);
text = "More Details can be viewed in the Log file : " + ConfParams.getLogFilename();
addText(450, 20, text, Graphics::kTextAlignLeft, 0, 0);
if (ConfParams.getLogDirectory().empty()) {
text = "Directory : .";
} else {
text = "Directory : " + ConfParams.getLogDirectory().toString(Common::Path::kNativeSeparator);
}
addText(500, 20, text, Graphics::kTextAlignLeft, 0, 0);
_yOffset += 5;
addButtonXY(_xOffset + 80, _yOffset, 120, 24, "Rerun test suite", kCmdRerunTestbed);
addButtonXY(_xOffset + 240, _yOffset, 60, 24, "Close", GUI::kCloseCmd);
addButtonXY(_xOffset + 340, _yOffset, 60, 24, "Open Log", kViewLogCmd);
}
void TestbedExitDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
default:
break;
case kCmdRerunTestbed:
ConfParams.setRerunFlag(true);
cmd = GUI::kCloseCmd;
break;
case kViewLogCmd:
Common::Path logPath = Common::Path(ConfParams.getLogDirectory());
GUI::TextViewerDialog viewer(logPath.appendComponent(ConfParams.getLogFilename()));
viewer.runModal();
g_gui.scheduleTopDialogRedraw();
break;
}
GUI::Dialog::handleCommand(sender, cmd, data);
}
bool TestbedEngine::hasFeature(EngineFeature f) const {
return (f == kSupportsReturnToLauncher) ? 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
// Put game-data dir in search path
Common::FSNode gameRoot(ConfMan.getPath("path"));
if (gameRoot.exists()) {
SearchMan.addDirectory(gameRoot.getDisplayName(), gameRoot);
}
DebugMan.enableDebugChannel("LOG");
pushTestsuites(_testsuiteList);
}
void TestbedEngine::pushTestsuites(Common::Array<Testsuite *> &testsuiteList) {
// Initialize testsuites here
Testsuite *ts;
// GFX
ts = new GFXTestSuite();
testsuiteList.push_back(ts);
// Image
ts = new ImageTestSuite();
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);
// Midi
ts = new MidiTestSuite();
testsuiteList.push_back(ts);
// Networking
ts = new NetworkingTestSuite();
testsuiteList.push_back(ts);
#ifdef USE_TTS
// TextToSpeech
ts = new SpeechTestSuite();
testsuiteList.push_back(ts);
#endif
#ifdef USE_CLOUD
// Cloud
ts = new CloudTestSuite();
testsuiteList.push_back(ts);
#endif
#ifdef USE_SDL_NET
// Webserver
ts = new WebserverTestSuite();
testsuiteList.push_back(ts);
#endif
#ifdef USE_IMGUI
// ImGui
ts = new ImGuiTestSuite();
testsuiteList.push_back(ts);
#endif
// Video decoder
ts = new VideoDecoderTestSuite();
testsuiteList.push_back(ts);
#if defined USE_TINYGL && defined USE_OPENGL_GAME
// TinyGL
ts = new TinyGLTestSuite();
testsuiteList.push_back(ts);
#endif
}
TestbedEngine::~TestbedEngine() {
ConfParams.deleteWriteStream();
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();
if (!numSuitesEnabled)
return;
for (iter = _testsuiteList.begin(); iter != _testsuiteList.end(); iter++) {
if (shouldQuit()) {
return;
}
(*iter)->reset();
if ((*iter)->isEnabled()) {
Testsuite::updateStats("Testsuite", (*iter)->getName(), count++, numSuitesEnabled, pt);
(*iter)->execute();
}
if ((*iter)->getNumTests() == (*iter)->getNumTestsPassed()) {
AchMan.setAchievement((*iter)->getName());
checkForAllAchievements();
}
}
}
void TestbedEngine::checkForAllAchievements() {
Common::Array<Testsuite *>::const_iterator iter;
for (iter = _testsuiteList.begin(); iter != _testsuiteList.end(); iter++) {
if (!AchMan.isAchieved((*iter)->getName())) {
return;
}
}
AchMan.setAchievement("EVERYTHINGWORKS");
}
Common::Error TestbedEngine::run() {
if (ConfMan.hasKey("start_movie")) {
return Videotests::videoTest(ConfMan.getPath("start_movie"));
}
// Initialize graphics using following:
initGraphics(320, 200);
// Initialize achievements manager
Common::AchievementsInfo info;
info.platform = Common::UNK_ACHIEVEMENTS;
info.appId = "testbed";
AchMan.setActiveDomain(info);
// 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
do {
Testsuite::clearEntireScreen();
cfMan.selectTestsuites();
// Init logging
ConfParams.initLogging(true);
invokeTestsuites(cfMan);
// Check if user wanted to exit.
if (Engine::shouldQuit()) {
return Common::kNoError;
}
TestbedExitDialog tbDialog(_testsuiteList);
tbDialog.init();
tbDialog.run();
} while (ConfParams.isRerunRequired());
return Common::kNoError;
}
} // End of namespace Testbed

82
engines/testbed/testbed.h Normal file
View File

@@ -0,0 +1,82 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_TESTBED_H
#define TESTBED_TESTBED_H
#include "common/array.h"
#include "engines/engine.h"
#include "testbed/config.h"
#include "testbed/testsuite.h"
namespace Testbed {
class TestbedConfigManager;
enum {
kTestbedLogOutput = 1,
kTestbedEngineDebug,
kViewLogCmd = 'vwlg',
kCmdRerunTestbed = 'crtb'
};
class TestbedEngine : public Engine {
public:
TestbedEngine(OSystem *syst);
~TestbedEngine() override;
Common::Error run() override;
/**
* Invokes configured testsuites.
*/
static void pushTestsuites(Common::Array<Testsuite *> &testsuiteList);
/**
* Invokes configured testsuites.
*/
void invokeTestsuites(TestbedConfigManager &cfMan);
bool hasFeature(EngineFeature f) const override;
private:
void checkForAllAchievements();
Common::Array<Testsuite *> _testsuiteList;
};
class TestbedExitDialog : public TestbedInteractionDialog {
public:
TestbedExitDialog(Common::Array<Testsuite *> &testsuiteList) : TestbedInteractionDialog(80, 40, 500, 330),
_testsuiteList(testsuiteList) {}
~TestbedExitDialog() override {}
void init();
void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
void run() { runModal(); }
private:
Common::Array<Testsuite *> &_testsuiteList;
};
} // End of namespace Testbed
#endif // TESTBED_TESTBED_H

View File

@@ -0,0 +1,333 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/events.h"
#include "common/stream.h"
#include "engines/achievements.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 {
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);
Common::WriteStream *ws = ConfigParams::instance().getLogWriteStream();
if (ws) {
ws->writeString(buffer);
ws->flush();
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);
Common::WriteStream *ws = ConfigParams::instance().getLogWriteStream();
if (ws) {
ws->writeString(buffer);
ws->flush();
debugCN(1, kTestbedLogOutput, "%s", buffer);
} else {
debugCN(1, kTestbedLogOutput, "%s", buffer);
}
}
Testsuite::Testsuite() {
_numTestsPassed = 0;
_numTestsExecuted = 0;
_numTestsSkipped = 0;
_toQuit = kLoopNormal;
// 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;
_numTestsSkipped = 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("Skipped: %d ", _numTestsSkipped);
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.c_str(), opt1, opt2);
return prompt.runModal() == result ? true : false;
}
void Testsuite::displayMessage(const Common::String &textToDisplay, const char *defaultButton) {
GUI::MessageDialog prompt(textToDisplay.c_str(), defaultButton);
prompt.runModal();
}
Common::Rect Testsuite::writeOnScreen(const Common::String &textToDisplay, const Common::Point &pt, WriteFlags flags) {
uint fillColor = kColorBlack;
uint textColor = kColorWhite;
if (flags & kWriteRGBColors) {
Graphics::PixelFormat pf = g_system->getScreenFormat();
fillColor = pf.RGBToColor(0, 0, 0);
textColor = pf.RGBToColor(255, 255, 255);
}
const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont));
int height = font.getFontHeight();
int width = g_system->getWidth();
Common::Rect rect(pt.x, pt.y, pt.x + width, pt.y + height);
g_system->fillScreen(rect, fillColor);
Graphics::Surface *screen = g_system->lockScreen();
if (flags & kWriteDrawFrame) {
screen->frameRect(Common::Rect(width, g_system->getHeight()), textColor);
}
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) {
g_system->fillScreen(rect, kColorBlack);
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]();
g_system->copyRectToScreen(buffer, numBytesPerLine, 0, 0, g_system->getWidth(), height);
g_system->updateScreen();
delete[] buffer;
}
void Testsuite::clearScreen(bool flag) {
uint fillColor = kColorBlack;
if (flag) {
fillColor = g_system->getScreenFormat().RGBToColor(0, 0, 0);
}
g_system->fillScreen(fillColor);
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_RETURN_TO_LAUNCHER:
return kEngineQuit;
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::format(" 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]();
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
uint count = 0;
Common::Point pt = getDisplayRegionCoordinates();
pt.y += getLineSeparation();
int numEnabledTests = getNumTestsEnabled();
if (!numEnabledTests)
return;
prepare();
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());
_numTestsSkipped++;
continue;
}
if ((*i)->isInteractive && !ConfParams.isSessionInteractive()) {
logPrintf("Info! Skipping Test: %s, non-interactive environment is selected\n", ((*i)->featureName).c_str());
_numTestsSkipped++;
continue;
}
logPrintf("Info! Executing Test: %s\n", ((*i)->featureName).c_str());
updateStats("Test", ((*i)->featureName).c_str(), count++, numEnabledTests, pt);
// Run the test and capture exit status.
TestExitStatus eStatus = (*i)->driver();
if (kTestPassed == eStatus) {
logPrintf("Result: Passed\n");
_numTestsExecuted++;
_numTestsPassed++;
} else if (kTestSkipped == eStatus){
logPrintf("Result: Skipped\n");
_numTestsSkipped++;
} else {
_numTestsExecuted++;
logPrintf("Result: Failed\n");
}
AchMan.setStatInt("NUM_TESTS", AchMan.getStatInt("NUM_TESTS") + 1);
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 return to launcher
// Check if user wants to quit/return to launcher/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

203
engines/testbed/testsuite.h Normal file
View File

@@ -0,0 +1,203 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_TESTSUITE_H
#define TESTBED_TESTSUITE_H
#include "common/array.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "common/str.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "testbed/config-params.h"
namespace Testbed {
enum {
kColorBlack = 0,
kColorWhite = 1,
kColorCustom = 2,
kColorSpecial = 5 ///< some random number
};
enum OptionSelected {
kOptionLeft = 0,
kOptionRight = 1
};
enum {
kEngineQuit = 0,
kSkipNext = 1,
kLoopNormal = 2,
// Event handling time,(in ms) used in parseEvent()
kEventHandlingTime = 50
};
enum TestExitStatus {
kTestPassed = 0,
kTestSkipped,
kTestFailed
};
enum WriteFlags {
kWriteNoFlag = 0,
kWriteRGBColors = (1 << 0),
kWriteDrawFrame = (1 << 1)
};
typedef TestExitStatus (*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 getNumTestsSkipped() const { return _numTestsSkipped; }
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");
static Common::Rect writeOnScreen(const Common::String &textToDisplay, const Common::Point &pt, WriteFlags flag = kWriteNoFlag);
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);
/**
* Testsuite state preparation code, like tweaking mouse cursors should go there
*/
virtual void prepare() {}
/**
* 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(MSVC_PRINTF const char *s, ...) GCC_PRINTF(1, 2);
static void logDetailedPrintf(MSVC_PRINTF const char *s, ...) GCC_PRINTF(1, 2);
// 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(ConfParams.getCurrentFontUsageType())->getFontHeight() * 3 + 15); // Buffer of 5 pixels per line
return pt;
}
static uint getLineSeparation() {
return FontMan.getFontByUsage(ConfParams.getCurrentFontUsageType())->getFontHeight() + 5;
}
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
int _numTestsSkipped;
bool _isTsEnabled;
private:
/**
* Used from the code to decide if the engine needs to exit
*/
uint _toQuit;
};
} // End of namespace Testbed
#endif // TESTBED_TESTSUITE_H

409
engines/testbed/tinygl.cpp Normal file
View File

@@ -0,0 +1,409 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "engines/util.h"
#include "image/png.h"
#include "graphics/managed_surface.h"
#include "graphics/opengl/system_headers.h"
#include "graphics/opengl/debug.h"
#include "graphics/tinygl/tinygl.h"
#include "testbed/tinygl.h"
namespace Testbed {
namespace TinyGLTests {
struct TextureEnvironmentArg {
GLuint _sourceRGB, _operandRGB;
GLuint _sourceAlpha, _operandAlpha;
TextureEnvironmentArg(
GLuint sourceRGB = GL_TEXTURE,
GLuint operandRGB = GL_SRC_COLOR,
GLuint sourceAlpha = GL_TEXTURE,
GLuint operandAlpha = GL_SRC_ALPHA)
: _sourceRGB(sourceRGB)
, _operandRGB(operandRGB)
, _sourceAlpha(sourceAlpha)
, _operandAlpha(operandAlpha) { }
};
struct TextureEnvironment {
GLuint _mode, _combineRGB, _combineAlpha;
TextureEnvironmentArg _arg0, _arg1;
byte _constantR = 255, _constantG = 255, _constantB = 255, _constantA = 255;
TextureEnvironment(
GLuint mode = GL_REPLACE,
GLuint combineRGB = GL_REPLACE,
GLuint combineAlpha = GL_REPLACE)
: _mode(mode)
, _combineRGB(combineRGB)
, _combineAlpha(combineAlpha) { }
template<typename TexEnvFunci, typename TexEnvFuncfv>
void apply(TexEnvFunci funci, TexEnvFuncfv funcfv) const {
funci(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, _mode);
funci(GL_TEXTURE_ENV, GL_COMBINE_RGB, _combineRGB);
funci(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, _combineAlpha);
funci(GL_TEXTURE_ENV, GL_SOURCE0_RGB, _arg0._sourceRGB);
funci(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, _arg0._sourceAlpha);
funci(GL_TEXTURE_ENV, GL_SOURCE1_RGB, _arg1._sourceRGB);
funci(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, _arg1._sourceAlpha);
funci(GL_TEXTURE_ENV, GL_OPERAND0_RGB, _arg0._operandRGB);
funci(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, _arg0._operandAlpha);
funci(GL_TEXTURE_ENV, GL_OPERAND1_RGB, _arg1._operandRGB);
funci(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, _arg1._operandAlpha);
const float values[] = {
_constantR / 255.0f,
_constantG / 255.0f,
_constantB / 255.0f,
_constantA / 255.0f
};
funcfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, values);
}
};
TestExitStatus runTexEnvTest(
const char *testName,
const TextureEnvironment &env,
byte colorR, byte colorG, byte colorB, byte colorA);
TestExitStatus testTexEnvReplace();
TestExitStatus testTexEnvModulate();
TestExitStatus testTexEnvDecal();
TestExitStatus testTexEnvAdd();
TestExitStatus testTexEnvBlend();
TestExitStatus testTexEnvCombineOpNormal();
TestExitStatus testTexEnvCombineOpInverse();
TestExitStatus testTexEnvCombineOpAlphaToColor();
TestExitStatus testTexEnvCombineMixedArgs();
TestExitStatus testTexEnvCombineReplace();
TestExitStatus testTexEnvCombineModulate();
TestExitStatus testTexEnvCombineAdd();
}
TinyGLTestSuite::TinyGLTestSuite() {
addTest("Replace", &TinyGLTests::testTexEnvReplace);
addTest("Modulate", &TinyGLTests::testTexEnvModulate);
addTest("Decal", &TinyGLTests::testTexEnvDecal);
addTest("Add", &TinyGLTests::testTexEnvAdd);
addTest("Blend", &TinyGLTests::testTexEnvBlend);
addTest("CombineOpNormal", &TinyGLTests::testTexEnvCombineOpNormal);
addTest("CombineOpInverse", &TinyGLTests::testTexEnvCombineOpInverse);
addTest("CombineOpAlphaToColor", &TinyGLTests::testTexEnvCombineOpAlphaToColor);
addTest("CombineMixedArgs", &TinyGLTests::testTexEnvCombineMixedArgs);
addTest("CombineReplace", &TinyGLTests::testTexEnvCombineReplace);
addTest("CombineModulate", &TinyGLTests::testTexEnvCombineModulate);
addTest("CombineAdd", TinyGLTests::testTexEnvCombineAdd);
}
TestExitStatus TinyGLTests::testTexEnvModulate() {
TextureEnvironment env(GL_MODULATE);
return runTexEnvTest("Modulate", env, 50, 111, 222, 255);
}
TestExitStatus TinyGLTests::testTexEnvReplace() {
TextureEnvironment env(GL_REPLACE);
return runTexEnvTest("Replace", env, 255, 0, 255, 255);
}
TestExitStatus TinyGLTests::testTexEnvDecal() {
TextureEnvironment env(GL_DECAL);
return runTexEnvTest("Decal", env, 50, 111, 222, 255);
}
TestExitStatus TinyGLTests::testTexEnvAdd() {
TextureEnvironment env(GL_ADD);
return runTexEnvTest("Add", env, 50, 111, 222, 255);
}
TestExitStatus TinyGLTests::testTexEnvBlend() {
TextureEnvironment env(GL_BLEND);
env._constantR = 250;
env._constantG = 150;
env._constantB = 100;
env._constantA = 50;
return runTexEnvTest("Blend", env, 50, 111, 222, 255);
}
TestExitStatus TinyGLTests::testTexEnvCombineOpNormal() {
TextureEnvironment env(GL_COMBINE, GL_REPLACE, GL_REPLACE);
env._arg0 = { GL_TEXTURE, GL_SRC_COLOR, GL_TEXTURE, GL_SRC_ALPHA };
return runTexEnvTest("CombineOpNormal", env, 50, 111, 222, 255);
}
TestExitStatus TinyGLTests::testTexEnvCombineOpInverse() {
TextureEnvironment env(GL_COMBINE, GL_REPLACE, GL_REPLACE);
env._arg0 = { GL_TEXTURE, GL_ONE_MINUS_SRC_COLOR, GL_TEXTURE, GL_ONE_MINUS_SRC_ALPHA };
return runTexEnvTest("CombineOpInverse", env, 50, 111, 222, 255);
}
TestExitStatus TinyGLTests::testTexEnvCombineOpAlphaToColor() {
TextureEnvironment env(GL_COMBINE, GL_REPLACE, GL_REPLACE);
env._arg0 = { GL_TEXTURE, GL_SRC_ALPHA, GL_TEXTURE, GL_ONE_MINUS_SRC_ALPHA };
return runTexEnvTest("CombineOpAlphaToColor", env, 50, 111, 222, 255);
}
TestExitStatus TinyGLTests::testTexEnvCombineMixedArgs() {
TextureEnvironment env(GL_COMBINE, GL_REPLACE, GL_REPLACE);
env._arg0 = { GL_PRIMARY_COLOR, GL_SRC_ALPHA, GL_TEXTURE, GL_SRC_ALPHA };
return runTexEnvTest("CombineMixedArgs", env, 255, 0, 255, 50);
}
TestExitStatus TinyGLTests::testTexEnvCombineReplace() {
TextureEnvironment env(GL_COMBINE, GL_REPLACE, GL_REPLACE);
env._arg0 = { GL_TEXTURE, GL_SRC_COLOR, GL_PRIMARY_COLOR, GL_SRC_ALPHA };
return runTexEnvTest("CombineReplace", env, 50, 11, 222, 127);
}
TestExitStatus TinyGLTests::testTexEnvCombineModulate() {
TextureEnvironment env(GL_COMBINE, GL_MODULATE, GL_MODULATE);
env._arg0 = { GL_TEXTURE, GL_SRC_COLOR, GL_TEXTURE, GL_SRC_ALPHA };
env._arg1 = { GL_PRIMARY_COLOR, GL_SRC_COLOR, GL_PRIMARY_COLOR, GL_SRC_ALPHA };
return runTexEnvTest("CombineModulate", env, 50, 11, 222, 127);
}
TestExitStatus TinyGLTests::testTexEnvCombineAdd() {
TextureEnvironment env(GL_COMBINE, GL_ADD, GL_ADD);
env._arg0 = { GL_TEXTURE, GL_SRC_COLOR, GL_TEXTURE, GL_SRC_ALPHA };
env._arg1 = { GL_PRIMARY_COLOR, GL_SRC_COLOR, GL_PRIMARY_COLOR, GL_SRC_ALPHA };
return runTexEnvTest("CombineAdd", env, 50, 11, 222, 127);
}
struct TinyGLContextDeleter {
inline void operator()(TinyGL::ContextHandle *handle) {
TinyGL::destroyContext(handle);
}
};
// using cdecl calling convention so we can use a function pointer
static void classicGLTexEnvi(GLenum target, GLenum param, GLint value) {
glTexEnvi(target, param, value);
}
static void classicGLTexEnvfv(GLenum target, GLenum param, const GLfloat *values) {
glTexEnvfv(target, param, values);
}
static void copyAlphaIntoColorChannels(Graphics::ManagedSurface &surface) {
byte a, r, g, b;
for (int y = 0; y < surface.h; y++) {
uint32 *pixel = (uint32*)surface.getBasePtr(0, y);
for (int x = 0; x < surface.w; x++, pixel++) {
surface.format.colorToARGB(*pixel, a, r, g, b);
r = g = b = a;
a = 255;
*pixel = surface.format.ARGBToColor(a, r, g, b);
}
}
}
static constexpr const int kScreenWidth = 800; // enough space for a 2x2 grid and some padding
static constexpr const int kScreenHeight = 600;
static constexpr const int kTextureSize = 256;
static void renderClassicQuad(GLuint texture, float x, float y, bool flipV = false) {
const float tLow = flipV ? 1.0f : 0.0f;
const float tHigh = flipV ? 0.0f : 1.0f;
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0.0f, tLow); glVertex2f(x, y);
glTexCoord2f(1.0f, tLow); glVertex2f(x + kTextureSize, y);
glTexCoord2f(0.0f, tHigh); glVertex2f(x, y + kTextureSize);
glTexCoord2f(1.0f, tHigh); glVertex2f(x + kTextureSize, y + kTextureSize);
glEnd();
}
static void renderClassicGridQuad(GLuint texture, int row, int column) {
renderClassicQuad(
texture,
kScreenWidth / 4 * (row * 2 + 1) - kTextureSize / 2,
kScreenHeight / 4 * (column * 2 + 1) - kTextureSize / 2,
true); // by rendering and reading back we have flipped the image
}
TestExitStatus TinyGLTests::runTexEnvTest(
const char *testName,
const TextureEnvironment &env,
byte colorR, byte colorG, byte colorB, byte colorA) {
int oldW = g_system->getWidth();
int oldH = g_system->getHeight();
auto oldFormat = g_system->getScreenFormat();
int oldGraphicsMode = g_system->getGraphicsMode();
// load test image (crop and scale to square power-of-two)
Graphics::ManagedSurface testImage;
{
constexpr const char *kImagePath = "image/pm5544-32bpp-grayalpha.png";
Image::PNGDecoder pngDecoder;
Common::File file;
if (!file.open(kImagePath) ||
!pngDecoder.loadStream(file)) {
Testsuite::logDetailedPrintf("Error! Could not load test image: %s\n", kImagePath);
return kTestFailed;
}
const auto *pngSurface = pngDecoder.getSurface();
if (pngSurface->w < 240 || pngSurface->h < 240) {
Testsuite::logDetailedPrintf("Error! Test image has unexpected size: %dx%d\n", pngSurface->w, pngSurface->h);
return kTestFailed;
}
int16 pngSize = MIN(pngSurface->w, pngSurface->h);
auto subRect = Common::Rect::center(pngSurface->w / 2, pngSurface->h / 2, pngSize, pngSize);
Graphics::ManagedSurface converted;
converted.convertFrom(*pngSurface, Graphics::PixelFormat::createFormatRGBA32());
testImage.create(kTextureSize, kTextureSize, Graphics::PixelFormat::createFormatRGBA32());
Graphics::scaleBlitBilinear(
(byte *)testImage.getBasePtr(0, 0),
(const byte *)converted.getBasePtr(subRect.left, subRect.top),
testImage.pitch,
converted.pitch,
testImage.w, testImage.h,
converted.w, converted.h,
converted.format);
}
Graphics::ManagedSurface readBack(testImage.w, testImage.h, testImage.format);
// Initialize classic opengl
initGraphics3d(kScreenWidth, kScreenHeight);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, kScreenWidth, 0, kScreenHeight, -1, 1);
glViewport(0, 0, kScreenWidth, kScreenHeight);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
glClearColor(0, 0, 0, 0);
GLuint classicTextures[5]; // test texture + 2*classic result textures + 2*TinyGL result textures
glGenTextures(5, classicTextures);
GLuint classicTestTexture = classicTextures[0];
glBindTexture(GL_TEXTURE_2D, classicTestTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, testImage.getPixels());
// Initialize TinyGL
Common::ScopedPtr<TinyGL::ContextHandle, TinyGLContextDeleter> tglContext(
TinyGL::createContext(kTextureSize, kTextureSize, testImage.format, kTextureSize, false, false));
TinyGL::setContext(tglContext.get());
Graphics::Surface tinyglSurface;
TinyGL::getSurfaceRef(tinyglSurface);
tglMatrixMode(TGL_MODELVIEW);
tglLoadIdentity();
tglMatrixMode(TGL_PROJECTION);
tglLoadIdentity();
tglOrtho(0, kScreenWidth, 0, kScreenHeight, -1, 1);
tglViewport(0, 0, kScreenWidth, kScreenHeight);
tglDisable(TGL_BLEND);
tglDisable(TGL_DEPTH_TEST);
tglDisable(TGL_CULL_FACE);
tglEnable(TGL_TEXTURE_2D);
tglClearColor(0, 0, 0, 0);
TGLuint tinyglTestTexture;
tglGenTextures(1, &tinyglTestTexture);
tglBindTexture(TGL_TEXTURE_2D, classicTestTexture);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MIN_FILTER, TGL_NEAREST);
tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MAG_FILTER, TGL_NEAREST);
tglTexImage2D(TGL_TEXTURE_2D, 0, TGL_RGBA, kTextureSize, kTextureSize, 0, TGL_RGBA, TGL_UNSIGNED_BYTE, testImage.getPixels());
// Render classic OpenGL
env.apply(classicGLTexEnvi, classicGLTexEnvfv);
glClear(GL_COLOR_BUFFER_BIT);
glColor4ub(colorR, colorG, colorB, colorA);
renderClassicQuad(classicTestTexture, 0, 0);
glFlush();
g_system->presentBuffer();
readBack.clear();
glReadPixels(0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE, readBack.getPixels());
GLuint classicReadBackColorTex = classicTextures[1];
glBindTexture(GL_TEXTURE_2D, classicReadBackColorTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, readBack.getPixels());
copyAlphaIntoColorChannels(readBack);
GLuint classicReadBackAlphaTex = classicTextures[2];
glBindTexture(GL_TEXTURE_2D, classicReadBackAlphaTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, readBack.getPixels());
// Render TinyGL
env.apply(tglTexEnvi, tglTexEnvfv);
tglClear(TGL_COLOR_BUFFER_BIT);
tglColor4ub(colorR, colorG, colorB, colorA);
tglBegin(TGL_TRIANGLE_STRIP);
tglTexCoord2f(0.0f, 1.0f); tglVertex2f(0.0f, 0.0f);
tglTexCoord2f(1.0f, 1.0f); tglVertex2f(kTextureSize, 0.0f);
tglTexCoord2f(0.0f, 0.0f); tglVertex2f(0.0f, kTextureSize);
tglTexCoord2f(1.0f, 0.0f); tglVertex2f(kTextureSize, kTextureSize);
tglEnd();
TinyGL::presentBuffer();
readBack.copyFrom(tinyglSurface);
GLuint tinyglReadBackColorTex = classicTextures[3];
glBindTexture(GL_TEXTURE_2D, tinyglReadBackColorTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, readBack.getPixels());
copyAlphaIntoColorChannels(readBack);
GLuint tinyglReadBackAlphaTex = classicTextures[4];
glBindTexture(GL_TEXTURE_2D, tinyglReadBackAlphaTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, readBack.getPixels());
// Render comparison
glClear(GL_COLOR_BUFFER_BIT);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, 1);
renderClassicGridQuad(classicReadBackColorTex, 0, 0);
renderClassicGridQuad(classicReadBackAlphaTex, 1, 0);
renderClassicGridQuad(tinyglReadBackColorTex, 0, 1);
renderClassicGridQuad(tinyglReadBackAlphaTex, 1, 1);
glFlush();
g_system->updateScreen();
glDeleteTextures(5, classicTextures);
g_system->delayMillis(1000);
TestExitStatus status = kTestPassed;
Common::String info = Common::String::format("Does the top row of images look like the bottom row?\n(Testing %s)", testName);
if (Testsuite::handleInteractiveInput(info, "Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! TinyGL and OpenGL have different texure environment behaviors for %s\n", testName);
status = kTestFailed;
}
// Return to previous state
g_system->beginGFXTransaction();
g_system->setGraphicsMode(oldGraphicsMode);
g_system->initSize(oldW, oldH, &oldFormat);
g_system->endGFXTransaction();
return status;
}
}

53
engines/testbed/tinygl.h Normal file
View File

@@ -0,0 +1,53 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_TINYGL_H
#define TESTBED_TINYGL_H
#include "testbed/testsuite.h"
namespace Testbed {
class TinyGLTestSuite : 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()
*/
TinyGLTestSuite();
~TinyGLTestSuite() override = default;
const char *getName() const override {
return "TinyGL";
}
const char *getDescription() const override {
return "Comparing TinyGL output with classic OpenGL";
}
};
} // End of namespace Testbed
#endif // TESTBED_TEMPLATE_H

260
engines/testbed/video.cpp Normal file
View File

@@ -0,0 +1,260 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/events.h"
#include "common/file.h"
#include "engines/util.h"
#include "video/avi_decoder.h"
#include "video/dxa_decoder.h"
#include "video/flic_decoder.h"
#include "video/mve_decoder.h"
#include "video/qt_decoder.h"
#include "video/qt_data.h"
#include "video/smk_decoder.h"
#include "testbed/testbed.h"
#include "testbed/video.h"
#include "graphics/cursorman.h"
#include "graphics/paletteman.h"
#include "gui/browser.h"
#include "video/qt_data.h"
namespace Testbed {
Common::Error Videotests::videoTest(const Common::Path &path) {
Common::File *file = new Common::File();
if (!file->open(path)) {
warning("Cannot open file %s", path.toString(Common::Path::kNativeSeparator).c_str());
delete file;
return Common::kNoGameDataFoundError;
}
return videoTest(file, path.toString(Common::Path::kNativeSeparator));
}
Common::Error Videotests::videoTest(const Common::FSNode &node) {
Common::SeekableReadStream *stream = node.createReadStream();
if (!stream) {
warning("Cannot open file %s", node.getName().c_str());
return Common::kNoGameDataFoundError;
}
return videoTest(stream, node.getName());
}
Common::Error Videotests::videoTest(Common::SeekableReadStream *stream, const Common::String &name) {
Video::QuickTimeDecoder *qtVideo = nullptr;
Video::VideoDecoder *video = nullptr;
if (name.hasSuffixIgnoreCase(".avi")) {
video = new Video::AVIDecoder();
} else if (name.hasSuffixIgnoreCase(".dxa")) {
video = new Video::DXADecoder();
} else if (name.hasSuffixIgnoreCase(".flc")) {
video = new Video::FlicDecoder();
} else if (name.hasSuffixIgnoreCase(".mve")) {
video = new Video::MveDecoder();
} else if (name.hasSuffixIgnoreCase(".smk")) {
video = new Video::SmackerDecoder();
} else {
qtVideo = new Video::QuickTimeDecoder();
video = qtVideo;
}
if (!video->loadStream(stream)) {
warning("Cannot open video %s", name.c_str());
delete video;
return Common::kReadingFailed;
}
if (qtVideo)
qtVideo->setTargetSize(400, 300);
warning("Video size: %d x %d", video->getWidth(), video->getHeight());
Common::List<Graphics::PixelFormat> supportedFormatsList = g_system->getSupportedFormats();
Graphics::PixelFormat pixelformat = supportedFormatsList.front();
warning("Best pixel format: %s", pixelformat.toString().c_str());
warning("Video pixel format: %s", video->getPixelFormat().toString().c_str());
if (video->getPixelFormat().isCLUT8()) {
pixelformat = Graphics::PixelFormat::createFormatCLUT8();
} else {
if (pixelformat.isCLUT8() && video->setDitheringPalette(Video::quickTimeDefaultPalette256)) {
pixelformat = Graphics::PixelFormat::createFormatCLUT8();
} else if (video->setOutputPixelFormats(supportedFormatsList)) {
pixelformat = video->getPixelFormat();
} else {
pixelformat = supportedFormatsList.front();
}
}
warning("Actual pixel format: %s", pixelformat.toString().c_str());
#ifdef __DS__
int w = 256, h = 192;
#elif defined(__3DS__)
int w = 320, h = 240;
#elif defined(USE_HIGHRES)
int w = 640, h = 480;
#else
int w = 320, h = 200;
#endif
if (w < video->getWidth() || h < video->getHeight()) {
w = video->getWidth();
h = video->getHeight();
}
initGraphics(w, h, &pixelformat);
video->start();
Common::Point mouse;
while (!video->endOfVideo()) {
if (video->needsUpdate()) {
uint32 pos = video->getTime();
debug(5, "video time: %d", pos);
if (pixelformat.isCLUT8() && video->hasDirtyPalette()) {
g_system->getPaletteManager()->setPalette(video->getPalette(), 0, 256);
}
const Graphics::Surface *frame = video->decodeNextFrame();
int x = 0, y = 0;
int mw = 0, mh = 0;
if (frame) {
const Graphics::Surface *surf = frame;
Graphics::Surface *conv = nullptr;
if (frame->format != pixelformat) {
surf = conv = frame->convertTo(pixelformat, Video::quickTimeDefaultPalette256);
}
mw = surf->w;
mh = surf->h;
if (surf->w < w)
x = (w - surf->w) >> 1;
if (surf->h < h)
y = (h - surf->h) >> 1;
g_system->copyRectToScreen(surf->getPixels(), surf->pitch, x, y, MIN<uint16>(surf->w, w), MIN<uint16>(surf->h, h));
if (conv) {
conv->free();
delete conv;
}
}
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
if (Common::isMouseEvent(event))
mouse = event.mouse;
if (qtVideo && mouse.x >= x && mouse.x < x + mw &&
mouse.y >= y && mouse.y < y + mh) {
switch (event.type) {
case Common::EVENT_LBUTTONDOWN:
qtVideo->handleMouseButton(true, event.mouse.x - x, event.mouse.y - y);
break;
case Common::EVENT_LBUTTONUP:
qtVideo->handleMouseButton(false, event.mouse.x - x, event.mouse.y - y);
break;
case Common::EVENT_MOUSEMOVE:
qtVideo->handleMouseMove(event.mouse.x - x, event.mouse.y - y);
break;
case Common::EVENT_KEYUP:
case Common::EVENT_KEYDOWN:
qtVideo->handleKey(event.kbd, event.type == Common::EVENT_KEYDOWN);
break;
default:
break;
}
} else {
CursorMan.showMouse(false);
}
if (Engine::shouldQuit()) {
video->close();
delete video;
return Common::kNoError;
}
}
g_system->updateScreen();
video->delayMillis(10);
}
}
video->close();
delete video;
return Common::kNoError;
}
TestExitStatus Videotests::testPlayback() {
Testsuite::clearScreen();
Common::String info = "Video playback test. A video should be selected using the file browser, and it'll be played on the screen.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing video playback", pt);
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : testPlayback\n");
return kTestSkipped;
}
GUI::BrowserDialog browser(Common::U32String("Select video file"), false);
if (browser.runModal() <= 0) {
Testsuite::logPrintf("Info! Skipping test : testPlayback\n");
return kTestSkipped;
}
byte palette[256 * 3];
g_system->getPaletteManager()->grabPalette(palette, 0, 256);
Common::Error error = videoTest(browser.getResult());
initGraphics(320, 200);
g_system->getPaletteManager()->setPalette(palette, 0, 256);
if (error.getCode() != Common::kNoError) {
Testsuite::logDetailedPrintf("Video playback failed: %s\n", error.getDesc().c_str());
return kTestFailed;
}
Common::String prompt = "Did the video display correctly?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("Video playback failed\n");
return kTestFailed;
}
return kTestPassed;
}
VideoDecoderTestSuite::VideoDecoderTestSuite() {
_isTsEnabled = false;
addTest("testPlayback", &Videotests::testPlayback, true);
}
} // End of namespace Testbed

69
engines/testbed/video.h Normal file
View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_VIDEO_H
#define TESTBED_VIDEO_H
#include "testbed/testsuite.h"
namespace Testbed {
namespace Videotests {
// Helper functions for Video tests
// will contain function declarations for Video tests
// add more here
TestExitStatus testPlayback();
Common::Error videoTest(const Common::Path &path);
Common::Error videoTest(const Common::FSNode &node);
Common::Error videoTest(Common::SeekableReadStream *stream, const Common::String &name);
} // End of namespace Videotests
class VideoDecoderTestSuite : 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()
*/
VideoDecoderTestSuite();
~VideoDecoderTestSuite() override {}
const char *getName() const override {
return "Video";
}
const char *getDescription() const override {
return "Video Decoder Subsystem";
}
};
} // End of namespace Testbed
#endif // TESTBED_VIDEO_H

View File

@@ -0,0 +1,235 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "testbed/webserver.h"
#include "backends/networking/sdl_net/localwebserver.h"
#include "common/config-manager.h"
namespace Testbed {
WebserverTestSuite::WebserverTestSuite() {
addTest("ResolveIP", &WebserverTests::testIP, true);
addTest("IndexPage", &WebserverTests::testIndexPage, true);
addTest("FilesPage", &WebserverTests::testFilesPageInvalidParameterValue, true);
addTest("CreateDirectory", &WebserverTests::testFilesPageCreateDirectory, true);
addTest("UploadFile", &WebserverTests::testFilesPageUploadFile, true);
addTest("UploadDirectory", &WebserverTests::testFilesPageUploadDirectory, true);
addTest("DownloadFile", &WebserverTests::testFilesPageDownloadFile, true);
}
///// TESTS GO HERE /////
/** This test calls Storage::info(). */
bool WebserverTests::startServer() {
Common::Point pt;
pt.x = 10; pt.y = 10;
Testsuite::writeOnScreen("Starting webserver...", pt);
LocalServer.start();
g_system->delayMillis(500);
Testsuite::clearScreen();
return LocalServer.isRunning();
}
TestExitStatus WebserverTests::testIP() {
if (!startServer()) {
Testsuite::logPrintf("Error! Can't start local webserver!\n");
return kTestFailed;
}
Common::String info = "Welcome to the Webserver test suite!\n"
"You would be visiting different server's pages and saying whether they work like they should.\n\n"
"Testing Webserver's IP resolving.\n"
"In this test we'll try to resolve server's IP.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : IP resolving\n");
return kTestSkipped;
}
if (Testsuite::handleInteractiveInput(
Common::String::format("Is this your machine's IP?\n%s", LocalServer.getAddress().c_str()),
"Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! IP was not resolved!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("IP was resolved\n");
return kTestPassed;
}
TestExitStatus WebserverTests::testIndexPage() {
if (!startServer()) {
Testsuite::logPrintf("Error! Can't start local webserver!\n");
return kTestFailed;
}
Common::String info = "Testing Webserver's index page.\n"
"In this test we'll try to open server's index page.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : index page\n");
return kTestSkipped;
}
g_system->openUrl(LocalServer.getAddress());
if (Testsuite::handleInteractiveInput(
Common::String::format("The %s page opens well?", LocalServer.getAddress().c_str()),
"Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Couldn't open server's index page!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Server's index page is OK\n");
return kTestPassed;
}
TestExitStatus WebserverTests::testFilesPageInvalidParameterValue() {
if (!startServer()) {
Testsuite::logPrintf("Error! Can't start local webserver!\n");
return kTestFailed;
}
Common::String info = "Testing Webserver's files page.\n"
"In this test we'll try to pass invalid parameter to files page.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : files page (invalid parameter)\n");
return kTestSkipped;
}
g_system->openUrl(LocalServer.getAddress()+"files?path=error");
if (Testsuite::handleInteractiveInput(
Common::String::format("The %sfiles?path=error page displays error message?", LocalServer.getAddress().c_str()),
"Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! No error message on invalid parameter in '/files'!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Server's files page detects invalid paramters fine\n");
return kTestPassed;
}
TestExitStatus WebserverTests::testFilesPageCreateDirectory() {
if (!startServer()) {
Testsuite::logPrintf("Error! Can't start local webserver!\n");
return kTestFailed;
}
Common::String info = "Testing Webserver's files page Create Directory feature.\n"
"In this test you'll try to create directory.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : files page create directory\n");
return kTestSkipped;
}
g_system->openUrl(LocalServer.getAddress() + "files?path=/root/");
if (Testsuite::handleInteractiveInput(
Common::String::format("You could go to %sfiles page, navigate to some directory with write access and create a directory there?", LocalServer.getAddress().c_str()),
"Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Create Directory is not working!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Create Directory is OK\n");
return kTestPassed;
}
TestExitStatus WebserverTests::testFilesPageUploadFile() {
if (!startServer()) {
Testsuite::logPrintf("Error! Can't start local webserver!\n");
return kTestFailed;
}
Common::String info = "Testing Webserver's files page Upload Files feature.\n"
"In this test you'll try to upload a file.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : files page file upload\n");
return kTestSkipped;
}
g_system->openUrl(LocalServer.getAddress() + "files?path=/root/");
if (Testsuite::handleInteractiveInput(
Common::String::format("You're able to upload a file in some directory with write access through %sfiles page?", LocalServer.getAddress().c_str()),
"Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Upload Files is not working!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Upload Files is OK\n");
return kTestPassed;
}
TestExitStatus WebserverTests::testFilesPageUploadDirectory() {
if (!startServer()) {
Testsuite::logPrintf("Error! Can't start local webserver!\n");
return kTestFailed;
}
Common::String info = "Testing Webserver's files page Upload Directory feature.\n"
"In this test you'll try to upload a directory (works in Chrome only).";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : files page directory upload\n");
return kTestSkipped;
}
g_system->openUrl(LocalServer.getAddress() + "files?path=/root/");
if (Testsuite::handleInteractiveInput(
Common::String::format("You're able to upload a directory into some directory with write access through %sfiles page using Chrome?", LocalServer.getAddress().c_str()),
"Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Upload Directory is not working!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Upload Directory is OK\n");
return kTestPassed;
}
TestExitStatus WebserverTests::testFilesPageDownloadFile() {
if (!startServer()) {
Testsuite::logPrintf("Error! Can't start local webserver!\n");
return kTestFailed;
}
Common::String info = "Testing Webserver's files downloading feature.\n"
"In this test you'll try to download a file.";
if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
Testsuite::logPrintf("Info! Skipping test : files page download\n");
return kTestSkipped;
}
g_system->openUrl(LocalServer.getAddress() + "files?path=/root/");
if (Testsuite::handleInteractiveInput(
Common::String::format("You're able to download a file through %sfiles page?", LocalServer.getAddress().c_str()),
"Yes", "No", kOptionRight)) {
Testsuite::logDetailedPrintf("Error! Files downloading is not working!\n");
return kTestFailed;
}
Testsuite::logDetailedPrintf("Files downloading is OK\n");
return kTestPassed;
}
} // End of namespace Testbed

View File

@@ -0,0 +1,68 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TESTBED_WEBSERVER_H
#define TESTBED_WEBSERVER_H
#include "testbed/testsuite.h"
namespace Testbed {
namespace WebserverTests {
// Helper functions for Webserver tests
bool startServer();
TestExitStatus testIP();
TestExitStatus testIndexPage();
TestExitStatus testFilesPageInvalidParameterValue();
TestExitStatus testFilesPageCreateDirectory();
TestExitStatus testFilesPageUploadFile();
TestExitStatus testFilesPageUploadDirectory();
TestExitStatus testFilesPageDownloadFile();
} // End of namespace WebserverTests
class WebserverTestSuite : public Testsuite {
public:
/**
* The constructor for the WebserverTestSuite
* 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()
*/
WebserverTestSuite();
~WebserverTestSuite() override {}
const char *getName() const override {
return "Webserver";
}
const char *getDescription() const override {
return "Webserver tests";
}
};
} // End of namespace Testbed
#endif // TESTBED_TEMPLATE_H