diff --git a/aqt/metadata.py b/aqt/metadata.py index 4e159739..86e40575 100644 --- a/aqt/metadata.py +++ b/aqt/metadata.py @@ -185,22 +185,58 @@ def get_semantic_version(qt_ver: str, is_preview: bool) -> Optional[Version]: and patch gets all the rest. As of May 2021, the version strings at https://download.qt.io/online/qtsdkrepository conform to this pattern; they are not guaranteed to do so in the future. + As of December 2024, it can handle version strings like 6_7_3 as well. """ - if not qt_ver or any(not ch.isdigit() for ch in qt_ver): + if not qt_ver: return None + + # Handle versions with underscores (new format) + if "_" in qt_ver: + parts = qt_ver.split("_") + if not (2 <= len(parts) <= 3): + return None + + try: + version_parts = [int(p) for p in parts] + except ValueError: + return None + + major, minor = version_parts[:2] + patch = version_parts[2] if len(version_parts) > 2 else 0 + + if is_preview: + minor_patch_combined = int(f"{minor}{patch}") if patch > 0 else minor + return Version( + major=major, + minor=minor_patch_combined, + patch=0, + prerelease=("preview",), + ) + + return Version( + major=major, + minor=minor, + patch=patch, + ) + + # Handle traditional format (continuous digits) + if not qt_ver.isdigit(): + return None + if is_preview: return Version( - major=int(qt_ver[:1]), + major=int(qt_ver[0]), minor=int(qt_ver[1:]), patch=0, prerelease=("preview",), ) elif len(qt_ver) >= 4: - return Version(major=int(qt_ver[:1]), minor=int(qt_ver[1:3]), patch=int(qt_ver[3:])) + return Version(major=int(qt_ver[0]), minor=int(qt_ver[1:3]), patch=int(qt_ver[3:])) elif len(qt_ver) == 3: - return Version(major=int(qt_ver[:1]), minor=int(qt_ver[1:2]), patch=int(qt_ver[2:])) + return Version(major=int(qt_ver[0]), minor=int(qt_ver[1]), patch=int(qt_ver[2])) elif len(qt_ver) == 2: - return Version(major=int(qt_ver[:1]), minor=int(qt_ver[1:2]), patch=0) + return Version(major=int(qt_ver[0]), minor=int(qt_ver[1]), patch=0) + raise ValueError("Invalid version string '{}'".format(qt_ver)) diff --git a/tests/test_metadata.py b/tests/test_metadata.py new file mode 100644 index 00000000..5d97a714 --- /dev/null +++ b/tests/test_metadata.py @@ -0,0 +1,59 @@ +import pytest + +from aqt.metadata import Version, get_semantic_version + + +@pytest.mark.parametrize( + "input_version, is_preview, expected", + [ + # Test cases for non-preview versions + ("51212", False, Version("5.12.12")), + ("600", False, Version("6.0.0")), + ("6_7_3", False, Version("6.7.3")), + ("6_7", False, Version("6.7.0")), + # Test cases for preview versions + ("51212", True, Version("5.1212-preview")), + ("600", True, Version("6.0-preview")), + ("6_7_3", True, Version("6.73-preview")), + ("6_7", True, Version("6.7-preview")), + ], +) +def test_get_semantic_version_valid(input_version, is_preview, expected): + """ + Test the get_semantic_version function with valid inputs. + + Args: + input_version (str): Input version string to be converted + is_preview (bool): Flag indicating whether this is a preview version + expected (Version): Expected semantic version output + """ + result = get_semantic_version(input_version, is_preview) + assert ( + result == expected + ), f"Failed for input '{input_version}' with is_preview={is_preview}. Expected '{expected}', but got '{result}'" + + +@pytest.mark.parametrize( + "invalid_input, is_preview", + [ + ("", False), # Empty string + ("abc", False), # Non-numeric input + ("1_2_3_4", False), # Too many underscores + ("1_a_2", False), # Non-numeric parts + (None, False), # None input + ], +) +def test_get_semantic_version_returns_none(invalid_input, is_preview): + """ + Test cases where the function should return None. + """ + result = get_semantic_version(invalid_input, is_preview) + assert result is None, f"Expected None for invalid input '{invalid_input}', but got '{result}'" + + +def test_get_semantic_version_raises_value_error(): + """ + Test the specific case that raises ValueError - single digit version. + """ + with pytest.raises(ValueError, match="Invalid version string '1'"): + get_semantic_version("1", False)