]> git.saurik.com Git - apt.git/commitdiff
edsp: optionally store a compressed copy of the last scenario
authorDavid Kalnischkies <david@kalnischkies.de>
Tue, 7 Jun 2016 15:01:33 +0000 (17:01 +0200)
committerDavid Kalnischkies <david@kalnischkies.de>
Wed, 8 Jun 2016 11:07:21 +0000 (13:07 +0200)
For bugreports and co it could be handy to have the scenario and all the
settings used in it around later for inspection for EDSP like protocols.
EDSP might not be the most interesting as the user can still interrupt
the process before the solution is applied and users tend to have an
opinion on the "rightness" of a solution, so it is disabled by default.

apt-pkg/edsp.cc
apt-pkg/edsp/edspsystem.cc
apt-pkg/init.cc
apt-private/private-cmndline.cc
apt-private/private-cmndline.h
apt-private/private-main.cc
cmdline/apt-dump-solver.cc
cmdline/apt-internal-solver.cc
cmdline/makefile
test/integration/test-external-dependency-solver-protocol

index 94cac4eb1564564431fc95e3f51033d1e2f78a42..58d2769f99a365639438c843c528ddae5756e133 100644 (file)
@@ -597,10 +597,12 @@ bool EDSP::WriteRequest(pkgDepCache &Cache, FileFd &output,
       WriteOkay(Okay, output, "Forbid-New-Install: yes\n");
    if (flags & Request::FORBID_REMOVE)
       WriteOkay(Okay, output, "Forbid-Remove: yes\n");
+   auto const solver = _config->Find("APT::Solver", "internal");
+   WriteOkay(Okay, output, "Solver: ", solver, "\n");
    if (_config->FindB("APT::Solver::Strict-Pinning", true) == false)
       WriteOkay(Okay, output, "Strict-Pinning: no\n");
    string solverpref("APT::Solver::");
-   solverpref.append(_config->Find("APT::Solver", "internal")).append("::Preferences");
+   solverpref.append(solver).append("::Preferences");
    if (_config->Exists(solverpref) == true)
       WriteOkay(Okay, output, "Preferences: ", _config->Find(solverpref,""), "\n");
    return WriteOkay(Okay, output, "\n");
@@ -926,15 +928,23 @@ bool EDSP::WriteError(char const * const uuid, std::string const &message, FileF
              "\n\n");
 }
                                                                        /*}}}*/
