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 % 232 0
Test Date: 2026-10-11 15:05:20 Functions: 0.0 % 14 0

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

Generated by: LCOV version 2.0-1