files: #!/usr/bin/env python
  #
  # Copyright (C) 2021 David Dalcino
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  import binascii
  import itertools
  import operator
  import posixpath
  import random
  import re
  import shutil
  from logging import getLogger
  from typing import Callable, Dict, Generator, Iterable, Iterator, List, Optional, Tuple, Union
  from xml.etree import ElementTree as ElementTree
  
  import bs4
  from semantic_version import SimpleSpec as SemanticSimpleSpec
  from semantic_version import Version as SemanticVersion
  from texttable import Texttable
  
  from aqt.exceptions import ArchiveConnectionError, ArchiveDownloadError, ArchiveListError, CliInputError, EmptyMetadata
  from aqt.helper import Settings, get_hash, getUrl, xml_to_modules
  
  
  class SimpleSpec(SemanticSimpleSpec):
      pass
  
      @staticmethod
      def usage() -> str:
          return (
              "See documentation at: "
              "https://python-semanticversion.readthedocs.io/en/latest/reference.html#semantic_version.SimpleSpec\n"
              "Examples:\n"
              '* "*": matches everything\n'
              '* "5": matches every version with major=5\n'
              '* "5.6": matches every version beginning with 5.6\n'
              '* "5.*.3": matches versions with major=5 and patch=3'
          )
  
  
  class Version(SemanticVersion):
      """Override semantic_version.Version class
      to accept Qt versions and tools versions
      If the version ends in `-preview`, the version is treated as a preview release.
      """
  
      def __init__(
          self,
          version_string=None,
          major=None,
          minor=None,
          patch=None,
          prerelease=None,
          build=None,
          partial=False,
      ):
          if version_string is None:
              super(Version, self).__init__(
                  version_string=None,
                  major=major,
                  minor=minor,
                  patch=patch,
                  prerelease=prerelease,
                  build=build,
                  partial=partial,
              )
              return
          # test qt versions
          match = re.match(r"^(\d+)\.(\d+)(\.(\d+)|-preview)$", version_string)
          if not match:
              # bad input
              raise ValueError("Invalid version string: '{}'".format(version_string))
          major, minor, end, patch = match.groups()
          is_preview = end == "-preview"
          super(Version, self).__init__(
              major=int(major),
              minor=int(minor),
              patch=int(patch) if patch else 0,
              prerelease=("preview",) if is_preview else None,
          )
  
      def __str__(self):
          if self.prerelease:
              return "{}.{}-preview".format(self.major, self.minor)
          return super(Version, self).__str__()
  
      @classmethod
      def permissive(cls, version_string: str):
          """Converts a version string with dots (5.X.Y, etc) into a semantic version.
          If the version omits either the patch or minor versions, they will be filled in with zeros,
          and the remaining version string becomes part of the prerelease component.
          If the version cannot be converted to a Version, a ValueError is raised.
  
          This is intended to be used on Version tags in an Updates.xml file.
  
          '1.33.1-202102101246' => Version('1.33.1-202102101246')
          '1.33-202102101246' => Version('1.33.0-202102101246')    # tools_conan
          '2020-05-19-1' => Version('2020.0.0-05-19-1')            # tools_vcredist
          """
  
          match = re.match(r"^(\d+)(\.(\d+)(\.(\d+))?)?(-(.+))?$", version_string)
          if not match:
              raise ValueError("Invalid version string: '{}'".format(version_string))
          major, dot_minor, minor, dot_patch, patch, hyphen_build, build = match.groups()
          return cls(
              major=int(major),
              minor=int(minor) if minor else 0,
              patch=int(patch) if patch else 0,
              build=(build,) if build else None,
          )
  
  
  class Versions:
      def __init__(
          self,
          versions: Union[None, Version, Iterable[Tuple[int, Iterable[Version]]]],
      ):
          if versions is None:
              self.versions = list()
          elif isinstance(versions, Version):
              self.versions = [[versions]]
          else:
              self.versions: List[List[Version]] = [list(versions_iterator) for _, versions_iterator in versions]
  
      def __str__(self) -> str:
          return str(self.versions)
  
      def __format__(self, format_spec) -> str:
          if format_spec == "":
              return "\n
  
  [... the rest is omitted ...]
