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