Skip to content
Snippets Groups Projects
  • Gesner Passos's avatar
    f1111fa8
    Installation Requirement change · f1111fa8
    Gesner Passos authored
    Now, we allow users to install ScriptRepository in an already used directory.
    This encourages the users to select the folder that they are already using,
    probably helping them to publish afterwards.
    
    re #6175
    f1111fa8
    History
    Installation Requirement change
    Gesner Passos authored
    Now, we allow users to install ScriptRepository in an already used directory.
    This encourages the users to select the folder that they are already using,
    probably helping them to publish afterwards.
    
    re #6175
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ScriptRepositoryTestImpl.h 18.64 KiB
#ifndef SCRIPTREPOSITORYIMPLTEST_H_
#define SCRIPTREPOSITORYIMPL_H_

#include <cxxtest/TestSuite.h>
#include "MantidScriptRepository/ScriptRepositoryImpl.h"
#include <Poco/File.h>
// Visual Studion compains with the inclusion of Poco/FileStream
// disabling this warning.
#if defined(_WIN32) || defined(_WIN64)
#pragma warning( push )
#pragma warning( disable : 4250 )
#include <Poco/FileStream.h>
#pragma warning( pop )
#else
#include <Poco/FileStream.h>
#endif
#include <Poco/TemporaryFile.h>
#include <algorithm>
#include <Poco/DateTimeFormatter.h>
#include <boost/algorithm/string.hpp>
using namespace std; 
using Mantid::API::ScriptRepositoryImpl;
using Mantid::API::ScriptRepoException;

const bool TEST_MANUALLY = false;

/** To run this tests (LINUX):

    make ScriptRepositoryTest

    ctest -R ScriptRepositoryTest [-verbose]

 **/

const std::string REPOSITORYJSON = "{\n"
    "\"TofConv\":\n"
    "{\n"
     " \"pub_date\": \"2012-Feb-13 10:00:50\",\n"
     " \"description\": \"the description\",\n"
     " \"directory\": true \n"
    "},\n"
    "\"TofConv/README.txt\":\n"
    "{\n"
 	  " \"pub_date\": \"2012-Feb-13 10:02:50\",\n"
	  " \"description\": \"tofconv description\",\n"
	  " \"directory\": false \n"
    "},\n"
    "\"TofConv/TofConverter.py\":\n"
    "{\n"
	  "  \"pub_date\": \"2012-Feb-10 10:00:50\",\n"
	  "  \"description\": \"tofconverter description\",\n"
	  "  \"directory\": false	\n"
    "},\n"
    "\"reflectometry\":\n"
    "{\n"
    "	\"pub_date\": \"2012-Jan-13 10:00:50\",\n"
	  "  \"directory\": true	\n"
    "},\n"
    "\"reflectometry/Quick.py\":\n"
    "{\n"
  	"  \"pub_date\": \"2012-Feb-13 10:00:00\",\n"
    "  \"description\": \"quick description\",\n"
	  "\"directory\": false	\n"
    "}\n"
  "}\n";

const std::string TOFCONV_README="This is the content of TOFCONV_README";
const std::string TOFCONV_CONVERTER = "print 'hello world'";

const std::string webserverurl = "http://localhost";


/** The ScriptRepositoryTest aims to ensure and protect the logic and 
the interfaces described for ScriptRepository without requiring 
the connection to the internet. 

In order to be able to do this, the ::ScriptRepositoryTestImpl 
uses the ::ScriptRepositoryImplLocal, which is a derived class
of ::ScriptRepositoryImpl, but adds the following: 

 - It overrides the doDownloadFile method, scaping from downloading
   to copy the files locally. 

Through this strategy, all the logic and the behavior of the
::ScriptRepositoryImpl can be tested. 

It provides some public attributes repository_json_content, 
tofconf_readme_content, tofconf_tofconverter_content that allows 
to simulate changes and new values for the downloading.

*/


