Imported Upstream version 17.23.5
[platform/upstream/libzypp.git] / tests / lib / TestSetup.h
1 #ifndef INCLUDE_TESTSETUP
2 #define INCLUDE_TESTSETUP
3 #include <iostream>
4
5 #ifndef INCLUDE_TESTSETUP_WITHOUT_BOOST
6 #include <boost/test/auto_unit_test.hpp>
7 using boost::unit_test::test_case;
8 #endif
9
10 #include <zypp/base/LogControl.h>
11 #include <zypp/base/LogTools.h>
12 #include <zypp/base/InputStream.h>
13 #include <zypp/base/IOStream.h>
14 #include <zypp/base/Flags.h>
15 #include <zypp/ZYppFactory.h>
16 #include <zypp/ZYpp.h>
17 #include <zypp/TmpPath.h>
18 #include <zypp/Glob.h>
19 #include <zypp/PathInfo.h>
20 #include <zypp/RepoManager.h>
21 #include <zypp/Target.h>
22 #include <zypp/ResPool.h>
23
24 using std::cin;
25 using std::cout;
26 using std::cerr;
27 using std::endl;
28 using std::flush;
29 using namespace zypp;
30
31 #ifndef BOOST_CHECK_NE
32 #define BOOST_CHECK_NE( L, R ) BOOST_CHECK( (L) != (R) )
33 #endif
34
35 #define LABELED(V) #V << ":\t" << V
36
37 inline std::string getXmlNodeVal( const std::string & line_r, const std::string & node_r )
38 {
39   std::string::size_type pos = line_r.find( node_r + "=\"" );
40   if ( pos != std::string::npos )
41   {
42     pos += node_r.size() + 2;
43     std::string::size_type epos = line_r.find( "\"", pos );
44     return line_r.substr( pos, epos-pos );
45   }
46   return std::string();
47 }
48
49 enum TestSetupOptionBits
50 {
51   TSO_CLEANROOT         = (1 <<  0),    // wipe rootdir in ctor
52   TSO_REPO_DEFAULT_GPG  = (1 <<  1),    // dont turn off gpgcheck in repos
53 };
54 ZYPP_DECLARE_FLAGS_AND_OPERATORS( TestSetupOptions, TestSetupOptionBits );
55
56 /** Build a test environment below a temp. root directory.
57  * If a \c rootdir_r was provided to the ctor, this directory
58  * will be used and it will \b not be removed.
59  *
60  * \note The lifetime of this objects is the lifetime of the temp. root directory.
61  *
62  * \code
63  * #include "TestSetup.h"
64  *
65  * BOOST_AUTO_TEST_CASE(WhatProvides)
66  * {
67  *   // enabls loging fot the scope of this block:
68  *   // base::LogControl::TmpLineWriter shutUp( new log::FileLineWriter( "/tmp/YLOG" ) );
69  *
70  *   TestSetup test( Arch_x86_64 );
71  *   // test.loadTarget(); // initialize and load target
72  *   test.loadRepo( TESTS_SRC_DIR"/data/openSUSE-11.1" );
73  *
74  *   // Here the pool is ready to be used.
75  *
76  * }
77  * \endcode
78 */
79 class TestSetup
80 {
81 public:
82   typedef TestSetupOptions Options;
83
84 public:
85   struct InitLaterType {};
86   static constexpr InitLaterType initLater = InitLaterType();
87
88   TestSetup( InitLaterType )
89   {}
90
91   TestSetup( const Arch & sysarch_r = Arch_empty, const Options & options_r = Options() )
92     : _pimpl { new Impl( Pathname(), sysarch_r, options_r ) }
93   {}
94
95   TestSetup( const Pathname & rootdir_r, const Arch & sysarch_r = Arch_empty, const Options & options_r = Options() )
96     : _pimpl { new Impl( rootdir_r, sysarch_r, options_r ) }
97   {}
98
99   TestSetup( const Pathname & rootdir_r, const Options & options_r )
100     : _pimpl { new Impl( rootdir_r, Arch_empty, options_r ) }
101   {}
102
103   void reset()
104   { _pimpl.reset(); }
105
106 public:
107   /** Whether directory \a path_r contains a solver testcase. */
108   static bool isTestcase( const Pathname & path_r )
109   {
110     return filesystem::PathInfo( path_r / "solver-test.xml" ).isFile();
111   }
112
113   /** Whether directory \a path_r contains a testsetup. */
114   static bool isTestSetup( const Pathname & path_r )
115   {
116     return filesystem::PathInfo( path_r / "repos.d" ).isDir() && filesystem::PathInfo( path_r / "raw" ).isDir();
117   }
118
119 public:
120   const Pathname & root() const { return _pimpl->_rootdir; }
121
122   Target &     target()      { if ( ! getZYpp()->getTarget() ) getZYpp()->initializeTarget( _pimpl->_rootdir ); return *getZYpp()->getTarget(); }
123   RepoManager  repomanager() { return RepoManager( RepoManagerOptions::makeTestSetup( _pimpl->_rootdir ) ); }
124   ResPool      pool()        { return ResPool::instance(); }
125   ResPoolProxy poolProxy()   { return pool().proxy(); }
126   sat::Pool    satpool()     { return sat::Pool::instance(); }
127   Resolver &   resolver()    { return *getZYpp()->resolver(); }
128
129 public:
130   /** Load target repo. */
131   void loadTarget()
132   { target().load(); }
133   /** Fake @System repo from url. */
134   void loadTargetRepo( const Url & url_r )
135   { loadRepo( url_r, sat::Pool::systemRepoAlias() ); }
136   /** Fake @System repo from Path. */
137   void loadTargetRepo( const Pathname & path_r )
138   { loadRepo( path_r, sat::Pool::systemRepoAlias() ); }
139   /** Fake @System repo from helix repo. */
140   void loadTargetHelix( const Pathname & path_r )
141   { loadHelix( path_r, sat::Pool::systemRepoAlias() ); }
142
143 public:
144   /** Directly load repoinfo to pool. */
145   void loadRepo( RepoInfo nrepo )
146   {
147     RepoManager rmanager( repomanager() );
148     if ( rmanager.hasRepo( nrepo ) )
149       nrepo.setAlias( RepoManager::makeStupidAlias( nrepo.url() ) );
150     rmanager.addRepository( nrepo );
151     rmanager.buildCache( nrepo );
152     rmanager.loadFromCache( nrepo );
153   }
154   /** Directly load repo from url to pool. */
155   void loadRepo( const Url & url_r, const std::string & alias_r = std::string() )
156   {
157     RepoInfo nrepo;
158     nrepo.setAlias( alias_r.empty() ? url_r.getHost()+":"+Pathname::basename(url_r.getPathName()) : alias_r );
159     nrepo.addBaseUrl( url_r );
160     if ( ! _pimpl->_options.testFlag( TSO_REPO_DEFAULT_GPG ) )
161       nrepo.setGpgCheck( false );
162     loadRepo( nrepo );
163   }
164   /** Directly load repo from metadata(dir) or solvfile(file) to pool.
165      * An empty alias is guessed.
166     */
167   void loadRepo( const Pathname & path_r, const std::string & alias_r = std::string() )
168   {
169     if ( filesystem::PathInfo( path_r ).isDir() )
170     {
171       loadRepo( path_r.asUrl(), alias_r );
172       return;
173     }
174     // .solv file is loaded directly using a faked RepoInfo
175     RepoInfo nrepo;
176     nrepo.setAlias( alias_r.empty() ? path_r.basename() : alias_r );
177     satpool().addRepoSolv( path_r, nrepo );
178   }
179   /** Directly load repo from some location (url or absolute(!)path).
180      * An empty alias is guessed.
181     */
182   void loadRepo( const std::string & loc_r, const std::string & alias_r = std::string() )
183   {
184     if ( *loc_r.c_str() == '/' )
185     {
186       loadRepo( Pathname( loc_r ), alias_r );
187     }
188     else
189     {
190       loadRepo( Url( loc_r ), alias_r );
191     }
192   }
193   /** Directly load repo from some location (url or absolute(!)path).
194      * An empty alias is guessed.
195     */
196   void loadRepo( const char * loc_r, const std::string & alias_r = std::string() )
197   { loadRepo( std::string( loc_r ? loc_r : "" ), alias_r ); }
198
199 private:
200   // repo data from solver-test.xml
201   struct RepoD {
202     DefaultIntegral<unsigned,0> priority;
203     std::string alias;
204     Url url;
205   };
206
207 public:
208   /** Directly load a helix repo from some testcase.
209      * An empty alias is guessed.
210      */
211   void loadHelix( const Pathname & path_r, const std::string & alias_r = std::string() )
212   {
213     // .solv file is loaded directly using a faked RepoInfo
214     RepoInfo nrepo;
215     nrepo.setAlias( alias_r.empty() ? path_r.basename() : alias_r );
216     satpool().addRepoHelix( path_r, nrepo );
217   }
218
219   // Load repos included in a solver testcase.
220   void loadTestcaseRepos( const Pathname & path_r )
221   {
222     filesystem::PathInfo pi( path_r / "solver-test.xml" );
223     if ( ! pi.isFile() )
224     {
225       ERR << "No testcase in " << filesystem::PathInfo( path_r ) << endl;
226       return;
227     }
228     // dumb parse
229     InputStream infile( pi.path() );
230     Arch sysarch( Arch_empty );
231     Url guessedUrl;
232     typedef std::map<std::string,RepoD> RepoI;
233     RepoI repoi;
234     for( iostr::EachLine in( infile ); in; in.next() )
235     {
236       if ( str::hasPrefix( *in, "\t<channel" ) )
237       {
238         RepoD & repod( repoi[getXmlNodeVal( *in, "file" )] );
239
240         repod.alias = getXmlNodeVal( *in, "name" );
241         repod.priority = str::strtonum<unsigned>( getXmlNodeVal( *in, "priority" ) );
242         repod.url = guessedUrl;
243         guessedUrl = Url();
244       }
245       else if ( str::hasPrefix( *in, "\t- url " ) )
246       {
247         std::string::size_type pos = in->find( ": " );
248         if ( pos != std::string::npos )
249         {
250           guessedUrl = Url( in->substr( pos+2 ) );
251         }
252       }
253       else if ( str::hasPrefix( *in, "\t<locale" ) )
254       {
255         satpool().addRequestedLocale( Locale( getXmlNodeVal( *in, "name" ) ) );
256       }
257       else if ( sysarch.empty() && str::hasPrefix( *in, "<setup" ) )
258       {
259         sysarch = Arch( getXmlNodeVal( *in, "arch" ) );
260         if ( ! sysarch.empty() )
261           ZConfig::instance().setSystemArchitecture( sysarch );
262       }
263     }
264
265     //
266     filesystem::Glob files( path_r/"*{.xml,.xml.gz}", filesystem::Glob::kBrace );
267     for_( it, files.begin(), files.end() )
268     {
269       std::string basename( Pathname::basename( *it ) );
270       if ( str::hasPrefix( basename, "solver-test.xml" ) )
271         continue; // master index currently unevaluated
272       if ( str::hasPrefix( basename, "solver-system.xml" ) )
273         loadTargetHelix( *it );
274       else
275       {
276         const RepoD & repod( repoi[basename] );
277
278         RepoInfo nrepo;
279         nrepo.setAlias( repod.alias.empty() ? basename : repod.alias );
280         nrepo.setPriority( repod.priority );
281         nrepo.setBaseUrl( repod.url );
282         satpool().addRepoHelix( *it, nrepo );
283       }
284     }
285
286     poolProxy(); // prepare
287   }
288
289 public:
290   /** Load all enabled repos in repos.d to pool. */
291   void loadRepos()
292   {
293     RepoManager repoManager( repomanager() );
294     RepoInfoList repos = repoManager.knownRepositories();
295     for ( RepoInfoList::iterator it = repos.begin(); it != repos.end(); ++it )
296     {
297       RepoInfo & nrepo( *it );
298       USR << nrepo << endl;
299
300       if ( ! nrepo.enabled() )
301         continue;
302
303       if ( ! repoManager.isCached( nrepo ) || nrepo.type() == repo::RepoType::RPMPLAINDIR )
304       {
305         if ( repoManager.isCached( nrepo ) )
306         {
307           USR << "cleanCache" << endl;
308           repoManager.cleanCache( nrepo );
309         }
310         //USR << "refreshMetadata" << endl;
311         //repoManager.refreshMetadata( nrepo );
312         USR << "buildCache" << endl;
313         repoManager.buildCache( nrepo );
314       }
315       USR << "Create from cache" << endl;
316       repoManager.loadFromCache( nrepo );
317     }
318   }
319
320 public:
321   /** Detect and load the system located at \a sysRoot.
322      *
323      * \a sysRoot needs to be a directory containing either a SolverTestcase,
324      * a TestSetup system or a real system. The  provided repostitories are
325      * loaded into the pool (without refresh).
326     */
327   static void LoadSystemAt( const Pathname & sysRoot, const Arch & _testSetupArch_r = Arch_x86_64 )
328   {
329     if ( ! PathInfo( sysRoot ).isDir() )
330       ZYPP_THROW( Exception("sysRoot argument needs to be a directory") );
331
332     if ( TestSetup::isTestcase( sysRoot ) )
333     {
334       USR << str::form( "*** Load Testcase from '%s'", sysRoot.c_str() ) << endl;
335       TestSetup test;
336       test.loadTestcaseRepos( sysRoot );
337     }
338     else if ( TestSetup::isTestSetup( sysRoot ) )
339     {
340       USR << str::form( "*** Load TestSetup from '%s'", sysRoot.c_str() ) << endl;
341
342       TestSetup test( sysRoot, _testSetupArch_r );
343       test.loadRepos();
344
345       Pathname solvCachePath( RepoManagerOptions::makeTestSetup( test.root() ).repoSolvCachePath );
346       Pathname fakeTargetSolv( solvCachePath / sat::Pool::systemRepoAlias() / "solv" );
347       if ( PathInfo( fakeTargetSolv ).isFile() )
348       {
349         USR << str::form( "*** Fake TestSetup Target from '%s'", fakeTargetSolv.c_str() ) << endl;
350         test.target();
351         test.loadTargetRepo( fakeTargetSolv );
352       }
353     }
354     else
355     {
356       sat::Pool satpool( sat::Pool::instance() );
357       // a system
358       USR << str::form( "*** Load system at '%s'", sysRoot.c_str() ) << endl;
359       if ( 1 )
360       {
361         USR << "*** load target '" << Repository::systemRepoAlias() << "'\t" << endl;
362         getZYpp()->initializeTarget( sysRoot );
363         getZYpp()->target()->load();
364         USR << satpool.systemRepo() << endl;
365       }
366
367       if ( 1 )
368       {
369         RepoManager repoManager( sysRoot );
370         RepoInfoList repos = repoManager.knownRepositories();
371         for_( it, repos.begin(), repos.end() )
372         {
373           RepoInfo & nrepo( *it );
374
375           if ( ! nrepo.enabled() )
376             continue;
377
378           if ( ! repoManager.isCached( nrepo ) )
379           {
380             USR << str::form( "*** omit uncached repo '%s' (do 'zypper refresh')", nrepo.name().c_str() ) << endl;
381             continue;
382           }
383
384           USR << str::form( "*** load repo '%s'\t", nrepo.name().c_str() ) << flush;
385           try
386           {
387             repoManager.loadFromCache( nrepo );
388             USR << satpool.reposFind( nrepo.alias() ) << endl;
389           }
390           catch ( const Exception & exp )
391           {
392             USR << exp.asString() + "\n" + exp.historyAsString() << endl;
393             USR << str::form( "*** omit broken repo '%s' (do 'zypper refresh')", nrepo.name().c_str() ) << endl;
394             continue;
395           }
396         }
397       }
398     }
399   }
400
401 private:
402   struct Impl
403   {
404     Impl( const Pathname & rootdir_r, const Arch & sysarch_r, const Options & options_r )
405     {
406       _options = options_r;
407
408       if ( rootdir_r.empty() )
409         _rootdir = _tmprootdir.path();
410       else
411       {
412         filesystem::assert_dir( (_rootdir = rootdir_r) );
413         if ( _options.testFlag( TSO_CLEANROOT ) )
414           filesystem::clean_dir( _rootdir );
415       }
416
417       // erase any old pool content...
418       getZYpp()->finishTarget();
419       sat::Pool::instance().reposEraseAll();
420       // prepare for the new one...
421       ZConfig::instance().setRepoManagerRoot( _rootdir );
422
423       if ( ! sysarch_r.empty() )
424         ZConfig::instance().setSystemArchitecture( sysarch_r );
425       USR << "CREATED TESTSETUP below " << _rootdir << endl;
426     }
427
428     ~Impl()
429     { USR << (_tmprootdir.path() == _rootdir ? "DELETE" : "KEEP") << " TESTSETUP below " << _rootdir << endl; }
430
431     filesystem::TmpDir _tmprootdir;
432     Pathname           _rootdir;
433     Options            _options;
434   };
435
436   std::unique_ptr<Impl> _pimpl; // maybe worth creating RW_pointer traits for it
437 };
438
439
440 #endif //INCLUDE_TESTSETUP