Detect and fix environment tampering in tests.

This commit is contained in:
Matt Clay 2017-05-11 19:05:21 +08:00
parent 99d328976d
commit b8cb3f519b
3 changed files with 109 additions and 4 deletions

View file

@ -17,13 +17,24 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# ============================================================
- name: python 2
set_fact:
python_suffix: ""
when: ansible_python_version | version_compare('3', '<')
- name: python 3
set_fact:
python_suffix: "-py3"
when: ansible_python_version | version_compare('3', '>=')
- include_vars: '{{ item }}'
with_first_found:
- files:
- '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml'
- '{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml'
- '{{ ansible_distribution }}.yml'
- '{{ ansible_os_family }}.yml'
- '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}{{ python_suffix }}.yml'
- '{{ ansible_os_family }}-{{ ansible_distribution_major_version }}{{ python_suffix }}.yml'
- '{{ ansible_distribution }}{{ python_suffix }}.yml'
- '{{ ansible_os_family }}{{ python_suffix }}.yml'
- 'default{{ python_suffix }}.yml'
paths: '../vars'
- name: install mysqldb_test rpm dependencies

View file

@ -0,0 +1,6 @@
mysql_service: 'mysql'
mysql_packages:
- mysql-server
- python3-mysqldb
- bzip2

View file

@ -2,6 +2,7 @@
from __future__ import absolute_import, print_function
import json
import os
import re
import tempfile
@ -48,6 +49,8 @@ from lib.util import (
make_dirs,
is_shippable,
is_binary_file,
find_executable,
raw_command,
)
from lib.test import (
@ -518,6 +521,10 @@ def command_integration_filtered(args, targets):
cloud_environment = get_cloud_environment(args, target)
original_environment = EnvironmentDescription()
display.info('>>> Environment Description\n%s' % original_environment, verbosity=3)
try:
while tries:
tries -= 1
@ -538,11 +545,16 @@ def command_integration_filtered(args, targets):
if cloud_environment:
cloud_environment.on_failure(target, tries)
if not original_environment.validate(target.name, throw=False):
raise
if not tries:
raise
display.warning('Retrying test target "%s" with maximum verbosity.' % target.name)
display.verbosity = args.verbosity = 6
original_environment.validate(target.name, throw=True)
except:
display.notice('To resume at this test target, use the option: --start-at %s' % target.name)
@ -1135,6 +1147,82 @@ def get_integration_remote_filter(args, targets):
return exclude
class EnvironmentDescription(object):
"""Description of current running environment."""
def __init__(self):
"""Initialize snapshot of environment configuration."""
versions = ['']
versions += 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_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_versions = dict((v, self.get_version([pip_paths[v], '--version'])) 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])
self.data = dict(
python_paths=python_paths,
python_versions=python_versions,
pip_paths=pip_paths,
pip_versions=pip_versions,
pip_interpreters=pip_interpreters,
)
def __str__(self):
"""
:rtype: str
"""
return json.dumps(self.data, sort_keys=True, indent=4)
def validate(self, target_name, throw):
"""
:type target_name: str
:type throw: bool
:rtype: bool
"""
current = EnvironmentDescription()
original_json = str(self)
current_json = str(current)
if original_json == current_json:
return True
message = ('Test target "%s" has changed the test environment!\n'
'If these changes are necessary, they must be reverted before the test finishes.\n'
'>>> Original Environment\n'
'%s\n'
'>>> Current Environment\n'
'%s' % (target_name, original_json, current_json))
if throw:
raise ApplicationError(message)
display.error(message)
return False
@staticmethod
def get_version(command):
"""
:type command: list[str]
:rtype: str
"""
stdout, stderr = raw_command(command, capture=True)
return (stdout or '').strip() + (stderr or '').strip()
@staticmethod
def get_shebang(path):
"""
:type path: str
:rtype: str
"""
with open(path) as script_fd:
return script_fd.readline()
class NoChangesDetected(ApplicationWarning):
"""Exception when change detection was performed, but no changes were found."""
def __init__(self):