import json import os import re import shutil import sys from pathlib import Path from typing import Dict, List, Set, Union from urllib.parse import urlparse import pytest from aqt.exceptions import ( AqtException, ArchiveConnectionError, ArchiveDownloadError, ArchiveListError, CliInputError, EmptyMetadata, ) from aqt.helper import Settings from aqt.installer import Cli from aqt.metadata import ( ArchiveId, MetadataFactory, QtRepoProperty, SimpleSpec, ToolData, Version, Versions, show_list, suggested_follow_up, ) Settings.load_settings() @pytest.mark.parametrize( "arch, version, expect", ( ("wasm_32", Version("5.13.1"), "wasm"), ("mingw", Version("5.13.1"), ""), ("android_fake", Version("5.13.1"), ""), ("android_x86", Version("5.13.1"), ""), ("android_x86", Version("6.13.1"), "x86"), ("android_x86", Version("6.0.0"), "x86"), ), ) def test_list_extension_for_arch(arch: str, version: Version, expect: str): ext = QtRepoProperty.extension_for_arch(arch, version >= Version("6.0.0")) assert ext == expect @pytest.mark.parametrize( "arch, expect", ( ("wasm_32", ["wasm"]), ("mingw", [""]), ("android_fake", [""]), ("android", [""]), ("android_x86", ["", "x86"]), ), ) def test_list_possible_extension_for_arch(arch: str, expect: List[str]): exts = QtRepoProperty.possible_extensions_for_arch(arch) assert set(exts) == set(expect) @pytest.mark.parametrize( "init_data, expect_str, expect_fmt, expect_flat, expect_last, expect_bool", ( ( [ (1, [Version("1.1.1"), Version("1.1.2")]), (2, [Version("1.2.1"), Version("1.2.2")]), ], "[[Version('1.1.1'), Version('1.1.2')], [Version('1.2.1'), Version('1.2.2')]]", "1.1.1 1.1.2\n1.2.1 1.2.2", [Version("1.1.1"), Version("1.1.2"), Version("1.2.1"), Version("1.2.2")], Version("1.2.2"), True, ), ( [], "[]", "", [], None, False, ), ( Version("1.2.3"), "[[Version('1.2.3')]]", "1.2.3", [Version("1.2.3")], Version("1.2.3"), True, ), ), ) def test_versions(init_data, expect_str, expect_fmt, expect_flat, expect_last, expect_bool): versions = Versions(init_data) assert str(versions) == expect_str assert format(versions) == expect_fmt assert format(versions, "s") == expect_str assert versions.flattened() == expect_flat assert versions.latest() == expect_last assert bool(versions) == expect_bool with pytest.raises(TypeError) as pytest_wrapped_e: format(versions, "x") assert pytest_wrapped_e.type == TypeError @pytest.fixture def spec_regex(): return re.compile(r"^(\d+\.\d+)") @pytest.mark.parametrize( "os_name,target,in_file,expect_out_file", [ ("windows", "android", "windows-android.html", "windows-android-expect.json"), ("windows", "desktop", "windows-desktop.html", "windows-desktop-expect.json"), ("windows", "winrt", "windows-winrt.html", "windows-winrt-expect.json"), ("linux", "android", "linux-android.html", "linux-android-expect.json"), ("linux", "desktop", "linux-desktop.html", "linux-desktop-expect.json"), ("mac", "android", "mac-android.html", "mac-android-expect.json"), ("mac", "desktop", "mac-desktop.html", "mac-desktop-expect.json"), ("mac", "ios", "mac-ios.html", "mac-ios-expect.json"), ], ) def test_list_versions_tools(monkeypatch, spec_regex, os_name, target, in_file, expect_out_file): _html = (Path(__file__).parent / "data" / in_file).read_text("utf-8") monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: _html) expected = json.loads((Path(__file__).parent / "data" / expect_out_file).read_text("utf-8")) # Test 'aqt list-tool' tools = MetadataFactory(ArchiveId("tools", os_name, target)).getList() assert tools == expected["tools"] for ext, expected_output in expected["qt"].items(): # Test 'aqt list-qt' archive_id = ArchiveId("qt", os_name, target, ext if ext != "qt" else "") all_versions = MetadataFactory(archive_id).getList() if len(expected_output) == 0: assert not all_versions else: assert f"{all_versions}" == "\n".join(expected_output) # Filter for the latest version only latest_ver = MetadataFactory(archive_id, is_latest_version=True).getList() if len(expected_output) == 0: assert not latest_ver else: assert f"{latest_ver}" == expected_output[-1].split(" ")[-1] for row in expected_output: spec_str = spec_regex.search(row).group(1) spec = SimpleSpec(spec_str) if not ext.endswith("preview") else SimpleSpec(spec_str + ".0-preview") # Find the latest version for a particular spec latest_ver_for_spec = MetadataFactory( archive_id, spec=spec, is_latest_version=True, ).getList() assert f"{latest_ver_for_spec}" == row.split(" ")[-1] # Find all versions for a particular spec all_ver_for_spec = MetadataFactory( archive_id, spec=spec, ).getList() assert f"{all_ver_for_spec}" == row @pytest.mark.parametrize( "version,extension,in_file,expect_out_file", [ ("5.14.0", "", "windows-5140-update.xml", "windows-5140-expect.json"), ("5.15.0", "", "windows-5150-update.xml", "windows-5150-expect.json"), ( "5.15.2", "src_doc_examples", "windows-5152-src-doc-example-update.xml", "windows-5152-src-doc-example-expect.json", ), ("6.2.0", "", "windows-620-update.xml", "windows-620-expect.json"), ], ) def test_list_architectures_and_modules(monkeypatch, version: str, extension: str, in_file: str, expect_out_file: str): archive_id = ArchiveId("qt", "windows", "desktop", extension) _xml = (Path(__file__).parent / "data" / in_file).read_text("utf-8") expect = json.loads((Path(__file__).parent / "data" / expect_out_file).read_text("utf-8")) monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: _xml) for arch in expect["architectures"]: modules = MetadataFactory(archive_id).fetch_modules(Version(version), arch) assert modules == sorted(expect["modules_by_arch"][arch]) arches = MetadataFactory(archive_id).fetch_arches(Version(version)) assert arches == expect["architectures"] @pytest.mark.parametrize( "version, arch, modules_to_query, modules_failed_query", ( ("5.14.0", "win32_mingw73", [], []), ("5.14.0", "win32_mingw73", ["qtcharts"], []), ("5.14.0", "win32_mingw73", ["all"], []), ("5.14.0", "win32_mingw73", ["debug_info"], ["debug_info"]), ("5.14.0", "win64_msvc2017_64", [], []), ("5.14.0", "win64_msvc2017_64", ["debug_info"], []), ), ) def test_list_archives( monkeypatch, capsys, version: str, arch: str, modules_to_query: List[str], modules_failed_query: List[str] ): archive_id = ArchiveId("qt", "windows", "desktop") in_file = f"{archive_id.host}-{version.replace('.', '')}-update.xml" expect_out_file = f"{archive_id.host}-{version.replace('.', '')}-expect.json" _xml = (Path(__file__).parent / "data" / in_file).read_text("utf-8") monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: _xml) expect = json.loads((Path(__file__).parent / "data" / expect_out_file).read_text("utf-8")) if not modules_to_query: expected_qt_archives = expect["qt_base_pkgs_by_arch"][arch]["DownloadableArchives"] expected = set([arc.split("-")[0] for arc in expected_qt_archives]) else: expected_mod_metadata = expect["modules_metadata_by_arch"][arch] if "all" not in modules_to_query: expected_mod_metadata = filter(lambda mod: mod["Name"].split(".")[-2] in modules_to_query, expected_mod_metadata) expected = set([arc.split("-")[0] for mod in expected_mod_metadata for arc in mod["DownloadableArchives"]]) archives_query = [version, arch, *modules_to_query] cli_args = ["list-qt", "windows", "desktop", "--archives", *archives_query] if not modules_failed_query: meta = set(MetadataFactory(archive_id, archives_query=archives_query).getList()) assert meta == expected cli = Cli() assert 0 == cli.run(cli_args) out, err = capsys.readouterr() assert out.rstrip() == " ".join(sorted(expected)) return expected_err_msg = f"The requested modules were not located: {sorted(modules_failed_query)}" with pytest.raises(CliInputError) as err: MetadataFactory(archive_id, archives_query=archives_query).getList() assert err.type == CliInputError assert format(err.value).startswith(expected_err_msg) cli = Cli() assert 1 == cli.run(cli_args) out, err = capsys.readouterr() assert expected_err_msg in err def test_list_archives_insufficient_args(capsys): cli = Cli() assert 1 == cli.run("list-qt mac desktop --archives 5.14.0".split()) out, err = capsys.readouterr() assert err.strip() == "The '--archives' flag requires a 'QT_VERSION' and an 'ARCHITECTURE' parameter." def test_list_archives_bad_xml(monkeypatch): archive_id = ArchiveId("qt", "windows", "desktop") archives_query = ["5.15.2", "win32_mingw81", "qtcharts"] xml_no_name = "" xml_empty_name = "" xml_broken = "" for _xml in (xml_no_name, xml_empty_name, xml_broken): monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: _xml) with pytest.raises(ArchiveListError) as e: MetadataFactory(archive_id, archives_query=archives_query).getList() assert e.type == ArchiveListError @pytest.mark.parametrize( "host, target, tool_name", [ ("mac", "desktop", "tools_cmake"), ("mac", "desktop", "tools_ifw"), ("mac", "desktop", "tools_qtcreator"), ], ) def test_tool_modules(monkeypatch, host: str, target: str, tool_name: str): archive_id = ArchiveId("tools", host, target) in_file = "{}-{}-{}-update.xml".format(host, target, tool_name) expect_out_file = "{}-{}-{}-expect.json".format(host, target, tool_name) _xml = (Path(__file__).parent / "data" / in_file).read_text("utf-8") expect = json.loads((Path(__file__).parent / "data" / expect_out_file).read_text("utf-8")) monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: _xml) modules = MetadataFactory(archive_id, tool_name=tool_name).getList() assert modules == expect["modules"] table = MetadataFactory(archive_id, tool_name=tool_name, is_long_listing=True).getList() assert table._rows() == expect["long_listing"] @pytest.fixture def expected_windows_desktop_5140() -> Dict: xmlexpect = "windows-5140-expect.json" return json.loads((Path(__file__).parent / "data" / xmlexpect).read_text("utf-8")) @pytest.mark.parametrize( "args, expect", ( ("--extensions latest", {"src_doc_examples"}), ("--spec 5.14 --extensions latest", {"wasm", "src_doc_examples"}), ("--extensions 5.14.0", {"wasm", "src_doc_examples"}), ("--modules latest win64_msvc2017_64", ["modules_by_arch", "win64_msvc2017_64"]), ("--spec 5.14 --modules latest win64_msvc2017_64", ["modules_by_arch", "win64_msvc2017_64"]), ("--modules 5.14.0 win32_mingw73", ["modules_by_arch", "win32_mingw73"]), ("--modules 5.14.0 win32_msvc2017", ["modules_by_arch", "win32_msvc2017"]), ("--modules 5.14.0 win64_mingw73", ["modules_by_arch", "win64_mingw73"]), ("--modules 5.14.0 win64_msvc2015_64", ["modules_by_arch", "win64_msvc2015_64"]), ("--modules 5.14.0 win64_msvc2017_64", ["modules_by_arch", "win64_msvc2017_64"]), ("--extension wasm --modules latest win64_msvc2017_64", ["modules_by_arch", "win64_msvc2017_64"]), ("--extension wasm --spec 5.14 --modules latest win64_msvc2017_64", ["modules_by_arch", "win64_msvc2017_64"]), ("--extension wasm --modules 5.14.0 win64_msvc2017_64", ["modules_by_arch", "win64_msvc2017_64"]), ("--arch latest", ["architectures"]), ("--spec 5.14 --arch latest", ["architectures"]), ("--arch 5.14.0", ["architectures"]), ("--extension wasm --arch latest", ["architectures"]), ("--extension wasm --spec 5.14 --arch latest", ["architectures"]), ("--extension wasm --arch 5.14.0", ["architectures"]), ), ) def test_list_qt_cli( monkeypatch, capsys, expected_windows_desktop_5140: Dict[str, Set[str]], args: str, expect: Union[Set[str], List[str]], ): htmlfile, xmlfile = "windows-desktop.html", "windows-5140-update.xml" version_string_to_replace = "qt5.5140" if isinstance(expect, list): # In this case, 'expect' is a list of keys to follow to the expected values. expected_dict = expected_windows_desktop_5140 for key in expect: # Follow the chain of keys to the list of values. expected_dict = expected_dict[key] assert isinstance(expected_dict, list) expect_set = set(expected_dict) else: expect_set = expect assert isinstance(expect_set, set) def _mock_fetch_http(_, rest_of_url: str) -> str: htmltext = (Path(__file__).parent / "data" / htmlfile).read_text("utf-8") if not rest_of_url.endswith("Updates.xml"): return htmltext xmltext = (Path(__file__).parent / "data" / xmlfile).read_text("utf-8") # If we are serving an Updates.xml, `aqt list` will look for a Qt version number. # We will replace the version numbers in the file with the requested version. match = re.search(r"qt(\d)_(\d+)", rest_of_url) assert match major, version_nodot = match.groups() desired_version_string = f"qt{major}.{version_nodot}" return xmltext.replace(version_string_to_replace, desired_version_string) monkeypatch.setattr(MetadataFactory, "fetch_http", _mock_fetch_http) cli = Cli() cli.run(["list-qt", "windows", "desktop", *args.split()]) out, err = capsys.readouterr() output_set = set(out.strip().split()) assert output_set == expect_set @pytest.mark.parametrize( "cmd, host, expect", ( ("list-qt", "windows", {"desktop", "android", "winrt"}), ("list-qt", "linux", {"desktop", "android"}), ("list-qt", "mac", {"desktop", "android", "ios"}), ("list-tool", "windows", {"desktop", "android", "winrt"}), ("list-tool", "linux", {"desktop", "android"}), ("list-tool", "mac", {"desktop", "android", "ios"}), ), ) def test_list_targets(capsys, cmd: str, host: str, expect: Set[str]): cli = Cli() cli.run([cmd, host]) out, err = capsys.readouterr() output_set = set(out.strip().split()) assert output_set == expect @pytest.mark.parametrize( "cmd, host, target", ( ("list-qt", "windows", "ios"), ("list-qt", "linux", "ios"), ("list-qt", "linux", "winrt"), ("list-qt", "mac", "winrt"), ("list-tool", "windows", "ios"), ("list-tool", "linux", "ios"), ("list-tool", "linux", "winrt"), ("list-tool", "mac", "winrt"), ), ) def test_list_wrong_target(capsys, cmd: str, host: str, target: str): expect = f"'{target}' is not a valid target for host '{host}'" cli = Cli() return_code = cli.run([cmd, host, target]) out, err = capsys.readouterr() assert return_code == 1 assert err.strip() == expect @pytest.mark.parametrize( "cmd, spec", ( ("list-qt", "not a spec"), ("list-qt", "1...3"), ("list-qt", ""), ("list-qt", ">3 <5"), ), ) def test_invalid_spec(capsys, cmd: str, spec: str): expect_prefix = f"Invalid version specification: '{spec}'" host, target = "linux", "desktop" cli = Cli() return_code = cli.run([cmd, host, target, "--spec", spec]) out, err = capsys.readouterr() assert return_code == 1 assert err.strip().startswith(expect_prefix) @pytest.mark.parametrize( "simple_spec, expected_name", ( (SimpleSpec("*"), "mytool.999"), (SimpleSpec(">3.5"), "mytool.999"), (SimpleSpec("3.5.5"), "mytool.355"), (SimpleSpec("<3.5"), "mytool.300"), (SimpleSpec("<=3.5"), "mytool.355"), (SimpleSpec("<=3.5.0"), "mytool.350"), (SimpleSpec(">10"), None), ), ) def test_list_choose_tool_by_version(simple_spec, expected_name): tools_data = { "mytool.999": {"Version": "9.9.9", "Name": "mytool.999"}, "mytool.355": {"Version": "3.5.5", "Name": "mytool.355"}, "mytool.350": {"Version": "3.5.0", "Name": "mytool.350"}, "mytool.300": {"Version": "3.0.0", "Name": "mytool.300"}, } item = MetadataFactory.choose_highest_version_in_spec(tools_data, simple_spec) if item is not None: assert item["Name"] == expected_name else: assert expected_name is None qt6_android_requires_ext_msg = ( "Qt 6 for Android requires one of the following extensions: " f"{ArchiveId.EXTENSIONS_REQUIRED_ANDROID_QT6}. " "Please add your extension using the `--extension` flag." ) no_arm64_v8_msg = "The extension 'arm64_v8a' is only valid for Qt 6 for Android" no_wasm_msg = "The extension 'wasm' is only available in Qt 5.13-5.15 and 6.2+ on desktop." @pytest.mark.parametrize( "target, ext, version, expected_msg", ( ("android", "", "6.2.0", qt6_android_requires_ext_msg), ("android", "arm64_v8a", "5.13.0", no_arm64_v8_msg), ("desktop", "arm64_v8a", "5.13.0", no_arm64_v8_msg), ("desktop", "arm64_v8a", "6.2.0", no_arm64_v8_msg), ("desktop", "wasm", "5.12.11", no_wasm_msg), # out of range ("desktop", "wasm", "6.1.9", no_wasm_msg), # out of range ("android", "wasm", "5.12.11", no_wasm_msg), # in range, wrong target ("android", "wasm", "5.14.0", no_wasm_msg), # in range, wrong target ("android", "wasm", "6.1.9", qt6_android_requires_ext_msg), ), ) def test_list_invalid_extensions(capsys, monkeypatch, target, ext, version, expected_msg): host = "windows" extension_params = ["--extension", ext] if ext else [] cli = Cli() cli.run(["list-qt", host, target, *extension_params, "--arch", version]) out, err = capsys.readouterr() sys.stdout.write(out) sys.stderr.write(err) assert expected_msg in err mac_qt = ArchiveId("qt", "mac", "desktop") mac_wasm = ArchiveId("qt", "mac", "desktop", "wasm") wrong_tool_name_msg = "Please use 'aqt list-tool mac desktop' to check what tools are available." wrong_qt_version_msg = "Please use 'aqt list-qt mac desktop' to show versions of Qt available." wrong_ext_msg = "Please use 'aqt list-qt mac desktop --extensions ' to list valid extensions." wrong_arch_msg = "Please use 'aqt list-qt mac desktop --arch ' to list valid architectures." @pytest.mark.parametrize( "meta, expected_message", ( (MetadataFactory(mac_qt), []), ( MetadataFactory(mac_qt, spec=SimpleSpec("5.0")), ["Please use 'aqt list-qt mac desktop' to check that versions of qt exist within the spec '5.0'."], ), ( MetadataFactory(ArchiveId("tools", "mac", "desktop"), tool_name="ifw"), [wrong_tool_name_msg], ), ( MetadataFactory(mac_qt, architectures_ver="1.2.3"), [wrong_qt_version_msg], ), ( MetadataFactory(mac_qt, modules_query=("1.2.3", "clang_64")), [wrong_qt_version_msg, wrong_arch_msg], ), ( MetadataFactory(mac_qt, extensions_ver="1.2.3"), [wrong_qt_version_msg], ), ( MetadataFactory(mac_qt, archives_query=["1.2.3", "clang_64", "a module", "another module"]), [ "Please use 'aqt list-qt mac desktop' to show versions of Qt available.", "Please use 'aqt list-qt mac desktop --arch ' to show architectures available.", "Please use 'aqt list-qt mac desktop --modules ' to show modules available.", ], ), ( MetadataFactory(mac_qt, archives_query=["1.2.3", "clang_64"]), [ "Please use 'aqt list-qt mac desktop' to show versions of Qt available.", "Please use 'aqt list-qt mac desktop --arch ' to show architectures available.", ], ), ( MetadataFactory(mac_wasm), [wrong_ext_msg], ), ( MetadataFactory(mac_wasm, spec=SimpleSpec("<5.9")), [ wrong_ext_msg, "Please use 'aqt list-qt mac desktop' to check that versions of qt exist within the spec '<5.9'.", ], ), ( MetadataFactory(ArchiveId("tools", "mac", "desktop", "wasm"), tool_name="ifw"), [ "Please use 'aqt list-tool mac desktop --extensions ' to list valid extensions.", wrong_tool_name_msg, ], ), ( MetadataFactory(mac_wasm, architectures_ver="1.2.3"), [wrong_ext_msg, wrong_qt_version_msg], ), ( MetadataFactory(mac_wasm, modules_query=("1.2.3", "clang_64")), [wrong_ext_msg, wrong_qt_version_msg, wrong_arch_msg], ), ( MetadataFactory(mac_wasm, extensions_ver="1.2.3"), [wrong_ext_msg, wrong_qt_version_msg], ), ), ) def test_suggested_follow_up(meta: MetadataFactory, expected_message: str): assert suggested_follow_up(meta) == expected_message def test_format_suggested_follow_up(): suggestions = [ "Please use 'aqt list-tool mac desktop --extensions ' to list valid extensions.", "Please use 'aqt list-tool mac desktop' to check what tools are available.", ] expected = ( "==============================Suggested follow-up:==============================\n" "* Please use 'aqt list-tool mac desktop --extensions ' to list valid extensions.\n" "* Please use 'aqt list-tool mac desktop' to check what tools are available." ) ex = AqtException("msg", suggested_action=suggestions) assert ex._format_suggested_follow_up() == expected def test_format_suggested_follow_up_empty(): ex = AqtException("msg", suggested_action=[]) assert format(ex) == "msg" @pytest.mark.parametrize( "meta, expect", ( ( MetadataFactory(ArchiveId("qt", "mac", "desktop"), spec=SimpleSpec("5.42")), "qt/mac/desktop with spec 5.42", ), ( MetadataFactory(ArchiveId("qt", "mac", "desktop", "wasm"), spec=SimpleSpec("5.42")), "qt/mac/desktop/wasm with spec 5.42", ), (MetadataFactory(ArchiveId("qt", "mac", "desktop")), "qt/mac/desktop"), ( MetadataFactory(ArchiveId("qt", "mac", "desktop", "wasm")), "qt/mac/desktop/wasm", ), ), ) def test_list_describe_filters(meta: MetadataFactory, expect: str): assert meta.describe_filters() == expect @pytest.mark.parametrize( "archive_id, spec, version_str, expect", ( (mac_qt, None, "5.12.42", Version("5.12.42")), ( mac_qt, None, "not a version", CliInputError("Invalid version string: 'not a version'"), ), (mac_qt, SimpleSpec("5"), "latest", Version("5.15.2")), ( mac_qt, SimpleSpec("5.0"), "latest", CliInputError("There is no latest version of Qt with the criteria 'qt/mac/desktop with spec 5.0'"), ), ), ) def test_list_to_version(monkeypatch, archive_id, spec, version_str, expect): _html = (Path(__file__).parent / "data" / "mac-desktop.html").read_text("utf-8") monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: _html) if isinstance(expect, Exception): with pytest.raises(CliInputError) as error: MetadataFactory(archive_id, spec=spec)._to_version(version_str) assert error.type == CliInputError assert str(expect) == str(error.value) else: assert MetadataFactory(archive_id, spec=spec)._to_version(version_str) == expect def test_list_fetch_tool_by_simple_spec(monkeypatch): update_xml = (Path(__file__).parent / "data" / "windows-desktop-tools_vcredist-update.xml").read_text("utf-8") monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: update_xml) expect_json = (Path(__file__).parent / "data" / "windows-desktop-tools_vcredist-expect.json").read_text("utf-8") expected = json.loads(expect_json)["modules_data"] def check(actual, expect): for key in ( "Description", "DisplayName", "DownloadableArchives", "ReleaseDate", "SHA1", "Version", "Virtual", ): assert actual[key] == expect[key] meta = MetadataFactory(ArchiveId("tools", "windows", "desktop")) check( meta.fetch_tool_by_simple_spec(tool_name="tools_vcredist", simple_spec=SimpleSpec("2011")), expected["qt.tools.vcredist"], ) check( meta.fetch_tool_by_simple_spec(tool_name="tools_vcredist", simple_spec=SimpleSpec("2014")), expected["qt.tools.vcredist_msvc2013_x86"], ) nonexistent = meta.fetch_tool_by_simple_spec(tool_name="tools_vcredist", simple_spec=SimpleSpec("1970")) assert nonexistent is None # Simulate a broken Updates.xml file, with invalid versions highest_module_info = MetadataFactory.choose_highest_version_in_spec( all_tools_data={"some_module": {"Version": "not_a_version"}}, simple_spec=SimpleSpec("*"), ) assert highest_module_info is None @pytest.mark.parametrize( "columns, expect", ( ( 120, ( "Tool Variant Name Version Release Date Display Name " " Description \n" "=====================================================================================" "===================================\n" "qt.tools.ifw.41 4.1.1-202105261132 2021-05-26 Qt Installer Framework 4.1 " "The Qt Installer Framework provides\n" " " "a set of tools and utilities to \n" " " "create installers for the supported\n" " " "desktop Qt platforms: Linux, \n" " " "Microsoft Windows, and macOS. \n" ), ), ( 80, "Tool Variant Name Version Release Date\n" "=====================================================\n" "qt.tools.ifw.41 4.1.1-202105261132 2021-05-26 \n", ), ( 0, "Tool Variant Name Version Release Date Display Name " " Descriptio" "n \n" "=====================================================================================" "=====================================================================================" "=============================================================================\n" "qt.tools.ifw.41 4.1.1-202105261132 2021-05-26 Qt Installer Framework 4.1 " "The Qt Installer Framework provides a set of tools and utilities to create installers" " for the supported desktop Qt platforms: Linux, Microsoft Windows, and macOS.\n", ), ), ) def test_show_list_tools_long_ifw(capsys, monkeypatch, columns, expect): update_xml = (Path(__file__).parent / "data" / "mac-desktop-tools_ifw-update.xml").read_text("utf-8") monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: update_xml) monkeypatch.setattr(shutil, "get_terminal_size", lambda fallback: os.terminal_size((columns, 24))) meta = MetadataFactory( ArchiveId("tools", "mac", "desktop"), tool_name="tools_ifw", is_long_listing=True, ) show_list(meta) out, err = capsys.readouterr() sys.stdout.write(out) sys.stderr.write(err) assert out == expect def test_show_list_versions(monkeypatch, capsys): _html = (Path(__file__).parent / "data" / "mac-desktop.html").read_text("utf-8") monkeypatch.setattr(MetadataFactory, "fetch_http", lambda *args: _html) expect_file = Path(__file__).parent / "data" / "mac-desktop-expect.json" expected = "\n".join(json.loads(expect_file.read_text("utf-8"))["qt"]["qt"]) + "\n" show_list(MetadataFactory(mac_qt)) out, err = capsys.readouterr() assert out == expected def test_show_list_tools(monkeypatch, capsys): page = (Path(__file__).parent / "data" / "mac-desktop.html").read_text("utf-8") monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: page) expect_file = Path(__file__).parent / "data" / "mac-desktop-expect.json" expect = "\n".join(json.loads(expect_file.read_text("utf-8"))["tools"]) + "\n" meta = MetadataFactory(ArchiveId("tools", "mac", "desktop")) show_list(meta) out, err = capsys.readouterr() sys.stdout.write(out) sys.stderr.write(err) assert out == expect def test_show_list_empty(monkeypatch, capsys): monkeypatch.setattr(MetadataFactory, "getList", lambda self: []) meta = MetadataFactory(ArchiveId("tools", "mac", "desktop")) with pytest.raises(EmptyMetadata) as error: show_list(meta) assert error.type == EmptyMetadata assert format(error.value) == "No tools available for this request." def test_show_list_bad_connection(monkeypatch, capsys): for exception_class, error_msg in ( (ArchiveConnectionError, "Failure to connect to some url"), (ArchiveDownloadError, "Failure to download some xml file"), ): def mock(self): raise exception_class(error_msg) monkeypatch.setattr(MetadataFactory, "getList", mock) meta = MetadataFactory(mac_wasm, spec=SimpleSpec("<5.9")) with pytest.raises(exception_class) as error: show_list(meta) assert error.type == exception_class assert format(error.value) == ( f"{error_msg}\n" "==============================Suggested follow-up:==============================\n" "* Please use 'aqt list-qt mac desktop --extensions ' to list valid extensions.\n" "* Please use 'aqt list-qt mac desktop' to check that versions of qt exist within the spec '<5.9'." ) def fetch_expected_tooldata(json_filename: str) -> ToolData: text = (Path(__file__).parent / "data" / json_filename).read_text("utf-8") raw_tooldata: List[List[str]] = json.loads(text)["long_listing"] keys = ("Version", "ReleaseDate", "DisplayName", "Description") tools: Dict[str, Dict[str, str]] = {} for variant_name, *values in raw_tooldata: assert len(keys) == len(values) tools[variant_name] = {k: v for k, v in zip(keys, values)} return ToolData(tools) @pytest.mark.parametrize("host, target, tool_name", (("mac", "desktop", "tools_cmake"),)) def test_list_tool_cli(monkeypatch, capsys, host: str, target: str, tool_name: str): html_file = f"{host}-{target}.html" xml_file = f"{host}-{target}-{tool_name}-update.xml" html_expect = f"{host}-{target}-expect.json" xml_expect = f"{host}-{target}-{tool_name}-expect.json" htmltext, xmltext, htmljson, xmljson = [ (Path(__file__).parent / "data" / filename).read_text("utf-8") for filename in (html_file, xml_file, html_expect, xml_expect) ] expected_tools = set(json.loads(htmljson)["tools"]) xml_data = json.loads(xmljson) expected_tool_modules = set(xml_data["modules"]) def _mock_fetch_http(_, rest_of_url: str) -> str: if not rest_of_url.endswith("Updates.xml"): return htmltext folder = urlparse(rest_of_url).path.split("/")[-2] assert folder.startswith("tools_") return xmltext monkeypatch.setattr(MetadataFactory, "fetch_http", _mock_fetch_http) cli = Cli() cli.run(["list-tool", host, target]) out, err = capsys.readouterr() output_set = set(out.strip().split()) assert output_set == expected_tools cli.run(["list-tool", host, target, tool_name]) out, err = capsys.readouterr() output_set = set(out.strip().split()) assert output_set == expected_tool_modules # Test abbreviated tool name: "aqt list-tool mac desktop ifw" assert tool_name.startswith("tools_") short_tool_name = tool_name[6:] cli.run(["list-tool", host, target, short_tool_name]) out, err = capsys.readouterr() output_set = set(out.strip().split()) assert output_set == expected_tool_modules cli.run(["list-tool", host, target, tool_name, "-l"]) out, err = capsys.readouterr() expected_tooldata = format(fetch_expected_tooldata(xml_expect)) assert out.strip() == expected_tooldata def test_fetch_http_ok(monkeypatch): monkeypatch.setattr("aqt.metadata.getUrl", lambda **kwargs: "some_html_content") assert MetadataFactory.fetch_http("some_url") == "some_html_content" def test_fetch_http_failover(monkeypatch): urls_requested = set() def _mock(url, **kwargs): urls_requested.add(url) if len(urls_requested) <= 1: raise ArchiveDownloadError() return "some_html_content" monkeypatch.setattr("aqt.metadata.getUrl", _mock) # Require that the first attempt failed, but the second did not assert MetadataFactory.fetch_http("some_url") == "some_html_content" assert len(urls_requested) == 2 def test_fetch_http_download_error(monkeypatch): urls_requested = set() def _mock(url, **kwargs): urls_requested.add(url) raise ArchiveDownloadError() monkeypatch.setattr("aqt.metadata.getUrl", _mock) with pytest.raises(ArchiveDownloadError) as e: MetadataFactory.fetch_http("some_url") assert e.type == ArchiveDownloadError # Require that a fallback url was tried assert len(urls_requested) == 2 def test_fetch_http_conn_error(monkeypatch): urls_requested = set() def _mock(url, **kwargs): urls_requested.add(url) raise ArchiveConnectionError() monkeypatch.setattr("aqt.metadata.getUrl", _mock) with pytest.raises(ArchiveConnectionError) as e: MetadataFactory.fetch_http("some_url") assert e.type == ArchiveConnectionError # Require that a fallback url was tried assert len(urls_requested) == 2