class ScriptRepositoryImplLocal : public ScriptRepositoryImpl{
 public:
 ScriptRepositoryImplLocal(std::string a="", std::string b=""):
 ScriptRepositoryImpl(a,b){
   repository_json_content = REPOSITORYJSON;
   tofconv_readme_content = TOFCONV_README;
   tofconv_tofconverter_content = TOFCONV_CONVERTER;
 }
 virtual ~ScriptRepositoryImplLocal()throw(){};

 std::string repository_json_content;
 std::string tofconv_readme_content; 
 std::string tofconv_tofconverter_content;

 /** Override the ScriptRepositoryImpl::doDownloadFile in order
     to mock its functioning avoiding the internet connection.

     It allows the following requests: 

     @code
     // download repository.json file
     doDownloadFile("<remote_url>/repository.json","<localpath>/repository.json); 
     // ping the site
     doDownloadFile("<remote_url>","");
     // download file TofConv/README.txt
     doDownloadFile("<remote_url>/TofConv/README.txt","<localpath>/TofConv/README.txt");
     // download file TofConv/README.txt
     doDownloadFile("<remote_url>/TofConv/TofConverter.py","<localpath>/TofConv/TofConverter.py");
     
     @endcode

     It also make it public, in order to be able to test this method itself.
  */
 void doDownloadFile(const std::string url_file, 
                     const std::string local_file_path){

   // answer when the download it to 'forget' the downloaded file
   // request to ping the site
   if (local_file_path.empty())
     return;

   Poco::FileStream _out(local_file_path); 
   
   if( url_file.find("repository.json") != std::string::npos){
     // request to download repository.json
     _out << repository_json_content;
     _out.close();
     return;
   }
   
   if (url_file.find("TofConv/README.txt") != std::string::npos){
     // request to download TofConv/README.txt
     _out << tofconv_readme_content ; 
     _out.close(); 
     return;
   }


   if (url_file.find("TofConv/TofConverter.py") != std::string::npos){
     // request to download TofConv/TofConverter.py
     _out << tofconv_tofconverter_content ; 
     _out.close(); 
     return;
   }

   if (url_file == remote_url){
     // request to ping the site
     _out << "<html><body>Site Alive</body></html>"; 
     _out.close(); 
     return;
   }
   
   std::stringstream ss; 
   ss << "Failed to download this file : " << url_file << " to " << local_file_path << std::ends;
   throw ScriptRepoException(ss.str()); 
 }
 
};


/** Protect the logic and behavior of ScriptRepositoryImpl without requiring internet connection. 



These tests do no depend on the internet connection
ctest -j8 -R ScriptRepositoryTestImpl_  --verbose

**/
class ScriptRepositoryTestImpl : public CxxTest::TestSuite{
  ScriptRepositoryImplLocal * repo;
  std::string local_rep;
 public: 
  static ScriptRepositoryTestImpl * createSuite(){return new ScriptRepositoryTestImpl(); }
  static void destroySuite (ScriptRepositoryTestImpl * suite){delete suite; }

  // ensure that all tests will be perfomed in a fresh repository
  void setUp(){    
    using Poco::TemporaryFile; 
    TemporaryFile temp_f; 
    local_rep = temp_f.path(); 
    TS_ASSERT_THROWS_NOTHING(repo = new ScriptRepositoryImplLocal(local_rep, webserverurl)); 
  }
  
  // ensure that the local files are free from the test created.
  void tearDown(){
    delete repo; 
    try{
      Poco::File f(local_rep); 
      f.remove(true);
    }catch(Poco::Exception & ex){
      TS_WARN(ex.displayText());
    }
  }


