Skip to content

Commit

Permalink
refactor CoreUtility to VersionComparator
Browse files Browse the repository at this point in the history
  • Loading branch information
feng-j678 committed Dec 10, 2024
1 parent 97b2fe1 commit 5ff57c6
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,37 @@
import re


class CoreUtility(object):
class VersionComparator(object):

@staticmethod
def compare_version(version_a, version_b):
def compare_version(self, version_a, version_b):
# type (str, str) -> int
""" Compare two versions with handling numeric and string parts, return -1 (less), +1 (greater), 0 (equal) """

def split_version_components(version):
""" Split a version into numeric and non-numeric into components list: 27.13.4~18.04.1 -> [27][14][4]"""
#return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', version) if x]
return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', version) if x]

def parse_version(version_components):
""" Parse the split version list into list [27][14][4] -> [[27], [14], [4]]"""
return [split_version_components(x) for x in version_components.split(".")]

parse_version_a = parse_version(version_a)
parse_version_b = parse_version(version_b)
parse_version_a = self.__parse_version(version_a)
parse_version_b = self.__parse_version(version_b)

for v1, v2 in zip(parse_version_a, parse_version_b):
for sub_v1, sub_v2 in zip(v1, v2):
if sub_v1 < sub_v2:
return -1 # less
return -1 # less
elif sub_v1 > sub_v2:
return 1 # greater

# If equal 27.13.4 vs 27.13.4, return 0
return (len(parse_version_a) > len(parse_version_b)) - (len(parse_version_a) - len(parse_version_b))

@staticmethod
def extract_version(path):
def extract_version(self, path):
# type (str) -> str
"""
Extract the version part from a given path.
Input: /var/lib/waagent/Microsoft.CPlat.Core.LinuxPatchExtension-1.2.5/config
Return: 1.2.5
Return: "1.2.5"
"""
match = re.search(r'([\d]+\.[\d]+\.[\d]+)', path)
return match.group(1) if match else ""
return match.group(1) if match else str()

@staticmethod
def sort_versions(paths):
def sort_versions_desc_order(self, paths):
# type (list[str]) -> list[str]
"""
Sort paths based on version numbers extracted from paths.
Input:
Expand All @@ -67,12 +58,25 @@ def sort_versions(paths):
"Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"]
"""
return sorted(paths, key=self.__version_key, reverse=True)

def version_key(path):
version_numbers = CoreUtility.extract_version(path)
return tuple(map(int, version_numbers.split('.'))) if version_numbers else (0, 0, 0)
def __version_key(self, path):
# type (str) -> (int)
""" Extract version number from input and return int tuple.
Input: "Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"
Return: (1.6.100)
"""
version_numbers = self.extract_version(path)
return tuple(map(int, version_numbers.split('.'))) if version_numbers else (0, 0, 0)

return sorted(paths, key=version_key, reverse=True)
def __split_version_components(self, version):
# type (str) -> [any]
""" Split a version into numeric and non-numeric into components list: 27.13.4~18.04.1 -> [27][14][4]"""
return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', version) if x]

def __parse_version(self, version_components):
# type (str) -> [[any]]
""" Parse the split version list into list [27][14][4] -> [[27], [14], [4]]"""
return [self.__split_version_components(x) for x in version_components.split(".")]


9 changes: 6 additions & 3 deletions src/core/src/package_managers/UbuntuProClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"""This is the Ubuntu Pro Client implementation"""
import json

from core.src.bootstrap.CoreUtility import CoreUtility
from core.src.core_logic.VersionComparator import VersionComparator
from core.src.bootstrap.Constants import Constants


Expand All @@ -29,6 +29,7 @@ def __init__(self, env_layer, composite_logger):
self.ubuntu_pro_client_security_status_cmd = 'pro security-status --format=json'
self.security_esm_criteria_strings = ["esm-infra", "esm-apps"]
self.is_ubuntu_pro_client_attached = False
self.version_comparator = VersionComparator()

def install_or_update_pro(self):
"""install/update pro(ubuntu-advantage-tools) to the latest version"""
Expand Down Expand Up @@ -56,10 +57,12 @@ def is_pro_working(self):
ubuntu_pro_client_version = version_result.installed_version

