diff options
| author | Robert Schumacher <roschuma@microsoft.com> | 2019-11-22 09:47:40 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-11-22 09:47:40 -0800 |
| commit | 45f4b820e5743b89bca3508ba2028cdd5d8bbd17 (patch) | |
| tree | f874a8c4a7392309bdbb86447288597ec0a4a281 /toolsrc/src/vcpkg-test | |
| parent | 62d67d3bf8eeff1afa8009041fd08b8822676b7b (diff) | |
| parent | 8831e8f25f1ff6546ee4a5291b91d599421637b3 (diff) | |
| download | vcpkg-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.cpp | 109 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/catch.cpp | 11 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/chrono.cpp | 34 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/dependencies.cpp | 28 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/files.cpp | 243 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/hash.cpp | 276 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/paragraph.cpp | 445 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/plan.cpp | 1241 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/specifier.cpp | 134 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/statusparagraphs.cpp | 110 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/strings.cpp | 33 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/stringview.cpp | 17 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/supports.cpp | 79 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/update.cpp | 102 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg-test/util.cpp | 183 |
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 + } +} |
