// test.cc // // Copyright (C) 2005, 2007-2010 Daniel Burrows // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of // the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. You should have // received a copy of the GNU General Public License along with this // program; see the file COPYING. If not, write to the Free // Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, // MA 02111-1307, USA. // // Demonstrates the use of the problem-resolver and (eventually) runs // tests on it. The convoluted adaptor classes are the way they are // in order to keep the APT end of things reasonably thin and // efficient. #include "dummy_universe.h" #include "problemresolver.h" #include "sanity_check_universe.h" #include #include #include #include #include #include #include #include using namespace std; namespace cw = cwidget; // This is a gross hack; without defining this here, we'd have to // somehow link in a higher-level library. log4cxx::LoggerPtr aptitude::Loggers::getAptitudeResolver() { return log4cxx::Logger::getLogger("aptitude.resolver"); } log4cxx::LoggerPtr aptitude::Loggers::getAptitudeResolverSearch() { return log4cxx::Logger::getLogger("aptitude.resolver.search"); } log4cxx::LoggerPtr aptitude::Loggers::getAptitudeResolverSearchGraph() { return log4cxx::Logger::getLogger("aptitude.resolver.search.graph"); } log4cxx::LoggerPtr aptitude::Loggers::getAptitudeResolverSearchTiers() { return log4cxx::Logger::getLogger("aptitude.resolver.search.tiers"); } // To make things easier, the tests are specified as plaintext files. // The syntax is quite simple: it consists of whitespace-separated // words, of the form: // // SCRIPT ::= "UNIVERSE" "[" UNIVERSE "]" TEST ... // UNIVERSE ::= (PACKAGE | DEP) ... // PACKAGE ::= "PACKAGE" pkgname "<" vername1 ... ">" currentver // DEP ::= "DEP" pkgname1 vername1 "->" "<" pkgname2 vername2 ... ">" // | "SOFTDEP" pkgname1 vername1 "->" "<" pkgname2 vername2 ... ">" // | "DEP" pkgname1 vername1 "!!" "<" pkgname2 vername2 ... ">" // // TEST ::= "TEST" step_score broken_score "{" SCORE ... "}" "EXPECT" "(" SOLN ... ")" // SCORE ::= "SCORE" pkgname "<" vername1 score1 ... ">" // | "SCORE" { (pkgname vername)* } score // SOLN ::= step_count "ANY" // | step_count "<" pkgname1 vername1 ... ">" // // The second DEP form is a Conflicts, and is implicitly converted to // dependency form internally. namespace { dummy_universe_ref::package parse_package(istream &f, dummy_universe_ref universe) { f >> ws; if(f.eof()) throw ParseError("Expected package name, got EOF"); string package; f >> package >> ws; return universe.find_package(package); } dummy_universe_ref::version parse_version(istream &f, dummy_universe_ref universe) { dummy_universe_ref::package package = parse_package(f, universe); f >> ws; if(f.eof()) throw ParseError("Expected version name, got EOF."); string version; f >> version >> ws; return package.version_from_name(version); } void parse_expected(istream &f, const std::string &expected) { f >> ws; if(f.eof()) throw ParseError(cw::util::ssprintf("Expected %s, got EOF.", expected.c_str())); string s; f >> s >> ws; if(s != expected) throw ParseError(cw::util::ssprintf("Expected %s, got %s.", expected.c_str(), s.c_str())); } imm::set parse_version_set_tail(istream &f, dummy_universe_ref universe) { imm::set rval; f >> ws; while(f.peek() != '}') { rval.insert(parse_version(f, universe)); f >> ws; } parse_expected(f, "}"); return rval; } } /** Reads the list of scores into the resolver. */ void read_scores(istream &f, dummy_universe_ref universe, dummy_resolver &resolver) { if(!universe) throw ParseError("Internal error: NULL universe in read_scores"); f >> ws; while(f) { f >> ws; if(f.eof()) throw ParseError("Expected '}' or SCORE, got EOF"); string s; f >> s >> ws; if(s == "}") return; else if(s == "SCORE") { if(f.eof()) throw ParseError("Expected package name following SCORE, got "+s); string pkgname; f >> pkgname >> ws; if(pkgname == "{") { imm::set joint_set = parse_version_set_tail(f, universe); int score; f >> ws; if(f.eof()) throw ParseError("Expected score, got EOF"); f >> score >> ws; if(!f) throw ParseError("Error parsing the score of a joint score set."); resolver.add_joint_score(joint_set, score); } else { dummy_universe::package pkg=universe.find_package(pkgname); if(f.eof()) throw ParseError("Expected '<' following package name, got EOF"); f >> s >> ws; if(s != "<") throw ParseError("Expected '<' following package name, got "+s); if(f.eof()) throw ParseError("Expected '>' or version name, got EOF"); while(f) { string vername; int score; f >> vername >> ws; if(vername == ">") break; dummy_universe::version ver=pkg.version_from_name(vername); if(f.eof()) throw ParseError("Expected score, got EOF"); f >> score >> ws; if(f.eof()) throw ParseError("Expected '>' or version name, got EOF"); if(!f) throw ParseError("Error reading score of " + pkgname + " " + vername); resolver.set_version_score(ver, score); } } } else throw ParseError("Expected 'SCORE' or '}', got "+s); } throw ParseError("Unexpected error reading score list."); } /** Reads the tail of a non-ANY SOLN form. */ map read_solution(istream &f, dummy_universe_ref universe) { if(!universe) throw ParseError("Internal error: NULL universe passed to read_solution"); map rval; f >> ws; while(f) { string s; if(f.eof()) throw ParseError("Expected '>' or package, got EOF"); f >> s >> ws; if(s == ">") return rval; else { if(f.eof()) throw ParseError("Expected version, got EOF"); dummy_universe::package pkg=universe.find_package(s); f >> s >> ws; if(f.eof()) throw ParseError("Expected '>' or package name, got EOF"); if(s == ">") throw ParseError("Expected version name, got '>'"); dummy_universe::version ver=pkg.version_from_name(s); if(rval.find(pkg) != rval.end()) throw ParseError("Package "+pkg.get_name()+" bound twice in solution"); rval[pkg]=ver; } } throw ParseError("Unexpected error reading solution list"); } int parse_int(const std::string &s) { if(s.empty()) throw ParseError("Can't parse an integer from an empty string."); char *endptr; long int rval = strtol(s.c_str(), &endptr, 0); if(*endptr != '\0') throw ParseError("Error in integer constant \"" + s + "\""); return rval; } void run_test_file(istream &f, bool show_world) { dummy_universe_ref universe=NULL; f >> ws; while(f) { string s; if(f.eof()) // This is the only place where EOF is valid. return; f >> s >> ws; if(s == "UNIVERSE") { if(f.eof()) throw ParseError("Expected '[' following UNIVERSE, got EOF."); f >> s >> ws; if(s != "[") throw ParseError("Expected '[' following UNIVERSE, got " + s); universe=parse_universe_tail(f); sanity_check_universe(universe); if(show_world) { cout << "Input universe:" << endl; dump_universe(universe, cout); } } else if(s == "TEST") { if(!universe) throw ParseError("Expected UNIVERSE before TEST"); if(f.eof()) throw ParseError("Expected step_score and broken_score following 'TEST', got EOF"); int step_score; int broken_score; int unfixed_soft_score; int infinity; int goal_score = 50; int future_horizon = 0; std::string goal_score_or_brace; f >> step_score >> broken_score >> unfixed_soft_score >> infinity >> ws >> goal_score_or_brace >> ws; if(f.eof()) throw ParseError("Expected '{' following broken_score, got EOF"); if(!f) throw ParseError("Error reading step_score, broken_score, unfixed_soft_score, infinity, max_succ, and goal_score after 'TEST'"); if(goal_score_or_brace != "{") { goal_score = parse_int(goal_score_or_brace); std::string future_horizon_or_brace; f >> future_horizon_or_brace >> ws; if(future_horizon_or_brace != "{") { future_horizon = parse_int(future_horizon_or_brace); f >> s >> ws; } else s = future_horizon_or_brace; } else s = goal_score; if(s != "{") throw ParseError("Expected '{' following TEST, got "+s); dummy_resolver resolver(step_score, broken_score, unfixed_soft_score, infinity, goal_score, // TODO: add the broken soft dep // tier operation to the test input // language. tier_operation(), future_horizon, imm::map(), universe); resolver.set_debug(true); read_scores(f, universe, resolver); if(f.eof()) throw ParseError("Expected 'EXPECT', got EOF"); f >> s >> ws; if(s != "EXPECT") throw ParseError("Expected 'EXPECT', got "+s); if(f.eof()) throw ParseError("Expected '(' following EXPECT, got EOF"); f >> s >> ws; if(s != "(") throw ParseError("Expected '(' following EXPECT, got "+s); while(f) { if(f.eof()) throw ParseError("Expected ')' or package name, got EOF"); f >> s >> ws; if(s == ")") break; int step_count=atoi(s.c_str()); if(step_count<=0) throw ParseError("step_count must be a positive integer, not "+s); f >> s >> ws; if(s == "ANY") { try { dummy_resolver::solution next_soln = resolver.find_next_solution(step_count, NULL); cout << "Next solution is "; next_soln.dump(cout); cout << " (ignored)" << endl; } catch(NoMoreTime) { cout << "Ran out of steps (ignored)" << endl; } catch(NoMoreSolutions) { cout << "Ran out of solutions (ignored)" << endl; } } else if(s == "<") { try { map expected=read_solution(f, universe); dummy_resolver::solution next_soln=resolver.find_next_solution(step_count, NULL); cout << "Next solution is "; next_soln.dump(cout); bool equal=true; // Compare the solution and the expected set by // checking their value at each package. // Slightly lame but correct and easier than // writing a proper cross-compare. for(dummy_universe::package_iterator pi = universe.packages_begin(); equal && !pi.end(); ++pi) { std::map::const_iterator expect_found = expected.find(*pi); dummy_resolver::version soln_version; bool soln_touches_package = next_soln.get_choices().get_version_of(*pi, soln_version); if(expect_found != expected.end()) { if(!soln_touches_package || soln_version != expect_found->second) equal = false; } else if(soln_touches_package) equal = false; } if(equal) cout << " (OK)" << endl; else { cout << " (FAILED)" << endl; cout << "Expected <"; for(map::const_iterator i=expected.begin(); i!=expected.end(); ++i) cout << i->first.get_name() << ":=" << i->second.get_name(); cout << ">" << endl; } } catch(NoMoreSolutions) { cout << "Ran out of solutions (FAILED)" << endl; } catch(NoMoreTime) { cout << "Ran out of time (FAILED)" << endl; } } else throw ParseError("Expected ANY or '<', got "+s); } } else throw ParseError("Expected UNIVERSE or TEST, got "+s); } } int main(int argc, char **argv) { int rval=0; bool show_world=false; for(int i=1; i> ws; run_test_file(f, show_world); } catch(const cwidget::util::Exception &e) { cerr << "Error reading " << argv[i] << ": " << e.errmsg() << endl; rval=-1; } } return rval; }