aboutsummaryrefslogtreecommitdiff
path: root/toolsrc/src/vcpkg-test
diff options
context:
space:
mode:
authorRobert Schumacher <roschuma@microsoft.com>2019-11-22 09:47:40 -0800
committerGitHub <noreply@github.com>2019-11-22 09:47:40 -0800
commit45f4b820e5743b89bca3508ba2028cdd5d8bbd17 (patch)
treef874a8c4a7392309bdbb86447288597ec0a4a281 /toolsrc/src/vcpkg-test
parent62d67d3bf8eeff1afa8009041fd08b8822676b7b (diff)
parent8831e8f25f1ff6546ee4a5291b91d599421637b3 (diff)
downloadvcpkg-45f4b820e5743b89bca3508ba2028cdd5d8bbd17.tar.gz
vcpkg-45f4b820e5743b89bca3508ba2028cdd5d8bbd17.zip
Merge branch 'master' into vcpkg_nuget
Diffstat (limited to 'toolsrc/src/vcpkg-test')
-rw-r--r--toolsrc/src/vcpkg-test/arguments.cpp109
-rw-r--r--toolsrc/src/vcpkg-test/catch.cpp11
-rw-r--r--toolsrc/src/vcpkg-test/chrono.cpp34
-rw-r--r--toolsrc/src/vcpkg-test/dependencies.cpp28
-rw-r--r--toolsrc/src/vcpkg-test/files.cpp243
-rw-r--r--toolsrc/src/vcpkg-test/hash.cpp276
-rw-r--r--toolsrc/src/vcpkg-test/paragraph.cpp445
-rw-r--r--toolsrc/src/vcpkg-test/plan.cpp1241
-rw-r--r--toolsrc/src/vcpkg-test/specifier.cpp134
-rw-r--r--toolsrc/src/vcpkg-test/statusparagraphs.cpp110
-rw-r--r--toolsrc/src/vcpkg-test/strings.cpp33
-rw-r--r--toolsrc/src/vcpkg-test/stringview.cpp17
-rw-r--r--toolsrc/src/vcpkg-test/supports.cpp79
-rw-r--r--toolsrc/src/vcpkg-test/update.cpp102
-rw-r--r--toolsrc/src/vcpkg-test/util.cpp183
15 files changed, 3045 insertions, 0 deletions
diff --git a/toolsrc/src/vcpkg-test/arguments.cpp b/toolsrc/src/vcpkg-test/arguments.cpp
new file mode 100644
index 000000000..c63a31396
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/arguments.cpp
@@ -0,0 +1,109 @@
+#include <catch2/catch.hpp>
+
+#include <vcpkg/vcpkgcmdarguments.h>
+
+#include <vector>
+
+using vcpkg::CommandSetting;
+using vcpkg::CommandStructure;
+using vcpkg::CommandSwitch;
+using vcpkg::VcpkgCmdArguments;
+
+TEST_CASE ("VcpkgCmdArguments from lowercase argument sequence", "[arguments]")
+{
+ std::vector<std::string> t = {"--vcpkg-root",
+ "C:\\vcpkg",
+ "--x-scripts-root=C:\\scripts",
+ "--debug",
+ "--sendmetrics",
+ "--printmetrics",
+ "--overlay-ports=C:\\ports1",
+ "--overlay-ports=C:\\ports2",
+ "--overlay-triplets=C:\\tripletsA",
+ "--overlay-triplets=C:\\tripletsB"};
+ auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
+
+ REQUIRE(*v.vcpkg_root_dir == "C:\\vcpkg");
+ REQUIRE(*v.scripts_root_dir == "C:\\scripts");
+ REQUIRE(v.debug);
+ REQUIRE(*v.debug.get());
+ REQUIRE(v.sendmetrics);
+ REQUIRE(*v.sendmetrics.get());
+ REQUIRE(v.printmetrics);
+ REQUIRE(*v.printmetrics.get());
+
+ REQUIRE(v.overlay_ports->size() == 2);
+ REQUIRE(v.overlay_ports->at(0) == "C:\\ports1");
+ REQUIRE(v.overlay_ports->at(1) == "C:\\ports2");
+
+ REQUIRE(v.overlay_triplets->size() == 2);
+ REQUIRE(v.overlay_triplets->at(0) == "C:\\tripletsA");
+ REQUIRE(v.overlay_triplets->at(1) == "C:\\tripletsB");
+}
+
+TEST_CASE ("VcpkgCmdArguments from uppercase argument sequence", "[arguments]")
+{
+ std::vector<std::string> t = {"--VCPKG-ROOT",
+ "C:\\vcpkg",
+ "--X-SCRIPTS-ROOT=C:\\scripts",
+ "--DEBUG",
+ "--SENDMETRICS",
+ "--PRINTMETRICS",
+ "--OVERLAY-PORTS=C:\\ports1",
+ "--OVERLAY-PORTS=C:\\ports2",
+ "--OVERLAY-TRIPLETS=C:\\tripletsA",
+ "--OVERLAY-TRIPLETS=C:\\tripletsB"};
+ auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
+
+ REQUIRE(*v.vcpkg_root_dir == "C:\\vcpkg");
+ REQUIRE(*v.scripts_root_dir == "C:\\scripts");
+ REQUIRE(v.debug);
+ REQUIRE(*v.debug.get());
+ REQUIRE(v.sendmetrics);
+ REQUIRE(*v.sendmetrics.get());
+ REQUIRE(v.printmetrics);
+ REQUIRE(*v.printmetrics.get());
+
+ REQUIRE(v.overlay_ports->size() == 2);
+ REQUIRE(v.overlay_ports->at(0) == "C:\\ports1");
+ REQUIRE(v.overlay_ports->at(1) == "C:\\ports2");
+
+ REQUIRE(v.overlay_triplets->size() == 2);
+ REQUIRE(v.overlay_triplets->at(0) == "C:\\tripletsA");
+ REQUIRE(v.overlay_triplets->at(1) == "C:\\tripletsB");
+}
+
+TEST_CASE ("VcpkgCmdArguments from argument sequence with valued options", "[arguments]")
+{
+ SECTION ("case 1")
+ {
+ std::array<CommandSetting, 1> settings = {{{"--a", ""}}};
+ CommandStructure cmdstruct = {"", 0, SIZE_MAX, {{}, settings}, nullptr};
+
+ std::vector<std::string> t = {"--a=b", "command", "argument"};
+ auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
+ auto opts = v.parse_arguments(cmdstruct);
+
+ REQUIRE(opts.settings["--a"] == "b");
+ REQUIRE(v.command_arguments.size() == 1);
+ REQUIRE(v.command_arguments[0] == "argument");
+ REQUIRE(v.command == "command");
+ }
+
+ SECTION ("case 2")
+ {
+ std::array<CommandSwitch, 2> switches = {{{"--a", ""}, {"--c", ""}}};
+ std::array<CommandSetting, 2> settings = {{{"--b", ""}, {"--d", ""}}};
+ CommandStructure cmdstruct = {"", 0, SIZE_MAX, {switches, settings}, nullptr};
+
+ std::vector<std::string> t = {"--a", "--b=c"};
+ auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
+ auto opts = v.parse_arguments(cmdstruct);
+
+ REQUIRE(opts.settings["--b"] == "c");
+ REQUIRE(opts.settings.find("--d") == opts.settings.end());
+ REQUIRE(opts.switches.find("--a") != opts.switches.end());
+ REQUIRE(opts.settings.find("--c") == opts.settings.end());
+ REQUIRE(v.command_arguments.size() == 0);
+ }
+}
diff --git a/toolsrc/src/vcpkg-test/catch.cpp b/toolsrc/src/vcpkg-test/catch.cpp
new file mode 100644
index 000000000..50331c644
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/catch.cpp
@@ -0,0 +1,11 @@
+#define CATCH_CONFIG_RUNNER
+#include <catch2/catch.hpp>
+
+#include <vcpkg/base/system.debug.h>
+
+int main(int argc, char** argv)
+{
+ vcpkg::Debug::g_debugging = true;
+
+ return Catch::Session().run(argc, argv);
+}
diff --git a/toolsrc/src/vcpkg-test/chrono.cpp b/toolsrc/src/vcpkg-test/chrono.cpp
new file mode 100644
index 000000000..fb8a0dee9
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/chrono.cpp
@@ -0,0 +1,34 @@
+#include <catch2/catch.hpp>
+
+#include <vcpkg/base/chrono.h>
+
+namespace Chrono = vcpkg::Chrono;
+
+TEST_CASE ("parse time", "[chrono]")
+{
+ auto timestring = "1990-02-03T04:05:06.0Z";
+ auto maybe_time = Chrono::CTime::parse(timestring);
+
+ REQUIRE(maybe_time.has_value());
+ REQUIRE(maybe_time.get()->to_string() == timestring);
+}
+
+TEST_CASE ("parse blank time", "[chrono]")
+{
+ auto maybe_time = Chrono::CTime::parse("");
+
+ REQUIRE_FALSE(maybe_time.has_value());
+}
+
+TEST_CASE ("difference of times", "[chrono]")
+{
+ auto maybe_time1 = Chrono::CTime::parse("1990-02-03T04:05:06.0Z");
+ auto maybe_time2 = Chrono::CTime::parse("1990-02-10T04:05:06.0Z");
+
+ REQUIRE(maybe_time1.has_value());
+ REQUIRE(maybe_time2.has_value());
+
+ auto delta = maybe_time2.get()->to_time_point() - maybe_time1.get()->to_time_point();
+
+ REQUIRE(std::chrono::duration_cast<std::chrono::hours>(delta).count() == 24 * 7);
+}
diff --git a/toolsrc/src/vcpkg-test/dependencies.cpp b/toolsrc/src/vcpkg-test/dependencies.cpp
new file mode 100644
index 000000000..2344bb990
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/dependencies.cpp
@@ -0,0 +1,28 @@
+#include <catch2/catch.hpp>
+
+#include <vcpkg/sourceparagraph.h>
+
+using namespace vcpkg;
+using Parse::parse_comma_list;
+
+TEST_CASE ("parse depends", "[dependencies]")
+{
+ auto v = expand_qualified_dependencies(parse_comma_list("libA (windows)"));
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.at(0).depend.name == "libA");
+ REQUIRE(v.at(0).qualifier == "windows");
+}
+
+TEST_CASE ("filter depends", "[dependencies]")
+{
+ auto deps = expand_qualified_dependencies(parse_comma_list("libA (windows), libB, libC (uwp)"));
+ auto v = filter_dependencies(deps, Triplet::X64_WINDOWS);
+ REQUIRE(v.size() == 2);
+ REQUIRE(v.at(0) == "libA");
+ REQUIRE(v.at(1) == "libB");
+
+ auto v2 = filter_dependencies(deps, Triplet::ARM_UWP);
+ REQUIRE(v.size() == 2);
+ REQUIRE(v2.at(0) == "libB");
+ REQUIRE(v2.at(1) == "libC");
+}
diff --git a/toolsrc/src/vcpkg-test/files.cpp b/toolsrc/src/vcpkg-test/files.cpp
new file mode 100644
index 000000000..d8bc5ba74
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/files.cpp
@@ -0,0 +1,243 @@
+#include <catch2/catch.hpp>
+#include <vcpkg-test/util.h>
+
+#include <vcpkg/base/files.h>
+#include <vcpkg/base/strings.h>
+
+#include <iostream>
+#include <random>
+
+#include <vector>
+
+using vcpkg::Test::AllowSymlinks;
+using vcpkg::Test::base_temporary_directory;
+using vcpkg::Test::can_create_symlinks;
+
+#define CHECK_EC_ON_FILE(file, ec) \
+ do \
+ { \
+ if (ec) \
+ { \
+ FAIL(file << ": " << ec.message()); \
+ } \
+ } while (0)
+
+namespace
+{
+ using uid_t = std::uniform_int_distribution<std::uint64_t>;
+ using urbg_t = std::mt19937_64;
+
+ urbg_t get_urbg(std::uint64_t index)
+ {
+ // smallest prime > 2**63 - 1
+ return urbg_t{index + 9223372036854775837ULL};
+ }
+
+ std::string get_random_filename(urbg_t& urbg) { return vcpkg::Strings::b32_encode(uid_t{}(urbg)); }
+
+ struct MaxDepth
+ {
+ std::uint64_t i;
+ explicit MaxDepth(std::uint64_t i) : i(i) {}
+ operator uint64_t() const { return i; }
+ };
+
+ struct Width
+ {
+ std::uint64_t i;
+ explicit Width(std::uint64_t i) : i(i) {}
+ operator uint64_t() const { return i; }
+ };
+
+ struct CurrentDepth
+ {
+ std::uint64_t i;
+ explicit CurrentDepth(std::uint64_t i) : i(i) {}
+ operator uint64_t() const { return i; }
+ CurrentDepth incremented() const { return CurrentDepth{i + 1}; }
+ };
+
+ void create_directory_tree(urbg_t& urbg,
+ vcpkg::Files::Filesystem& fs,
+ const fs::path& base,
+ MaxDepth max_depth,
+ AllowSymlinks allow_symlinks = AllowSymlinks::Yes,
+ Width width = Width{5},
+ CurrentDepth current_depth = CurrentDepth{0})
+ {
+ std::random_device rd;
+
+ // we want ~70% of our "files" to be directories, and then a third
+ // each of the remaining ~30% to be regular files, directory symlinks,
+ // and regular symlinks
+ constexpr std::uint64_t directory_min_tag = 0;
+ constexpr std::uint64_t directory_max_tag = 6;
+ constexpr std::uint64_t regular_file_tag = 7;
+ constexpr std::uint64_t regular_symlink_tag = 8;
+ constexpr std::uint64_t directory_symlink_tag = 9;
+
+ allow_symlinks = AllowSymlinks{allow_symlinks && can_create_symlinks()};
+
+ // if we're at the max depth, we only want to build non-directories
+ std::uint64_t file_type;
+ if (current_depth >= max_depth)
+ {
+ file_type = uid_t{regular_file_tag, directory_symlink_tag}(urbg);
+ }
+ else if (current_depth < 2)
+ {
+ file_type = directory_min_tag;
+ }
+ else
+ {
+ file_type = uid_t{directory_min_tag, regular_symlink_tag}(urbg);
+ }
+
+ if (!allow_symlinks && file_type > regular_file_tag)
+ {
+ file_type = regular_file_tag;
+ }
+
+ std::error_code ec;
+ if (file_type <= directory_max_tag)
+ {
+ fs.create_directory(base, ec);
+ if (ec)
+ {
+ CHECK_EC_ON_FILE(base, ec);
+ }
+
+ for (std::uint64_t i = 0; i < width; ++i)
+ {
+ create_directory_tree(urbg,
+ fs,
+ base / get_random_filename(urbg),
+ max_depth,
+ allow_symlinks,
+ width,
+ current_depth.incremented());
+ }
+ }
+ else if (file_type == regular_file_tag)
+ {
+ // regular file
+ fs.write_contents(base, "", ec);
+ }
+ else if (file_type == regular_symlink_tag)
+ {
+ // regular symlink
+ auto base_link = base;
+ base_link.replace_filename(base.filename().u8string() + "-orig");
+ fs.write_contents(base_link, "", ec);
+ CHECK_EC_ON_FILE(base_link, ec);
+ vcpkg::Test::create_symlink(base_link, base, ec);
+ }
+ else // type == directory_symlink_tag
+ {
+ // directory symlink
+ auto parent = base;
+ parent.remove_filename();
+ vcpkg::Test::create_directory_symlink(parent, base, ec);
+ }
+
+ CHECK_EC_ON_FILE(base, ec);
+ REQUIRE(fs::exists(fs.symlink_status(base, ec)));
+ CHECK_EC_ON_FILE(base, ec);
+ }
+
+ vcpkg::Files::Filesystem& setup()
+ {
+ auto& fs = vcpkg::Files::get_real_filesystem();
+
+ std::error_code ec;
+ fs.create_directory(base_temporary_directory(), ec);
+ CHECK_EC_ON_FILE(base_temporary_directory(), ec);
+
+ return fs;
+ }
+}
+
+TEST_CASE ("remove all", "[files]")
+{
+ auto urbg = get_urbg(0);
+
+ auto& fs = setup();
+
+ fs::path temp_dir = base_temporary_directory() / get_random_filename(urbg);
+ INFO("temp dir is: " << temp_dir);
+
+ create_directory_tree(urbg, fs, temp_dir, MaxDepth{5});
+
+ std::error_code ec;
+ fs::path fp;
+ fs.remove_all(temp_dir, ec, fp);
+ CHECK_EC_ON_FILE(fp, ec);
+
+ REQUIRE_FALSE(fs.exists(temp_dir, ec));
+ CHECK_EC_ON_FILE(temp_dir, ec);
+}
+
+#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
+TEST_CASE ("remove all -- benchmarks", "[files][!benchmark]")
+{
+ auto urbg = get_urbg(1);
+ auto& fs = setup();
+
+ struct
+ {
+ urbg_t& urbg;
+ vcpkg::Files::Filesystem& fs;
+
+ void operator()(Catch::Benchmark::Chronometer& meter, MaxDepth max_depth, AllowSymlinks allow_symlinks) const
+ {
+ std::vector<fs::path> temp_dirs;
+ temp_dirs.resize(meter.runs());
+
+ std::generate(begin(temp_dirs), end(temp_dirs), [&] {
+ fs::path temp_dir = base_temporary_directory() / get_random_filename(urbg);
+ create_directory_tree(urbg, fs, temp_dir, max_depth, allow_symlinks);
+ return temp_dir;
+ });
+
+ meter.measure([&](int run) {
+ std::error_code ec;
+ fs::path fp;
+ const auto& temp_dir = temp_dirs[run];
+
+ fs.remove_all(temp_dir, ec, fp);
+ CHECK_EC_ON_FILE(fp, ec);
+ });
+
+ for (const auto& dir : temp_dirs)
+ {
+ std::error_code ec;
+ REQUIRE_FALSE(fs.exists(dir, ec));
+ CHECK_EC_ON_FILE(dir, ec);
+ }
+ }
+ } do_benchmark = {urbg, fs};
+
+ BENCHMARK_ADVANCED("small directory, no symlinks")(Catch::Benchmark::Chronometer meter)
+ {
+ do_benchmark(meter, MaxDepth{2}, AllowSymlinks::No);
+ };
+
+ BENCHMARK_ADVANCED("large directory, no symlinks")(Catch::Benchmark::Chronometer meter)
+ {
+ do_benchmark(meter, MaxDepth{5}, AllowSymlinks::No);
+ };
+
+ if (can_create_symlinks())
+ {
+ BENCHMARK_ADVANCED("small directory, symlinks")(Catch::Benchmark::Chronometer meter)
+ {
+ do_benchmark(meter, MaxDepth{2}, AllowSymlinks::Yes);
+ };
+
+ BENCHMARK_ADVANCED("large directory, symlinks")(Catch::Benchmark::Chronometer meter)
+ {
+ do_benchmark(meter, MaxDepth{5}, AllowSymlinks::Yes);
+ };
+ }
+}
+#endif
diff --git a/toolsrc/src/vcpkg-test/hash.cpp b/toolsrc/src/vcpkg-test/hash.cpp
new file mode 100644
index 000000000..9f3ccc25e
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/hash.cpp
@@ -0,0 +1,276 @@
+#include <catch2/catch.hpp>
+
+#include <vcpkg/base/hash.h>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <map>
+
+namespace Hash = vcpkg::Hash;
+using vcpkg::StringView;
+
+// Require algorithm: Hash::Algorithm::Tag to be in scope
+#define CHECK_HASH(size, value, real_hash) \
+ do \
+ { \
+ unsigned char data[size]; \
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(value)); \
+ const auto hash = Hash::get_bytes_hash(data, data + size, algorithm); \
+ REQUIRE(hash == real_hash); \
+ } while (0)
+
+#define CHECK_HASH_OF(data, real_hash) \
+ do \
+ { \
+ const auto hash = Hash::get_bytes_hash(std::begin(data), std::end(data), algorithm); \
+ REQUIRE(hash == real_hash); \
+ } while (0)
+
+#define CHECK_HASH_STRING(data, real_hash) \
+ do \
+ { \
+ const auto hash = Hash::get_string_hash(data, algorithm); \
+ REQUIRE(hash == real_hash); \
+ } while (0)
+
+// Requires hasher: std::unique_ptr<Hash::Hasher> to be in scope
+#define CHECK_HASH_LARGE(size, value, real_hash) \
+ do \
+ { \
+ hasher->clear(); \
+ std::uint64_t remaining = size; \
+ unsigned char buffer[512]; \
+ std::fill(std::begin(buffer), std::end(buffer), static_cast<unsigned char>(value)); \
+ while (remaining) \
+ { \
+ if (remaining < 512) \
+ { \
+ hasher->add_bytes(std::begin(buffer), std::begin(buffer) + remaining); \
+ remaining = 0; \
+ } \
+ else \
+ { \
+ hasher->add_bytes(std::begin(buffer), std::end(buffer)); \
+ remaining -= 512; \
+ } \
+ } \
+ REQUIRE(hasher->get_hash() == real_hash); \
+ } while (0)
+
+TEST_CASE ("SHA1: basic tests", "[hash][sha1]")
+{
+ const auto algorithm = Hash::Algorithm::Sha1;
+
+ CHECK_HASH_STRING("", "da39a3ee5e6b4b0d3255bfef95601890afd80709");
+ CHECK_HASH_STRING(";", "2d14ab97cc3dc294c51c0d6814f4ea45f4b4e312");
+ CHECK_HASH_STRING("asdifasdfnas", "b77eb8a1b4c2ef6716d7d302647e4511b1a638a6");
+ CHECK_HASH_STRING("asdfanvoinaoifawenflawenfiwnofvnasfjvnaslkdfjlkasjdfanm,"
+ "werflawoienfowanevoinwai32910u2740918741o;j;wejfqwioaher9283hrpf;asd",
+ "c69bcd30c196c7050906d212722dd7a7659aad04");
+}
+
+TEST_CASE ("SHA1: NIST test cases (small)", "[hash][sha1]")
+{
+ const auto algorithm = Hash::Algorithm::Sha1;
+
+ CHECK_HASH_STRING("abc", "a9993e364706816aba3e25717850c26c9cd0d89d");
+ CHECK_HASH_STRING("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "84983e441c3bd26ebaae4aa1f95129e5e54670f1");
+}
+
+TEST_CASE ("SHA256: basic tests", "[hash][sha256]")
+{
+ const auto algorithm = Hash::Algorithm::Sha256;
+
+ CHECK_HASH_STRING("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+ CHECK_HASH_STRING(";", "41b805ea7ac014e23556e98bb374702a08344268f92489a02f0880849394a1e4");
+ CHECK_HASH_STRING("asdifasdfnas", "2bb1fb910831fdc11d5a3996425a84ace27aeb81c9c20ace9f60ac1b3218b291");
+ CHECK_HASH_STRING("asdfanvoinaoifawenflawenfiwnofvnasfjvnaslkdfjlkasjdfanm,"
+ "werflawoienfowanevoinwai32910u2740918741o;j;wejfqwioaher9283hrpf;asd",
+ "10c98034b424d4e40ca933bc524ea38b4e53290d76e8b38edc4ea2fec7f529aa");
+}
+
+TEST_CASE ("SHA256: NIST test cases (small)", "[hash][sha256]")
+{
+ const auto algorithm = Hash::Algorithm::Sha256;
+
+ CHECK_HASH(1, 0xbd, "68325720aabd7c82f30f554b313d0570c95accbb7dc4b5aae11204c08ffe732b");
+ {
+ const unsigned char data[] = {0xc9, 0x8c, 0x8e, 0x55};
+ CHECK_HASH_OF(data, "7abc22c0ae5af26ce93dbb94433a0e0b2e119d014f8e7f65bd56c61ccccd9504");
+ }
+ CHECK_HASH(55, 0, "02779466cdec163811d078815c633f21901413081449002f24aa3e80f0b88ef7");
+ CHECK_HASH(56, 0, "d4817aa5497628e7c77e6b606107042bbba3130888c5f47a375e6179be789fbb");
+ CHECK_HASH(57, 0, "65a16cb7861335d5ace3c60718b5052e44660726da4cd13bb745381b235a1785");
+ CHECK_HASH(64, 0, "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b");
+ CHECK_HASH(1000, 0, "541b3e9daa09b20bf85fa273e5cbd3e80185aa4ec298e765db87742b70138a53");
+ CHECK_HASH(1000, 'A', "c2e686823489ced2017f6059b8b239318b6364f6dcd835d0a519105a1eadd6e4");
+ CHECK_HASH(1005, 'U', "f4d62ddec0f3dd90ea1380fa16a5ff8dc4c54b21740650f24afc4120903552b0");
+}
+
+TEST_CASE ("SHA512: NIST test cases (small)", "[hash][sha512]")
+{
+ const auto algorithm = Hash::Algorithm::Sha512;
+
+ CHECK_HASH_STRING("",
+ "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f"
+ "63b931bd47417a81a538327af927da3e");
+
+ CHECK_HASH(111,
+ 0,
+ "77ddd3a542e530fd047b8977c657ba6ce72f1492e360b2b2212cd264e75ec03882e4ff0525517ab4207d14c70c2259ba88d4d33"
+ "5ee0e7e20543d22102ab1788c");
+ CHECK_HASH(112,
+ 0,
+ "2be2e788c8a8adeaa9c89a7f78904cacea6e39297d75e0573a73c756234534d6627ab4156b48a6657b29ab8beb73334040ad39e"
+ "ad81446bb09c70704ec707952");
+ CHECK_HASH(113,
+ 0,
+ "0e67910bcf0f9ccde5464c63b9c850a12a759227d16b040d98986d54253f9f34322318e56b8feb86c5fb2270ed87f31252f7f68"
+ "493ee759743909bd75e4bb544");
+ CHECK_HASH(122,
+ 0,
+ "4f3f095d015be4a7a7cc0b8c04da4aa09e74351e3a97651f744c23716ebd9b3e822e5077a01baa5cc0ed45b9249e88ab343d433"
+ "3539df21ed229da6f4a514e0f");
+ CHECK_HASH(1000,
+ 0,
+ "ca3dff61bb23477aa6087b27508264a6f9126ee3a004f53cb8db942ed345f2f2d229b4b59c859220a1cf1913f34248e3803bab6"
+ "50e849a3d9a709edc09ae4a76");
+ CHECK_HASH(1000,
+ 'A',
+ "329c52ac62d1fe731151f2b895a00475445ef74f50b979c6f7bb7cae349328c1d4cb4f7261a0ab43f936a24b000651d4a824fcd"
+ "d577f211aef8f806b16afe8af");
+ CHECK_HASH(1005,
+ 'U',
+ "59f5e54fe299c6a8764c6b199e44924a37f59e2b56c3ebad939b7289210dc8e4c21b9720165b0f4d4374c90f1bf4fb4a5ace17a"
+ "1161798015052893a48c3d161");
+}
+
+TEST_CASE ("SHA256: NIST test cases (large)", "[.][hash-expensive][sha256-expensive]")
+{
+ auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha256);
+ CHECK_HASH_LARGE(1'000'000, 0, "d29751f2649b32ff572b5e0a9f541ea660a50f94ff0beedfb0b692b924cc8025");
+ CHECK_HASH_LARGE(0x2000'0000, 'Z', "15a1868c12cc53951e182344277447cd0979536badcc512ad24c67e9b2d4f3dd");
+ CHECK_HASH_LARGE(0x4100'0000, 0, "461c19a93bd4344f9215f5ec64357090342bc66b15a148317d276e31cbc20b53");
+ CHECK_HASH_LARGE(0x6000'003E, 'B', "c23ce8a7895f4b21ec0daf37920ac0a262a220045a03eb2dfed48ef9b05aabea");
+}
+
+TEST_CASE ("SHA512: NIST test cases (large)", "[.][hash-expensive][sha512-expensive]")
+{
+ auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha512);
+ CHECK_HASH_LARGE(1'000'000,
+ 0,
+ "ce044bc9fd43269d5bbc946cbebc3bb711341115cc4abdf2edbc3ff2c57ad4b15deb699bda257fea5aef9c6e55fcf4cf9"
+ "dc25a8c3ce25f2efe90908379bff7ed");
+ CHECK_HASH_LARGE(0x2000'0000,
+ 'Z',
+ "da172279f3ebbda95f6b6e1e5f0ebec682c25d3d93561a1624c2fa9009d64c7e9923f3b46bcaf11d39a531f43297992ba"
+ "4155c7e827bd0f1e194ae7ed6de4cac");
+ CHECK_HASH_LARGE(0x4100'0000,
+ 0,
+ "14b1be901cb43549b4d831e61e5f9df1c791c85b50e85f9d6bc64135804ad43ce8402750edbe4e5c0fc170b99cf78b9f4"
+ "ecb9c7e02a157911d1bd1832d76784f");
+ CHECK_HASH_LARGE(0x6000'003E,
+ 'B',
+ "fd05e13eb771f05190bd97d62647157ea8f1f6949a52bb6daaedbad5f578ec59b1b8d6c4a7ecb2feca6892b4dc1387716"
+ "70a0f3bd577eea326aed40ab7dd58b1");
+}
+
+#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
+using Catch::Benchmark::Chronometer;
+void benchmark_hasher(Chronometer& meter, Hash::Hasher& hasher, std::uint64_t size, unsigned char byte) noexcept
+{
+ unsigned char buffer[1024];
+ std::fill(std::begin(buffer), std::end(buffer), byte);
+
+ meter.measure([&] {
+ hasher.clear();
+ std::uint64_t remaining = size;
+ while (remaining)
+ {
+ if (remaining < 512)
+ {
+ hasher.add_bytes(std::begin(buffer), std::begin(buffer) + remaining);
+ remaining = 0;
+ }
+ else
+ {
+ hasher.add_bytes(std::begin(buffer), std::end(buffer));
+ remaining -= 512;
+ }
+ }
+ hasher.get_hash();
+ });
+}
+
+TEST_CASE ("SHA1: benchmark", "[.][hash][sha256][!benchmark]")
+{
+ using Catch::Benchmark::Chronometer;
+
+ auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha1);
+
+ BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 1'000'000, 0);
+ };
+ BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z');
+ };
+ BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x4100'0000, 0);
+ };
+ BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x6000'003E, 'B');
+ };
+}
+
+TEST_CASE ("SHA256: benchmark", "[.][hash][sha256][!benchmark]")
+{
+ using Catch::Benchmark::Chronometer;
+
+ auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha256);
+
+ BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 1'000'000, 0);
+ };
+ BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z');
+ };
+ BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x4100'0000, 0);
+ };
+ BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x6000'003E, 'B');
+ };
+}
+
+TEST_CASE ("SHA512: large -- benchmark", "[.][hash][sha512][!benchmark]")
+{
+ auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha512);
+
+ BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 1'000'000, 0);
+ };
+ BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z');
+ };
+ BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x4100'0000, 0);
+ };
+ BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter)
+ {
+ benchmark_hasher(meter, *hasher, 0x6000'003E, 'B');
+ };
+}
+#endif
diff --git a/toolsrc/src/vcpkg-test/paragraph.cpp b/toolsrc/src/vcpkg-test/paragraph.cpp
new file mode 100644
index 000000000..85c37851d
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/paragraph.cpp
@@ -0,0 +1,445 @@
+#include <catch2/catch.hpp>
+#include <vcpkg-test/util.h>
+
+#include <vcpkg/base/strings.h>
+
+#include <vcpkg/paragraphs.h>
+
+namespace Strings = vcpkg::Strings;
+
+TEST_CASE ("SourceParagraph construct minimum", "[paragraph]")
+{
+ auto m_pgh =
+ vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{
+ {"Source", "zlib"},
+ {"Version", "1.2.8"},
+ }});
+
+ REQUIRE(m_pgh.has_value());
+ auto& pgh = **m_pgh.get();
+
+ REQUIRE(pgh.core_paragraph->name == "zlib");
+ REQUIRE(pgh.core_paragraph->version == "1.2.8");
+ REQUIRE(pgh.core_paragraph->maintainer == "");
+ REQUIRE(pgh.core_paragraph->description == "");
+ REQUIRE(pgh.core_paragraph->depends.size() == 0);
+}
+
+TEST_CASE ("SourceParagraph construct maximum", "[paragraph]")
+{
+ auto m_pgh =
+ vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{
+ {"Source", "s"},
+ {"Version", "v"},
+ {"Maintainer", "m"},
+ {"Description", "d"},
+ {"Build-Depends", "bd"},
+ {"Default-Features", "df"},
+ {"Supports", "x64"},
+ }});
+ REQUIRE(m_pgh.has_value());
+ auto& pgh = **m_pgh.get();
+
+ REQUIRE(pgh.core_paragraph->name == "s");
+ REQUIRE(pgh.core_paragraph->version == "v");
+ REQUIRE(pgh.core_paragraph->maintainer == "m");
+ REQUIRE(pgh.core_paragraph->description == "d");
+ REQUIRE(pgh.core_paragraph->depends.size() == 1);
+ REQUIRE(pgh.core_paragraph->depends[0].name() == "bd");
+ REQUIRE(pgh.core_paragraph->default_features.size() == 1);
+ REQUIRE(pgh.core_paragraph->default_features[0] == "df");
+ REQUIRE(pgh.core_paragraph->supports.size() == 1);
+ REQUIRE(pgh.core_paragraph->supports[0] == "x64");
+}
+
+TEST_CASE ("SourceParagraph two depends", "[paragraph]")
+{
+ auto m_pgh =
+ vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{
+ {"Source", "zlib"},
+ {"Version", "1.2.8"},
+ {"Build-Depends", "z, openssl"},
+ }});
+ REQUIRE(m_pgh.has_value());
+ auto& pgh = **m_pgh.get();
+
+ REQUIRE(pgh.core_paragraph->depends.size() == 2);
+ REQUIRE(pgh.core_paragraph->depends[0].name() == "z");
+ REQUIRE(pgh.core_paragraph->depends[1].name() == "openssl");
+}
+
+TEST_CASE ("SourceParagraph three depends", "[paragraph]")
+{
+ auto m_pgh =
+ vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{
+ {"Source", "zlib"},
+ {"Version", "1.2.8"},
+ {"Build-Depends", "z, openssl, xyz"},
+ }});
+ REQUIRE(m_pgh.has_value());
+ auto& pgh = **m_pgh.get();
+
+ REQUIRE(pgh.core_paragraph->depends.size() == 3);
+ REQUIRE(pgh.core_paragraph->depends[0].name() == "z");
+ REQUIRE(pgh.core_paragraph->depends[1].name() == "openssl");
+ REQUIRE(pgh.core_paragraph->depends[2].name() == "xyz");
+}
+
+TEST_CASE ("SourceParagraph three supports", "[paragraph]")
+{
+ auto m_pgh =
+ vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{
+ {"Source", "zlib"},
+ {"Version", "1.2.8"},
+ {"Supports", "x64, windows, uwp"},
+ }});
+ REQUIRE(m_pgh.has_value());
+ auto& pgh = **m_pgh.get();
+
+ REQUIRE(pgh.core_paragraph->supports.size() == 3);
+ REQUIRE(pgh.core_paragraph->supports[0] == "x64");
+ REQUIRE(pgh.core_paragraph->supports[1] == "windows");
+ REQUIRE(pgh.core_paragraph->supports[2] == "uwp");
+}
+
+TEST_CASE ("SourceParagraph construct qualified depends", "[paragraph]")
+{
+ auto m_pgh =
+ vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{
+ {"Source", "zlib"},
+ {"Version", "1.2.8"},
+ {"Build-Depends", "libA (windows), libB (uwp)"},
+ }});
+ REQUIRE(m_pgh.has_value());
+ auto& pgh = **m_pgh.get();
+
+ REQUIRE(pgh.core_paragraph->name == "zlib");
+ REQUIRE(pgh.core_paragraph->version == "1.2.8");
+ REQUIRE(pgh.core_paragraph->maintainer == "");
+ REQUIRE(pgh.core_paragraph->description == "");
+ REQUIRE(pgh.core_paragraph->depends.size() == 2);
+ REQUIRE(pgh.core_paragraph->depends[0].name() == "libA");
+ REQUIRE(pgh.core_paragraph->depends[0].qualifier == "windows");
+ REQUIRE(pgh.core_paragraph->depends[1].name() == "libB");
+ REQUIRE(pgh.core_paragraph->depends[1].qualifier == "uwp");
+}
+
+TEST_CASE ("SourceParagraph default features", "[paragraph]")
+{
+ auto m_pgh =
+ vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{
+ {"Source", "a"},
+ {"Version", "1.0"},
+ {"Default-Features", "a1"},
+ }});
+ REQUIRE(m_pgh.has_value());
+ auto& pgh = **m_pgh.get();
+
+ REQUIRE(pgh.core_paragraph->default_features.size() == 1);
+ REQUIRE(pgh.core_paragraph->default_features[0] == "a1");
+}
+
+TEST_CASE ("BinaryParagraph construct minimum", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "zlib"},
+ {"Version", "1.2.8"},
+ {"Architecture", "x86-windows"},
+ {"Multi-Arch", "same"},
+ });
+
+ REQUIRE(pgh.spec.name() == "zlib");
+ REQUIRE(pgh.version == "1.2.8");
+ REQUIRE(pgh.maintainer == "");
+ REQUIRE(pgh.description == "");
+ REQUIRE(pgh.spec.triplet().canonical_name() == "x86-windows");
+ REQUIRE(pgh.depends.size() == 0);
+}
+
+TEST_CASE ("BinaryParagraph construct maximum", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "s"},
+ {"Version", "v"},
+ {"Architecture", "x86-windows"},
+ {"Multi-Arch", "same"},
+ {"Maintainer", "m"},
+ {"Description", "d"},
+ {"Depends", "bd"},
+ });
+
+ REQUIRE(pgh.spec.name() == "s");
+ REQUIRE(pgh.version == "v");
+ REQUIRE(pgh.maintainer == "m");
+ REQUIRE(pgh.description == "d");
+ REQUIRE(pgh.depends.size() == 1);
+ REQUIRE(pgh.depends[0] == "bd");
+}
+
+TEST_CASE ("BinaryParagraph three depends", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "zlib"},
+ {"Version", "1.2.8"},
+ {"Architecture", "x86-windows"},
+ {"Multi-Arch", "same"},
+ {"Depends", "a, b, c"},
+ });
+
+ REQUIRE(pgh.depends.size() == 3);
+ REQUIRE(pgh.depends[0] == "a");
+ REQUIRE(pgh.depends[1] == "b");
+ REQUIRE(pgh.depends[2] == "c");
+}
+
+TEST_CASE ("BinaryParagraph abi", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "zlib"},
+ {"Version", "1.2.8"},
+ {"Architecture", "x86-windows"},
+ {"Multi-Arch", "same"},
+ {"Abi", "abcd123"},
+ });
+
+ REQUIRE(pgh.depends.size() == 0);
+ REQUIRE(pgh.abi == "abcd123");
+}
+
+TEST_CASE ("BinaryParagraph default features", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "a"},
+ {"Version", "1.0"},
+ {"Architecture", "x86-windows"},
+ {"Multi-Arch", "same"},
+ {"Default-Features", "a1"},
+ });
+
+ REQUIRE(pgh.depends.size() == 0);
+ REQUIRE(pgh.default_features.size() == 1);
+ REQUIRE(pgh.default_features[0] == "a1");
+}
+
+TEST_CASE ("parse paragraphs empty", "[paragraph]")
+{
+ const char* str = "";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+ REQUIRE(pghs.empty());
+}
+
+TEST_CASE ("parse paragraphs one field", "[paragraph]")
+{
+ const char* str = "f1: v1";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0].size() == 1);
+ REQUIRE(pghs[0]["f1"] == "v1");
+}
+
+TEST_CASE ("parse paragraphs one pgh", "[paragraph]")
+{
+ const char* str = "f1: v1\n"
+ "f2: v2";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0].size() == 2);
+ REQUIRE(pghs[0]["f1"] == "v1");
+ REQUIRE(pghs[0]["f2"] == "v2");
+}
+
+TEST_CASE ("parse paragraphs two pgh", "[paragraph]")
+{
+ const char* str = "f1: v1\n"
+ "f2: v2\n"
+ "\n"
+ "f3: v3\n"
+ "f4: v4";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 2);
+ REQUIRE(pghs[0].size() == 2);
+ REQUIRE(pghs[0]["f1"] == "v1");
+ REQUIRE(pghs[0]["f2"] == "v2");
+ REQUIRE(pghs[1].size() == 2);
+ REQUIRE(pghs[1]["f3"] == "v3");
+ REQUIRE(pghs[1]["f4"] == "v4");
+}
+
+TEST_CASE ("parse paragraphs field names", "[paragraph]")
+{
+ const char* str = "1:\n"
+ "f:\n"
+ "F:\n"
+ "0:\n"
+ "F-2:\n";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0].size() == 5);
+}
+
+TEST_CASE ("parse paragraphs multiple blank lines", "[paragraph]")
+{
+ const char* str = "f1: v1\n"
+ "f2: v2\n"
+ "\n"
+ "\n"
+ "f3: v3\n"
+ "f4: v4";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 2);
+}
+
+TEST_CASE ("parse paragraphs empty fields", "[paragraph]")
+{
+ const char* str = "f1:\n"
+ "f2: ";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0].size() == 2);
+ REQUIRE(pghs[0]["f1"] == "");
+ REQUIRE(pghs[0]["f2"] == "");
+ REQUIRE(pghs[0].size() == 2);
+}
+
+TEST_CASE ("parse paragraphs multiline fields", "[paragraph]")
+{
+ const char* str = "f1: simple\n"
+ " f1\r\n"
+ "f2:\r\n"
+ " f2\r\n"
+ " continue\r\n";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0]["f1"] == "simple\n f1");
+ REQUIRE(pghs[0]["f2"] == "\n f2\n continue");
+}
+
+TEST_CASE ("parse paragraphs crlfs", "[paragraph]")
+{
+ const char* str = "f1: v1\r\n"
+ "f2: v2\r\n"
+ "\r\n"
+ "f3: v3\r\n"
+ "f4: v4";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 2);
+ REQUIRE(pghs[0].size() == 2);
+ REQUIRE(pghs[0]["f1"] == "v1");
+ REQUIRE(pghs[0]["f2"] == "v2");
+ REQUIRE(pghs[1].size() == 2);
+ REQUIRE(pghs[1]["f3"] == "v3");
+ REQUIRE(pghs[1]["f4"] == "v4");
+}
+
+TEST_CASE ("parse paragraphs comment", "[paragraph]")
+{
+ const char* str = "f1: v1\r\n"
+ "#comment\r\n"
+ "f2: v2\r\n"
+ "#comment\r\n"
+ "\r\n"
+ "#comment\r\n"
+ "f3: v3\r\n"
+ "#comment\r\n"
+ "f4: v4";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 2);
+ REQUIRE(pghs[0].size() == 2);
+ REQUIRE(pghs[0]["f1"] == "v1");
+ REQUIRE(pghs[0]["f2"] == "v2");
+ REQUIRE(pghs[1].size());
+ REQUIRE(pghs[1]["f3"] == "v3");
+ REQUIRE(pghs[1]["f4"] == "v4");
+}
+
+TEST_CASE ("parse comment before single line feed", "[paragraph]")
+{
+ const char* str = "f1: v1\r\n"
+ "#comment\n";
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO);
+ REQUIRE(pghs[0].size() == 1);
+ REQUIRE(pghs[0]["f1"] == "v1");
+}
+
+TEST_CASE ("BinaryParagraph serialize min", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "zlib"},
+ {"Version", "1.2.8"},
+ {"Architecture", "x86-windows"},
+ {"Multi-Arch", "same"},
+ });
+ std::string ss = Strings::serialize(pgh);
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0].size() == 4);
+ REQUIRE(pghs[0]["Package"] == "zlib");
+ REQUIRE(pghs[0]["Version"] == "1.2.8");
+ REQUIRE(pghs[0]["Architecture"] == "x86-windows");
+ REQUIRE(pghs[0]["Multi-Arch"] == "same");
+}
+
+TEST_CASE ("BinaryParagraph serialize max", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "zlib"},
+ {"Version", "1.2.8"},
+ {"Architecture", "x86-windows"},
+ {"Description", "first line\n second line"},
+ {"Maintainer", "abc <abc@abc.abc>"},
+ {"Depends", "dep"},
+ {"Multi-Arch", "same"},
+ });
+ std::string ss = Strings::serialize(pgh);
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0].size() == 7);
+ REQUIRE(pghs[0]["Package"] == "zlib");
+ REQUIRE(pghs[0]["Version"] == "1.2.8");
+ REQUIRE(pghs[0]["Architecture"] == "x86-windows");
+ REQUIRE(pghs[0]["Multi-Arch"] == "same");
+ REQUIRE(pghs[0]["Description"] == "first line\n second line");
+ REQUIRE(pghs[0]["Depends"] == "dep");
+}
+
+TEST_CASE ("BinaryParagraph serialize multiple deps", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "zlib"},
+ {"Version", "1.2.8"},
+ {"Architecture", "x86-windows"},
+ {"Multi-Arch", "same"},
+ {"Depends", "a, b, c"},
+ });
+ std::string ss = Strings::serialize(pgh);
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0]["Depends"] == "a, b, c");
+}
+
+TEST_CASE ("BinaryParagraph serialize abi", "[paragraph]")
+{
+ vcpkg::BinaryParagraph pgh({
+ {"Package", "zlib"},
+ {"Version", "1.2.8"},
+ {"Architecture", "x86-windows"},
+ {"Multi-Arch", "same"},
+ {"Depends", "a, b, c"},
+ {"Abi", "123abc"},
+ });
+ std::string ss = Strings::serialize(pgh);
+ auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO);
+
+ REQUIRE(pghs.size() == 1);
+ REQUIRE(pghs[0]["Abi"] == "123abc");
+}
diff --git a/toolsrc/src/vcpkg-test/plan.cpp b/toolsrc/src/vcpkg-test/plan.cpp
new file mode 100644
index 000000000..e354b7551
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/plan.cpp
@@ -0,0 +1,1241 @@
+#include <catch2/catch.hpp>
+#include <vcpkg-test/util.h>
+
+#include <vcpkg/dependencies.h>
+#include <vcpkg/sourceparagraph.h>
+#include <vcpkg/triplet.h>
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+using namespace vcpkg;
+
+using Test::make_status_feature_pgh;
+using Test::make_status_pgh;
+using Test::unsafe_pspec;
+
+static std::unique_ptr<SourceControlFile> make_control_file(
+ const char* name,
+ const char* depends,
+ const std::vector<std::pair<const char*, const char*>>& features = {},
+ const std::vector<const char*>& default_features = {})
+{
+ using Pgh = std::unordered_map<std::string, std::string>;
+ std::vector<Pgh> scf_pghs;
+ scf_pghs.push_back(Pgh{{"Source", name},
+ {"Version", "0"},
+ {"Build-Depends", depends},
+ {"Default-Features", Strings::join(", ", default_features)}});
+ for (auto&& feature : features)
+ {
+ scf_pghs.push_back(Pgh{
+ {"Feature", feature.first},
+ {"Description", "feature"},
+ {"Build-Depends", feature.second},
+ });
+ }
+ auto m_pgh = vcpkg::SourceControlFile::parse_control_file(std::move(scf_pghs));
+ REQUIRE(m_pgh.has_value());
+ return std::move(*m_pgh.get());
+}
+
+/// <summary>
+/// Assert that the given action an install of given features from given package.
+/// </summary>
+static void features_check(Dependencies::AnyAction& install_action,
+ std::string pkg_name,
+ std::vector<std::string> vec,
+ const Triplet& triplet = Triplet::X86_WINDOWS)
+{
+ REQUIRE(install_action.install_action.has_value());
+ const auto& plan = install_action.install_action.value_or_exit(VCPKG_LINE_INFO);
+ const auto& feature_list = plan.feature_list;
+
+ REQUIRE(plan.spec.triplet().to_string() == triplet.to_string());
+
+ auto& scfl = *plan.source_control_file_location.get();
+ REQUIRE(pkg_name == scfl.source_control_file->core_paragraph->name);
+ REQUIRE(feature_list.size() == vec.size());
+
+ for (auto&& feature_name : vec)
+ {
+ // TODO: see if this can be simplified
+ if (feature_name == "core" || feature_name == "")
+ {
+ REQUIRE((Util::find(feature_list, "core") != feature_list.end() ||
+ Util::find(feature_list, "") != feature_list.end()));
+ continue;
+ }
+ REQUIRE(Util::find(feature_list, feature_name) != feature_list.end());
+ }
+}
+
+/// <summary>
+/// Assert that the given action is a remove of given package.
+/// </summary>
+static void remove_plan_check(Dependencies::AnyAction& remove_action,
+ std::string pkg_name,
+ const Triplet& triplet = Triplet::X86_WINDOWS)
+{
+ const auto& plan = remove_action.remove_action.value_or_exit(VCPKG_LINE_INFO);
+ REQUIRE(plan.spec.triplet().to_string() == triplet.to_string());
+ REQUIRE(pkg_name == plan.spec.name());
+}
+
+/// <summary>
+/// Map of source control files by their package name.
+/// </summary>
+struct PackageSpecMap
+{
+ std::unordered_map<std::string, SourceControlFileLocation> map;
+ Triplet triplet;
+ PackageSpecMap(const Triplet& t = Triplet::X86_WINDOWS) noexcept { triplet = t; }
+
+ PackageSpec emplace(const char* name,
+ const char* depends = "",
+ const std::vector<std::pair<const char*, const char*>>& features = {},
+ const std::vector<const char*>& default_features = {})
+ {
+ auto scfl = SourceControlFileLocation{make_control_file(name, depends, features, default_features), ""};
+ return emplace(std::move(scfl));
+ }
+
+ PackageSpec emplace(vcpkg::SourceControlFileLocation&& scfl)
+ {
+ auto spec = PackageSpec::from_name_and_triplet(scfl.source_control_file->core_paragraph->name, triplet);
+ REQUIRE(spec.has_value());
+ map.emplace(scfl.source_control_file->core_paragraph->name, std::move(scfl));
+ return PackageSpec{*spec.get()};
+ }
+};
+
+TEST_CASE ("basic install scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "b");
+ auto spec_b = spec_map.emplace("b", "c");
+ auto spec_c = spec_map.emplace("c");
+
+ Dependencies::MapPortFileProvider map_port(spec_map.map);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 3);
+ REQUIRE(install_plan.at(0).spec().name() == "c");
+ REQUIRE(install_plan.at(1).spec().name() == "b");
+ REQUIRE(install_plan.at(2).spec().name() == "a");
+}
+
+TEST_CASE ("multiple install scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "d");
+ auto spec_b = spec_map.emplace("b", "d, e");
+ auto spec_c = spec_map.emplace("c", "e, h");
+ auto spec_d = spec_map.emplace("d", "f, g, h");
+ auto spec_e = spec_map.emplace("e", "g");
+ auto spec_f = spec_map.emplace("f");
+ auto spec_g = spec_map.emplace("g");
+ auto spec_h = spec_map.emplace("h");
+
+ Dependencies::MapPortFileProvider map_port(spec_map.map);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ map_port,
+ {FeatureSpec{spec_a, ""}, FeatureSpec{spec_b, ""}, FeatureSpec{spec_c, ""}},
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ auto iterator_pos = [&](const PackageSpec& spec) {
+ auto it =
+ std::find_if(install_plan.begin(), install_plan.end(), [&](auto& action) { return action.spec() == spec; });
+ REQUIRE(it != install_plan.end());
+ return it - install_plan.begin();
+ };
+
+ const auto a_pos = iterator_pos(spec_a);
+ const auto b_pos = iterator_pos(spec_b);
+ const auto c_pos = iterator_pos(spec_c);
+ const auto d_pos = iterator_pos(spec_d);
+ const auto e_pos = iterator_pos(spec_e);
+ const auto f_pos = iterator_pos(spec_f);
+ const auto g_pos = iterator_pos(spec_g);
+ const auto h_pos = iterator_pos(spec_h);
+
+ REQUIRE(a_pos > d_pos);
+ REQUIRE(b_pos > e_pos);
+ REQUIRE(b_pos > d_pos);
+ REQUIRE(c_pos > e_pos);
+ REQUIRE(c_pos > h_pos);
+ REQUIRE(d_pos > f_pos);
+ REQUIRE(d_pos > g_pos);
+ REQUIRE(d_pos > h_pos);
+ REQUIRE(e_pos > g_pos);
+}
+
+TEST_CASE ("existing package scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(vcpkg::Test::make_status_pgh("a"));
+
+ PackageSpecMap spec_map;
+ auto spec_a = FullPackageSpec{spec_map.emplace("a")};
+
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 1);
+ const auto p = install_plan.at(0).install_action.get();
+ REQUIRE(p);
+ REQUIRE(p->spec.name() == "a");
+ REQUIRE(p->plan_type == Dependencies::InstallPlanType::ALREADY_INSTALLED);
+ REQUIRE(p->request_type == Dependencies::RequestType::USER_REQUESTED);
+}
+
+TEST_CASE ("user requested package scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map;
+ const auto spec_a = FullPackageSpec{spec_map.emplace("a", "b")};
+ const auto spec_b = FullPackageSpec{spec_map.emplace("b")};
+
+ const auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 2);
+ const auto p = install_plan.at(0).install_action.get();
+ REQUIRE(p);
+ REQUIRE(p->spec.name() == "b");
+ REQUIRE(p->plan_type == Dependencies::InstallPlanType::BUILD_AND_INSTALL);
+ REQUIRE(p->request_type == Dependencies::RequestType::AUTO_SELECTED);
+
+ const auto p2 = install_plan.at(1).install_action.get();
+ REQUIRE(p2);
+ REQUIRE(p2->spec.name() == "a");
+ REQUIRE(p2->plan_type == Dependencies::InstallPlanType::BUILD_AND_INSTALL);
+ REQUIRE(p2->request_type == Dependencies::RequestType::USER_REQUESTED);
+}
+
+TEST_CASE ("long install scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("j", "k"));
+ status_paragraphs.push_back(make_status_pgh("k"));
+
+ PackageSpecMap spec_map;
+
+ auto spec_a = spec_map.emplace("a", "b, c, d, e, f, g, h, j, k");
+ auto spec_b = spec_map.emplace("b", "c, d, e, f, g, h, j, k");
+ auto spec_c = spec_map.emplace("c", "d, e, f, g, h, j, k");
+ auto spec_d = spec_map.emplace("d", "e, f, g, h, j, k");
+ auto spec_e = spec_map.emplace("e", "f, g, h, j, k");
+ auto spec_f = spec_map.emplace("f", "g, h, j, k");
+ auto spec_g = spec_map.emplace("g", "h, j, k");
+ auto spec_h = spec_map.emplace("h", "j, k");
+ auto spec_j = spec_map.emplace("j", "k");
+ auto spec_k = spec_map.emplace("k");
+
+ Dependencies::MapPortFileProvider map_port(spec_map.map);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 8);
+ REQUIRE(install_plan.at(0).spec().name() == "h");
+ REQUIRE(install_plan.at(1).spec().name() == "g");
+ REQUIRE(install_plan.at(2).spec().name() == "f");
+ REQUIRE(install_plan.at(3).spec().name() == "e");
+ REQUIRE(install_plan.at(4).spec().name() == "d");
+ REQUIRE(install_plan.at(5).spec().name() == "c");
+ REQUIRE(install_plan.at(6).spec().name() == "b");
+ REQUIRE(install_plan.at(7).spec().name() == "a");
+}
+
+TEST_CASE ("basic feature test 1", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a", "b, b[b1]"));
+ status_paragraphs.push_back(make_status_pgh("b"));
+ status_paragraphs.push_back(make_status_feature_pgh("b", "b1"));
+
+ PackageSpecMap spec_map;
+ auto spec_a = FullPackageSpec{spec_map.emplace("a", "b, b[b1]", {{"a1", "b[b2]"}}), {"a1"}};
+ auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}, {"b3", ""}})};
+
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 4);
+ remove_plan_check(install_plan.at(0), "a");
+ remove_plan_check(install_plan.at(1), "b");
+ features_check(install_plan.at(2), "b", {"b1", "core", "b1"});
+ features_check(install_plan.at(3), "a", {"a1", "core"});
+}
+
+TEST_CASE ("basic feature test 2", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map;
+
+ auto spec_a = FullPackageSpec{spec_map.emplace("a", "b[b1]", {{"a1", "b[b2]"}}), {"a1"}};
+ auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}, {"b3", ""}})};
+
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 2);
+ features_check(install_plan.at(0), "b", {"b1", "b2", "core"});
+ features_check(install_plan.at(1), "a", {"a1", "core"});
+}
+
+TEST_CASE ("basic feature test 3", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a"));
+
+ PackageSpecMap spec_map;
+
+ auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}};
+ auto spec_b = FullPackageSpec{spec_map.emplace("b")};
+ auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}};
+
+ auto install_plan = Dependencies::create_feature_install_plan(spec_map.map,
+ FullPackageSpec::to_feature_specs({spec_c, spec_a}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 4);
+ remove_plan_check(install_plan.at(0), "a");
+ features_check(install_plan.at(1), "b", {"core"});
+ features_check(install_plan.at(2), "a", {"a1", "core"});
+ features_check(install_plan.at(3), "c", {"core"});
+}
+
+TEST_CASE ("basic feature test 4", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a"));
+ status_paragraphs.push_back(make_status_feature_pgh("a", "a1", ""));
+
+ PackageSpecMap spec_map;
+
+ auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}})};
+ auto spec_b = FullPackageSpec{spec_map.emplace("b")};
+ auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}};
+
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map, FullPackageSpec::to_feature_specs({spec_c}), StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 1);
+ features_check(install_plan.at(0), "c", {"core"});
+}
+
+TEST_CASE ("basic feature test 5", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map;
+
+ auto spec_a =
+ FullPackageSpec{spec_map.emplace("a", "", {{"a1", "b[b1]"}, {"a2", "b[b2]"}, {"a3", "a[a2]"}}), {"a3"}};
+ auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}})};
+
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 2);
+ features_check(install_plan.at(0), "b", {"core", "b2"});
+ features_check(install_plan.at(1), "a", {"core", "a3", "a2"});
+}
+
+TEST_CASE ("basic feature test 6", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("b"));
+
+ PackageSpecMap spec_map;
+ auto spec_a = FullPackageSpec{spec_map.emplace("a", "b[core]"), {"core"}};
+ auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}};
+
+ auto install_plan = Dependencies::create_feature_install_plan(spec_map.map,
+ FullPackageSpec::to_feature_specs({spec_a, spec_b}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 3);
+ remove_plan_check(install_plan.at(0), "b");
+ features_check(install_plan.at(1), "b", {"core", "b1"});
+ features_check(install_plan.at(2), "a", {"core"});
+}
+
+TEST_CASE ("basic feature test 7", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("x", "b"));
+ status_paragraphs.push_back(make_status_pgh("b"));
+
+ PackageSpecMap spec_map;
+
+ auto spec_a = FullPackageSpec{spec_map.emplace("a")};
+ auto spec_x = FullPackageSpec{spec_map.emplace("x", "a"), {"core"}};
+ auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}};
+
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map, FullPackageSpec::to_feature_specs({spec_b}), StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 5);
+ remove_plan_check(install_plan.at(0), "x");
+ remove_plan_check(install_plan.at(1), "b");
+
+ // TODO: order here may change but A < X, and B anywhere
+ features_check(install_plan.at(2), "b", {"core", "b1"});
+ features_check(install_plan.at(3), "a", {"core"});
+ features_check(install_plan.at(4), "x", {"core"});
+}
+
+TEST_CASE ("basic feature test 8", "[plan][!mayfail]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a"));
+ status_paragraphs.push_back(make_status_pgh("a"));
+ status_paragraphs.back()->package.spec =
+ PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO);
+
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}};
+ auto spec_b_64 = FullPackageSpec{spec_map.emplace("b")};
+ auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}};
+
+ spec_map.triplet = Triplet::X86_WINDOWS;
+ auto spec_a_86 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}};
+ auto spec_b_86 = FullPackageSpec{spec_map.emplace("b")};
+ auto spec_c_86 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}};
+
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({spec_c_64, spec_a_86, spec_a_64, spec_c_86}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ remove_plan_check(install_plan.at(0), "a", Triplet::X64_WINDOWS);
+ remove_plan_check(install_plan.at(1), "a");
+ features_check(install_plan.at(2), "b", {"core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(3), "a", {"a1", "core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(4), "c", {"core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(5), "b", {"core"});
+ features_check(install_plan.at(6), "a", {"a1", "core"});
+ features_check(install_plan.at(7), "c", {"core"});
+}
+
+TEST_CASE ("install all features test", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}), {"core"}};
+
+ auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS);
+ REQUIRE(install_specs.has_value());
+ if (!install_specs.has_value()) return;
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 1);
+ features_check(install_plan.at(0), "a", {"0", "1", "core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("install default features test 1", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ // Add a port "a" with default features "1" and features "0" and "1".
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"});
+
+ // Install "a" (without explicit feature specification)
+ auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ // Expect the default feature "1" to be installed, but not "0"
+ REQUIRE(install_plan.size() == 1);
+ features_check(install_plan.at(0), "a", {"1", "core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("install default features test 2", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a"));
+ status_paragraphs.back()->package.spec =
+ PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO);
+
+ // Add a port "a" of which "core" is already installed, but we will
+ // install the default features "explicitly"
+ // "a" has two features, of which "a1" is default.
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"});
+
+ // Install "a" (without explicit feature specification)
+ auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ // Expect "a" to get removed for rebuild and then installed with default
+ // features.
+ REQUIRE(install_plan.size() == 2);
+ remove_plan_check(install_plan.at(0), "a", Triplet::X64_WINDOWS);
+ features_check(install_plan.at(1), "a", {"a1", "core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("install default features test 3", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ // "a" has two features, of which "a1" is default.
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"});
+
+ // Explicitly install "a" without default features
+ auto install_specs = FullPackageSpec::from_string("a[core]", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ // Expect the default feature not to get installed.
+ REQUIRE(install_plan.size() == 1);
+ features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("install default features of dependency test 1", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ // Add a port "a" which depends on the core of "b"
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ spec_map.emplace("a", "b[core]");
+ // "b" has two features, of which "b1" is default.
+ spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"});
+
+ // Install "a" (without explicit feature specification)
+ auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ // Expect "a" to get installed and defaults of "b" through the dependency,
+ // as no explicit features of "b" are installed by the user.
+ REQUIRE(install_plan.size() == 2);
+ features_check(install_plan.at(0), "b", {"b1", "core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(1), "a", {"core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("do not install default features of existing dependency", "[plan]")
+{
+ // Add a port "a" which depends on the core of "b"
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ spec_map.emplace("a", "b[core]");
+ // "b" has two features, of which "b1" is default.
+ spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"});
+
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ // "b[core]" is already installed
+ status_paragraphs.push_back(make_status_pgh("b"));
+ status_paragraphs.back()->package.spec =
+ PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO);
+
+ // Install "a" (without explicit feature specification)
+ auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ // Expect "a" to get installed, but not require rebuilding "b"
+ REQUIRE(install_plan.size() == 1);
+ features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("install default features of dependency test 2", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("b"));
+ status_paragraphs.back()->package.spec =
+ PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO);
+
+ // Add a port "a" which depends on the core of "b", which was already
+ // installed explicitly
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ spec_map.emplace("a", "b[core]");
+ // "b" has two features, of which "b1" is default.
+ spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"});
+
+ // Install "a" (without explicit feature specification)
+ auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ // Expect "a" to get installed, not the defaults of "b", as the required
+ // dependencies are already there, installed explicitly by the user.
+ REQUIRE(install_plan.size() == 1);
+ features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("install plan action dependencies", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ // Add a port "a" which depends on the core of "b", which was already
+ // installed explicitly
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_c = spec_map.emplace("c");
+ auto spec_b = spec_map.emplace("b", "c");
+ spec_map.emplace("a", "b");
+
+ // Install "a" (without explicit feature specification)
+ auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 3);
+ features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS);
+
+ features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS);
+ REQUIRE(install_plan.at(1).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_c});
+
+ features_check(install_plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS);
+ REQUIRE(install_plan.at(2).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_b});
+}
+
+TEST_CASE ("install plan action dependencies 2", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ // Add a port "a" which depends on the core of "b", which was already
+ // installed explicitly
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_c = spec_map.emplace("c");
+ auto spec_b = spec_map.emplace("b", "c");
+ spec_map.emplace("a", "c, b");
+
+ // Install "a" (without explicit feature specification)
+ auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 3);
+ features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS);
+
+ features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS);
+ REQUIRE(install_plan.at(1).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_c});
+
+ features_check(install_plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS);
+ REQUIRE(install_plan.at(2).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_b, spec_c});
+}
+
+TEST_CASE ("install plan action dependencies 3", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ // Add a port "a" which depends on the core of "b", which was already
+ // installed explicitly
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ spec_map.emplace("a", "", {{"0", ""}, {"1", "a[0]"}}, {"1"});
+
+ // Install "a" (without explicit feature specification)
+ auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS);
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 1);
+ features_check(install_plan.at(0), "a", {"1", "0", "core"}, Triplet::X64_WINDOWS);
+ REQUIRE(install_plan.at(0).install_action.get()->computed_dependencies == std::vector<PackageSpec>{});
+}
+
+TEST_CASE ("install with default features", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a", ""));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto b_spec = spec_map.emplace("b", "", {{"0", ""}}, {"0"});
+ auto a_spec = spec_map.emplace("a", "b[core]", {{"0", ""}});
+
+ // Install "a" and indicate that "b" should not install default features
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map, {FeatureSpec{a_spec, "0"}, FeatureSpec{b_spec, "core"}}, status_db);
+
+ REQUIRE(install_plan.size() == 3);
+ remove_plan_check(install_plan.at(0), "a");
+ features_check(install_plan.at(1), "b", {"core"});
+ features_check(install_plan.at(2), "a", {"0", "core"});
+}
+
+TEST_CASE ("upgrade with default features 1", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a", "", "1"));
+ pghs.push_back(make_status_feature_pgh("a", "0"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ // Add a port "a" of which "core" and "0" are already installed.
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"});
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+ auto plan = graph.serialize();
+
+ // The upgrade should not install the default feature
+ REQUIRE(plan.size() == 2);
+
+ REQUIRE(plan.at(0).spec().name() == "a");
+ remove_plan_check(plan.at(0), "a");
+ features_check(plan.at(1), "a", {"core", "0"});
+}
+
+TEST_CASE ("upgrade with default features 2", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ // B is currently installed _without_ default feature b0
+ pghs.push_back(make_status_pgh("b", "", "b0", "x64-windows"));
+ pghs.push_back(make_status_pgh("a", "b[core]", "", "x64-windows"));
+
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_a = spec_map.emplace("a", "b[core]");
+ auto spec_b = spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0", "b1"});
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+ graph.upgrade(spec_b);
+ auto plan = graph.serialize();
+
+ // The upgrade should install the new default feature b1 but not b0
+ REQUIRE(plan.size() == 4);
+ remove_plan_check(plan.at(0), "a", Triplet::X64_WINDOWS);
+ remove_plan_check(plan.at(1), "b", Triplet::X64_WINDOWS);
+ features_check(plan.at(2), "b", {"core", "b1"}, Triplet::X64_WINDOWS);
+ features_check(plan.at(3), "a", {"core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("upgrade with default features 3", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ // note: unrelated package due to x86 triplet
+ pghs.push_back(make_status_pgh("b", "", "", "x86-windows"));
+ pghs.push_back(make_status_pgh("a", "", "", "x64-windows"));
+
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_a = spec_map.emplace("a", "b[core]");
+ spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0"});
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+ auto plan = graph.serialize();
+
+ // The upgrade should install the default feature
+ REQUIRE(plan.size() == 3);
+ remove_plan_check(plan.at(0), "a", Triplet::X64_WINDOWS);
+ features_check(plan.at(1), "b", {"b0", "core"}, Triplet::X64_WINDOWS);
+ features_check(plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("upgrade with new default feature", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a", "", "0", "x86-windows"));
+
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}, {"2", ""}}, {"0", "1"});
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+ auto plan = graph.serialize();
+
+ // The upgrade should install the new default feature but not the old default feature 0
+ REQUIRE(plan.size() == 2);
+ remove_plan_check(plan.at(0), "a", Triplet::X86_WINDOWS);
+ features_check(plan.at(1), "a", {"core", "1"}, Triplet::X86_WINDOWS);
+}
+
+TEST_CASE ("transitive features test", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"0", "b[0]"}}), {"core"}};
+ auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "c", {{"0", "c[0]"}}), {"core"}};
+ auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}};
+
+ auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS);
+ REQUIRE(install_specs.has_value());
+ if (!install_specs.has_value()) return;
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 3);
+ features_check(install_plan.at(0), "c", {"0", "core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(1), "b", {"0", "core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("no transitive features test", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"0", ""}}), {"core"}};
+ auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "c", {{"0", ""}}), {"core"}};
+ auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}};
+
+ auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS);
+ REQUIRE(install_specs.has_value());
+ if (!install_specs.has_value()) return;
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 3);
+ features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("only transitive features test", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+
+ PackageSpecMap spec_map(Triplet::X64_WINDOWS);
+ auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "", {{"0", "b[0]"}}), {"core"}};
+ auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "", {{"0", "c[0]"}}), {"core"}};
+ auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}};
+
+ auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS);
+ REQUIRE(install_specs.has_value());
+ if (!install_specs.has_value()) return;
+ auto install_plan = Dependencies::create_feature_install_plan(
+ spec_map.map,
+ FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}),
+ StatusParagraphs(std::move(status_paragraphs)));
+
+ REQUIRE(install_plan.size() == 3);
+ features_check(install_plan.at(0), "c", {"0", "core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(1), "b", {"0", "core"}, Triplet::X64_WINDOWS);
+ features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS);
+}
+
+TEST_CASE ("basic remove scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db);
+
+ REQUIRE(remove_plan.size() == 1);
+ REQUIRE(remove_plan.at(0).spec.name() == "a");
+}
+
+TEST_CASE ("recurse remove scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_pgh("b", "a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db);
+
+ REQUIRE(remove_plan.size() == 2);
+ REQUIRE(remove_plan.at(0).spec.name() == "b");
+ REQUIRE(remove_plan.at(1).spec.name() == "a");
+}
+
+TEST_CASE ("features depend remove scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_pgh("b"));
+ pghs.push_back(make_status_feature_pgh("b", "0", "a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db);
+
+ REQUIRE(remove_plan.size() == 2);
+ REQUIRE(remove_plan.at(0).spec.name() == "b");
+ REQUIRE(remove_plan.at(1).spec.name() == "a");
+}
+
+TEST_CASE ("features depend remove scheme once removed", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("expat"));
+ pghs.push_back(make_status_pgh("vtk", "expat"));
+ pghs.push_back(make_status_pgh("opencv"));
+ pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("expat")}, status_db);
+
+ REQUIRE(remove_plan.size() == 3);
+ REQUIRE(remove_plan.at(0).spec.name() == "opencv");
+ REQUIRE(remove_plan.at(1).spec.name() == "vtk");
+ REQUIRE(remove_plan.at(2).spec.name() == "expat");
+}
+
+TEST_CASE ("features depend remove scheme once removed x64", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("expat", "", "", "x64"));
+ pghs.push_back(make_status_pgh("vtk", "expat", "", "x64"));
+ pghs.push_back(make_status_pgh("opencv", "", "", "x64"));
+ pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk", "x64"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ auto remove_plan =
+ Dependencies::create_remove_plan({unsafe_pspec("expat", Triplet::from_canonical_name("x64"))}, status_db);
+
+ REQUIRE(remove_plan.size() == 3);
+ REQUIRE(remove_plan.at(0).spec.name() == "opencv");
+ REQUIRE(remove_plan.at(1).spec.name() == "vtk");
+ REQUIRE(remove_plan.at(2).spec.name() == "expat");
+}
+
+TEST_CASE ("features depend core remove scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("curl", "", "", "x64"));
+ pghs.push_back(make_status_pgh("cpr", "curl[core]", "", "x64"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ auto remove_plan =
+ Dependencies::create_remove_plan({unsafe_pspec("curl", Triplet::from_canonical_name("x64"))}, status_db);
+
+ REQUIRE(remove_plan.size() == 2);
+ REQUIRE(remove_plan.at(0).spec.name() == "cpr");
+ REQUIRE(remove_plan.at(1).spec.name() == "curl");
+}
+
+TEST_CASE ("features depend core remove scheme 2", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("curl", "", "", "x64"));
+ pghs.push_back(make_status_feature_pgh("curl", "a", "", "x64"));
+ pghs.push_back(make_status_feature_pgh("curl", "b", "curl[a]", "x64"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ auto remove_plan =
+ Dependencies::create_remove_plan({unsafe_pspec("curl", Triplet::from_canonical_name("x64"))}, status_db);
+
+ REQUIRE(remove_plan.size() == 1);
+ REQUIRE(remove_plan.at(0).spec.name() == "curl");
+}
+
+TEST_CASE ("basic upgrade scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a");
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+
+ auto plan = graph.serialize();
+
+ REQUIRE(plan.size() == 2);
+ REQUIRE(plan.at(0).spec().name() == "a");
+ REQUIRE(plan.at(0).remove_action.has_value());
+ REQUIRE(plan.at(1).spec().name() == "a");
+ REQUIRE(plan.at(1).install_action.has_value());
+}
+
+TEST_CASE ("basic upgrade scheme with recurse", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_pgh("b", "a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a");
+ spec_map.emplace("b", "a");
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+
+ auto plan = graph.serialize();
+
+ REQUIRE(plan.size() == 4);
+ REQUIRE(plan.at(0).spec().name() == "b");
+ REQUIRE(plan.at(0).remove_action.has_value());
+
+ REQUIRE(plan.at(1).spec().name() == "a");
+ REQUIRE(plan.at(1).remove_action.has_value());
+
+ REQUIRE(plan.at(2).spec().name() == "a");
+ REQUIRE(plan.at(2).install_action.has_value());
+
+ REQUIRE(plan.at(3).spec().name() == "b");
+ REQUIRE(plan.at(3).install_action.has_value());
+}
+
+TEST_CASE ("basic upgrade scheme with bystander", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_pgh("b"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a");
+ spec_map.emplace("b", "a");
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+
+ auto plan = graph.serialize();
+
+ REQUIRE(plan.size() == 2);
+ REQUIRE(plan.at(0).spec().name() == "a");
+ REQUIRE(plan.at(0).remove_action.has_value());
+ REQUIRE(plan.at(1).spec().name() == "a");
+ REQUIRE(plan.at(1).install_action.has_value());
+}
+
+TEST_CASE ("basic upgrade scheme with new dep", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "b");
+ spec_map.emplace("b");
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+
+ auto plan = graph.serialize();
+
+ REQUIRE(plan.size() == 3);
+ REQUIRE(plan.at(0).spec().name() == "a");
+ REQUIRE(plan.at(0).remove_action.has_value());
+ REQUIRE(plan.at(1).spec().name() == "b");
+ REQUIRE(plan.at(1).install_action.has_value());
+ REQUIRE(plan.at(2).spec().name() == "a");
+ REQUIRE(plan.at(2).install_action.has_value());
+}
+
+TEST_CASE ("basic upgrade scheme with features", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_feature_pgh("a", "a1"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "", {{"a1", ""}});
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+
+ auto plan = graph.serialize();
+
+ REQUIRE(plan.size() == 2);
+
+ REQUIRE(plan.at(0).spec().name() == "a");
+ REQUIRE(plan.at(0).remove_action.has_value());
+
+ features_check(plan.at(1), "a", {"core", "a1"});
+}
+
+TEST_CASE ("basic upgrade scheme with new default feature", "[plan]")
+{
+ // only core of package "a" is installed
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ // a1 was added as a default feature and should be installed in upgrade
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}, {"a1"});
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+
+ auto plan = graph.serialize();
+
+ REQUIRE(plan.size() == 2);
+
+ REQUIRE(plan.at(0).spec().name() == "a");
+ REQUIRE(plan.at(0).remove_action.has_value());
+
+ features_check(plan.at(1), "a", {"core", "a1"});
+}
+
+TEST_CASE ("basic upgrade scheme with self features", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_feature_pgh("a", "a1", ""));
+ pghs.push_back(make_status_feature_pgh("a", "a2", "a[a1]"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "", {{"a1", ""}, {"a2", "a[a1]"}});
+
+ Dependencies::MapPortFileProvider provider(spec_map.map);
+ Dependencies::PackageGraph graph(provider, status_db);
+
+ graph.upgrade(spec_a);
+
+ auto plan = graph.serialize();
+
+ REQUIRE(plan.size() == 2);
+
+ REQUIRE(plan.at(0).spec().name() == "a");
+ REQUIRE(plan.at(0).remove_action.has_value());
+
+ REQUIRE(plan.at(1).spec().name() == "a");
+ REQUIRE(plan.at(1).install_action.has_value());
+ REQUIRE(plan.at(1).install_action.get()->feature_list == std::set<std::string>{"core", "a1", "a2"});
+}
+
+TEST_CASE ("basic export scheme", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a");
+
+ auto plan = Dependencies::create_export_plan({spec_a}, status_db);
+
+ REQUIRE(plan.size() == 1);
+ REQUIRE(plan.at(0).spec.name() == "a");
+ REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT);
+}
+
+TEST_CASE ("basic export scheme with recurse", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_pgh("b", "a"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a");
+ auto spec_b = spec_map.emplace("b", "a");
+
+ auto plan = Dependencies::create_export_plan({spec_b}, status_db);
+
+ REQUIRE(plan.size() == 2);
+ REQUIRE(plan.at(0).spec.name() == "a");
+ REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT);
+
+ REQUIRE(plan.at(1).spec.name() == "b");
+ REQUIRE(plan.at(1).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT);
+}
+
+TEST_CASE ("basic export scheme with bystander", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_pgh("b"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a");
+ auto spec_b = spec_map.emplace("b", "a");
+
+ auto plan = Dependencies::create_export_plan({spec_a}, status_db);
+
+ REQUIRE(plan.size() == 1);
+ REQUIRE(plan.at(0).spec.name() == "a");
+ REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT);
+}
+
+TEST_CASE ("basic export scheme with missing", "[plan]")
+{
+ StatusParagraphs status_db;
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a");
+
+ auto plan = Dependencies::create_export_plan({spec_a}, status_db);
+
+ REQUIRE(plan.size() == 1);
+ REQUIRE(plan.at(0).spec.name() == "a");
+ REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::NOT_BUILT);
+}
+
+TEST_CASE ("basic export scheme with features", "[plan]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> pghs;
+ pghs.push_back(make_status_pgh("b"));
+ pghs.push_back(make_status_pgh("a"));
+ pghs.push_back(make_status_feature_pgh("a", "a1", "b[core]"));
+ StatusParagraphs status_db(std::move(pghs));
+
+ PackageSpecMap spec_map;
+ auto spec_a = spec_map.emplace("a", "", {{"a1", ""}});
+
+ auto plan = Dependencies::create_export_plan({spec_a}, status_db);
+
+ REQUIRE(plan.size() == 2);
+
+ REQUIRE(plan.at(0).spec.name() == "b");
+ REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT);
+
+ REQUIRE(plan.at(1).spec.name() == "a");
+ REQUIRE(plan.at(1).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT);
+}
diff --git a/toolsrc/src/vcpkg-test/specifier.cpp b/toolsrc/src/vcpkg-test/specifier.cpp
new file mode 100644
index 000000000..33df8ba83
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/specifier.cpp
@@ -0,0 +1,134 @@
+#include <catch2/catch.hpp>
+
+#include <vcpkg/base/util.h>
+#include <vcpkg/packagespec.h>
+
+using namespace vcpkg;
+
+TEST_CASE ("specifier conversion", "[specifier]")
+{
+ SECTION ("full package spec to feature specs")
+ {
+ constexpr std::size_t SPEC_SIZE = 6;
+
+ auto a_spec = PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO);
+ auto b_spec = PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO);
+
+ auto fspecs = FullPackageSpec::to_feature_specs({{a_spec, {"0", "1"}}, {b_spec, {"2", "3"}}});
+
+ REQUIRE(fspecs.size() == SPEC_SIZE);
+
+ std::array<const char*, SPEC_SIZE> features = {"", "0", "1", "", "2", "3"};
+ std::array<PackageSpec*, SPEC_SIZE> specs = {&a_spec, &a_spec, &a_spec, &b_spec, &b_spec, &b_spec};
+
+ for (std::size_t i = 0; i < SPEC_SIZE; ++i)
+ {
+ REQUIRE(features.at(i) == fspecs.at(i).feature());
+ REQUIRE(*specs.at(i) == fspecs.at(i).spec());
+ }
+ }
+}
+
+TEST_CASE ("specifier parsing", "[specifier]")
+{
+ SECTION ("parsed specifier from string")
+ {
+ auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib");
+ REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS);
+
+ auto& spec = *maybe_spec.get();
+ REQUIRE(spec.name == "zlib");
+ REQUIRE(spec.features.size() == 0);
+ REQUIRE(spec.triplet == "");
+ }
+
+ SECTION ("parsed specifier from string with triplet")
+ {
+ auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib:x64-uwp");
+ REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS);
+
+ auto& spec = *maybe_spec.get();
+ REQUIRE(spec.name == "zlib");
+ REQUIRE(spec.triplet == "x64-uwp");
+ }
+
+ SECTION ("parsed specifier from string with colons")
+ {
+ auto ec = vcpkg::ParsedSpecifier::from_string("zlib:x86-uwp:").error();
+ REQUIRE(ec == vcpkg::PackageSpecParseResult::TOO_MANY_COLONS);
+ }
+
+ SECTION ("parsed specifier from string with feature")
+ {
+ auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[feature]:x64-uwp");
+ REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS);
+
+ auto& spec = *maybe_spec.get();
+ REQUIRE(spec.name == "zlib");
+ REQUIRE(spec.features.size() == 1);
+ REQUIRE(spec.features.at(0) == "feature");
+ REQUIRE(spec.triplet == "x64-uwp");
+ }
+
+ SECTION ("parsed specifier from string with many features")
+ {
+ auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[0, 1,2]");
+ REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS);
+
+ auto& spec = *maybe_spec.get();
+ REQUIRE(spec.name == "zlib");
+ REQUIRE(spec.features.size() == 3);
+ REQUIRE(spec.features.at(0) == "0");
+ REQUIRE(spec.features.at(1) == "1");
+ REQUIRE(spec.features.at(2) == "2");
+ REQUIRE(spec.triplet == "");
+ }
+
+ SECTION ("parsed specifier wildcard feature")
+ {
+ auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[*]");
+ REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS);
+
+ auto& spec = *maybe_spec.get();
+ REQUIRE(spec.name == "zlib");
+ REQUIRE(spec.features.size() == 1);
+ REQUIRE(spec.features.at(0) == "*");
+ REQUIRE(spec.triplet == "");
+ }
+
+ SECTION ("expand wildcards")
+ {
+ auto zlib = vcpkg::FullPackageSpec::from_string("zlib[0,1]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO);
+ auto openssl =
+ vcpkg::FullPackageSpec::from_string("openssl[*]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO);
+ auto specs = FullPackageSpec::to_feature_specs({zlib, openssl});
+ Util::sort(specs);
+ auto spectargets = FeatureSpec::from_strings_and_triplet(
+ {
+ "openssl",
+ "zlib",
+ "openssl[*]",
+ "zlib[0]",
+ "zlib[1]",
+ },
+ Triplet::X86_UWP);
+ Util::sort(spectargets);
+
+ REQUIRE(specs.size() == spectargets.size());
+ REQUIRE(Util::all_equal(specs, spectargets));
+ }
+
+#if defined(_WIN32)
+ SECTION ("ASCII to utf16")
+ {
+ auto str = vcpkg::Strings::to_utf16("abc");
+ REQUIRE(str == L"abc");
+ }
+
+ SECTION ("ASCII to utf16 with whitespace")
+ {
+ auto str = vcpkg::Strings::to_utf16("abc -x86-windows");
+ REQUIRE(str == L"abc -x86-windows");
+ }
+#endif
+}
diff --git a/toolsrc/src/vcpkg-test/statusparagraphs.cpp b/toolsrc/src/vcpkg-test/statusparagraphs.cpp
new file mode 100644
index 000000000..88b499118
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/statusparagraphs.cpp
@@ -0,0 +1,110 @@
+#include <catch2/catch.hpp>
+#include <vcpkg-test/util.h>
+
+#include <vcpkg/base/util.h>
+#include <vcpkg/paragraphs.h>
+#include <vcpkg/statusparagraphs.h>
+
+using namespace vcpkg;
+using namespace vcpkg::Paragraphs;
+using namespace vcpkg::Test;
+
+TEST_CASE ("find installed", "[statusparagraphs]")
+{
+ auto pghs = parse_paragraphs(R"(
+Package: ffmpeg
+Version: 3.3.3
+Architecture: x64-windows
+Multi-Arch: same
+Description:
+Status: install ok installed
+)");
+
+ REQUIRE(pghs);
+
+ StatusParagraphs status_db(
+ Util::fmap(*pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); }));
+
+ auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS));
+ REQUIRE(it != status_db.end());
+}
+
+TEST_CASE ("find not installed", "[statusparagraphs]")
+{
+ auto pghs = parse_paragraphs(R"(
+Package: ffmpeg
+Version: 3.3.3
+Architecture: x64-windows
+Multi-Arch: same
+Description:
+Status: purge ok not-installed
+)");
+
+ REQUIRE(pghs);
+
+ StatusParagraphs status_db(
+ Util::fmap(*pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); }));
+
+ auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS));
+ REQUIRE(it == status_db.end());
+}
+
+TEST_CASE ("find with feature packages", "[statusparagraphs]")
+{
+ auto pghs = parse_paragraphs(R"(
+Package: ffmpeg
+Version: 3.3.3
+Architecture: x64-windows
+Multi-Arch: same
+Description:
+Status: install ok installed
+
+Package: ffmpeg
+Feature: openssl
+Depends: openssl
+Architecture: x64-windows
+Multi-Arch: same
+Description:
+Status: purge ok not-installed
+)");
+
+ REQUIRE(pghs);
+
+ StatusParagraphs status_db(
+ Util::fmap(*pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); }));
+
+ auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS));
+ REQUIRE(it != status_db.end());
+
+ // Feature "openssl" is not installed and should not be found
+ auto it1 = status_db.find_installed({unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS), "openssl"});
+ REQUIRE(it1 == status_db.end());
+}
+
+TEST_CASE ("find for feature packages", "[statusparagraphs]")
+{
+ auto pghs = parse_paragraphs(R"(
+Package: ffmpeg
+Version: 3.3.3
+Architecture: x64-windows
+Multi-Arch: same
+Description:
+Status: install ok installed
+
+Package: ffmpeg
+Feature: openssl
+Depends: openssl
+Architecture: x64-windows
+Multi-Arch: same
+Description:
+Status: install ok installed
+)");
+ REQUIRE(pghs);
+
+ StatusParagraphs status_db(
+ Util::fmap(*pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); }));
+
+ // Feature "openssl" is installed and should therefore be found
+ auto it = status_db.find_installed({unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS), "openssl"});
+ REQUIRE(it != status_db.end());
+}
diff --git a/toolsrc/src/vcpkg-test/strings.cpp b/toolsrc/src/vcpkg-test/strings.cpp
new file mode 100644
index 000000000..d58d1b172
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/strings.cpp
@@ -0,0 +1,33 @@
+#include <catch2/catch.hpp>
+
+#include <vcpkg/base/strings.h>
+
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+TEST_CASE ("b32 encoding", "[strings]")
+{
+ using u64 = std::uint64_t;
+
+ std::vector<std::pair<std::uint64_t, std::string>> map;
+
+ map.emplace_back(0, "AAAAAAAAAAAAA");
+ map.emplace_back(1, "BAAAAAAAAAAAA");
+
+ map.emplace_back(u64(1) << 32, "AAAAAAEAAAAAA");
+ map.emplace_back((u64(1) << 32) + 1, "BAAAAAEAAAAAA");
+
+ map.emplace_back(0xE4D0'1065'D11E'0229, "JRA4RIXMQAUJO");
+ map.emplace_back(0xA626'FE45'B135'07FF, "77BKTYWI6XJMK");
+ map.emplace_back(0xEE36'D228'0C31'D405, "FAVDDGAFSWN4O");
+ map.emplace_back(0x1405'64E7'FE7E'A88C, "MEK5H774ELBIB");
+ map.emplace_back(0xFFFF'FFFF'FFFF'FFFF, "777777777777P");
+
+ std::string result;
+ for (const auto& pr : map)
+ {
+ result = vcpkg::Strings::b32_encode(pr.first);
+ REQUIRE(vcpkg::Strings::b32_encode(pr.first) == pr.second);
+ }
+}
diff --git a/toolsrc/src/vcpkg-test/stringview.cpp b/toolsrc/src/vcpkg-test/stringview.cpp
new file mode 100644
index 000000000..4df8e6be5
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/stringview.cpp
@@ -0,0 +1,17 @@
+#include <catch2/catch.hpp>
+
+#include <vcpkg/base/stringview.h>
+
+template <std::size_t N>
+static vcpkg::StringView sv(const char (&cstr)[N]) {
+ return cstr;
+}
+
+TEST_CASE("string view operator==", "[stringview]") {
+ // these are due to a bug in operator==
+ // see commit 782723959399a1a0725ac49
+ REQUIRE(sv("hey") != sv("heys"));
+ REQUIRE(sv("heys") != sv("hey"));
+ REQUIRE(sv("hey") == sv("hey"));
+ REQUIRE(sv("hey") != sv("hex"));
+}
diff --git a/toolsrc/src/vcpkg-test/supports.cpp b/toolsrc/src/vcpkg-test/supports.cpp
new file mode 100644
index 000000000..f4d8dc65a
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/supports.cpp
@@ -0,0 +1,79 @@
+#include <catch2/catch.hpp>
+
+#include <vcpkg/sourceparagraph.h>
+
+using namespace vcpkg;
+using Parse::parse_comma_list;
+
+TEST_CASE ("parse supports all", "[supports]")
+{
+ auto v = Supports::parse({
+ "x64",
+ "x86",
+ "arm",
+ "windows",
+ "uwp",
+ "v140",
+ "v141",
+ "crt-static",
+ "crt-dynamic",
+ });
+
+ REQUIRE(v.has_value());
+
+ REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64,
+ Supports::Platform::UWP,
+ Supports::Linkage::DYNAMIC,
+ Supports::ToolsetVersion::V140));
+ REQUIRE(v.get()->is_supported(System::CPUArchitecture::ARM,
+ Supports::Platform::WINDOWS,
+ Supports::Linkage::STATIC,
+ Supports::ToolsetVersion::V141));
+}
+
+TEST_CASE ("parse supports invalid", "[supports]")
+{
+ auto v = Supports::parse({"arm64"});
+
+ REQUIRE_FALSE(v.has_value());
+
+ REQUIRE(v.error().size() == 1);
+ REQUIRE(v.error().at(0) == "arm64");
+}
+
+TEST_CASE ("parse supports case sensitive", "[supports]")
+{
+ auto v = Supports::parse({"Windows"});
+
+ REQUIRE_FALSE(v.has_value());
+ REQUIRE(v.error().size() == 1);
+ REQUIRE(v.error().at(0) == "Windows");
+}
+
+TEST_CASE ("parse supports some", "[supports]")
+{
+ auto v = Supports::parse({
+ "x64",
+ "x86",
+ "windows",
+ });
+
+ REQUIRE(v.has_value());
+
+ REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64,
+ Supports::Platform::WINDOWS,
+ Supports::Linkage::DYNAMIC,
+ Supports::ToolsetVersion::V140));
+ REQUIRE_FALSE(v.get()->is_supported(System::CPUArchitecture::ARM,
+ Supports::Platform::WINDOWS,
+ Supports::Linkage::DYNAMIC,
+ Supports::ToolsetVersion::V140));
+ REQUIRE_FALSE(v.get()->is_supported(System::CPUArchitecture::X64,
+ Supports::Platform::UWP,
+ Supports::Linkage::DYNAMIC,
+ Supports::ToolsetVersion::V140));
+ REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64,
+ Supports::Platform::WINDOWS,
+ Supports::Linkage::STATIC,
+ Supports::ToolsetVersion::V141));
+}
diff --git a/toolsrc/src/vcpkg-test/update.cpp b/toolsrc/src/vcpkg-test/update.cpp
new file mode 100644
index 000000000..6f1a87d23
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/update.cpp
@@ -0,0 +1,102 @@
+#include <catch2/catch.hpp>
+#include <vcpkg-test/util.h>
+
+#include <vcpkg/base/sortedvector.h>
+
+#include <vcpkg/update.h>
+
+using namespace vcpkg;
+using namespace vcpkg::Update;
+using namespace vcpkg::Test;
+
+using Pgh = std::vector<std::unordered_map<std::string, std::string>>;
+
+TEST_CASE ("find outdated packages basic", "[update]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a"));
+ status_paragraphs.back()->package.version = "2";
+
+ StatusParagraphs status_db(std::move(status_paragraphs));
+
+ std::unordered_map<std::string, SourceControlFileLocation> map;
+ auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}}));
+ map.emplace("a", SourceControlFileLocation{std::move(scf), ""});
+ Dependencies::MapPortFileProvider provider(map);
+
+ auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db),
+ &OutdatedPackage::compare_by_name);
+
+ REQUIRE(pkgs.size() == 1);
+ REQUIRE(pkgs[0].version_diff.left.to_string() == "2");
+ REQUIRE(pkgs[0].version_diff.right.to_string() == "0");
+}
+
+TEST_CASE ("find outdated packages features", "[update]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a"));
+ status_paragraphs.back()->package.version = "2";
+
+ status_paragraphs.push_back(make_status_feature_pgh("a", "b"));
+ status_paragraphs.back()->package.version = "2";
+
+ StatusParagraphs status_db(std::move(status_paragraphs));
+
+ std::unordered_map<std::string, SourceControlFileLocation> map;
+ auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}}));
+ map.emplace("a", SourceControlFileLocation{std::move(scf), ""});
+ Dependencies::MapPortFileProvider provider(map);
+
+ auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db),
+ &OutdatedPackage::compare_by_name);
+
+ REQUIRE(pkgs.size() == 1);
+ REQUIRE(pkgs[0].version_diff.left.to_string() == "2");
+ REQUIRE(pkgs[0].version_diff.right.to_string() == "0");
+}
+
+TEST_CASE ("find outdated packages features 2", "[update]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a"));
+ status_paragraphs.back()->package.version = "2";
+
+ status_paragraphs.push_back(make_status_feature_pgh("a", "b"));
+ status_paragraphs.back()->package.version = "0";
+ status_paragraphs.back()->state = InstallState::NOT_INSTALLED;
+ status_paragraphs.back()->want = Want::PURGE;
+
+ StatusParagraphs status_db(std::move(status_paragraphs));
+
+ std::unordered_map<std::string, SourceControlFileLocation> map;
+ auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}}));
+ map.emplace("a", SourceControlFileLocation{std::move(scf), ""});
+ Dependencies::MapPortFileProvider provider(map);
+
+ auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db),
+ &OutdatedPackage::compare_by_name);
+
+ REQUIRE(pkgs.size() == 1);
+ REQUIRE(pkgs[0].version_diff.left.to_string() == "2");
+ REQUIRE(pkgs[0].version_diff.right.to_string() == "0");
+}
+
+TEST_CASE ("find outdated packages none", "[update]")
+{
+ std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs;
+ status_paragraphs.push_back(make_status_pgh("a"));
+ status_paragraphs.back()->package.version = "2";
+
+ StatusParagraphs status_db(std::move(status_paragraphs));
+
+ std::unordered_map<std::string, SourceControlFileLocation> map;
+ auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "2"}}}));
+ map.emplace("a", SourceControlFileLocation{std::move(scf), ""});
+ Dependencies::MapPortFileProvider provider(map);
+
+ auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db),
+ &OutdatedPackage::compare_by_name);
+
+ REQUIRE(pkgs.size() == 0);
+}
diff --git a/toolsrc/src/vcpkg-test/util.cpp b/toolsrc/src/vcpkg-test/util.cpp
new file mode 100644
index 000000000..daa21567d
--- /dev/null
+++ b/toolsrc/src/vcpkg-test/util.cpp
@@ -0,0 +1,183 @@
+#include <catch2/catch.hpp>
+#include <vcpkg-test/util.h>
+
+#include <vcpkg/base/checks.h>
+#include <vcpkg/base/files.h>
+#include <vcpkg/base/util.h>
+#include <vcpkg/statusparagraph.h>
+
+// used to get the implementation specific compiler flags (i.e., __cpp_lib_filesystem)
+#include <ciso646>
+
+#include <iostream>
+#include <memory>
+
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+
+#define FILESYSTEM_SYMLINK_STD 0
+#define FILESYSTEM_SYMLINK_UNIX 1
+#define FILESYSTEM_SYMLINK_NONE 2
+
+#if defined(__cpp_lib_filesystem)
+
+#define FILESYSTEM_SYMLINK FILESYSTEM_SYMLINK_STD
+#include <filesystem> // required for filesystem::create_{directory_}symlink
+
+#elif !defined(_MSC_VER)
+
+#define FILESYSTEM_SYMLINK FILESYSTEM_SYMLINK_UNIX
+#include <unistd.h>
+
+#else
+
+#define FILESYSTEM_SYMLINK FILESYSTEM_SYMLINK_NONE
+
+#endif
+
+namespace vcpkg::Test
+{
+ std::unique_ptr<vcpkg::StatusParagraph> make_status_pgh(const char* name,
+ const char* depends,
+ const char* default_features,
+ const char* triplet)
+ {
+ using Pgh = std::unordered_map<std::string, std::string>;
+ return std::make_unique<StatusParagraph>(Pgh{{"Package", name},
+ {"Version", "1"},
+ {"Architecture", triplet},
+ {"Multi-Arch", "same"},
+ {"Depends", depends},
+ {"Default-Features", default_features},
+ {"Status", "install ok installed"}});
+ }
+
+ std::unique_ptr<StatusParagraph> make_status_feature_pgh(const char* name,
+ const char* feature,
+ const char* depends,
+ const char* triplet)
+ {
+ using Pgh = std::unordered_map<std::string, std::string>;
+ return std::make_unique<StatusParagraph>(Pgh{{"Package", name},
+ {"Version", "1"},
+ {"Feature", feature},
+ {"Architecture", triplet},
+ {"Multi-Arch", "same"},
+ {"Depends", depends},
+ {"Status", "install ok installed"}});
+ }
+
+ PackageSpec unsafe_pspec(std::string name, Triplet t)
+ {
+ auto m_ret = PackageSpec::from_name_and_triplet(name, t);
+ REQUIRE(m_ret.has_value());
+ return m_ret.value_or_exit(VCPKG_LINE_INFO);
+ }
+
+ static AllowSymlinks internal_can_create_symlinks() noexcept
+ {
+#if FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_NONE
+ return AllowSymlinks::No;
+#elif FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_UNIX
+ return AllowSymlinks::Yes;
+#elif !defined(_WIN32) // FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_STD
+ return AllowSymlinks::Yes;
+#else
+ HKEY key;
+ bool allow_symlinks = true;
+
+ const auto status = RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE, LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock)", 0, 0, &key);
+
+ if (status == ERROR_FILE_NOT_FOUND)
+ {
+ allow_symlinks = false;
+ std::clog << "Symlinks are not allowed on this system\n";
+ }
+
+ if (status == ERROR_SUCCESS) RegCloseKey(key);
+
+ return allow_symlinks ? AllowSymlinks::Yes : AllowSymlinks::No;
+#endif
+ }
+ const static AllowSymlinks CAN_CREATE_SYMLINKS = internal_can_create_symlinks();
+
+ AllowSymlinks can_create_symlinks() noexcept { return CAN_CREATE_SYMLINKS; }
+
+ static fs::path internal_base_temporary_directory()
+ {
+#if defined(_WIN32)
+ wchar_t* tmp = static_cast<wchar_t*>(std::calloc(32'767, 2));
+
+ if (!GetEnvironmentVariableW(L"TEMP", tmp, 32'767))
+ {
+ std::cerr << "No temporary directory found.\n";
+ std::abort();
+ }
+
+ fs::path result = tmp;
+ std::free(tmp);
+
+ return result / L"vcpkg-test";
+#else
+ return "/tmp/vcpkg-test";
+#endif
+ }
+
+ const static fs::path BASE_TEMPORARY_DIRECTORY = internal_base_temporary_directory();
+
+ const fs::path& base_temporary_directory() noexcept { return BASE_TEMPORARY_DIRECTORY; }
+
+#if FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_NONE
+ constexpr char no_filesystem_message[] =
+ "<filesystem> doesn't exist; on windows, we don't attempt to use the win32 calls to create symlinks";
+#endif
+
+ void create_symlink(const fs::path& target, const fs::path& file, std::error_code& ec)
+ {
+#if FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_STD
+ if (can_create_symlinks())
+ {
+ std::filesystem::path targetp = target.native();
+ std::filesystem::path filep = file.native();
+
+ std::filesystem::create_symlink(targetp, filep, ec);
+ }
+ else
+ {
+ vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Symlinks are not allowed on this system");
+ }
+#elif FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_UNIX
+ if (symlink(target.c_str(), file.c_str()) != 0)
+ {
+ ec.assign(errno, std::system_category());
+ }
+#else
+ Util::unused(target, file, ec);
+ vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, no_filesystem_message);
+#endif
+ }
+
+ void create_directory_symlink(const fs::path& target, const fs::path& file, std::error_code& ec)
+ {
+#if FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_STD
+ if (can_create_symlinks())
+ {
+ std::filesystem::path targetp = target.native();
+ std::filesystem::path filep = file.native();
+
+ std::filesystem::create_directory_symlink(targetp, filep, ec);
+ }
+ else
+ {
+ vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Symlinks are not allowed on this system");
+ }
+#elif FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_UNIX
+ ::vcpkg::Test::create_symlink(target, file, ec);
+#else
+ Util::unused(target, file, ec);
+ vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, no_filesystem_message);
+#endif
+ }
+}