// file      : tests/load/driver.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <iostream>
#include <exception>
#include <algorithm> // sort(), find()

#include <odb/session.hxx>
#include <odb/transaction.hxx>

#include <odb/pgsql/database.hxx>

#include <libbutl/process.mxx>
#include <libbutl/filesystem.mxx>

#include <libbrep/types.hxx>
#include <libbrep/utility.hxx>

#include <libbrep/package.hxx>
#include <libbrep/package-odb.hxx>

using std::cerr;
using std::endl;

using namespace odb::core;
using namespace butl;
using namespace brep;

using labels = small_vector<string, 5>;
using req_alts = small_vector<string, 1>;

static const path packages     ("packages.manifest");
static const path repositories ("repositories.manifest");

static bool
check_location (shared_ptr<package>& p)
{
  if (p->internal ())
    return p->location && *p->location ==
      path (p->name.string () + "-" + p->version.string () + ".tar.gz");
  else
    return !p->location;
}

static bool
check_external (const package& p)
{
  return p.summary.empty ()               &&
         p.topics.empty ()                &&
         p.keywords.empty ()              &&
         !p.description                   &&
         !p.url                           &&
         !p.package_url                   &&
         !p.email                         &&
         !p.package_email                 &&
         !p.internal ()                   &&
         p.other_repositories.size () > 0 &&
         p.priority == priority ()        &&
         p.changes.empty ()               &&
         p.license_alternatives.empty ()  &&
         p.dependencies.empty ()          &&
         p.requirements.empty ()          &&
         !p.sha256sum;
}

namespace bpkg
{
  static bool
  operator== (const build_constraint& x, const build_constraint& y)
  {
    return x.exclusion == y.exclusion && x.config == y.config &&
      x.target == y.target && x.comment == y.comment;
  }
}

static void
test_pkg_repos (const cstrings& loader_args,
                const dir_path& loadtab_dir,
                odb::pgsql::database&,
                const string& tenant);

static void
test_git_repos (const cstrings& loader_args,
                const dir_path& loadtab_dir,
                odb::pgsql::database&,
                const string& tenant);

int
main (int argc, char* argv[])
{
  auto print_usage = [argv]()
  {
    cerr << "usage: " << argv[0] << " (pkg|git) "
         << "<loader-path> [loader-options] <loadtab-dir>" << endl;
  };

  if (argc < 4)
  {
    print_usage ();
    return 1;
  }

  int i (1);
  repository_type rt;

  try
  {
    rt = to_repository_type (argv[i++]);
  }
  catch (const invalid_argument&)
  {
    print_usage ();
    return 1;
  }

  // Parse the tenant and database options.
  //
  string tenant;
  string user;
  string password;
  string name ("brep_package");
  string host;
  unsigned int port (0);

  for (++i; i < argc - 1; ++i)
  {
    string n (argv[i]);
    if (n == "--tenant")
      tenant = argv[++i];
    else if (n == "--db-user" || n == "-u")
      user = argv[++i];
    else if (n == "--db-password")
      password = argv[++i];
    else if (n == "--db-name" || n == "-n")
      name = argv[++i];
    else if (n == "--db-host" || n == "-h")
      host = argv[++i];
    else if (n == "--db-port" || n == "-p")
      port = std::stoul (argv[++i]);
  }

  if (i != argc - 1)
  {
    print_usage ();
    return 1;
  }

  dir_path loadtab_dir (argv[i]);

  // Make configuration file directory absolute to use it as base for internal
  // repositories relative local paths.
  //
  if (loadtab_dir.relative ())
    loadtab_dir.complete ();

  // Extract the loader args that are common for all tests (database options,
  // etc). Note that they don't contain the loadtab path and the trailing
  // NULL.
  //
  cstrings loader_args;
  for (int i (2); i != argc - 1; ++i)
    loader_args.push_back (argv[i]);

  try
  {
    odb::pgsql::database db (
      user,
      password,
      name,
      host,
      port,
      "options='-c default_transaction_isolation=serializable'");

    switch (rt)
    {
    case repository_type::pkg:
      {
        test_pkg_repos (loader_args, loadtab_dir, db, tenant);
        break;
      }
    case repository_type::git:
      {
        test_git_repos (loader_args, loadtab_dir, db, tenant);
        break;
      }
    default:
      {
        print_usage ();
        return 1;
      }
    }
  }
  // Fully qualified to avoid ambiguity with odb exception.
  //
  catch (const std::exception& e)
  {
    cerr << e << endl;
    return 1;
  }

  return 0;
}

static inline dependency
dep (const char* n, optional<version_constraint> c)
{
  return dependency {package_name (n), move (c), nullptr /* package */};
}

