Imported Upstream version 17.15.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
290     void closeSetup()
291     {
292       if ( _inSetup )
293       {
294         *file << "</setup>" << endl << "<trial>" << endl;
295         _inSetup = false;
296       }
297     }
298
299     std::ostream & writeTag()
300     { return *file << (_inSetup ? TAB : ""); }
301
302     void addTagIf( const std::string & tag_r, bool yesno_r = true )
303     {
304       if ( yesno_r )
305         writeTag() << "<" << tag_r << "/>" << endl;
306     }
307
308     void installResolvable( const PoolItem & pi_r );
309     void lockResolvable( const PoolItem & pi_r );
310     void keepResolvable( const PoolItem & pi_r );
311     void deleteResolvable( const PoolItem & pi_r );
312     void addDependencies (const CapabilitySet &capRequire, const CapabilitySet &capConflict);
313     void addUpgradeRepos( const std::set<Repository> & upgradeRepos_r );
314
315     std::string filename () { return dumpFile; }
316 };
317
318 HelixControl::HelixControl(const std::string & controlPath,
319                            const RepositoryTable & repoTable,
320                            const Arch & systemArchitecture,
321                            const target::Modalias::ModaliasList & modaliasList,
322                            const std::set<std::string> & multiversionSpec,
323                            const std::string & systemPath)
324     :dumpFile (controlPath)
325     ,_inSetup( true )
326 {
327     file = new ofstream(controlPath.c_str());
328     if (!file) {
329         ZYPP_THROW (Exception( "Can't open " + controlPath ) );
330     }
331
332     *file << "<?xml version=\"1.0\"?>" << endl
333           << "<!-- libzypp resolver testcase -->" << endl
334           << "<test>" << endl
335           << "<setup arch=\"" << systemArchitecture << "\">" << endl
336           << TAB << "<system file=\"" << systemPath << "\"/>" << endl << endl;
337     for ( RepositoryTable::const_iterator it = repoTable.begin();
338           it != repoTable.end(); ++it ) {
339         RepoInfo repo = it->first.info();
340         *file << TAB << "<!-- " << endl
341               << TAB << "- alias       : " << repo.alias() << endl;
342         for ( RepoInfo::urls_const_iterator itUrl = repo.baseUrlsBegin();
343               itUrl != repo.baseUrlsEnd();
344               ++itUrl )
345         {
346             *file << TAB << "- url         : " << *itUrl << endl;
347         }
348         *file << TAB << "- path        : " << repo.path() << endl;
349         *file << TAB << "- type        : " << repo.type() << endl;
350         *file << TAB << "- generated   : " << (it->first.generatedTimestamp()).form( "%Y-%m-%d %H:%M:%S" ) << endl;
351         *file << TAB << "- outdated    : " << (it->first.suggestedExpirationTimestamp()).form( "%Y-%m-%d %H:%M:%S" ) << endl;
352         *file << TAB << " -->" << endl;
353
354         *file << TAB << "<channel file=\"" << str::numstring((long)it->first.id())
355               << "-package.xml.gz\" name=\"" << repo.alias() << "\""
356               << " priority=\"" << repo.priority()
357               << "\" />" << endl << endl;
358     }
359
360     // HACK: directly access sat::pool
361     const sat::Pool & satpool( sat::Pool::instance() );
362
363     // RequestedLocales
364     const LocaleSet & addedLocales( satpool.getAddedRequestedLocales() );
365     const LocaleSet & removedLocales( satpool.getRemovedRequestedLocales() );
366     const LocaleSet & requestedLocales( satpool.getRequestedLocales() );
367
368     for ( Locale l : requestedLocales )
369     {
370       const char * fate = ( addedLocales.count(l) ? "\" fate=\"added" : "" );
371       *file << TAB << "<locale name=\"" << l << fate << "\" />" << endl;
372     }
373     for ( Locale l : removedLocales )
374     {
375       *file << TAB << "<locale name=\"" << l << "\" fate=\"removed\" />" << endl;
376     }
377
378     // AutoInstalled
379     for ( IdString::IdType n : satpool.autoInstalled() )
380     {
381       *file << TAB << "<autoinst name=\"" << IdString(n) << "\" />" << endl;
382     }
383
384
385
386     for_( it, modaliasList.begin(), modaliasList.end() ) {
387         *file << TAB << "<modalias name=\"" <<  xml_escape(*it)
388               << "\" />" << endl;
389     }
390
391     for_( it, multiversionSpec.begin(), multiversionSpec.end() ) {
392         *file << TAB << "<multiversion name=\"" <<  *it
393               << "\" />" << endl;
394     }
395
396     // setup continued outside....
397 }
398
399 HelixControl::~HelixControl()
400 {
401     closeSetup();       // in case it is still open
402     *file << "</trial>" << endl
403           << "</test>" << endl;
404     delete(file);
405 }
406
407 void HelixControl::installResolvable( const PoolItem & pi_r )
408 {
409     *file << "<install channel=\"" << pi_r.repoInfo().alias() << "\""
410           << " kind=\"" << pi_r.kind() << "\""
411           << " name=\"" << pi_r.name() << "\""
412           << " arch=\"" << pi_r.arch() << "\""
413           << " version=\"" << pi_r.edition().version() << "\""
414           << " release=\"" << pi_r.edition().release() << "\""
415           << " status=\"" << pi_r.status() << "\""
416           << "/>" << endl;
417 }
418
419 void HelixControl::lockResolvable( const PoolItem & pi_r )
420 {
421     *file << "<lock channel=\"" << pi_r.repoInfo().alias() << "\""
422           << " kind=\"" << pi_r.kind() << "\""
423           << " name=\"" << pi_r.name() << "\""
424           << " arch=\"" << pi_r.arch() << "\""
425           << " version=\"" << pi_r.edition().version() << "\""
426           << " release=\"" << pi_r.edition().release() << "\""
427           << " status=\"" << pi_r.status() << "\""
428           << "/>" << endl;
429 }
430
431 void HelixControl::keepResolvable( const PoolItem & pi_r )
432 {
433     *file << "<keep channel=\"" << pi_r.repoInfo().alias() << "\""
434           << " kind=\"" << pi_r.kind() << "\""
435           << " name=\"" << pi_r.name() << "\""
436           << " arch=\"" << pi_r.arch() << "\""
437           << " version=\"" << pi_r.edition().version() << "\""
438           << " release=\"" << pi_r.edition().release() << "\""
439           << " status=\"" << pi_r.status() << "\""
440           << "/>" << endl;
441 }
442
443 void HelixControl::deleteResolvable( const PoolItem & pi_r )
444 {
445     *file << "<uninstall  kind=\"" << pi_r.kind() << "\""
446           << " name=\"" << pi_r.name() << "\""
447           << " status=\"" << pi_r.status() << "\""
448           << "/>" << endl;
449 }
450
451 void HelixControl::addDependencies (const CapabilitySet & capRequire, const CapabilitySet & capConflict)
452 {
453     for (CapabilitySet::const_iterator iter = capRequire.begin(); iter != capRequire.end(); iter++) {
454         *file << "<addRequire " <<  " name=\"" << iter->asString() << "\"" << "/>" << endl;
455     }
456     for (CapabilitySet::const_iterator iter = capConflict.begin(); iter != capConflict.end(); iter++) {
457         *file << "<addConflict " << " name=\"" << iter->asString() << "\"" << "/>" << endl;
458     }
459 }
460
461 void HelixControl::addUpgradeRepos( const std::set<Repository> & upgradeRepos_r )
462 {
463   for_( it, upgradeRepos_r.begin(), upgradeRepos_r.end() )
464   {
465     *file << "<upgradeRepo name=\"" << it->alias() << "\"/>" << endl;
466   }
467 }
468
469 //---------------------------------------------------------------------------
470
471 Testcase::Testcase()
472     :dumpPath("/var/log/YaST2/solverTestcase")
473 {}
474
475 Testcase::Testcase(const std::string & path)
476     :dumpPath(path)
477 {}
478
479 Testcase::~Testcase()
480 {}
481
482 bool Testcase::createTestcase(Resolver & resolver, bool dumpPool, bool runSolver)
483 {
484     PathInfo path (dumpPath);
485
486     if ( !path.isExist() ) {
487         if (zypp::filesystem::assert_dir (dumpPath)!=0) {
488             ERR << "Cannot create directory " << dumpPath << endl;
489             return false;
490         }
491     } else {
492         if (!path.isDir()) {
493             ERR << dumpPath << " is not a directory." << endl;
494             return false;
495         }
496         // remove old stuff if pool will be dump
497         if (dumpPool)
498             zypp::filesystem::clean_dir (dumpPath);
499     }
500
501     if (runSolver) {
502         zypp::base::LogControl::TmpLineWriter tempRedirect;
503         zypp::base::LogControl::instance().logfile( dumpPath +"/y2log" );
504         zypp::base::LogControl::TmpExcessive excessive;
505
506         resolver.resolvePool();
507     }
508
509     ResPool pool        = resolver.pool();
510     RepositoryTable     repoTable;
511     PoolItemList        items_to_install;
512     PoolItemList        items_to_remove;
513     PoolItemList        items_locked;
514     PoolItemList        items_keep;
515     HelixResolvable_Ptr system = NULL;
516
517     if (dumpPool)
518         system = new HelixResolvable(dumpPath + "/solver-system.xml.gz");
519
520     for ( const PoolItem & pi : pool )
521     {
522         if ( system && pi.status().isInstalled() ) {
523             // system channel
524             system->addResolvable( pi );
525         } else {
526             // repo channels
527             Repository repo  = pi.repository();
528             if (dumpPool) {
529                 if (repoTable.find (repo) == repoTable.end()) {
530                     repoTable[repo] = new HelixResolvable(dumpPath + "/"
531                                                           + str::numstring((long)repo.id())
532                                                           + "-package.xml.gz");
533                 }
534                 repoTable[repo]->addResolvable( pi );
535             }
536         }
537
538         if ( pi.status().isToBeInstalled()
539              && !(pi.status().isBySolver())) {
540             items_to_install.push_back( pi );
541         }
542         if ( pi.status().isKept()
543              && !(pi.status().isBySolver())) {
544             items_keep.push_back( pi );
545         }
546         if ( pi.status().isToBeUninstalled()
547              && !(pi.status().isBySolver())) {
548             items_to_remove.push_back( pi );
549         }
550         if ( pi.status().isLocked()
551              && !(pi.status().isBySolver())) {
552             items_locked.push_back( pi );
553         }
554     }
555
556     // writing control file "*-test.xml"
557     HelixControl control (dumpPath + "/solver-test.xml",
558                           repoTable,
559                           ZConfig::instance().systemArchitecture(),
560                           target::Modalias::instance().modaliasList(),
561                           ZConfig::instance().multiversionSpec(),
562                           "solver-system.xml.gz");
563
564     // In <setup>: resolver flags,...
565     control.writeTag() << "<focus value=\"" << resolver.focus() << "\"/>" << endl;
566
567     control.addTagIf( "ignorealreadyrecommended",       resolver.ignoreAlreadyRecommended() );
568     control.addTagIf( "onlyRequires",           resolver.onlyRequires() );
569     control.addTagIf( "forceResolve",           resolver.forceResolve() );
570
571     control.addTagIf( "cleandepsOnRemove",      resolver.cleandepsOnRemove() );
572
573     control.addTagIf( "allowDowngrade",         resolver.allowDowngrade() );
574     control.addTagIf( "allowNameChange",        resolver.allowNameChange() );
575     control.addTagIf( "allowArchChange",        resolver.allowArchChange() );
576     control.addTagIf( "allowVendorChange",      resolver.allowVendorChange() );
577
578     control.addTagIf( "dupAllowDowngrade",      resolver.dupAllowDowngrade() );
579     control.addTagIf( "dupAllowNameChange",     resolver.dupAllowNameChange() );
580     control.addTagIf( "dupAllowArchChange",     resolver.dupAllowArchChange() );
581     control.addTagIf( "dupAllowVendorChange",   resolver.dupAllowVendorChange() );
582
583     control.closeSetup();
584     // Entering <trial>...
585
586     for ( const PoolItem & pi : items_to_install )
587     { control.installResolvable( pi ); }
588
589     for ( const PoolItem & pi : items_locked )
590     { control.lockResolvable( pi ); }
591
592     for ( const PoolItem & pi : items_keep )
593     { control.keepResolvable( pi ); }
594
595     for ( const PoolItem & pi : items_to_remove )
596     { control.deleteResolvable( pi ); }
597
598     control.addDependencies (resolver.extraRequires(), resolver.extraConflicts());
599     control.addDependencies (SystemCheck::instance().requiredSystemCap(),
600                              SystemCheck::instance().conflictSystemCap());
601     control.addUpgradeRepos( resolver.upgradeRepos() );
602
603     control.addTagIf( "distupgrade",    resolver.isUpgradeMode() );
604     control.addTagIf( "update",         resolver.isUpdateMode() );
605     control.addTagIf( "verify",         resolver.isVerifyingMode() );
606
607     return true;
608 }
609
610
611       ///////////////////////////////////////////////////////////////////
612     };// namespace detail
613     /////////////////////////////////////////////////////////////////////
614     /////////////////////////////////////////////////////////////////////
615   };// namespace solver
616   ///////////////////////////////////////////////////////////////////////
617   ///////////////////////////////////////////////////////////////////////
618 };// namespace zypp
619 /////////////////////////////////////////////////////////////////////////