Imported Upstream version 17.0.0
[platform/upstream/libzypp.git] / zypp / solver / detail / Testcase.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/solver/detail/Testcase.cc
10  *
11 */
12 #include <iostream>
13 #include <fstream>
14 #include <sstream>
15 #include <streambuf>
16
17 #define ZYPP_USE_RESOLVER_INTERNALS
18
19 #include "zypp/solver/detail/Testcase.h"
20 #include "zypp/base/Logger.h"
21 #include "zypp/base/LogControl.h"
22 #include "zypp/base/GzStream.h"
23 #include "zypp/base/String.h"
24 #include "zypp/base/PtrTypes.h"
25 #include "zypp/base/NonCopyable.h"
26 #include "zypp/base/ReferenceCounted.h"
27
28 #include "zypp/parser/xml/XmlEscape.h"
29
30 #include "zypp/ZConfig.h"
31 #include "zypp/PathInfo.h"
32 #include "zypp/ResPool.h"
33 #include "zypp/Repository.h"
34 #include "zypp/target/modalias/Modalias.h"
35
36 #include "zypp/sat/detail/PoolImpl.h"
37 #include "zypp/solver/detail/Resolver.h"
38 #include "zypp/solver/detail/SystemCheck.h"
39
40 /////////////////////////////////////////////////////////////////////////
41 namespace zypp
42 { ///////////////////////////////////////////////////////////////////////
43   ///////////////////////////////////////////////////////////////////////
44   namespace solver
45   { /////////////////////////////////////////////////////////////////////
46     /////////////////////////////////////////////////////////////////////
47     namespace detail
48     { ///////////////////////////////////////////////////////////////////
49
50 #define TAB "\t"
51 #define TAB2 "\t\t"
52
53 using namespace std;
54 using namespace zypp::str;
55
56 //---------------------------------------------------------------------------
57
58 inline std::string xml_escape( const std::string &text )
59 {
60   return zypp::xml::escape(text);
61 }
62
63 inline std::string xml_tag_enclose( const std::string &text, const std::string &tag, bool escape = false )
64 {
65   string result;
66   result += "<" + tag + ">";
67
68   if ( escape)
69    result += xml_escape(text);
70   else
71    result += text;
72
73   result += "</" + tag + ">";
74   return result;
75 }
76
77 template<class T>
78 std::string helixXML( const T &obj ); //undefined
79
80 template<>
81 std::string helixXML( const Edition &edition )
82 {
83     stringstream str;
84     str << xml_tag_enclose(edition.version(), "version");
85     if (!edition.release().empty())
86         str << xml_tag_enclose(edition.release(), "release");
87     if (edition.epoch() != Edition::noepoch)
88         str << xml_tag_enclose(numstring(edition.epoch()), "epoch");
89     return str.str();
90 }
91
92 template<>
93 std::string helixXML( const Arch &arch )
94 {
95     stringstream str;
96     str << xml_tag_enclose(arch.asString(), "arch");
97     return str.str();
98 }
99
100 template<>
101 std::string helixXML( const Capability &cap )
102 {
103     stringstream str;
104     CapDetail detail = cap.detail();
105     if (detail.isSimple()) {
106         if (detail.isVersioned()) {
107             str << "<dep name='" << xml_escape(detail.name().asString()) << "'"
108                 << " op='" << xml_escape(detail.op().asString()) << "'"
109                 << " version='" <<  xml_escape(detail.ed().version()) << "'";
110             if (!detail.ed().release().empty())
111                 str << " release='" << xml_escape(detail.ed().release()) << "'";
112             if (detail.ed().epoch() != Edition::noepoch)
113                 str << " epoch='" << xml_escape(numstring(detail.ed().epoch())) << "'";
114             str << " />" << endl;
115         } else {
116             str << "<dep name='" << xml_escape(cap.asString()) << "' />" << endl;
117         }
118     } else if (detail.isExpression()) {
119         if (detail.capRel() == CapDetail::CAP_AND
120             && detail.lhs().detail().isNamed()
121             && detail.rhs().detail().isNamed()) {
122             // packageand dependency
123             str << "<dep name='packageand("
124                 << IdString(detail.lhs().id()) << ":"
125                 << IdString(detail.rhs().id()) << ")' />" << endl;
126         } else if (detail.capRel() == CapDetail::CAP_NAMESPACE
127             && detail.lhs().id() == NAMESPACE_OTHERPROVIDERS) {
128             str << "<dep name='otherproviders("
129                 << IdString(detail.rhs().id()) << ")' />" << endl;
130         } else {
131             // modalias ?
132             IdString packageName;
133             if (detail.capRel() == CapDetail::CAP_AND) {
134                 packageName = IdString(detail.lhs().id());
135                 detail = detail.rhs().detail();
136             }
137             if (detail.capRel() == CapDetail::CAP_NAMESPACE
138                 && detail.lhs().id() == NAMESPACE_MODALIAS) {
139                 str << "<dep name='modalias(";
140                 if (!packageName.empty())
141                     str << packageName << ":";
142                 str << IdString(detail.rhs().id()) << ")' />" << endl;
143             } else {
144                 str << "<!--- ignoring '" << xml_escape(cap.asString()) << "' -->" << endl;
145             }
146         }
147     } else {
148         str << "<!--- ignoring '" << xml_escape(cap.asString()) << "' -->" << endl;
149     }
150
151     return str.str();
152 }
153
154 template<>
155 std::string helixXML( const Capabilities &caps )
156 {
157     stringstream str;
158     Capabilities::const_iterator it = caps.begin();
159     str << endl;
160     for ( ; it != caps.end(); ++it)
161     {
162         str << TAB2 << helixXML((*it));
163     }
164     str << TAB;
165     return str.str();
166 }
167
168 template<>
169 std::string helixXML( const CapabilitySet &caps )
170 {
171     stringstream str;
172     CapabilitySet::const_iterator it = caps.begin();
173     str << endl;
174     for ( ; it != caps.end(); ++it)
175     {
176         str << TAB2 << helixXML((*it));
177     }
178     str << TAB;
179     return str.str();
180 }
181
182 inline string helixXML( const PoolItem & obj, Dep deptag_r )
183 {
184   stringstream out;
185   Capabilities caps( obj[deptag_r] );
186   if ( ! caps.empty() )
187     out << TAB << xml_tag_enclose(helixXML(caps), deptag_r.asString()) << endl;
188   return out.str();
189 }
190
191 std::string helixXML( const PoolItem & item )
192 {
193   stringstream str;
194   str << "<" << item.kind() << ">" << endl;
195   str << TAB << xml_tag_enclose( item.name(), "name", true ) << endl;
196   str << TAB << xml_tag_enclose( item.vendor().asString(), "vendor", true ) << endl;
197   str << TAB << xml_tag_enclose( item.buildtime().asSeconds(), "buildtime", true ) << endl;
198   if ( isKind<Package>( item ) ) {
199       str << TAB << "<history>" << endl << TAB << "<update>" << endl;
200       str << TAB2 << helixXML( item.arch() ) << endl;
201       str << TAB2 << helixXML( item.edition() ) << endl;
202       str << TAB << "</update>" << endl << TAB << "</history>" << endl;
203   } else {
204       str << TAB << helixXML( item.arch() ) << endl;
205       str << TAB << helixXML( item.edition() ) << endl;
206   }
207   str << helixXML( item, Dep::PROVIDES );
208   str << helixXML( item, Dep::PREREQUIRES );
209   str << helixXML( item, Dep::CONFLICTS );
210   str << helixXML( item, Dep::OBSOLETES );
211   str << helixXML( item, Dep::REQUIRES );
212   str << helixXML( item, Dep::RECOMMENDS );
213   str << helixXML( item, Dep::ENHANCES );
214   str << helixXML( item, Dep::SUPPLEMENTS );
215   str << helixXML( item, Dep::SUGGESTS );
216
217   str << "</" << item.kind() << ">" << endl;
218   return str.str();
219 }
220
221 ///////////////////////////////////////////////////////////////////
222 //
223 //      CLASS NAME : HelixResolvable
224 /**
225  * Creates a file in helix format which includes all available
226  * or installed packages,patches,selections.....
227  **/
228 class  HelixResolvable : public base::ReferenceCounted, private base::NonCopyable{
229
230   private:
231     std::string dumpFile; // Path of the generated testcase
232     ofgzstream *file;
233
234   public:
235     HelixResolvable (const std::string & path);
236     ~HelixResolvable ();
237
238     void addResolvable (const PoolItem item)
239     { *file << helixXML (item); }
240
241     std::string filename ()
242     { return dumpFile; }
243 };
244
245 DEFINE_PTR_TYPE(HelixResolvable);
246 IMPL_PTR_TYPE(HelixResolvable);
247
248 typedef std::map<Repository, HelixResolvable_Ptr> RepositoryTable;
249
250 HelixResolvable::HelixResolvable(const std::string & path)
251     :dumpFile (path)
252 {
253     file = new ofgzstream(path.c_str());
254     if (!file) {
255         ZYPP_THROW (Exception( "Can't open " + path ) );
256     }
257
258     *file << "<channel><subchannel>" << endl;
259 }
260
261 HelixResolvable::~HelixResolvable()
262 {
263     *file << "</subchannel></channel>" << endl;
264     delete(file);
265 }
266
267 ///////////////////////////////////////////////////////////////////
268 //
269 //      CLASS NAME : HelixControl
270 /**
271  * Creates a file in helix format which contains all controll
272  * action of a testcase ( file is known as *-test.xml)
273  **/
274 class  HelixControl {
275
276   private:
277     std::string dumpFile; // Path of the generated testcase
278     std::ofstream *file;
279     bool _inSetup;
280
281   public:
282     HelixControl (const std::string & controlPath,
283                   const RepositoryTable & sourceTable,
284                   const Arch & systemArchitecture,
285                   const target::Modalias::ModaliasList & modaliasList,
286                   const std::set<std::string> & multiversionSpec,
287                   const std::string & systemPath);
288     HelixControl ();
289     ~HelixControl ();
290
291     void closeSetup()
292     {
293       if ( _inSetup )
294       {
295         *file << "</setup>" << endl << "<trial>" << endl;
296         _inSetup = false;
297       }
298     }
299
300     void addTagIf( const std::string & tag_r, bool yesno_r = true )
301     {
302       if ( yesno_r )
303         *file << (_inSetup ? TAB : "") << "<" << tag_r << "/>" << endl;
304     }
305
306     void installResolvable( const PoolItem & pi_r );
307     void lockResolvable( const PoolItem & pi_r );
308     void keepResolvable( const PoolItem & pi_r );
309     void deleteResolvable( const PoolItem & pi_r );
310     void addDependencies (const CapabilitySet &capRequire, const CapabilitySet &capConflict);
311     void addUpgradeRepos( const std::set<Repository> & upgradeRepos_r );
312
313     std::string filename () { return dumpFile; }
314 };
315
316 HelixControl::HelixControl(const std::string & controlPath,
317                            const RepositoryTable & repoTable,
318                            const Arch & systemArchitecture,
319                            const target::Modalias::ModaliasList & modaliasList,
320                            const std::set<std::string> & multiversionSpec,
321                            const std::string & systemPath)
322     :dumpFile (controlPath)
323     ,_inSetup( true )
324 {
325     file = new ofstream(controlPath.c_str());
326     if (!file) {
327         ZYPP_THROW (Exception( "Can't open " + controlPath ) );
328     }
329
330     *file << "<?xml version=\"1.0\"?>" << endl
331           << "<!-- testcase generated by YaST -->" << endl
332           << "<test>" << endl
333           << "<setup arch=\"" << systemArchitecture << "\">" << endl
334           << TAB << "<system file=\"" << systemPath << "\"/>" << endl << endl;
335     for ( RepositoryTable::const_iterator it = repoTable.begin();
336           it != repoTable.end(); ++it ) {
337         RepoInfo repo = it->first.info();
338         *file << TAB << "<!-- " << endl
339               << TAB << "- alias       : " << repo.alias() << endl;
340         for ( RepoInfo::urls_const_iterator itUrl = repo.baseUrlsBegin();
341               itUrl != repo.baseUrlsEnd();
342               ++itUrl )
343         {
344             *file << TAB << "- url         : " << *itUrl << endl;
345         }
346         *file << TAB << "- path        : " << repo.path() << endl;
347         *file << TAB << "- type        : " << repo.type() << endl;
348         *file << TAB << "- generated   : " << (it->first.generatedTimestamp()).form( "%Y-%m-%d %H:%M:%S" ) << endl;
349         *file << TAB << "- outdated    : " << (it->first.suggestedExpirationTimestamp()).form( "%Y-%m-%d %H:%M:%S" ) << endl;
350         *file << TAB << " -->" << endl;
351
352         *file << TAB << "<channel file=\"" << str::numstring((long)it->first.id())
353               << "-package.xml.gz\" name=\"" << repo.alias() << "\""
354               << " priority=\"" << repo.priority()
355               << "\" />" << endl << endl;
356     }
357
358     // HACK: directly access sat::pool
359     const sat::Pool & satpool( sat::Pool::instance() );
360
361     // RequestedLocales
362     const LocaleSet & addedLocales( satpool.getAddedRequestedLocales() );
363     const LocaleSet & removedLocales( satpool.getRemovedRequestedLocales() );
364     const LocaleSet & requestedLocales( satpool.getRequestedLocales() );
365
366     for ( Locale l : requestedLocales )
367     {
368       const char * fate = ( addedLocales.count(l) ? "\" fate=\"added" : "" );
369       *file << TAB << "<locale name=\"" << l << fate << "\" />" << endl;
370     }
371     for ( Locale l : removedLocales )
372     {
373       *file << TAB << "<locale name=\"" << l << "\" fate=\"removed\" />" << endl;
374     }
375
376     // AutoInstalled
377     for ( IdString::IdType n : satpool.autoInstalled() )
378     {
379       *file << TAB << "<autoinst name=\"" << IdString(n) << "\" />" << endl;
380     }
381
382
383
384     for_( it, modaliasList.begin(), modaliasList.end() ) {
385         *file << TAB << "<modalias name=\"" <<  xml_escape(*it)
386               << "\" />" << endl;
387     }
388
389     for_( it, multiversionSpec.begin(), multiversionSpec.end() ) {
390         *file << TAB << "<multiversion name=\"" <<  *it
391               << "\" />" << endl;
392     }
393
394     // setup continued outside....
395 }
396
397 HelixControl::HelixControl()
398     :dumpFile ("/var/log/YaST2/solverTestcase/solver-test.xml")
399 {
400     HelixControl (dumpFile);
401 }
402
403 HelixControl::~HelixControl()
404 {
405     closeSetup();       // in case it is still open
406     *file << "</trial>" << endl
407           << "</test>" << endl;
408     delete(file);
409 }
410
411 void HelixControl::installResolvable( const PoolItem & pi_r )
412 {
413     *file << "<install channel=\"" << pi_r.repoInfo().alias() << "\""
414           << " kind=\"" << pi_r.kind() << "\""
415           << " name=\"" << pi_r.name() << "\""
416           << " arch=\"" << pi_r.arch() << "\""
417           << " version=\"" << pi_r.edition().version() << "\""
418           << " release=\"" << pi_r.edition().release() << "\""
419           << " status=\"" << pi_r.status() << "\""
420           << "/>" << endl;
421 }
422
423 void HelixControl::lockResolvable( const PoolItem & pi_r )
424 {
425     *file << "<lock channel=\"" << pi_r.repoInfo().alias() << "\""
426           << " kind=\"" << pi_r.kind() << "\""
427           << " name=\"" << pi_r.name() << "\""
428           << " arch=\"" << pi_r.arch() << "\""
429           << " version=\"" << pi_r.edition().version() << "\""
430           << " release=\"" << pi_r.edition().release() << "\""
431           << " status=\"" << pi_r.status() << "\""
432           << "/>" << endl;
433 }
434
435 void HelixControl::keepResolvable( const PoolItem & pi_r )
436 {
437     *file << "<keep channel=\"" << pi_r.repoInfo().alias() << "\""
438           << " kind=\"" << pi_r.kind() << "\""
439           << " name=\"" << pi_r.name() << "\""
440           << " arch=\"" << pi_r.arch() << "\""
441           << " version=\"" << pi_r.edition().version() << "\""
442           << " release=\"" << pi_r.edition().release() << "\""
443           << " status=\"" << pi_r.status() << "\""
444           << "/>" << endl;
445 }
446
447 void HelixControl::deleteResolvable( const PoolItem & pi_r )
448 {
449     *file << "<uninstall  kind=\"" << pi_r.kind() << "\""
450           << " name=\"" << pi_r.name() << "\""
451           << " status=\"" << pi_r.status() << "\""
452           << "/>" << endl;
453 }
454
455 void HelixControl::addDependencies (const CapabilitySet & capRequire, const CapabilitySet & capConflict)
456 {
457     for (CapabilitySet::const_iterator iter = capRequire.begin(); iter != capRequire.end(); iter++) {
458         *file << "<addRequire " <<  " name=\"" << iter->asString() << "\"" << "/>" << endl;
459     }
460     for (CapabilitySet::const_iterator iter = capConflict.begin(); iter != capConflict.end(); iter++) {
461         *file << "<addConflict " << " name=\"" << iter->asString() << "\"" << "/>" << endl;
462     }
463 }
464
465 void HelixControl::addUpgradeRepos( const std::set<Repository> & upgradeRepos_r )
466 {
467   for_( it, upgradeRepos_r.begin(), upgradeRepos_r.end() )
468   {
469     *file << "<upgradeRepo name=\"" << it->alias() << "\"/>" << endl;
470   }
471 }
472
473 //---------------------------------------------------------------------------
474
475 Testcase::Testcase()
476     :dumpPath("/var/log/YaST2/solverTestcase")
477 {}
478
479 Testcase::Testcase(const std::string & path)
480     :dumpPath(path)
481 {}
482
483 Testcase::~Testcase()
484 {}
485
486 bool Testcase::createTestcase(Resolver & resolver, bool dumpPool, bool runSolver)
487 {
488     PathInfo path (dumpPath);
489
490     if ( !path.isExist() ) {
491         if (zypp::filesystem::assert_dir (dumpPath)!=0) {
492             ERR << "Cannot create directory " << dumpPath << endl;
493             return false;
494         }
495     } else {
496         if (!path.isDir()) {
497             ERR << dumpPath << " is not a directory." << endl;
498             return false;
499         }
500         // remove old stuff if pool will be dump
501         if (dumpPool)
502             zypp::filesystem::clean_dir (dumpPath);
503     }
504
505     if (runSolver) {
506         zypp::base::LogControl::TmpLineWriter tempRedirect;
507         zypp::base::LogControl::instance().logfile( dumpPath +"/y2log" );
508         zypp::base::LogControl::TmpExcessive excessive;
509
510         resolver.resolvePool();
511     }
512
513     ResPool pool        = resolver.pool();
514     RepositoryTable     repoTable;
515     PoolItemList        items_to_install;
516     PoolItemList        items_to_remove;
517     PoolItemList        items_locked;
518     PoolItemList        items_keep;
519     HelixResolvable_Ptr system = NULL;
520
521     if (dumpPool)
522         system = new HelixResolvable(dumpPath + "/solver-system.xml.gz");
523
524     for ( const PoolItem & pi : pool )
525     {
526         if ( system && pi.status().isInstalled() ) {
527             // system channel
528             system->addResolvable( pi );
529         } else {
530             // repo channels
531             Repository repo  = pi.repository();
532             if (dumpPool) {
533                 if (repoTable.find (repo) == repoTable.end()) {
534                     repoTable[repo] = new HelixResolvable(dumpPath + "/"
535                                                           + str::numstring((long)repo.id())
536                                                           + "-package.xml.gz");
537                 }
538                 repoTable[repo]->addResolvable( pi );
539             }
540         }
541
542         if ( pi.status().isToBeInstalled()
543              && !(pi.status().isBySolver())) {
544             items_to_install.push_back( pi );
545         }
546         if ( pi.status().isKept()
547              && !(pi.status().isBySolver())) {
548             items_keep.push_back( pi );
549         }
550         if ( pi.status().isToBeUninstalled()
551              && !(pi.status().isBySolver())) {
552             items_to_remove.push_back( pi );
553         }
554         if ( pi.status().isLocked()
555              && !(pi.status().isBySolver())) {
556             items_locked.push_back( pi );
557         }
558     }
559
560     // writing control file "*-test.xml"
561     HelixControl control (dumpPath + "/solver-test.xml",
562                           repoTable,
563                           ZConfig::instance().systemArchitecture(),
564                           target::Modalias::instance().modaliasList(),
565                           ZConfig::instance().multiversionSpec(),
566                           "solver-system.xml.gz");
567
568     // In <setup>: resolver flags,...
569     control.addTagIf( "ignorealreadyrecommended",       resolver.ignoreAlreadyRecommended() );
570     control.addTagIf( "onlyRequires",           resolver.onlyRequires() );
571     control.addTagIf( "forceResolve",           resolver.forceResolve() );
572
573     control.addTagIf( "cleandepsOnRemove",      resolver.cleandepsOnRemove() );
574
575     control.addTagIf( "allowDowngrade",         resolver.allowDowngrade() );
576     control.addTagIf( "allowNameChange",        resolver.allowNameChange() );
577     control.addTagIf( "allowArchChange",        resolver.allowArchChange() );
578     control.addTagIf( "allowVendorChange",      resolver.allowVendorChange() );
579
580     control.addTagIf( "dupAllowDowngrade",      resolver.dupAllowDowngrade() );
581     control.addTagIf( "dupAllowNameChange",     resolver.dupAllowNameChange() );
582     control.addTagIf( "dupAllowArchChange",     resolver.dupAllowArchChange() );
583     control.addTagIf( "dupAllowVendorChange",   resolver.dupAllowVendorChange() );
584
585     control.closeSetup();
586     // Entering <trial>...
587
588     for ( const PoolItem & pi : items_to_install )
589     { control.installResolvable( pi ); }
590
591     for ( const PoolItem & pi : items_locked )
592     { control.lockResolvable( pi ); }
593
594     for ( const PoolItem & pi : items_keep )
595     { control.keepResolvable( pi ); }
596
597     for ( const PoolItem & pi : items_to_remove )
598     { control.deleteResolvable( pi ); }
599
600     control.addDependencies (resolver.extraRequires(), resolver.extraConflicts());
601     control.addDependencies (SystemCheck::instance().requiredSystemCap(),
602                              SystemCheck::instance().conflictSystemCap());
603     control.addUpgradeRepos( resolver.upgradeRepos() );
604
605     control.addTagIf( "distupgrade",    resolver.isUpgradeMode() );
606     control.addTagIf( "verify",         resolver.isUpdateMode() );
607     control.addTagIf( "update",         resolver.isVerifyingMode() );
608
609     return true;
610 }
611
612
613       ///////////////////////////////////////////////////////////////////
614     };// namespace detail
615     /////////////////////////////////////////////////////////////////////
616     /////////////////////////////////////////////////////////////////////
617   };// namespace solver
618   ///////////////////////////////////////////////////////////////////////
619   ///////////////////////////////////////////////////////////////////////
620 };// namespace zypp
621 /////////////////////////////////////////////////////////////////////////