static inline version
dep_ver (const char* v)
{
  return version (v, false /* fold_zero_revision */);
}

static void
test_git_repos (const cstrings& loader_args,
                const dir_path& loadtab_dir,
                odb::pgsql::database& db,
                const string& tenant)
{
  path loadtab (loadtab_dir / "git-loadtab");

  cstrings args (loader_args);
  args.push_back ("--force");
  args.push_back ("--shallow");
  args.push_back (loadtab.string ().c_str ());
  args.push_back (nullptr);

  {
    // Run the loader.
    //
    assert (process (args.data ()).wait ());

    // Check persistent objects.
    //
    session s;
    transaction t (db.begin ());

    assert (db.query<repository> (
              query<repository>::id.tenant == tenant).size () == 1);

    assert (db.query<package> (
              query<package>::id.tenant == tenant).size () == 1);

    // Verify 'foo' repository.
    //
    shared_ptr<repository> r (
      db.load<repository> (repository_id (tenant,
                                          "git:example.com/foo#master")));

    assert (r->location.string () == "https://git.example.com/foo.git#master");
    assert (r->summary && *r->summary == "foo project repository");
    assert (r->buildable);

    // Verify libfoo package version.
    //
    // libfoo-1.0
    //
    shared_ptr<package> p (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("1.0"))));

    assert (p->fragment &&
            *p->fragment == "0f50af28d1cfb0c22f5b88e2bf674ab732e058d9");

    assert (p->dependencies.size () == 1);
    assert (p->dependencies[0].size () == 1);

    assert (p->dependencies[0][0] ==
            dep ("libmisc",
                 version_constraint (
                   dep_ver ("1.0"), false, dep_ver ("1.0"), false)));

    assert (p->buildable);

    t.commit ();
  }
}

