From 9c99ecfcfdbb47656227a6dcd3941a38293d3d7d Mon Sep 17 00:00:00 2001 From: Nick Draper <nick.draper@stfc.ac.uk> Date: Fri, 3 Jul 2015 17:57:20 +0100 Subject: [PATCH] Added CheckMantidVersion Alg Still to do documentationand wiring in to startup re #12064 --- .../Framework/DataHandling/CMakeLists.txt | 3 + .../MantidDataHandling/CheckMantidVersion.h | 58 +++++ .../DataHandling/src/CheckMantidVersion.cpp | 227 ++++++++++++++++++ .../test/CheckMantidVersionTest.h | 137 +++++++++++ 4 files changed, 425 insertions(+) create mode 100644 Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/CheckMantidVersion.h create mode 100644 Code/Mantid/Framework/DataHandling/src/CheckMantidVersion.cpp create mode 100644 Code/Mantid/Framework/DataHandling/test/CheckMantidVersionTest.h diff --git a/Code/Mantid/Framework/DataHandling/CMakeLists.txt b/Code/Mantid/Framework/DataHandling/CMakeLists.txt index cb40c02c93b..353a1034547 100644 --- a/Code/Mantid/Framework/DataHandling/CMakeLists.txt +++ b/Code/Mantid/Framework/DataHandling/CMakeLists.txt @@ -1,6 +1,7 @@ set ( SRC_FILES src/AppendGeometryToSNSNexus.cpp src/AsciiPointBase.cpp + src/CheckMantidVersion.cpp src/CompressEvents.cpp src/CreateChopperModel.cpp src/CreateChunkingFromInstrument.cpp @@ -157,6 +158,7 @@ set ( SRC_FILES set ( INC_FILES inc/MantidDataHandling/AppendGeometryToSNSNexus.h inc/MantidDataHandling/AsciiPointBase.h + inc/MantidDataHandling/CheckMantidVersion.h inc/MantidDataHandling/CompressEvents.h inc/MantidDataHandling/CreateChopperModel.h inc/MantidDataHandling/CreateChunkingFromInstrument.h @@ -312,6 +314,7 @@ set ( INC_FILES set ( TEST_FILES AppendGeometryToSNSNexusTest.h + CheckMantidVersionTest.h CompressEventsTest.h CreateChopperModelTest.h CreateChunkingFromInstrumentTest.h diff --git a/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/CheckMantidVersion.h b/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/CheckMantidVersion.h new file mode 100644 index 00000000000..4f0aae1e4bf --- /dev/null +++ b/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/CheckMantidVersion.h @@ -0,0 +1,58 @@ +#ifndef MANTID_DATAHANDLING_CHECKMANTIDVERSION_H_ +#define MANTID_DATAHANDLING_CHECKMANTIDVERSION_H_ + +#include "MantidKernel/System.h" +#include "MantidAPI/Algorithm.h" + +namespace Mantid { +namespace DataHandling { + +/** CheckMantidVersion : Checks if the current version of Mantid is the most recent + + Copyright © 2015 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge + National Laboratory & European Spallation Source + + This file is part of Mantid. + + Mantid 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. + + Mantid 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/>. + + File change history is stored at: <https://github.com/mantidproject/mantid> + Code Documentation is available at: <http://doxygen.mantidproject.org> +*/ +class DLLExport CheckMantidVersion : public API::Algorithm { +public: + CheckMantidVersion(); + virtual ~CheckMantidVersion(); + + virtual const std::string name() const; + virtual int version() const; + virtual const std::string category() const; + virtual const std::string summary() const; +protected: + virtual std::string getVersionsFromGitHub(const std::string &url); + virtual std::string getCurrentVersion() const; +private: + void init(); + void exec(); + + std::string cleanVersionTag(const std::string& versionTag) const; + std::vector<int> splitVersionString(const std::string& versionString) const; + bool isVersionMoreRecent(const std::string& localVersion, const std::string& gitHubVersion) const; + +}; + +} // namespace DataHandling +} // namespace Mantid + +#endif /* MANTID_DATAHANDLING_CHECKMANTIDVERSION_H_ */ \ No newline at end of file diff --git a/Code/Mantid/Framework/DataHandling/src/CheckMantidVersion.cpp b/Code/Mantid/Framework/DataHandling/src/CheckMantidVersion.cpp new file mode 100644 index 00000000000..89c9946799b --- /dev/null +++ b/Code/Mantid/Framework/DataHandling/src/CheckMantidVersion.cpp @@ -0,0 +1,227 @@ +#include "MantidDataHandling/CheckMantidVersion.h" +#include "MantidKernel/InternetHelper.h" +#include "MantidKernel/MantidVersion.h" +#include "MantidKernel/Strings.h" + +#include <Poco/DateTimeFormatter.h> +#include <Poco/DateTimeFormat.h> +#include <Poco/DateTimeParser.h> +#include <Poco/StringTokenizer.h> + +// jsoncpp +#include <json/json.h> + +#include <boost/lexical_cast.hpp> + +namespace Mantid { +namespace DataHandling { + +using namespace Mantid::Kernel; +using Mantid::API::WorkspaceProperty; + +// Register the algorithm into the AlgorithmFactory +DECLARE_ALGORITHM(CheckMantidVersion) + +//---------------------------------------------------------------------------------------------- +/** Constructor + */ +CheckMantidVersion::CheckMantidVersion() {} + +//---------------------------------------------------------------------------------------------- +/** Destructor + */ +CheckMantidVersion::~CheckMantidVersion() {} + +//---------------------------------------------------------------------------------------------- + +/// Algorithms name for identification. @see Algorithm::name +const std::string CheckMantidVersion::name() const { + return "CheckMantidVersion"; +} + +/// Algorithm's version for identification. @see Algorithm::version +int CheckMantidVersion::version() const { return 1; } + +/// Algorithm's category for identification. @see Algorithm::category +const std::string CheckMantidVersion::category() const { + return "Utility\\Development"; +} + +/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary +const std::string CheckMantidVersion::summary() const { + return "Checks if there is a more recent version of Mantid available using " + "the Github API"; +} + +//---------------------------------------------------------------------------------------------- +/** Initialize the algorithm's properties. + */ +void CheckMantidVersion::init() { + declareProperty("CurrentVersion", "", Direction::Output); + declareProperty("MostRecentVersion", "", Direction::Output); + declareProperty("IsNewVersionAvailable", false, Direction::Output); +} + + //---------------------------------------------------------------------------------------------- +/** Execute the algorithm. + */ +void CheckMantidVersion::exec() { + std::string currentVersion = getCurrentVersion(); + std::string mostRecentVersion = ""; + + std::string gitHubReleaseUrl = ConfigService::Instance().getString( + "CheckMantidVersion.GitHubReleaseURL"); + if (gitHubReleaseUrl.empty()) { + gitHubReleaseUrl = + "https://api.github.com/repos/mantidproject/mantid/releases/latest"; + } + std::string downloadUrl = + ConfigService::Instance().getString("CheckMantidVersion.DownloadURL"); + if (downloadUrl.empty()) { + downloadUrl = "http://download.mantidproject.org"; + } + + std::string json = ""; + try { + json = getVersionsFromGitHub(gitHubReleaseUrl); + } catch (Exception::InternetError &ex) { + if (ex.errorCode() == InternetHelper::HTTP_NOT_MODIFIED) { + // No changes since last release + mostRecentVersion = getCurrentVersion(); + } else { + throw; + } + } + + if (!json.empty()) { + Json::Reader r; + Json::Value root; + r.parse(json, root); + + std::string gitHubVersionTag = root["tag_name"].asString(); + mostRecentVersion = cleanVersionTag(gitHubVersionTag); + + } + + g_log.information("Current Mantid Version: " + currentVersion); + g_log.information("Most Recent Mantid Version: " + mostRecentVersion); + bool isNewVersionAvailable = isVersionMoreRecent(currentVersion, mostRecentVersion); + if (isVersionMoreRecent(currentVersion, mostRecentVersion)) { + // output a notice level log + g_log.notice("A new version of Mantid(" + mostRecentVersion + + ") is available for download from " + downloadUrl); + } + setProperty("CurrentVersion", currentVersion); + setProperty("MostRecentVersion", mostRecentVersion); + setProperty("IsNewVersionAvailable", isNewVersionAvailable); +} + +/** Cleans the tag name from github to make it similar to that from +* MantidVersion +* @param versionTag the version tag that needs cleaning +* @returns a clean string +*/ +std::string +CheckMantidVersion::cleanVersionTag(const std::string &versionTag) const { + std::string retVal = versionTag; + + retVal = Strings::replaceAll(retVal, "v", ""); + retVal = Strings::replaceAll(retVal, "V", ""); + retVal = Strings::strip(retVal); + + return retVal; +} + +/** splits a . separated version string into a vector of integers +* @param versionString Something like "2.3.4" +* @returns a vector of [2,3,4] +*/ +std::vector<int> +CheckMantidVersion::splitVersionString(const std::string &versionString) const { + std::vector<int> retVal; + Poco::StringTokenizer tokenizer(versionString, ".", + Poco::StringTokenizer::TOK_TRIM | + Poco::StringTokenizer::TOK_IGNORE_EMPTY); + Poco::StringTokenizer::Iterator h = tokenizer.begin(); + + for (; h != tokenizer.end(); ++h) { + try { + int part = boost::lexical_cast<int>(*h); + retVal.push_back(part); + } catch (const boost::bad_lexical_cast &) { + g_log.error("Failed to convert the following string to an integer '" + + *h + "' as part of CheckMantidVersion::splitVersionString"); + retVal.push_back(0); + } + } + return retVal; +} + +/** Compare two version strings, tests if the gitHubVersion is more recent +* @param localVersion Something like "2.3.4" +* @param gitHubVersion Something like "2.3.4" +* @returns True if gitHubVersion is more recent +*/ +bool CheckMantidVersion::isVersionMoreRecent( + const std::string &localVersion, const std::string &gitHubVersion) const { + auto localVersionParts = splitVersionString(localVersion); + auto gitHubVersionParts = splitVersionString(gitHubVersion); + + for (int i = 0; i < gitHubVersionParts.size(); i++) { + // sanity check + if (i >= localVersionParts.size()) { + // ran out of items to compare + break; + } + + // the revision number needs to be handled separately + if (i == 2) { + if (localVersionParts[i] > 2000) { + // this is a date string, nightly build + // state that the local version is up to date + return false; + } + } + if (gitHubVersionParts[i] > localVersionParts[i]) { + return true; + } + if (gitHubVersionParts[i] < localVersionParts[i]) { + return false; + } + } + return false; +} + +/** Gets the version json for the most recent release from gitHub + +@param urlFile : The url to download the contents of +@exception Mantid::Kernel::Exception::InternetError : For any unexpected +behaviour. +*/ +std::string CheckMantidVersion::getVersionsFromGitHub(const std::string &url) { + std::string retVal = ""; + + Kernel::InternetHelper inetHelper; + std::ostringstream os; + int tzd = 0; + + inetHelper.headers().insert(std::make_pair( + "if-modified-since", + Poco::DateTimeFormatter::format( + Poco::DateTimeParser::parse(MantidVersion::releaseDate(), tzd), + Poco::DateTimeFormat::HTTP_FORMAT))); + inetHelper.sendRequest(url, os); + retVal = os.str(); + + return retVal; +} +/** Gets the version of this Mantid +@returns a string of the form "1.2.3[.4]" +*/ +std::string CheckMantidVersion::getCurrentVersion() const +{ + return Mantid::Kernel::MantidVersion::version(); +} + +} // namespace DataHandling +} // namespace Mantid \ No newline at end of file diff --git a/Code/Mantid/Framework/DataHandling/test/CheckMantidVersionTest.h b/Code/Mantid/Framework/DataHandling/test/CheckMantidVersionTest.h new file mode 100644 index 00000000000..5aff476cd75 --- /dev/null +++ b/Code/Mantid/Framework/DataHandling/test/CheckMantidVersionTest.h @@ -0,0 +1,137 @@ +#ifndef MANTID_DATAHANDLING_CHECKMANTIDVERSIONTEST_H_ +#define MANTID_DATAHANDLING_CHECKMANTIDVERSIONTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidDataHandling/CheckMantidVersion.h" + +using Mantid::DataHandling::CheckMantidVersion; +using namespace Mantid::API; + +namespace { +/** + * Mock out the version calls of this algorithm + */ +class MockedCheckMantidVersion : public CheckMantidVersion { +public: + MockedCheckMantidVersion(std::string currentVersion, std::string gitHubVersion) : CurrentVersion(currentVersion), + GitHubVersion(gitHubVersion), CheckMantidVersion() {} + + std::string CurrentVersion; + std::string GitHubVersion; +private: + virtual std::string getVersionsFromGitHub(const std::string &url) { + std::string outputString; + outputString ="{\n" + " \"url\": \"https://api.github.com/repos/mantidproject/mantid/releases/1308203\",\n" + " \"assets_url\": \"https://api.github.com/repos/mantidproject/mantid/releases/1308203/assets\",\n" + " \"upload_url\": \"https://uploads.github.com/repos/mantidproject/mantid/releases/1308203/assets{?name}\",\n" + " \"html_url\": \"https://github.com/mantidproject/mantid/releases/tag/v3.4.0\",\n" + " \"id\": 1308203,\n" + " \"tag_name\": \"" + GitHubVersion + "\",\n" + " \"target_commitish\": \"master\",\n" + " \"name\": \"Release version 3.4.0\",\n" + " \"draft\": false,\n" + " \"author\": {\n" + " \"login\": \"peterfpeterson\",\n" + " \"id\": 404003,\n" + " \"avatar_url\": \"https://avatars.githubusercontent.com/u/404003?v=3\",\n" + " \"gravatar_id\": \"\",\n" + " \"url\": \"https://api.github.com/users/peterfpeterson\",\n" + " \"html_url\": \"https://github.com/peterfpeterson\",\n" + " \"followers_url\": \"https://api.github.com/users/peterfpeterson/followers\",\n" + " \"following_url\": \"https://api.github.com/users/peterfpeterson/following{/other_user}\",\n" + " \"gists_url\": \"https://api.github.com/users/peterfpeterson/gists{/gist_id}\",\n" + " \"starred_url\": \"https://api.github.com/users/peterfpeterson/starred{/owner}{/repo}\",\n" + " \"subscriptions_url\": \"https://api.github.com/users/peterfpeterson/subscriptions\",\n" + " \"organizations_url\": \"https://api.github.com/users/peterfpeterson/orgs\",\n" + " \"repos_url\": \"https://api.github.com/users/peterfpeterson/repos\",\n" + " \"events_url\": \"https://api.github.com/users/peterfpeterson/events{/privacy}\",\n" + " \"received_events_url\": \"https://api.github.com/users/peterfpeterson/received_events\",\n" + " \"type\": \"User\",\n" + " \"site_admin\": false\n" + " }"; + + return outputString; + } + virtual std::string getCurrentVersion() const { + return CurrentVersion; + } + + +}; +} + +class CheckMantidVersionTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static CheckMantidVersionTest *createSuite() { return new CheckMantidVersionTest(); } + static void destroySuite( CheckMantidVersionTest *suite ) { delete suite; } + + + void test_Init() + { + CheckMantidVersion alg; + TS_ASSERT_THROWS_NOTHING( alg.initialize() ) + TS_ASSERT( alg.isInitialized() ) + } + + void runTest(const std::string& localVersion, const std::string& gitHubVersion, bool expectedResult) + { + MockedCheckMantidVersion alg(localVersion,gitHubVersion); + TS_ASSERT_THROWS_NOTHING( alg.initialize() ) + + TS_ASSERT( alg.isInitialized() ); + TS_ASSERT_THROWS_NOTHING( alg.execute(); ); + TS_ASSERT( alg.isExecuted() ); + + std::string currentVersion = alg.PropertyManagerOwner::getProperty("CurrentVersion"); + std::string mostRecentVersion = alg.PropertyManagerOwner::getProperty("MostRecentVersion"); + bool isNewVersionAvailable = alg.PropertyManagerOwner::getProperty("IsNewVersionAvailable"); + + // Check the results + TS_ASSERT_EQUALS(alg.CurrentVersion,currentVersion); + TS_ASSERT_EQUALS(alg.CurrentVersion,currentVersion); + TS_ASSERT_EQUALS(expectedResult,isNewVersionAvailable); + } + + void test_execLocalNewerRevision() + { + runTest("3.4.2","v3.4.0",false); + } + void test_execRemoteNewerRevision() + { + runTest("3.4.0","v3.4.1",true); + } + void test_execLocaldevelopRevision() + { + runTest("3.4.20150703.1043","v3.4.0",false); + } + void test_execLocaldevelopNewerRevision() + { + runTest("3.4.20150703.1043","v3.4.1",false); + } + + void test_execLocalNewerMinor() + { + runTest("3.5.2","v3.4.7",false); + } + void test_execRemoteNewerMinor() + { + runTest("3.3.7","v3.4.1",true); + } + void test_execLocalNewerMajor() + { + runTest("2.0.2","v1.11.7",false); + } + void test_execRemoteNewerMajor() + { + runTest("2.3.7","v3.0.0",true); + } + + +}; + + +#endif /* MANTID_DATAHANDLING_CHECKMANTIDVERSIONTEST_H_ */ \ No newline at end of file -- GitLab