files: aqt/metadata.py
files: #!/usr/bin/env python
  #
  # Copyright (C) 2021 David Dalcino
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
  # this software and associated documentation files (the "Software"), to deal in
  # the Software without restriction, including without limitation the rights to
  # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  # the Software, and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in all
  # copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  import binascii
  import itertools
  import operator
  import posixpath
  import random
  import re
  import shutil
  from logging import getLogger
  from typing import Callable, Dict, Generator, Iterable, Iterator, List, Optional, Tuple, Union
  from xml.etree import ElementTree as ElementTree
  
  import bs4
  from semantic_version import SimpleSpec as SemanticSimpleSpec
  from semantic_version import Version as SemanticVersion
  from texttable import Texttable
  
  from aqt.exceptions import ArchiveConnectionError, ArchiveDownloadError, ArchiveListError, CliInputError, EmptyMetadata
  from aqt.helper import Settings, get_hash, getUrl, xml_to_modules
  
  
  class SimpleSpec(SemanticSimpleSpec):
      pass
  
      @staticmethod
      def usage() -> str:
          return (
              "See documentation at: "
              "https://python-semanticversion.readthedocs.io/en/latest/reference.html#semantic_version.SimpleSpec\n"
              "Examples:\n"
              '* "*": matches everything\n'
              '* "5": matches every version with major=5\n'
              '* "5.6": matches every version beginning with 5.6\n'
              '* "5.*.3": matches versions with major=5 and patch=3'
          )
  
  
  class Version(SemanticVersion):
      """Override semantic_version.Version class
      to accept Qt versions and tools versions
      If the version ends in `-preview`, the version is treated as a preview release.
      """
  
      def __init__(
          self,
          version_string=None,
          major=None,
          minor=None,
          patch=None,
          prerelease=None,
          build=None,
          partial=False,
      ):
          if version_string is None:
              super(Version, self).__init__(
                  version_string=None,
                  major=major,
                  minor=minor,
                  patch=patch,
                  prerelease=prerelease,
                  build=build,
                  partial=partial,
              )
              return
          # test qt versions
          match = re.match(r"^(\d+)\.(\d+)(\.(\d+)|-preview)$", version_string)
          if not match:
              # bad input
              raise ValueError("Invalid version string: '{}'".format(version_string))
          major, minor, end, patch = match.groups()
          is_preview = end == "-preview"
          super(Version, self).__init__(
              major=int(major),
              minor=int(minor),
              patch=int(patch) if patch else 0,
              prerelease=("preview",) if is_preview else None,
          )
  
      def __str__(self):
          if self.prerelease:
              return "{}.{}-preview".format(self.major, self.minor)
          return super(Version, self).__str__()
  
      @classmethod
      def permissive(cls, version_string: str):
          """Converts a version string with dots (5.X.Y, etc) into a semantic version.
          If the version omits either the patch or minor versions, they will be filled in with zeros,
          and the remaining version string becomes part of the prerelease component.
          If the version cannot be converted to a Version, a ValueError is raised.
  
          This is intended to be used on Version tags in an Updates.xml file.
  
          '1.33.1-202102101246' => Version('1.33.1-202102101246')
          '1.33-202102101246' => Version('1.33.0-202102101246')    # tools_conan
          '2020-05-19-1' => Version('2020.0.0-05-19-1')            # tools_vcredist
          """
  
          match = re.match(r"^(\d+)(\.(\d+)(\.(\d+))?)?(-(.+))?$", version_string)
          if not match:
              raise ValueError("Invalid version string: '{}'".format(version_string))
          major, dot_minor, minor, dot_patch, patch, hyphen_build, build = match.groups()
          return cls(
              major=int(major),
              minor=int(minor) if minor else 0,
              patch=int(patch) if patch else 0,
              build=(build,) if build else None,
          )
  
  
  class Versions:
      def __init__(
          self,
          versions: Union[None, Version, Iterable[Tuple[int, Iterable[Version]]]],
      ):
          if versions is None:
              self.versions = list()
          elif isinstance(versions, Version):
              self.versions = [[versions]]
          else:
              self.versions: List[List[Version]] = [list(versions_iterator) for _, versions_iterator in versions]
  
      def __str__(self) -> str:
          return str(self.versions)
  
      def __format__(self, format_spec) -> str:
          if format_spec == "":
              return "\n
  
  [... the rest is omitted ...]