static void
test_pkg_repos (const cstrings& loader_args,
                const dir_path& loadtab_dir,
                odb::pgsql::database& db,
                const string& tenant)
{
  path p (loadtab_dir / dir_path ("1/stable") / packages);
  timestamp srt (file_mtime (p));

  path loadtab (loadtab_dir / "loadtab");

  // Load the repositories and check persistent objects validity.
  //
  {
    cstrings args (loader_args);
    args.push_back ("--force");
    args.push_back (loadtab.string ().c_str ());
    args.push_back (nullptr);

    // Run the loader.
    //
    assert (process (args.data ()).wait ());

    // Check persistent objects.
    //
    session s;
    transaction t (db.begin ());

    assert (db.query<repository> (
              query<repository>::id.tenant == tenant).size () == 7);

    assert (db.query<package> (
              query<package>::id.tenant == tenant).size () == 21);

    shared_ptr<repository> sr (
      db.load<repository> (repository_id (tenant,
                                          "pkg:dev.cppget.org/stable")));

    shared_ptr<repository> mr (
      db.load<repository> (repository_id (tenant,
                                          "pkg:dev.cppget.org/math")));

    shared_ptr<repository> cr (
      db.load<repository> (repository_id (tenant, "pkg:dev.cppget.org/misc")));

    shared_ptr<repository> tr (
      db.load<repository> (repository_id (tenant,
                                          "pkg:dev.cppget.org/testing")));

    shared_ptr<repository> gr (
      db.load<repository> (repository_id (tenant,
                                          "pkg:dev.cppget.org/staging")));

    // Verify 'stable' repository.
    //
    assert (sr->location.canonical_name () == "pkg:dev.cppget.org/stable");
    assert (sr->location.string () ==
            "http://dev.cppget.org/1/stable");
    assert (sr->display_name == "stable");
    assert (sr->priority == 1);
    assert (!sr->interface_url);
    assert (sr->email && *sr->email == "repoman@dev.cppget.org" &&
            sr->email->comment == "public mailing list");
    assert (sr->summary &&
            *sr->summary == "General C++ package stable repository");
    assert (sr->description && *sr->description ==
            "This is the awesome C++ package repository full of exciting "
            "stuff.");

    dir_path srp (loadtab.directory () / dir_path ("1/stable"));
    assert (sr->cache_location.path () == srp.normalize ());

    assert (!sr->buildable);

    assert (sr->packages_timestamp == srt);
    assert (sr->repositories_timestamp ==
            file_mtime (sr->cache_location.path () / repositories));

    assert (sr->internal);
    assert (sr->complements.empty ());
    assert (sr->prerequisites.size () == 2);
    assert (sr->prerequisites[0].load () == cr);
    assert (sr->prerequisites[1].load () == mr);

    // Verify libfoo package versions.
    //
    // libfoo-+0-X.Y
    //
    shared_ptr<package> fpvxy (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("+0-X.Y"))));

    assert (fpvxy->project == package_name ("libfoo"));
    assert (fpvxy->summary == "The Foo Library");
    assert (fpvxy->keywords.empty ());
    assert (!fpvxy->description);
    assert (!fpvxy->url);
    assert (!fpvxy->package_url);
    assert (!fpvxy->email);
    assert (!fpvxy->package_email);

    assert (fpvxy->internal_repository.load () == mr);
    assert (fpvxy->other_repositories.empty ());

    assert (fpvxy->priority == priority::low);
    assert (fpvxy->changes.empty ());

    assert (fpvxy->license_alternatives.size () == 1);
    assert (fpvxy->license_alternatives[0].size () == 1);
    assert (fpvxy->license_alternatives[0][0] == "MIT");

    assert (fpvxy->dependencies.empty ());
    assert (fpvxy->requirements.empty ());

    assert (check_location (fpvxy));

    assert (fpvxy->sha256sum && *fpvxy->sha256sum ==
            "c994fd49f051ab7fb25f3a4e68ca878e484c5d3c2cb132b37d41224b0621b618");

    assert (fpvxy->buildable);

    // libfoo-1.0
    //
    shared_ptr<package> fpv1 (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("1.0"))));

    assert (fpv1->summary == "The Foo Library");
    assert (fpv1->keywords.empty ());
    assert (!fpv1->description);
    assert (!fpv1->url);
    assert (!fpv1->package_url);
    assert (!fpv1->email);
    assert (!fpv1->package_email);

    assert (fpv1->internal_repository.load () == sr);
    assert (fpv1->other_repositories.size () == 2);
    assert (fpv1->other_repositories[0].load () == mr);
    assert (fpv1->other_repositories[1].load () == cr);

    assert (fpv1->priority == priority::low);
    assert (fpv1->changes.empty ());

    assert (fpv1->license_alternatives.size () == 1);
    assert (fpv1->license_alternatives[0].size () == 1);
    assert (fpv1->license_alternatives[0][0] == "MIT");

    assert (fpv1->dependencies.empty ());
    assert (fpv1->requirements.empty ());

    assert (check_location (fpv1));

    assert (fpv1->sha256sum && *fpv1->sha256sum ==
            "e89c6d746f8b1ea3ec58d294946d2f683d133438d2ac8c88549ba24c19627e76");

    assert (fpv1->buildable);

    // libfoo-1.2.2
    //
    shared_ptr<package> fpv2 (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("1.2.2"))));

    assert (fpv2->summary == "The Foo library");

    assert (fpv2->keywords == labels ({"c++", "foo"}));
    assert (!fpv2->description);
    assert (fpv2->url && fpv2->url->string () == "http://www.example.com/foo/");
    assert (!fpv2->package_url);
    assert (fpv2->email && *fpv2->email == "foo-users@example.com");
    assert (!fpv2->package_email);

    assert (fpv2->internal_repository.load () == sr);
    assert (fpv2->other_repositories.empty ());
    assert (fpv2->priority == priority::low);
    assert (fpv2->changes.empty ());

    assert (fpv2->license_alternatives.size () == 1);
    assert (fpv2->license_alternatives[0].size () == 1);
    assert (fpv2->license_alternatives[0][0] == "MIT");

    assert (fpv2->dependencies.size () == 2);
    assert (fpv2->dependencies[0].size () == 1);
    assert (fpv2->dependencies[1].size () == 1);

    assert (fpv2->dependencies[0][0] ==
            dep ("libbar",
                 version_constraint (
                   nullopt, true, dep_ver ("2.4.0"), false)));

    assert (fpv2->dependencies[1][0] ==
            dep ("libexp",
                 version_constraint (
                   dep_ver ("+2-1.2"), false, dep_ver ("+2-1.2"), false)));

    assert (check_location (fpv2));

    assert (fpv2->sha256sum && *fpv2->sha256sum ==
            "088068ea3d69542a153f829cf836013374763148fba0a43d8047974f58b5efd7");

    assert (!fpv2->buildable);

    // libfoo-1.2.2-alpha.1
    //
    shared_ptr<package> fpv2a (
      db.load<package> (
        package_id (tenant,
                    package_name ("libfoo"),
                    version ("1.2.2-alpha.1"))));

    assert (fpv2a->summary == "The Foo library");
    assert (fpv2a->keywords == labels ({"c++", "foo"}));
    assert (!fpv2a->description);
    assert (fpv2a->url && fpv2a->url->string () == "ftp://www.example.com/foo/");
    assert (!fpv2a->package_url);
    assert (fpv2a->email && *fpv2a->email == "foo-users@example.com");
    assert (!fpv2a->package_email);

    assert (fpv2a->internal_repository.load () == sr);
    assert (fpv2a->other_repositories.empty ());
    assert (fpv2a->priority == priority::security);
    assert (fpv2a->changes.empty ());

    assert (fpv2a->license_alternatives.size () == 1);
    assert (fpv2a->license_alternatives[0].size () == 1);
    assert (fpv2a->license_alternatives[0][0] == "MIT");

    assert (fpv2a->dependencies.size () == 3);
    assert (fpv2a->dependencies[0].size () == 2);
    assert (fpv2a->dependencies[1].size () == 1);
    assert (fpv2a->dependencies[2].size () == 2);

    assert (fpv2a->dependencies[0][0] ==
            dep ("libmisc",
                 version_constraint (
                   dep_ver ("0.1"), false, dep_ver ("2.0.0-"), true)));

    assert (fpv2a->dependencies[0][1] ==
            dep ("libmisc",
                 version_constraint (
                   dep_ver ("2.0"), false, dep_ver ("5.0"), false)));

    assert (fpv2a->dependencies[1][0] ==
            dep ("libgenx",
                 version_constraint (
                   dep_ver ("0.2"), true, dep_ver ("3.0"), true)));

    assert (fpv2a->dependencies[2][0] ==
            dep ("libexpat",
                 version_constraint (
                   nullopt, true, dep_ver ("5.2"), true)));

    assert (fpv2a->dependencies[2][1] ==
            dep ("libexpat",
                 version_constraint (
                   dep_ver ("1"), true, dep_ver ("5.1"), false)));

    assert (fpv2a->requirements.empty ());

    assert (check_location (fpv2a));

    assert (fpv2a->sha256sum && *fpv2a->sha256sum ==
            "f5d3e9e6e8f9621a638b1375d31f0eb50e6279d8066170b25da21e84198cfd82");

    assert (!fpv2a->buildable);

    // libfoo-1.2.3-4
    //
    shared_ptr<package> fpv3 (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("1.2.3+4"))));

    assert (fpv3->summary == "The Foo library");
    assert (fpv3->keywords == labels ({"c++", "foo"}));
    assert (!fpv3->description);
    assert (fpv3->url && fpv3->url->string () == "http://www.example.com/foo/");
    assert (!fpv3->package_url);
    assert (fpv3->email && *fpv3->email == "foo-users@example.com");
    assert (!fpv3->package_email);

    assert (fpv3->internal_repository.load () == sr);
    assert (fpv3->other_repositories.empty ());
    assert (fpv3->priority == priority::medium);

    assert (fpv3->changes.empty ());

    assert (fpv3->license_alternatives.size () == 1);
    assert (fpv3->license_alternatives[0].size () == 1);
    assert (fpv3->license_alternatives[0][0] == "MIT");

    assert (fpv3->dependencies.size () == 1);
    assert (fpv3->dependencies[0].size () == 1);
    assert (fpv3->dependencies[0][0] ==
            dep ("libmisc",
                 version_constraint (
                   dep_ver ("2.0.0"), false, nullopt, true)));

    assert (check_location (fpv3));

    assert (fpv3->sha256sum && *fpv3->sha256sum ==
            "f2ebecac6cac8addd7c623bc1becf055e76b13a0d2dd385832b92c38c58956d8");

    assert (!fpv3->buildable);

    // libfoo-1.2.4
    //
    shared_ptr<package> fpv4 (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("1.2.4"))));

    assert (fpv4->summary == "The Foo Library");
    assert (fpv4->keywords == labels ({"c++", "foo"}));
    assert (*fpv4->description == "Very good foo library.");
    assert (fpv4->url && fpv4->url->string () == "http://www.example.com/foo/");
    assert (!fpv4->package_url);
    assert (fpv4->email && *fpv4->email == "foo-users@example.com");
    assert (!fpv4->package_email);

    assert (fpv4->internal_repository.load () == sr);
    assert (fpv4->other_repositories.empty ());
    assert (fpv4->priority == priority::low);
    assert (fpv4->changes == "some changes 1\n\nsome changes 2");

    assert (fpv4->license_alternatives.size () == 1);
    assert (fpv4->license_alternatives[0].comment ==
            "Permissive free software license.");
    assert (fpv4->license_alternatives[0].size () == 1);
    assert (fpv4->license_alternatives[0][0] == "MIT");

    assert (fpv4->dependencies.size () == 1);
    assert (fpv4->dependencies[0].size () == 1);
    assert (fpv4->dependencies[0][0] ==
            dep ("libmisc",
                 version_constraint (
                   dep_ver ("2.0.0"), false, nullopt, true)));

    assert (check_location (fpv4));

    assert (fpv4->sha256sum && *fpv4->sha256sum ==
            "aa1606323bfc59b70de642629dc5d8318cc5348e3646f90ed89406d975db1e1d");

    assert (!fpv4->buildable);

    // Verify 'math' repository.
    //
    assert (mr->location.canonical_name () == "pkg:dev.cppget.org/math");
    assert (mr->location.string () ==
            "http://dev.cppget.org/1/math");
    assert (mr->display_name == "math");
    assert (mr->priority == 2);
    assert (!mr->interface_url);
    assert (mr->email && *mr->email == "repoman@dev.cppget.org");
    assert (mr->summary && *mr->summary == "Math C++ package repository");
    assert (mr->description && *mr->description ==
            "This is the awesome C++ package repository full of remarkable "
            "algorithms and\nAPIs.");

    dir_path mrp (loadtab.directory () / dir_path ("1/math"));
    assert (mr->cache_location.path () == mrp.normalize ());

    assert (mr->buildable);

    assert (mr->packages_timestamp ==
            file_mtime (mr->cache_location.path () / packages));

    assert (mr->repositories_timestamp ==
            file_mtime (mr->cache_location.path () / repositories));

    assert (mr->internal);

    assert (mr->complements.empty ());
    assert (mr->prerequisites.size () == 1);
    assert (mr->prerequisites[0].load () == cr);

    // Verify libstudxml package version.
    //
    shared_ptr<package> xpv (
      db.load<package> (
        package_id (tenant,
                    package_name ("libstudxml"),
                    version ("1.0.0+1"))));

    assert (xpv->summary == "Modern C++ XML API");
    assert (xpv->keywords ==
            labels ({"c++", "xml", "parser", "serializer", "pull"}));
    assert (!xpv->description);
    assert (xpv->url &&
            xpv->url->string () == "http://www.codesynthesis.com/projects/libstudxml/");
    assert (!xpv->package_url);
    assert (xpv->email && *xpv->email == email ("studxml-users@example.com",
                   "Public mailing list, posts by  non-members "
                   "are allowed but moderated."));

    assert (xpv->package_email &&
            *xpv->package_email == email ("studxml-package@example.com",
                                          "Direct email to the packager."));
    assert (xpv->build_warning_email &&
            *xpv->build_warning_email ==
            email ("studxml-warnings@example.com"));

    assert (xpv->build_error_email &&
            *xpv->build_error_email ==
            email ("studxml-errors@example.com"));

    assert (xpv->internal_repository.load () == mr);
    assert (xpv->other_repositories.empty ());
    assert (xpv->priority == priority::low);
    assert (xpv->changes.empty ());

    assert (xpv->license_alternatives.size () == 1);
    assert (xpv->license_alternatives[0].size () == 1);
    assert (xpv->license_alternatives[0][0] == "MIT");

    assert (xpv->dependencies.size () == 2);
    assert (xpv->dependencies[0].size () == 1);
    assert (xpv->dependencies[0][0] ==
            dep ("libexpat",
                 version_constraint (
                   dep_ver ("2.0.0"), false, nullopt, true)));

    assert (xpv->dependencies[1].size () == 1);
    assert (xpv->dependencies[1][0] == dep ("libgenx", nullopt));

    assert (xpv->requirements.empty ());

    assert (check_location (xpv));

    assert (xpv->sha256sum && *xpv->sha256sum ==
            "1833906dd93ccc0cda832d6a1b3ef9ed7877bb9958b46d9b2666033d4a7919c9");

    assert (xpv->buildable);

    // Verify libfoo package versions.
    //
    // libfoo-1.2.4+1
    //
    shared_ptr<package> fpv5 (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("1.2.4+1"))));

    assert (fpv5->summary == "The Foo Math Library");
    assert (fpv5->topics ==
            labels ({"math library", "math API", "libbaz fork"}));
    assert (fpv5->keywords == labels ({"c++", "foo", "math", "best"}));
    assert (*fpv5->description ==
            "A modern C++ library with easy to use linear algebra and lot "
            "of optimization\ntools.\n\nThere are over 100 functions in "
            "total with an extensive test suite. The API is\nsimilar to "
            "~~mathlab~~ **MATLAB**.\n\nUseful for conversion of research "
            "code into production environments.");

    assert (fpv5->url && fpv5->url->string () == "http://www.example.com/foo/");

    assert (fpv5->doc_url && fpv5->doc_url->string () ==
            "http://www.example.org/projects/libfoo/man.xhtml" &&
            fpv5->doc_url->comment == "Documentation page.");

    assert (fpv5->src_url && fpv5->src_url->string () ==
            "http://scm.example.com/?p=odb/libodb.git;a=tree" &&
            fpv5->src_url->comment == "Source tree url.");

    assert (fpv5->package_url &&
            fpv5->package_url->string () == "http://www.example.com/foo/pack");
    assert (fpv5->email && *fpv5->email == "foo-users@example.com");
    assert (fpv5->package_email &&
            *fpv5->package_email == "pack@example.com");

    assert (fpv5->internal_repository.load () == mr);
    assert (fpv5->other_repositories.size () == 1);
    assert (fpv5->other_repositories[0].load () == cr);

    assert (fpv5->priority == priority::high);
    assert (fpv5->priority.comment ==
            "Critical bug fixes, performance improvement.");

    const char ch[] = R"DLM(1.2.4+1
 * applied patch for critical bug-219
 * regenerated documentation

