From 5a6ff377598f2a8fd86da188bbbc409086786c8a Mon Sep 17 00:00:00 2001 From: GPayne Date: Fri, 3 Apr 2020 21:43:24 -0600 Subject: [PATCH] Created unit tests for profileFile --- include/openspace/scene/profile.h | 2 - .../scene/{profileFile.h => profilefile.h} | 95 ++-- src/CMakeLists.txt | 5 + src/scene/profile.cpp | 13 - src/scene/profileFile.cpp | 308 ------------ src/scene/profilefile.cpp | 450 ++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/profile/test_profilefile.cpp | 268 +++++++++++ 8 files changed, 772 insertions(+), 370 deletions(-) rename include/openspace/scene/{profileFile.h => profilefile.h} (64%) delete mode 100644 src/scene/profileFile.cpp create mode 100644 src/scene/profilefile.cpp create mode 100644 tests/profile/test_profilefile.cpp diff --git a/include/openspace/scene/profile.h b/include/openspace/scene/profile.h index c90b28556d..d839a6ca97 100644 --- a/include/openspace/scene/profile.h +++ b/include/openspace/scene/profile.h @@ -47,8 +47,6 @@ namespace scripting { struct LuaLibrary; } class Profile { public: - Profile(); - void saveCurrentSettingsToProfile(std::string filename); /** diff --git a/include/openspace/scene/profileFile.h b/include/openspace/scene/profilefile.h similarity index 64% rename from include/openspace/scene/profileFile.h rename to include/openspace/scene/profilefile.h index 132dd0ee96..065b36f4fe 100644 --- a/include/openspace/scene/profileFile.h +++ b/include/openspace/scene/profilefile.h @@ -25,15 +25,10 @@ #ifndef __OPENSPACE_CORE___PROFILEFILE___H__ #define __OPENSPACE_CORE___PROFILEFILE___H__ -#include - -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include namespace ghoul { class Dictionary; } @@ -44,18 +39,15 @@ namespace openspace { namespace documentation { struct Documentation; } namespace scripting { struct LuaLibrary; } - class ProfileFile { public: - ProfileFile(std::string filename); - ~ProfileFile(); + void readLines(std::function reader); + void readFromFile(std::string filename); + void processIndividualLine(bool& insideSection, std::string line); + void write(std::ostream& output); + void writeToFile(std::string filename); - //Need a copy constructor here to do a deep copy - - void read(); - void write(); - - void setFilename(std::string filename); + const std::string getVersion() const; //Methods for updating contents void updateTime(); @@ -67,21 +59,24 @@ public: void addMarkNodesLine(std::string line); //Methods for getting contents of each section - std::string time(); - std::string camera(); - std::vector modules(); - std::vector assets(); - std::vector properties(); - std::vector keybindings(); - std::vector markNodes(); + std::string time() const; + std::string camera() const; + std::vector modules() const; + std::vector assets() const; + std::vector properties() const; + std::vector keybindings() const; + std::vector markNodes() const; private: - void logError(std::string message); + std::string errorString(std::string message); void clearAllFields(); bool isBlank(std::string line); - int splitByTab(std::string line, std::vector& result); + size_t splitByTab(std::string line, std::vector& result); + void verifyRequiredFields(std::string sectionName, std::vector fields, + std::vector standard, unsigned int nFields); + bool determineSection(std::string line); - void parseCurrentSection(std::string line); + void (ProfileFile::* parseCurrentSection)(std::string); void parseVersion(std::string line); void parseModule(std::string line); void parseAsset(std::string line); @@ -90,28 +85,34 @@ private: void parseTime(std::string line); void parseCamera(std::string line); void parseMarkNodes(std::string line); - void verifyRequiredFields(std::string sectionName, std::vector fields, - std::vector standard, unsigned int nFields); + void addAllElements(std::ostream& file, std::vector& list); - const int versionLinesExpected = 1; - const int timeLinesExpected = 1; - const int cameraLinesExpected = 1; + const size_t _versionLinesExpected = 1; + const size_t _timeLinesExpected = 1; + const size_t _cameraLinesExpected = 1; + const size_t _versionFieldsExpected = 1; + const size_t _moduleFieldsExpected = 3; + const size_t _assetFieldsExpected = 2; + const size_t _propertyFieldsExpected = 3; + const size_t _keybindingFieldsExpected = 6; + const size_t _timeFieldsExpected = 2; + const size_t _cameraNavigationFieldsExpected = 8; + const size_t _cameraGeoFieldsExpected = 5; + const size_t _markNodesFieldsExpected = 1; - const int versionFieldsExpected = 1; - const int moduleFieldsExpected = 3; - const int assetFieldsExpected = 2; - const int propertyFieldsExpected = 3; - const int keybindingFieldsExpected = 6; - const int timeFieldsExpected = 2; - const int cameraNavigationFieldsExpected = 8; - const int cameraGeoFieldsExpected = 5; - const int markNodesFieldsExpected = 1; + const std::string header_Version = "#Version"; + const std::string header_Module = "#Module"; + const std::string header_Asset = "#Asset"; + const std::string header_Property = "#Property"; + const std::string header_Keybinding = "#Keybinding"; + const std::string header_Time = "#Time"; + const std::string header_Camera = "#Camera"; + const std::string header_MarkNodes = "#MarkNodes"; - std::string _filename; - unsigned int _lineNum = 1; - unsigned int _numLinesVersion = 0; - unsigned int _numLinesTime = 0; - unsigned int _numLinesCamera = 0; + size_t _lineNum = 1; + size_t _numLinesVersion = 0; + size_t _numLinesTime = 0; + size_t _numLinesCamera = 0; std::string _version; std::string _time; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dde2ceb7e2..edaa11a0e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -152,6 +152,9 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/scene/assetmanager.cpp ${OPENSPACE_BASE_DIR}/src/scene/assetmanager_lua.inl ${OPENSPACE_BASE_DIR}/src/scene/lightsource.cpp + ${OPENSPACE_BASE_DIR}/src/scene/profile.cpp + ${OPENSPACE_BASE_DIR}/src/scene/profilefile.cpp + ${OPENSPACE_BASE_DIR}/src/scene/profile_lua.inl ${OPENSPACE_BASE_DIR}/src/scene/rotation.cpp ${OPENSPACE_BASE_DIR}/src/scene/scale.cpp ${OPENSPACE_BASE_DIR}/src/scene/scene.cpp @@ -342,6 +345,8 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/scene/assetloader.h ${OPENSPACE_BASE_DIR}/include/openspace/scene/assetmanager.h ${OPENSPACE_BASE_DIR}/include/openspace/scene/lightsource.h + ${OPENSPACE_BASE_DIR}/include/openspace/scene/profile.h + ${OPENSPACE_BASE_DIR}/include/openspace/scene/profilefile.h ${OPENSPACE_BASE_DIR}/include/openspace/scene/rotation.h ${OPENSPACE_BASE_DIR}/include/openspace/scene/scale.h ${OPENSPACE_BASE_DIR}/include/openspace/scene/scene.h diff --git a/src/scene/profile.cpp b/src/scene/profile.cpp index b4b0560d5b..bf9f8217ae 100644 --- a/src/scene/profile.cpp +++ b/src/scene/profile.cpp @@ -53,19 +53,6 @@ namespace { namespace openspace { -Profile::Profile() - : properties::PropertyOwner({"Scene", "Scene"}) - , _initializer(std::move(initializer)) -{ - _rootDummy.setIdentifier(SceneGraphNode::RootNodeIdentifier); - _rootDummy.setScene(this); -} - -Profile::~Profile() { - clear(); - _rootDummy.setScene(nullptr); -} - void Profile::saveCurrentSettingsToProfile(std::string filename) { if (! global::configuration.usingProfile) { std::string errorMessage = "Program was not started using a profile, " diff --git a/src/scene/profileFile.cpp b/src/scene/profileFile.cpp deleted file mode 100644 index d029f5ac1b..0000000000 --- a/src/scene/profileFile.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this * - * software and associated documentation files (the "Software"), to deal in the Software * - * without restriction, including without limitation the rights to use, copy, modify, * - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * - * permit persons to whom the Software is furnished to do so, subject to the following * - * conditions: * - * * - * The above copyright notice and this permission notice shall be included in all copies * - * or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * - * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - ****************************************************************************************/ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - constexpr const char* _loggerCat = "ProfileFile"; - constexpr const char* KeyIdentifier = "Identifier"; - constexpr const char* KeyParent = "Parent"; - -} // namespace - -namespace openspace { - -ProfileFile::ProfileFile(std::string filename) - : _filename(filename) -{ -} - -ProfileFile::~ProfileFile() { -} - -void ProfileFile::read() { - clearAllFields(); - std::ifstream inFile; - inFile.open(_filename, std::ifstream::in); - std::string line; - _lineNum = 1; - bool insideSection = false; - - while (getline(inFile, line)) { - if (insideSection) { - if (isBlank(line)) { - insideSection = false; - } - else { - parseCurrentSection(line); - } - } - else if (line.substr(0, 1) == "#") { - if (determineSection(line)) { - insideSection = true; - } - } - _lineNum++; - } - inFile.close(); -} - -void ProfileFile::clearAllFields() { - _numLinesVersion = 0; - _numLinesTime = 0; - _numLinesCamera = 0; - - _version.clear(); - _time.clear(); - _camera.clear(); - _modules.clear(); - _assets.clear(); - _properties.clear(); - _keybindings.clear(); - _markNodes.clear(); -} - -bool ProfileFile::isBlank(std::string line) { - char* c = line.c_str(); - int nonBlanks = 0; - while (*c) { - if (!isspace(*c)) { - nonBlanks++; - } - c++; - } - return (nonBlanks == 0); -} - -bool ProfileFile::determineSection(std::string line) { - -} - -void ProfileFile::parseCurrentSection(std::string line) { - -} - -void ProfileFile::logError(std::string message) { - std::string e = "Error @ line " + std::to_string(_lineNum) + ": "; - LERROR(e + message); -} - -void ProfileFile::parseVersion(std::string line) { - std::vector fields; - - if (++_numLinesVersion > versionLinesExpected) { - logError("Too many lines in Version section"); - } - if (splitByTab(line, fields) > versionFieldsExpected) { - logError("No tabs allowed in Version entry"); - } - else { - _version = line; - } -} - -void ProfileFile::parseModule(std::string line) { - std::vector fields; - - if (splitByTab(line, fields) != moduleFieldsExpected) { - logError(std::to_string(moduleFieldsExpected) + - " fields required in a Module entry"); - } - std::vector standard = { - "module name", - "", - "" - }; - verifyRequiredFields(fields, standard, moduleFieldsExpected); - _modules.push_back(line); -} - -void ProfileFile::parseAsset(std::string line) { - std::vector fields; - - if (splitByTab(line, fields) != assetFieldsExpected) { - logError(std::to_string(assetFieldsExpected) + - " fields required in an Asset entry"); - } - std::vector standard = { - "asset name", - "" - }; - verifyRequiredFields(fields, standard, assetFieldsExpected); - _assets.push_back(line); -} - -void ProfileFile::parseProperty(std::string line) { - std::vector fields; - - if (splitByTab(line, fields) != propertyFieldsExpected) { - logError(std::to_string(propertyFieldsExpected) + - " fields required in Property entry"); - } - std::vector standard = { - "set command", - "name", - "value" - }; - verifyRequiredFields("Property", fields, standard, propertyFieldsExpected); - _properties.push_back(line); -} - -void ProfileFile::parseKeybinding(std::string line) { - std::vector fields; - - if (splitByTab(line, fields) != keybindingFieldsExpected) { - logError(std::to_string(keybindingFieldsExpected) + - " fields required in Keybinding entry"); - } - std::vector standard = { - "key", - "documentation", - "name", - "GuiPath", - "local(T/F)", - "script to execute" - }; - verifyRequiredFields("Keybinding", fields, standard, propertyFieldsExpected); - _properties.push_back(line); -} - -void ProfileFile::parseTime(std::string line) { - std::vector fields; - - if (++_numLinesTime > timeLinesExpected) { - logError("Too many lines in time section"); - } - if (splitByTab(line, fields) != timeFieldsExpected) { - logError(std::to_string(timeFieldsExpected) + - " fields required in Time entry"); - } - std::vector standard = { - "time set type", - "time value to set" - }; - verifyRequiredFields("Time", fields, standard, propertyFieldsExpected); - _time = line; -} - -void ProfileFile::parseCamera(std::string line) { - std::vector fields; - - if (++_numLinesCamera > cameraLinesExpected) { - logError("Too many lines in camera section"); - } - int nFields = splitByTab(line, fields); - if (nFields == cameraNavigationFieldsExpected) { - std::vector standard = { - "Type of camera set (setNavigationState)", - "setNavigationState Anchor", - "", - "", - "setNavigationState position vector", - "", - "", - "" - }; - verifyRequiredFields("Camera", fields, standard, cameraNavigationFieldsExpected); - } - else if (nFields == cameraGeoFieldsExpected) { - std::vector standard = { - "Type of camera set (goToGeo)", - "", - "Camera goToGeo Latitude", - "Camera goToGeo Longitude", - "" - }; - verifyRequiredFields("Camera goToGeo", fields, standard, - cameraNavigationFieldsExpected); - } - else { - logError(std::to_string(cameraNavigationFieldsExpected) + " or " + - std::to_string(cameraGeoFieldsExpected) + " fields required in Camera entry"); - } - _camera = line; -} - -void ProfileFile::parseMarkNodes(std::string line) { - std::vector fields; - - if (splitByTab(line, fields) != markNodesFieldsExpected) { - logError(std::to_string(markNodesFieldsExpected) + - " field required in an Mark Nodes entry"); - } - std::vector standard = { - "Mark Interesting Node name" - }; - verifyRequiredFields("Mark Interesting Nodes", fields, standard, - markNodesFieldsExpected); - _assets.push_back(line); -} - -void ProfileFile::verifyRequiredFields(std::string sectionName, - std::vector fields, - std::vector standard, - unsigned int nFields) -{ - for (unsigned int i = 0; i < fields.size(); i++) { - if (!standard[i].empty() && fields[i].empty()) { - std::string errMsg = sectionName + " " + standard[i]; - errMsg += "(arg " + std::to_string(i) + "/" + nFields + ") is required"; - logError(errMsg); - } - } -} - -int ProfileFile::splitByTab(std::string line, std::vector& result) { - std::istringstream iss(line); - std::string tmp; - result.clear(); - while(std::getline(iss, tmp, '\t')) { - result.push_back(tmp); - } - return result.size(); -} - -} // namespace openspace diff --git a/src/scene/profilefile.cpp b/src/scene/profilefile.cpp new file mode 100644 index 0000000000..c9d3c16964 --- /dev/null +++ b/src/scene/profilefile.cpp @@ -0,0 +1,450 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "ProfileFile"; + constexpr const char* KeyIdentifier = "Identifier"; + constexpr const char* KeyParent = "Parent"; + +} // namespace + +namespace openspace { + +void ProfileFile::readFromFile(std::string filename) { + clearAllFields(); + std::ifstream inFile; + + try { + inFile.open(filename, std::_Ios_Openmode::_S_in); + } + catch (std::ifstream::failure& e) { + throw ghoul::RuntimeError("Exception opening profile file for read: " + + filename, "profileFile"); + } + + try { + readLines([&inFile] (std::string& line) { + if (getline(inFile, line)) + return true; + else + return false; + }); + } + catch (std::ifstream::failure& e) { + throw ghoul::RuntimeError(errorString("Read error using getline"), + "profileFile"); + } + + try { + inFile.close(); + } + catch (std::ifstream::failure& e) { + throw ghoul::RuntimeError("Exception closing profile file after read: " + + filename, "profileFile"); + } +} + +void ProfileFile::readLines(std::function reader) { + std::string line; + bool insideSection = false; + _lineNum = 1; + + while (reader(line)) { + processIndividualLine(insideSection, line); + _lineNum++; + } +} + +void ProfileFile::processIndividualLine(bool& insideSection, std::string line) { + if (insideSection) { + if (isBlank(line)) { + insideSection = false; + } + else { + if (parseCurrentSection != nullptr) { + (this->*parseCurrentSection)(line); + } + } + } + else if (line.substr(0, 1) == "#") { + if (determineSection(line)) { + insideSection = true; + } + } +} + +void ProfileFile::writeToFile(std::string filename) { + std::ofstream outFile; + try { + outFile.open(filename, std::ofstream::out | std::ofstream::app); + } + catch (std::ofstream::failure& e) { + LERROR("Exception opening profile file for write: " + filename); + } + + try { + write(outFile); + } + catch (std::ofstream::failure& e) { + LERROR("Data write error to file: " + filename); + } + + try { + outFile.close(); + } + catch (std::ofstream::failure& e) { + LERROR("Exception closing profile file after write: " + filename); + } +} + +void ProfileFile::write(std::ostream& output) { + output << header_Version << std::endl; + output << _version << std::endl << std::endl; + output << header_Module << std::endl; + addAllElements(output, _modules); + output << std::endl; + output << header_Asset << std::endl; + addAllElements(output, _assets); + output << std::endl; + output << header_Property << std::endl; + addAllElements(output, _properties); + output << std::endl; + output << header_Keybinding << std::endl; + addAllElements(output, _keybindings); + output << std::endl; + output << header_Time << std::endl; + output << _time << std::endl << std::endl; + output << header_Camera << std::endl; + output << _camera << std::endl; + output << header_MarkNodes << std::endl; + addAllElements(output, _markNodes); +} + +const std::string ProfileFile::getVersion() const { + return _version; +} + +void ProfileFile::addAllElements(std::ostream& file, std::vector& list) { + for (auto s : list) { + file << s << std::endl; + } +} + +void ProfileFile::clearAllFields() { + _numLinesVersion = 0; + _numLinesTime = 0; + _numLinesCamera = 0; + + _version.clear(); + _time.clear(); + _camera.clear(); + _modules.clear(); + _assets.clear(); + _properties.clear(); + _keybindings.clear(); + _markNodes.clear(); +} + +bool ProfileFile::isBlank(std::string line) { + char* c = const_cast(line.c_str()); + int nonBlanks = 0; + while (*c) { + if (!isspace(*c)) { + nonBlanks++; + } + c++; + } + return (nonBlanks == 0); +} + +bool ProfileFile::determineSection(std::string line) { + bool foundSection = true; + + if (line.compare(header_Version) == 0) { + parseCurrentSection = &ProfileFile::parseVersion; + } + else if (line.compare(header_Module) == 0) { + parseCurrentSection = &ProfileFile::parseModule; + } + else if (line.compare(header_Asset) == 0) { + parseCurrentSection = &ProfileFile::parseAsset; + } + else if (line.compare(header_Property) == 0) { + parseCurrentSection = &ProfileFile::parseProperty; + } + else if (line.compare(header_Keybinding) == 0) { + parseCurrentSection = &ProfileFile::parseKeybinding; + } + else if (line.compare(header_Time) == 0) { + parseCurrentSection = &ProfileFile::parseTime; + } + else if (line.compare(header_Camera) == 0) { + parseCurrentSection = &ProfileFile::parseCamera; + } + else if (line.compare(header_MarkNodes) == 0) { + parseCurrentSection = &ProfileFile::parseMarkNodes; + } + else { + throw ghoul::RuntimeError(errorString("Invalid section header '" + line + "'"), + "profileFile"); + foundSection = false; + } + return foundSection; +} + +std::string ProfileFile::errorString(std::string message) { + std::string e = "Error @ line " + std::to_string(_lineNum) + ": "; + return e + message; +} + +std::string ProfileFile::time() const { + return _time; +} + +std::string ProfileFile::camera() const { + return _camera; +} + +std::vector ProfileFile::modules() const { + return _modules; +} + +std::vector ProfileFile::assets() const { + return _assets; +} + +std::vector ProfileFile::properties() const { + return _properties; +} + +std::vector ProfileFile::keybindings() const { + return _keybindings; +} + +std::vector ProfileFile::markNodes() const { + return _markNodes; +} + +void ProfileFile::parseVersion(std::string line) { + std::vector fields; + + if (++_numLinesVersion > _versionLinesExpected) { + throw ghoul::RuntimeError(errorString("Too many lines in Version section"), + "profileFile"); + } + if (splitByTab(line, fields) > _versionFieldsExpected) { + throw ghoul::RuntimeError(errorString("No tabs allowed in Version entry"), + "profileFile"); + } + else { + _version = line; + } +} + +void ProfileFile::parseModule(std::string line) { + std::vector fields; + + if (splitByTab(line, fields) != _moduleFieldsExpected) { + throw ghoul::RuntimeError(errorString(std::to_string(_moduleFieldsExpected) + + " fields required in a Module entry"), "profileFile"); + } + std::vector standard = { + "module name", + "", + "" + }; + verifyRequiredFields("Module", fields, standard, _moduleFieldsExpected); + _modules.push_back(line); +} + +void ProfileFile::parseAsset(std::string line) { + std::vector fields; + + if (splitByTab(line, fields) != _assetFieldsExpected) { + throw ghoul::RuntimeError(errorString(std::to_string(_assetFieldsExpected) + + " fields required in an Asset entry"), "profileFile"); + } + std::vector standard = { + "asset name", + "" + }; + verifyRequiredFields("Asset", fields, standard, _assetFieldsExpected); + _assets.push_back(line); +} + +void ProfileFile::parseProperty(std::string line) { + std::vector fields; + + if (splitByTab(line, fields) != _propertyFieldsExpected) { + throw ghoul::RuntimeError(errorString(std::to_string(_propertyFieldsExpected) + + " fields required in Property entry"), "profileFile"); + } + std::vector standard = { + "set command", + "name", + "value" + }; + verifyRequiredFields("Property", fields, standard, _propertyFieldsExpected); + _properties.push_back(line); +} + +void ProfileFile::parseKeybinding(std::string line) { + std::vector fields; + + if (splitByTab(line, fields) != _keybindingFieldsExpected) { + throw ghoul::RuntimeError(errorString(std::to_string(_keybindingFieldsExpected) + + " fields required in Keybinding entry"), "profileFile"); + } + std::vector standard = { + "key", + "documentation", + "name", + "GuiPath", + "local(T/F)", + "script to execute" + }; + verifyRequiredFields("Keybinding", fields, standard, _keybindingFieldsExpected); + _keybindings.push_back(line); +} + +void ProfileFile::parseTime(std::string line) { + std::vector fields; + + if (++_numLinesTime > _timeLinesExpected) { + throw ghoul::RuntimeError(errorString("Too many lines in time section"), + "profileFile"); + } + if (splitByTab(line, fields) != _timeFieldsExpected) { + throw ghoul::RuntimeError(errorString(std::to_string(_timeFieldsExpected) + + " fields required in Time entry"), "profileFile"); + } + std::vector standard = { + "time set type", + "time value to set" + }; + verifyRequiredFields("Time", fields, standard, _timeFieldsExpected); + _time = line; +} + +void ProfileFile::parseCamera(std::string line) { + std::vector fields; + + if (++_numLinesCamera > _cameraLinesExpected) { + throw ghoul::RuntimeError(errorString("Too many lines in camera section"), + "profileFile"); + } + size_t nFields = splitByTab(line, fields); + if (nFields == _cameraNavigationFieldsExpected) { + std::vector standard = { + "Type of camera set (setNavigationState)", + "setNavigationState Anchor", + "", + "", + "setNavigationState position vector", + "", + "", + "" + }; + verifyRequiredFields("Camera navigation", fields, standard, + _cameraNavigationFieldsExpected); + } + else if (nFields == _cameraGeoFieldsExpected) { + std::vector standard = { + "Type of camera set (goToGeo)", + "", + "Camera goToGeo Latitude", + "Camera goToGeo Longitude", + "" + }; + verifyRequiredFields("Camera goToGeo", fields, standard, + _cameraGeoFieldsExpected); + } + else { + throw ghoul::RuntimeError(errorString(std::to_string( + _cameraNavigationFieldsExpected) + " or " + std::to_string( + _cameraGeoFieldsExpected) + " fields required in Camera entry"), + "profileFile"); + } + _camera = line; +} + +void ProfileFile::parseMarkNodes(std::string line) { + std::vector fields; + + if (splitByTab(line, fields) != _markNodesFieldsExpected) { + throw ghoul::RuntimeError(errorString(std::to_string(_markNodesFieldsExpected) + + " field required in an Mark Nodes entry"), "profileFile"); + } + std::vector standard = { + "Mark Interesting Node name" + }; + verifyRequiredFields("Mark Interesting Nodes", fields, standard, + _markNodesFieldsExpected); + _markNodes.push_back(line); +} + +void ProfileFile::verifyRequiredFields(std::string sectionName, + std::vector fields, + std::vector standard, + unsigned int nFields) +{ + for (unsigned int i = 0; i < fields.size(); i++) { + if (!standard[i].empty() && fields[i].empty()) { + std::string errMsg = sectionName + " " + standard[i]; + errMsg += "(arg " + std::to_string(i) + "/"; + errMsg += std::to_string(nFields) + ") is required"; + throw ghoul::RuntimeError(errorString(errMsg), "profileFile"); + } + } +} + +size_t ProfileFile::splitByTab(std::string line, std::vector& result) { + std::istringstream iss(line); + std::string tmp; + result.clear(); + while (getline(iss, tmp, '\t')) { + result.push_back(tmp); + } + //Insert additional empty fields only for the case of tab delimiters at end of + // string without populated field(s) + size_t nTabs = std::count(line.begin(), line.end(), '\t'); + for (size_t i = 0; i < (nTabs - result.size() + 1); ++i) { + result.push_back(""); + } + return result.size(); +} + +} // namespace openspace diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 74b2691cf4..018bc93ba6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable( test_lrucache.cpp test_luaconversions.cpp test_optionproperty.cpp + profile/test_profilefile.cpp test_rawvolumeio.cpp test_scriptscheduler.cpp test_spicemanager.cpp diff --git a/tests/profile/test_profilefile.cpp b/tests/profile/test_profilefile.cpp new file mode 100644 index 0000000000..91af2108ed --- /dev/null +++ b/tests/profile/test_profilefile.cpp @@ -0,0 +1,268 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include "catch2/catch.hpp" + +#include "openspace/scene/profilefile.h" +#include +#include +#include +#include + +using namespace openspace; + +namespace { +} + +struct testProfileFormat { + std::vector tsv; + std::vector tsm; + std::vector tsa; + std::vector tsp; + std::vector tsk; + std::vector tst; + std::vector tsc; + std::vector tsn; +}; + +testProfileFormat buildTestProfile1() { + testProfileFormat tp1; + tp1.tsv.push_back("#Version"); + tp1.tsv.push_back("123.4"); + tp1.tsm.push_back("#Module"); + tp1.tsm.push_back("globebrowsing\t\t"); + tp1.tsm.push_back("gaia\tprint(\"success.\")\t"); + tp1.tsm.push_back("volume\t\tquit"); + tp1.tsa.push_back("#Asset"); + tp1.tsa.push_back("scene/solarsystem/planets/earth/moon/moon\trequired"); + tp1.tsa.push_back("scene/solarsystem/missions/apollo/apollo8\trequested"); + tp1.tsa.push_back("scene/solarsystem/planets/earth/earth\t"); + tp1.tsp.push_back("#Property"); + tp1.tsp.push_back("setPropertyValue\tNavigationHandler.OrbitalNavigator.FollowAnchorNodeRotationDistance\t20.000000"); + tp1.tsp.push_back("setPropertyValueSingle\tScene.Pluto.Renderable.Enabled\tfalse"); + tp1.tsp.push_back("setPropertyValue\tScene.Charon.Renderable.Enabled\tfalse"); + tp1.tsp.push_back("setPropertyValueSingle\tScene.PlutoBarycenterTrail.Renderable.Enabled\tfalse"); + tp1.tsk.push_back("#Keybinding"); + tp1.tsk.push_back("F8\tSets the time to the approach at Bennu.\tSet Bennu approach time\t/Missions/Osiris Rex\tfalse\t\"openspace.printInfo('Set time: Approach'); openspace.time.setTime('2018-SEP-11 21:31:01.183')\""); + tp1.tsk.push_back("F9\tSets the time to the preliminary survey of Bennu.\tSet Bennu survey time\t/Missions/Osiris Rex\tfalse\t\"openspace.printInfo('Set time: Preliminary Survey'); openspace.time.setTime('2018-NOV-20 01:13:12.183')\""); + tp1.tsk.push_back("F10\tSets the time to the orbital B event.\tSet orbital B event time\t/Missions/Osiris Rex\tfalse\t\"openspace.printInfo('Set time: Orbital B'); openspace.time.setTime('2019-APR-08 10:35:27.186')\""); + tp1.tsk.push_back("F11\tSets the time to the recon event.\tSet recon event time\t/Missions/Osiris Rex\tfalse\t\"openspace.printInfo('Set time: Recon'); openspace.time.setTime('2019-MAY-25 03:50:31.195')\""); + tp1.tst.push_back("#Time"); + tp1.tst.push_back("absolute\t1977-12-21T12:51:51.0"); + tp1.tsc.push_back("#Camera"); + tp1.tsc.push_back("setNavigationState\t\"NewHorizons\"\t\t\"Root\"\t-6.572656E1, -7.239404E1, -2.111890E1\t0.102164, -0.362945, 0.926193\t\t"); + tp1.tsn.push_back("#MarkNodes"); + tp1.tsn.push_back("Pluto"); + tp1.tsn.push_back("NewHorizons"); + tp1.tsn.push_back("Charon"); + + return tp1; +} + +std::string stringFromSingleProfileSection(std::vector& section) { + std::string result; + for (std::string s : section) { + result += s + "\n"; + } + result += "\n"; + return result; +} + +std::string stringFromTestProfileFormat(testProfileFormat& tpf) { + std::string fullProfile; + + fullProfile += stringFromSingleProfileSection(tpf.tsv); + fullProfile += stringFromSingleProfileSection(tpf.tsm); + fullProfile += stringFromSingleProfileSection(tpf.tsa); + fullProfile += stringFromSingleProfileSection(tpf.tsp); + fullProfile += stringFromSingleProfileSection(tpf.tsk); + fullProfile += stringFromSingleProfileSection(tpf.tst); + fullProfile += stringFromSingleProfileSection(tpf.tsc); + fullProfile += stringFromSingleProfileSection(tpf.tsn); + + return fullProfile; +} + +TEST_CASE("profileFile: Simple read and verify", "[profileFile]") { + testProfileFormat test = buildTestProfile1(); + std::string testFull_string = stringFromTestProfileFormat(test); + std::istringstream iss(testFull_string); + + ProfileFile pf; + pf.readLines([&iss](std::string& line) { + if (getline(iss, line)) + return true; + else + return false; + }); + + std::vector tVect; + + REQUIRE(pf.getVersion() == test.tsv[1]); + REQUIRE(pf.time() == test.tst[1]); + REQUIRE(pf.camera() == test.tsc[1]); + tVect = pf.modules(); + REQUIRE(tVect[0] == test.tsm[1]); + REQUIRE(tVect[1] == test.tsm[2]); + REQUIRE(tVect[2] == test.tsm[3]); + tVect = pf.assets(); + REQUIRE(tVect[0] == test.tsa[1]); + REQUIRE(tVect[1] == test.tsa[2]); + REQUIRE(tVect[2] == test.tsa[3]); + tVect = pf.properties(); + REQUIRE(tVect[0] == test.tsp[1]); + REQUIRE(tVect[1] == test.tsp[2]); + REQUIRE(tVect[2] == test.tsp[3]); + REQUIRE(tVect[3] == test.tsp[4]); + tVect = pf.keybindings(); + REQUIRE(tVect[0] == test.tsk[1]); + REQUIRE(tVect[1] == test.tsk[2]); + REQUIRE(tVect[2] == test.tsk[3]); + REQUIRE(tVect[3] == test.tsk[4]); + tVect = pf.markNodes(); + REQUIRE(tVect[0] == test.tsn[1]); + REQUIRE(tVect[1] == test.tsn[2]); + REQUIRE(tVect[2] == test.tsn[3]); +} + +TEST_CASE("profileFile: Unrecognized header", "[profileFile]") { + testProfileFormat test = buildTestProfile1(); + + test.tsa[0] = "#Azzet"; + std::string testFull_string = stringFromTestProfileFormat(test); + std::istringstream iss(testFull_string); + + ProfileFile pf; + REQUIRE_THROWS_WITH( + pf.readLines([&iss](std::string& line) { + if (getline(iss, line)) + return true; + else + return false; + } + ), + Catch::Matchers::Contains ( "Invalid section header" ) + && Catch::Matchers::Contains( "#Azzet" ) + ); +} + +TEST_CASE("profileFile: Bad number of fields", "[profileFile]") { + testProfileFormat test = buildTestProfile1(); + std::string testFull_string; + test.tsm[1] = "globebrowsing\t\t\t"; + testFull_string = stringFromTestProfileFormat(test); + { + std::istringstream iss(testFull_string); + ProfileFile pf; + REQUIRE_THROWS_WITH( + pf.readLines([&iss](std::string& line) { + if (getline(iss, line)) + return true; + else + return false; + } + ), + Catch::Matchers::Contains ("fields required in a Module entry") + ); + } + + test.tsm[1] = "globebrowsing\t\t"; + test.tsc[1] = "setNavigationState\t\"NewHorizons\"\t\"Root\"\t-6.572656E1, -7.239404E1, -2.111890E1\t0.102164, -0.362945, 0.926193\t\t"; + testFull_string = stringFromTestProfileFormat(test); + { + std::istringstream iss(testFull_string); + ProfileFile pf; + REQUIRE_THROWS_WITH( + pf.readLines([&iss](std::string& line) { + if (getline(iss, line)) + return true; + else + return false; + } + ), + Catch::Matchers::Contains ("fields required in Camera entry") + ); + } +} + +TEST_CASE("profileFile: Too many lines in time entry", "[profileFile]") { + testProfileFormat test = buildTestProfile1(); + std::string testFull_string; + test.tst.push_back("relative\t\"-1 day\""); + testFull_string = stringFromTestProfileFormat(test); + { + std::istringstream iss(testFull_string); + ProfileFile pf; + REQUIRE_THROWS_WITH( + pf.readLines([&iss](std::string& line) { + if (getline(iss, line)) + return true; + else + return false; + } + ), + Catch::Matchers::Contains ("Too many lines in time section") + ); + } +} + +TEST_CASE("profileFile: Required field missing", "[profileFile]") { + testProfileFormat test = buildTestProfile1(); + std::string testFull_string; + test.tsc[1] = "setNavigationState\t\"NewHorizons\"\ttest\t\"Root\"\t\t0.102164, -0.362945, 0.926193\t\t"; + + testFull_string = stringFromTestProfileFormat(test); + { + std::istringstream iss(testFull_string); + ProfileFile pf; + REQUIRE_THROWS_WITH( + pf.readLines([&iss](std::string& line) { + if (getline(iss, line)) + return true; + else + return false; + } + ), + Catch::Matchers::Contains ("Camera navigation setNavigationState position vector(arg 4/8) is required") + ); + } + + test.tsc[1] = "setNavigationState\t\"NewHorizons\"\t\t\"Root\"\t1, 2, 3\t0.102164, -0.362945, 0.926193\t\t"; + test.tsk[3] = "F10\tSets the time to the orbital B event.\tSet orbital B event time\t/Missions/Osiris Rex\t\t\"openspace.printInfo('Set time: Orbital B'); openspace.time.setTime('2019-APR-08 10:35:27.186')\""; + testFull_string = stringFromTestProfileFormat(test); + { + std::istringstream iss(testFull_string); + ProfileFile pf; + REQUIRE_THROWS_WITH( + pf.readLines([&iss](std::string& line) { + if (getline(iss, line)) + return true; + else + return false; + } + ), + Catch::Matchers::Contains ("Keybinding local(T/F)(arg 4/6) is required") + ); + } +}