files: aqt/metadata.py
files: import re
  import sys
  from pathlib import Path
  from typing import Optional
  
  import pytest
  
  from aqt.exceptions import CliInputError
  from aqt.installer import Cli
  from aqt.metadata import MetadataFactory, SimpleSpec, Version
  
  
  def expected_help(actual, prefix=None):
      expected = (
          "usage: aqt [-h] [-c CONFIG]\n"
          "           {install-qt,install-tool,install-doc,install-example,install-src,"
          "list-qt,list-tool,list-doc,list-example,list-src,"
          "install,tool,doc,examples,src,help,version}\n"
          "           ...\n"
          "\n"
          "Another unofficial Qt Installer.\n"
          "aqt helps you install Qt SDK, tools, examples and others\n"
          "\n"
          "option",
          "  -h, --help            show this help message and exit\n"
          "  -c CONFIG, --config CONFIG\n"
          "                        Configuration ini file.\n"
          "\n"
          "subcommands:\n"
          "  aqt accepts several subcommands:\n"
          "  install-* subcommands are commands that install components\n"
          "  list-* subcommands are commands that show available components\n"
          "  \n"
          "  commands {install|tool|src|examples|doc} are deprecated and marked for "
          "removal\n"
          "\n"
          "  {install-qt,install-tool,install-doc,install-example,install-src,list-qt,"
          "list-tool,list-doc,list-example,list-src,"
          "install,tool,doc,examples,src,help,version}\n"
          "                        Please refer to each help message by using '--help' "
          "with each subcommand\n",
      )
      if prefix is not None:
          return actual.startswith(prefix + expected[0]) and actual.endswith(expected[1])
      return actual.startswith(expected[0]) and actual.endswith(expected[1])
  
  
  def test_cli_help(capsys):
      cli = Cli()
      cli.run(["help"])
      out, err = capsys.readouterr()
      assert expected_help(out)
  
  
  def test_cli_check_module():
      cli = Cli()
      cli._setup_settings()
      assert cli._check_modules_arg("5.11.3", ["qtcharts", "qtwebengine"])
      assert not cli._check_modules_arg("5.7", ["not_exist"])
      assert cli._check_modules_arg("5.14.0", None)
      assert not cli._check_modules_arg("5.15.0", ["Unknown"])
  
  
  def test_cli_check_combination():
      cli = Cli()
      cli._setup_settings()
      assert cli._check_qt_arg_combination("5.11.3", "linux", "desktop", "gcc_64")
      assert cli._check_qt_arg_combination("5.11.3", "mac", "desktop", "clang_64")
      assert not cli._check_qt_arg_combination("5.14.0", "android", "desktop", "clang_64")
  
  
  def test_cli_check_version():
      cli = Cli()
      cli._setup_settings()
      assert cli._check_qt_arg_versions("5.12.0")
      assert not cli._check_qt_arg_versions("5.12")
  
  
  @pytest.mark.parametrize(
      "host, target, arch, version_or_spec, expected_version, is_bad_spec",
      (
          ("windows", "desktop", "wasm_32", "6.1", None, False),
          ("windows", "desktop", "wasm_32", "5.12", None, False),
          ("windows", "desktop", "wasm_32", "5.13", Version("5.13.2"), False),
          ("windows", "desktop", "wasm_32", "5", Version("5.15.2"), False),
          ("windows", "desktop", "wasm_32", "<5.14.5", Version("5.14.2"), False),
          ("windows", "desktop", "mingw32", "6.0", Version("6.0.3"), False),
          ("windows", "winrt", "mingw32", "6", None, False),
          ("windows", "winrt", "mingw32", "bad spec", None, True),
          ("windows", "android", "android_x86", "6", Version("6.1.0"), False),
          ("windows", "desktop", "android_x86", "6", Version("6.1.0"), False),  # does not validate arch
          ("windows", "desktop", "android_fake", "6", Version("6.1.0"), False),  # does not validate arch
      ),
  )
  def test_cli_determine_qt_version(
      monkeypatch, host, target, arch, version_or_spec: str, expected_version: Version, is_bad_spec: bool
  ):
      _html = (Path(__file__).parent / "data" / f"{host}-{target}.html").read_text("utf-8")
      monkeypatch.setattr(MetadataFactory, "fetch_http", lambda *args, **kwargs: _html)
      cli = Cli()
      cli._setup_settings()
  
      if is_bad_spec:
          with pytest.raises(CliInputError) as e:
              Cli._determine_qt_version(version_or_spec, host, target, arch)
          assert e.type == CliInputError
          assert format(e.value) == f"Invalid version or SimpleSpec: '{version_or_spec}'\n" + SimpleSpec.usage()
      elif not expected_version:
          with pytest.raises(CliInputError) as e:
              Cli._determine_qt_version
  
  [... the rest is omitted ...]
