Imported Upstream version 0.7.8
[platform/upstream/libsolv.git] / ext / testcase.c
1 /*
2  * Copyright (c) 2012, Novell Inc.
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 #include <sys/types.h>
9 #include <sys/stat.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <errno.h>
14
15 #include "pool.h"
16 #include "poolarch.h"
17 #include "poolvendor.h"
18 #include "evr.h"
19 #include "repo.h"
20 #include "repo_solv.h"
21 #include "solver.h"
22 #include "solverdebug.h"
23 #include "chksum.h"
24 #include "testcase.h"
25 #include "selection.h"
26 #include "solv_xfopen.h"
27 #if ENABLE_TESTCASE_HELIXREPO
28 #include "ext/repo_helix.h"
29 #endif
30
31 #ifdef _WIN32
32   #include <direct.h>
33 #endif
34
35 /* see repo_testcase.c */
36 struct oplist {
37   Id flags;
38   const char *opname;
39 };
40 extern struct oplist oplist[];
41
42
43 static struct job2str {
44   Id job;
45   const char *str;
46 } job2str[] = {
47   { SOLVER_NOOP,           "noop" },
48   { SOLVER_INSTALL,        "install" },
49   { SOLVER_ERASE,          "erase" },
50   { SOLVER_UPDATE,         "update" },
51   { SOLVER_WEAKENDEPS,     "weakendeps" },
52   { SOLVER_MULTIVERSION,   "multiversion" },
53   { SOLVER_MULTIVERSION,   "noobsoletes" },     /* old name */
54   { SOLVER_LOCK,           "lock" },
55   { SOLVER_DISTUPGRADE,    "distupgrade" },
56   { SOLVER_VERIFY,         "verify" },
57   { SOLVER_DROP_ORPHANED,  "droporphaned" },
58   { SOLVER_USERINSTALLED,  "userinstalled" },
59   { SOLVER_ALLOWUNINSTALL, "allowuninstall" },
60   { SOLVER_FAVOR,          "favor" },
61   { SOLVER_DISFAVOR,       "disfavor" },
62   { SOLVER_BLACKLIST,      "blacklist" },
63   { 0, 0 }
64 };
65
66 static struct jobflags2str {
67   Id flag;
68   const char *str;
69 } jobflags2str[] = {
70   { SOLVER_WEAK,      "weak" },
71   { SOLVER_ESSENTIAL, "essential" },
72   { SOLVER_CLEANDEPS, "cleandeps" },
73   { SOLVER_ORUPDATE,  "orupdate" },
74   { SOLVER_FORCEBEST, "forcebest" },
75   { SOLVER_TARGETED,  "targeted" },
76   { SOLVER_NOTBYUSER, "notbyuser" },
77   { SOLVER_SETEV,     "setev" },
78   { SOLVER_SETEVR,    "setevr" },
79   { SOLVER_SETARCH,   "setarch" },
80   { SOLVER_SETVENDOR, "setvendor" },
81   { SOLVER_SETREPO,   "setrepo" },
82   { SOLVER_NOAUTOSET, "noautoset" },
83   { 0, 0 }
84 };
85
86 static struct resultflags2str {
87   Id flag;
88   const char *str;
89 } resultflags2str[] = {
90   { TESTCASE_RESULT_TRANSACTION,        "transaction" },
91   { TESTCASE_RESULT_PROBLEMS,           "problems" },
92   { TESTCASE_RESULT_ORPHANED,           "orphaned" },
93   { TESTCASE_RESULT_RECOMMENDED,        "recommended" },
94   { TESTCASE_RESULT_UNNEEDED,           "unneeded" },
95   { TESTCASE_RESULT_ALTERNATIVES,       "alternatives" },
96   { TESTCASE_RESULT_RULES,              "rules" },
97   { TESTCASE_RESULT_GENID,              "genid" },
98   { TESTCASE_RESULT_REASON,             "reason" },
99   { TESTCASE_RESULT_CLEANDEPS,          "cleandeps" },
100   { TESTCASE_RESULT_JOBS,               "jobs" },
101   { TESTCASE_RESULT_USERINSTALLED,      "userinstalled" },
102   { 0, 0 }
103 };
104
105 static struct solverflags2str {
106   Id flag;
107   const char *str;
108   int def;
109 } solverflags2str[] = {
110   { SOLVER_FLAG_ALLOW_DOWNGRADE,            "allowdowngrade", 0 },
111   { SOLVER_FLAG_ALLOW_NAMECHANGE,           "allownamechange", 1 },
112   { SOLVER_FLAG_ALLOW_ARCHCHANGE,           "allowarchchange", 0 },
113   { SOLVER_FLAG_ALLOW_VENDORCHANGE,         "allowvendorchange", 0 },
114   { SOLVER_FLAG_ALLOW_UNINSTALL,            "allowuninstall", 0 },
115   { SOLVER_FLAG_NO_UPDATEPROVIDE,           "noupdateprovide", 0 },
116   { SOLVER_FLAG_SPLITPROVIDES,              "splitprovides", 0 },
117   { SOLVER_FLAG_IGNORE_RECOMMENDED,         "ignorerecommended", 0 },
118   { SOLVER_FLAG_ADD_ALREADY_RECOMMENDED,    "addalreadyrecommended", 0 },
119   { SOLVER_FLAG_NO_INFARCHCHECK,            "noinfarchcheck", 0 },
120   { SOLVER_FLAG_KEEP_EXPLICIT_OBSOLETES,    "keepexplicitobsoletes", 0 },
121   { SOLVER_FLAG_BEST_OBEY_POLICY,           "bestobeypolicy", 0 },
122   { SOLVER_FLAG_NO_AUTOTARGET,              "noautotarget", 0 },
123   { SOLVER_FLAG_DUP_ALLOW_DOWNGRADE,        "dupallowdowngrade", 1 },
124   { SOLVER_FLAG_DUP_ALLOW_ARCHCHANGE,       "dupallowarchchange", 1 },
125   { SOLVER_FLAG_DUP_ALLOW_VENDORCHANGE,     "dupallowvendorchange", 1 },
126   { SOLVER_FLAG_DUP_ALLOW_NAMECHANGE,       "dupallownamechange", 1 },
127   { SOLVER_FLAG_KEEP_ORPHANS,               "keeporphans", 0 },
128   { SOLVER_FLAG_BREAK_ORPHANS,              "breakorphans", 0 },
129   { SOLVER_FLAG_FOCUS_INSTALLED,            "focusinstalled", 0 },
130   { SOLVER_FLAG_YUM_OBSOLETES,              "yumobsoletes", 0 },
131   { SOLVER_FLAG_NEED_UPDATEPROVIDE,         "needupdateprovide", 0 },
132   { SOLVER_FLAG_URPM_REORDER,               "urpmreorder", 0 },
133   { SOLVER_FLAG_FOCUS_BEST,                 "focusbest", 0 },
134   { SOLVER_FLAG_STRONG_RECOMMENDS,          "strongrecommends", 0 },
135   { SOLVER_FLAG_INSTALL_ALSO_UPDATES,       "installalsoupdates", 0 },
136   { SOLVER_FLAG_ONLY_NAMESPACE_RECOMMENDED, "onlynamespacerecommended", 0 },
137   { 0, 0, 0 }
138 };
139
140 static struct poolflags2str {
141   Id flag;
142   const char *str;
143   int def;
144 } poolflags2str[] = {
145   { POOL_FLAG_PROMOTEEPOCH,                 "promoteepoch", 0 },
146   { POOL_FLAG_FORBIDSELFCONFLICTS,          "forbidselfconflicts", 0 },
147   { POOL_FLAG_OBSOLETEUSESPROVIDES,         "obsoleteusesprovides", 0 },
148   { POOL_FLAG_IMPLICITOBSOLETEUSESPROVIDES, "implicitobsoleteusesprovides", 0 },
149   { POOL_FLAG_OBSOLETEUSESCOLORS,           "obsoleteusescolors", 0 },
150   { POOL_FLAG_IMPLICITOBSOLETEUSESCOLORS,   "implicitobsoleteusescolors", 0 },
151   { POOL_FLAG_NOINSTALLEDOBSOLETES,         "noinstalledobsoletes", 0 },
152   { POOL_FLAG_HAVEDISTEPOCH,                "havedistepoch", 0 },
153   { POOL_FLAG_NOOBSOLETESMULTIVERSION,      "noobsoletesmultiversion", 0 },
154   { POOL_FLAG_ADDFILEPROVIDESFILTERED,      "addfileprovidesfiltered", 0 },
155   { POOL_FLAG_NOWHATPROVIDESAUX,            "nowhatprovidesaux", 0 },
156   { POOL_FLAG_WHATPROVIDESWITHDISABLED,     "whatprovideswithdisabled", 0 },
157   { 0, 0, 0 }
158 };
159
160 static struct disttype2str {
161   Id type;
162   const char *str;
163 } disttype2str[] = {
164   { DISTTYPE_RPM,  "rpm" },
165   { DISTTYPE_DEB,  "deb" },
166   { DISTTYPE_ARCH, "arch" },
167   { DISTTYPE_HAIKU, "haiku" },
168   { DISTTYPE_CONDA, "conda" },
169   { 0, 0 }
170 };
171
172 static struct selflags2str {
173   Id flag;
174   const char *str;
175 } selflags2str[] = {
176   { SELECTION_NAME, "name" },
177   { SELECTION_PROVIDES, "provides" },
178   { SELECTION_FILELIST, "filelist" },
179   { SELECTION_CANON, "canon" },
180   { SELECTION_DOTARCH, "dotarch" },
181   { SELECTION_REL, "rel" },
182   { SELECTION_INSTALLED_ONLY, "installedonly" },
183   { SELECTION_GLOB, "glob" },
184   { SELECTION_FLAT, "flat" },
185   { SELECTION_NOCASE, "nocase" },
186   { SELECTION_SOURCE_ONLY, "sourceonly" },
187   { SELECTION_WITH_SOURCE, "withsource" },
188   { SELECTION_SKIP_KIND, "skipkind" },
189   { SELECTION_MATCH_DEPSTR, "depstr" },
190   { SELECTION_WITH_DISABLED, "withdisabled" },
191   { SELECTION_WITH_BADARCH, "withbadarch" },
192   { SELECTION_ADD, "add" },
193   { SELECTION_SUBTRACT, "subtract" },
194   { SELECTION_FILTER, "filter" },
195   { 0, 0 }
196 };
197
198 static const char *features[] = {
199 #ifdef ENABLE_LINKED_PKGS
200   "linked_packages",
201 #endif
202 #ifdef ENABLE_COMPLEX_DEPS
203   "complex_deps",
204 #endif
205 #if ENABLE_TESTCASE_HELIXREPO
206   "testcase_helixrepo",
207 #endif
208   0
209 };
210
211 typedef struct strqueue {
212   char **str;
213   int nstr;
214 } Strqueue;
215
216 #define STRQUEUE_BLOCK 63
217
218 static void
219 strqueue_init(Strqueue *q)
220 {
221   q->str = 0;
222   q->nstr = 0;
223 }
224
225 static void
226 strqueue_free(Strqueue *q)
227 {
228   int i;
229   for (i = 0; i < q->nstr; i++)
230     solv_free(q->str[i]);
231   q->str = solv_free(q->str);
232   q->nstr = 0;
233 }
234
235 static void
236 strqueue_push(Strqueue *q, const char *s)
237 {
238   q->str = solv_extend(q->str, q->nstr, 1, sizeof(*q->str), STRQUEUE_BLOCK);
239   q->str[q->nstr++] = solv_strdup(s);
240 }
241
242 static void
243 strqueue_pushjoin(Strqueue *q, const char *s1, const char *s2, const char *s3)
244 {
245   q->str = solv_extend(q->str, q->nstr, 1, sizeof(*q->str), STRQUEUE_BLOCK);
246   q->str[q->nstr++] = solv_dupjoin(s1, s2, s3);
247 }
248
249 static int
250 strqueue_sort_cmp(const void *ap, const void *bp, void *dp)
251 {
252   const char *a = *(const char **)ap;
253   const char *b = *(const char **)bp;
254   return strcmp(a ? a : "", b ? b : "");
255 }
256
257 static void
258 strqueue_sort(Strqueue *q)
259 {
260   if (q->nstr > 1)
261     solv_sort(q->str, q->nstr, sizeof(*q->str), strqueue_sort_cmp, 0);
262 }
263
264 static void
265 strqueue_sort_u(Strqueue *q)
266 {
267   int i, j;
268   strqueue_sort(q);
269   for (i = j = 0; i < q->nstr; i++)
270     if (!j || strqueue_sort_cmp(q->str + i, q->str + j - 1, 0) != 0)
271       q->str[j++] = q->str[i];
272   q->nstr = j;
273 }
274
275 static char *
276 strqueue_join(Strqueue *q)
277 {
278   int i, l = 0;
279   char *r, *rp;
280   for (i = 0; i < q->nstr; i++)
281     if (q->str[i])
282       l += strlen(q->str[i]) + 1;
283   l++;  /* trailing \0 */
284   r = solv_malloc(l);
285   rp = r;
286   for (i = 0; i < q->nstr; i++)
287     if (q->str[i])
288       {
289         strcpy(rp, q->str[i]);
290         rp += strlen(rp);
291         *rp++ = '\n';
292       }
293   *rp = 0;
294   return r;
295 }
296
297 static void
298 strqueue_split(Strqueue *q, const char *s)
299 {
300   const char *p;
301   if (!s)
302     return;
303   while ((p = strchr(s, '\n')) != 0)
304     {
305       q->str = solv_extend(q->str, q->nstr, 1, sizeof(*q->str), STRQUEUE_BLOCK);
306       q->str[q->nstr] = solv_malloc(p - s + 1);
307       if (p > s)
308         memcpy(q->str[q->nstr], s, p - s);
309       q->str[q->nstr][p - s] = 0;
310       q->nstr++;
311       s = p + 1;
312     }
313   if (*s)
314     strqueue_push(q, s);
315 }
316
317 static void
318 strqueue_diff(Strqueue *sq1, Strqueue *sq2, Strqueue *osq)
319 {
320   int i = 0, j = 0;
321   while (i < sq1->nstr && j < sq2->nstr)
322     {
323       int r = strqueue_sort_cmp(sq1->str + i, sq2->str + j, 0);
324       if (!r)
325         i++, j++;
326       else if (r < 0)
327         strqueue_pushjoin(osq, "-", sq1->str[i++], 0);
328       else
329         strqueue_pushjoin(osq, "+", sq2->str[j++], 0);
330     }
331   while (i < sq1->nstr)
332     strqueue_pushjoin(osq, "-", sq1->str[i++], 0);
333   while (j < sq2->nstr)
334     strqueue_pushjoin(osq, "+", sq2->str[j++], 0);
335 }
336
337 /**********************************************************/
338
339 const char *
340 testcase_repoid2str(Pool *pool, Id repoid)
341 {
342   Repo *repo = pool_id2repo(pool, repoid);
343   if (repo->name)
344     {
345       char *r = pool_tmpjoin(pool, repo->name, 0, 0);
346       char *rp;
347       for (rp = r; *rp; rp++)
348         if (*rp == ' ' || *rp == '\t')
349           *rp = '_';
350       return r;
351     }
352   else
353     {
354       char buf[20];
355       sprintf(buf, "#%d", repoid);
356       return pool_tmpjoin(pool, buf, 0, 0);
357     }
358 }
359
360 const char *
361 testcase_solvid2str(Pool *pool, Id p)
362 {
363   Solvable *s = pool->solvables + p;
364   const char *n, *e, *a;
365   char *str, buf[20];
366
367   if (p == SYSTEMSOLVABLE)
368     return "@SYSTEM";
369   n = pool_id2str(pool, s->name);
370   e = pool_id2str(pool, s->evr);
371   a = pool_id2str(pool, s->arch);
372   str = pool_alloctmpspace(pool, strlen(n) + strlen(e) + strlen(a) + 3);
373   sprintf(str, "%s-%s", n, e);
374   if (solvable_lookup_type(s, SOLVABLE_BUILDFLAVOR))
375     {
376       Queue flavorq;
377       int i;
378
379       queue_init(&flavorq);
380       solvable_lookup_idarray(s, SOLVABLE_BUILDFLAVOR, &flavorq);
381       for (i = 0; i < flavorq.count; i++)
382         str = pool_tmpappend(pool, str, "-", pool_id2str(pool, flavorq.elements[i]));
383       queue_free(&flavorq);
384     }
385   if (s->arch)
386     str = pool_tmpappend(pool, str, ".", a);
387   if (!s->repo)
388     return pool_tmpappend(pool, str, "@", 0);
389   if (s->repo->name)
390     {
391       int l = strlen(str);
392       str = pool_tmpappend(pool, str, "@", s->repo->name);
393       for (; str[l]; l++)
394         if (str[l] == ' ' || str[l] == '\t')
395           str[l] = '_';
396       return str;
397     }
398   sprintf(buf, "@#%d", s->repo->repoid);
399   return pool_tmpappend(pool, str, buf, 0);
400 }
401
402 Repo *
403 testcase_str2repo(Pool *pool, const char *str)
404 {
405   int repoid;
406   Repo *repo = 0;
407   if (str[0] == '#' && (str[1] >= '0' && str[1] <= '9'))
408     {
409       int j;
410       repoid = 0;
411       for (j = 1; str[j] >= '0' && str[j] <= '9'; j++)
412         repoid = repoid * 10 + (str[j] - '0');
413       if (!str[j] && repoid > 0 && repoid < pool->nrepos)
414         repo = pool_id2repo(pool, repoid);
415     }
416   if (!repo)
417     {
418       FOR_REPOS(repoid, repo)
419         {
420           int i, l;
421           if (!repo->name)
422             continue;
423           l = strlen(repo->name);
424           for (i = 0; i < l; i++)
425             {
426               int c = repo->name[i];
427               if (c == ' ' || c == '\t')
428                 c = '_';
429               if (c != str[i])
430                 break;
431             }
432           if (i == l && !str[l])
433             break;
434         }
435       if (repoid >= pool->nrepos)
436         repo = 0;
437     }
438   return repo;
439 }
440
441 /* check evr and buildflavors */
442 static int
443 str2solvid_check(Pool *pool, Solvable *s, const char *start, const char *end, Id evrid)
444 {
445   if (!solvable_lookup_type(s, SOLVABLE_BUILDFLAVOR))
446     {
447       /* just check the evr */
448       return evrid && s->evr == evrid;
449     }
450   else
451     {
452       Queue flavorq;
453       int i;
454
455       queue_init(&flavorq);
456       solvable_lookup_idarray(s, SOLVABLE_BUILDFLAVOR, &flavorq);
457       queue_unshift(&flavorq, s->evr);
458       for (i = 0; i < flavorq.count; i++)
459         {
460           const char *part = pool_id2str(pool, flavorq.elements[i]);
461           size_t partl = strlen(part);
462           if (start + partl > end || strncmp(start, part, partl) != 0)
463             break;
464           start += partl;
465           if (i + 1 < flavorq.count)
466             {
467               if (start >= end || *start != '-')
468                 break;
469               start++;
470             }
471         }
472       if (i < flavorq.count)
473         {
474           queue_free(&flavorq);
475           return 0;
476         }
477       queue_free(&flavorq);
478       return start == end;
479     }
480 }
481
482 Id
483 testcase_str2solvid(Pool *pool, const char *str)
484 {
485   int i, l = strlen(str);
486   int repostart;
487   Repo *repo;
488   Id arch;
489
490   if (!l)
491     return 0;
492   if (*str == '@' && !strcmp(str, "@SYSTEM"))
493     return SYSTEMSOLVABLE;
494   repo = 0;
495   for (i = l - 1; i >= 0; i--)
496     if (str[i] == '@' && (repo = testcase_str2repo(pool, str + i + 1)) != 0)
497       break;
498   if (i < 0)
499     i = l;
500   repostart = i;
501   /* now find the arch (if present) */
502   arch = 0;
503   for (i = repostart - 1; i > 0; i--)
504     if (str[i] == '.')
505       {
506         arch = pool_strn2id(pool, str + i + 1, repostart - (i + 1), 0);
507         if (arch)
508           repostart = i;
509         break;
510       }
511   /* now find the name */
512   for (i = repostart - 1; i > 0; i--)
513     {
514       if (str[i] == '-')
515         {
516           Id nid, evrid, p, pp;
517           nid = pool_strn2id(pool, str, i, 0);
518           if (!nid)
519             continue;
520           evrid = pool_strn2id(pool, str + i + 1, repostart - (i + 1), 0);
521           /* first check whatprovides */
522           FOR_PROVIDES(p, pp, nid)
523             {
524               Solvable *s = pool->solvables + p;
525               if (s->name != nid)
526                 continue;
527               if (repo && s->repo != repo)
528                 continue;
529               if (arch && s->arch != arch)
530                 continue;
531               if (str2solvid_check(pool, s, str + i + 1, str + repostart, evrid))
532                 return p;
533             }
534           /* maybe it's not installable and thus not in whatprovides. do a slow search */
535           if (repo)
536             {
537               Solvable *s;
538               FOR_REPO_SOLVABLES(repo, p, s)
539                 {
540                   if (s->name != nid)
541                     continue;
542                   if (arch && s->arch != arch)
543                     continue;
544                   if (str2solvid_check(pool, s, str + i + 1, str + repostart, evrid))
545                     return p;
546                 }
547             }
548           else
549             {
550               FOR_POOL_SOLVABLES(p)
551                 {
552                   Solvable *s = pool->solvables + p;
553                   if (s->name != nid)
554                     continue;
555                   if (arch && s->arch != arch)
556                     continue;
557                   if (str2solvid_check(pool, s, str + i + 1, str + repostart, evrid))
558                     return p;
559                 }
560             }
561         }
562     }
563   return 0;
564 }
565
566 const char *
567 testcase_job2str(Pool *pool, Id how, Id what)
568 {
569   char *ret;
570   const char *jobstr;
571   const char *selstr;
572   const char *pkgstr;
573   int i, o;
574   Id select = how & SOLVER_SELECTMASK;
575
576   for (i = 0; job2str[i].str; i++)
577     if ((how & SOLVER_JOBMASK) == job2str[i].job)
578       break;
579   jobstr = job2str[i].str ? job2str[i].str : "unknown";
580   if (select == SOLVER_SOLVABLE)
581     {
582       selstr = " pkg ";
583       pkgstr = testcase_solvid2str(pool, what);
584     }
585   else if (select == SOLVER_SOLVABLE_NAME)
586     {
587       selstr = " name ";
588       pkgstr = testcase_dep2str(pool, what);
589     }
590   else if (select == SOLVER_SOLVABLE_PROVIDES)
591     {
592       selstr = " provides ";
593       pkgstr = testcase_dep2str(pool, what);
594     }
595   else if (select == SOLVER_SOLVABLE_ONE_OF)
596     {
597       Id p;
598       selstr = " oneof ";
599       pkgstr = 0;
600       while ((p = pool->whatprovidesdata[what++]) != 0)
601         {
602           const char *s = testcase_solvid2str(pool, p);
603           if (pkgstr)
604             {
605               pkgstr = pool_tmpappend(pool, pkgstr, " ", s);
606               pool_freetmpspace(pool, s);
607             }
608           else
609             pkgstr = s;
610         }
611       if (!pkgstr)
612         pkgstr = "nothing";
613     }
614   else if (select == SOLVER_SOLVABLE_REPO)
615     {
616       Repo *repo = pool_id2repo(pool, what);
617       selstr = " repo ";
618       if (!repo->name)
619         {
620           char buf[20];
621           sprintf(buf, "#%d", repo->repoid);
622           pkgstr = pool_tmpjoin(pool, buf, 0, 0);
623         }
624       else
625         pkgstr = pool_tmpjoin(pool, repo->name, 0, 0);
626     }
627   else if (select == SOLVER_SOLVABLE_ALL)
628     {
629       selstr = " all ";
630       pkgstr = "packages";
631     }
632   else
633     {
634       selstr = " unknown ";
635       pkgstr = "unknown";
636     }
637   ret = pool_tmpjoin(pool, jobstr, selstr, pkgstr);
638   o = strlen(ret);
639   ret = pool_tmpappend(pool, ret, " ", 0);
640   for (i = 0; jobflags2str[i].str; i++)
641     if ((how & jobflags2str[i].flag) != 0)
642       ret = pool_tmpappend(pool, ret, ",", jobflags2str[i].str);
643   if (!ret[o + 1])
644     ret[o] = 0;
645   else
646     {
647       ret[o + 1] = '[';
648       ret = pool_tmpappend(pool, ret, "]", 0);
649     }
650   return ret;
651 }
652
653 static int
654 str2selflags(Pool *pool, char *s)       /* modifies the string! */
655 {
656   int i, selflags = 0;
657   while (s)
658     {
659       char *se = strchr(s, ',');
660       if (se)
661         *se++ = 0;
662       for (i = 0; selflags2str[i].str; i++)
663         if (!strcmp(s, selflags2str[i].str))
664           {
665             selflags |= selflags2str[i].flag;
666             break;
667           }
668       if (!selflags2str[i].str)
669         pool_error(pool, 0, "str2job: unknown selection flag '%s'", s);
670       s = se;
671     }
672   return selflags;
673 }
674
675 static int
676 str2jobflags(Pool *pool, char *s)       /* modifies the string */
677 {
678   int i, jobflags = 0;
679   while (s)
680     {
681       char *se = strchr(s, ',');
682       if (se)
683         *se++ = 0;
684       for (i = 0; jobflags2str[i].str; i++)
685         if (!strcmp(s, jobflags2str[i].str))
686           {
687             jobflags |= jobflags2str[i].flag;
688             break;
689           }
690       if (!jobflags2str[i].str)
691         pool_error(pool, 0, "str2job: unknown job flag '%s'", s);
692       s = se;
693     }
694   return jobflags;
695 }
696
697 static Id
698 testcase_str2jobsel(Pool *pool, const char *caller, char **pieces, int npieces, Id *whatp)
699 {
700   Id job, what;
701   if (!strcmp(pieces[0], "pkg") && npieces == 2)
702     {
703       job = SOLVER_SOLVABLE;
704       what = testcase_str2solvid(pool, pieces[1]);
705       if (!what)
706         return pool_error(pool, -1, "%s: unknown package '%s'", caller, pieces[1]);
707     }
708   else if (!strcmp(pieces[0], "name") || !strcmp(pieces[0], "provides"))
709     {
710       /* join em again for dep2str... */
711       char *sp;
712       for (sp = pieces[1]; sp < pieces[npieces - 1]; sp++)
713         if (*sp == 0)
714           *sp = ' ';
715       what = 0;
716       if (pieces[0][0] == 'p' && strncmp(pieces[1], "namespace:", 10) == 0)
717         {
718           char *spe = strchr(pieces[1], '(');
719           int l = strlen(pieces[1]);
720           if (spe && pieces[1][l - 1] == ')')
721             {
722               /* special namespace provides */
723               if (strcmp(spe, "(<NULL>)") != 0)
724                 {
725                   pieces[1][l - 1] = 0;
726                   what = testcase_str2dep(pool, spe + 1);
727                   pieces[1][l - 1] = ')';
728                 }
729               what = pool_rel2id(pool, pool_strn2id(pool, pieces[1], spe - pieces[1], 1), what, REL_NAMESPACE, 1);
730             }
731         }
732       if (!what)
733         what = testcase_str2dep(pool, pieces[1]);
734       if (pieces[0][0] == 'n')
735         job = SOLVER_SOLVABLE_NAME;
736       else
737         job = SOLVER_SOLVABLE_PROVIDES;
738     }
739   else if (!strcmp(pieces[0], "oneof"))
740     {
741       Queue q;
742       job = SOLVER_SOLVABLE_ONE_OF;
743       queue_init(&q);
744       if (npieces > 1 && strcmp(pieces[1], "nothing") != 0)
745         {
746           int i;
747           for (i = 1; i < npieces; i++)
748             {
749               Id p = testcase_str2solvid(pool, pieces[i]);
750               if (!p)
751                 {
752                   queue_free(&q);
753                   return pool_error(pool, -1, "%s: unknown package '%s'", caller, pieces[i]);
754                 }
755               queue_push(&q, p);
756             }
757         }
758       what = pool_queuetowhatprovides(pool, &q);
759       queue_free(&q);
760     }
761   else if (!strcmp(pieces[0], "repo") && npieces == 2)
762     {
763       Repo *repo = testcase_str2repo(pool, pieces[1]);
764       if (!repo)
765         return pool_error(pool, -1, "%s: unknown repo '%s'", caller, pieces[1]);
766       job = SOLVER_SOLVABLE_REPO;
767       what = repo->repoid;
768     }
769   else if (!strcmp(pieces[0], "all") && npieces == 2 && !strcmp(pieces[1], "packages"))
770     {
771       job = SOLVER_SOLVABLE_ALL;
772       what = 0;
773     }
774   else
775     {
776       /* join em again for the error message... */
777       char *sp;
778       for (sp = pieces[0]; sp < pieces[npieces - 1]; sp++)
779         if (*sp == 0)
780           *sp = ' ';
781       return pool_error(pool, -1, "%s: bad line '%s'", caller, pieces[0]);
782     }
783   *whatp = what;
784   return job;
785 }
786
787 Id
788 testcase_str2job(Pool *pool, const char *str, Id *whatp)
789 {
790   int i;
791   Id job, jobsel;
792   Id what;
793   char *s;
794   char **pieces = 0;
795   int npieces = 0;
796
797   *whatp = 0;
798   /* so we can patch it */
799   s = pool_tmpjoin(pool, str, 0, 0);
800   /* split it in pieces */
801   for (;;)
802     {
803       while (*s == ' ' || *s == '\t')
804         s++;
805       if (!*s)
806         break;
807       pieces = solv_extend(pieces, npieces, 1, sizeof(*pieces), 7);
808       pieces[npieces++] = s;
809       while (*s && *s != ' ' && *s != '\t')
810         s++;
811       if (*s)
812         *s++ = 0;
813     }
814   if (npieces < 3)
815     {
816       pool_error(pool, -1, "str2job: bad line '%s'", str);
817       solv_free(pieces);
818       return -1;
819     }
820
821   for (i = 0; job2str[i].str; i++)
822     if (!strcmp(pieces[0], job2str[i].str))
823       break;
824   if (!job2str[i].str)
825     {
826       pool_error(pool, -1, "str2job: unknown job '%s'", str);
827       solv_free(pieces);
828       return -1;
829     }
830   job = job2str[i].job;
831   what = 0;
832   if (npieces > 3)
833     {
834       char *flags = pieces[npieces - 1];
835       if (*flags == '[' && flags[strlen(flags) - 1] == ']')
836         {
837           npieces--;
838           flags++;
839           flags[strlen(flags) - 1] = 0;
840           job |= str2jobflags(pool, flags);
841         }
842     }
843   jobsel = testcase_str2jobsel(pool, "str2job", pieces + 1, npieces - 1, &what);
844   solv_free(pieces);
845   if (jobsel == -1)
846     return -1;
847   *whatp = what;
848   return job | jobsel;
849 }
850
851 #define SELECTIONJOB_MATCHDEPS          1
852 #define SELECTIONJOB_MATCHDEPID         2
853 #define SELECTIONJOB_MATCHSOLVABLE      3
854
855 static int
856 addselectionjob(Pool *pool, char **pieces, int npieces, Queue *jobqueue, int type, int keyname)
857 {
858   Id job;
859   int i, r = 0;
860   int selflags;
861   Queue sel;
862   char *sp;
863
864   for (i = 0; job2str[i].str; i++)
865     if (!strcmp(pieces[0], job2str[i].str))
866       break;
867   if (!job2str[i].str)
868     return pool_error(pool, -1, "selstr2job: unknown job '%s'", pieces[0]);
869   job = job2str[i].job;
870   if (npieces > 3)
871     {
872       char *flags = pieces[npieces - 1];
873       if (*flags == '[' && flags[strlen(flags) - 1] == ']')
874         {
875           npieces--;
876           flags++;
877           flags[strlen(flags) - 1] = 0;
878           job |= str2jobflags(pool, flags);
879         }
880     }
881   if (npieces < 4)
882     return pool_error(pool, -1, "selstr2job: no selection flags");
883   selflags = str2selflags(pool, pieces[npieces - 1]);
884   /* re-join pieces */
885   for (sp = pieces[2]; sp < pieces[npieces - 2]; sp++)
886     if (*sp == 0)
887       *sp = ' ';
888   queue_init(&sel);
889   if (selflags & (SELECTION_ADD | SELECTION_SUBTRACT | SELECTION_FILTER))
890     {
891       for (i = 0; i < jobqueue->count; i += 2)
892         queue_push2(&sel, jobqueue->elements[i] & (SOLVER_SELECTMASK | SOLVER_SETMASK), jobqueue->elements[i + 1]);
893       queue_empty(jobqueue);
894     }
895   if (!type)
896     r = selection_make(pool, &sel, pieces[2], selflags);
897   else if (type == SELECTIONJOB_MATCHDEPS)
898     r = selection_make_matchdeps(pool, &sel, pieces[2], selflags, keyname, 0);
899   else if (type == SELECTIONJOB_MATCHDEPID)
900     r = selection_make_matchdepid(pool, &sel, testcase_str2dep(pool, pieces[2]), selflags, keyname, 0);
901   else if (type == SELECTIONJOB_MATCHSOLVABLE)
902     r = selection_make_matchsolvable(pool, &sel, testcase_str2solvid(pool, pieces[2]), selflags, keyname, 0);
903   for (i = 0; i < sel.count; i += 2)
904     queue_push2(jobqueue, job | sel.elements[i], sel.elements[i + 1]);
905   queue_free(&sel);
906   return r;
907 }
908
909 const char *
910 testcase_getpoolflags(Pool *pool)
911 {
912   const char *str = 0;
913   int i, v;
914   for (i = 0; poolflags2str[i].str; i++)
915     {
916       v = pool_get_flag(pool, poolflags2str[i].flag);
917       if (v == poolflags2str[i].def)
918         continue;
919       str = pool_tmpappend(pool, str, v ? " " : " !", poolflags2str[i].str);
920     }
921   return str ? str + 1 : "";
922 }
923
924 int
925 testcase_setpoolflags(Pool *pool, const char *str)
926 {
927   const char *p = str, *s;
928   int i, v;
929   for (;;)
930     {
931       while (*p == ' ' || *p == '\t' || *p == ',')
932         p++;
933       v = 1;
934       if (*p == '!')
935         {
936           p++;
937           v = 0;
938         }
939       if (!*p)
940         break;
941       s = p;
942       while (*p && *p != ' ' && *p != '\t' && *p != ',')
943         p++;
944       for (i = 0; poolflags2str[i].str; i++)
945         if (!strncmp(poolflags2str[i].str, s, p - s) && poolflags2str[i].str[p - s] == 0)
946           break;
947       if (!poolflags2str[i].str)
948         return pool_error(pool, 0, "setpoolflags: unknown flag '%.*s'", (int)(p - s), s);
949       pool_set_flag(pool, poolflags2str[i].flag, v);
950     }
951   return 1;
952 }
953
954 void
955 testcase_resetpoolflags(Pool *pool)
956 {
957   int i;
958   for (i = 0; poolflags2str[i].str; i++)
959     pool_set_flag(pool, poolflags2str[i].flag, poolflags2str[i].def);
960 }
961
962 const char *
963 testcase_getsolverflags(Solver *solv)
964 {
965   Pool *pool = solv->pool;
966   const char *str = 0;
967   int i, v;
968   for (i = 0; solverflags2str[i].str; i++)
969     {
970       v = solver_get_flag(solv, solverflags2str[i].flag);
971       if (v == solverflags2str[i].def)
972         continue;
973       str = pool_tmpappend(pool, str, v ? " " : " !", solverflags2str[i].str);
974     }
975   return str ? str + 1 : "";
976 }
977
978 int
979 testcase_setsolverflags(Solver *solv, const char *str)
980 {
981   const char *p = str, *s;
982   int i, v;
983   for (;;)
984     {
985       while (*p == ' ' || *p == '\t' || *p == ',')
986         p++;
987       v = 1;
988       if (*p == '!')
989         {
990           p++;
991           v = 0;
992         }
993       if (!*p)
994         break;
995       s = p;
996       while (*p && *p != ' ' && *p != '\t' && *p != ',')
997         p++;
998       for (i = 0; solverflags2str[i].str; i++)
999         if (!strncmp(solverflags2str[i].str, s, p - s) && solverflags2str[i].str[p - s] == 0)
1000           break;
1001       if (!solverflags2str[i].str)
1002         return pool_error(solv->pool, 0, "setsolverflags: unknown flag '%.*s'", (int)(p - s), s);
1003       if (solver_set_flag(solv, solverflags2str[i].flag, v) == -1)
1004         return pool_error(solv->pool, 0, "setsolverflags: unsupported flag '%s'", solverflags2str[i].str);
1005     }
1006   return 1;
1007 }
1008
1009 void
1010 testcase_resetsolverflags(Solver *solv)
1011 {
1012   int i;
1013   for (i = 0; solverflags2str[i].str; i++)
1014     solver_set_flag(solv, solverflags2str[i].flag, solverflags2str[i].def);
1015 }
1016
1017 static const char *
1018 testcase_ruleid(Solver *solv, Id rid)
1019 {
1020   Strqueue sq;
1021   Queue q;
1022   int i;
1023   Chksum *chk;
1024   const unsigned char *md5;
1025   int md5l;
1026   const char *s;
1027
1028   queue_init(&q);
1029   strqueue_init(&sq);
1030   solver_ruleliterals(solv, rid, &q);
1031   for (i = 0; i < q.count; i++)
1032     {
1033       Id p = q.elements[i];
1034       s = testcase_solvid2str(solv->pool, p > 0 ? p : -p);
1035       if (p < 0)
1036         s = pool_tmpjoin(solv->pool, "!", s, 0);
1037       strqueue_push(&sq, s);
1038     }
1039   queue_free(&q);
1040   strqueue_sort_u(&sq);
1041   chk = solv_chksum_create(REPOKEY_TYPE_MD5);
1042   for (i = 0; i < sq.nstr; i++)
1043     solv_chksum_add(chk, sq.str[i], strlen(sq.str[i]) + 1);
1044   md5 = solv_chksum_get(chk, &md5l);
1045   s = pool_bin2hex(solv->pool, md5, md5l);
1046   chk = solv_chksum_free(chk, 0);
1047   strqueue_free(&sq);
1048   return s;
1049 }
1050
1051 static const char *
1052 testcase_problemid(Solver *solv, Id problem)
1053 {
1054   Strqueue sq;
1055   Queue q;
1056   Chksum *chk;
1057   const unsigned char *md5;
1058   int i, md5l;
1059   const char *s;
1060
1061   /* we build a hash of all rules that define the problem */
1062   queue_init(&q);
1063   strqueue_init(&sq);
1064   solver_findallproblemrules(solv, problem, &q);
1065   for (i = 0; i < q.count; i++)
1066     strqueue_push(&sq, testcase_ruleid(solv, q.elements[i]));
1067   queue_free(&q);
1068   strqueue_sort_u(&sq);
1069   chk = solv_chksum_create(REPOKEY_TYPE_MD5);
1070   for (i = 0; i < sq.nstr; i++)
1071     solv_chksum_add(chk, sq.str[i], strlen(sq.str[i]) + 1);
1072   md5 = solv_chksum_get(chk, &md5l);
1073   s = pool_bin2hex(solv->pool, md5, 4);
1074   chk = solv_chksum_free(chk, 0);
1075   strqueue_free(&sq);
1076   return s;
1077 }
1078
1079 static const char *
1080 testcase_solutionid(Solver *solv, Id problem, Id solution)
1081 {
1082   Id intid;
1083   Chksum *chk;
1084   const unsigned char *md5;
1085   int md5l;
1086   const char *s;
1087
1088   intid = solver_solutionelement_internalid(solv, problem, solution);
1089   /* internal stuff! handle with care! */
1090   if (intid < 0)
1091     {
1092       /* it's a job */
1093       s = testcase_job2str(solv->pool, solv->job.elements[-intid - 1], solv->job.elements[-intid]);
1094     }
1095   else
1096     {
1097       /* it's a rule */
1098       s = testcase_ruleid(solv, intid);
1099     }
1100   chk = solv_chksum_create(REPOKEY_TYPE_MD5);
1101   solv_chksum_add(chk, s, strlen(s) + 1);
1102   md5 = solv_chksum_get(chk, &md5l);
1103   s = pool_bin2hex(solv->pool, md5, 4);
1104   chk = solv_chksum_free(chk, 0);
1105   return s;
1106 }
1107
1108 static const char *
1109 testcase_alternativeid(Solver *solv, int type, Id id, Id from)
1110 {
1111   const char *s;
1112   Pool *pool = solv->pool;
1113   Chksum *chk;
1114   const unsigned char *md5;
1115   int md5l;
1116   chk = solv_chksum_create(REPOKEY_TYPE_MD5);
1117   if (type == SOLVER_ALTERNATIVE_TYPE_RECOMMENDS)
1118     {
1119       s = testcase_solvid2str(pool, from);
1120       solv_chksum_add(chk, s, strlen(s) + 1);
1121       s = testcase_dep2str(pool, id);
1122       solv_chksum_add(chk, s, strlen(s) + 1);
1123     }
1124   else if (type == SOLVER_ALTERNATIVE_TYPE_RULE)
1125     {
1126       s = testcase_ruleid(solv, id);
1127       solv_chksum_add(chk, s, strlen(s) + 1);
1128     }
1129   md5 = solv_chksum_get(chk, &md5l);
1130   s = pool_bin2hex(pool, md5, 4);
1131   chk = solv_chksum_free(chk, 0);
1132   return s;
1133 }
1134
1135 static struct class2str {
1136   Id class;
1137   const char *str;
1138 } class2str[] = {
1139   { SOLVER_TRANSACTION_ERASE,          "erase" },
1140   { SOLVER_TRANSACTION_INSTALL,        "install" },
1141   { SOLVER_TRANSACTION_REINSTALLED,    "reinstall" },
1142   { SOLVER_TRANSACTION_DOWNGRADED,     "downgrade" },
1143   { SOLVER_TRANSACTION_CHANGED,        "change" },
1144   { SOLVER_TRANSACTION_UPGRADED,       "upgrade" },
1145   { SOLVER_TRANSACTION_OBSOLETED,      "obsolete" },
1146   { SOLVER_TRANSACTION_MULTIINSTALL,   "multiinstall" },
1147   { SOLVER_TRANSACTION_MULTIREINSTALL, "multireinstall" },
1148   { 0, 0 }
1149 };
1150
1151 static struct reason2str {
1152   Id reason;
1153   const char *str;
1154 } reason2str[] = {
1155   { SOLVER_REASON_UNRELATED,            "unrelated" },
1156   { SOLVER_REASON_UNIT_RULE,            "unit" },
1157   { SOLVER_REASON_KEEP_INSTALLED,       "keep" },
1158   { SOLVER_REASON_RESOLVE_JOB,          "job" },
1159   { SOLVER_REASON_UPDATE_INSTALLED,     "update" },
1160   { SOLVER_REASON_CLEANDEPS_ERASE,      "cleandeps" },
1161   { SOLVER_REASON_RESOLVE,              "resolve" },
1162   { SOLVER_REASON_WEAKDEP,              "weakdep" },
1163   { SOLVER_REASON_RESOLVE_ORPHAN,       "orphan" },
1164
1165   { SOLVER_REASON_RECOMMENDED,          "recommended" },
1166   { SOLVER_REASON_SUPPLEMENTED,         "supplemented" },
1167   { 0, 0 }
1168 };
1169
1170 static const char *
1171 testcase_reason2str(Id reason)
1172 {
1173   int i;
1174   for (i = 0; reason2str[i].str; i++)
1175     if (reason == reason2str[i].reason)
1176       return reason2str[i].str;
1177   return "?";
1178 }
1179
1180 static struct rclass2str {
1181   Id rclass;
1182   const char *str;
1183 } rclass2str[] = {
1184   { SOLVER_RULE_PKG, "pkg" },
1185   { SOLVER_RULE_UPDATE, "update" },
1186   { SOLVER_RULE_FEATURE, "feature" },
1187   { SOLVER_RULE_JOB, "job" },
1188   { SOLVER_RULE_DISTUPGRADE, "distupgrade" },
1189   { SOLVER_RULE_INFARCH, "infarch" },
1190   { SOLVER_RULE_CHOICE, "choice" },
1191   { SOLVER_RULE_LEARNT, "learnt" },
1192   { SOLVER_RULE_BEST, "best" },
1193   { SOLVER_RULE_YUMOBS, "yumobs" },
1194   { SOLVER_RULE_BLACK, "black" },
1195   { SOLVER_RULE_RECOMMENDS, "recommends" },
1196   { 0, 0 }
1197 };
1198
1199 static const char *
1200 testcase_rclass2str(Id rclass)
1201 {
1202   int i;
1203   for (i = 0; rclass2str[i].str; i++)
1204     if (rclass == rclass2str[i].rclass)
1205       return rclass2str[i].str;
1206   return "unknown";
1207 }
1208
1209 static int
1210 dump_genid(Pool *pool, Strqueue *sq, Id id, int cnt)
1211 {
1212   struct oplist *op;
1213   char cntbuf[20];
1214   const char *s;
1215
1216   if (ISRELDEP(id))
1217     {
1218       Reldep *rd = GETRELDEP(pool, id);
1219       for (op = oplist; op->flags; op++)
1220         if (rd->flags == op->flags)
1221           break;
1222       cnt = dump_genid(pool, sq, rd->name, cnt);
1223       cnt = dump_genid(pool, sq, rd->evr, cnt);
1224       sprintf(cntbuf, "genid %2d: genid ", cnt++);
1225       s = pool_tmpjoin(pool, cntbuf, "op ", op->flags ? op->opname : "unknown");
1226     }
1227   else
1228     {
1229       sprintf(cntbuf, "genid %2d: genid ", cnt++);
1230       s = pool_tmpjoin(pool, cntbuf, id ? "lit " : "null", id ? pool_id2str(pool, id) : 0);
1231     }
1232   strqueue_push(sq, s);
1233   return cnt;
1234 }
1235
1236 char *
1237 testcase_solverresult(Solver *solv, int resultflags)
1238 {
1239   Pool *pool = solv->pool;
1240   int i, j;
1241   Id p, op;
1242   const char *s;
1243   char *result;
1244   Strqueue sq;
1245
1246   strqueue_init(&sq);
1247   if ((resultflags & TESTCASE_RESULT_TRANSACTION) != 0)
1248     {
1249       Transaction *trans = solver_create_transaction(solv);
1250       Queue q;
1251
1252       queue_init(&q);
1253       for (i = 0; class2str[i].str; i++)
1254         {
1255           queue_empty(&q);
1256           transaction_classify_pkgs(trans, SOLVER_TRANSACTION_KEEP_PSEUDO, class2str[i].class, 0, 0, &q);
1257           for (j = 0; j < q.count; j++)
1258             {
1259               p = q.elements[j];
1260               op = 0;
1261               if (pool->installed && pool->solvables[p].repo == pool->installed)
1262                 op = transaction_obs_pkg(trans, p);
1263               s = pool_tmpjoin(pool, class2str[i].str, " ", testcase_solvid2str(pool, p));
1264               if (op)
1265                 s = pool_tmpjoin(pool, s, " ", testcase_solvid2str(pool, op));
1266               strqueue_push(&sq, s);
1267             }
1268         }
1269       queue_free(&q);
1270       transaction_free(trans);
1271     }
1272   if ((resultflags & TESTCASE_RESULT_PROBLEMS) != 0)
1273     {
1274       char *probprefix, *solprefix;
1275       int problem, solution, element;
1276       int pcnt, scnt;
1277
1278       pcnt = solver_problem_count(solv);
1279       for (problem = 1; problem <= pcnt; problem++)
1280         {
1281           Id rid, from, to, dep;
1282           SolverRuleinfo rinfo;
1283           rid = solver_findproblemrule(solv, problem);
1284           s = testcase_problemid(solv, problem);
1285           probprefix = solv_dupjoin("problem ", s, 0);
1286           rinfo = solver_ruleinfo(solv, rid, &from, &to, &dep);
1287           s = pool_tmpjoin(pool, probprefix, " info ", solver_problemruleinfo2str(solv, rinfo, from, to, dep));
1288           strqueue_push(&sq, s);
1289           scnt = solver_solution_count(solv, problem);
1290           for (solution = 1; solution <= scnt; solution++)
1291             {
1292               s = testcase_solutionid(solv, problem, solution);
1293               solprefix = solv_dupjoin(probprefix, " solution ", s);
1294               element = 0;
1295               while ((element = solver_next_solutionelement(solv, problem, solution, element, &p, &op)) != 0)
1296                 {
1297                   if (p == SOLVER_SOLUTION_JOB)
1298                     s = pool_tmpjoin(pool, solprefix, " deljob ", testcase_job2str(pool, solv->job.elements[op - 1], solv->job.elements[op]));
1299                   else if (p > 0 && op == 0)
1300                     s = pool_tmpjoin(pool, solprefix, " erase ", testcase_solvid2str(pool, p));
1301                   else if (p > 0 && op > 0)
1302                     {
1303                       s = pool_tmpjoin(pool, solprefix, " replace ", testcase_solvid2str(pool, p));
1304                       s = pool_tmpappend(pool, s, " ", testcase_solvid2str(pool, op));
1305                     }
1306                   else if (p < 0 && op > 0)
1307                     s = pool_tmpjoin(pool, solprefix, " allow ", testcase_solvid2str(pool, op));
1308                   else
1309                     s = pool_tmpjoin(pool, solprefix, " unknown", 0);
1310                   strqueue_push(&sq, s);
1311                 }
1312               solv_free(solprefix);
1313             }
1314           solv_free(probprefix);
1315         }
1316     }
1317
1318   if ((resultflags & TESTCASE_RESULT_ORPHANED) != 0)
1319     {
1320       Queue q;
1321
1322       queue_init(&q);
1323       solver_get_orphaned(solv, &q);
1324       for (i = 0; i < q.count; i++)
1325         {
1326           s = pool_tmpjoin(pool, "orphaned ", testcase_solvid2str(pool, q.elements[i]), 0);
1327           strqueue_push(&sq, s);
1328         }
1329       queue_free(&q);
1330     }
1331
1332   if ((resultflags & TESTCASE_RESULT_RECOMMENDED) != 0)
1333     {
1334       Queue qr, qs;
1335
1336       queue_init(&qr);
1337       queue_init(&qs);
1338       solver_get_recommendations(solv, &qr, &qs, 0);
1339       for (i = 0; i < qr.count; i++)
1340         {
1341           s = pool_tmpjoin(pool, "recommended ", testcase_solvid2str(pool, qr.elements[i]), 0);
1342           strqueue_push(&sq, s);
1343         }
1344       for (i = 0; i < qs.count; i++)
1345         {
1346           s = pool_tmpjoin(pool, "suggested ", testcase_solvid2str(pool, qs.elements[i]), 0);
1347           strqueue_push(&sq, s);
1348         }
1349       queue_free(&qr);
1350       queue_free(&qs);
1351     }
1352
1353   if ((resultflags & TESTCASE_RESULT_UNNEEDED) != 0)
1354     {
1355       Queue q, qf;
1356
1357       queue_init(&q);
1358       queue_init(&qf);
1359       solver_get_unneeded(solv, &q, 0);
1360       solver_get_unneeded(solv, &qf, 1);
1361       for (i = j = 0; i < q.count; i++)
1362         {
1363           /* we rely on qf containing a subset of q in the same order */
1364           if (j < qf.count && q.elements[i] == qf.elements[j])
1365             {
1366               s = pool_tmpjoin(pool, "unneeded_filtered ", testcase_solvid2str(pool, q.elements[i]), 0);
1367               j++;
1368             }
1369           else
1370             s = pool_tmpjoin(pool, "unneeded ", testcase_solvid2str(pool, q.elements[i]), 0);
1371           strqueue_push(&sq, s);
1372         }
1373       queue_free(&q);
1374       queue_free(&qf);
1375     }
1376   if ((resultflags & TESTCASE_RESULT_USERINSTALLED) != 0)
1377     {
1378       Queue q;
1379       solver_get_userinstalled(solv, &q, 0);
1380       for (i = 0; i < q.count; i++)
1381         {
1382           s = pool_tmpjoin(pool, "userinstalled pkg ", testcase_solvid2str(pool, q.elements[i]), 0);
1383           strqueue_push(&sq, s);
1384         }
1385       queue_empty(&q);
1386       solver_get_userinstalled(solv, &q, GET_USERINSTALLED_NAMES | GET_USERINSTALLED_INVERTED);
1387       for (i = 0; i < q.count; i++)
1388         {
1389           s = pool_tmpjoin(pool, "autoinst name ", pool_id2str(pool, q.elements[i]), 0);
1390           strqueue_push(&sq, s);
1391         }
1392       queue_free(&q);
1393     }
1394   if ((resultflags & TESTCASE_RESULT_ALTERNATIVES) != 0)
1395     {
1396       char *altprefix;
1397       Queue q, rq;
1398       int cnt;
1399       Id alternative;
1400       queue_init(&q);
1401       queue_init(&rq);
1402       cnt = solver_alternatives_count(solv);
1403       for (alternative = 1; alternative <= cnt; alternative++)
1404         {
1405           Id id, from, chosen;
1406           char num[20];
1407           int type = solver_get_alternative(solv, alternative, &id, &from, &chosen, &q, 0);
1408           altprefix = solv_dupjoin("alternative ", testcase_alternativeid(solv, type, id, from), " ");
1409           strcpy(num, " 0 ");
1410           if (type == SOLVER_ALTERNATIVE_TYPE_RECOMMENDS)
1411             {
1412               char *s = pool_tmpjoin(pool, altprefix, num, testcase_solvid2str(pool, from));
1413               s = pool_tmpappend(pool, s, " recommends ", testcase_dep2str(pool, id));
1414               strqueue_push(&sq, s);
1415             }
1416           else if (type == SOLVER_ALTERNATIVE_TYPE_RULE)
1417             {
1418               /* map choice rules back to pkg rules */
1419               if (solver_ruleclass(solv, id) == SOLVER_RULE_CHOICE)
1420                 id = solver_rule2pkgrule(solv, id);
1421               if (solver_ruleclass(solv, id) == SOLVER_RULE_RECOMMENDS)
1422                 id = solver_rule2pkgrule(solv, id);
1423               solver_allruleinfos(solv, id, &rq);
1424               for (i = 0; i < rq.count; i += 4)
1425                 {
1426                   int rtype = rq.elements[i];
1427                   if ((rtype & SOLVER_RULE_TYPEMASK) == SOLVER_RULE_JOB)
1428                     {
1429                       const char *js = testcase_job2str(pool, rq.elements[i + 2], rq.elements[i + 3]);
1430                       char *s = pool_tmpjoin(pool, altprefix, num, "job ");
1431                       s = pool_tmpappend(pool, s, js, 0);
1432                       strqueue_push(&sq, s);
1433                     }
1434                   else if (rtype == SOLVER_RULE_PKG_REQUIRES)
1435                     {
1436                       char *s = pool_tmpjoin(pool, altprefix, num, testcase_solvid2str(pool, rq.elements[i + 1]));
1437                       s = pool_tmpappend(pool, s, " requires ", testcase_dep2str(pool, rq.elements[i + 3]));
1438                       strqueue_push(&sq, s);
1439                     }
1440                   else if (rtype == SOLVER_RULE_UPDATE || rtype == SOLVER_RULE_FEATURE)
1441                     {
1442                       const char *js = testcase_solvid2str(pool, rq.elements[i + 1]);
1443                       char *s = pool_tmpjoin(pool, altprefix, num, "update ");
1444                       s = pool_tmpappend(pool, s, js, 0);
1445                       strqueue_push(&sq, s);
1446                     }
1447                 }
1448             }
1449           for (i = 0; i < q.count; i++)
1450             {
1451               Id p = q.elements[i];
1452               if (i >= 9)
1453                 num[0] = '0' + (i + 1) / 10;
1454               num[1] = '0' + (i + 1) % 10;
1455               if (-p == chosen)
1456                 s = pool_tmpjoin(pool, altprefix, num, "+ ");
1457               else if (p < 0)
1458                 s = pool_tmpjoin(pool, altprefix, num, "- ");
1459               else if (p >= 0)
1460                 s = pool_tmpjoin(pool, altprefix, num, "  ");
1461               s = pool_tmpappend(pool, s,  testcase_solvid2str(pool, p < 0 ? -p : p), 0);
1462               strqueue_push(&sq, s);
1463             }
1464           solv_free(altprefix);
1465         }
1466       queue_free(&q);
1467       queue_free(&rq);
1468     }
1469   if ((resultflags & TESTCASE_RESULT_RULES) != 0)
1470     {
1471       /* dump all rules */
1472       Id rid;
1473       SolverRuleinfo rclass;
1474       Queue q;
1475       int i;
1476
1477       queue_init(&q);
1478       for (rid = 1; (rclass = solver_ruleclass(solv, rid)) != SOLVER_RULE_UNKNOWN; rid++)
1479         {
1480           char *prefix = solv_dupjoin("rule ", testcase_rclass2str(rclass), " ");
1481           prefix = solv_dupappend(prefix, testcase_ruleid(solv, rid), 0);
1482           solver_ruleliterals(solv, rid, &q);
1483           if (rclass == SOLVER_RULE_FEATURE && q.count == 1 && q.elements[0] == -SYSTEMSOLVABLE)
1484             continue;
1485           for (i = 0; i < q.count; i++)
1486             {
1487               Id p = q.elements[i];
1488               const char *s;
1489               if (p < 0)
1490                 s = pool_tmpjoin(pool, prefix, " -", testcase_solvid2str(pool, -p));
1491               else
1492                 s = pool_tmpjoin(pool, prefix, "  ", testcase_solvid2str(pool, p));
1493               strqueue_push(&sq, s);
1494             }
1495           solv_free(prefix);
1496         }
1497       queue_free(&q);
1498     }
1499   if ((resultflags & TESTCASE_RESULT_GENID) != 0)
1500     {
1501       for (i = 0 ; i < solv->job.count; i += 2)
1502         {
1503           Id id, id2;
1504           if (solv->job.elements[i] != (SOLVER_NOOP | SOLVER_SOLVABLE_PROVIDES))
1505             continue;
1506           id = solv->job.elements[i + 1];
1507           s = testcase_dep2str(pool, id);
1508           strqueue_push(&sq, pool_tmpjoin(pool, "genid dep ", s, 0));
1509           if ((id2 = testcase_str2dep(pool, s)) != id)
1510             {
1511               s = pool_tmpjoin(pool, "genid roundtrip error: ", testcase_dep2str(pool, id2), 0);
1512               strqueue_push(&sq, s);
1513             }
1514           dump_genid(pool, &sq, id, 1);
1515         }
1516     }
1517   if ((resultflags & TESTCASE_RESULT_REASON) != 0)
1518     {
1519       Queue whyq;
1520       queue_init(&whyq);
1521       FOR_POOL_SOLVABLES(p)
1522         {
1523           Id info, p2, id;
1524           int reason = solver_describe_decision(solv, p, &info);
1525           if (reason == SOLVER_REASON_UNRELATED)
1526             continue;
1527           if (reason == SOLVER_REASON_WEAKDEP)
1528             {
1529               solver_describe_weakdep_decision(solv, p, &whyq);
1530               if (whyq.count)
1531                 {
1532                   for (i = 0; i < whyq.count; i += 3)
1533                     {
1534                       reason = whyq.elements[i];
1535                       p2 = whyq.elements[i + 1];
1536                       id = whyq.elements[i + 2];
1537                       s = pool_tmpjoin(pool, "reason ", testcase_solvid2str(pool, p), 0);
1538                       s = pool_tmpappend(pool, s, " ", testcase_reason2str(reason));
1539                       s = pool_tmpappend(pool, s, " ", testcase_dep2str(pool, id));
1540                       if (p2)
1541                         s = pool_tmpappend(pool, s, " ", testcase_solvid2str(pool, p2));
1542                       strqueue_push(&sq, s);
1543                     }
1544                   continue;
1545                 }
1546             }
1547           s = pool_tmpjoin(pool, "reason ", testcase_solvid2str(pool, p), 0);
1548           s = pool_tmpappend(pool, s, " ", testcase_reason2str(reason));
1549           if (info)
1550             s = pool_tmpappend(pool, s, " ", testcase_ruleid(solv, info));
1551           strqueue_push(&sq, s);
1552         }
1553       queue_free(&whyq);
1554     }
1555   if ((resultflags & TESTCASE_RESULT_CLEANDEPS) != 0)
1556     {
1557       Queue q;
1558
1559       queue_init(&q);
1560       solver_get_cleandeps(solv, &q);
1561       for (i = 0; i < q.count; i++)
1562         {
1563           s = pool_tmpjoin(pool, "cleandeps ", testcase_solvid2str(pool, q.elements[i]), 0);
1564           strqueue_push(&sq, s);
1565         }
1566       queue_free(&q);
1567     }
1568   if ((resultflags & TESTCASE_RESULT_JOBS) != 0)
1569     {
1570       for (i = 0; i < solv->job.count; i += 2)
1571         {
1572           s = (char *)testcase_job2str(pool, solv->job.elements[i], solv->job.elements[i + 1]);
1573           s = pool_tmpjoin(pool, "job ", s, 0);
1574           strqueue_push(&sq, s);
1575         }
1576     }
1577   strqueue_sort(&sq);
1578   result = strqueue_join(&sq);
1579   strqueue_free(&sq);
1580   return result;
1581 }
1582
1583
1584 static int
1585 testcase_write_mangled(Solver *solv, const char *dir, int resultflags, const char *testcasename, const char *resultname)
1586 {
1587   Pool *pool = solv->pool;
1588   Repo *repo;
1589   int i;
1590   Id arch, repoid;
1591   Id lowscore;
1592   FILE *fp;
1593   Strqueue sq;
1594   char *cmd, *out, *result;
1595   const char *s;
1596
1597   if (!testcasename)
1598     testcasename = "testcase.t";
1599   if (!resultname)
1600     resultname = "solver.result";
1601
1602 #ifdef _WIN32
1603   if (mkdir(dir) && errno != EEXIST)
1604 #else
1605   if (mkdir(dir, 0777) && errno != EEXIST)
1606 #endif
1607     return pool_error(solv->pool, 0, "testcase_write: could not create directory '%s'", dir);
1608   strqueue_init(&sq);
1609   FOR_REPOS(repoid, repo)
1610     {
1611       const char *name = testcase_repoid2str(pool, repoid);
1612       char priobuf[50];
1613       if (repo->subpriority)
1614         sprintf(priobuf, "%d.%d", repo->priority, repo->subpriority);
1615       else
1616         sprintf(priobuf, "%d", repo->priority);
1617 #if !defined(WITHOUT_COOKIEOPEN) && defined(ENABLE_ZLIB_COMPRESSION)
1618       out = pool_tmpjoin(pool, name, ".repo", ".gz");
1619 #else
1620       out = pool_tmpjoin(pool, name, ".repo", 0);
1621 #endif
1622       for (i = 0; out[i]; i++)
1623         if (out[i] == '/')
1624           out[i] = '_';
1625       cmd = pool_tmpjoin(pool, "repo ", name, " ");
1626       cmd = pool_tmpappend(pool, cmd, priobuf, " ");
1627       cmd = pool_tmpappend(pool, cmd, "testtags ", out);
1628       strqueue_push(&sq, cmd);
1629       out = pool_tmpjoin(pool, dir, "/", out);
1630       if (!(fp = solv_xfopen(out, "w")))
1631         {
1632           pool_error(solv->pool, 0, "testcase_write: could not open '%s' for writing", out);
1633           strqueue_free(&sq);
1634           return 0;
1635         }
1636       testcase_write_testtags(repo, fp);
1637       if (fclose(fp))
1638         {
1639           pool_error(solv->pool, 0, "testcase_write: write error");
1640           strqueue_free(&sq);
1641           return 0;
1642         }
1643     }
1644   /* hmm, this is not optimal... we currently search for the lowest score */
1645   lowscore = 0;
1646   arch = pool->solvables[SYSTEMSOLVABLE].arch;
1647   for (i = 0; i < pool->lastarch; i++)
1648     {
1649       if (pool->id2arch[i] == 1 && !lowscore)
1650         arch = i;
1651       if (pool->id2arch[i] > 0x10000 && (!lowscore || pool->id2arch[i] < lowscore))
1652         {
1653           arch = i;
1654           lowscore = pool->id2arch[i];
1655         }
1656     }
1657   cmd = pool_tmpjoin(pool, "system ", pool->lastarch ? pool_id2str(pool, arch) : "-", 0);
1658   for (i = 0; disttype2str[i].str != 0; i++)
1659     if (pool->disttype == disttype2str[i].type)
1660       break;
1661   pool_tmpappend(pool, cmd, " ", disttype2str[i].str ? disttype2str[i].str : "unknown");
1662   if (pool->installed)
1663     cmd = pool_tmpappend(pool, cmd, " ", testcase_repoid2str(pool, pool->installed->repoid));
1664   strqueue_push(&sq, cmd);
1665   s = testcase_getpoolflags(solv->pool);
1666   if (*s)
1667     {
1668       cmd = pool_tmpjoin(pool, "poolflags ", s, 0);
1669       strqueue_push(&sq, cmd);
1670     }
1671
1672   if (pool->vendorclasses)
1673     {
1674       cmd = 0;
1675       for (i = 0; pool->vendorclasses[i]; i++)
1676         {
1677           cmd = pool_tmpappend(pool, cmd ? cmd : "vendorclass", " ", pool->vendorclasses[i]);
1678           if (!pool->vendorclasses[i + 1])
1679             {
1680               strqueue_push(&sq, cmd);
1681               cmd = 0;
1682               i++;
1683             }
1684         }
1685     }
1686
1687   /* dump disabled packages (must come before the namespace/job lines) */
1688   if (pool->considered)
1689     {
1690       Id p;
1691       FOR_POOL_SOLVABLES(p)
1692         if (!MAPTST(pool->considered, p))
1693           {
1694             cmd = pool_tmpjoin(pool, "disable pkg ", testcase_solvid2str(pool, p), 0);
1695             strqueue_push(&sq, cmd);
1696           }
1697     }
1698
1699   s = testcase_getsolverflags(solv);
1700   if (*s)
1701     {
1702       cmd = pool_tmpjoin(pool, "solverflags ", s, 0);
1703       strqueue_push(&sq, cmd);
1704     }
1705
1706   /* now dump all the ns callback values we know */
1707   if (pool->nscallback)
1708     {
1709       Id rid;
1710       int d;
1711       for (rid = 1; rid < pool->nrels; rid++)
1712         {
1713           Reldep *rd = pool->rels + rid;
1714           if (rd->flags != REL_NAMESPACE || rd->name == NAMESPACE_OTHERPROVIDERS)
1715             continue;
1716           /* evaluate all namespace ids, skip empty results */
1717           d = pool_whatprovides(pool, MAKERELDEP(rid));
1718           if (!d || !pool->whatprovidesdata[d])
1719             continue;
1720           cmd = pool_tmpjoin(pool, "namespace ", pool_id2str(pool, rd->name), "(");
1721           cmd = pool_tmpappend(pool, cmd, pool_id2str(pool, rd->evr), ")");
1722           for (;  pool->whatprovidesdata[d]; d++)
1723             cmd = pool_tmpappend(pool, cmd, " ", testcase_solvid2str(pool, pool->whatprovidesdata[d]));
1724           strqueue_push(&sq, cmd);
1725         }
1726     }
1727
1728   for (i = 0; i < solv->job.count; i += 2)
1729     {
1730       cmd = (char *)testcase_job2str(pool, solv->job.elements[i], solv->job.elements[i + 1]);
1731       cmd = pool_tmpjoin(pool, "job ", cmd, 0);
1732       strqueue_push(&sq, cmd);
1733     }
1734
1735   if ((resultflags & ~TESTCASE_RESULT_REUSE_SOLVER) != 0)
1736     {
1737       cmd = 0;
1738       for (i = 0; resultflags2str[i].str; i++)
1739         if ((resultflags & resultflags2str[i].flag) != 0)
1740           cmd = pool_tmpappend(pool, cmd, cmd ? "," : 0, resultflags2str[i].str);
1741       cmd = pool_tmpjoin(pool, "result ", cmd ? cmd : "?", 0);
1742       cmd = pool_tmpappend(pool, cmd, " ", resultname);
1743       strqueue_push(&sq, cmd);
1744       result = testcase_solverresult(solv, resultflags);
1745       if (!strcmp(resultname, "<inline>"))
1746         {
1747           Strqueue rsq;
1748           strqueue_init(&rsq);
1749           strqueue_split(&rsq, result);
1750           for (i = 0; i < rsq.nstr; i++)
1751             {
1752               cmd = pool_tmpjoin(pool, "#>", rsq.str[i], 0);
1753               strqueue_push(&sq, cmd);
1754             }
1755           strqueue_free(&rsq);
1756         }
1757       else
1758         {
1759           out = pool_tmpjoin(pool, dir, "/", resultname);
1760           if (!(fp = fopen(out, "w")))
1761             {
1762               pool_error(solv->pool, 0, "testcase_write: could not open '%s' for writing", out);
1763               solv_free(result);
1764               strqueue_free(&sq);
1765               return 0;
1766             }
1767           if (result && *result && fwrite(result, strlen(result), 1, fp) != 1)
1768             {
1769               pool_error(solv->pool, 0, "testcase_write: write error");
1770               solv_free(result);
1771               strqueue_free(&sq);
1772               fclose(fp);
1773               return 0;
1774             }
1775           if (fclose(fp))
1776             {
1777               pool_error(solv->pool, 0, "testcase_write: write error");
1778               solv_free(result);
1779               strqueue_free(&sq);
1780               return 0;
1781             }
1782         }
1783       solv_free(result);
1784     }
1785
1786   result = strqueue_join(&sq);
1787   strqueue_free(&sq);
1788   out = pool_tmpjoin(pool, dir, "/", testcasename);
1789   if (!(fp = fopen(out, "w")))
1790     {
1791       pool_error(solv->pool, 0, "testcase_write: could not open '%s' for writing", out);
1792       solv_free(result);
1793       return 0;
1794     }
1795   if (*result && fwrite(result, strlen(result), 1, fp) != 1)
1796     {
1797       pool_error(solv->pool, 0, "testcase_write: write error");
1798       solv_free(result);
1799       fclose(fp);
1800       return 0;
1801     }
1802   if (fclose(fp))
1803     {
1804       pool_error(solv->pool, 0, "testcase_write: write error");
1805       solv_free(result);
1806       return 0;
1807     }
1808   solv_free(result);
1809   return 1;
1810 }
1811
1812 int
1813 testcase_write(Solver *solv, const char *dir, int resultflags, const char *testcasename, const char *resultname)
1814 {
1815   Pool *pool = solv->pool;
1816   int i, r, repoid;
1817   int mangle = 1;
1818   const char **orignames;
1819
1820   /* mangle repo names so that there are no conflicts */
1821   orignames = solv_calloc(pool->nrepos, sizeof(char *));
1822   for (repoid = 1; repoid < pool->nrepos; repoid++)
1823     {
1824       Repo *repo = pool_id2repo(pool, repoid);
1825       char *buf = solv_malloc((repo->name ? strlen(repo->name) : 0) + 40);
1826       char *mp;
1827       orignames[repoid] = repo->name;
1828       if (!repo->name || !repo->name[0])
1829         sprintf(buf, "#%d", repoid);
1830       else
1831         strcpy(buf, repo->name);
1832       for (i = 0; buf[i]; i++)
1833         if (buf[i] == ' ' || buf[i] == '\t' || buf[i] == '/')
1834           buf[i] = '_';
1835       mp = buf + strlen(buf);
1836       for (;;)
1837         {
1838           for (i = 1; i < repoid; i++)
1839             if (!strcmp(buf, pool_id2repo(pool, i)->name))
1840               break;
1841           if (i == repoid)
1842             break;
1843           sprintf(mp, "_%d", mangle++);
1844         }
1845       repo->name = buf;
1846     }
1847   r = testcase_write_mangled(solv, dir, resultflags, testcasename, resultname);
1848   for (repoid = 1; repoid < pool->nrepos; repoid++)
1849     {
1850       Repo *repo = pool_id2repo(pool, repoid);
1851       solv_free((void *)repo->name);
1852       repo->name = orignames[repoid];
1853     }
1854   solv_free(orignames);
1855   return r;
1856 }
1857
1858 static char *
1859 read_inline_file(FILE *fp, char **bufp, char **bufpp, int *buflp)
1860 {
1861   char *result = solv_malloc(1024);
1862   char *rp = result;
1863   int resultl = 1024;
1864
1865   for (;;)
1866     {
1867       size_t rl;
1868       if (rp - result + 256 >= resultl)
1869         {
1870           resultl = rp - result;
1871           result = solv_realloc(result, resultl + 1024);
1872           rp = result + resultl;
1873           resultl += 1024;
1874         }
1875       if (!fgets(rp, resultl - (rp - result), fp))
1876         *rp = 0;
1877       rl = strlen(rp);
1878       if (rl && (rp == result || rp[-1] == '\n'))
1879         {
1880           if (rl > 1 && rp[0] == '#' && rp[1] == '>')
1881             {
1882               memmove(rp, rp + 2, rl - 2);
1883               rl -= 2;
1884             }
1885           else
1886             {
1887               while (rl + 16 > *buflp)
1888                 {
1889                   *bufp = solv_realloc(*bufp, *buflp + 512);
1890                   *buflp += 512;
1891                 }
1892               memmove(*bufp, rp, rl);
1893               if ((*bufp)[rl - 1] == '\n')
1894                 {
1895                   ungetc('\n', fp);
1896                   rl--;
1897                 }
1898               (*bufp)[rl] = 0;
1899               (*bufpp) = *bufp + rl;
1900               rl = 0;
1901             }
1902         }
1903       if (rl <= 0)
1904         {
1905           *rp = 0;
1906           break;
1907         }
1908       rp += rl;
1909     }
1910   return result;
1911 }
1912
1913 static char *
1914 read_file(FILE *fp)
1915 {
1916   char *result = solv_malloc(1024);
1917   char *rp = result;
1918   int resultl = 1024;
1919
1920   for (;;)
1921     {
1922       size_t rl;
1923       if (rp - result + 256 >= resultl)
1924         {
1925           resultl = rp - result;
1926           result = solv_realloc(result, resultl + 1024);
1927           rp = result + resultl;
1928           resultl += 1024;
1929         }
1930       rl = fread(rp, 1, resultl - (rp - result), fp);
1931       if (rl <= 0)
1932         {
1933           *rp = 0;
1934           break;
1935         }
1936       rp += rl;
1937     }
1938   return result;
1939 }
1940
1941 static int
1942 str2resultflags(Pool *pool, char *s)    /* modifies the string! */
1943 {
1944   int i, resultflags = 0;
1945   while (s)
1946     {
1947       char *se = strchr(s, ',');
1948       if (se)
1949         *se++ = 0;
1950       for (i = 0; resultflags2str[i].str; i++)
1951         if (!strcmp(s, resultflags2str[i].str))
1952           {
1953             resultflags |= resultflags2str[i].flag;
1954             break;
1955           }
1956       if (!resultflags2str[i].str)
1957         pool_error(pool, 0, "result: unknown flag '%s'", s);
1958       s = se;
1959     }
1960   return resultflags;
1961 }
1962
1963 Solver *
1964 testcase_read(Pool *pool, FILE *fp, const char *testcase, Queue *job, char **resultp, int *resultflagsp)
1965 {
1966   Solver *solv;
1967   char *buf, *bufp;
1968   int bufl;
1969   char *testcasedir, *s;
1970   int l;
1971   char **pieces = 0;
1972   int npieces = 0;
1973   int prepared = 0;
1974   int closefp = !fp;
1975   int poolflagsreset = 0;
1976   int missing_features = 0;
1977   Id *genid = 0;
1978   int ngenid = 0;
1979   Queue autoinstq;
1980
1981   if (resultp)
1982     *resultp = 0;
1983   if (resultflagsp)
1984     *resultflagsp = 0;
1985   if (!fp && !(fp = fopen(testcase, "r")))
1986     {
1987       pool_error(pool, 0, "testcase_read: could not open '%s'", testcase);
1988       return 0;
1989     }
1990   testcasedir = solv_strdup(testcase);
1991   s = strrchr(testcasedir, '/');
1992 #ifdef _WIN32
1993   buf = strrchr(testcasedir, '\\');
1994   if (!s || (buf && buf > s))
1995     s = buf;
1996 #endif
1997   if (s)
1998     s[1] = 0;
1999   else
2000     *testcasedir = 0;
2001   bufl = 1024;
2002   buf = solv_malloc(bufl);
2003   bufp = buf;
2004   solv = 0;
2005   queue_init(&autoinstq);
2006   for (;;)
2007     {
2008       if (bufp - buf + 16 > bufl)
2009         {
2010           bufl = bufp - buf;
2011           buf = solv_realloc(buf, bufl + 512);
2012           bufp = buf + bufl;
2013           bufl += 512;
2014         }
2015       if (!fgets(bufp, bufl - (bufp - buf), fp))
2016         break;
2017       bufp = buf;
2018       l = strlen(buf);
2019       if (!l || buf[l - 1] != '\n')
2020         {
2021           bufp += l;
2022           continue;
2023         }
2024       buf[--l] = 0;
2025       s = buf;
2026       while (*s && (*s == ' ' || *s == '\t'))
2027         s++;
2028       if (!*s || *s == '#')
2029         continue;
2030       npieces = 0;
2031       /* split it in pieces */
2032       for (;;)
2033         {
2034           while (*s == ' ' || *s == '\t')
2035             s++;
2036           if (!*s)
2037             break;
2038           pieces = solv_extend(pieces, npieces, 1, sizeof(*pieces), 7);
2039           pieces[npieces++] = s;
2040           while (*s && *s != ' ' && *s != '\t')
2041             s++;
2042           if (*s)
2043             *s++ = 0;
2044         }
2045       pieces = solv_extend(pieces, npieces, 1, sizeof(*pieces), 7);
2046       pieces[npieces] = 0;
2047       if (!strcmp(pieces[0], "repo") && npieces >= 4)
2048         {
2049           Repo *repo = repo_create(pool, pieces[1]);
2050           FILE *rfp;
2051           int prio, subprio;
2052           const char *rdata;
2053
2054           prepared = 0;
2055           if (!poolflagsreset)
2056             {
2057               poolflagsreset = 1;
2058               testcase_resetpoolflags(pool);    /* hmm */
2059             }
2060           if (sscanf(pieces[2], "%d.%d", &prio, &subprio) != 2)
2061             {
2062               subprio = 0;
2063               prio = atoi(pieces[2]);
2064             }
2065           repo->priority = prio;
2066           repo->subpriority = subprio;
2067           if (strcmp(pieces[3], "empty") != 0)
2068             {
2069               const char *repotype = pool_tmpjoin(pool, pieces[3], 0, 0);       /* gets overwritten in <inline> case */
2070               if (!strcmp(pieces[4], "<inline>"))
2071                 {
2072                   char *idata = read_inline_file(fp, &buf, &bufp, &bufl);
2073                   rdata = "<inline>";
2074                   rfp = solv_xfopen_buf(rdata, &idata, 0, "rf");
2075                 }
2076               else
2077                 {
2078                   rdata = pool_tmpjoin(pool, testcasedir, pieces[4], 0);
2079                   rfp = solv_xfopen(rdata, "r");
2080                 }
2081               if (!rfp)
2082                 {
2083                   pool_error(pool, 0, "testcase_read: could not open '%s'", rdata);
2084                 }
2085               else if (!strcmp(repotype, "testtags"))
2086                 {
2087                   testcase_add_testtags(repo, rfp, 0);
2088                   fclose(rfp);
2089                 }
2090               else if (!strcmp(repotype, "solv"))
2091                 {
2092                   repo_add_solv(repo, rfp, 0);
2093                   fclose(rfp);
2094                 }
2095 #if ENABLE_TESTCASE_HELIXREPO
2096               else if (!strcmp(repotype, "helix"))
2097                 {
2098                   repo_add_helix(repo, rfp, 0);
2099                   fclose(rfp);
2100                 }
2101 #endif
2102               else
2103                 {
2104                   fclose(rfp);
2105                   pool_error(pool, 0, "testcase_read: unknown repo type for repo '%s'", repo->name);
2106                 }
2107             }
2108         }
2109       else if (!strcmp(pieces[0], "system") && npieces >= 3)
2110         {
2111           int i;
2112
2113           /* must set the disttype before the arch */
2114           prepared = 0;
2115           if (strcmp(pieces[2], "*") != 0)
2116             {
2117               char *dp = pieces[2];
2118               while (dp && *dp)
2119                 {
2120                   char *dpe = strchr(dp, ',');
2121                   if (dpe)
2122                     *dpe = 0;
2123                   for (i = 0; disttype2str[i].str != 0; i++)
2124                     if (!strcmp(disttype2str[i].str, dp))
2125                       break;
2126                   if (dpe)
2127                     *dpe++ = ',';
2128                   if (disttype2str[i].str)
2129                     {
2130 #ifdef MULTI_SEMANTICS
2131                       if (pool->disttype != disttype2str[i].type)
2132                         pool_setdisttype(pool, disttype2str[i].type);
2133 #endif
2134                       if (pool->disttype == disttype2str[i].type)
2135                         break;
2136                     }
2137                   dp = dpe;
2138                 }
2139               if (!(dp && *dp))
2140                 {
2141                   pool_error(pool, 0, "testcase_read: system: could not change disttype to '%s'", pieces[2]);
2142                   missing_features = 1;
2143                 }
2144             }
2145           if (strcmp(pieces[1], "unset") == 0 || strcmp(pieces[1], "-") == 0)
2146             pool_setarch(pool, 0);
2147           else if (pieces[1][0] == ':')
2148             pool_setarchpolicy(pool, pieces[1] + 1);
2149           else
2150             pool_setarch(pool, pieces[1]);
2151           if (npieces > 3)
2152             {
2153               Repo *repo = testcase_str2repo(pool, pieces[3]);
2154               if (!repo)
2155                 pool_error(pool, 0, "testcase_read: system: unknown repo '%s'", pieces[3]);
2156               else
2157                 pool_set_installed(pool, repo);
2158             }
2159         }
2160       else if (!strcmp(pieces[0], "job") && npieces > 1)
2161         {
2162           char *sp;
2163           Id how, what;
2164           if (prepared <= 0)
2165             {
2166               pool_addfileprovides(pool);
2167               pool_createwhatprovides(pool);
2168               prepared = 1;
2169             }
2170           if (npieces >= 3 && !strcmp(pieces[2], "selection"))
2171             {
2172               addselectionjob(pool, pieces + 1, npieces - 1, job, 0, 0);
2173               continue;
2174             }
2175           if (npieces >= 4 && !strcmp(pieces[2], "selection_matchdeps"))
2176             {
2177               pieces[2] = pieces[1];
2178               addselectionjob(pool, pieces + 2, npieces - 2, job, SELECTIONJOB_MATCHDEPS, pool_str2id(pool, pieces[3], 1));
2179               continue;
2180             }
2181           if (npieces >= 4 && !strcmp(pieces[2], "selection_matchdepid"))
2182             {
2183               pieces[2] = pieces[1];
2184               addselectionjob(pool, pieces + 2, npieces - 2, job, SELECTIONJOB_MATCHDEPID, pool_str2id(pool, pieces[3], 1));
2185               continue;
2186             }
2187           if (npieces >= 4 && !strcmp(pieces[2], "selection_matchsolvable"))
2188             {
2189               pieces[2] = pieces[1];
2190               addselectionjob(pool, pieces + 2, npieces - 2, job, SELECTIONJOB_MATCHSOLVABLE, pool_str2id(pool, pieces[3], 1));
2191               continue;
2192             }
2193           /* rejoin */
2194           for (sp = pieces[1]; sp < pieces[npieces - 1]; sp++)
2195             if (*sp == 0)
2196               *sp = ' ';
2197           how = testcase_str2job(pool, pieces[1], &what);
2198           if (how >= 0 && job)
2199             queue_push2(job, how, what);
2200         }
2201       else if (!strcmp(pieces[0], "vendorclass") && npieces > 1)
2202         {
2203           pool_addvendorclass(pool, (const char **)(pieces + 1));
2204         }
2205       else if (!strcmp(pieces[0], "namespace") && npieces > 1)
2206         {
2207           int i = strlen(pieces[1]);
2208           s = strchr(pieces[1], '(');
2209           if (!s || pieces[1][i - 1] != ')')
2210             {
2211               pool_error(pool, 0, "testcase_read: bad namespace '%s'", pieces[1]);
2212             }
2213           else
2214             {
2215               Id name, evr, id;
2216               Queue q;
2217               queue_init(&q);
2218               *s = 0;
2219               pieces[1][i - 1] = 0;
2220               name = pool_str2id(pool, pieces[1], 1);
2221               evr = pool_str2id(pool, s + 1, 1);
2222               *s = '(';
2223               pieces[1][i - 1] = ')';
2224               id = pool_rel2id(pool, name, evr, REL_NAMESPACE, 1);
2225               for (i = 2; i < npieces; i++)
2226                 queue_push(&q, testcase_str2solvid(pool, pieces[i]));
2227               /* now do the callback */
2228               if (prepared <= 0)
2229                 {
2230                   pool_addfileprovides(pool);
2231                   pool_createwhatprovides(pool);
2232                   prepared = 1;
2233                 }
2234               pool->whatprovides_rel[GETRELID(id)] = pool_queuetowhatprovides(pool, &q);
2235               queue_free(&q);
2236             }
2237         }
2238       else if (!strcmp(pieces[0], "poolflags"))
2239         {
2240           int i;
2241           if (!poolflagsreset)
2242             {
2243               poolflagsreset = 1;
2244               testcase_resetpoolflags(pool);    /* hmm */
2245             }
2246           for (i = 1; i < npieces; i++)
2247             testcase_setpoolflags(pool, pieces[i]);
2248         }
2249       else if (!strcmp(pieces[0], "solverflags") && npieces > 1)
2250         {
2251           int i;
2252           if (!solv)
2253             {
2254               solv = solver_create(pool);
2255               testcase_resetsolverflags(solv);
2256             }
2257           for (i = 1; i < npieces; i++)
2258             testcase_setsolverflags(solv, pieces[i]);
2259         }
2260       else if (!strcmp(pieces[0], "result") && npieces > 1)
2261         {
2262           char *result = 0;
2263           int resultflags = str2resultflags(pool, pieces[1]);
2264           const char *rdata;
2265           if (npieces > 2)
2266             {
2267               rdata = pool_tmpjoin(pool, testcasedir, pieces[2], 0);
2268               if (!strcmp(pieces[2], "<inline>"))
2269                 result = read_inline_file(fp, &buf, &bufp, &bufl);
2270               else
2271                 {
2272                   FILE *rfp = fopen(rdata, "r");
2273                   if (!rfp)
2274                     pool_error(pool, 0, "testcase_read: could not open '%s'", rdata);
2275                   else
2276                     {
2277                       result = read_file(rfp);
2278                       fclose(rfp);
2279                     }
2280                 }
2281             }
2282           if (resultp)
2283             *resultp = result;
2284           else
2285             solv_free(result);
2286           if (resultflagsp)
2287             *resultflagsp = resultflags;
2288         }
2289       else if (!strcmp(pieces[0], "nextjob"))
2290         {
2291           if (npieces == 2 && resultflagsp && !strcmp(pieces[1], "reusesolver"))
2292             *resultflagsp |= TESTCASE_RESULT_REUSE_SOLVER;
2293           break;
2294         }
2295       else if (!strcmp(pieces[0], "disable") && npieces == 3)
2296         {
2297           Id p, pp, jobsel, what = 0;
2298           if (!prepared)
2299             pool_createwhatprovides(pool);
2300           prepared = -1;
2301           if (!pool->considered)
2302             {
2303               pool->considered = solv_calloc(1, sizeof(Map));
2304               map_init(pool->considered, pool->nsolvables);
2305               map_setall(pool->considered);
2306             }
2307           jobsel = testcase_str2jobsel(pool, "disable", pieces + 1, npieces - 1, &what);
2308           if (jobsel < 0)
2309             continue;
2310           if (jobsel == SOLVER_SOLVABLE_ALL)
2311             map_empty(pool->considered);
2312           else if (jobsel == SOLVER_SOLVABLE_REPO)
2313             {
2314               Repo *repo = pool_id2repo(pool, what);
2315               Solvable *s;
2316               FOR_REPO_SOLVABLES(repo, p, s)
2317                 MAPCLR(pool->considered, p);
2318             }
2319           FOR_JOB_SELECT(p, pp, jobsel, what)
2320             MAPCLR(pool->considered, p);
2321         }
2322       else if (!strcmp(pieces[0], "feature"))
2323         {
2324           int i, j;
2325           for (i = 1; i < npieces; i++)
2326             {
2327               for (j = 0; features[j]; j++)
2328                 if (!strcmp(pieces[i], features[j]))
2329                   break;
2330               if (!features[j])
2331                 {
2332                   pool_error(pool, 0, "testcase_read: missing feature '%s'", pieces[i]);
2333                   missing_features++;
2334                 }
2335             }
2336           if (missing_features)
2337             break;
2338         }
2339       else if (!strcmp(pieces[0], "genid") && npieces > 1)
2340         {
2341           Id id;
2342           /* rejoin */
2343           if (npieces > 2)
2344             {
2345               char *sp;
2346               for (sp = pieces[2]; sp < pieces[npieces - 1]; sp++)
2347                 if (*sp == 0)
2348                   *sp = ' ';
2349             }
2350           genid = solv_extend(genid, ngenid, 1, sizeof(*genid), 7);
2351           if (!strcmp(pieces[1], "op") && npieces > 2)
2352             {
2353               struct oplist *op;
2354               for (op = oplist; op->flags; op++)
2355                 if (!strncmp(pieces[2], op->opname, strlen(op->opname)))
2356                   break;
2357               if (!op->flags)
2358                 {
2359                   pool_error(pool, 0, "testcase_read: genid: unknown op '%s'", pieces[2]);
2360                   break;
2361                 }
2362               if (ngenid < 2)
2363                 {
2364                   pool_error(pool, 0, "testcase_read: genid: out of stack");
2365                   break;
2366                 }
2367               ngenid -= 2;
2368               id = pool_rel2id(pool, genid[ngenid] , genid[ngenid + 1], op->flags, 1);
2369             }
2370           else if (!strcmp(pieces[1], "lit"))
2371             id = pool_str2id(pool, npieces > 2 ? pieces[2] : "", 1);
2372           else if (!strcmp(pieces[1], "null"))
2373             id = 0;
2374           else if (!strcmp(pieces[1], "dep"))
2375             id = testcase_str2dep(pool, pieces[2]);
2376           else
2377             {
2378               pool_error(pool, 0, "testcase_read: genid: unknown command '%s'", pieces[1]);
2379               break;
2380             }
2381           genid[ngenid++] = id;
2382         }
2383       else if (!strcmp(pieces[0], "autoinst") && npieces > 2)
2384         {
2385           if (strcmp(pieces[1], "name"))
2386             {
2387               pool_error(pool, 0, "testcase_read: autoinst: illegal mode");
2388               break;
2389             }
2390           queue_push(&autoinstq, pool_str2id(pool, pieces[2], 1));
2391         }
2392       else if (!strcmp(pieces[0], "evrcmp") && npieces == 3)
2393         {
2394           Id evr1 = pool_str2id(pool, pieces[1], 1);
2395           Id evr2 = pool_str2id(pool, pieces[2], 1);
2396           int r = pool_evrcmp(pool, evr1, evr2, EVRCMP_COMPARE);
2397           r = r < 0 ? REL_LT : r > 0 ? REL_GT : REL_EQ;
2398           queue_push2(job, SOLVER_NOOP | SOLVER_SOLVABLE_PROVIDES, pool_rel2id(pool, evr1, evr2, r, 1));
2399         }
2400       else
2401         {
2402           pool_error(pool, 0, "testcase_read: cannot parse command '%s'", pieces[0]);
2403         }
2404     }
2405   while (job && ngenid > 0)
2406     queue_push2(job, SOLVER_NOOP | SOLVER_SOLVABLE_PROVIDES, genid[--ngenid]);
2407   if (autoinstq.count)
2408     pool_add_userinstalled_jobs(pool, &autoinstq, job, GET_USERINSTALLED_NAMES | GET_USERINSTALLED_INVERTED);
2409   queue_free(&autoinstq);
2410   genid = solv_free(genid);
2411   buf = solv_free(buf);
2412   pieces = solv_free(pieces);
2413   solv_free(testcasedir);
2414   if (!prepared)
2415     {
2416       pool_addfileprovides(pool);
2417       pool_createwhatprovides(pool);
2418     }
2419   if (!solv)
2420     {
2421       solv = solver_create(pool);
2422       testcase_resetsolverflags(solv);
2423     }
2424   if (closefp)
2425     fclose(fp);
2426   if (missing_features)
2427     {
2428       solver_free(solv);
2429       solv = 0;
2430       if (resultflagsp)
2431         *resultflagsp = 77;     /* hack for testsolv */
2432     }
2433   return solv;
2434 }
2435
2436 char *
2437 testcase_resultdiff(const char *result1, const char *result2)
2438 {
2439   Strqueue sq1, sq2, osq;
2440   char *r;
2441   strqueue_init(&sq1);
2442   strqueue_init(&sq2);
2443   strqueue_init(&osq);
2444   strqueue_split(&sq1, result1);
2445   strqueue_split(&sq2, result2);
2446   strqueue_sort(&sq1);
2447   strqueue_sort(&sq2);
2448   strqueue_diff(&sq1, &sq2, &osq);
2449   r = osq.nstr ? strqueue_join(&osq) : 0;
2450   strqueue_free(&sq1);
2451   strqueue_free(&sq2);
2452   strqueue_free(&osq);
2453   return r;
2454 }
2455