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