files: tests/test_cli.py
files: import re
  import sys
  from pathlib import Path
  from typing import Optional
  
  import pytest
  
  from aqt.exceptions import CliInputError
  from aqt.installer import Cli
  from aqt.metadata import MetadataFactory, SimpleSpec, Version
  
  
  def expected_help(actual, prefix=None):
      expected = (
          "usage: aqt [-h] [-c CONFIG]\n"
          "           {install-qt,install-tool,install-doc,install-example,install-src,"
          "list-qt,list-tool,list-doc,list-example,list-src,"
          "install,tool,doc,examples,src,help,version}\n"
          "           ...\n"
          "\n"
          "Another unofficial Qt Installer.\n"
          "aqt helps you install Qt SDK, tools, examples and others\n"
          "\n"
          "option",
          "  -h, --help            show this help message and exit\n"
          "  -c CONFIG, --config CONFIG\n"
          "                        Configuration ini file.\n"
          "\n"
          "subcommands:\n"
          "  aqt accepts several subcommands:\n"
          "  install-* subcommands are commands that install components\n"
          "  list-* subcommands are commands that show available components\n"
          "  \n"
          "  commands {install|tool|src|examples|doc} are deprecated and marked for "
          "removal\n"
          "\n"
          "  {install-qt,install-tool,install-doc,install-example,install-src,list-qt,"
          "list-tool,list-doc,list-example,list-src,"
          "install,tool,doc,examples,src,help,version}\n"
          "                        Please refer to each help message by using '--help' "
          "with each subcommand\n",
      )
      if prefix is not None:
          return actual.startswith(prefix + expected[0]) and actual.endswith(expected[1])
      return actual.startswith(expected[0]) and actual.endswith(expected[1])
  
  
  def test_cli_help(capsys):
      cli = Cli()
      cli.run(["help"])
      out, err = capsys.readouterr()
      assert expected_help(out)
  
  
  def test_cli_check_module():
      cli = Cli()
      cli._setup_settings()
      assert cli._check_modules_arg("5.11.3", ["qtcharts", "qtwebengine"])
      assert not cli._check_modules_arg("5.7", ["not_exist"])
      assert cli._check_modules_arg("5.14.0", None)
      assert not cli._check_modules_arg("5.15.0", ["Unknown"])
  
  
  def test_cli_check_combination():
      cli = Cli()
      cli._setup_settings()
      assert cli._check_qt_arg_combination("5.11.3", "linux", "desktop", "gcc_64")
      assert cli._check_qt_arg_combination("5.11.3", "mac", "desktop", "clang_64")
      assert not cli._check_qt_arg_combination("5.14.0", "android", "desktop", "clang_64")
  
  
  def test_cli_check_version():
      cli = Cli()
      cli._setup_settings()
      assert cli._check_qt_arg_versions("5.12.0")
      assert not cli._check_qt_arg_versions("5.12")
  
  
  @pytest.mark.parametrize(
      "host, target, arch, version_or_spec, expected_version, is_bad_spec",
      (
          ("windows", "desktop", "wasm_32", "6.1", None, False),
          ("windows", "desktop", "wasm_32", "5.12", None, False),
          ("windows", "desktop", "wasm_32", "5.13", Version("5.13.2"), False),
          ("windows", "desktop", "wasm_32", "5", Version("5.15.2"), False),
          ("windows", "desktop", "wasm_32", "<5.14.5", Version("5.14.2"), False),
          ("windows", "desktop", "mingw32", "6.0", Version("6.0.3"), False),
          ("windows", "winrt", "mingw32", "6", None, False),
          ("windows", "winrt", "mingw32", "bad spec", None, True),
          ("windows", "android", "android_x86", "6", Version("6.1.0"), False),
          ("windows", "desktop", "android_x86", "6", Version("6.1.0"), False),  # does not validate arch
          ("windows", "desktop", "android_fake", "6", Version("6.1.0"), False),  # does not validate arch
      ),
  )
  def test_cli_determine_qt_version(
      monkeypatch, host, target, arch, version_or_spec: str, expected_version: Version, is_bad_spec: bool
  ):
      _html = (Path(__file__).parent / "data" / f"{host}-{target}.html").read_text("utf-8")
      monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: _html)
      cli = Cli()
      cli._setup_settings()
  
      if is_bad_spec:
          with pytest.raises(CliInputError) as e:
              Cli._determine_qt_version(version_or_spec, host, target, arch)
          assert e.type == CliInputError
          assert format(e.value) == f"Invalid version or SimpleSpec: '{version_or_spec}'\n" + SimpleSpec.usage()
      elif not expected_version:
          with pytest.raises(CliInputError) as e:
              Cli._determine_qt_version(version
  
  [... the rest is omitted ...]
files: tests/test_cli.py
files: import hashlib
  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 *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
         
  
  [... the rest is omitted ...]
files: tests/test_list.py
files: import hashlib
  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_
  
  [... the rest is omitted ...]
files: tests/test_list.py