tests: check min required python version with vermin (#14289)
This commit removes the `python_version.py` unit test module and the vendored dependencies `pyqver2.py` and `pyqver3.py`. It substitutes them with an equivalent check done using `vermin` that is run as a separate workflow via Github Actions. This allows us to delete 2 vendored dependencies that are unmaintained and substitutes them with a maintained tool. Also, updates the list of vendored dependencies.
This commit is contained in:
		 Massimiliano Culpo
					Massimiliano Culpo
				
			
				
					committed by
					
						 Todd Gamblin
						Todd Gamblin
					
				
			
			
				
	
			
			
			 Todd Gamblin
						Todd Gamblin
					
				
			
						parent
						
							1e2c9d960c
						
					
				
				
					commit
					d333e14721
				
			
							
								
								
									
										30
									
								
								.github/workflows/minimum_python_versions.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/minimum_python_versions.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| name: Minimum Python Versions | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - master | ||||
|       - develop | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - master | ||||
|       - develop | ||||
| jobs: | ||||
|   build: | ||||
|  | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|     - uses: actions/checkout@v1 | ||||
|     - name: Setup Python | ||||
|       uses: actions/setup-python@v1 | ||||
|       with: | ||||
|         python-version: 2.7 | ||||
|     - name: Install Python Packages | ||||
|       run: | | ||||
|         pip install --upgrade pip | ||||
|         pip install --upgrade vermin | ||||
|     - name: Minimum Version (Spack's Core) | ||||
|       run: vermin --backport argparse -t=2.6- -t=3.5- -v lib/spack/spack/ lib/spack/llnl/ bin/ | ||||
|     - name: Minimum Version (Repositories) | ||||
|       run: vermin --backport argparse -t=2.6- -t=3.5- -v var/spack/repos | ||||
							
								
								
									
										8
									
								
								lib/spack/external/__init__.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								lib/spack/external/__init__.py
									
									
									
									
										vendored
									
									
								
							| @@ -82,14 +82,6 @@ | ||||
|   ini-parsing, io, code, and log facilities. | ||||
| * Version: 1.4.34 (last version supporting Python 2.6) | ||||
| 
 | ||||
| pyqver | ||||
| ------ | ||||
| 
 | ||||
| * Homepage: https://github.com/ghewgill/pyqver | ||||
| * Usage: External script to query required python version of | ||||
|   python source code. Used for ensuring 2.6 compatibility. | ||||
| * Version: Unversioned | ||||
| 
 | ||||
| pytest | ||||
| ------ | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										344
									
								
								lib/spack/external/pyqver2.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										344
									
								
								lib/spack/external/pyqver2.py
									
									
									
									
										vendored
									
									
								
							| @@ -1,344 +0,0 @@ | ||||
| #!/usr/bin/env python | ||||
| # | ||||
| # pyqver2.py | ||||
| # by Greg Hewgill | ||||
| # https://github.com/ghewgill/pyqver | ||||
| # | ||||
| # This software is provided 'as-is', without any express or implied | ||||
| # warranty.  In no event will the author be held liable for any damages | ||||
| # arising from the use of this software. | ||||
| # | ||||
| # Permission is granted to anyone to use this software for any purpose, | ||||
| # including commercial applications, and to alter it and redistribute it | ||||
| # freely, subject to the following restrictions: | ||||
| # | ||||
| # 1. The origin of this software must not be misrepresented; you must not | ||||
| #    claim that you wrote the original software. If you use this software | ||||
| #    in a product, an acknowledgment in the product documentation would be | ||||
| #    appreciated but is not required. | ||||
| # 2. Altered source versions must be plainly marked as such, and must not be | ||||
| #    misrepresented as being the original software. | ||||
| # 3. This notice may not be removed or altered from any source distribution. | ||||
| # | ||||
| # Copyright (c) 2009-2013 Greg Hewgill http://hewgill.com | ||||
| # | ||||
| 
 | ||||
| import compiler | ||||
| import platform | ||||
| import sys | ||||
| 
 | ||||
| StandardModules = { | ||||
|     "__future__":       (2, 1), | ||||
|     "abc":              (2, 6), | ||||
| # skip argparse now that it's in lib/spack/external | ||||
| #    "argparse":         (2, 7), | ||||
|     "ast":              (2, 6), | ||||
|     "atexit":           (2, 0), | ||||
|     "bz2":              (2, 3), | ||||
|     "cgitb":            (2, 2), | ||||
|     "collections":      (2, 4), | ||||
|     "contextlib":       (2, 5), | ||||
|     "cookielib":        (2, 4), | ||||
|     "cProfile":         (2, 5), | ||||
|     "csv":              (2, 3), | ||||
|     "ctypes":           (2, 5), | ||||
|     "datetime":         (2, 3), | ||||
|     "decimal":          (2, 4), | ||||
|     "difflib":          (2, 1), | ||||
|     "DocXMLRPCServer":  (2, 3), | ||||
|     "dummy_thread":     (2, 3), | ||||
|     "dummy_threading":  (2, 3), | ||||
|     "email":            (2, 2), | ||||
|     "fractions":        (2, 6), | ||||
|     "functools":        (2, 5), | ||||
|     "future_builtins":  (2, 6), | ||||
|     "hashlib":          (2, 5), | ||||
|     "heapq":            (2, 3), | ||||
|     "hmac":             (2, 2), | ||||
|     "hotshot":          (2, 2), | ||||
|     "HTMLParser":       (2, 2), | ||||
|     "importlib":        (2, 7), | ||||
|     "inspect":          (2, 1), | ||||
|     "io":               (2, 6), | ||||
|     "itertools":        (2, 3), | ||||
|     "json":             (2, 6), | ||||
|     "logging":          (2, 3), | ||||
|     "modulefinder":     (2, 3), | ||||
|     "msilib":           (2, 5), | ||||
|     "multiprocessing":  (2, 6), | ||||
|     "netrc":            (1, 5, 2), | ||||
|     "numbers":          (2, 6), | ||||
|     "optparse":         (2, 3), | ||||
|     "ossaudiodev":      (2, 3), | ||||
|     "pickletools":      (2, 3), | ||||
|     "pkgutil":          (2, 3), | ||||
|     "platform":         (2, 3), | ||||
|     "pydoc":            (2, 1), | ||||
|     "runpy":            (2, 5), | ||||
|     "sets":             (2, 3), | ||||
|     "shlex":            (1, 5, 2), | ||||
|     "SimpleXMLRPCServer": (2, 2), | ||||
|     "spwd":             (2, 5), | ||||
|     "sqlite3":          (2, 5), | ||||
|     "ssl":              (2, 6), | ||||
|     "stringprep":       (2, 3), | ||||
|     "subprocess":       (2, 4), | ||||
|     "sysconfig":        (2, 7), | ||||
|     "tarfile":          (2, 3), | ||||
|     "textwrap":         (2, 3), | ||||
|     "timeit":           (2, 3), | ||||
|     "unittest":         (2, 1), | ||||
|     "uuid":             (2, 5), | ||||
|     "warnings":         (2, 1), | ||||
|     "weakref":          (2, 1), | ||||
|     "winsound":         (1, 5, 2), | ||||
|     "wsgiref":          (2, 5), | ||||
|     "xml.dom":          (2, 0), | ||||
|     "xml.dom.minidom":  (2, 0), | ||||
|     "xml.dom.pulldom":  (2, 0), | ||||
|     "xml.etree.ElementTree": (2, 5), | ||||
|     "xml.parsers.expat":(2, 0), | ||||
|     "xml.sax":          (2, 0), | ||||
|     "xml.sax.handler":  (2, 0), | ||||
|     "xml.sax.saxutils": (2, 0), | ||||
|     "xml.sax.xmlreader":(2, 0), | ||||
|     "xmlrpclib":        (2, 2), | ||||
|     "zipfile":          (1, 6), | ||||
|     "zipimport":        (2, 3), | ||||
|     "_ast":             (2, 5), | ||||
|     "_winreg":          (2, 0), | ||||
| } | ||||
| 
 | ||||
| Functions = { | ||||
|     "all":                      (2, 5), | ||||
|     "any":                      (2, 5), | ||||
|     "collections.Counter":      (2, 7), | ||||
|     "collections.defaultdict":  (2, 5), | ||||
|     "collections.OrderedDict":  (2, 7), | ||||
|     "functools.total_ordering": (2, 7), | ||||
|     "enumerate":                (2, 3), | ||||
|     "frozenset":                (2, 4), | ||||
|     "itertools.compress":       (2, 7), | ||||
|     "math.erf":                 (2, 7), | ||||
|     "math.erfc":                (2, 7), | ||||
|     "math.expm1":               (2, 7), | ||||
|     "math.gamma":               (2, 7), | ||||
|     "math.lgamma":              (2, 7), | ||||
|     "memoryview":               (2, 7), | ||||
|     "next":                     (2, 6), | ||||
|     "os.getresgid":             (2, 7), | ||||
|     "os.getresuid":             (2, 7), | ||||
|     "os.initgroups":            (2, 7), | ||||
|     "os.setresgid":             (2, 7), | ||||
|     "os.setresuid":             (2, 7), | ||||
|     "reversed":                 (2, 4), | ||||
|     "set":                      (2, 4), | ||||
|     "subprocess.check_call":    (2, 5), | ||||
|     "subprocess.check_output":  (2, 7), | ||||
|     "sum":                      (2, 3), | ||||
|     "symtable.is_declared_global": (2, 7), | ||||
|     "weakref.WeakSet":          (2, 7), | ||||
| } | ||||
| 
 | ||||
| Identifiers = { | ||||
|     "False":        (2, 2), | ||||
|     "True":         (2, 2), | ||||
| } | ||||
| 
 | ||||
| def uniq(a): | ||||
|     if len(a) == 0: | ||||
|         return [] | ||||
|     else: | ||||
|         return [a[0]] + uniq([x for x in a if x != a[0]]) | ||||
| 
 | ||||
| class NodeChecker(object): | ||||
|     def __init__(self): | ||||
|         self.vers = dict() | ||||
|         self.vers[(2,0)] = [] | ||||
|     def add(self, node, ver, msg): | ||||
|         if ver not in self.vers: | ||||
|             self.vers[ver] = [] | ||||
|         self.vers[ver].append((node.lineno, msg)) | ||||
|     def default(self, node): | ||||
|         for child in node.getChildNodes(): | ||||
|             self.visit(child) | ||||
|     def visitCallFunc(self, node): | ||||
|         def rollup(n): | ||||
|             if isinstance(n, compiler.ast.Name): | ||||
|                 return n.name | ||||
|             elif isinstance(n, compiler.ast.Const): | ||||
|                 return type(n.value).__name__ | ||||
|             elif isinstance(n, compiler.ast.Getattr): | ||||
|                 r = rollup(n.expr) | ||||
|                 if r: | ||||
|                     return r + "." + n.attrname | ||||
|         name = rollup(node.node) | ||||
|         if name: | ||||
|             # Special handling for empty format strings, which aren't | ||||
|             # allowed in Python 2.6 | ||||
|             if name in ('unicode.format', 'str.format'): | ||||
|                 n = node.node | ||||
|                 if isinstance(n, compiler.ast.Getattr): | ||||
|                     n = n.expr | ||||
|                     if isinstance(n, compiler.ast.Const): | ||||
|                         if '{}' in n.value: | ||||
|                             self.add(node, (2,7), name + ' with {} format string') | ||||
| 
 | ||||
|             v = Functions.get(name) | ||||
|             if v is not None: | ||||
|                 self.add(node, v, name) | ||||
|         self.default(node) | ||||
|     def visitClass(self, node): | ||||
|         if node.bases: | ||||
|             self.add(node, (2,2), "new-style class") | ||||
|         if node.decorators: | ||||
|             self.add(node, (2,6), "class decorator") | ||||
|         self.default(node) | ||||
|     def visitDictComp(self, node): | ||||
|         self.add(node, (2,7), "dictionary comprehension") | ||||
|         self.default(node) | ||||
|     def visitFloorDiv(self, node): | ||||
|         self.add(node, (2,2), "// operator") | ||||
|         self.default(node) | ||||
|     def visitFrom(self, node): | ||||
|         v = StandardModules.get(node.modname) | ||||
|         if v is not None: | ||||
|             self.add(node, v, node.modname) | ||||
|         for n in node.names: | ||||
|             name = node.modname + "." + n[0] | ||||
|             v = Functions.get(name) | ||||
|             if v is not None: | ||||
|                 self.add(node, v, name) | ||||
|     def visitFunction(self, node): | ||||
|         if node.decorators: | ||||
|             self.add(node, (2,4), "function decorator") | ||||
|         self.default(node) | ||||
|     def visitGenExpr(self, node): | ||||
|         self.add(node, (2,4), "generator expression") | ||||
|         self.default(node) | ||||
|     def visitGetattr(self, node): | ||||
|         if (isinstance(node.expr, compiler.ast.Const) | ||||
|             and isinstance(node.expr.value, str) | ||||
|             and node.attrname == "format"): | ||||
|             self.add(node, (2,6), "string literal .format()") | ||||
|         self.default(node) | ||||
|     def visitIfExp(self, node): | ||||
|         self.add(node, (2,5), "inline if expression") | ||||
|         self.default(node) | ||||
|     def visitImport(self, node): | ||||
|         for n in node.names: | ||||
|             v = StandardModules.get(n[0]) | ||||
|             if v is not None: | ||||
|                 self.add(node, v, n[0]) | ||||
|         self.default(node) | ||||
|     def visitName(self, node): | ||||
|         v = Identifiers.get(node.name) | ||||
|         if v is not None: | ||||
|             self.add(node, v, node.name) | ||||
|         self.default(node) | ||||
|     def visitSet(self, node): | ||||
|         self.add(node, (2,7), "set literal") | ||||
|         self.default(node) | ||||
|     def visitSetComp(self, node): | ||||
|         self.add(node, (2,7), "set comprehension") | ||||
|         self.default(node) | ||||
|     def visitTryFinally(self, node): | ||||
|         # try/finally with a suite generates a Stmt node as the body, | ||||
|         # but try/except/finally generates a TryExcept as the body | ||||
|         if isinstance(node.body, compiler.ast.TryExcept): | ||||
|             self.add(node, (2,5), "try/except/finally") | ||||
|         self.default(node) | ||||
|     def visitWith(self, node): | ||||
|         if isinstance(node.body, compiler.ast.With): | ||||
|             self.add(node, (2,7), "with statement with multiple contexts") | ||||
|         else: | ||||
|             self.add(node, (2,5), "with statement") | ||||
|         self.default(node) | ||||
|     def visitYield(self, node): | ||||
|         self.add(node, (2,2), "yield expression") | ||||
|         self.default(node) | ||||
| 
 | ||||
| def get_versions(source, filename=None): | ||||
|     """Return information about the Python versions required for specific features. | ||||
| 
 | ||||
|     The return value is a dictionary with keys as a version number as a tuple | ||||
|     (for example Python 2.6 is (2,6)) and the value are a list of features that | ||||
|     require the indicated Python version. | ||||
|     """ | ||||
|     tree = compiler.parse(source) | ||||
|     checker = compiler.walk(tree, NodeChecker()) | ||||
|     return checker.vers | ||||
| 
 | ||||
| def v27(source): | ||||
|     if sys.version_info >= (2, 7): | ||||
|         return qver(source) | ||||
|     else: | ||||
|         print >>sys.stderr, "Not all features tested, run --test with Python 2.7" | ||||
|         return (2, 7) | ||||
| 
 | ||||
| def qver(source): | ||||
|     """Return the minimum Python version required to run a particular bit of code. | ||||
| 
 | ||||
|     >>> qver('print "hello world"') | ||||
|     (2, 0) | ||||
|     >>> qver('class test(object): pass') | ||||
|     (2, 2) | ||||
|     >>> qver('yield 1') | ||||
|     (2, 2) | ||||
|     >>> qver('a // b') | ||||
|     (2, 2) | ||||
|     >>> qver('True') | ||||
|     (2, 2) | ||||
|     >>> qver('enumerate(a)') | ||||
|     (2, 3) | ||||
|     >>> qver('total = sum') | ||||
|     (2, 0) | ||||
|     >>> qver('sum(a)') | ||||
|     (2, 3) | ||||
|     >>> qver('(x*x for x in range(5))') | ||||
|     (2, 4) | ||||
|     >>> qver('class C:\\n @classmethod\\n def m(): pass') | ||||
|     (2, 4) | ||||
|     >>> qver('y if x else z') | ||||
|     (2, 5) | ||||
|     >>> qver('import hashlib') | ||||
|     (2, 5) | ||||
|     >>> qver('from hashlib import md5') | ||||
|     (2, 5) | ||||
|     >>> qver('import xml.etree.ElementTree') | ||||
|     (2, 5) | ||||
|     >>> qver('try:\\n try: pass;\\n except: pass;\\nfinally: pass') | ||||
|     (2, 0) | ||||
|     >>> qver('try: pass;\\nexcept: pass;\\nfinally: pass') | ||||
|     (2, 5) | ||||
|     >>> qver('from __future__ import with_statement\\nwith x: pass') | ||||
|     (2, 5) | ||||
|     >>> qver('collections.defaultdict(list)') | ||||
|     (2, 5) | ||||
|     >>> qver('from collections import defaultdict') | ||||
|     (2, 5) | ||||
|     >>> qver('"{0}".format(0)') | ||||
|     (2, 6) | ||||
|     >>> qver('memoryview(x)') | ||||
|     (2, 7) | ||||
|     >>> v27('{1, 2, 3}') | ||||
|     (2, 7) | ||||
|     >>> v27('{x for x in s}') | ||||
|     (2, 7) | ||||
|     >>> v27('{x: y for x in s}') | ||||
|     (2, 7) | ||||
|     >>> qver('from __future__ import with_statement\\nwith x:\\n with y: pass') | ||||
|     (2, 5) | ||||
|     >>> v27('from __future__ import with_statement\\nwith x, y: pass') | ||||
|     (2, 7) | ||||
|     >>> qver('@decorator\\ndef f(): pass') | ||||
|     (2, 4) | ||||
|     >>> qver('@decorator\\nclass test:\\n pass') | ||||
|     (2, 6) | ||||
| 
 | ||||
|     #>>> qver('0o0') | ||||
|     #(2, 6) | ||||
|     #>>> qver('@foo\\nclass C: pass') | ||||
|     #(2, 6) | ||||
|     """ | ||||
|     return max(get_versions(source).keys()) | ||||
							
								
								
									
										248
									
								
								lib/spack/external/pyqver3.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										248
									
								
								lib/spack/external/pyqver3.py
									
									
									
									
										vendored
									
									
								
							| @@ -1,248 +0,0 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # | ||||
| # pyqver3.py | ||||
| # by Greg Hewgill | ||||
| # https://github.com/ghewgill/pyqver | ||||
| # | ||||
| # This software is provided 'as-is', without any express or implied | ||||
| # warranty.  In no event will the author be held liable for any damages | ||||
| # arising from the use of this software. | ||||
| # | ||||
| # Permission is granted to anyone to use this software for any purpose, | ||||
| # including commercial applications, and to alter it and redistribute it | ||||
| # freely, subject to the following restrictions: | ||||
| # | ||||
| # 1. The origin of this software must not be misrepresented; you must not | ||||
| #    claim that you wrote the original software. If you use this software | ||||
| #    in a product, an acknowledgment in the product documentation would be | ||||
| #    appreciated but is not required. | ||||
| # 2. Altered source versions must be plainly marked as such, and must not be | ||||
| #    misrepresented as being the original software. | ||||
| # 3. This notice may not be removed or altered from any source distribution. | ||||
| # | ||||
| # Copyright (c) 2009-2013 Greg Hewgill http://hewgill.com | ||||
| # | ||||
| import ast | ||||
| import platform | ||||
| import sys | ||||
| 
 | ||||
| StandardModules = { | ||||
| # skip argparse now that it's in lib/spack/external | ||||
| #    "argparse":         (3, 2), | ||||
|     "faulthandler":     (3, 3), | ||||
|     "importlib":        (3, 1), | ||||
|     "ipaddress":        (3, 3), | ||||
|     "lzma":             (3, 3), | ||||
|     "tkinter.ttk":      (3, 1), | ||||
|     "unittest.mock":    (3, 3), | ||||
|     "venv":             (3, 3), | ||||
| } | ||||
| 
 | ||||
| Functions = { | ||||
|     "bytearray.maketrans":                      (3, 1), | ||||
|     "bytes.maketrans":                          (3, 1), | ||||
|     "bz2.open":                                 (3, 3), | ||||
|     "collections.Counter":                      (3, 1), | ||||
|     "collections.OrderedDict":                  (3, 1), | ||||
|     "crypt.mksalt":                             (3, 3), | ||||
|     "email.generator.BytesGenerator":           (3, 2), | ||||
|     "email.message_from_binary_file":           (3, 2), | ||||
|     "email.message_from_bytes":                 (3, 2), | ||||
|     "functools.lru_cache":                      (3, 2), | ||||
|     "gzip.compress":                            (3, 2), | ||||
|     "gzip.decompress":                          (3, 2), | ||||
|     "inspect.getclosurevars":                   (3, 3), | ||||
|     "inspect.getgeneratorlocals":               (3, 3), | ||||
|     "inspect.getgeneratorstate":                (3, 2), | ||||
|     "itertools.combinations_with_replacement":  (3, 1), | ||||
|     "itertools.compress":                       (3, 1), | ||||
|     "logging.config.dictConfig":                (3, 2), | ||||
|     "logging.NullHandler":                      (3, 1), | ||||
|     "math.erf":                                 (3, 2), | ||||
|     "math.erfc":                                (3, 2), | ||||
|     "math.expm1":                               (3, 2), | ||||
|     "math.gamma":                               (3, 2), | ||||
|     "math.isfinite":                            (3, 2), | ||||
|     "math.lgamma":                              (3, 2), | ||||
|     "math.log2":                                (3, 3), | ||||
|     "os.environb":                              (3, 2), | ||||
|     "os.fsdecode":                              (3, 2), | ||||
|     "os.fsencode":                              (3, 2), | ||||
|     "os.fwalk":                                 (3, 3), | ||||
|     "os.getenvb":                               (3, 2), | ||||
|     "os.get_exec_path":                         (3, 2), | ||||
|     "os.getgrouplist":                          (3, 3), | ||||
|     "os.getpriority":                           (3, 3), | ||||
|     "os.getresgid":                             (3, 2), | ||||
|     "os.getresuid":                             (3, 2), | ||||
|     "os.get_terminal_size":                     (3, 3), | ||||
|     "os.getxattr":                              (3, 3), | ||||
|     "os.initgroups":                            (3, 2), | ||||
|     "os.listxattr":                             (3, 3), | ||||
|     "os.lockf":                                 (3, 3), | ||||
|     "os.pipe2":                                 (3, 3), | ||||
|     "os.posix_fadvise":                         (3, 3), | ||||
|     "os.posix_fallocate":                       (3, 3), | ||||
|     "os.pread":                                 (3, 3), | ||||
|     "os.pwrite":                                (3, 3), | ||||
|     "os.readv":                                 (3, 3), | ||||
|     "os.removexattr":                           (3, 3), | ||||
|     "os.replace":                               (3, 3), | ||||
|     "os.sched_get_priority_max":                (3, 3), | ||||
|     "os.sched_get_priority_min":                (3, 3), | ||||
|     "os.sched_getaffinity":                     (3, 3), | ||||
|     "os.sched_getparam":                        (3, 3), | ||||
|     "os.sched_getscheduler":                    (3, 3), | ||||
|     "os.sched_rr_get_interval":                 (3, 3), | ||||
|     "os.sched_setaffinity":                     (3, 3), | ||||
|     "os.sched_setparam":                        (3, 3), | ||||
|     "os.sched_setscheduler":                    (3, 3), | ||||
|     "os.sched_yield":                           (3, 3), | ||||
|     "os.sendfile":                              (3, 3), | ||||
|     "os.setpriority":                           (3, 3), | ||||
|     "os.setresgid":                             (3, 2), | ||||
|     "os.setresuid":                             (3, 2), | ||||
|     "os.setxattr":                              (3, 3), | ||||
|     "os.sync":                                  (3, 3), | ||||
|     "os.truncate":                              (3, 3), | ||||
|     "os.waitid":                                (3, 3), | ||||
|     "os.writev":                                (3, 3), | ||||
|     "shutil.chown":                             (3, 3), | ||||
|     "shutil.disk_usage":                        (3, 3), | ||||
|     "shutil.get_archive_formats":               (3, 3), | ||||
|     "shutil.get_terminal_size":                 (3, 3), | ||||
|     "shutil.get_unpack_formats":                (3, 3), | ||||
|     "shutil.make_archive":                      (3, 3), | ||||
|     "shutil.register_archive_format":           (3, 3), | ||||
|     "shutil.register_unpack_format":            (3, 3), | ||||
|     "shutil.unpack_archive":                    (3, 3), | ||||
|     "shutil.unregister_archive_format":         (3, 3), | ||||
|     "shutil.unregister_unpack_format":          (3, 3), | ||||
|     "shutil.which":                             (3, 3), | ||||
|     "signal.pthread_kill":                      (3, 3), | ||||
|     "signal.pthread_sigmask":                   (3, 3), | ||||
|     "signal.sigpending":                        (3, 3), | ||||
|     "signal.sigtimedwait":                      (3, 3), | ||||
|     "signal.sigwait":                           (3, 3), | ||||
|     "signal.sigwaitinfo":                       (3, 3), | ||||
|     "socket.CMSG_LEN":                          (3, 3), | ||||
|     "socket.CMSG_SPACE":                        (3, 3), | ||||
|     "socket.fromshare":                         (3, 3), | ||||
|     "socket.if_indextoname":                    (3, 3), | ||||
|     "socket.if_nameindex":                      (3, 3), | ||||
|     "socket.if_nametoindex":                    (3, 3), | ||||
|     "socket.sethostname":                       (3, 3), | ||||
|     "ssl.match_hostname":                       (3, 2), | ||||
|     "ssl.RAND_bytes":                           (3, 3), | ||||
|     "ssl.RAND_pseudo_bytes":                    (3, 3), | ||||
|     "ssl.SSLContext":                           (3, 2), | ||||
|     "ssl.SSLEOFError":                          (3, 3), | ||||
|     "ssl.SSLSyscallError":                      (3, 3), | ||||
|     "ssl.SSLWantReadError":                     (3, 3), | ||||
|     "ssl.SSLWantWriteError":                    (3, 3), | ||||
|     "ssl.SSLZeroReturnError":                   (3, 3), | ||||
|     "stat.filemode":                            (3, 3), | ||||
|     "textwrap.indent":                          (3, 3), | ||||
|     "threading.get_ident":                      (3, 3), | ||||
|     "time.clock_getres":                        (3, 3), | ||||
|     "time.clock_gettime":                       (3, 3), | ||||
|     "time.clock_settime":                       (3, 3), | ||||
|     "time.get_clock_info":                      (3, 3), | ||||
|     "time.monotonic":                           (3, 3), | ||||
|     "time.perf_counter":                        (3, 3), | ||||
|     "time.process_time":                        (3, 3), | ||||
|     "types.new_class":                          (3, 3), | ||||
|     "types.prepare_class":                      (3, 3), | ||||
| } | ||||
| 
 | ||||
| def uniq(a): | ||||
|     if len(a) == 0: | ||||
|         return [] | ||||
|     else: | ||||
|         return [a[0]] + uniq([x for x in a if x != a[0]]) | ||||
| 
 | ||||
| class NodeChecker(ast.NodeVisitor): | ||||
|     def __init__(self): | ||||
|         self.vers = dict() | ||||
|         self.vers[(3,0)] = [] | ||||
|     def add(self, node, ver, msg): | ||||
|         if ver not in self.vers: | ||||
|             self.vers[ver] = [] | ||||
|         self.vers[ver].append((node.lineno, msg)) | ||||
|     def visit_Call(self, node): | ||||
|         def rollup(n): | ||||
|             if isinstance(n, ast.Name): | ||||
|                 return n.id | ||||
|             elif isinstance(n, ast.Attribute): | ||||
|                 r = rollup(n.value) | ||||
|                 if r: | ||||
|                     return r + "." + n.attr | ||||
|         name = rollup(node.func) | ||||
|         if name: | ||||
|             v = Functions.get(name) | ||||
|             if v is not None: | ||||
|                 self.add(node, v, name) | ||||
|         self.generic_visit(node) | ||||
|     def visit_Import(self, node): | ||||
|         for n in node.names: | ||||
|             v = StandardModules.get(n.name) | ||||
|             if v is not None: | ||||
|                 self.add(node, v, n.name) | ||||
|         self.generic_visit(node) | ||||
|     def visit_ImportFrom(self, node): | ||||
|         v = StandardModules.get(node.module) | ||||
|         if v is not None: | ||||
|             self.add(node, v, node.module) | ||||
|         for n in node.names: | ||||
|             name = node.module + "." + n.name | ||||
|             v = Functions.get(name) | ||||
|             if v is not None: | ||||
|                 self.add(node, v, name) | ||||
|     def visit_Raise(self, node): | ||||
|         if isinstance(node.cause, ast.Name) and node.cause.id == "None": | ||||
|             self.add(node, (3,3), "raise ... from None") | ||||
|     def visit_YieldFrom(self, node): | ||||
|         self.add(node, (3,3), "yield from") | ||||
| 
 | ||||
| def get_versions(source, filename=None): | ||||
|     """Return information about the Python versions required for specific features. | ||||
| 
 | ||||
|     The return value is a dictionary with keys as a version number as a tuple | ||||
|     (for example Python 3.1 is (3,1)) and the value are a list of features that | ||||
|     require the indicated Python version. | ||||
|     """ | ||||
|     tree = ast.parse(source, filename=filename) | ||||
|     checker = NodeChecker() | ||||
|     checker.visit(tree) | ||||
|     return checker.vers | ||||
| 
 | ||||
| def v33(source): | ||||
|     if sys.version_info >= (3, 3): | ||||
|         return qver(source) | ||||
|     else: | ||||
|         print("Not all features tested, run --test with Python 3.3", file=sys.stderr) | ||||
|         return (3, 3) | ||||
| 
 | ||||
| def qver(source): | ||||
|     """Return the minimum Python version required to run a particular bit of code. | ||||
| 
 | ||||
|     >>> qver('print("hello world")') | ||||
|     (3, 0) | ||||
|     >>> qver("import importlib") | ||||
|     (3, 1) | ||||
|     >>> qver("from importlib import x") | ||||
|     (3, 1) | ||||
|     >>> qver("import tkinter.ttk") | ||||
|     (3, 1) | ||||
|     >>> qver("from collections import Counter") | ||||
|     (3, 1) | ||||
|     >>> qver("collections.OrderedDict()") | ||||
|     (3, 1) | ||||
|     >>> qver("import functools\\n@functools.lru_cache()\\ndef f(x): x*x") | ||||
|     (3, 2) | ||||
|     >>> v33("yield from x") | ||||
|     (3, 3) | ||||
|     >>> v33("raise x from None") | ||||
|     (3, 3) | ||||
|     """ | ||||
|     return max(get_versions(source).keys()) | ||||
| @@ -8,7 +8,7 @@ | ||||
| import warnings | ||||
| 
 | ||||
| try: | ||||
|     from collections.abc import Sequence | ||||
|     from collections.abc import Sequence  # novm | ||||
| except ImportError: | ||||
|     from collections import Sequence | ||||
| 
 | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| import os.path | ||||
| 
 | ||||
| try: | ||||
|     from collections.abc import MutableMapping | ||||
|     from collections.abc import MutableMapping  # novm | ||||
| except ImportError: | ||||
|     from collections import MutableMapping | ||||
| 
 | ||||
|   | ||||
| @@ -612,12 +612,14 @@ def load_module_from_file(module_name, module_path): | ||||
|     """ | ||||
|     if sys.version_info[0] == 3 and sys.version_info[1] >= 5: | ||||
|         import importlib.util | ||||
|         spec = importlib.util.spec_from_file_location(module_name, module_path) | ||||
|         module = importlib.util.module_from_spec(spec) | ||||
|         spec = importlib.util.spec_from_file_location(  # novm | ||||
|             module_name, module_path) | ||||
|         module = importlib.util.module_from_spec(spec)  # novm | ||||
|         spec.loader.exec_module(module) | ||||
|     elif sys.version_info[0] == 3 and sys.version_info[1] < 5: | ||||
|         import importlib.machinery | ||||
|         loader = importlib.machinery.SourceFileLoader(module_name, module_path) | ||||
|         loader = importlib.machinery.SourceFileLoader(  # novm | ||||
|             module_name, module_path) | ||||
|         module = loader.load_module() | ||||
|     elif sys.version_info[0] == 2: | ||||
|         import imp | ||||
|   | ||||
| @@ -24,7 +24,7 @@ | ||||
| from ordereddict_backport import OrderedDict | ||||
| 
 | ||||
| try: | ||||
|     from collections.abc import Mapping | ||||
|     from collections.abc import Mapping  # novm | ||||
| except ImportError: | ||||
|     from collections import Mapping | ||||
| 
 | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
| from six import string_types, add_metaclass | ||||
| 
 | ||||
| try: | ||||
|     from collections.abc import Mapping | ||||
|     from collections.abc import Mapping  # novm | ||||
| except ImportError: | ||||
|     from collections import Mapping | ||||
| 
 | ||||
|   | ||||
| @@ -1,159 +0,0 @@ | ||||
| # Copyright 2013-2019 Lawrence Livermore National Security, LLC and other | ||||
| # Spack Project Developers. See the top-level COPYRIGHT file for details. | ||||
| # | ||||
| # SPDX-License-Identifier: (Apache-2.0 OR MIT) | ||||
| 
 | ||||
| """Check that Spack complies with minimum supported python versions. | ||||
| 
 | ||||
| We ensure that all Spack files work with Python2 >= 2.6 and Python3 >= 3.0. | ||||
| 
 | ||||
| We'd like to drop 2.6 support at some point, but there are still many HPC | ||||
| systems that ship with RHEL6/CentOS 6, which have Python 2.6 as the | ||||
| default version.  Once those go away, we can likely drop 2.6 and increase | ||||
| the minimum supported Python 3 version, as well. | ||||
| """ | ||||
| from __future__ import print_function | ||||
| 
 | ||||
| import os | ||||
| import sys | ||||
| import re | ||||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| import llnl.util.tty as tty | ||||
| 
 | ||||
| import spack.paths | ||||
| from spack.paths import lib_path as spack_lib_path | ||||
| 
 | ||||
| 
 | ||||
| # | ||||
| # This test uses pyqver, by Greg Hewgill, which is a dual-source module. | ||||
| # That means we need to do different checks depending on whether we're | ||||
| # running Python 2 or Python 3. | ||||
| # | ||||
| if sys.version_info[0] < 3: | ||||
|     import pyqver2 as pyqver | ||||
|     spack_min_supported = (2, 6) | ||||
| 
 | ||||
|     # Exclude Python 3 versions of dual-source modules when using Python 2 | ||||
|     exclude_paths = [ | ||||
|         # Jinja 2 has some 'async def' functions that are not treated correctly | ||||
|         # by pyqver.py | ||||
|         os.path.join(spack_lib_path, 'external', 'jinja2', 'asyncfilters.py'), | ||||
|         os.path.join(spack_lib_path, 'external', 'jinja2', 'asyncsupport.py'), | ||||
|         os.path.join(spack_lib_path, 'external', 'yaml', 'lib3'), | ||||
|         os.path.join(spack_lib_path, 'external', 'pyqver3.py'), | ||||
|         # Uses importlib | ||||
|         os.path.join(spack_lib_path, 'spack', 'test', 'schema.py') | ||||
|     ] | ||||
| 
 | ||||
| else: | ||||
|     import pyqver3 as pyqver | ||||
|     spack_min_supported = (3, 0) | ||||
| 
 | ||||
|     # Exclude Python 2 versions of dual-source modules when using Python 3 | ||||
|     exclude_paths = [ | ||||
|         # Jinja 2 has some 'async def' functions that are not treated correctly | ||||
|         # by pyqver.py | ||||
|         os.path.join(spack_lib_path, 'external', 'jinja2', 'asyncfilters.py'), | ||||
|         os.path.join(spack_lib_path, 'external', 'jinja2', 'asyncsupport.py'), | ||||
|         os.path.join(spack_lib_path, 'external', 'yaml', 'lib'), | ||||
|         os.path.join(spack_lib_path, 'external', 'pyqver2.py'), | ||||
|         # Uses importlib | ||||
|         os.path.join(spack_lib_path, 'spack', 'test', 'schema.py') | ||||
|     ] | ||||
| 
 | ||||
| 
 | ||||
| def pyfiles(search_paths, exclude=()): | ||||
|     """Generator that yields all the python files in the search paths. | ||||
| 
 | ||||
|     Args: | ||||
|         search_paths (list of str): list of paths to search for python files | ||||
|         exclude (list of str): file paths to exclude from search | ||||
| 
 | ||||
|     Yields: | ||||
|         python files in the search path. | ||||
|     """ | ||||
|     # first file is the spack script. | ||||
|     yield spack.paths.spack_script | ||||
| 
 | ||||
|     # Iterate through the whole spack source tree. | ||||
|     for path in search_paths: | ||||
|         for root, dirnames, filenames in os.walk(path): | ||||
|             for filename in filenames: | ||||
|                 realpath = os.path.realpath(os.path.join(root, filename)) | ||||
|                 if any(realpath.startswith(p) for p in exclude): | ||||
|                     continue | ||||
| 
 | ||||
|                 if re.match(r'^[^.#].*\.py$', filename): | ||||
|                     yield os.path.join(root, filename) | ||||
| 
 | ||||
| 
 | ||||
| def check_python_versions(files): | ||||
|     """Check that a set of Python files works with supported Ptyhon versions""" | ||||
|     # This is a dict dict mapping: | ||||
|     #   version -> filename -> reasons | ||||
|     # | ||||
|     # Reasons are tuples of (lineno, string), where the string is the | ||||
|     # cause for a version incompatibility. | ||||
|     all_issues = {} | ||||
| 
 | ||||
|     # Parse files and run pyqver on each file. | ||||
|     for path in files: | ||||
|         with open(path) as pyfile: | ||||
|             full_text = pyfile.read() | ||||
|         versions = pyqver.get_versions(full_text, path) | ||||
| 
 | ||||
|         for ver, reasons in versions.items(): | ||||
|             if ver <= spack_min_supported: | ||||
|                 continue | ||||
| 
 | ||||
|             # Record issues. Mark exceptions with '# nopyqver' comment | ||||
|             for lineno, cause in reasons: | ||||
|                 lines = full_text.split('\n') | ||||
|                 if not re.search(r'#\s*nopyqver\s*$', lines[lineno - 1]): | ||||
|                     all_issues.setdefault(ver, {})[path] = reasons | ||||
| 
 | ||||
|     # Print a message if there are are issues | ||||
|     if all_issues: | ||||
|         tty.msg("Spack must remain compatible with Python version %d.%d" | ||||
|                 % spack_min_supported) | ||||
| 
 | ||||
|     # Print out a table showing which files/linenos require which | ||||
|     # python version, and a string describing why. | ||||
|     for v in sorted(all_issues.keys(), reverse=True): | ||||
|         messages = [] | ||||
|         for path in sorted(all_issues[v].keys()): | ||||
|             short_path = path | ||||
|             if path.startswith(spack.paths.prefix): | ||||
|                 short_path = path[len(spack.paths.prefix):] | ||||
| 
 | ||||
|             reasons = [r for r in set(all_issues[v][path]) if r] | ||||
|             for lineno, cause in reasons: | ||||
|                 file_line = "%s:%s" % (short_path.lstrip('/'), lineno) | ||||
|                 messages.append((file_line, cause)) | ||||
| 
 | ||||
|         print() | ||||
|         tty.msg("These files require version %d.%d:" % v) | ||||
|         maxlen = max(len(f) for f, prob in messages) | ||||
|         fmt = "%%-%ds%%s" % (maxlen + 3) | ||||
|         print(fmt % ('File', 'Reason')) | ||||
|         print(fmt % ('-' * (maxlen), '-' * 20)) | ||||
|         for msg in messages: | ||||
|             print(fmt % msg) | ||||
| 
 | ||||
|     # Fail this test if there were issues. | ||||
|     assert not all_issues | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.maybeslow | ||||
| def test_core_module_compatibility(): | ||||
|     """Test that all core spack modules work with supported Python versions.""" | ||||
|     check_python_versions( | ||||
|         pyfiles([spack_lib_path], exclude=exclude_paths)) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.maybeslow | ||||
| def test_package_module_compatibility(): | ||||
|     """Test that all spack packages work with supported Python versions.""" | ||||
|     check_python_versions(pyfiles([spack.paths.packages_path])) | ||||
| @@ -107,7 +107,7 @@ def test_module_suffixes(module_suffixes_schema): | ||||
|     'repos' | ||||
| ]) | ||||
| def test_schema_validation(meta_schema, config_name): | ||||
|     import importlib | ||||
|     import importlib  # novm | ||||
|     module_name = 'spack.schema.{0}'.format(config_name) | ||||
|     module = importlib.import_module(module_name) | ||||
|     schema = getattr(module, 'schema') | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
| 
 | ||||
| ``importlib`` is only fully implemented in Python 3. | ||||
| """ | ||||
| from importlib.machinery import SourceFileLoader | ||||
| from importlib.machinery import SourceFileLoader  # novm | ||||
| 
 | ||||
| 
 | ||||
| class PrependFileLoader(SourceFileLoader): | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
| import spack.error as error | ||||
| 
 | ||||
| try: | ||||
|     from collections.abc import Sequence | ||||
|     from collections.abc import Sequence  # novm | ||||
| except ImportError: | ||||
|     from collections import Sequence | ||||
| 
 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user