LCOV - code coverage report
Current view: top level - oks/apps - oks_validate_repository.cxx (source / functions) Coverage Total Hit
Test: code.result Lines: 0.0 % 209 0
Test Date: 2025-12-21 13:07:08 Functions: 0.0 % 14 0

            Line data    Source code
       1              : /**
       2              :  *  \file oks_validate_repository.cpp
       3              :  *
       4              :  *  This file is part of the OKS package.
       5              :  *  Author: <Igor.Soloviev@cern.ch>
       6              :  */
       7              : 
       8              : 
       9              : #include "oks/kernel.hpp"
      10              : #include "oks/pipeline.hpp"
      11              : #include "oks/exceptions.hpp"
      12              : 
      13              : #include <boost/program_options.hpp>
      14              : 
      15              : #include "ers/ers.hpp"
      16              : #include "logging/Logging.hpp"
      17              : 
      18              : #include <algorithm>
      19              : #include <chrono>
      20              : #include <vector>
      21              : #include <iostream>
      22              : #include <filesystem>
      23              : #include <mutex>
      24              : 
      25              : using namespace dunedaq::oks;
      26              : 
      27              : enum __OksValidateRepositoryExitStatus__ {
      28              :   __Success__ = 0,
      29              :   __BadCommandLine__,
      30              :   __UserAuthenticationFailure__,
      31              :   __NoRepository__,
      32              :   __ConsistencyError__,
      33              :   __IncludesCircularDependencyError__,
      34              :   __AccessManagerAuthorizationFailed__,
      35              :   __AccessManagerNoPermission__,
      36              :   __NoIncludedFile__,
      37              :   __ExceptionCaught__
      38              : };
      39              : 
      40              : 
      41              : std::string s_load_error;
      42              : 
      43              : static void
      44            0 : init_file_load_error(const std::string& file)
      45              : {
      46            0 :   s_load_error = "repository validation failed for file \'";
      47            0 :   s_load_error += file;
      48            0 :   s_load_error += "\':\n";
      49            0 : }
      50              : 
      51              : struct OksValidateJob : public OksJob
      52              : {
      53              : public:
      54              : 
      55            0 :   OksValidateJob(OksKernel& kernel, const std::string& file_name) :
      56            0 :       m_kernel(kernel), m_file_name(file_name)
      57              :   {
      58            0 :     ;
      59            0 :   }
      60              : 
      61              :   void
      62            0 :   run()
      63              :   {
      64            0 :     static std::mutex s_mutex;
      65              : 
      66            0 :     try
      67              :       {
      68            0 :         auto start_usage = std::chrono::steady_clock::now();
      69              : 
      70            0 :         m_kernel.set_silence_mode(true);
      71              : 
      72            0 :         m_kernel.load_file(m_file_name);
      73              : 
      74            0 :         if (!m_kernel.get_bind_classes_status().empty() || !m_kernel.get_bind_objects_status().empty())
      75              :           {
      76            0 :             std::lock_guard lock(s_mutex);
      77              : 
      78            0 :             if (s_load_error.empty())
      79              :               {
      80            0 :                 init_file_load_error(m_file_name);
      81              : 
      82            0 :                 if (!m_kernel.get_bind_classes_status().empty())
      83              :                   {
      84            0 :                     s_load_error += "the schema contains dangling references to non-loaded classes:\n";
      85            0 :                     s_load_error += m_kernel.get_bind_classes_status();
      86              :                   }
      87              : 
      88            0 :                 if (!m_kernel.get_bind_objects_status().empty())
      89              :                   {
      90            0 :                     s_load_error += "the data contain dangling references to non-loaded objects:\n";
      91            0 :                     s_load_error += m_kernel.get_bind_objects_status();
      92              :                   }
      93              :               }
      94            0 :           }
      95              : 
      96            0 :         static std::mutex s_log_mutex;
      97            0 :         std::lock_guard scoped_lock(s_log_mutex);
      98              : 
      99            0 :         log_timestamp() << "validated file \"" << m_file_name << "\" in " << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now()-start_usage).count() / 1000. << " ms\n";
     100            0 :       }
     101            0 :     catch (std::exception& ex)
     102              :       {
     103            0 :         std::lock_guard lock(s_mutex);
     104              : 
     105            0 :         if (s_load_error.empty())
     106              :           {
     107            0 :             init_file_load_error(m_file_name);
     108            0 :             s_load_error += ex.what();
     109              : 
     110            0 :             const std::string& user_repository(m_kernel.get_user_repository_root());
     111            0 :             const std::size_t user_repository_len(m_kernel.get_user_repository_root().length() + 1);
     112              : 
     113            0 :             std::size_t pos;
     114            0 :             while ((pos = s_load_error.find(user_repository)) != std::string::npos)
     115            0 :               s_load_error.replace(pos, user_repository_len, "");
     116              :           }
     117            0 :       }
     118            0 :   }
     119              : 
     120              : private:
     121              : 
     122              :   OksKernel m_kernel;
     123              :   const std::string& m_file_name;
     124              : 
     125              :   // protect usage of copy constructor and assignment operator
     126              : 
     127              : private:
     128              : 
     129              :   OksValidateJob(const OksValidateJob&);
     130              :   OksValidateJob&
     131              :   operator=(const OksValidateJob&);
     132              : 
     133              : };
     134              : 
     135              : 
     136              : struct FoundCircularDependency
     137              : {
     138              :   unsigned int m_count;
     139              :   std::ostringstream m_text;
     140              : 
     141            0 :   FoundCircularDependency() :
     142            0 :       m_count(0)
     143              :   {
     144            0 :     ;
     145            0 :   }
     146              : } s_circular_dependency_message;
     147              : 
     148              : 
     149              : struct TestCircularDependency
     150              : {
     151            0 :   TestCircularDependency(const std::string * file)
     152            0 :   {
     153            0 :     p_set_includes.insert(file);
     154            0 :     p_vector_includes.push_back(file);
     155            0 :   }
     156              : 
     157              :   bool
     158            0 :   push(const std::string * file)
     159              :   {
     160            0 :     auto it = p_set_includes.insert(file);
     161            0 :     if (it.second == false)
     162              :       {
     163            0 :         std::ostringstream s;
     164              : 
     165            0 :         bool report = false;
     166              : 
     167            0 :         for (const auto& x : p_vector_includes)
     168              :           {
     169            0 :             if (x == *it.first)
     170              :               {
     171            0 :                 s_circular_dependency_message.m_text << "\nCircular dependency [" << ++s_circular_dependency_message.m_count << "]:";
     172              :                 report = true;
     173              :               }
     174              : 
     175            0 :             if (report)
     176            0 :               s_circular_dependency_message.m_text << '\n' << " - \"" << *x << "\"";
     177              :           }
     178              : 
     179            0 :         return false;
     180            0 :       }
     181              : 
     182            0 :     p_vector_includes.push_back(file);
     183              : 
     184              :     return true;
     185              :   }
     186              : 
     187              :   void
     188            0 :   pop()
     189              :   {
     190            0 :     p_set_includes.erase(p_vector_includes.back());
     191            0 :     p_vector_includes.pop_back();
     192            0 :   }
     193              : 
     194              :   std::vector<const std::string *> p_vector_includes;
     195              :   std::set<const std::string *, OksFile::SortByName> p_set_includes;
     196              : };
     197              : 
     198              : 
     199              : std::set<std::string>&
     200            0 : define_includes(const std::string& f, const std::set<std::string>& s, std::map<std::string, std::set<std::string>>& file_all_includes, std::map<std::string, std::set<std::string>>& file_explicit_includes, TestCircularDependency& cd_fuse)
     201              : {
     202            0 :   std::set<std::string>& all_includes = file_all_includes[f];
     203              : 
     204            0 :   if(all_includes.empty())
     205              :     {
     206            0 :       for(auto& x : s)
     207              :         {
     208            0 :           if(cd_fuse.push(&x))
     209              :             {
     210            0 :               all_includes.insert(x);
     211              : 
     212            0 :               std::set<std::string>& includes = define_includes(x, file_explicit_includes[x], file_all_includes, file_explicit_includes, cd_fuse);
     213            0 :               for(const auto& y : includes)
     214            0 :                 all_includes.insert(y);
     215              : 
     216            0 :               cd_fuse.pop();
     217              :             }
     218              :         }
     219              :     }
     220              : 
     221            0 :   return all_includes;
     222              : }
     223              : 
     224              : 
     225              : int
     226            0 : main(int argc, char **argv)
     227              : {
     228            0 :   boost::program_options::options_description desc("This program validates OKS git repository for commit by pre-receive hook");
     229              : 
     230            0 :   std::vector<std::string> created, updated, deleted;
     231            0 :   bool circular_dependency_between_includes_is_error = true;
     232            0 :   bool verbose = false;
     233            0 :   std::string user;
     234            0 :   std::size_t pipeline_size = 4;
     235              : 
     236            0 :   try
     237              :     {
     238            0 :       std::vector<std::string> app_types_list;
     239            0 :       std::vector<std::string> segments_list;
     240              : 
     241            0 :       desc.add_options()
     242            0 :         ("add,a", boost::program_options::value<std::vector<std::string> >(&created)->multitoken(), "list of new OKS files and directories to be added to the repository")
     243            0 :         ("update,u", boost::program_options::value<std::vector<std::string> >(&updated)->multitoken(), "list of new OKS files and directories to be updated in the repository")
     244            0 :         ("remove,r", boost::program_options::value<std::vector<std::string> >(&deleted)->multitoken(), "list of new OKS files and directories to be removed from the repository")
     245            0 :         ("permissive-circular-dependencies-between-includes,C", "downgrade severity of detected circular dependencies between includes from errors to warnings")
     246            0 :         ("user,U", boost::program_options::value<std::string>(&user), "user id")
     247            0 :         ("threads-number,t", boost::program_options::value<std::size_t>(&pipeline_size)->default_value(pipeline_size), "number of threads used by validation pipeline")
     248            0 :         ("verbose,v", "Print debug information")
     249            0 :         ("help,h", "Print help message");
     250              : 
     251            0 :       boost::program_options::variables_map vm;
     252            0 :       boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm);
     253              : 
     254            0 :       if (vm.count("help"))
     255              :         {
     256            0 :           std::cout << desc << std::endl;
     257            0 :           return __Success__;
     258              :         }
     259              : 
     260            0 :       if (vm.count("permissive-circular-dependencies-between-includes"))
     261            0 :         circular_dependency_between_includes_is_error = false;
     262              : 
     263            0 :       if (vm.count("verbose"))
     264            0 :         verbose = true;
     265              : 
     266            0 :       boost::program_options::notify(vm);
     267              : 
     268            0 :     }
     269            0 :   catch (std::exception& ex)
     270              :     {
     271            0 :       std::cerr << "Command line parsing errors occurred:\n" << ex.what() << std::endl;
     272            0 :       return __BadCommandLine__;
     273            0 :     }
     274              : 
     275              : 
     276            0 :   try
     277              :     {
     278            0 :       OksKernel kernel;
     279              : 
     280            0 :       kernel.set_allow_duplicated_objects_mode(false);
     281            0 :       kernel.set_test_duplicated_objects_via_inheritance_mode(true);
     282              : 
     283            0 :       if(kernel.get_user_repository_root().empty())
     284              :         {
     285            0 :           std::cerr << "There is no OKS repository set (check TDAQ_DB_REPOSITORY)" << std::endl;
     286              :           return __NoRepository__;
     287              :         }
     288              : 
     289            0 :       std::filesystem::current_path(kernel.get_user_repository_root());
     290              : 
     291            0 :       auto start_usage = std::chrono::steady_clock::now();
     292              : 
     293              :       // directories
     294              : 
     295            0 :       std::set<std::string> directories;
     296              : 
     297              : 
     298              :       // file: explicit includes
     299            0 :       std::map<std::string, std::set<std::string>> file_explicit_includes;
     300              : 
     301            0 :       for (auto& p : std::filesystem::recursive_directory_iterator("."))
     302            0 :         if (std::filesystem::is_directory(p))
     303            0 :           directories.insert(p.path().native().substr(2));
     304            0 :         else if (std::filesystem::is_regular_file(p) && p.path().native().find("./.git") != 0 && p.path().native().find("./admin") != 0 && p.path().native().find("./README.md") != 0)
     305            0 :           kernel.get_includes(p.path().native(), file_explicit_includes[p.path().native().substr(2)], true);
     306              : 
     307            0 :       if (verbose)
     308            0 :         log_timestamp(Debug) << "scan " << file_explicit_includes.size() << " repository files in " << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now()-start_usage).count() / 1000. << " ms\n";
     309              : 
     310              : 
     311            0 :       auto start_usage2 = std::chrono::steady_clock::now();
     312              : 
     313            0 :       std::set<std::string> all_includes;
     314              : 
     315              :       // check every include exists
     316            0 :       for (const auto& f : file_explicit_includes)
     317              :         {
     318            0 :           for (const auto& i : f.second)
     319              :             {
     320            0 :               if(file_explicit_includes.find(i) != file_explicit_includes.end())
     321              :                 {
     322            0 :                   all_includes.insert(i);
     323              :                 }
     324              :               else
     325              :                 {
     326            0 :                   std::cerr << "Cannot find file \"" << i << "\" included by \"" << f.first << "\"" << std::endl;
     327            0 :                   return __NoIncludedFile__;
     328              :                 }
     329              :             }
     330              :         }
     331              : 
     332            0 :       if (verbose)
     333            0 :         log_timestamp(Debug) << "check existence of includes in " << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now()-start_usage2).count() / 1000. << " ms\n";
     334              : 
     335              : 
     336            0 :       auto start_usage3 = std::chrono::steady_clock::now();
     337              : 
     338              :       // file: all includes
     339            0 :       std::map<std::string, std::set<std::string>> file_all_includes;
     340              : 
     341            0 :       for(auto& x : file_explicit_includes)
     342              :         {
     343            0 :           TestCircularDependency cd_fuse(&x.first);
     344            0 :           define_includes(x.first, x.second, file_all_includes, file_explicit_includes, cd_fuse);
     345            0 :         }
     346              : 
     347            0 :       auto stop_usage3 = std::chrono::steady_clock::now();
     348              : 
     349            0 :       if (verbose)
     350            0 :         log_timestamp(Debug) << "calculated inclusion graph in " << std::chrono::duration_cast<std::chrono::microseconds>(stop_usage3-start_usage3).count() / 1000. << " ms\n";
     351              : 
     352            0 :       log_timestamp() << "process " << file_explicit_includes.size() << " repository files and their includes in " << std::chrono::duration_cast<std::chrono::microseconds>(stop_usage3-start_usage).count() / 1000. << " ms" << std::endl;
     353              : 
     354              : 
     355            0 :       if (s_circular_dependency_message.m_count)
     356              :         {
     357            0 :           log_timestamp((circular_dependency_between_includes_is_error == true ? Error : Warning)) << "Detected " << s_circular_dependency_message.m_count << " circular dependencies between includes of the repository files:" << s_circular_dependency_message.m_text.str() << std::endl;
     358              : 
     359            0 :           if (circular_dependency_between_includes_is_error == true)
     360              :             return __IncludesCircularDependencyError__;
     361              :         }
     362              : 
     363            0 :       if (ers::debug_level() >= 2)
     364              :         {
     365            0 :           std::ostringstream text;
     366            0 :           text << "ALL INCLUDES:\n";
     367              : 
     368            0 :           for (const auto& x : file_all_includes)
     369              :             {
     370            0 :               text << "FILE \"" << x.first << "\" has " << x.second.size() << " includes:\n";
     371              : 
     372            0 :               for (const auto& y : x.second)
     373            0 :                 text << " - \"" << y << "\"\n";
     374              :             }
     375              : 
     376            0 :           TLOG_DEBUG(2) << text.str();
     377            0 :         }
     378              : 
     379            0 :       OksPipeline pipeline(pipeline_size);
     380              : 
     381              :       // do not run check for README file
     382            0 :       auto ignore_files = [](std::string& x)
     383              :       {
     384            0 :         static const std::string readme_file("README.md");
     385            0 :         return x != readme_file;
     386              :       };
     387              : 
     388              :       // all modified file paths
     389            0 :       std::set<std::string> modified;
     390            0 :       std::copy_if(created.begin(), created.end(), std::inserter(modified, modified.end()), ignore_files);
     391            0 :       std::copy_if(updated.begin(), updated.end(), std::inserter(modified, modified.end()), ignore_files);
     392              : 
     393              :       // validate independently every created or updated file
     394            0 :       for (const auto& x : modified)
     395            0 :         pipeline.addJob(new OksValidateJob(kernel, x));
     396              : 
     397            0 :       std::copy_if(deleted.begin(), deleted.end(), std::inserter(modified, modified.end()), ignore_files);
     398              : 
     399            0 :       for (const auto& f : file_explicit_includes)
     400            0 :         if (all_includes.find(f.first) == all_includes.end())
     401              :           {
     402            0 :             if (modified.empty() == false)
     403              :               {
     404            0 :                 if (modified.find(f.first) == modified.end())
     405              :                   {
     406            0 :                     const auto& file_includes = file_all_includes[f.first];
     407              : 
     408            0 :                     bool found = false;
     409              : 
     410            0 :                     for (const auto& x : modified)
     411            0 :                       if (file_includes.find(x) != file_includes.end())
     412              :                         {
     413            0 :                           found = true;
     414            0 :                           TLOG_DEBUG(1) <<  "file \"" << f.first << "\" contains modified include \"" << x << '\"';
     415            0 :                           break;
     416              :                         }
     417              : 
     418            0 :                     if(found == false)
     419              :                       {
     420            0 :                         TLOG_DEBUG(1) << "skip file \"" << f.first << '\"';
     421            0 :                         continue;
     422            0 :                       }
     423              :                   }
     424              :                 else
     425              :                   {
     426            0 :                     TLOG_DEBUG(1) <<  "list of modified files contains file \"" << f.first << '\"';
     427              :                   }
     428              :               }
     429              : 
     430            0 :             if (modified.find(f.first) == modified.end())
     431            0 :               pipeline.addJob(new OksValidateJob(kernel, f.first));
     432              :           }
     433              : 
     434            0 :       pipeline.waitForCompletion();
     435              : 
     436            0 :       if (!s_load_error.empty())
     437              :         {
     438            0 :           log_timestamp(Error) << s_load_error << std::endl;
     439            0 :           return __ConsistencyError__;
     440              :         }
     441            0 :     }
     442            0 :   catch (exception & ex)
     443              :     {
     444            0 :       log_timestamp(Error) << "Caught oks exception:\n" << ex << std::endl;
     445            0 :       return __ExceptionCaught__;
     446            0 :     }
     447            0 :   catch (std::exception & e)
     448              :     {
     449            0 :       log_timestamp(Error) << "Caught standard C++ exception:\n" << e.what() << std::endl;
     450            0 :       return __ExceptionCaught__;
     451            0 :     }
     452            0 :   catch (...)
     453              :     {
     454            0 :       log_timestamp(Error) << "Caught unknown exception" << std::endl;
     455            0 :       return __ExceptionCaught__;
     456            0 :     }
     457              : 
     458            0 :   return __Success__;
     459            0 : }
        

Generated by: LCOV version 2.0-1