  /*****************
   * ENSURING  ScriptRepositoryImplLocal::doDownloadFile do not introduce errors
   ******************/
  void test_doDownloadFile(){
    // ensure it can ping the remote url
    TS_ASSERT_THROWS_NOTHING(repo->doDownloadFile(webserverurl,"")); 
    
    // simulate the installation.
    Poco::File dir(local_rep); 
    dir.createDirectories(); 

    {
      // ensure it can download repository.json      
    std::string local_j_file = std::string(local_rep).append("/.repository.json");
    TS_ASSERT_THROWS_NOTHING(repo->doDownloadFile(std::string(webserverurl).append("/repository.json"),
                                                  local_j_file));
    }
    {
      // ensure it can download TofConv/README.txt
    std::string local_j_file = std::string(local_rep).append("/TofConv/README.txt");
    Poco::File dir(std::string(local_rep).append("/TofConv")); 
    dir.createDirectories(); 
    TS_ASSERT_THROWS_NOTHING(repo->doDownloadFile(std::string(webserverurl).append("/TofConv/README.txt"),
                                                  local_j_file));
    Poco::File f(local_j_file); 
    TS_ASSERT(f.exists());
    }
  }


  /*************************************
   *   INSTALL
   *************************************/

  /**
     Testing the installation of the Repository Service:
     
     The normal test, it should be able to create the new folder and put inside the 
     repository.json and local.json files. 
   */
  void test_normal_installation_procedure(){
    // before installing the repository, ScriptRepositoryImpl will be always invalid
    TSM_ASSERT("Why valid?",!repo->isValid()); 
    // the installation should throw nothing
    TSM_ASSERT_THROWS_NOTHING("Installation should not throw",repo->install(local_rep)); 
    // the repository must be valid
    TSM_ASSERT("Now should be valid!",repo->isValid());
    // checking that repository.json and local.json exists
    {
      Poco::File l(std::string(local_rep).append("/.repository.json")); 
      TSM_ASSERT("Failed to create repository.json", l.exists()); 
      Poco::File r(std::string(local_rep).append("/.local.json")); 
      TSM_ASSERT("Failed to create local.json", r.exists()); 
    }

    // after the installation, all the others instances of ScriptRepositoryImpl should be valid, 
    // by geting the information from the ScriptRepository settings.
    ScriptRepositoryImplLocal * other = new ScriptRepositoryImplLocal(); 
    TSM_ASSERT("All the others should recognize that this is a valid repository",other->isValid()); 
    delete other; 
  }

  /** 
      Installation may install on non-empty directory. If the directory is already a ScriptRepository, 
      the installation should just return. If it is not, the installation, should install the 
      two hidden files in that directory. 
   */
  void test_installation_do_not_install_on_non_empty_directory(){
    // fill the local_rep path with files
    Poco::File d(local_rep); 
    d.createDirectories(); 
    Poco::FileStream f(std::string(local_rep).append("/myfile")); 
    f << "nothing"; 
    f.close(); 
    // now, local_rep is not empty!

    // before installing the repository, ScriptRepositoryImpl will be always invalid
    TSM_ASSERT("Why valid?",!repo->isValid()); 
    // the installation should throw, directory is not empty    
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
  }


  /*************************************
   *   List Files
   *************************************/

  /**
     List Files must list all the files at central repository
   */
  void test_listFiles_must_list_all_files_at_central_repository(){
    const char * test_entries []= {"TofConv",
                                 "TofConv/README.txt",
                                 "TofConv/TofConverter.py",
                                 "reflectometry",
                                 "reflectometry/Quick.py"};
    std::vector<std::string> list_files;
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    TS_ASSERT_THROWS_NOTHING(list_files = repo->listFiles());
    TS_ASSERT(list_files.size() == 5); 
    // check that all the files at the central repository are inside
    for (int i = 0; i< 5; i++)
      TSM_ASSERT_THROWS_NOTHING(test_entries[i],repo->info(test_entries[i]));     
    }

  /**
     List File must list all the local files as well.
   */
  void test_listFiles_must_list_all_local_files(){
    // will create the folder
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    
    // creating a file to test the list Files
    std::string local_file = std::string(local_rep).append("/myfile");
    Poco::FileStream f(local_file);    
    f << "nothing"; 
    f.close(); 

    // 
    std::vector<std::string > files;
    TS_ASSERT_THROWS_NOTHING(files = repo->listFiles()); 
    
    // checking that the local_file was listed in listFiles.
    TSM_ASSERT_THROWS_NOTHING(local_file, repo->info("myfile"));  
    // MUST ACCEPT AN ABSOLUTE PATH AS WELL
    TSM_ASSERT_THROWS_NOTHING(local_file, repo->info(local_file));
  }


