@ -14,6 +14,8 @@ import functools
import pipes
import pipes
import sys
import sys
import hashlib
import hashlib
import difflib
import filecmp
import lib . pytar
import lib . pytar
import lib . thread
import lib . thread
@ -741,6 +743,8 @@ def command_integration_filtered(args, targets, all_targets):
results = { }
results = { }
current_environment = None # type: EnvironmentDescription | None
for target in targets_iter :
for target in targets_iter :
if args . start_at and not found :
if args . start_at and not found :
found = target . name == args . start_at
found = target . name == args . start_at
@ -757,7 +761,8 @@ def command_integration_filtered(args, targets, all_targets):
cloud_environment = get_cloud_environment ( args , target )
cloud_environment = get_cloud_environment ( args , target )
original_environment = EnvironmentDescription ( args )
original_environment = current_environment if current_environment else EnvironmentDescription ( args )
current_environment = None
display . info ( ' >>> Environment Description \n %s ' % original_environment , verbosity = 3 )
display . info ( ' >>> Environment Description \n %s ' % original_environment , verbosity = 3 )
@ -816,9 +821,11 @@ def command_integration_filtered(args, targets, all_targets):
display . verbosity = args . verbosity = 6
display . verbosity = args . verbosity = 6
start_time = time . time ( )
start_time = time . time ( )
original_environment. validate ( target . name , throw = True )
current_environment = EnvironmentDescription ( args )
end_time = time . time ( )
end_time = time . time ( )
EnvironmentDescription . check ( original_environment , current_environment , target . name , throw = True )
results [ target . name ] [ ' validation_seconds ' ] = int ( end_time - start_time )
results [ target . name ] [ ' validation_seconds ' ] = int ( end_time - start_time )
passed . append ( target )
passed . append ( target )
@ -1534,27 +1541,84 @@ class EnvironmentDescription(object):
self . data = { }
self . data = { }
return
return
warnings = [ ]
versions = [ ' ' ]
versions = [ ' ' ]
versions + = SUPPORTED_PYTHON_VERSIONS
versions + = SUPPORTED_PYTHON_VERSIONS
versions + = list ( set ( v . split ( ' . ' ) [ 0 ] for v in SUPPORTED_PYTHON_VERSIONS ) )
versions + = list ( set ( v . split ( ' . ' ) [ 0 ] for v in SUPPORTED_PYTHON_VERSIONS ) )
python_paths = dict ( ( v , find_executable ( ' python %s ' % v , required = False ) ) for v in sorted ( versions ) )
python_paths = dict ( ( v , find_executable ( ' python %s ' % v , required = False ) ) for v in sorted ( versions ) )
python_versions = dict ( ( v , self . get_version ( [ python_paths [ v ] , ' -V ' ] ) ) for v in sorted ( python_paths ) if python_paths [ v ] )
pip_paths = dict ( ( v , find_executable ( ' pip %s ' % v , required = False ) ) for v in sorted ( versions ) )
pip_paths = dict ( ( v , find_executable ( ' pip %s ' % v , required = False ) ) for v in sorted ( versions ) )
p ip _versions = dict ( ( v , self . get_version ( [ p ip_paths[ v ] , ' --version ' ] ) ) for v in sorted ( pip_paths ) if pip _paths[ v ] )
program_versions = dict ( ( v , self . get_version ( [ python_paths [ v ] , ' test/runner/versions.py ' ] , warnings ) ) for v in sorted ( python_paths ) if python_paths [ v ] )
pip_interpreters = dict ( ( v , self . get_shebang ( pip_paths [ v ] ) ) for v in sorted ( pip_paths ) if pip_paths [ v ] )
pip_interpreters = dict ( ( v , self . get_shebang ( pip_paths [ v ] ) ) for v in sorted ( pip_paths ) if pip_paths [ v ] )
known_hosts_hash = self . get_hash ( os . path . expanduser ( ' ~/.ssh/known_hosts ' ) )
known_hosts_hash = self . get_hash ( os . path . expanduser ( ' ~/.ssh/known_hosts ' ) )
for version in sorted ( versions ) :
self . check_python_pip_association ( version , python_paths , pip_paths , pip_interpreters , warnings )
for warning in warnings :
display . warning ( warning , unique = True )
self . data = dict (
self . data = dict (
python_paths = python_paths ,
python_paths = python_paths ,
python_versions = python_versions ,
pip_paths = pip_paths ,
pip_paths = pip_paths ,
pip_versions = pip_versions ,
p rogram_versions= program _versions,
pip_interpreters = pip_interpreters ,
pip_interpreters = pip_interpreters ,
known_hosts_hash = known_hosts_hash ,
known_hosts_hash = known_hosts_hash ,
warnings = warnings ,
)
)
@staticmethod
def check_python_pip_association ( version , python_paths , pip_paths , pip_interpreters , warnings ) :
"""
: type version : str
: param python_paths : dict [ str , str ]
: param pip_paths : dict [ str , str ]
: param pip_interpreters : dict [ str , str ]
: param warnings : list [ str ]
"""
python_label = ' Python %s ' % ( ' %s ' % version if version else ' ' )
pip_path = pip_paths . get ( version )
python_path = python_paths . get ( version )
if not python_path and not pip_path :
# neither python or pip is present for this version
return
if not python_path :
warnings . append ( ' A %s interpreter was not found, yet a matching pip was found at " %s " . ' % ( python_label , pip_path ) )
return
if not pip_path :
warnings . append ( ' A %s interpreter was found at " %s " , yet a matching pip was not found. ' % ( python_label , python_path ) )
return
pip_shebang = pip_interpreters . get ( version )
match = re . search ( r ' #! \ s*(?P<command>[^ \ s]+) ' , pip_shebang )
if not match :
warnings . append ( ' A %s pip was found at " %s " , but it does not have a valid shebang: %s ' % ( python_label , pip_path , pip_shebang ) )
return
pip_interpreter = os . path . realpath ( match . group ( ' command ' ) )
python_interpreter = os . path . realpath ( python_path )
if pip_interpreter == python_interpreter :
return
try :
identical = filecmp . cmp ( pip_interpreter , python_interpreter )
except OSError :
identical = False
if identical :
return
warnings . append ( ' A %s pip was found at " %s " , but it uses interpreter " %s " instead of " %s " . ' % (
python_label , pip_path , pip_interpreter , python_interpreter ) )
def __str__ ( self ) :
def __str__ ( self ) :
"""
"""
: rtype : str
: rtype : str
@ -1569,18 +1633,40 @@ class EnvironmentDescription(object):
"""
"""
current = EnvironmentDescription ( self . args )
current = EnvironmentDescription ( self . args )
original_json = str ( self )
return self . check ( self , current , target_name , throw )
@staticmethod
def check ( original , current , target_name , throw ) :
"""
: type original : EnvironmentDescription
: type current : EnvironmentDescription
: type target_name : str
: type throw : bool
: rtype : bool
"""
original_json = str ( original )
current_json = str ( current )
current_json = str ( current )
if original_json == current_json :
if original_json == current_json :
return True
return True
unified_diff = ' \n ' . join ( difflib . unified_diff (
a = original_json . splitlines ( ) ,
b = current_json . splitlines ( ) ,
fromfile = ' original.json ' ,
tofile = ' current.json ' ,
lineterm = ' ' ,
) )
message = ( ' Test target " %s " has changed the test environment! \n '
message = ( ' Test target " %s " has changed the test environment! \n '
' If these changes are necessary, they must be reverted before the test finishes. \n '
' If these changes are necessary, they must be reverted before the test finishes. \n '
' >>> Original Environment \n '
' >>> Original Environment \n '
' %s \n '
' %s \n '
' >>> Current Environment \n '
' >>> Current Environment \n '
' %s ' % ( target_name , original_json , current_json ) )
' %s \n '
' >>> Environment Diff \n '
' %s '
% ( target_name , original_json , current_json , unified_diff ) )
if throw :
if throw :
raise ApplicationError ( message )
raise ApplicationError ( message )
@ -1590,17 +1676,19 @@ class EnvironmentDescription(object):
return False
return False
@staticmethod
@staticmethod
def get_version ( command ):
def get_version ( command , warnings ):
"""
"""
: type command : list [ str ]
: type command : list [ str ]
: rtype : str
: type warnings : list [ str ]
: rtype : list [ str ]
"""
"""
try :
try :
stdout , stderr = raw_command ( command , capture = True , cmd_verbosity = 2 )
stdout , stderr = raw_command ( command , capture = True , cmd_verbosity = 2 )
except SubprocessError :
except SubprocessError as ex :
warnings . append ( u ' %s ' % ex )
return None # all failures are equal, we don't care why it failed, only that it did
return None # all failures are equal, we don't care why it failed, only that it did
return (stdout or ' ' ) . strip ( ) + ( stderr or ' ' ) . strip ( )
return [line . strip ( ) for line in ( (stdout or ' ' ) . strip ( ) + ( stderr or ' ' ) . strip ( ) ) . splitlines ( ) ]
@staticmethod
@staticmethod
def get_shebang ( path ) :
def get_shebang ( path ) :
@ -1609,7 +1697,7 @@ class EnvironmentDescription(object):
: rtype : str
: rtype : str
"""
"""
with open ( path ) as script_fd :
with open ( path ) as script_fd :
return script_fd . readline ( )
return script_fd . readline ( ) . strip ( )
@staticmethod
@staticmethod
def get_hash ( path ) :
def get_hash ( path ) :