+static std::string findExecutable(std::vector<std::string> const &dirs, char const * const binary) {/*{{{*/
+       for (auto && dir : dirs) {
+               std::string const file = flCombine(dir, binary);
+               if (RealFileExists(file) == true)
+                       return file;
+       }
+       return "";
+}
+                                                                       /*}}}*/
 static pid_t ExecuteExternal(char const* const type, char const * const binary, char const * const configdir, int * const solver_in, int * const solver_out) {/*{{{*/
-       std::vector<std::string> const solverDirs = _config->FindVector(configdir);
-       std::string file;
-       for (std::vector<std::string>::const_iterator dir = solverDirs.begin();
-            dir != solverDirs.end(); ++dir) {
-               file = flCombine(*dir, binary);
-               if (RealFileExists(file.c_str()) == true)
-                       break;
-               file.clear();
+       auto const solverDirs = _config->FindVector(configdir);
+       auto const file = findExecutable(solverDirs, binary);
+       std::string dumper;
+       {
+               dumper = findExecutable(solverDirs, "apt-dump-solver");
+               if (dumper.empty())
+                       dumper = findExecutable(solverDirs, "dump");
        }
 
        if (file.empty() == true)
@@ -955,8 +965,18 @@ static pid_t ExecuteExternal(char const* const type, char const * const binary,
        if (Solver == 0) {
                dup2(external[0], STDIN_FILENO);
                dup2(external[3], STDOUT_FILENO);
-               const char* calling[2] = { file.c_str(), 0 };
-               execv(calling[0], (char**) calling);
+               auto const dumpfile = _config->FindFile((std::string("Dir::Log::") + type).c_str());
+               auto const dumpdir = flNotFile(dumpfile);
+               if (dumper.empty() || dumpfile.empty() || dumper == file || CreateAPTDirectoryIfNeeded(dumpdir, dumpdir) == false)
+               {
+                  char const * const calling[] = { file.c_str(), nullptr };
+                  execv(calling[0], const_cast<char**>(calling));
+               }
+               else
+               {
+                  char const * const calling[] = { dumper.c_str(), dumpfile.c_str(), file.c_str(), nullptr };
+                  execv(calling[0], const_cast<char**>(calling));
+               }
                std::cerr << "Failed to execute " << type << " '" << binary << "'!" << std::endl;
                _exit(100);
        }
index 9b23dc3ca9d0379ea44e08462766f811354218de..2a78efe58d6e0702f7919ec6f16bc80a6a5559a7 100644 (file)
@@ -60,9 +60,12 @@ pkgPackageManager *edspLikeSystem::CreatePM(pkgDepCache * /*Cache*/) const
 // System::Initialize - Setup the configuration space..                        /*{{{*/
 bool edspLikeSystem::Initialize(Configuration &Cnf)
 {
+   Cnf.Set("Dir::Log", "/dev/null");
    // state is included completely in the input files
+   Cnf.Set("Dir::Etc::preferences", "/dev/null");
    Cnf.Set("Dir::Etc::preferencesparts", "/dev/null");
    Cnf.Set("Dir::State::status","/dev/null");
+   Cnf.Set("Dir::State::extended_states","/dev/null");
    Cnf.Set("Dir::State::lists","/dev/null");
    // do not store an mmap cache
    Cnf.Set("Dir::Cache::pkgcache", "");
index 0dfb109780b3e86828a7d7a796a63a4ebb34c401..a41d604d31d74e8854720e8a7915db67f0bccd8d 100644 (file)
@@ -56,7 +56,7 @@ bool pkgInitConfig(Configuration &Cnf)
    Cnf.CndSet("Dir::Cache::archives","archives/");
    Cnf.CndSet("Dir::Cache::srcpkgcache","srcpkgcache.bin");
    Cnf.CndSet("Dir::Cache::pkgcache","pkgcache.bin");
-   
+
    // Configuration
    Cnf.CndSet("Dir::Etc","etc/apt/");
    Cnf.CndSet("Dir::Etc::sourcelist","sources.list");
@@ -72,7 +72,7 @@ bool pkgInitConfig(Configuration &Cnf)
    Cnf.CndSet("Dir::Bin::solvers::","/usr/lib/apt/solvers");
    Cnf.CndSet("Dir::Media::MountPath","/media/apt");
 
-   // State   
+   // State
    Cnf.CndSet("Dir::Log","var/log/apt");
    Cnf.CndSet("Dir::Log::Terminal","term.log");
    Cnf.CndSet("Dir::Log::History","history.log");
index 7e50b140156ce7b4e560278b3488a570ba89b793..135ee3c4e39362b0014f9ee1c25361bb75bb06d3 100644 (file)
@@ -128,6 +128,11 @@ static bool addArgumentsAPTConfig(std::vector<CommandLine::Args> &Args, char con
    else
       return false;
 
+   return true;
+}
+                                                                       /*}}}*/
+static bool addArgumentsAPTDumpSolver(std::vector<CommandLine::Args> &, char const * const)/*{{{*/
+{
    return true;
 }
                                                                        /*}}}*/
@@ -347,6 +352,7 @@ std::vector<CommandLine::Args> getCommandArgs(APT_CMD const Program, char const
         case APT_CMD::APT_CACHE: addArgumentsAPTCache(Args, Cmd); break;
         case APT_CMD::APT_CDROM: addArgumentsAPTCDROM(Args, Cmd); break;
         case APT_CMD::APT_CONFIG: addArgumentsAPTConfig(Args, Cmd); break;
+        case APT_CMD::APT_DUMP_SOLVER: addArgumentsAPTDumpSolver(Args, Cmd); break;
         case APT_CMD::APT_EXTRACTTEMPLATES: addArgumentsAPTExtractTemplates(Args, Cmd); break;
         case APT_CMD::APT_FTPARCHIVE: addArgumentsAPTFTPArchive(Args, Cmd); break;
         case APT_CMD::APT_HELPER: addArgumentsAPTHelper(Args, Cmd); break;
@@ -402,6 +408,7 @@ static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector<
       case APT_CMD::APT_CACHE: cmd = "apt-cache(8)"; break;
       case APT_CMD::APT_CDROM: cmd = "apt-cdrom(8)"; break;
       case APT_CMD::APT_CONFIG: cmd = "apt-config(8)"; break;
+      case APT_CMD::APT_DUMP_SOLVER: cmd = nullptr; break;
       case APT_CMD::APT_EXTRACTTEMPLATES: cmd = "apt-extracttemplates(1)"; break;
       case APT_CMD::APT_FTPARCHIVE: cmd = "apt-ftparchive(1)"; break;
       case APT_CMD::APT_GET: cmd = "apt-get(8)"; break;
@@ -412,14 +419,15 @@ static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector<
    }
    if (cmd != nullptr)
       ioprintf(std::cout, _("See %s for more information about the available commands."), cmd);
-   std::cout << std::endl <<
-      _("Configuration options and syntax is detailed in apt.conf(5).\n"
-           "Information about how to configure sources can be found in sources.list(5).\n"
-           "Package and version choices can be expressed via apt_preferences(5).\n"
-           "Security details are available in apt-secure(8).\n");
+   if (Binary != APT_CMD::APT_DUMP_SOLVER && Binary != APT_CMD::APT_INTERNAL_SOLVER)
+      std::cout << std::endl <<
+        _("Configuration options and syntax is detailed in apt.conf(5).\n"
+              "Information about how to configure sources can be found in sources.list(5).\n"
+              "Package and version choices can be expressed via apt_preferences(5).\n"
+              "Security details are available in apt-secure(8).\n");
    if (Binary == APT_CMD::APT_GET || Binary == APT_CMD::APT)
       std::cout << std::right << std::setw(70) << _("This APT has Super Cow Powers.") << std::endl;
-   else if (Binary == APT_CMD::APT_HELPER)
+   else if (Binary == APT_CMD::APT_HELPER || Binary == APT_CMD::APT_DUMP_SOLVER)
       std::cout << std::right << std::setw(70) << _("This APT helper has Super Meep Powers.") << std::endl;
    return true;
 }
index 6235ef9f5870a356a17a11bceb1d1c7d405aba8e..c0c5a7455b46d92c536a6a476849a1b6cd218d74 100644 (file)
@@ -21,6 +21,7 @@ enum class APT_CMD {
    APT_INTERNAL_SOLVER,
    APT_MARK,
    APT_SORTPKG,
+   APT_DUMP_SOLVER,
 };
 struct aptDispatchWithHelp
 {
index d6517dd2a7acd3d89c1ec90e782753b17d19a06c..5a5940b7ea39b30de43e3d16914e6bc030ef3472 100644 (file)
@@ -34,6 +34,7 @@ void InitLocale(APT_CMD const binary)                         /*{{{*/
       case APT_CMD::APT_MARK:
         textdomain("apt");
         break;
+      case APT_CMD::APT_DUMP_SOLVER:
       case APT_CMD::APT_EXTRACTTEMPLATES:
       case APT_CMD::APT_FTPARCHIVE:
       case APT_CMD::APT_INTERNAL_SOLVER:
index 0de248e75861f1a5a5aa3996e0448d135af5ff46..c6d98cd97dec63208f6f8ff0a853bbaf5b56d5b8 100644 (file)
    ##################################################################### */
                                                                        /*}}}*/
 // Include Files                                                       /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/cmndline.h>
+#include <apt-pkg/configuration.h>
 #include <apt-pkg/edsp.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/strutl.h>
+
+#include <apt-private/private-cmndline.h>
 
-#include <string.h>
-#include <unistd.h>
 #include <cstdio>
 #include <iostream>
+#include <memory>
 #include <sstream>
 
-#include <config.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <string.h>
+#include <unistd.h>
+
+#include <apti18n.h>
                                                                        /*}}}*/
 
-// ShowHelp - Show a help screen                                       /*{{{*/
-// ---------------------------------------------------------------------
-/* */
-static bool ShowHelp() {
-       ioprintf(std::cout, "%s %s (%s)\n", PACKAGE, PACKAGE_VERSION, COMMON_ARCH);
-       std::cout <<
-               "Usage: apt-dump-solver\n"
-               "\n"
-               "apt-dump-solver is a dummy solver who just dumps its input to the\n"
-               "file specified in the environment variable APT_EDSP_DUMP_FILENAME and\n"
-               "exists with a proper EDSP error.\n"
-               "\n"
-               "                       This dump has lost Super Cow Powers.\n";
-       return true;
+static bool ShowHelp(CommandLine &)                                    /*{{{*/
+{
+   std::cout <<
+      _("Usage: apt-dump-solver\n"
+           "\n"
+           "apt-dump-solver is an interface to store an EDSP scenario in\n"
+           "a file and optionally forwards it to another solver.\n");
+   return true;
+}
+                                                                       /*}}}*/
+static std::vector<aptDispatchWithHelp> GetCommands()                  /*{{{*/
+{
+   return {};
+}
+                                                                       /*}}}*/
+static int WriteError(char const * const uid, std::ostringstream &out, FileFd &stdoutfd, pid_t const &Solver)/*{{{*/
+{
+   _error->DumpErrors(out);
+   // ensure the solver isn't printing into "our" error message, too
+   if (Solver != 0)
+      ExecWait(Solver, "dump", true);
+   EDSP::WriteError(uid, out.str(), stdoutfd);
+   return 0;
 }
                                                                        /*}}}*/
 int main(int argc,const char *argv[])                                  /*{{{*/
 {
-       // we really don't need anything
-       DropPrivileges();
-
-       if (argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1],"-h") == 0 ||
-           strcmp(argv[1],"-v") == 0 || strcmp(argv[1],"--version") == 0)) {
-               ShowHelp();
-               return 0;
-       }
-
-       FileFd stdoutfd;
-       if (stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
-          return 1;
-
-       char const * const filename = getenv("APT_EDSP_DUMP_FILENAME");
-       if (filename == NULL || strlen(filename) == 0)
-       {
-          EDSP::WriteError("ERR_NO_FILENAME", "You have to set the environment variable APT_EDSP_DUMP_FILENAME\n"
-                "to a valid filename to store the dump of EDSP solver input in.\n"
-                "For example with: export APT_EDSP_DUMP_FILENAME=/tmp/dump.edsp", stdoutfd);
-          return 0;
-       }
-
-       RemoveFile(argv[0], filename);
-       FileFd input, output;
-       if (input.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false ||
-             output.Open(filename, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive, FileFd::Extension, 0600) == false ||
-             CopyFile(input, output) == false || input.Close() == false || output.Close() == false)
-       {
-          std::ostringstream out;
-          out << "Writing EDSP solver input to file '" << filename << "' failed!\n";
-          _error->DumpErrors(out);
-          EDSP::WriteError("ERR_WRITE_ERROR", out.str(), stdoutfd);
-          return 0;
-       }
-
-       EDSP::WriteError("ERR_JUST_DUMPING", "I am too dumb, i can just dump!\nPlease use one of my friends instead!", stdoutfd);
-       return 0;
+   CommandLine CmdL;
+   ParseCommandLine(CmdL, APT_CMD::APT_DUMP_SOLVER, &_config, nullptr, argc, argv, &ShowHelp, &GetCommands);
+   _config->Clear("Dir::Log");
+
+   bool const is_forwarding_dumper = (CmdL.FileSize() != 0);
+
+   FileFd stdoutfd;
+   if (stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
+      return 252;
+
+   FileFd dump;
+   char const * const filename = is_forwarding_dumper ? CmdL.FileList[0] : getenv("APT_EDSP_DUMP_FILENAME");
+   if (filename == nullptr || strlen(filename) == 0)
+   {
+      if (is_forwarding_dumper == false)
+      {
+        EDSP::WriteError("ERR_NO_FILENAME", "You have to set the environment variable APT_EDSP_DUMP_FILENAME\n"
+              "to a valid filename to store the dump of EDSP solver input in.\n"
+              "For example with: export APT_EDSP_DUMP_FILENAME=/tmp/dump.edsp", stdoutfd);
+        return 0;
+      }
+   }
+   else
+   {
+      // ignore errors here as logging isn't really critical
+      _error->PushToStack();
+      if (dump.Open(filename, FileFd::WriteOnly | FileFd::Exclusive | FileFd::Create, FileFd::Extension, 0644) == false &&
+           is_forwarding_dumper == false)
+      {
+        _error->MergeWithStack();
+        std::ostringstream out;
+        out << "Writing EDSP solver input to file '" << filename << "' failed as it couldn't be created!\n";
+        return WriteError("ERR_CREATE_FILE", out, stdoutfd, 0);
+      }
+      _error->RevertToStack();
+   }
+
+   pid_t Solver = 0;
+   FileFd forward;
+   if (is_forwarding_dumper)
+   {
+      int external[] = {-1, -1};
+      if (pipe(external) != 0)
+        return 250;
+      for (int i = 0; i < 2; ++i)
+        SetCloseExec(external[i], true);
+
+      Solver = ExecFork();
+      if (Solver == 0) {
+        dup2(external[0], STDIN_FILENO);
+        execv(CmdL.FileList[1], const_cast<char**>(CmdL.FileList + 1));
+        std::cerr << "Failed to execute  '" << CmdL.FileList[1] << "'!" << std::endl;
+        _exit(100);
+      }
+      close(external[0]);
+
+      if (WaitFd(external[1], true, 5) == false)
+        return 251;
+
+      if (forward.OpenDescriptor(external[1], FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
+        return 252;
+   }
+
+   DropPrivileges();
+
+   FileFd input;
+   if (input.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false)
+   {
+      std::ostringstream out;
+      out << "Writing EDSP solver input to file '" << filename << "' failed as stdin couldn't be opened!\n";
+      return WriteError("ERR_READ_ERROR", out, stdoutfd, Solver);
+   }
+
+   constexpr size_t BufSize = 64 * 1024;
+   std::unique_ptr<char[]> Buf(new char[BufSize]);
+   unsigned long long ToRead = 0;
+   do {
+      if (input.Read(Buf.get(),BufSize, &ToRead) == false)
+      {
+        std::ostringstream out;
+        out << "Writing EDSP solver input to file '" << filename << "' failed as reading from stdin failed!\n";
+        return WriteError("ERR_READ_ERROR", out, stdoutfd, Solver);
+      }
+      if (ToRead == 0)
+        break;
+      if (forward.IsOpen() && forward.Failed() == false && forward.Write(Buf.get(),ToRead) == false)
+        forward.Close();
+      if (dump.IsOpen() && dump.Failed() == false && dump.Write(Buf.get(),ToRead) == false)
+        dump.Close();
+   } while (true);
+   input.Close();
+   forward.Close();
+   dump.Close();
+
+   if (_error->PendingError())
+   {
+      std::ostringstream out;
+      out << "Writing EDSP solver input to file '" << filename << "' failed due to write errors!\n";
+      return WriteError("ERR_WRITE_ERROR", out, stdoutfd, Solver);
+   }
+
+   if (is_forwarding_dumper)
+   {
+      // Wait and collect the error code
+      int Status;
+      while (waitpid(Solver, &Status, 0) != Solver)
+      {
+        if (errno == EINTR)
+           continue;
+
+        std::ostringstream out;
+        ioprintf(out, _("Waited for %s but it wasn't there"), CmdL.FileList[1]);
+        return WriteError("ERR_FORWARD", out, stdoutfd, 0);
+      }
+      if (WIFEXITED(Status))
+        return WEXITSTATUS(Status);
+      else
+        return 255;
+   }
+   else
+      EDSP::WriteError("ERR_JUST_DUMPING", "I am too dumb, i can just dump!\nPlease use one of my friends instead!", stdoutfd);
+   return 0;
 }
index 8296e8d01514f2633298337caa18dcad755dd654..aecb0eaba224ec6d511c81978c1b5759850b280f 100644 (file)
@@ -123,6 +123,7 @@ int main(int argc,const char *argv[])                                       /*{{{*/
        _config->Set("APT::System", "Debian APT solver interface");
        _config->Set("APT::Solver", "internal");
        _config->Set("edsp::scenario", "/nonexistent/stdin");
+       _config->Clear("Dir::Log");
        FileFd output;
        if (output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
           DIE("stdout couldn't be opened");
index 6d21b08031ab34d5bc1d20ec29240adb741e791e..cd8ff8252f10e9112dbfd8f75bff649eaac867d0 100644 (file)
@@ -88,8 +88,8 @@ include $(PROGRAM_H)
 
 # This just dumps out the state
 PROGRAM=apt-dump-solver
-SLIBS = -lapt-pkg $(INTLLIBS)
-LIB_MAKES = apt-pkg/makefile
+SLIBS = -lapt-pkg -lapt-private $(INTLLIBS)
+LIB_MAKES = apt-pkg/makefile apt-private/makefile
 SOURCE = apt-dump-solver.cc
 include $(PROGRAM_H)
 
index 4b13edc0948fafa57e72773aece99235947b5586..10b07e8961f1b926a4d9f706be1c6120a837e80e 100755 (executable)
@@ -25,11 +25,17 @@ insertpackage 'experimental' 'coolstuff' 'i386,amd64' '3' 'Depends: cool, stuff'
 
 setupaptarchive
 
+testsuccess aptget install --solver apt coolstuff -s
+testempty find -name 'edsp.last.*'
+echo 'Dir::Log::Solver "edsp.last.xz";' > rootdir/etc/apt/apt.conf.d/log-edsp.conf
+
 testfailure aptget install --solver dump coolstuff -s
-testsuccess grep ERR_NO_FILENAME rootdir/tmp/testfailure.output
+testsuccess grep 'ERR_NO_FILENAME' rootdir/tmp/testfailure.output
+testfailure test -s rootdir/var/log/apt/edsp.last.xz
 export APT_EDSP_DUMP_FILENAME="/nonexistent/apt/edsp.dump"
 testfailure aptget install --solver dump coolstuff -s
-testsuccess grep ERR_WRITE_ERROR rootdir/tmp/testfailure.output
+testsuccess grep 'ERR_CREATE_FILE' rootdir/tmp/testfailure.output
+testfailure test -s rootdir/var/log/apt/edsp.last.xz
 export APT_EDSP_DUMP_FILENAME="${TMPWORKINGDIRECTORY}/downloaded/dump.edsp"
 
 testfailureequal 'Reading package lists...
@@ -41,8 +47,8 @@ I am too dumb, i can just dump!
 Please use one of my friends instead!
 
 E: External solver failed with: I am too dumb, i can just dump!' aptget install --solver dump coolstuff -s
+testfailure test -s rootdir/var/log/apt/edsp.last.xz
 testsuccess test -s "$APT_EDSP_DUMP_FILENAME"
-rm -f "$APT_EDSP_DUMP_FILENAME"
 
 testsuccessequal 'Reading package lists...
 Building dependency tree...
@@ -52,6 +58,12 @@ The following NEW packages will be installed:
 0 upgraded, 1 newly installed, 0 to remove and 2 not upgraded.
 Inst coolstuff (2 unstable [amd64])
 Conf coolstuff (2 unstable [amd64])' aptget install --solver apt coolstuff -s
+testsuccess test -s rootdir/var/log/apt/edsp.last.xz
+sed -i -e 's#^Solver: dump$#Solver: apt#' "$APT_EDSP_DUMP_FILENAME"
+testequal "$(cat "$APT_EDSP_DUMP_FILENAME")
+" apthelper cat-file rootdir/var/log/apt/edsp.last.xz
+cp rootdir/var/log/apt/edsp.last.xz rootdir/var/log/apt/edsp.last.xz.1
+rm -f "$APT_EDSP_DUMP_FILENAME"
 
 testsuccessequal 'Reading package lists...
 Building dependency tree...
@@ -61,6 +73,7 @@ The following NEW packages will be installed:
 0 upgraded, 1 newly installed, 0 to remove and 2 not upgraded.
 Inst coolstuff (3 experimental [amd64])
 Conf coolstuff (3 experimental [amd64])' aptget install --solver apt coolstuff -s -t experimental
+testfailure cmp rootdir/var/log/apt/edsp.last.xz rootdir/var/log/apt/edsp.last.xz.1
 
 testsuccessequal "Reading package lists...
 Building dependency tree...