Skip to content

Commit

Permalink
Fix version regex when querying veresions
Browse files Browse the repository at this point in the history
  • Loading branch information
tekktrik committed Mar 15, 2024
1 parent 3d54cdc commit 66678e7
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 59 deletions.
19 changes: 19 additions & 0 deletions circfirm/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"""

import enum
import re
from typing import Tuple


class Language(enum.Enum):
Expand Down Expand Up @@ -51,3 +53,20 @@ class Language(enum.Enum):
.replace(r"[language]", f"({_ALL_LANGUAGES_REGEX})")
.replace(r"[version]", _VALID_VERSIONS_CAPTURE)
)


def get_uf2_filename(board_id: str, version: str, language: str = "en_US") -> str:
"""Get the structured name for a specific board/version CircuitPython."""
return f"adafruit-circuitpython-{board_id}-{language}-{version}.uf2"


def parse_firmware_info(uf2_filename: str) -> Tuple[str, str]:
"""Get firmware info."""
regex_match = re.match(FIRMWARE_REGEX, uf2_filename)
if regex_match is None:
raise ValueError(
"Firmware information could not be determined from the filename"
)
version = regex_match[3]
language = regex_match[2]
return version, language
23 changes: 3 additions & 20 deletions circfirm/backend/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,11 @@
import circfirm.startup


def get_uf2_filename(board_id: str, version: str, language: str = "en_US") -> str:
"""Get the structured name for a specific board/version CircuitPython."""
return f"adafruit-circuitpython-{board_id}-{language}-{version}.uf2"


def get_uf2_filepath(
board_id: str, version: str, language: str = "en_US"
) -> pathlib.Path:
"""Get the path to a downloaded UF2 file."""
file = get_uf2_filename(board_id, version, language)
file = circfirm.backend.get_uf2_filename(board_id, version, language)
uf2_folder = get_board_folder(board_id)
return uf2_folder / file

Expand All @@ -46,7 +41,7 @@ def is_downloaded(board_id: str, version: str, language: str = "en_US") -> bool:

def download_uf2(board_id: str, version: str, language: str = "en_US") -> None:
"""Download a version of CircuitPython for a specific board."""
file = get_uf2_filename(board_id, version, language=language)
file = circfirm.backend.get_uf2_filename(board_id, version, language=language)
uf2_file = get_uf2_filepath(board_id, version, language=language)
url = f"https://downloads.circuitpython.org/bin/{board_id}/{language}/{file}"
response = requests.get(url)
Expand All @@ -70,7 +65,7 @@ def get_sorted_boards(board_id: Optional[str]) -> Dict[str, Dict[str, Set[str]]]
continue
board_folder_full = get_board_folder(board_folder)
for item in os.listdir(board_folder_full):
version, language = parse_firmware_info(item)
version, language = circfirm.backend.parse_firmware_info(item)
try:
version_set = set(versions[version])
version_set.add(language)
Expand All @@ -83,15 +78,3 @@ def get_sorted_boards(board_id: Optional[str]) -> Dict[str, Dict[str, Set[str]]]
sorted_versions[sorted_version] = versions[sorted_version]
boards[board_folder] = sorted_versions
return boards


def parse_firmware_info(uf2_filename: str) -> Tuple[str, str]:
"""Get firmware info."""
regex_match = re.match(circfirm.backend.FIRMWARE_REGEX, uf2_filename)
if regex_match is None:
raise ValueError(
"Firmware information could not be determined from the filename"
)
version = regex_match[3]
language = regex_match[2]
return version, language
14 changes: 8 additions & 6 deletions circfirm/backend/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from mypy_boto3_s3 import S3ServiceResource

import circfirm.backend
import circfirm.backend.cache

S3_CONFIG = botocore.client.Config(signature_version=botocore.UNSIGNED)
S3_RESOURCE: S3ServiceResource = boto3.resource("s3", config=S3_CONFIG)
Expand All @@ -32,18 +33,19 @@ def get_board_versions(
firmware_regex = circfirm.backend.FIRMWARE_REGEX_PATTERN.replace(
r"[board]", board_id
).replace(r"[language]", language)
version_regex = f"({regex})" if regex else circfirm.backend._VALID_VERSIONS_CAPTURE
version_regex = circfirm.backend._VALID_VERSIONS_CAPTURE
firmware_regex = firmware_regex.replace(r"[version]", version_regex)
s3_objects = BUCKET.objects.filter(Prefix=prefix)
versions = set()
for s3_object in s3_objects:
result = re.match(f"{prefix}/{firmware_regex}", s3_object.key)
if result:
try:
_ = packaging.version.Version(result[1])
versions.add(result[1])
except packaging.version.InvalidVersion:
pass
if regex:
firmware_filename = s3_object.key.split("/")[-1]
version, _ = circfirm.backend.parse_firmware_info(firmware_filename)
if not re.match(regex, version):
continue
versions.add(result[1])
return sorted(versions, key=packaging.version.Version, reverse=True)


Expand Down
41 changes: 41 additions & 0 deletions tests/backend/test_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-FileCopyrightText: 2024 Alec Delaney, for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""Tests the backend functionality.
Author(s): Alec Delaney
"""

import shutil

import pytest

import circfirm.backend.cache


def test_parse_firmware_info() -> None:
"""Tests the ability to get firmware information."""
board_id = "feather_m4_express"
language = "en_US"

# Test successful parsing
for version in ("8.0.0", "9.0.0-beta.2"):
try:
board_folder = circfirm.backend.cache.get_board_folder(board_id)
circfirm.backend.cache.download_uf2(board_id, version, language)
downloaded_filename = [file.name for file in board_folder.glob("*")][0]