 /*************************************
   *   File Info
   *************************************/
  void test_info_correctly_parses_the_repository_json(){
    using Mantid::API::ScriptInfo;
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    ScriptInfo information = repo->info("TofConv/TofConverter.py"); 
    TS_ASSERT(information.description == "tofconverter description");
    TS_ASSERT(information.author.empty()); 
    TSM_ASSERT("check time", information.pub_date == DateAndTime("2012-02-10 10:00:50")); 
    TS_ASSERT(!information.auto_update);
  }


 /*************************************
   *   Download
   *************************************/


  /** Test that we are able to download files from the remote repository
   */
  void test_download_new_files_from_repository(){
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    TS_ASSERT_THROWS_NOTHING(repo->download("TofConv/README.txt"));    
  }

  /** 
      Test that we are able to download folders from the remote repository
   */
  void test_download_new_folder_from_repository(){
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    TS_ASSERT_THROWS_NOTHING(repo->download("TofConv"));    
  }

  /** Test that we can download files inside folder one at once
   */
  void test_downloading_single_files(){
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    TS_ASSERT_THROWS_NOTHING(repo->download("TofConv/README.txt")); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    TS_ASSERT_THROWS_NOTHING(repo->download("TofConv/TofConverter.py"));     
  }


  /** 
      There is no point downloading files if they have only local_changes, 
      so, this test that it is not possible to download the same file 
      twice, witouth a new version.      
   */
  void tnoest_downloading_twice_the_same_file(){
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    TS_ASSERT_THROWS_NOTHING(repo->download("TofConv/README.txt")); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    // there is no new version, so there is no point to download it again.
    // it throws that the file has not changed.
    TS_ASSERT_THROWS(repo->download("TofConv/README.txt"),ScriptRepoException);       
  }

 /*************************************
   *  UPDATE
   *************************************/
  void test_update(){
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    std::vector<string> list_of_files;
    TS_ASSERT_THROWS_NOTHING(list_of_files = repo->listFiles()); 
    TS_ASSERT (list_of_files.size() == 5); 

    
    // simulate remove of cleaning up the repository, and having just a README.md file
    // 
    repo->repository_json_content = "{\n"
      "\"README.md\":\n"
      "{\n"
      " \"pub_date\": \"2012-02-20 10:00:50\",\n"
      " \"description\": \"Script Repository Script\",\n"
      " \"directory\": false \n"
      "}\n"
      "}\n";
    
    TS_ASSERT_THROWS_NOTHING(repo->check4Update());
    TS_ASSERT_THROWS_NOTHING(list_of_files = repo->listFiles()); 

    std::cout << "After update, the files are: ";
    for (std::vector<string>::iterator it = list_of_files.begin(); 
         it != list_of_files.end();
         it++){
      std::cout << *it << ", ";
    }
    std::cout << std::endl; 
    TS_ASSERT (list_of_files.size() == 1); 
    TS_ASSERT (list_of_files[0] == "README.md");     
}




