Imported Upstream version 17.25.4
[platform/upstream/libzypp.git] / zypp / misc / YamlTestcaseHelpers.h
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file  zypp/misc/YamlTestcaseHelpers.h
10  *
11 */
12 #ifndef ZYPP_MISC_YAMLTESTCASEHELPERS_H
13 #define ZYPP_MISC_YAMLTESTCASEHELPERS_H
14
15 #include <zypp/base/LogControl.h>
16 #include "LoadTestcase.h"
17 #include "TestcaseSetupImpl.h"
18
19 #include <yaml-cpp/yaml.h>
20
21 #include <type_traits>
22
23 namespace yamltest::detail {
24
25   bool parseSetup ( const YAML::Node &setup, zypp::misc::testcase::TestcaseSetup &t, std::string *err ) {
26
27     auto &target = t.data();
28     MIL << "Parsing setup node " << std::endl;
29     for ( YAML::const_iterator it = setup.begin(); it != setup.end(); it++ ) {
30
31       const std::string &key = it->first.as<std::string>();
32       const auto &data = it->second;
33
34       MIL << "Found key " << key << std::endl;
35
36       // reads a sequence either from a file or inline, depending on the type of "data"
37       auto readListInlineOrFromFile = [&]( const auto &cb , std::string *err ) -> bool {
38         if ( data.Type() == YAML::NodeType::Sequence ) {
39           int cnt = 0;
40           for ( const auto &node: data ) {
41             if ( !cb( node, err ) ) return false;
42             cnt ++;
43           }
44           MIL << "Loaded " << cnt << " Elements inline" << std::endl;
45         } else {
46           const std::string &fName = data.as<std::string>();
47           MIL << "Trying to load list from file " << fName << std::endl;
48           try {
49             auto doc = YAML::LoadFile( fName );
50             if ( doc.Type() != YAML::NodeType::Sequence ) {
51               if ( err ) *err = "Expected the top node to be a sequence in external file for key: ";
52               return false;
53             }
54
55             int cnt = 0;
56             for ( const auto &node : doc ) {
57               if ( !cb( node, err ) ) return false;
58               cnt ++;
59             }
60             MIL << "Loaded " << cnt << " Elements from file" << std::endl;
61           } catch ( YAML::Exception &e ) {
62             if ( err ) *err = e.what();
63             return false;
64           } catch ( ... )  {
65             if ( err ) *err = zypp::str::Str() << "Unknown error when parsing the file for " << key;
66             return false;
67           }
68         }
69         return true;
70       };
71
72       if ( key == "resolverFlags" ) {
73 #define if_SolverFlag( N ) if ( data[#N] ) { target.N = data[#N].as<bool>(); }
74         if_SolverFlag( ignorealreadyrecommended ) if ( data["ignorealready"] )       { target.ignorealreadyrecommended = data["ignorealready"].as<bool>(); }
75         if_SolverFlag( onlyRequires )        if ( data["ignorerecommended"] ) { target.onlyRequires = data["ignorerecommended"].as<bool>(); }
76         if_SolverFlag( forceResolve )
77
78         if_SolverFlag( cleandepsOnRemove )
79
80         if_SolverFlag( allowDowngrade )
81         if_SolverFlag( allowNameChange )
82         if_SolverFlag( allowArchChange )
83         if_SolverFlag( allowVendorChange )
84
85         if_SolverFlag( dupAllowDowngrade )
86         if_SolverFlag( dupAllowNameChange )
87         if_SolverFlag( dupAllowArchChange )
88         if_SolverFlag( dupAllowVendorChange )
89 #undef if_SolverFlag
90         if ( data["focus"] ) {
91           target.resolverFocus = zypp::resolverFocusFromString( data["focus"].as<std::string>() );
92         }
93       } else if ( key == ("system") ) {
94         target.systemRepo = zypp::misc::testcase::RepoDataImpl {
95           zypp::misc::testcase::TestcaseRepoType::Testtags,
96           "@System",
97           99,
98           data["file"].as<std::string>()
99         };
100       }
101       else if ( key == ("hardwareInfo") ) {
102         target.hardwareInfoFile = data.as<std::string>();
103       }
104       else if ( key == ("modalias") ) {
105         bool success = readListInlineOrFromFile( [&target]( const YAML::Node &dataNode, auto ){
106           target.modaliasList.push_back( dataNode.as<std::string>() );
107           return true;
108         }, err );
109         if ( !success ) return false;
110       }
111       else if ( key == ("multiversion") ) {
112         bool success = readListInlineOrFromFile( [&target]( const YAML::Node &dataNode, auto ){
113           target.multiversionSpec.insert( dataNode.as<std::string>() );
114           return true;
115         }, err );
116         if ( !success ) return false;
117       }
118       else if (key ==  ("channels")) {
119         bool success = readListInlineOrFromFile( [&target]( const YAML::Node &dataNode, auto ){
120           std::string name = dataNode["alias"].as<std::string>();
121           std::string file = dataNode["file"].as<std::string>();
122           std::string type = dataNode["type"].as<std::string>();
123
124           unsigned prio = 99;
125           if ( dataNode["priority"] )
126             prio = dataNode["priority"].as<unsigned>();
127
128           target.repos.push_back( zypp::misc::testcase::RepoDataImpl{
129             zypp::misc::testcase::TestcaseRepoType::Testtags,
130             name,
131             prio,
132             file
133           });
134           return true;
135         }, err );
136         if ( !success ) return false;
137       }
138       else if ( key == ("sources") )
139       {
140         bool success = readListInlineOrFromFile( [&target]( const YAML::Node &dataNode, auto ){
141           std::string url   = dataNode["url"].as<std::string>();
142           std::string alias = dataNode["name"].as<std::string>();
143           target.repos.push_back( zypp::misc::testcase::RepoDataImpl{
144             zypp::misc::testcase::TestcaseRepoType::Url,
145             alias,
146             99,
147             url
148           });
149           return true;
150         }, err );
151         if ( !success ) return false;
152       }
153       else if ( key == ("force-install") )
154       {
155         bool success = readListInlineOrFromFile( [&target]( const YAML::Node &dataNode, auto ){
156           target.forceInstallTasks.push_back( zypp::misc::testcase::ForceInstallImpl{
157             dataNode["channel"].as<std::string>(),
158             dataNode["package"].as<std::string>(),
159             dataNode["kind"].as<std::string>()
160           });
161           return true;
162         }, err );
163         if ( !success ) return false;
164       }
165       else if ( key == ("mediaid") )
166       {
167         target.show_mediaid = data.as<bool>();
168       }
169       else if ( key == ("arch") ) {
170         std::string architecture = data.as<std::string>();
171         if ( architecture.empty() ) {
172           if (err) *err = zypp::str::Str() << "Property 'arch' in setup can not be empty." << std::endl;
173           return false;
174         }
175         else {
176           MIL << "Setting architecture to '" << architecture << "'" << std::endl;
177           target.architecture = zypp::Arch( architecture );
178         }
179       }
180       else if ( key == ("locales") )
181       {
182         bool success = readListInlineOrFromFile( [&target]( const YAML::Node &dataNode, std::string *err ){
183           zypp::Locale loc( dataNode["name"].as<std::string>() );
184           std::string fate = dataNode["fate"].as<std::string>();
185           if ( !loc ) {
186             if (err) *err = zypp::str::Str() << "Bad or missing name in locale..." << std::endl;
187             return false;
188           }
189           else if ( fate == "added" ) {
190             target.localesTracker.added().insert( loc );
191           }
192           else if ( fate == "removed" ) {
193             target.localesTracker.removed().insert( loc );
194           }
195           else {
196             target.localesTracker.current().insert( loc );
197           }
198           return true;
199         }, err );
200         if ( !success ) return false;
201       }
202       else if ( key == ("vendors") )
203       {
204         bool success = readListInlineOrFromFile( [&target]( const YAML::Node & dataNode, std::string * err ) {
205           std::vector<std::string> vlist;
206           for ( const auto & node : dataNode )
207             vlist.push_back( node.as<std::string>() );
208           if ( ! vlist.empty() )
209             target.vendorLists.push_back( std::move(vlist) );
210           return true;
211         }, err );
212         if ( !success ) return false;
213       }
214       else if ( key == ("autoinst") ) {
215         bool success = readListInlineOrFromFile( [&]( const YAML::Node &dataNode, auto ){
216           target.autoinstalled.push( zypp::IdString( dataNode.as<std::string>() ).id() );
217           return true;
218         }, err );
219         if ( !success ) return false;
220       }
221       else if ( key == ("systemCheck") ) {
222         target.systemCheck = data.as<std::string>();
223       }
224       else if ( key == ("setlicencebit") ) {
225         target.set_licence = data.as<bool>();
226       }
227       else {
228         ERR << "Ignoring unrecognized tag '" << key << "' in setup" << std::endl;
229       }
230     }
231     return true;
232   }
233
234   template <typename T>
235   bool parseJobs  ( const YAML::Node &trial, std::vector<T> &target, std::string *err );
236
237   template <typename T>
238   bool parseSingleJob ( const YAML::Node &jobNode, std::vector<T> &target, std::string *err ) {
239
240     constexpr bool isSubNode = std::is_same_v<T, std::shared_ptr<zypp::misc::testcase::TestcaseTrial::Node>>;
241     if ( jobNode["include"] ) {
242       //handle include
243       const auto &fName = jobNode["include"].as<std::string>();
244       MIL << "Including file " << fName << std::endl;
245       try {
246         auto doc = YAML::LoadFile( fName );
247         if ( !parseJobs( doc, target, err ) )
248           return false;
249         MIL << "Including file " << fName << "was successfull" << std::endl;
250       } catch ( YAML::Exception &e ) {
251         if ( err ) *err = e.what();
252         return false;
253       } catch ( ... )  {
254         if ( err ) *err = zypp::str::Str() << "Unknown error when parsing the file: " << fName;
255         return false;
256       }
257       return true;
258     }
259
260     zypp::misc::testcase::TestcaseTrial::Node n;
261     if ( !jobNode["job"] ) {
262       if ( err ) {
263         auto errStr = zypp::str::Str();
264         const auto &mark = jobNode.Mark();
265         errStr << "'job' key missing from trial node.";
266         if ( !mark.is_null() ) {
267           errStr << " Line: " << mark.line << " Col: " << mark.column << " pos: " << mark.pos;
268         }
269         *err = errStr;
270       }
271       return false;
272     }
273
274     for ( const auto &elem : jobNode ) {
275       const std::string &key = elem.first.as<std::string>();
276       const auto &data = elem.second;
277       if ( key == "job" ) {
278         n.name() = data.as<std::string>();
279       } else if ( key == "__content") {
280         n.value() = data.as<std::string>();
281       } else {
282         if( data.IsScalar() ) {
283           n.properties().insert( { key, data.as<std::string>() } );
284         } if ( data.IsSequence() ) {
285           // if the type of a data field is a sequence, we treat all the elements in there
286           // as sub elements. Just like in XML you can have sub nodes its the same here
287           // the key name is ignored in those cases and can be chosen freely
288           if ( !parseJobs( data, n.children(), err ) )
289             return false;
290         } else if ( data.IsMap() ) {
291           // if the type of a data field is a map, we build a child node from it.
292           // Just like with sequence but a single field.
293           // The key name is ignored in those cases and can be chosen freely
294           if ( !parseSingleJob( data, n.children(), err) )
295             return false;
296         } else {
297           ERR << "Ignoring field " << key << " with unsupported type." << std::endl;
298         }
299       }
300     }
301     if constexpr ( isSubNode ) {
302       target.push_back( std::make_shared<zypp::misc::testcase::TestcaseTrial::Node>( std::move(n) ) );
303     } else {
304       target.push_back( std::move(n) );
305     }
306     return true;
307   }
308
309   template <typename T>
310   bool parseJobs  ( const YAML::Node &trial, std::vector<T> &target, std::string *err ) {
311     for ( const auto &jobNode : trial ) {
312       if ( !parseSingleJob( jobNode, target, err ) )
313         return false;
314     }
315     return true;
316   }
317
318   bool parseTrial ( const YAML::Node &trial, zypp::misc::testcase::TestcaseTrial &target, std::string *err ) {
319     MIL << "Parsing trials." << std::endl;
320     return parseJobs( trial, target.nodes(), err );
321   }
322 }
323
324 #endif // ZYPP_MISC_YAMLTESTCASEHELPERS_H