# extract version from pro_client_verison 27.13.4~18.04.1 -> 27.13.4
extracted_ubuntu_pro_client_version = CoreUtility.extract_version(ubuntu_pro_client_version)
extracted_ubuntu_pro_client_version = self.version_comparator.extract_version(ubuntu_pro_client_version)

self.composite_logger.log_debug("Ubuntu Pro Client current version: [ClientVersion={0}]".format(str(extracted_ubuntu_pro_client_version)))

# use custom comparator output 0 (equal), -1 (less), +1 (greater)
is_minimum_ubuntu_pro_version_installed = CoreUtility.compare_version(extracted_ubuntu_pro_client_version, Constants.UbuntuProClientSettings.MINIMUM_CLIENT_VERSION) >= 0
is_minimum_ubuntu_pro_version_installed = self.version_comparator.compare_version(extracted_ubuntu_pro_client_version, Constants.UbuntuProClientSettings.MINIMUM_CLIENT_VERSION) >= 0

if ubuntu_pro_client_version is not None and is_minimum_ubuntu_pro_version_installed:
is_ubuntu_pro_client_working = True
Expand Down
73 changes: 0 additions & 73 deletions src/core/tests/Test_CoreUtility.py

This file was deleted.

77 changes: 77 additions & 0 deletions src/core/tests/Test_VersionComparator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2020 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Requires Python 2.7+

import unittest

from core.src.core_logic.VersionComparator import VersionComparator


class TestVersionComparator(unittest.TestCase):

def setUp(self):
self.version_comparator = VersionComparator()

def test_linux_version_comparator(self):
# Test extract version logic
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25"), "1.2.25")
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc"), "1.2.25")
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25+abc.123"), "1.2.25")
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123"), "1.2.25")
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001"), "1.21.1001")
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100"), "1.6.100")
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99"), "1.6.99")
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-1.6."), "")
self.assertEqual(self.version_comparator.extract_version("Microsoft.CPlat.Core.LinuxPatchExtension-a.b.c"), "")

expected_extracted_version = "27.13.4"
test_extracted_v1 = self.version_comparator.extract_version("27.13.4~18.04.1")
test_extracted_v2 = self.version_comparator.extract_version("27.13.4+18.04.1")
test_extracted_v3 = self.version_comparator.extract_version("27.13.4-18.04.1")

self.assertEqual(test_extracted_v1, expected_extracted_version)
self.assertEqual(test_extracted_v2, expected_extracted_version)
self.assertEqual(test_extracted_v3, expected_extracted_version)

# Test compare versions logic
self.assertEqual(self.version_comparator.compare_version(test_extracted_v1, "27.13.4"), 0) # equal
self.assertEqual(self.version_comparator.compare_version(test_extracted_v2, "27.13.3"), 1) # greater
self.assertEqual(self.version_comparator.compare_version(test_extracted_v3, "27.13.5"), -1) # less

# Test sort versions logic
unsorted_path_versions = [
"Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc",
]

expected_sorted_path_versions = [
"Microsoft.CPlat.Core.LinuxPatchExtension-1.21.1001",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.21.100",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.6.100",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.6.99",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc+def.123",
"Microsoft.CPlat.Core.LinuxPatchExtension-1.2.25-abc"
]

# valid versions
self.assertEqual(self.version_comparator.sort_versions_desc_order(unsorted_path_versions), expected_sorted_path_versions)

if __name__ == '__main__':
unittest.main()

Check warning on line 76 in src/core/tests/Test_VersionComparator.py

View check run for this annotation

Codecov / codecov/patch

src/core/tests/Test_VersionComparator.py#L76

Added line #L76 was not covered by tests

2 changes: 1 addition & 1 deletion src/extension/tests/Test_Utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,6 @@ def test_extract_sorted_versions(self):
]

# valid versions
self.assertEqual(self.utility.sort_versions(unsorted_path_versions), expected_sorted_path_versions)
self.assertEqual(self.utility.sort_versions_desc_order(unsorted_path_versions), expected_sorted_path_versions)


0 comments on commit 5ff57c6

Please sign in to comment.