root/tests/test_resolver.cc

Revision 1327:a58503871244, 13.8 kB (checked in by Daniel Burrows <dburrows@…>, 2 years ago)

Add support in the backend for adding a score that's only applied if a set of actions are all performed.
Hopefully this will provide a reasonable basis for handling
conflicts/provides/replaces relationships: the idea is basically
to add a bonus to "remove this pacakge and install its replacement",
so the resolver can climb over this hill.

The next step is to detect c/p/r relationships and actually add
these scores to the resolver.

Line 
1// test_resolver.cc                       -*-c++-*-
2//
3//   Copyright (C) 2005, 2007-2008 Daniel Burrows
4//
5//   This program is free software; you can redistribute it and/or
6//   modify it under the terms of the GNU General Public License as
7//   published by the Free Software Foundation; either version 2 of
8//   the License, or (at your option) any later version.
9//
10//   This program is distributed in the hope that it will be useful,
11//   but WITHOUT ANY WARRANTY; without even the implied warranty of
12//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13//   General Public License for more details.
14//
15//   You should have received a copy of the GNU General Public License
16//   along with this program; see the file COPYING.  If not, write to
17//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18//   Boston, MA 02111-1307, USA.
19//
20// A test of the generic resolver layer.
21
22#include <generic/problemresolver/dummy_universe.h>
23#include <generic/problemresolver/problemresolver.h>
24
25#include <cppunit/extensions/HelperMacros.h>
26
27#include <sstream>
28
29using namespace std;
30
31typedef generic_solution<dummy_universe_ref> dummy_solution;
32
33const char *dummy_universe_1 = "\
34UNIVERSE [ \
35  PACKAGE a < v1 v2 v3 > v1 \
36  PACKAGE b < v1 v2 v3 > v1 \
37  PACKAGE c < v1 v2 v3 > v1 \
38\
39  DEP a v1 -> < b v2 > \
40  DEP b v2 -> < c v2 > \
41\
42  DEP a v2 -> < > \
43  DEP a v3 -> < > \
44]";
45
46const char *dummy_universe_2 = "\
47UNIVERSE [ \
48  PACKAGE a < v1 v2 > v1 \
49  PACKAGE b < v1 v2 > v1 \
50  PACKAGE c < v1 v2 > v1 \
51\
52  DEP a v1 -> < b v2 > \
53  DEP a v1 -> < c v2 > \
54]";
55
56// Done this way so meaningful line numbers are generated.
57#define assertEqEquivalent(x1, x2) \
58do { \
59    CPPUNIT_ASSERT((x1) == (x2)); \
60    CPPUNIT_ASSERT((x2) == (x1)); \
61    CPPUNIT_ASSERT(!((x1) != (x2))); \
62    CPPUNIT_ASSERT(!((x2) != (x1))); \
63    CPPUNIT_ASSERT(!((x1) < (x2))); \
64    CPPUNIT_ASSERT(!((x2) < (x1))); \
65} while(0)
66
67#define assertEqInequivalent(x1, x2) \
68do { \
69    CPPUNIT_ASSERT((x1) != (x2)); \
70    CPPUNIT_ASSERT((x2) != (x1)); \
71    CPPUNIT_ASSERT(!((x1) == (x2))); \
72    CPPUNIT_ASSERT(!((x2) == (x1))); \
73    CPPUNIT_ASSERT((x1) < (x2) || (x2) < (x1)); \
74    CPPUNIT_ASSERT(!((x1) < (x2) && (x2) < (x1))); \
75} while(0)
76
77#define assertLtEquivalent(x1, x2, lt) \
78do { \
79    CPPUNIT_ASSERT(!(lt((x1), (x2)))); \
80    CPPUNIT_ASSERT(!(lt((x2), (x1)))); \
81} while(0)
82
83#define assertLtInequivalent(x1, x2, lt) \
84do { \
85    CPPUNIT_ASSERT(lt((x1), (x2)) || lt((x2), (x1))); \
86    CPPUNIT_ASSERT(!(lt((x1), (x2)) && lt((x2), (x1)))); \
87} while(0)
88
89class ResolverTest : public CppUnit::TestFixture
90{
91  CPPUNIT_TEST_SUITE(ResolverTest);
92
93  CPPUNIT_TEST(testActionCompare);
94  CPPUNIT_TEST(testSolutionCompare);
95  CPPUNIT_TEST(testRejections);
96  CPPUNIT_TEST(testJointScores);
97  CPPUNIT_TEST(testDropSolutionSupersets);
98
99  CPPUNIT_TEST_SUITE_END();
100
101private:
102  static dummy_universe_ref parseUniverse(const std::string &s)
103  {
104    std::istringstream in(s);
105
106    return parse_universe(in);
107  }
108
109  /** Test that the comparison operators on actions work. */
110  void testActionCompare()
111  {
112    dummy_universe_ref u = parseUniverse(dummy_universe_1);
113
114    // Grab two arbitrary deps.
115    dummy_universe::dep_iterator di = u.deps_begin();
116    CPPUNIT_ASSERT(!di.end());
117    dummy_universe::dep d1 = *di;
118    ++di;
119    CPPUNIT_ASSERT(!di.end());
120    dummy_universe::dep d2 = *di;
121
122    dummy_solution::action a1(u.find_package("a").version_from_name("v1"),
123                              d1, false, 49);
124    dummy_solution::action a2(u.find_package("a").version_from_name("v1"),
125                              d2, false, 21);
126
127    assertEqEquivalent(a1, a1);
128    assertEqEquivalent(a1, a2);
129
130    dummy_solution::action a3(u.find_package("a").version_from_name("v2"),
131                              d1, false, 49);
132
133    assertEqEquivalent(a2, a2);
134    assertEqEquivalent(a3, a3);
135
136    assertEqInequivalent(a1, a3);
137    assertEqInequivalent(a2, a3);
138
139    dummy_solution::action a4(u.find_package("c").version_from_name("v3"),
140                              d2, false, 21);
141
142    assertEqEquivalent(a4, a4);
143    assertEqInequivalent(a1, a4);
144    assertEqInequivalent(a2, a4);
145    assertEqInequivalent(a3, a4);
146  }
147
148  /** Generate a successor solution that just contains the given
149   *  information by using internal constructors of the solution
150   *  class.  Used when testing the solution class itself.
151   */
152  template<class a_iter, class u_iter>
153  dummy_solution unsafe_successor(const dummy_solution &parent,
154                                  a_iter abegin, a_iter aend,
155                                  u_iter ubegin, u_iter uend)
156  {
157    imm::map<dummy_universe::package, dummy_solution::action> actions = parent.get_actions();
158    imm::set<dummy_universe::dep> unresolved = parent.get_unresolved_soft_deps();
159
160    for(a_iter ai = abegin; ai != aend; ++ai)
161      actions.put((*ai).ver.get_package(), *ai);
162
163    for(u_iter ui = ubegin; ui != uend; ++ui)
164      unresolved.insert(*ui);
165
166    return dummy_solution(new dummy_solution::solution_rep(actions,
167                                                           parent.get_broken(),
168                                                           unresolved,
169                                                           parent.get_forbidden_versions(),
170                                                           parent.get_score(),
171                                                           parent.get_action_score()));
172  }
173
174  /** Tests that the comparison operations on solutions work. */
175  void testSolutionCompare()
176  {
177    dummy_universe_ref u = parseUniverse(dummy_universe_1);
178
179    // Grab two arbitrary deps.
180    dummy_universe::dep_iterator di = u.deps_begin();
181    CPPUNIT_ASSERT(!di.end());
182    dummy_universe::dep d1 = *di;
183    ++di;
184    CPPUNIT_ASSERT(!di.end());
185    dummy_universe::dep d2 = *di;
186
187    solution_weights<dummy_universe_ref> weights(0, 0, 0, 0, u.get_version_count());
188
189    imm::set<dummy_universe::dep> u_broken;
190    for(dummy_universe::broken_dep_iterator bi = u.broken_begin();
191        !bi.end(); ++bi)
192      u_broken.insert(*bi);
193
194    dummy_solution::action a1(u.find_package("a").version_from_name("v1"),
195                              d1, false, 49);
196    dummy_solution::action a2(u.find_package("a").version_from_name("v1"),
197                              d2, false, 21);
198    dummy_solution::action a3(u.find_package("a").version_from_name("v2"),
199                              d1, false, 49);
200
201    dummy_solution::action a4(u.find_package("c").version_from_name("v3"),
202                              d2, false, 21);
203
204
205    // Generate some meaningless solutions to check that equivalency
206    // is correctly calculated according to the version mappings and
207    // the set of unsolved soft deps.
208    dummy_solution s0 = dummy_solution::root_node(u_broken,
209                                                  u, weights);
210    dummy_solution s1
211      = unsafe_successor(s0, &a1, &a1+1,
212                         (dummy_universe::dep *) 0,
213                         (dummy_universe::dep *) 0);
214    dummy_solution s2
215      = unsafe_successor(s0, &a2, &a2+1,
216                         (dummy_universe::dep *) 0,
217                         (dummy_universe::dep *) 0);
218    dummy_solution s3
219      = unsafe_successor(s0, &a3, &a3+1,
220                         (dummy_universe::dep *) 0,
221                         (dummy_universe::dep *) 0);
222    dummy_solution s4
223      = unsafe_successor(s0, &a4, &a4+1,
224                         (dummy_universe::dep *) 0,
225                         (dummy_universe::dep *) 0);
226
227    // the following two should be equal.
228    dummy_solution s5
229      = unsafe_successor(s1, &a4, &a4+1,
230                         (dummy_universe::dep *) 0,
231                         (dummy_universe::dep *) 0);
232    dummy_solution s6
233      = unsafe_successor(s4, &a1, &a1+1,
234                         (dummy_universe::dep *) 0,
235                         (dummy_universe::dep *) 0);
236
237    // and this should not equal any other solution.
238    dummy_solution s7
239      = unsafe_successor(s0,
240                         (dummy_solution::action *) 0,
241                         (dummy_solution::action *) 0,
242                         &d1, &d1+1);
243
244    dummy_resolver::solution_contents_compare solcmp;
245
246    assertLtEquivalent(s0, s0, solcmp);
247    assertLtInequivalent(s0, s1, solcmp);
248    assertLtInequivalent(s0, s2, solcmp);
249    assertLtInequivalent(s0, s3, solcmp);
250    assertLtInequivalent(s0, s4, solcmp);
251    assertLtInequivalent(s0, s5, solcmp);
252    assertLtInequivalent(s0, s6, solcmp);
253    assertLtInequivalent(s0, s7, solcmp);
254
255    assertLtEquivalent(s1, s1, solcmp);
256    assertLtEquivalent(s1, s2, solcmp);
257    assertLtInequivalent(s1, s3, solcmp);
258    assertLtInequivalent(s1, s4, solcmp);
259    assertLtInequivalent(s1, s5, solcmp);
260    assertLtInequivalent(s1, s6, solcmp);
261    assertLtInequivalent(s1, s7, solcmp);
262
263    assertLtEquivalent(s2, s2, solcmp);
264    assertLtInequivalent(s2, s3, solcmp);
265    assertLtInequivalent(s2, s4, solcmp);
266    assertLtInequivalent(s2, s5, solcmp);
267    assertLtInequivalent(s2, s6, solcmp);
268    assertLtInequivalent(s2, s7, solcmp);
269
270    assertLtEquivalent(s3, s3, solcmp);
271    assertLtInequivalent(s3, s4, solcmp);
272    assertLtInequivalent(s3, s5, solcmp);
273    assertLtInequivalent(s3, s6, solcmp);
274    assertLtInequivalent(s3, s7, solcmp);
275
276    assertLtEquivalent(s4, s4, solcmp);
277    assertLtInequivalent(s4, s5, solcmp);
278    assertLtInequivalent(s4, s6, solcmp);
279    assertLtInequivalent(s4, s7, solcmp);
280
281    assertLtEquivalent(s5, s5, solcmp);
282    assertLtEquivalent(s5, s6, solcmp);
283    assertLtInequivalent(s5, s7, solcmp);
284
285    assertLtEquivalent(s6, s6, solcmp);
286    assertLtInequivalent(s6, s7, solcmp);
287
288    assertLtEquivalent(s7, s7, solcmp);
289  }
290
291  // Check that rejections of versions don't block out all versions of
292  // the package (this actually happened once).  As installing version
293  // 2 of everything is a solution, rejecting the third versions
294  // should be OK.
295  void testRejections()
296  {
297    dummy_universe_ref u = parseUniverse(dummy_universe_1);
298    dummy_resolver r(10, -300, -100, 100000, 0, 50000, u);
299
300    r.reject_version(u.find_package("a").version_from_name("v3"));
301    r.reject_version(u.find_package("b").version_from_name("v3"));
302    r.reject_version(u.find_package("c").version_from_name("v3"));
303
304    try
305      {
306        r.find_next_solution(1000000, NULL);
307      }
308    catch(NoMoreSolutions)
309      {
310        CPPUNIT_FAIL("Expected at least one solution, got none.");
311      }
312  }
313
314  // Check that joint scores work.
315  void testJointScores()
316  {
317    typedef dummy_universe_ref::package package;
318    typedef dummy_universe_ref::version version;
319    typedef dummy_universe_ref::dep dep;
320    typedef dummy_solution::action action;
321
322    dummy_universe_ref u = parseUniverse(dummy_universe_2);
323    dummy_resolver r(10, -300, -100, 10000000, 0, 500, u);
324    r.set_debug(true);
325    // Score the combination of b, v1 and a, v2 highly.
326    package a = u.find_package("a");
327    package b = u.find_package("b");
328    package c = u.find_package("c");
329    version av2 = a.version_from_name("v2");
330    version bv1 = b.version_from_name("v1");
331    version bv2 = b.version_from_name("v2");
332    version cv2 = c.version_from_name("v2");
333
334    imm::set<version> action_pair, action_pair2;
335    action_pair.insert(av2);
336    action_pair.insert(bv1);
337    action_pair2.insert(bv2);
338    action_pair2.insert(cv2);
339
340    const int test_score = 100000;
341    const int test_score2 = -200000;
342    r.add_joint_score(action_pair, test_score);
343    r.add_joint_score(action_pair2, test_score2);
344
345    bool saw_one_positive = false;
346    bool saw_one_negative = false;
347    bool saw_one_positive2 = false;
348    while(1)
349      {
350        try
351          {
352            dummy_solution sol = r.find_next_solution(1000000, NULL);
353
354            if(sol.version_of(a) == av2 &&
355               sol.version_of(b) == bv1)
356              {
357                saw_one_positive = true;
358                // Check that we *don't* apply the joint score in
359                // this case, since b starts out at v1.
360                CPPUNIT_ASSERT_EQUAL((int)(sol.get_actions().size()) * r.get_step_score() + r.get_full_solution_score(),
361                                     sol.get_score());
362              }
363            else if(sol.version_of(b) == bv2 &&
364                    sol.version_of(c) == cv2)
365              {
366                saw_one_positive2 = true;
367                saw_one_negative = true;
368                CPPUNIT_ASSERT_EQUAL(test_score2 + ((int)sol.get_actions().size()) * r.get_step_score() + r.get_full_solution_score(),
369                                     sol.get_score());
370              }
371            else
372              {
373                saw_one_negative = true;
374                CPPUNIT_ASSERT_EQUAL((int)(sol.get_actions().size() * r.get_step_score()) + r.get_full_solution_score(),
375                                     sol.get_score());
376              }
377          }
378        catch(NoMoreTime&)
379          {
380            CPPUNIT_FAIL("Unable to solve the solution in the ridiculous amount of time I allocated.");
381          }
382        catch(NoMoreSolutions&)
383          {
384            if(!saw_one_positive)
385              CPPUNIT_FAIL("Expected at least one solution containing a, version 2 and b, version 1");
386            else if(!saw_one_negative)
387              CPPUNIT_FAIL("Expected at least one solution containing a, version 1 or b, version 2");
388            else if(!saw_one_positive2)
389              CPPUNIT_FAIL("Expected at least one solution containing b, version 2 and c, version 2");
390
391            break;
392          }
393      }
394  }
395
396  // Test that the resolver ignores already-generated solutions when
397  // generating successors.  Also tests that the resolver generates
398  // solutions in the expected order in a simple situation.
399  void testDropSolutionSupersets()
400  {
401    dummy_universe_ref u = parseUniverse(dummy_universe_2);
402    dummy_resolver r(10, -300, -100, 100000, 0, 50000, u);
403
404    dummy_universe::package a = u.find_package("a");
405    dummy_universe::version av1 = a.version_from_name("v1");
406    dummy_universe::version av2 = a.version_from_name("v2");
407
408    r.add_version_score(av2, 100);
409
410    dummy_solution s;
411
412    try
413      {
414        s = r.find_next_solution(100, NULL);
415      }
416    catch(NoMoreSolutions)
417      {
418        CPPUNIT_FAIL("Expected at least one solution, got none.");
419      }
420
421    CPPUNIT_ASSERT(s.version_of(a) == av2);
422    CPPUNIT_ASSERT_EQUAL(s.get_actions().size(), 1U);
423
424    try
425      {
426        s = r.find_next_solution(100, NULL);
427      }
428    catch(NoMoreSolutions)
429      {
430        CPPUNIT_FAIL("Expected at least two solutions, got only one.");
431      }
432
433    // Note that the only solutions to this problem are (a) install
434    // a:v2 and do anything else, or (b) leave a:v1 installed and
435    // install b:v2 and c:v2.  The search algorithm will "see" the
436    // option of installing a:v2 to resolve the second dep of a:v1
437    // after seeing the solution <a:=v2>.  If the bug we're testing
438    // for is present, it'll try to install a:v2 again; otherwise
439    // it'll reject that solution as containing a conflict.
440    CPPUNIT_ASSERT(s.version_of(a) != av2);
441
442    try
443      {
444        s = r.find_next_solution(100, NULL);
445        CPPUNIT_FAIL("Expected exactly two solutions, got more than two.");
446      }
447    catch(NoMoreSolutions)
448      {
449      }
450  }
451};
452
453CPPUNIT_TEST_SUITE_REGISTRATION(ResolverTest);
Note: See TracBrowser for help on using the browser.