From: Christian Van Wambeke Date: Wed, 24 Oct 2018 12:51:47 +0000 (+0200) Subject: add src.versionMinorMajorPatch.py and test.test_021_versionMinorMajorPatch.py X-Git-Tag: 5.2.0~9^2~5 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=50c2f8e0303872aefb4f8f25e1e35b6c4e82b003;p=tools%2Fsat.git add src.versionMinorMajorPatch.py and test.test_021_versionMinorMajorPatch.py --- diff --git a/commands/config.py b/commands/config.py index 6cb7f9d..d6b9dba 100644 --- a/commands/config.py +++ b/commands/config.py @@ -904,9 +904,13 @@ def run(args, runner, logger): res = DBG.indent(DBG.getStrConfigDbg(runner.cfg)) logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res)) else: - exec("a = runner.cfg.%s" % options.debug) + if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc" + od = options.debug[1:] + else: + od = options.debug + exec("a = runner.cfg.%s" % od) res = DBG.indent(DBG.getStrConfigDbg(a)) - logger.write("\nConfig.%s of application %s:\n\n%s\n" % (options.debug, runner.cfg.VARS.application, res)) + logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res)) # case : edit user pyconf file or application file diff --git a/src/product.py b/src/product.py index 47bd73a..db231d2 100644 --- a/src/product.py +++ b/src/product.py @@ -27,6 +27,7 @@ import pprint as PP import src import src.debug as DBG +import src.versionMinorMajorPatch as VMMP AVAILABLE_VCS = ['git', 'svn', 'cvs'] config_expression = "^config-\d+$" @@ -303,12 +304,12 @@ Please provide a 'compil_script' key in its definition.""") % product_name return prod_info -def get_product_section(config, product_name, version, section=None): +def get_product_section(config, product_name, version, section=None, verbose=False): """Get the product description from the configuration :param config Config: The global configuration :param product_name str: The product name - :param version str: The version of the product + :param version str: The version of the product as 'V8_4_0', or else. :param section str: The searched section (if not None, the section is explicitly given :return: The product description @@ -316,25 +317,61 @@ def get_product_section(config, product_name, version, section=None): """ # if section is not None, try to get the corresponding section + aProd = config.PRODUCTS[product_name] + versionMMP = VMMP.MinorMajorPatch(version) + DBG.write("get_product_section for product %s '%s' as '%s'" % (product_name, version, versionMMP), + (section, aProd.keys()), verbose) + # DBG.write("yoo1", aProd, True) if section: - if section not in config.PRODUCTS[product_name]: + if section not in aProd: return None # returns specific information for the given version - prod_info = config.PRODUCTS[product_name][section] + prod_info = aProd[section] prod_info.section = section - prod_info.from_file = config.PRODUCTS[product_name].from_file + prod_info.from_file = aProd.from_file return prod_info # If it exists, get the information of the product_version - if "version_" + version in config.PRODUCTS[product_name]: + # ex: 'version_V6_6_0' as salome version classical syntax + if "version_" + version in aProd: + DBG.write("found section for version_" + version, "", verbose) # returns specific information for the given version - prod_info = config.PRODUCTS[product_name]["version_" + version] + prod_info = aProd["version_" + version] prod_info.section = "version_" + version - prod_info.from_file = config.PRODUCTS[product_name].from_file + prod_info.from_file = aProd.from_file return prod_info - + # Else, check if there is a description for multiple versions - l_section_name = config.PRODUCTS[product_name].keys() + l_section_names = aProd.keys() + l_section_ranges = [] + for name in l_section_names: + # DBG.write("name", name,True) + aRange = VMMP.getRange_majorMinorPatch(name) + if aRange is not None: + DBG.write("found version range for section '%s'" % name, aRange, verbose) + l_section_ranges.append((name, aRange)) + + if len(l_section_ranges) > 0: + tagged = [] + for name, (vmin, vmax) in l_section_ranges: + if versionMMP >= vmin and versionMMP <= vmax: + tagged.append((name, [vmin, vmax])) + + if len(tagged) > 1: + DBG.write("multiple version ranges tagged for '%s', fix it" % version, + PP.pformat(tagged), True) + return None + if len(tagged) == 1: # ok + DBG.write("one version range tagged for '%s'" % version, + PP.pformat(tagged), verbose) + name, (vmin, vmax) = tagged[0] + prod_info = aProd[name] + prod_info.section = name + prod_info.from_file = aProd.from_file + return prod_info + + """ + l_section_name = aProd.keys() l_section_ranges = [section_name for section_name in l_section_name if VERSION_DELIMITER in section_name] for section_range in l_section_ranges: @@ -342,19 +379,19 @@ def get_product_section(config, product_name, version, section=None): if (src.only_numbers(version) >= src.only_numbers(minimum) and src.only_numbers(version) <= src.only_numbers(maximum)): # returns specific information for the versions - prod_info = config.PRODUCTS[product_name][section_range] + prod_info = aProd[section_range] prod_info.section = section_range - prod_info.from_file = config.PRODUCTS[product_name].from_file + prod_info.from_file = aProd.from_file return prod_info + """ - - # Else, get the standard informations - if "default" in config.PRODUCTS[product_name]: + if "default" in aProd: # returns the generic information (given version not found) - prod_info = config.PRODUCTS[product_name].default + prod_info = aProd.default + DBG.write("default tagged for '%s'" % version, prod_info, verbose) prod_info.section = "default" - prod_info.from_file = config.PRODUCTS[product_name].from_file + prod_info.from_file = aProd.from_file return prod_info # if noting was found, return None diff --git a/src/versionMinorMajorPatch.py b/src/versionMinorMajorPatch.py index d79831b..94b8921 100755 --- a/src/versionMinorMajorPatch.py +++ b/src/versionMinorMajorPatch.py @@ -19,7 +19,8 @@ """\ -class and utilities to define a version as MAJOR.MINOR.PATCH +class and utilities to define a version as MAJOR.MINOR.PATCH, +and range of versions | Given a version number MAJOR.MINOR.PATCH separator "_" or "." | increment the @@ -37,7 +38,9 @@ verbose = False # True def only_numbers(aStr): """ Remove non numericals characters from string, - returns None if no numbers + + :param aStr: string to work + :return: None if no number presence """ res = ''.join([nb for nb in aStr if nb in '0123456789']) if res == "": @@ -46,19 +49,39 @@ def only_numbers(aStr): return res ############################################# -def toList_majorMinorPatch(aStr): +def remove_startswith(aStr, startsToCheck): + """ + remove starting strings, if begining of aStr correspond + order of list startsToCheck matter + do the stuff only for the first correspondence in startsToCheck + """ + for s in startsToCheck: + if aStr.startswith(s): + return aStr[len(s):] + return aStr + +############################################# +def toList_majorMinorPatch(aStr, verbose=False): """ Returns list of integer as [major, minor, patch] from a string, - raise exception if problem + + | accepts '1.2.3' '1_2_3' 'version_1.2.3' 'version1.2.3' 'v1.2.3', + | completion '123' means '123.0.0', '1.2' means '1.2.0' + | lower or upper + | raise exception if problem """ if verbose: print("toList_majorMinorPatch('%s')" % aStr) - res = aStr.replace(" ", "").replace(".", "_").split("_") + res = aStr.replace(" ", "") + res = res.lower() + res = remove_startswith(res, "version_ version v".split()) + res = res.replace(".", "_").split("_") if len(res) > 3: msg = "Not a major_minor_patch correct syntax: '%s'" % aStr raise Exception(msg) if len(res) == 0: msg = "An empty string is not a major_minor_patch syntax" raise Exception(msg) + # complete MINOR.PATCH if not existing if len(res) == 1: res.append("0") @@ -118,6 +141,59 @@ def toCompactStr_majorMinorPatch(version): return res +############################################# +def getRange_majorMinorPatch(aStr, verbose=False): + """ + extract from aStr a version range, defined as "*_from_aMinVersionTag_to_aMaxVersionTag. + where aMinVersionTag and aMaxVersionTag are compatible with MinorMajorPatch class syntaxes + '1.2.3' or '1_2_3' etc. + if not found '_from_' then aMinVersionTag is '0.0.0' + + :param aStr: string to work + :return: list [min, max], where min, max are MinorMajorPatch instances. + else None if not found + """ + tmp1 = aStr.lower().split("_to_") + + if len(tmp1) < 2: + return None # no '_to_' + if len(tmp1) > 2: + msg = "more than one '_to_' is incorrect for version range: '%s'" % aStr + raise Exception(msg) + aMax = tmp1[1] + + tmp0 = aStr.lower().split("_from_") + + if len(tmp0) > 2: + msg = "more than one '_from_' is incorrect for version range: '%s'" % aStr + raise Exception(msg) + + tmp2 = tmp1[0].split("_from_") + + if len(tmp2) == 2: + aMin = tmp2[1] + else: + aMin ="0.0.0" + + if verbose: + msg = "version min '%s' and version max '%s' in version range: '%s'" % (aMin, aMax, aStr) + print(msg) + + try: + rMin = MinorMajorPatch(aMin) + rMax = MinorMajorPatch(aMax) + except: + msg = "problem version range in '%s'" % aStr + raise Exception(msg) + """if verbose: + print("WARNING: problem version range in '%s'" % aStr) + return None""" + + if rMin > rMax: + msg = "version min '%s' > version max '%s' in version range: '%s'" % (rMin, rMax, aStr) + raise Exception(msg) + + return [rMin, rMax] ############################################# class MinorMajorPatch(object): @@ -191,106 +267,4 @@ class MinorMajorPatch(object): res = (self.toList() != other.toList()) return res -############################################# -import unittest -import pprint as PP - - -class TestCase(unittest.TestCase): - "Test the versionMajorMinorPatch.py""" - - def test_010(self): - if verbose: print(PP.pformat(dir(self))) - self.assertTrue(only_numbers("") is None) - self.assertEqual(only_numbers("1.2.3"), "123") - self.assertEqual(only_numbers("\n11.12.13\n"), "111213") - self.assertEqual(only_numbers(" \n 11.\t\n\t..12.13-rc2\n"), "1112132") - - def test_020(self): - res = [11, 222, 3333] - self.assertEqual(toList_majorMinorPatch("11.222.3333"), res) - self.assertEqual(toList_majorMinorPatch("11_222_3333"), res) - self.assertEqual(toList_majorMinorPatch("11.222_3333"), res) - self.assertEqual(toList_majorMinorPatch(" 11. 222 . 3333 "), res) - self.assertEqual(toList_majorMinorPatch("\n 11 . 222 . 3333 \n"), res) - self.assertEqual(toList_majorMinorPatch(" \n11.\t222.\r3333\n "), res) # could be tricky - - self.assertEqual(toList_majorMinorPatch("11"), [11, 0, 0]) - self.assertEqual(toList_majorMinorPatch("11.0"), [11, 0, 0]) - self.assertEqual(toList_majorMinorPatch("11.2"), [11, 2, 0]) - self.assertEqual(toList_majorMinorPatch("\n1 . 2 \n"), [1, 2, 0]) - - with self.assertRaises(Exception): toList_majorMinorPatch("") - with self.assertRaises(Exception): toList_majorMinorPatch("11.") - with self.assertRaises(Exception): toList_majorMinorPatch("11.2.") - with self.assertRaises(Exception): toList_majorMinorPatch("11.2.3.") - with self.assertRaises(Exception): toList_majorMinorPatch(".11") - with self.assertRaises(Exception): toList_majorMinorPatch("1_2_3_4") - with self.assertRaises(Exception): toList_majorMinorPatch("_1_2_3_") - with self.assertRaises(Exception): toList_majorMinorPatch(" \n 11...22.333-rc2\n") - with self.assertRaises(Exception): toList_majorMinorPatch(" \n 11...22.333-rc2\n") - with self.assertRaises(Exception): toList_majorMinorPatch(" \n 11...22.333-rc2\n") - - - def test_030(self): - self.assertEqual(toCompactStr_majorMinorPatch([1, 2, 3]), "123") - self.assertEqual(toCompactStr_majorMinorPatch([11, 2, 3]), "1123") - self.assertEqual(toCompactStr_majorMinorPatch([1, 9, 9]), "199") - - with self.assertRaises(Exception): toCompactStr_majorMinorPatch([1, 2, 10]) - with self.assertRaises(Exception): toCompactStr_majorMinorPatch([1, 10, 3]) - with self.assertRaises(Exception): toCompactStr_majorMinorPatch([10, 10, 10]) - - def test_040(self): - MMP = MinorMajorPatch - v = [1, 2, 3] - self.assertEqual(MMP(v).__str__(), "1.2.3") - self.assertEqual(MMP(v).__str__(sep="_"), "1_2_3") - self.assertEqual(str(MMP(v)), "1.2.3") - - self.assertEqual(MMP(v).__repr__(), "version_1_2_3") - self.assertEqual(MMP(v).__repr__(sep="."), "version_1.2.3") - - self.assertEqual(MMP(v).strSalome(), "1_2_3") - self.assertEqual(MMP(v).strClassic(), "1.2.3") - - self.assertEqual(MMP([' 123 \n', 2, 10]).strClassic(), "123.2.10") - self.assertEqual(MMP([' 123 \n', 2, 10]).strSalome(), "123_2_10") - self.assertEqual(MMP([' 123 \n', 2, 9]).strCompact(), "12329") # no ambigous - - with self.assertRaises(Exception): MMP([-5, 2, 10]) - with self.assertRaises(Exception): MMP([5, -2, 10]) - with self.assertRaises(Exception): MMP([5, 2, -10]) - with self.assertRaises(Exception): MMP(['-123', 2, 10]) - with self.assertRaises(Exception): MMP([123, 2, 10].strCompact()) # ambigous - - def test_040(self): - MMP = MinorMajorPatch - v000 = MMP("0.0.0") - v010 = MMP("0.1.0") - v100 = MMP("1.0.0") - v101 = MMP("1.0.1") - - va = v000 - vb = MMP("0.0.0") - self.assertTrue(va == vb) - self.assertTrue(va >= vb) - self.assertTrue(va <= vb) - self.assertFalse(va != vb) - self.assertFalse(va > vb) - self.assertFalse(va < vb) - - va = v000 - vb = v010 - self.assertFalse(va == vb) - self.assertFalse(va >= vb) - self.assertTrue(va <= vb) - self.assertTrue(va != vb) - self.assertFalse(va > vb) - self.assertTrue(va < vb) - - -if __name__ == '__main__': - unittest.main(exit=False) - pass diff --git a/test/test_021_versionMinorMajorPatch.py b/test/test_021_versionMinorMajorPatch.py new file mode 100755 index 0000000..6fbb061 --- /dev/null +++ b/test/test_021_versionMinorMajorPatch.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +# Copyright (C) 2010-2018 CEA/DEN +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +"""\ +class and utilities to define a version as MAJOR.MINOR.PATCH, +and range of versions + +| Given a version number MAJOR.MINOR.PATCH separator "_" or "." +| increment the +| MAJOR version when you make incompatible API changes, +| MINOR version when you add functionality in a backwards-compatible manner, +| PATCH version when you make backwards-compatible bug fixes. +""" + +import os +import sys + +import unittest +import pprint as PP + +import initializeTest # set PATH etc for test + +import src.versionMinorMajorPatch as VMMP + +verbose = False # True + +class TestCase(unittest.TestCase): + "Test the versionMajorMinorPatch.py""" + + def test_010(self): + if verbose: print(PP.pformat(dir(self))) + self.assertTrue(VMMP.only_numbers("") is None) + self.assertEqual(VMMP.only_numbers("1.2.3"), "123") + self.assertEqual(VMMP.only_numbers("\n11.12.13\n"), "111213") + self.assertEqual(VMMP.only_numbers(" \n 11.\t\n\t..12.13-rc2\n"), "1112132") + + def test_015(self): + res = "a_b_c" + self.assertEqual(VMMP.remove_startswith("version_a_b_c", "version_".split()), res) + self.assertEqual(VMMP.remove_startswith("v_a_b_c", "version_ v_".split()), res) + self.assertEqual(VMMP.remove_startswith("va_b_c", "version_ v_ v".split()), res) + + ini = "version_a_b_c" + self.assertEqual(VMMP.remove_startswith(ini, "V".split()), ini) + self.assertEqual(VMMP.remove_startswith(ini, "_".split()), ini) + self.assertEqual(VMMP.remove_startswith(ini, "a_b_c".split()), ini) + self.assertEqual(VMMP.remove_startswith(ini, "VERSION".split()), ini) + + + def test_020(self): + res = [11, 222, 3333] + self.assertEqual(VMMP.toList_majorMinorPatch("11.222.3333"), res) + self.assertEqual(VMMP.toList_majorMinorPatch("11_222_3333"), res) + self.assertEqual(VMMP.toList_majorMinorPatch("11.222_3333"), res) + self.assertEqual(VMMP.toList_majorMinorPatch(" 11. 222 . 3333 "), res) + self.assertEqual(VMMP.toList_majorMinorPatch("\n 11 . 222 . 3333 \n"), res) + self.assertEqual(VMMP.toList_majorMinorPatch(" \n11.\t222.\r3333\n "), res) # could be tricky + + self.assertEqual(VMMP.toList_majorMinorPatch("V11.222.3333"), res) + self.assertEqual(VMMP.toList_majorMinorPatch("Version11_222_3333"), res) + self.assertEqual(VMMP.toList_majorMinorPatch("Version_11_222_3333"), res) + + + self.assertEqual(VMMP.toList_majorMinorPatch("11"), [11, 0, 0]) + self.assertEqual(VMMP.toList_majorMinorPatch("11.0"), [11, 0, 0]) + self.assertEqual(VMMP.toList_majorMinorPatch("11.2"), [11, 2, 0]) + self.assertEqual(VMMP.toList_majorMinorPatch("\n1 . 2 \n"), [1, 2, 0]) + + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch("") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch("11.") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch("11.2.") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch("11.2.3.") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch(".11") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch("1_2_3_4") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch("_1_2_3_") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch(" \n 11...22.333-rc2\n") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch(" \n 11...22.333-rc2\n") + with self.assertRaises(Exception): VMMP.toList_majorMinorPatch(" \n 11...22.333-rc2\n") + + def test_030(self): + self.assertEqual(VMMP.toCompactStr_majorMinorPatch([1, 2, 3]), "123") + self.assertEqual(VMMP.toCompactStr_majorMinorPatch([11, 2, 3]), "1123") + self.assertEqual(VMMP.toCompactStr_majorMinorPatch([1, 9, 9]), "199") + + with self.assertRaises(Exception): VMMP.toCompactStr_majorMinorPatch([1, 2, 10]) + with self.assertRaises(Exception): VMMP.toCompactStr_majorMinorPatch([1, 10, 3]) + with self.assertRaises(Exception): VMMP.toCompactStr_majorMinorPatch([10, 10, 10]) + + def test_040(self): + MMP = VMMP.MinorMajorPatch + v = [1, 2, 3] + self.assertEqual(MMP(v).__str__(), "1.2.3") + self.assertEqual(MMP(v).__str__(sep="_"), "1_2_3") + self.assertEqual(str(MMP(v)), "1.2.3") + + self.assertEqual(MMP(v).__repr__(), "version_1_2_3") + self.assertEqual(MMP(v).__repr__(sep="."), "version_1.2.3") + + self.assertEqual(MMP(v).strSalome(), "1_2_3") + self.assertEqual(MMP(v).strClassic(), "1.2.3") + + self.assertEqual(MMP([' 123 \n', 2, 10]).strClassic(), "123.2.10") + self.assertEqual(MMP([' 123 \n', 2, 10]).strSalome(), "123_2_10") + self.assertEqual(MMP([' 123 \n', 2, 9]).strCompact(), "12329") # no ambigous + + with self.assertRaises(Exception): MMP([-5, 2, 10]) + with self.assertRaises(Exception): MMP([5, -2, 10]) + with self.assertRaises(Exception): MMP([5, 2, -10]) + with self.assertRaises(Exception): MMP(['-123', 2, 10]) + with self.assertRaises(Exception): MMP([123, 2, 10].strCompact()) # ambigous + + def test_050(self): + MMP = VMMP.MinorMajorPatch + v000 = MMP("0.0.0") + v010 = MMP("0.1.0") + v100 = MMP("1.0.0") + v101 = MMP("1.0.1") + + va = v000 + vb = MMP("0.0.0") + self.assertTrue(va == vb) + self.assertTrue(va >= vb) + self.assertTrue(va <= vb) + self.assertFalse(va != vb) + self.assertFalse(va > vb) + self.assertFalse(va < vb) + + va = v000 + vb = v010 + self.assertFalse(va == vb) + self.assertFalse(va >= vb) + self.assertTrue(va <= vb) + self.assertTrue(va != vb) + self.assertFalse(va > vb) + self.assertTrue(va < vb) + + va = v101 + vb = v100 + self.assertFalse(va == vb) + self.assertTrue(va >= vb) + self.assertFalse(va <= vb) + self.assertTrue(va != vb) + self.assertTrue(va > vb) + self.assertFalse(va < vb) + + def test_060(self): + MMP = VMMP.MinorMajorPatch + v0 = MMP("0") + v1 = MMP("1") + v2 = MMP("2") + v123 = MMP("1.2.3") + v456 = MMP("4.5.6") + + tests = """\ +toto_from_1_to_2 + _from_1.0.0_to_2.0.0 +_from_1_0. 0_to_ 2.0_0 +_from_V1.0.0_to_2.0.0 +_from_version_1.0.0_to_2.0.0""".split("\n") + + for a in tests: + # print("test '%s'" % a) + r1, r2 = VMMP.getRange_majorMinorPatch(a) + self.assertEqual(r1, v1) + self.assertEqual(r2, v2) + + a = "toto_to_2" + r1, r2 = VMMP.getRange_majorMinorPatch(a) + self.assertEqual(r1, v0) + self.assertEqual(r2, v2) + + a = "toto_to_Version2" + r1, r2 = VMMP.getRange_majorMinorPatch(a) + self.assertEqual(r1, v0) + self.assertEqual(r2, v2) + + a = "toto_from_1.2.3_to_Version4_5_6" + r1, r2 = VMMP.getRange_majorMinorPatch(a) + self.assertEqual(r1, v123) + self.assertEqual(r2, v456) + + a = "toto_from_1.2.3_to_Version1_2_3" + r1, r2 = VMMP.getRange_majorMinorPatch(a) + self.assertEqual(r1, v123) + self.assertEqual(r2, v123) + + # _from_ without _to_ does not matter + tests = """\ + +toto +from +to +_from_ +toto_from_2""".split("\n") + + for a in tests: + rx = VMMP.getRange_majorMinorPatch(a, verbose=False) + self.assertEqual(rx, None) + + # _to_ without _from_ does not matter, as implicit _from_ '0.0.0' + # empty _to_ raise error + with self.assertRaises(Exception): VMMP.getRange_majorMinorPatch("_to_") + with self.assertRaises(Exception): VMMP.getRange_majorMinorPatch("_from_to_") + with self.assertRaises(Exception): VMMP.getRange_majorMinorPatch("_from__to_") + with self.assertRaises(Exception): VMMP.getRange_majorMinorPatch("toto_from__to_") + with self.assertRaises(Exception): VMMP.getRange_majorMinorPatch("toto_from_123_to_") + + # min > max does matter + with self.assertRaises(Exception): VMMP.getRange_majorMinorPatch("_from_3_to_2") + with self.assertRaises(Exception): VMMP.getRange_majorMinorPatch("_from_3.2.5_to_V2_1_1") + +if __name__ == '__main__': + unittest.main(exit=False) + pass +