LCOV - code coverage report
Current view: top level - src/base - CapabilityRegistry.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 220 235 93.6 %
Date: 2026-05-29 20:35:17 Functions: 23 23 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "CapabilityRegistry.h"
      11             : 
      12             : #include "CapabilityException.h"
      13             : #include "MooseStringUtils.h"
      14             : 
      15             : #include "peglib.h"
      16             : 
      17             : #include <regex>
      18             : #include <set>
      19             : #include <utility>
      20             : 
      21             : namespace Moose::internal
      22             : {
      23             : 
      24             : const std::set<std::string, std::less<>> CapabilityRegistry::augmented_capability_names{
      25             :     // TestHarness.getCapabilities()
      26             :     "hpc",
      27             :     "machine",
      28             :     "library_mode",
      29             :     // TestHarness.testers.RunApp.getAugmentedCapabilities()
      30             :     "mpi_procs",
      31             :     "num_threads"};
      32             : 
      33             : Capability &
      34     3185194 : CapabilityRegistry::add(const std::string_view name,
      35             :                         const Capability::Value & value,
      36             :                         const std::string_view doc)
      37             : {
      38     3185194 :   auto it_pair = _registry.lower_bound(name);
      39     3185194 :   if (it_pair != _registry.end() && it_pair->first == name)
      40             :   {
      41       84694 :     auto & capability = it_pair->second;
      42       84694 :     if (capability.getValue() != value || capability.getDoc() != doc)
      43          28 :       throw CapabilityException("Capability '" + std::string(name) +
      44          42 :                                 "' already exists and is not equal");
      45       84680 :     return capability;
      46             :   }
      47             : 
      48             :   return _registry
      49     3100500 :       .emplace_hint(it_pair,
      50             :                     std::piecewise_construct,
      51     3100500 :                     std::forward_as_tuple(name),
      52     6201000 :                     std::forward_as_tuple(name, value, doc))
      53     3100500 :       ->second;
      54             : }
      55             : 
      56             : const Capability *
      57        9751 : CapabilityRegistry::query(std::string capability) const
      58             : {
      59        9751 :   capability = MooseUtils::toLower(capability);
      60        9751 :   if (const auto it = _registry.find(capability); it != _registry.end())
      61        9654 :     return &it->second;
      62          97 :   return nullptr;
      63             : }
      64             : 
      65             : const Capability &
      66          50 : CapabilityRegistry::get(const std::string & capability) const
      67             : {
      68          50 :   if (const auto capability_ptr = query(capability))
      69          46 :     return *capability_ptr;
      70           4 :   throw CapabilityException("Capability '" + capability + "' not registered");
      71             : }
      72             : 
      73             : [[noreturn]] void
      74          47 : checkException(const peg::SemanticValues & vs,
      75             :                const std::string & message,
      76             :                const std::optional<Capability> capability = {})
      77             : {
      78          47 :   std::string msg = "Capability statement '" + vs.token_to_string() + "': ";
      79          47 :   if (capability)
      80          16 :     msg += "capability '" + capability->toString() + "' ";
      81          47 :   msg += message;
      82          47 :   throw CapabilityException(msg);
      83          47 : }
      84             : 
      85             : CapabilityRegistry::CheckResult
      86        8533 : CapabilityRegistry::check(std::string requirements,
      87             :                           const CapabilityRegistry::CheckOptions &
      88             :                               options /* = CapabilityRegistry::CheckOptions() */) const
      89             : {
      90             :   using namespace peg;
      91             : 
      92             :   // unquote
      93             :   while (true)
      94             :   {
      95        8695 :     const auto len = requirements.length();
      96       17224 :     if (len >= 2 && ((requirements[0] == '\'' && requirements[len - 1] == '\'') ||
      97        8529 :                      (requirements[0] == '"' && requirements[len - 1] == '"')))
      98         162 :       requirements = requirements.substr(1, len - 2);
      99             :     else
     100        8533 :       break;
     101         162 :   }
     102             : 
     103        8533 :   CheckResult result;
     104        8533 :   result.state = CheckState::CERTAIN_FAIL;
     105             : 
     106        8533 :   if (requirements.length() == 0)
     107             :   {
     108           4 :     result.state = CheckState::CERTAIN_PASS;
     109           4 :     return result;
     110             :   }
     111             : 
     112             :   static parser parser(R"(
     113             :     Expression    <-  _ Bool _ LogicOperator _ Expression / Bool _
     114             :     Bool          <-  Comparison / '!' Bool / '!' Identifier / Identifier / '(' _ Expression _ ')'
     115             :     Comparison    <-  Identifier _ Operator _ Version / Identifier _ Operator _ String
     116             :     String        <-  [a-zA-Z0-9_-]+
     117             :     Identifier    <-  [a-zA-Z][a-zA-Z0-9_]*
     118             :     Operator      <-  [<>=!]+
     119             :     LogicOperator <-  [&|]
     120             :     Version       <-  Number '.' Version  / Number
     121             :     Number        <-  [0-9]+
     122             :     ~_            <-  [ \t]*
     123        8529 :   )");
     124             : 
     125        8529 :   if (!static_cast<bool>(parser))
     126           0 :     throw CapabilityException("Capabilities parser build failure.");
     127             : 
     128             :   // Keep track of unknown capabilities in the event that
     129             :   // the check must be certain
     130        8529 :   std::set<std::string> unknown_capabilities;
     131          85 :   const auto add_unknown_capability = [&unknown_capabilities](const auto & name)
     132          85 :   { unknown_capabilities.insert(MooseUtils::toLower(name)); };
     133             : 
     134             :   // Make sure that the capabilities to ignore are valid capabilities
     135        8597 :   for (const auto & name : options.ignore_capabilities)
     136          72 :     if (!_registry.count(name))
     137           4 :       throw CapabilityException("Capability to ignore '" + name + "' is not known");
     138             : 
     139       12669 :   parser["Number"] = [](const SemanticValues & vs) { return vs.token_to_number<int>(); };
     140             : 
     141        8525 :   parser["Version"] = [](const SemanticValues & vs)
     142             :   {
     143        4144 :     switch (vs.choice())
     144             :     {
     145        2271 :       case 0: // Number '.' Version
     146             :       {
     147        4542 :         std::vector<int> ret{std::any_cast<int>(vs[0])};
     148        2271 :         const auto & vs1 = std::any_cast<std::vector<int>>(vs[1]);
     149        2271 :         ret.insert(ret.end(), vs1.begin(), vs1.end());
     150        2271 :         return ret;
     151        2271 :       }
     152             : 
     153        1873 :       case 1: // Number
     154        5619 :         return std::vector<int>{std::any_cast<int>(vs[0])};
     155             :     }
     156             : 
     157           0 :     checkException(vs, "unknown number match.");
     158             :   };
     159             : 
     160             :   enum LogicOperator
     161             :   {
     162             :     OP_AND,
     163             :     OP_OR
     164             :   };
     165             : 
     166        8525 :   parser["LogicOperator"] = [](const SemanticValues & vs)
     167             :   {
     168        1138 :     const auto op = vs.token();
     169        1138 :     if (op == "&")
     170        1085 :       return OP_AND;
     171          53 :     if (op == "|")
     172          53 :       return OP_OR;
     173           0 :     checkException(vs, "unknown logic operator.");
     174             :   };
     175             : 
     176             :   enum Operator
     177             :   {
     178             :     OP_LESS_EQ,
     179             :     OP_GREATER_EQ,
     180             :     OP_LESS,
     181             :     OP_GREATER,
     182             :     OP_NOT_EQ,
     183             :     OP_EQ
     184             :   };
     185             : 
     186        8525 :   parser["Operator"] = [](const SemanticValues & vs)
     187             :   {
     188        4051 :     const auto op = vs.token();
     189        4051 :     if (op == "<=")
     190          74 :       return OP_LESS_EQ;
     191        3977 :     if (op == ">=")
     192        1117 :       return OP_GREATER_EQ;
     193        2860 :     if (op == "<")
     194         169 :       return OP_LESS;
     195        2691 :     if (op == ">")
     196         124 :       return OP_GREATER;
     197        2567 :     if (op == "!=")
     198         390 :       return OP_NOT_EQ;
     199        2177 :     if (op == "=" || op == "==")
     200        2171 :       return OP_EQ;
     201          24 :     checkException(vs, "unknown operator.");
     202             :   };
     203             : 
     204       10687 :   parser["String"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
     205       18188 :   parser["Identifier"] = [](const SemanticValues & vs) { return vs.token_to_string(); };
     206             : 
     207        8525 :   parser["Comparison"] =
     208        4035 :       [this, &add_unknown_capability, &options, &result](const SemanticValues & vs)
     209             :   {
     210        4035 :     const auto left = std::any_cast<std::string>(vs[0]);
     211        4035 :     const auto op = std::any_cast<Operator>(vs[1]);
     212             : 
     213             :     // check existence
     214        4035 :     const auto capability_ptr = query(left);
     215        4035 :     if (!capability_ptr)
     216             :     {
     217             :       // return an unknown if the capability does not exist, this is important as it
     218             :       // stays unknown upon negation
     219          37 :       add_unknown_capability(left);
     220          37 :       return CheckState::UNKNOWN;
     221             :     }
     222             : 
     223             :     // capability is registered by the app
     224        3998 :     const auto & capability = *capability_ptr;
     225        3998 :     const auto & name = capability.getName();
     226             : 
     227             :     // register capability as seen
     228        3998 :     result.capability_names.insert(name);
     229             : 
     230             :     // whether or not the capability is ignored
     231        3975 :     const auto is_ignored = [&name, &options]() { return options.ignore_capabilities.count(name); };
     232             : 
     233             :     // explicitly false causes any comparison to fail unless ignored
     234        3998 :     if (const auto bool_ptr = capability.queryBoolValue(); (bool_ptr && !(*bool_ptr)))
     235           8 :       return is_ignored() ? CheckState::IGNORE : CheckState::CERTAIN_FAIL;
     236             : 
     237             :     // comparator
     238        3967 :     auto comp = [&is_ignored](const int i, const auto & a, const auto & b)
     239             :     {
     240             :       // early exit for ignored capabilities
     241        3967 :       if (is_ignored())
     242          10 :         return CheckState::IGNORE;
     243             : 
     244             :       // do the comparison
     245        7914 :       const auto do_comp = [&i, &a, &b]()
     246             :       {
     247        3957 :         switch (i)
     248             :         {
     249          72 :           case OP_LESS_EQ:
     250          72 :             return a <= b;
     251        1117 :           case OP_GREATER_EQ:
     252        1117 :             return a >= b;
     253         156 :           case OP_LESS:
     254         156 :             return a < b;
     255         104 :           case OP_GREATER:
     256         104 :             return a > b;
     257         386 :           case OP_NOT_EQ:
     258         386 :             return a != b;
     259        2122 :           case OP_EQ:
     260        2122 :             return a == b;
     261             :         }
     262           0 :         return false;
     263             :       };
     264        3957 :       return do_comp() ? CheckState::CERTAIN_PASS : CheckState::CERTAIN_FAIL;
     265        3990 :     };
     266             : 
     267             :     // version comparison
     268        3990 :     std::vector<int> app_value_version;
     269             : 
     270        3990 :     switch (vs.choice())
     271             :     {
     272        1848 :       case 0: // Identifier _ Operator _ Version
     273             :       {
     274             :         // int comparison
     275        1848 :         const auto right = std::any_cast<std::vector<int>>(vs[2]);
     276        1848 :         if (const auto int_ptr = capability.queryIntValue())
     277             :         {
     278         504 :           if (right.size() != 1)
     279           8 :             checkException(vs, "cannot be compared to a version.", capability);
     280             : 
     281         502 :           return comp(op, *int_ptr, right[0]);
     282             :         }
     283             : 
     284        1344 :         const auto string_ptr = capability.queryStringValue();
     285        1344 :         if (!string_ptr)
     286          16 :           checkException(vs,
     287          12 :                          "cannot be compared to a " +
     288          24 :                              std::string(right.size() == 1 ? "number" : "version number") + ".",
     289             :                          capability);
     290             : 
     291        2680 :         if (!MooseUtils::tokenizeAndConvert(*string_ptr, app_value_version, "."))
     292          16 :           checkException(vs, "cannot be compared to a version.", capability);
     293             : 
     294             :         // compare versions
     295        1336 :         return comp(op, app_value_version, right);
     296        1848 :       }
     297             : 
     298        2142 :       case 1: // Identifier _ Operator _ String
     299             :       {
     300             :         // here we would check for valid options and throw if not valid
     301        2142 :         const auto right = MooseUtils::toLower(std::any_cast<std::string>(vs[2]));
     302             :         // the capability value has to be a string
     303        2142 :         const auto string_ptr = capability.queryStringValue();
     304        2142 :         if (!string_ptr)
     305          16 :           checkException(vs, "cannot be compared to a string.", capability);
     306             : 
     307             :         // If this capability has an enumeration, make sure a valid
     308             :         // choice is used
     309        2138 :         if (!capability.hasEnumeration(right))
     310          28 :           checkException(vs,
     311          42 :                          "'" + right + "' invalid for capability '" + left +
     312          35 :                              "'; valid values: " + capability.enumerationToString());
     313             : 
     314             :         // Capability is a version
     315        4262 :         if (MooseUtils::tokenizeAndConvert(*string_ptr, app_value_version, "."))
     316           8 :           checkException(vs, "cannot be compared to a string.", capability);
     317             : 
     318        2129 :         return comp(op, *string_ptr, right);
     319        2142 :       }
     320             :     }
     321             : 
     322           0 :     checkException(vs, "failed comparison.", capability);
     323        4058 :   };
     324             : 
     325        8525 :   parser["Bool"] = [this, &add_unknown_capability, &options, &result](const SemanticValues & vs)
     326             :   {
     327       10704 :     switch (vs.choice())
     328             :     {
     329        4452 :       case 0: // Comparison
     330             :       case 4: // '(' _ Expression _ ')'
     331        4452 :         return std::any_cast<CheckState>(vs[0]);
     332             : 
     333         630 :       case 1: // '!' Bool
     334         630 :         switch (std::any_cast<CheckState>(vs[0]))
     335             :         {
     336         381 :           case CheckState::CERTAIN_FAIL:
     337         381 :             return CheckState::CERTAIN_PASS;
     338         203 :           case CheckState::CERTAIN_PASS:
     339         203 :             return CheckState::CERTAIN_FAIL;
     340          21 :           case CheckState::POSSIBLE_FAIL:
     341          21 :             return CheckState::POSSIBLE_PASS;
     342           0 :           case CheckState::POSSIBLE_PASS:
     343           0 :             return CheckState::POSSIBLE_FAIL;
     344          22 :           case CheckState::IGNORE:
     345          22 :             return CheckState::IGNORE;
     346           3 :           default:
     347           3 :             return CheckState::UNKNOWN;
     348             :         }
     349             : 
     350        5622 :       case 2: // '!' Identifier
     351             :       case 3: // Identifier
     352             :       {
     353        5622 :         const bool negated = vs.choice() == 2;
     354        5622 :         const auto identifier = std::any_cast<std::string>(vs[0]);
     355        5622 :         if (const auto capability_ptr = query(identifier))
     356             :         {
     357        5574 :           const auto & capability = *capability_ptr;
     358        5574 :           const auto & name = capability.getName();
     359             : 
     360             :           // explicit; cannot be a bool expression
     361        5574 :           if (capability.getExplicit())
     362             :           {
     363          36 :             std::string message = "capability '" + name +
     364          18 :                                   "' requires a value and cannot be used in a boolean expression";
     365          18 :             if (capability.queryEnumeration())
     366           4 :               message += "; valid values: " + capability.enumerationToString();
     367          36 :             checkException(vs, message);
     368          18 :           }
     369             : 
     370             :           // mark as used
     371        5556 :           result.capability_names.insert(name);
     372             :           // is ignored
     373        5556 :           if (options.ignore_capabilities.count(name))
     374          48 :             return CheckState::IGNORE;
     375             : 
     376             :           // helper for negating a passing value if needed
     377        5508 :           const auto bool_to_pass = [&negated](const bool val)
     378        5508 :           { return (val ^ negated) ? CheckState::CERTAIN_FAIL : CheckState::CERTAIN_PASS; };
     379             :           // has a boolean value, so use it
     380        5508 :           if (const auto bool_ptr = capability.queryBoolValue())
     381         604 :             return bool_to_pass(!*bool_ptr);
     382             :           // not ignored and doesn't have a boolean value
     383        4904 :           return bool_to_pass(false);
     384             :         }
     385             : 
     386          48 :         add_unknown_capability(identifier);
     387          48 :         return negated ? CheckState::POSSIBLE_PASS : CheckState::POSSIBLE_FAIL;
     388        5622 :       }
     389             : 
     390           0 :       default:
     391           0 :         throw CapabilityException("Unknown choice in Bool non-terminal");
     392             :     }
     393             :   };
     394             : 
     395        8525 :   parser["Expression"] = [](const SemanticValues & vs)
     396             :   {
     397       10056 :     switch (vs.choice())
     398             :     {
     399        1138 :       case 0: // Bool _ LogicOperator _ Expression
     400             :       {
     401        1138 :         const auto left = std::any_cast<CheckState>(vs[0]);
     402        1138 :         const auto right = std::any_cast<CheckState>(vs[2]);
     403        1138 :         const auto op = std::any_cast<LogicOperator>(vs[1]);
     404             : 
     405        1138 :         switch (op)
     406             :         {
     407        1085 :           case OP_AND:
     408        1085 :             if (left == CheckState::IGNORE)
     409           6 :               return right;
     410        1079 :             if (right == CheckState::IGNORE)
     411           2 :               return left;
     412        5099 :             for (const auto state : {CheckState::CERTAIN_FAIL,
     413             :                                      CheckState::POSSIBLE_FAIL,
     414             :                                      CheckState::UNKNOWN,
     415             :                                      CheckState::POSSIBLE_PASS,
     416        6176 :                                      CheckState::CERTAIN_PASS})
     417        5099 :               if (left == state || right == state)
     418        1077 :                 return state;
     419           0 :             throw CapabilityException("Conjunction failure");
     420             : 
     421          53 :           case OP_OR:
     422          53 :             if (left == CheckState::IGNORE || right == CheckState::IGNORE)
     423           8 :               return CheckState::IGNORE;
     424         143 :             for (const auto state : {CheckState::CERTAIN_PASS,
     425             :                                      CheckState::POSSIBLE_PASS,
     426             :                                      CheckState::UNKNOWN,
     427             :                                      CheckState::POSSIBLE_FAIL,
     428         188 :                                      CheckState::CERTAIN_FAIL})
     429         143 :               if (left == state || right == state)
     430          45 :                 return state;
     431           0 :             throw CapabilityException("Conjunction failure");
     432             : 
     433           0 :           default:
     434           0 :             throw CapabilityException("Unknown logic operator");
     435             :         }
     436             :       }
     437             : 
     438        8918 :       case 1: // Bool
     439        8918 :         return std::any_cast<CheckState>(vs[0]);
     440             : 
     441           0 :       default:
     442           0 :         throw CapabilityException("Unknown choice in Expression non-terminal");
     443             :     }
     444             :   };
     445             : 
     446             :   // (4) Parse
     447        8525 :   parser.enable_packrat_parsing(); // Enable packrat parsing.
     448             : 
     449        8525 :   if (!parser.parse(requirements, result.state))
     450          16 :     throw CapabilityException("Unable to parse requested capabilities '", requirements, "'.");
     451             : 
     452             :   // If certain and unknown capabilities were found, throw accordingly
     453        8462 :   if (options.certain && unknown_capabilities.size())
     454          12 :     throw UnknownCapabilitiesException({unknown_capabilities.begin(), unknown_capabilities.end()});
     455             : 
     456             :   // Consider an ignored state to be a pass
     457        8458 :   if (result.state == CheckState::IGNORE)
     458          46 :     result.state = CheckState::CERTAIN_PASS;
     459             : 
     460        8458 :   return result;
     461        8600 : }
     462             : } // namespace CapabilityUtils

Generated by: LCOV version 1.14