(
parsed_version,
parsed_language,
) = circfirm.backend.parse_firmware_info(downloaded_filename)
assert parsed_version == version
assert parsed_language == language
finally:
# Clean up post tests
shutil.rmtree(board_folder)

# Test failed parsing
with pytest.raises(ValueError):
circfirm.backend.parse_firmware_info("cannotparse")
27 changes: 0 additions & 27 deletions tests/backend/test_backend_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,3 @@ def test_download_uf2() -> None:

# Clean up post tests
shutil.rmtree(expected_path.parent)


def test_parse_firmware_info() -> None:
"""Tests the ability to get firmware information."""
board_id = "feather_m4_express"
language = "en_US"

# Test successful parsing
for version in ("8.0.0", "9.0.0-beta.2"):
try:
board_folder = circfirm.backend.cache.get_board_folder(board_id)
circfirm.backend.cache.download_uf2(board_id, version, language)
downloaded_filename = [file.name for file in board_folder.glob("*")][0]

(
parsed_version,
parsed_language,
) = circfirm.backend.cache.parse_firmware_info(downloaded_filename)
assert parsed_version == version
assert parsed_language == language
finally:
# Clean up post tests
shutil.rmtree(board_folder)

# Test failed parsing
with pytest.raises(ValueError):
circfirm.backend.cache.parse_firmware_info("cannotparse")
43 changes: 41 additions & 2 deletions tests/backend/test_backend_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,27 @@
Author(s): Alec Delaney
"""

from collections import namedtuple
from functools import partial
from typing import List

import boto3.resources.collection
import pytest

import circfirm.backend.s3

MockS3Object = namedtuple("MockS3Object", ["key"])


def get_fake_s3_objects(
board: str, keys: List[str], *args, **kwargs
) -> List[MockS3Object]:
"""Create a set of fake S3 objects."""
template_link = (
f"bin/{board}/en_US/adafruit-circuitpython-{board}-en_US-[version].uf2"
)
return [MockS3Object(template_link.replace("[version]", key)) for key in keys]


def test_get_board_versions() -> None:
"""Tests getting firmware versions for a given board."""
Expand All @@ -22,5 +41,25 @@ def test_get_board_versions() -> None:
versions = circfirm.backend.s3.get_board_versions(board, language)
assert versions == expected_versions

# Chck that invalid versions are skipped for code coverage
_ = circfirm.backend.s3.get_board_versions(board, regex=r".*")

def test_get_board_versions_bad_version(monkeypatch: pytest.MonkeyPatch) -> None:
"""Tests getting only valid firmware versions for a given board."""
board = "test_board"
possible_versions = [
"6.2.0-beta.2",
"badversion",
"6.2.0-beta.1",
"6.2.0-beta.0",
]

monkeypatch.setattr(
boto3.resources.collection.CollectionManager,
"filter",
partial(get_fake_s3_objects, board, possible_versions),
)

expected_versions = possible_versions.copy()
del expected_versions[1]

versions = circfirm.backend.s3.get_board_versions(board)
assert versions == expected_versions
2 changes: 1 addition & 1 deletion tests/cli/test_cli_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def test_install_successful() -> None:
threading.Thread(target=tests.helpers.wait_and_set_bootloader).start()
result = RUNNER.invoke(cli, ["install", VERSION])
assert result.exit_code == 0
expected_uf2_filename = circfirm.backend.cache.get_uf2_filename(
expected_uf2_filename = circfirm.backend.get_uf2_filename(
"feather_m4_express", VERSION
)
expected_uf2_filepath = tests.helpers.get_mount_node(expected_uf2_filename)
Expand Down
15 changes: 15 additions & 0 deletions tests/cli/test_cli_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ def test_query_versions() -> None:
assert result.exit_code == 0
assert result.output == expected_output

result = RUNNER.invoke(
cli,
[
"query",
"versions",
board,
"--language",
language,
"--regex",
"^.*beta.1$",
],
)
assert result.exit_code == 0
assert result.output == "6.2.0-beta.1\n"


def test_query_latest() -> None:
"""Tests the ability to query the latest version of the firmware using the CLI."""
Expand Down
6 changes: 3 additions & 3 deletions tests/cli/test_cli_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def test_update() -> None:
threading.Thread(target=tests.helpers.wait_and_set_bootloader).start()
result = RUNNER.invoke(cli, ["update", "--language", "cs"])
expected_version = "6.1.0"
expected_uf2_filename = circfirm.backend.cache.get_uf2_filename(
expected_uf2_filename = circfirm.backend.get_uf2_filename(
"feather_m4_express", expected_version, language="cs"
)
expected_uf2_filepath = tests.helpers.get_mount_node(expected_uf2_filename)
Expand All @@ -56,7 +56,7 @@ def test_update_pre_release() -> None:
threading.Thread(target=tests.helpers.wait_and_set_bootloader).start()
result = RUNNER.invoke(cli, ["update", "--language", "cs", "--pre-release"])
expected_version = "6.2.0-beta.2"
expected_uf2_filename = circfirm.backend.cache.get_uf2_filename(
expected_uf2_filename = circfirm.backend.get_uf2_filename(
"feather_m4_express", expected_version, language="cs"
)
expected_uf2_filepath = tests.helpers.get_mount_node(expected_uf2_filename)
Expand All @@ -79,7 +79,7 @@ def test_update_bootloader_mode() -> None:
result = RUNNER.invoke(
cli, ["update", "--board-id", board_id, "--language", "cs"]
)
expected_uf2_filename = circfirm.backend.cache.get_uf2_filename(
expected_uf2_filename = circfirm.backend.get_uf2_filename(
board_id, expected_version, language="cs"
)
expected_uf2_filepath = tests.helpers.get_mount_node(expected_uf2_filename)
Expand Down

0 comments on commit 66678e7

Please sign in to comment.