This repository has been archived by the owner on Sep 14, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
__init__.py
207 lines (180 loc) · 7.38 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# -*- coding: utf-8 -*-
from __future__ import print_function
import logging
import os
import pytest
import subprocess
import shlex
import sys
from pipes import quote
import yaml
try:
from molecule.api import drivers
except ImportError:
# molecule<3.0
from molecule.api import molecule_drivers as drivers
def pytest_addoption(parser):
group = parser.getgroup("molecule")
help_msg = (
"What marker to add to molecule scenarios when driver is "
"unavailable. (ex: skip, xfail)"
)
default = "skip"
dest = "molecule_unavailable_driver"
group.addoption(
"--molecule-unavailable-driver",
action="store",
dest=dest,
default=default,
help=help_msg,
)
parser.addini(dest, help_msg, default=default)
def pytest_configure(config):
config.option.molecule = {}
for driver in drivers():
config.addinivalue_line(
"markers", "{0}: mark test to run only when {0} is available".format(driver)
)
config.option.molecule[driver] = {"available": True}
# TODO(ssbarnea): extend molecule itself to allow it to report usable drivers
if driver == "docker":
try:
import docker
# validate docker connectivity
# Default docker value is 60s but we want to fail faster
# With parallel execution 5s proved to give errors.
c = docker.from_env(timeout=10, version="auto")
if not c.ping():
raise Exception("Failed to ping docker server.")
except Exception as e:
msg = "Molecule {} driver is not available due to: {}.".format(
driver, e
)
if config.option.molecule_unavailable_driver:
msg += " We will tag scenarios using it with '{}' marker.".format(
config.option.molecule_unavailable_driver
)
logging.getLogger().warning(msg)
config.option.molecule[driver]["available"] = False
if driver == "delegated":
# To protect ourselves from case where a molecule scenario using
# `delegated` is accidentally altering the localhost on a developer
# machine, we verify run delegated tests only when ansible `zuul`
# or `use_for_testing` vars are defined.
cmd = [
"ansible",
"localhost",
"-e",
"ansible_connection=local" "-o",
"-m",
"shell",
"-a",
"exit {% if zuul is defined or use_for_testing is defined %}0{% else %}1{% endif %}",
]
try:
p = subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
universal_newlines=True,
)
p.wait()
if p.returncode != 0:
raise Exception(
"Error code %s returned by: %s" % (p.returncode, " ".join(cmd))
)
except Exception:
msg = "Molecule {} driver was not enabled because missing zuul.build variable in current inventory.".format(
driver
)
if config.option.molecule_unavailable_driver:
msg += " We will tag scenarios using it with '{}' marker.".format(
config.option.molecule_unavailable_driver
)
logging.getLogger().warning(msg)
config.option.molecule[driver]["available"] = False
config.addinivalue_line("markers", "molecule: mark used by all molecule scenarios")
# validate selinux availability
if sys.platform == "linux" and os.path.isfile("/etc/selinux/config"):
try:
import selinux # noqa
except Exception:
logging.error(
"It appears that you are trying to use "
"molecule with a Python interpreter that does not have the "
"libselinux python bindings installed. These can only be "
"installed using your distro package manager and are specific "
"to each python version. Common package names: "
"libselinux-python python2-libselinux python3-libselinux"
)
# we do not re-raise this exception because missing or broken
# selinux bindings are not guaranteed to fail molecule execution.
def pytest_collect_file(parent, path):
if path.basename == "molecule.yml":
return MoleculeFile(path, parent)
class MoleculeFile(pytest.File):
def collect(self):
yield MoleculeItem("test", self)
def __str__(self):
return str(self.fspath.relto(os.getcwd()))
class MoleculeItem(pytest.Item):
def __init__(self, name, parent):
super(MoleculeItem, self).__init__(name, parent)
stream = open(str(self.fspath), "r")
data = yaml.load(stream, Loader=yaml.SafeLoader)
# we add the driver as mark
self.molecule_driver = data["driver"]["name"]
self.add_marker(self.molecule_driver)
# we also add platforms as marks
for x in data["platforms"]:
p = x["name"]
self.config.addinivalue_line(
"markers", "{0}: molecule platform name is {0}".format(p)
)
self.add_marker(p)
self.add_marker("molecule")
if (
self.config.option.molecule_unavailable_driver
and not self.config.option.molecule[self.molecule_driver]["available"]
):
self.add_marker(self.config.option.molecule_unavailable_driver)
def runtest(self):
folders = self.fspath.dirname.split(os.sep)
cwd = os.path.abspath(os.path.join(self.fspath.dirname, "../.."))
scenario = folders[-1]
role = folders[-3] # noqa
cmd = [sys.executable, "-m", "molecule", self.name, "-s", scenario]
# We append the additional options to molecule call, allowing user to
# control how molecule is called by pytest-molecule
opts = os.environ.get("MOLECULE_OPTS")
if opts:
cmd.extend(shlex.split(opts))
print("running: %s (from %s)" % (" ".join(quote(arg) for arg in cmd), cwd))
try:
# Workaround for STDOUT/STDERR line ordering issue:
# https://github.com/pytest-dev/pytest/issues/5449
p = subprocess.Popen(
cmd,
cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
)
for line in p.stdout:
print(line, end="")
p.wait()
if p.returncode != 0:
pytest.fail(
"Error code %s returned by: %s" % (p.returncode, " ".join(cmd)),
pytrace=False,
)
except Exception as e:
pytest.fail(
"Exception %s returned by: %s" % (e, " ".join(cmd)), pytrace=False
)
def reportinfo(self):
return self.fspath, 0, "usecase: %s" % self.name
def __str__(self):
return "{}[{}]".format(self.name, self.molecule_driver)
class MoleculeException(Exception):
""" custom exception for error reporting. """