1.2.4
 * test suite extended significantly)DLM";

    assert (fpv5->changes == ch);

    assert (fpv5->license_alternatives.size () == 2);
    assert (fpv5->license_alternatives[0].comment ==
            "If using with GNU TLS.");
    assert (fpv5->license_alternatives[0].size () == 2);
    assert (fpv5->license_alternatives[0][0] == "LGPLv2");
    assert (fpv5->license_alternatives[0][1] == "MIT");
    assert (fpv5->license_alternatives[1].comment ==
            "If using with OpenSSL.");
    assert (fpv5->license_alternatives[1].size () == 1);
    assert (fpv5->license_alternatives[1][0] == "BSD");

    assert (fpv5->dependencies.size () == 3);
    assert (fpv5->dependencies[0].size () == 2);
    assert (fpv5->dependencies[0].comment ==
            "Crashes with 1.1.0-2.3.0.");

    assert (fpv5->dependencies[0][0] ==
            dep ("libmisc",
                 version_constraint (
                   nullopt, true, dep_ver ("1.1"), true)));

    assert (fpv5->dependencies[0][1] ==
            dep ("libmisc",
                 version_constraint (
                   dep_ver ("2.3.0+0"), true, nullopt, true)));

    assert (fpv5->dependencies[1].size () == 1);
    assert (fpv5->dependencies[1].comment.empty ());

    assert (fpv5->dependencies[1][0] ==
            dep ("libexp",
                 version_constraint (
                   dep_ver ("1.0"), false, nullopt, true)));

    assert (fpv5->dependencies[2].size () == 2);
    assert (fpv5->dependencies[2].comment == "The newer the better.");

    assert (fpv5->dependencies[2][0] == dep ("libstudxml", nullopt));
    assert (fpv5->dependencies[2][1] == dep ("libexpat", nullopt));

    requirements& fpvr5 (fpv5->requirements);
    assert (fpvr5.size () == 4);

    assert (fpvr5[0] == req_alts ({"linux", "windows", "macosx"}));
    assert (!fpvr5[0].conditional);
    assert (fpvr5[0].comment == "Symbian support is coming.");

    assert (fpvr5[1] == req_alts ({"c++11"}));
    assert (!fpvr5[1].conditional);
    assert (fpvr5[1].comment.empty ());

    assert (fpvr5[2].empty ());
    assert (fpvr5[2].conditional);
    assert (fpvr5[2].comment ==
            "libc++ standard library if using Clang on Mac OS X.");

    assert (fpvr5[3] == req_alts ({"vc++ >= 12.0"}));
    assert (fpvr5[3].conditional);
    assert (fpvr5[3].comment == "Only if using VC++ on Windows.");

    assert (check_location (fpv5));

    assert (fpv5->sha256sum && *fpv5->sha256sum ==
            "c02b6033107387e05f48aa62ee6498152c967deb0e91a62f1e618fe9fd1bc644");

    assert (fpv5->buildable);

    // Verify libexp package version.
    //
    // libexp-+2-1.2
    //
    shared_ptr<package> epv (
      db.load<package> (
        package_id (tenant, package_name ("libexp"), version ("+2-1.2+1"))));

    assert (epv->upstream_version && *epv->upstream_version == "1.2.abc.15-x");
    assert (epv->project == "mathLab");
    assert (epv->summary == "The exponent");
    assert (epv->keywords == labels ({"mathlab", "c++", "exponent"}));
    assert (epv->description && *epv->description ==
            "The exponent math function.");
    assert (epv->url && epv->url->string () == "http://exp.example.com");
    assert (!epv->package_url);
    assert (epv->email && *epv->email == email ("users@exp.example.com"));
    assert (!epv->package_email);
    assert (epv->build_email && *epv->build_email == "builds@exp.example.com");

    assert (epv->internal_repository.load () == mr);
    assert (epv->other_repositories.empty ());
    assert (epv->priority == priority (priority::low));
    assert (epv->changes.empty ());

    assert (epv->license_alternatives.size () == 1);
    assert (epv->license_alternatives[0].size () == 1);
    assert (epv->license_alternatives[0][0] == "MIT");

    assert (epv->dependencies.size () == 2);
    assert (epv->dependencies[0].size () == 1);
    assert (epv->dependencies[0][0] == dep ("libmisc", nullopt));

    assert (epv->dependencies[1].size () == 1);
    assert (epv->dependencies[1][0] ==
            dep ("libpq",
                 version_constraint (
                   dep_ver ("9.0.0"), false, nullopt, true)));

    assert (epv->requirements.empty ());

    assert (epv->buildable);

    db.load (*epv, epv->build_section);

    assert (
      epv->build_constraints ==
      build_constraints ({
          build_constraint (
            false, "windows**d", optional<string> ("x86_64**"), ""),
        build_constraint (false, "windows-vc_13**", nullopt, ""),
        build_constraint (true, "**", nullopt, "Only supported on Windows.")}));

    assert (check_location (epv));
    assert (epv->sha256sum && *epv->sha256sum ==
            "317c8c6f45d9dfdfdef3a823411920cecd51729c7c4f58f9a0b0bbd681c07bd6");

    // Verify libpq package version.
    //
    // libpq-0
    //
    shared_ptr<package> qpv (
      db.load<package> (package_id (tenant,
                                    package_name ("libpq"),
                                    version ("0"))));

    assert (qpv->summary == "PostgreSQL C API client library");
    assert (!qpv->buildable);

    // Verify 'misc' repository.
    //
    assert (cr->location.canonical_name () == "pkg:dev.cppget.org/misc");
    assert (cr->location.string () ==
            "http://dev.cppget.org/1/misc");
    assert (cr->display_name.empty ());
    assert (cr->priority == 0);
    assert (cr->interface_url &&
            *cr->interface_url == "http://misc.cppget.org/");
    assert (!cr->email);
    assert (!cr->summary);
    assert (!cr->description);

    dir_path crp (loadtab.directory () / dir_path ("1/misc"));
    assert (cr->cache_location.path () == crp.normalize ());

    assert (!cr->buildable);

    assert (cr->packages_timestamp ==
            file_mtime (cr->cache_location.path () / packages));

    assert (cr->repositories_timestamp ==
            file_mtime (cr->cache_location.path () / repositories));

    assert (!cr->internal);
    assert (cr->prerequisites.empty ());
    assert (cr->complements.size () == 1);
    assert (cr->complements[0].load () == tr);

    // Verify libbar package version.
    //
    // libbar-2.4.0+3
    //
    shared_ptr<package> bpv (
      db.load<package> (
        package_id (tenant, package_name ("libbar"), version ("2.4.0+3"))));

    assert (check_external (*bpv));
    assert (bpv->other_repositories.size () == 1);
    assert (bpv->other_repositories[0].load () == cr);
    assert (check_location (bpv));

    assert (!bpv->buildable);

    // Verify libfoo package versions.
    //
    // libfoo-0.1
    //
    shared_ptr<package> fpv0 (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("0.1"))));

    assert (check_external (*fpv0));
    assert (fpv0->other_repositories.size () == 1);
    assert (fpv0->other_repositories[0].load () == cr);
    assert (check_location (fpv0));

    assert (!fpv0->buildable);

    // libfoo-1.2.4+2
    //
    shared_ptr<package> fpv6 (
      db.load<package> (
        package_id (tenant, package_name ("libfoo"), version ("1.2.4+2"))));

    assert (check_external (*fpv6));
    assert (fpv6->other_repositories.size () == 1);
    assert (fpv6->other_repositories[0].load () == cr);
    assert (check_location (fpv6));

    assert (!fpv6->buildable);

    // Verify 'testing' repository.
    //
    assert (tr->location.canonical_name () == "pkg:dev.cppget.org/testing");
    assert (tr->location.string () ==
            "http://dev.cppget.org/1/testing");
    assert (tr->display_name == "testing");
    assert (tr->priority == 3);
    assert (tr->interface_url &&
            *tr->interface_url == "http://test.cppget.org/hello/");
    assert (!tr->email);
    assert (!tr->summary);
    assert (!tr->description);

    dir_path trp (loadtab.directory () / dir_path ("1/testing"));
    assert (tr->cache_location.path () == trp.normalize ());

    assert (!tr->buildable);

    assert (tr->packages_timestamp ==
            file_mtime (tr->cache_location.path () / packages));

    assert (tr->repositories_timestamp ==
            file_mtime (tr->cache_location.path () / repositories));

    assert (tr->internal);
    assert (tr->prerequisites.empty ());
    assert (tr->complements.size () == 1);
    assert (tr->complements[0].load () == gr);

    // Verify libmisc package version.
    //
    // libmisc-2.4.0
    //
    shared_ptr<package> mpv0 (
      db.load<package> (
        package_id (tenant, package_name ("libmisc"), version ("2.4.0"))));

    assert (mpv0->internal_repository.load () == tr);
    assert (mpv0->other_repositories.empty ());
    assert (check_location (mpv0));
    assert (!mpv0->buildable);

    // libmisc-2.3.0+1
    //
    shared_ptr<package> mpv1 (
      db.load<package> (
        package_id (tenant, package_name ("libmisc"), version ("2.3.0+1"))));

    assert (mpv1->internal_repository.load () == tr);
    assert (mpv1->other_repositories.empty ());
    assert (check_location (mpv1));
    assert (!mpv1->buildable);

    // Verify 'staging' repository.
    //
    assert (gr->location.canonical_name () == "pkg:dev.cppget.org/staging");
    assert (gr->location.string () ==
            "http://dev.cppget.org/1/staging");
    assert (gr->display_name.empty ());
    assert (gr->priority == 0);
    assert (gr->interface_url &&
            *gr->interface_url == "http://dev.cppget.org/");
    assert (!gr->email);
    assert (!gr->summary);
    assert (!gr->description);

    dir_path grp (loadtab.directory () / dir_path ("1/staging"));
    assert (gr->cache_location.path () == grp.normalize ());
    assert (!gr->buildable);

    assert (gr->packages_timestamp ==
            file_mtime (gr->cache_location.path () / packages));

    assert (gr->repositories_timestamp ==
            file_mtime (gr->cache_location.path () / repositories));

    assert (!gr->internal);
    assert (gr->prerequisites.empty ());
    assert (gr->complements.empty ());

    // Verify libexpat package version.
    //
    // libexpat-5.1
    //
    shared_ptr<package> tpv (
      db.load<package> (
        package_id (tenant, package_name ("libexpat"), version ("5.1"))));

    assert (check_external (*tpv));
    assert (tpv->other_repositories.size () == 1);
    assert (tpv->other_repositories[0].load () == gr);
    assert (check_location (tpv));
    assert (!tpv->buildable);

    // Verify libgenx package version.
    //
    // libgenx-1.0
    //
    shared_ptr<package> gpv (
      db.load<package> (
        package_id (tenant, package_name ("libgenx"), version ("1.0"))));

    assert (check_external (*gpv));
    assert (gpv->other_repositories.size () == 1);
    assert (gpv->other_repositories[0].load () == gr);
    assert (check_location (gpv));
    assert (!gpv->buildable);

    // Verify libmisc package version.
    //
    // libmisc-1.0
    //
    shared_ptr<package> mpv2 (
      db.load<package> (
        package_id (tenant, package_name ("libmisc"), version ("1.0"))));

    assert (check_external (*mpv2));
    assert (mpv2->other_repositories.size () == 1);
    assert (mpv2->other_repositories[0].load () == gr);
    assert (check_location (mpv2));
    assert (!mpv2->buildable);

    // Change package summary, update the object persistent state, rerun
    // the loader and make sure the model were not rebuilt.
    //
    bpv->summary = "test";
    db.update (bpv);

    t.commit ();
  }

  // Rerun the loader without --force and make sure the model were not
  // rebuilt.
  //
  {
    cstrings args (loader_args);
    args.push_back (loadtab.string ().c_str ());
    args.push_back (nullptr);

    assert (process (args.data ()).wait ());

    transaction t (db.begin ());

    shared_ptr<package> bpv (
      db.load<package> (
        package_id (tenant, package_name ("libbar"), version ("2.4.0+3"))));

    assert (bpv->summary == "test");

    t.commit ();
  }

  // Restore the original setup.
  //
  {
    cstrings args (loader_args);
    args.push_back ("--force");
    args.push_back (loadtab.string ().c_str ());
    args.push_back (nullptr);

    assert (process (args.data ()).wait ());

    transaction t (db.begin ());

    shared_ptr<package> bpv (
      db.find<package> (
        package_id (tenant, package_name ("libbar"), version ("2.4.0+3"))));

    // External package summary is not saved.
    //
    assert (bpv->summary.empty ());

    t.commit ();
  }
}