 /*************************************
   *   FILE STATUS
   *************************************/
  void test_info_of_one_file(){
    std::string file_name = "TofConv/README.txt";
    std::string dir_name = "TofConv";
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    // before downloading the file is REMOTE_ONLY
    TS_ASSERT(repo->fileStatus(file_name) == Mantid::API::REMOTE_ONLY);
    TS_ASSERT(repo->fileStatus(dir_name) == Mantid::API::REMOTE_ONLY);


    // do download
    TS_ASSERT_THROWS_NOTHING(repo->download(file_name));
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 

    // after downloading the file is BOTH_UNCHANGED
    TS_ASSERT(repo->fileStatus(file_name) == Mantid::API::BOTH_UNCHANGED) ;
    TS_ASSERT(repo->fileStatus(dir_name) == Mantid::API::BOTH_UNCHANGED) ;
   

    std::string original_time = "2012-Feb-13 10:02:50"; 
    std::string change_to_ =  "2012-Mar-13 10:02:50";

    // simulate new version of the file
    boost::replace_all(repo->repository_json_content,
                       original_time,
                       change_to_);

    TS_ASSERT_THROWS_NOTHING(repo->check4Update());

    // should change to REMOTE_CHANGED
    TS_ASSERT(repo->fileStatus(file_name) == Mantid::API::REMOTE_CHANGED);
    TS_ASSERT(repo->fileStatus(dir_name) == Mantid::API::REMOTE_CHANGED);


    // restore the file
    repo->repository_json_content = REPOSITORYJSON;

    TS_ASSERT_THROWS_NOTHING(repo->check4Update());


    // after downloading the file is BOTH_UNCHANGED
    TS_ASSERT(repo->fileStatus(file_name) == Mantid::API::BOTH_UNCHANGED) ;
    TS_ASSERT(repo->fileStatus(dir_name) == Mantid::API::BOTH_UNCHANGED) ;

    /**
       We have to change the local file in order to verify if 
       it changes its local status. 
       unfortunatelly, the only way to see a local change, is giving some time, at least 
       one seccond.
       
       so, we will propose two versions, but for production, we will use the fastest one.
    */
    
    if (TEST_MANUALLY){
#if defined(WIN32) || defined(WIN64)  
      Sleep(1000000);
#else
      sleep(1);
#endif
      Poco::FileStream _ap(std::string(local_rep).append("/").append(file_name));
      _ap << "Local change";
      _ap.close();
    }else{
      // we will simulate the change of the file, by, changin the local.json file

      Poco::FileStream ss(std::string(local_rep).append("/local.json")); 
      ss << "{\n"
        <<"\"TofConv\\/README.txt\":\n"
        <<"{\n"
        <<"\"downloaded_date\": \"2013-Mar-07 14:30:09\",\n"
        <<"\"downloaded_pubdate\": \"2012-Feb-13 10:02:50\"\n"
        <<"}\n"
         <<"}";
      ss.close();
      std::string localjson = string(local_rep).append("/.local.json");
      Poco::File f(std::string(local_rep).append("/local.json")); 
      
      #if defined(_WIN32) ||  defined(_WIN64)
      //set the .repository.json and .local.json hidden
      SetFileAttributes( localjson.c_str(), FILE_ATTRIBUTE_NORMAL);     
      #endif
      f.moveTo(localjson);
      #if defined(_WIN32) ||  defined(_WIN64)
     //set the .repository.json and .local.json hidden
      SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_HIDDEN);     
      #endif    
    }
    
    TS_ASSERT_THROWS_NOTHING(repo->listFiles());
    
    // file has local changes
    TS_ASSERT(repo->fileStatus(file_name) == Mantid::API::LOCAL_CHANGED) ;
    TS_ASSERT(repo->fileStatus(dir_name) == Mantid::API::LOCAL_CHANGED) ;

    // simulate new version of the file
    boost::replace_all(repo->repository_json_content,
                       original_time,
                       change_to_);

    TS_ASSERT_THROWS_NOTHING(repo->check4Update());

    
    // file has local and remote changes
    TS_ASSERT(repo->fileStatus(file_name) == Mantid::API::BOTH_CHANGED);
    TS_ASSERT(repo->fileStatus(dir_name) == Mantid::API::BOTH_CHANGED);


  }




  void test_list_files_after_download_repository(){
    TS_ASSERT_THROWS_NOTHING(repo->install(local_rep)); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    TS_ASSERT_THROWS_NOTHING(repo->download("TofConv/TofConverter.py")); 
    TS_ASSERT_THROWS_NOTHING(repo->listFiles()); 
    TS_ASSERT_THROWS_NOTHING(repo->check4Update()); 
  }



};

#endif