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