mirror of
https://github.com/miurahr/aqtinstall.git
synced 2025-12-18 05:04:38 +03:00
This change allows MetadataFactory.iterate_folders to recognize when MetadataFactory has retrieved a malformed HTML file, or an HTML file that does not contain the table and rows that it expects to see. If the URL is correct, it should retrieve the right page, but this is not guaranteed, and MetadataFactory should be able to recover and generate an appropriate error message. One possible situation where this kind of error might occur could be when the user is connected to a public WIFI network, but not logged in. The router would redirect aqt to the network login page, and aqt would have no way of knowing that it did not receive the correct page. Aqt should be resilient to this kind of error.
1021 lines
39 KiB
Python
1021 lines
39 KiB
Python
import hashlib
|
|
import json
|
|
import os
|
|
import posixpath
|
|
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 *args, **kwargs: _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(
|
|
"html_doc",
|
|
(
|
|
"<html><body>Login to my public WIFI network:<form>...</form></body></html>",
|
|
"<html>malformed-html/",
|
|
),
|
|
)
|
|
def test_list_bad_html(monkeypatch, html_doc: str):
|
|
monkeypatch.setattr(MetadataFactory, "fetch_http", lambda *args, **kwargs: html_doc)
|
|
archive_id = ArchiveId("qt", "linux", "desktop")
|
|
expected_url = posixpath.join(Settings.baseurl, archive_id.to_url())
|
|
expected_exception = ArchiveConnectionError(
|
|
f"Failed to retrieve the expected HTML page at {expected_url}",
|
|
suggested_action=[
|
|
"Check your network connection.",
|
|
f"Make sure that you can access {expected_url} in your web browser.",
|
|
],
|
|
)
|
|
|
|
with pytest.raises(ArchiveConnectionError) as e:
|
|
MetadataFactory(archive_id).fetch_versions()
|
|
assert e.type == ArchiveConnectionError
|
|
assert format(e.value) == format(expected_exception)
|
|
|
|
|
|
@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.fixture
|
|
def win_5152_sde_xml_file() -> str:
|
|
return (Path(__file__).parent / "data" / "windows-5152-src-doc-example-update.xml").read_text("utf-8")
|
|
|
|
|
|
def win_5152_sde_expected(cmd_type: str, query_type: str) -> Set[str]:
|
|
assert cmd_type in ("src", "doc", "examples")
|
|
assert query_type in ("archives", "modules")
|
|
_json = json.loads((Path(__file__).parent / "data/windows-5152-src-doc-example-expect.json").read_text("utf-8"))
|
|
return set(_json[cmd_type][query_type])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"cmd_type, host, version, expected",
|
|
[
|
|
(
|
|
_cmd_type,
|
|
"windows",
|
|
"5.15.2",
|
|
win_5152_sde_expected(_cmd_type, "archives"),
|
|
)
|
|
for _cmd_type in ("src", "doc", "examples")
|
|
],
|
|
)
|
|
def test_list_src_doc_examples_archives(
|
|
monkeypatch, win_5152_sde_xml_file, cmd_type: str, host: str, version: str, expected: Set[str]
|
|
):
|
|
monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: win_5152_sde_xml_file)
|
|
|
|
archive_id = ArchiveId("qt", host, "desktop", "src_doc_examples")
|
|
archives = set(MetadataFactory(archive_id).fetch_archives_sde(cmd_type, Version(version)))
|
|
assert archives == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"cmd_type, host, version, expected",
|
|
[
|
|
(
|
|
_cmd_type,
|
|
"windows",
|
|
"5.15.2",
|
|
win_5152_sde_expected(_cmd_type, "modules"),
|
|
)
|
|
for _cmd_type in ("doc", "examples")
|
|
],
|
|
)
|
|
def test_list_src_doc_examples_modules(
|
|
monkeypatch, win_5152_sde_xml_file, cmd_type: str, host: str, version: str, expected: Set[str]
|
|
):
|
|
monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: win_5152_sde_xml_file)
|
|
|
|
archive_id = ArchiveId("qt", host, "desktop", "src_doc_examples")
|
|
modules = set(MetadataFactory(archive_id).fetch_modules_sde(cmd_type, Version(version)))
|
|
assert modules == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"command, expected",
|
|
(
|
|
("list-src windows 5.15.2", win_5152_sde_expected("src", "archives")),
|
|
("list-doc windows 5.15.2", win_5152_sde_expected("doc", "archives")),
|
|
("list-example windows 5.15.2", win_5152_sde_expected("examples", "archives")),
|
|
("list-doc windows 5.15.2 --modules", win_5152_sde_expected("doc", "modules")),
|
|
("list-example windows 5.15.2 --modules", win_5152_sde_expected("examples", "modules")),
|
|
),
|
|
)
|
|
def test_list_src_doc_examples_cli(monkeypatch, capsys, win_5152_sde_xml_file, command: str, expected: Set[str]):
|
|
monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: win_5152_sde_xml_file)
|
|
|
|
cli = Cli()
|
|
assert 0 == cli.run(command.split())
|
|
out, err = capsys.readouterr()
|
|
assert not err
|
|
out_set = set(out.strip().split())
|
|
assert out_set == expected
|
|
|
|
|
|
@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 = "<Updates><PackageUpdate><badname></badname></PackageUpdate></Updates>"
|
|
xml_empty_name = "<Updates><PackageUpdate><Name></Name></PackageUpdate></Updates>"
|
|
xml_broken = "<Updates></PackageUpdate><PackageUpdate></Updates><Name></Name>"
|
|
|
|
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, *args, **kwargs: 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 <QT_VERSION>' to list valid extensions."
|
|
wrong_arch_msg = "Please use 'aqt list-qt mac desktop --arch <QT_VERSION>' 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 <QT_VERSION>' to show architectures available.",
|
|
"Please use 'aqt list-qt mac desktop --modules <QT_VERSION>' 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 <QT_VERSION>' 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 <QT_VERSION>' 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 <QT_VERSION>' 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 <QT_VERSION>' 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 *args, **kwargs: _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 *args, **kwargs: 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 <QT_VERSION>' 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, *args, **kwargs: 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.get_hash", lambda *args, **kwargs: hashlib.sha256(b"some_html_content").hexdigest())
|
|
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.get_hash", lambda *args, **kwargs: hashlib.sha256(b"some_html_content").hexdigest())
|
|
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
|
|
|
|
|
|
@pytest.mark.parametrize("exception_on_error", (ArchiveDownloadError, ArchiveConnectionError))
|
|
def test_fetch_http_download_error(monkeypatch, exception_on_error):
|
|
urls_requested = set()
|
|
|
|
def _mock(url, **kwargs):
|
|
urls_requested.add(url)
|
|
raise exception_on_error()
|
|
|
|
monkeypatch.setattr("aqt.metadata.get_hash", lambda *args, **kwargs: hashlib.sha256(b"some_html_content").hexdigest())
|
|
monkeypatch.setattr("aqt.metadata.getUrl", _mock)
|
|
with pytest.raises(exception_on_error) as e:
|
|
MetadataFactory.fetch_http("some_url")
|
|
assert e.type == exception_on_error
|
|
|
|
# Require that a fallback url was tried
|
|
assert len(urls_requested) == 2
|