]> SALOME platform Git repositories - tools/sat.git/commitdiff
Salome HOME
add src.versionMinorMajorPatch.py and test.test_021_versionMinorMajorPatch.py
authorChristian Van Wambeke <chvw@orange.fr>
Wed, 24 Oct 2018 12:51:47 +0000 (14:51 +0200)
committerChristian Van Wambeke <chvw@orange.fr>
Wed, 24 Oct 2018 12:51:47 +0000 (14:51 +0200)
commands/config.py
src/product.py
src/versionMinorMajorPatch.py
test/test_021_versionMinorMajorPatch.py [new file with mode: 0755]

index 6cb7f9dbeecd323b8296f811fda8956b4e90279e..d6b9dba6c555009ca020ff5a33583380f7ee9bfc 100644 (file)
@@ -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
index 47bd73a112d715d2dd24db5b639fc6734287692d..db231d2401c26ebad1864eff0113c9af8fa3cff8 100644 (file)
@@ -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
index d79831b82b160a29c0c5839013503801be0db97d..94b8921efc00d35d96edee29df33842d30a9b1d5 100755 (executable)
@@ -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 (executable)
index 0000000..6fbb061
--- /dev/null
@@ -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
+