Merge ~peppepetra/charm-sudo-pair:fix-tests into ~sudo-pair-charmers/charm-sudo-pair:master
- Git
- lp:~peppepetra/charm-sudo-pair
- fix-tests
- Merge into master
Proposed by
Giuseppe Petralia
Status: | Merged |
---|---|
Approved by: | Giuseppe Petralia |
Approved revision: | 51a606515c12e0087a05ebb350794530d4f51987 |
Merged at revision: | 51a606515c12e0087a05ebb350794530d4f51987 |
Proposed branch: | ~peppepetra/charm-sudo-pair:fix-tests |
Merge into: | ~sudo-pair-charmers/charm-sudo-pair:master |
Diff against target: |
841 lines (+214/-130) 12 files modified
.gitignore (+33/-7) Makefile (+25/-22) actions/actions.py (+2/-2) lib/libsudopair.py (+22/-2) reactive/sudo_pair.py (+1/-1) tests/functional/conftest.py (+23/-21) tests/functional/requirements.txt (+1/-0) tests/functional/test_deploy.py (+62/-51) tests/unit/conftest.py (+3/-2) tests/unit/requirements.txt (+1/-0) tests/unit/test_libsudopair.py (+18/-4) tox.ini (+23/-18) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alvaro Uria (community) | Approve | ||
Review via email: mp+379381@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/.gitignore b/.gitignore | |||
2 | index a437a87..eda8bea 100644 | |||
3 | --- a/.gitignore | |||
4 | +++ b/.gitignore | |||
5 | @@ -1,7 +1,33 @@ | |||
13 | 1 | __pycache__ | 1 | # Byte-compiled / optimized / DLL files |
14 | 2 | *~ | 2 | __pycache__/ |
15 | 3 | *.swp | 3 | *.py[cod] |
16 | 4 | builds/ | 4 | *$py.class |
17 | 5 | .idea | 5 | |
18 | 6 | .tox | 6 | # Log files |
19 | 7 | /repo-info | 7 | *.log |
20 | 8 | |||
21 | 9 | .tox/ | ||
22 | 10 | src/.tox/ | ||
23 | 11 | .coverage | ||
24 | 12 | |||
25 | 13 | # vi | ||
26 | 14 | .*.swp | ||
27 | 15 | |||
28 | 16 | # pycharm | ||
29 | 17 | .idea/ | ||
30 | 18 | .unit-state.db | ||
31 | 19 | src/.unit-state.db | ||
32 | 20 | |||
33 | 21 | # version data | ||
34 | 22 | repo-info | ||
35 | 23 | |||
36 | 24 | |||
37 | 25 | # reports | ||
38 | 26 | report/* | ||
39 | 27 | src/report/* | ||
40 | 28 | |||
41 | 29 | # virtual env | ||
42 | 30 | venv/* | ||
43 | 31 | |||
44 | 32 | # builds | ||
45 | 33 | builds/* | ||
46 | 8 | \ No newline at end of file | 34 | \ No newline at end of file |
47 | diff --git a/Makefile b/Makefile | |||
48 | index 6c2a104..c7506b2 100644 | |||
49 | --- a/Makefile | |||
50 | +++ b/Makefile | |||
51 | @@ -1,7 +1,9 @@ | |||
56 | 1 | PROJECTPATH = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) | 1 | PROJECTPATH = $(dir $(realpath $(MAKEFILE_LIST))) |
57 | 2 | ifndef JUJU_REPOSITORY | 2 | DIRNAME = $(notdir $(PROJECTPATH:%/=%)) |
58 | 3 | JUJU_REPOSITORY := $(shell pwd) | 3 | |
59 | 4 | $(warning Warning JUJU_REPOSITORY was not set, defaulting to $(JUJU_REPOSITORY)) | 4 | ifndef CHARM_BUILD_DIR |
60 | 5 | CHARM_BUILD_DIR := /tmp/$(DIRNAME)-builds | ||
61 | 6 | $(warning Warning CHARM_BUILD_DIR was not set, defaulting to $(CHARM_BUILD_DIR)) | ||
62 | 5 | endif | 7 | endif |
63 | 6 | 8 | ||
64 | 7 | help: | 9 | help: |
65 | @@ -9,42 +11,43 @@ help: | |||
66 | 9 | @echo "" | 11 | @echo "" |
67 | 10 | @echo " make help - show this text" | 12 | @echo " make help - show this text" |
68 | 11 | @echo " make lint - run flake8" | 13 | @echo " make lint - run flake8" |
71 | 12 | @echo " make test - run the unittests and lint" | 14 | @echo " make test - run the functional tests, unittests and lint" |
72 | 13 | @echo " make unittest - run the tests defined in the unit subdirectory" | 15 | @echo " make unittest - run the tests defined in the unittest subdirectory" |
73 | 14 | @echo " make functional - run the tests defined in the functional subdirectory" | 16 | @echo " make functional - run the tests defined in the functional subdirectory" |
74 | 15 | @echo " make release - build the charm" | 17 | @echo " make release - build the charm" |
75 | 16 | @echo " make clean - remove unneeded files" | 18 | @echo " make clean - remove unneeded files" |
76 | 17 | @echo "" | 19 | @echo "" |
77 | 18 | 20 | ||
78 | 19 | submodules: | ||
79 | 20 | @echo "Cloning submodules" | ||
80 | 21 | @git submodule update --init --recursive | ||
81 | 22 | |||
82 | 23 | lint: | 21 | lint: |
83 | 24 | @echo "Running flake8" | 22 | @echo "Running flake8" |
84 | 25 | @tox -e lint | 23 | @tox -e lint |
85 | 26 | 24 | ||
87 | 27 | test: unittest functional lint | 25 | test: lint unittest functional |
88 | 26 | |||
89 | 27 | functional: build | ||
90 | 28 | @PYTEST_KEEP_MODEL=$(PYTEST_KEEP_MODEL) \ | ||
91 | 29 | PYTEST_CLOUD_NAME=$(PYTEST_CLOUD_NAME) \ | ||
92 | 30 | PYTEST_CLOUD_REGION=$(PYTEST_CLOUD_REGION) \ | ||
93 | 31 | tox -e functional | ||
94 | 28 | 32 | ||
95 | 29 | unittest: | 33 | unittest: |
96 | 30 | @tox -e unit | 34 | @tox -e unit |
97 | 31 | 35 | ||
98 | 32 | functional: build | ||
99 | 33 | @tox -e functional | ||
100 | 34 | |||
101 | 35 | build: | 36 | build: |
106 | 36 | @echo "Building charm to base directory $(JUJU_REPOSITORY)" | 37 | @echo "Building charm to base directory $(CHARM_BUILD_DIR)" |
107 | 37 | @-git describe --tags > $(PROJECTPATH)/repo-info | 38 | @-git describe --tags > ./repo-info |
108 | 38 | @LAYER_PATH=./layers INTERFACE_PATH=./interfaces\ | 39 | @CHARM_LAYERS_DIR=./layers CHARM_INTERFACES_DIR=./interfaces TERM=linux\ |
109 | 39 | JUJU_REPOSITORY=$(JUJU_REPOSITORY) charm build ./ --force | 40 | charm build --output-dir $(CHARM_BUILD_DIR) $(PROJECTPATH) --force |
110 | 40 | 41 | ||
111 | 41 | release: clean build | 42 | release: clean build |
113 | 42 | @echo "Charm is built at $(JUJU_REPOSITORY)/builds" | 43 | @echo "Charm is built at $(CHARM_BUILD_DIR)/builds" |
114 | 43 | 44 | ||
115 | 44 | clean: | 45 | clean: |
116 | 45 | @echo "Cleaning files" | 46 | @echo "Cleaning files" |
119 | 46 | @rm -rf $(PROJECTPATH)/.tox | 47 | @find $(PROJECTPATH) -iname __pycache__ -exec rm -r {} + |
120 | 47 | @rm -rf $(PROJECTPATH)/.pytest_cache | 48 | @if [ -d $(CHARM_BUILD_DIR)/builds ] ; then rm -r $(CHARM_BUILD_DIR)/builds ; fi |
121 | 49 | @if [ -d $(PROJECTPATH)/.tox ] ; then rm -r $(PROJECTPATH)/.tox ; fi | ||
122 | 50 | @if [ -d $(PROJECTPATH)/.pytest_cache ] ; then rm -r $(PROJECTPATH)/.pytest_cache ; fi | ||
123 | 48 | 51 | ||
124 | 49 | # The targets below don't depend on a file | 52 | # The targets below don't depend on a file |
126 | 50 | .PHONY: lint test unittest functional build release clean help submodules | 53 | .PHONY: lint test unittest functional build release clean help |
127 | diff --git a/actions/actions.py b/actions/actions.py | |||
128 | index 7dd03d8..ccf3ffc 100755 | |||
129 | --- a/actions/actions.py | |||
130 | +++ b/actions/actions.py | |||
131 | @@ -16,7 +16,7 @@ | |||
132 | 16 | import os | 16 | import os |
133 | 17 | import sys | 17 | import sys |
134 | 18 | 18 | ||
136 | 19 | from charmhelpers.core.hookenv import action_set, action_fail | 19 | from charmhelpers.core.hookenv import action_fail, action_set |
137 | 20 | 20 | ||
138 | 21 | 21 | ||
139 | 22 | sys.path.append("lib") | 22 | sys.path.append("lib") |
140 | @@ -25,7 +25,7 @@ import libsudopair # NOQA | |||
141 | 25 | 25 | ||
142 | 26 | 26 | ||
143 | 27 | def remove(): | 27 | def remove(): |
145 | 28 | """Action to remove sudo-pair config and binaries""" | 28 | """Remove sudo-pair config and binaries.""" |
146 | 29 | sph = libsudopair.SudoPairHelper() | 29 | sph = libsudopair.SudoPairHelper() |
147 | 30 | sph.deconfigure() | 30 | sph.deconfigure() |
148 | 31 | action_set({"message": "Successfully removed sudo-pair config and binaries"}) | 31 | action_set({"message": "Successfully removed sudo-pair config and binaries"}) |
149 | diff --git a/lib/libsudopair.py b/lib/libsudopair.py | |||
150 | index 095e9a4..05d1f9d 100644 | |||
151 | --- a/lib/libsudopair.py | |||
152 | +++ b/lib/libsudopair.py | |||
153 | @@ -1,9 +1,11 @@ | |||
154 | 1 | import grp | 1 | import grp |
155 | 2 | import os | 2 | import os |
157 | 3 | from charmhelpers.core import host, hookenv, templating | 3 | |
158 | 4 | from charmhelpers.core import hookenv, host, templating | ||
159 | 4 | 5 | ||
160 | 5 | 6 | ||
161 | 6 | def check_valid_group(group_name): | 7 | def check_valid_group(group_name): |
162 | 8 | """Check that a group exists.""" | ||
163 | 7 | try: | 9 | try: |
164 | 8 | grp.getgrnam(group_name) | 10 | grp.getgrnam(group_name) |
165 | 9 | return True | 11 | return True |
166 | @@ -12,12 +14,14 @@ def check_valid_group(group_name): | |||
167 | 12 | 14 | ||
168 | 13 | 15 | ||
169 | 14 | def group_id(group_name): | 16 | def group_id(group_name): |
170 | 17 | """Check that a group exists.""" | ||
171 | 15 | return grp.getgrnam(group_name).gr_gid | 18 | return grp.getgrnam(group_name).gr_gid |
172 | 16 | 19 | ||
173 | 17 | 20 | ||
174 | 18 | def group_names_to_group_ids(group_names): | 21 | def group_names_to_group_ids(group_names): |
175 | 19 | """ | 22 | """ |
177 | 20 | From Group Names comma-separated list to Group Ids | 23 | Return comma-separated list of Group Ids. |
178 | 24 | |||
179 | 21 | :param group_names: i.e. "root,user1,user2" | 25 | :param group_names: i.e. "root,user1,user2" |
180 | 22 | :return gids: i.e. "0,1001,1002" | 26 | :return gids: i.e. "0,1001,1002" |
181 | 23 | """ | 27 | """ |
182 | @@ -26,6 +30,7 @@ def group_names_to_group_ids(group_names): | |||
183 | 26 | 30 | ||
184 | 27 | 31 | ||
185 | 28 | def copy_file(source, destination, owner, group, perms): | 32 | def copy_file(source, destination, owner, group, perms): |
186 | 33 | """Copy a file on the unit.""" | ||
187 | 29 | if destination is not None: | 34 | if destination is not None: |
188 | 30 | target_dir = os.path.dirname(destination) | 35 | target_dir = os.path.dirname(destination) |
189 | 31 | if not os.path.exists(target_dir): | 36 | if not os.path.exists(target_dir): |
190 | @@ -37,7 +42,10 @@ def copy_file(source, destination, owner, group, perms): | |||
191 | 37 | 42 | ||
192 | 38 | 43 | ||
193 | 39 | class SudoPairHelper(object): | 44 | class SudoPairHelper(object): |
194 | 45 | """Configure sudo-pair.""" | ||
195 | 46 | |||
196 | 40 | def __init__(self): | 47 | def __init__(self): |
197 | 48 | """Retrieve charm config and set defaults.""" | ||
198 | 41 | self.charm_config = hookenv.config() | 49 | self.charm_config = hookenv.config() |
199 | 42 | self.binary_path = '/usr/bin/sudo_approve' | 50 | self.binary_path = '/usr/bin/sudo_approve' |
200 | 43 | self.sudo_conf_path = '/etc/sudo.conf' | 51 | self.sudo_conf_path = '/etc/sudo.conf' |
201 | @@ -58,6 +66,7 @@ class SudoPairHelper(object): | |||
202 | 58 | self.sudo_approve_perms = 0o755 | 66 | self.sudo_approve_perms = 0o755 |
203 | 59 | 67 | ||
204 | 60 | def get_config(self): | 68 | def get_config(self): |
205 | 69 | """Return config as a dict.""" | ||
206 | 61 | config = { | 70 | config = { |
207 | 62 | 'binary_path': self.binary_path, | 71 | 'binary_path': self.binary_path, |
208 | 63 | 'user_prompt_path': self.user_prompt_path, | 72 | 'user_prompt_path': self.user_prompt_path, |
209 | @@ -71,41 +80,51 @@ class SudoPairHelper(object): | |||
210 | 71 | return config | 80 | return config |
211 | 72 | 81 | ||
212 | 73 | def set_charm_config(self, charm_config): | 82 | def set_charm_config(self, charm_config): |
213 | 83 | """Update configuration.""" | ||
214 | 74 | self.charm_config = charm_config | 84 | self.charm_config = charm_config |
215 | 75 | 85 | ||
216 | 76 | def render_sudo_conf(self): | 86 | def render_sudo_conf(self): |
217 | 87 | """Render sudo.conf file.""" | ||
218 | 77 | return templating.render('sudo.conf.tmpl', self.sudo_conf_path, self.get_config(), | 88 | return templating.render('sudo.conf.tmpl', self.sudo_conf_path, self.get_config(), |
219 | 78 | perms=self.sudo_conf_perms, owner=self.owner, group=self.group) | 89 | perms=self.sudo_conf_perms, owner=self.owner, group=self.group) |
220 | 79 | 90 | ||
221 | 80 | def create_socket_dir(self): | 91 | def create_socket_dir(self): |
222 | 92 | """Create socket dir.""" | ||
223 | 81 | host.mkdir(self.socket_dir, perms=self.socket_dir_perms, owner=self.owner, group=self.group) | 93 | host.mkdir(self.socket_dir, perms=self.socket_dir_perms, owner=self.owner, group=self.group) |
224 | 82 | 94 | ||
225 | 83 | def create_tmpfiles_conf(self): | 95 | def create_tmpfiles_conf(self): |
226 | 96 | """Create temporary conf file.""" | ||
227 | 84 | with open(self.tmpfiles_conf, "w") as f: | 97 | with open(self.tmpfiles_conf, "w") as f: |
228 | 85 | f.write("d {} 0755 - - -\n".format(self.socket_dir)) | 98 | f.write("d {} 0755 - - -\n".format(self.socket_dir)) |
229 | 86 | 99 | ||
230 | 87 | def install_sudo_pair_so(self): | 100 | def install_sudo_pair_so(self): |
231 | 101 | """Install sudo-pair lib.""" | ||
232 | 88 | sudo_pair_lib = os.path.join(hookenv.charm_dir(), 'files', 'sudo_pair.so') | 102 | sudo_pair_lib = os.path.join(hookenv.charm_dir(), 'files', 'sudo_pair.so') |
233 | 89 | copy_file(sudo_pair_lib, self.sudo_lib_path, self.owner, self.group, self.sudo_pair_so_perms) | 103 | copy_file(sudo_pair_lib, self.sudo_lib_path, self.owner, self.group, self.sudo_pair_so_perms) |
234 | 90 | 104 | ||
235 | 91 | def copy_user_prompt(self): | 105 | def copy_user_prompt(self): |
236 | 106 | """Copy user prompt on the unit.""" | ||
237 | 92 | prompt_file = os.path.join(hookenv.charm_dir(), 'files', 'sudo.prompt.user') | 107 | prompt_file = os.path.join(hookenv.charm_dir(), 'files', 'sudo.prompt.user') |
238 | 93 | copy_file(prompt_file, self.user_prompt_path, self.owner, self.group, self.prompt_perms) | 108 | copy_file(prompt_file, self.user_prompt_path, self.owner, self.group, self.prompt_perms) |
239 | 94 | 109 | ||
240 | 95 | def copy_pair_prompt(self): | 110 | def copy_pair_prompt(self): |
241 | 111 | """Copy pair prompt on the unit.""" | ||
242 | 96 | prompt_file = os.path.join(hookenv.charm_dir(), 'files', 'sudo.prompt.pair') | 112 | prompt_file = os.path.join(hookenv.charm_dir(), 'files', 'sudo.prompt.pair') |
243 | 97 | copy_file(prompt_file, self.pair_prompt_path, self.owner, self.group, self.prompt_perms) | 113 | copy_file(prompt_file, self.pair_prompt_path, self.owner, self.group, self.prompt_perms) |
244 | 98 | 114 | ||
245 | 99 | def copy_sudoers(self): | 115 | def copy_sudoers(self): |
246 | 116 | """Copy sudoers file on the unit.""" | ||
247 | 100 | sudoers_file = os.path.join(hookenv.charm_dir(), 'files', 'sudoers') | 117 | sudoers_file = os.path.join(hookenv.charm_dir(), 'files', 'sudoers') |
248 | 101 | copy_file(sudoers_file, self.sudoers_path, self.owner, self.group, self.sudoers_perms) | 118 | copy_file(sudoers_file, self.sudoers_path, self.owner, self.group, self.sudoers_perms) |
249 | 102 | 119 | ||
250 | 103 | def render_sudo_approve(self): | 120 | def render_sudo_approve(self): |
251 | 121 | """Render sudo-approve file.""" | ||
252 | 104 | hookenv.log("Rendering sudo_approve.tmpl to {}".format(self.binary_path)) | 122 | hookenv.log("Rendering sudo_approve.tmpl to {}".format(self.binary_path)) |
253 | 105 | return templating.render('sudo_approve.tmpl', self.binary_path, self.get_config(), | 123 | return templating.render('sudo_approve.tmpl', self.binary_path, self.get_config(), |
254 | 106 | perms=self.sudo_approve_perms, owner=self.owner, group=self.group) | 124 | perms=self.sudo_approve_perms, owner=self.owner, group=self.group) |
255 | 107 | 125 | ||
256 | 108 | def render_bypass_cmds(self): | 126 | def render_bypass_cmds(self): |
257 | 127 | """Render bypass command file.""" | ||
258 | 109 | if self.get_config()['bypass_cmds'] != "" and self.get_config()['bypass_group'] != "": | 128 | if self.get_config()['bypass_cmds'] != "" and self.get_config()['bypass_group'] != "": |
259 | 110 | hookenv.log("Render bypass cmds to {}".format(self.sudoers_bypass_path)) | 129 | hookenv.log("Render bypass cmds to {}".format(self.sudoers_bypass_path)) |
260 | 111 | return templating.render('91-bypass-sudopair-cmds.tmpl', self.sudoers_bypass_path, | 130 | return templating.render('91-bypass-sudopair-cmds.tmpl', self.sudoers_bypass_path, |
261 | @@ -113,6 +132,7 @@ class SudoPairHelper(object): | |||
262 | 113 | return None | 132 | return None |
263 | 114 | 133 | ||
264 | 115 | def deconfigure(self): | 134 | def deconfigure(self): |
265 | 135 | """Remove sudo-pair configuration.""" | ||
266 | 116 | paths = [ | 136 | paths = [ |
267 | 117 | self.sudo_conf_path, | 137 | self.sudo_conf_path, |
268 | 118 | self.sudo_lib_path, | 138 | self.sudo_lib_path, |
269 | diff --git a/reactive/sudo_pair.py b/reactive/sudo_pair.py | |||
270 | index be557f8..974dd66 100644 | |||
271 | --- a/reactive/sudo_pair.py | |||
272 | +++ b/reactive/sudo_pair.py | |||
273 | @@ -1,6 +1,6 @@ | |||
274 | 1 | from charms.reactive import when, when_not, set_state, remove_state, hook | ||
275 | 2 | from charmhelpers.core import hookenv | 1 | from charmhelpers.core import hookenv |
276 | 3 | 2 | ||
277 | 3 | from charms.reactive import hook, remove_state, set_state, when, when_not | ||
278 | 4 | 4 | ||
279 | 5 | from libsudopair import SudoPairHelper | 5 | from libsudopair import SudoPairHelper |
280 | 6 | 6 | ||
281 | diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py | |||
282 | index 7279748..aa42a09 100644 | |||
283 | --- a/tests/functional/conftest.py | |||
284 | +++ b/tests/functional/conftest.py | |||
285 | @@ -2,21 +2,23 @@ | |||
286 | 2 | 2 | ||
287 | 3 | import asyncio | 3 | import asyncio |
288 | 4 | import json | 4 | import json |
289 | 5 | import juju | ||
290 | 6 | import os | 5 | import os |
291 | 7 | import pytest | ||
292 | 8 | import uuid | 6 | import uuid |
293 | 7 | |||
294 | 8 | import juju | ||
295 | 9 | from juju.controller import Controller | 9 | from juju.controller import Controller |
296 | 10 | from juju.errors import JujuError | 10 | from juju.errors import JujuError |
297 | 11 | from juju.model import Model | 11 | from juju.model import Model |
298 | 12 | 12 | ||
299 | 13 | import pytest | ||
300 | 14 | |||
301 | 15 | |||
302 | 13 | STAT_FILE = "python3 -c \"import json; import os; s=os.stat('%s'); print(json.dumps({'uid': s.st_uid, 'gid': s.st_gid, 'mode': oct(s.st_mode), 'size': s.st_size}))\"" # noqa: E501 | 16 | STAT_FILE = "python3 -c \"import json; import os; s=os.stat('%s'); print(json.dumps({'uid': s.st_uid, 'gid': s.st_gid, 'mode': oct(s.st_mode), 'size': s.st_size}))\"" # noqa: E501 |
303 | 14 | 17 | ||
304 | 15 | 18 | ||
305 | 16 | @pytest.yield_fixture(scope='module') | 19 | @pytest.yield_fixture(scope='module') |
306 | 17 | def event_loop(request): | 20 | def event_loop(request): |
309 | 18 | '''Override the default pytest event loop to allow for broaded scoped | 21 | """Override the default pytest event loop to allow for broaded scopedv fixtures.""" |
308 | 19 | fixtures''' | ||
310 | 20 | loop = asyncio.get_event_loop_policy().new_event_loop() | 22 | loop = asyncio.get_event_loop_policy().new_event_loop() |
311 | 21 | asyncio.set_event_loop(loop) | 23 | asyncio.set_event_loop(loop) |
312 | 22 | loop.set_debug(True) | 24 | loop.set_debug(True) |
313 | @@ -27,7 +29,7 @@ def event_loop(request): | |||
314 | 27 | 29 | ||
315 | 28 | @pytest.fixture(scope='module') | 30 | @pytest.fixture(scope='module') |
316 | 29 | async def controller(): | 31 | async def controller(): |
318 | 30 | '''Connect to the current controller''' | 32 | """Connect to the current controller.""" |
319 | 31 | controller = Controller() | 33 | controller = Controller() |
320 | 32 | await controller.connect_current() | 34 | await controller.connect_current() |
321 | 33 | yield controller | 35 | yield controller |
322 | @@ -36,7 +38,7 @@ async def controller(): | |||
323 | 36 | 38 | ||
324 | 37 | @pytest.fixture(scope='module') | 39 | @pytest.fixture(scope='module') |
325 | 38 | async def model(controller): | 40 | async def model(controller): |
327 | 39 | '''This model lives only for the duration of the test''' | 41 | """Create a model that lives only for the duration of the test.""" |
328 | 40 | model_name = "functest-{}".format(uuid.uuid4()) | 42 | model_name = "functest-{}".format(uuid.uuid4()) |
329 | 41 | model = await controller.add_model(model_name) | 43 | model = await controller.add_model(model_name) |
330 | 42 | yield model | 44 | yield model |
331 | @@ -50,7 +52,7 @@ async def model(controller): | |||
332 | 50 | 52 | ||
333 | 51 | @pytest.fixture(scope='module') | 53 | @pytest.fixture(scope='module') |
334 | 52 | async def current_model(): | 54 | async def current_model(): |
336 | 53 | '''Returns the current model, does not create or destroy it''' | 55 | """Return the current model, does not create or destroy it.""" |
337 | 54 | model = Model() | 56 | model = Model() |
338 | 55 | await model.connect_current() | 57 | await model.connect_current() |
339 | 56 | yield model | 58 | yield model |
340 | @@ -59,7 +61,7 @@ async def current_model(): | |||
341 | 59 | 61 | ||
342 | 60 | @pytest.fixture | 62 | @pytest.fixture |
343 | 61 | async def get_app(model): | 63 | async def get_app(model): |
345 | 62 | '''Returns the application requested''' | 64 | """Return the application requested.""" |
346 | 63 | async def _get_app(name): | 65 | async def _get_app(name): |
347 | 64 | try: | 66 | try: |
348 | 65 | return model.applications[name] | 67 | return model.applications[name] |
349 | @@ -70,7 +72,7 @@ async def get_app(model): | |||
350 | 70 | 72 | ||
351 | 71 | @pytest.fixture | 73 | @pytest.fixture |
352 | 72 | async def get_unit(model): | 74 | async def get_unit(model): |
354 | 73 | '''Returns the requested <app_name>/<unit_number> unit''' | 75 | """Return the requested <app_name>/<unit_number> unit.""" |
355 | 74 | async def _get_unit(name): | 76 | async def _get_unit(name): |
356 | 75 | try: | 77 | try: |
357 | 76 | (app_name, unit_number) = name.split('/') | 78 | (app_name, unit_number) = name.split('/') |
358 | @@ -82,7 +84,7 @@ async def get_unit(model): | |||
359 | 82 | 84 | ||
360 | 83 | @pytest.fixture | 85 | @pytest.fixture |
361 | 84 | async def get_entity(model, get_unit, get_app): | 86 | async def get_entity(model, get_unit, get_app): |
363 | 85 | '''Returns a unit or an application''' | 87 | """Return a unit or an application.""" |
364 | 86 | async def _get_entity(name): | 88 | async def _get_entity(name): |
365 | 87 | try: | 89 | try: |
366 | 88 | return await get_unit(name) | 90 | return await get_unit(name) |
367 | @@ -96,12 +98,12 @@ async def get_entity(model, get_unit, get_app): | |||
368 | 96 | 98 | ||
369 | 97 | @pytest.fixture | 99 | @pytest.fixture |
370 | 98 | async def run_command(get_unit): | 100 | async def run_command(get_unit): |
373 | 99 | ''' | 101 | """ |
374 | 100 | Runs a command on a unit. | 102 | Run a command on a unit. |
375 | 101 | 103 | ||
376 | 102 | :param cmd: Command to be run | 104 | :param cmd: Command to be run |
377 | 103 | :param target: Unit object or unit name string | 105 | :param target: Unit object or unit name string |
379 | 104 | ''' | 106 | """ |
380 | 105 | async def _run_command(cmd, target): | 107 | async def _run_command(cmd, target): |
381 | 106 | unit = ( | 108 | unit = ( |
382 | 107 | target | 109 | target |
383 | @@ -115,12 +117,12 @@ async def run_command(get_unit): | |||
384 | 115 | 117 | ||
385 | 116 | @pytest.fixture | 118 | @pytest.fixture |
386 | 117 | async def file_stat(run_command): | 119 | async def file_stat(run_command): |
389 | 118 | ''' | 120 | """ |
390 | 119 | Runs stat on a file | 121 | Run stat on a file. |
391 | 120 | 122 | ||
392 | 121 | :param path: File path | 123 | :param path: File path |
393 | 122 | :param target: Unit object or unit name string | 124 | :param target: Unit object or unit name string |
395 | 123 | ''' | 125 | """ |
396 | 124 | async def _file_stat(path, target): | 126 | async def _file_stat(path, target): |
397 | 125 | cmd = STAT_FILE % path | 127 | cmd = STAT_FILE % path |
398 | 126 | results = await run_command(cmd, target) | 128 | results = await run_command(cmd, target) |
399 | @@ -130,12 +132,12 @@ async def file_stat(run_command): | |||
400 | 130 | 132 | ||
401 | 131 | @pytest.fixture | 133 | @pytest.fixture |
402 | 132 | async def file_contents(run_command): | 134 | async def file_contents(run_command): |
405 | 133 | ''' | 135 | """ |
406 | 134 | Returns the contents of a file | 136 | Return the contents of a file. |
407 | 135 | 137 | ||
408 | 136 | :param path: File path | 138 | :param path: File path |
409 | 137 | :param target: Unit object or unit name string | 139 | :param target: Unit object or unit name string |
411 | 138 | ''' | 140 | """ |
412 | 139 | async def _file_contents(path, target): | 141 | async def _file_contents(path, target): |
413 | 140 | cmd = 'cat {}'.format(path) | 142 | cmd = 'cat {}'.format(path) |
414 | 141 | results = await run_command(cmd, target) | 143 | results = await run_command(cmd, target) |
415 | @@ -145,7 +147,7 @@ async def file_contents(run_command): | |||
416 | 145 | 147 | ||
417 | 146 | @pytest.fixture | 148 | @pytest.fixture |
418 | 147 | async def reconfigure_app(get_app, model): | 149 | async def reconfigure_app(get_app, model): |
420 | 148 | '''Applies a different config to the requested app''' | 150 | """Apply a different config to the requested app.""" |
421 | 149 | async def _reconfigure_app(cfg, target): | 151 | async def _reconfigure_app(cfg, target): |
422 | 150 | application = ( | 152 | application = ( |
423 | 151 | target | 153 | target |
424 | @@ -160,7 +162,7 @@ async def reconfigure_app(get_app, model): | |||
425 | 160 | 162 | ||
426 | 161 | @pytest.fixture | 163 | @pytest.fixture |
427 | 162 | async def create_group(run_command): | 164 | async def create_group(run_command): |
429 | 163 | '''Creates the UNIX group specified''' | 165 | """Create the UNIX group specified.""" |
430 | 164 | async def _create_group(group_name, target): | 166 | async def _create_group(group_name, target): |
431 | 165 | cmd = "sudo groupadd %s" % group_name | 167 | cmd = "sudo groupadd %s" % group_name |
432 | 166 | await run_command(cmd, target) | 168 | await run_command(cmd, target) |
433 | diff --git a/tests/functional/requirements.txt b/tests/functional/requirements.txt | |||
434 | index b9815c2..1da5a06 100644 | |||
435 | --- a/tests/functional/requirements.txt | |||
436 | +++ b/tests/functional/requirements.txt | |||
437 | @@ -2,5 +2,6 @@ juju | |||
438 | 2 | requests | 2 | requests |
439 | 3 | pytest | 3 | pytest |
440 | 4 | pytest-asyncio | 4 | pytest-asyncio |
441 | 5 | pytest-cov | ||
442 | 5 | mock | 6 | mock |
443 | 6 | flake8 | 7 | flake8 |
444 | diff --git a/tests/functional/test_deploy.py b/tests/functional/test_deploy.py | |||
445 | index 5c8e690..cdb5ae4 100644 | |||
446 | --- a/tests/functional/test_deploy.py | |||
447 | +++ b/tests/functional/test_deploy.py | |||
448 | @@ -1,38 +1,67 @@ | |||
449 | 1 | #!/usr/bin/python3.6 | 1 | #!/usr/bin/python3.6 |
451 | 2 | import asyncio | 2 | |
452 | 3 | import os | 3 | import os |
453 | 4 | |||
454 | 4 | import pytest | 5 | import pytest |
455 | 5 | 6 | ||
456 | 6 | pytestmark = pytest.mark.asyncio | 7 | pytestmark = pytest.mark.asyncio |
457 | 7 | 8 | ||
458 | 9 | charm_build_dir = os.getenv('CHARM_BUILD_DIR', '..').rstrip('/') | ||
459 | 10 | |||
460 | 11 | |||
461 | 12 | sources = [('local', '{}/builds/sudo-pair'.format(charm_build_dir))] | ||
462 | 13 | |||
463 | 14 | series = ['xenial', | ||
464 | 15 | 'bionic', | ||
465 | 16 | ] | ||
466 | 8 | 17 | ||
467 | 9 | def get_series(): | ||
468 | 10 | series = os.getenv('test_series', 'xenial bionic').strip() | ||
469 | 11 | return series.split() | ||
470 | 12 | 18 | ||
471 | 13 | ############ | 19 | ############ |
472 | 14 | # FIXTURES # | 20 | # FIXTURES # |
473 | 15 | ############ | 21 | ############ |
474 | 16 | 22 | ||
475 | 17 | 23 | ||
483 | 18 | # This fixture shouldn't really be in conftest.py since it's specific to this | 24 | @pytest.fixture(params=series) |
484 | 19 | # charm | 25 | def series(request): |
485 | 20 | @pytest.fixture(scope='module', | 26 | """Return ubuntu version (i.e. xenial) in use in the test.""" |
486 | 21 | params=get_series()) | 27 | return request.param |
487 | 22 | async def deploy_app(request, model): | 28 | |
488 | 23 | '''Deploys the sudo_pair app as a subordinate of ubuntu''' | 29 | |
489 | 24 | release = request.param | 30 | @pytest.fixture(params=sources, ids=[s[0] for s in sources]) |
490 | 31 | def source(request): | ||
491 | 32 | """Return source of the charm under test (i.e. local, cs).""" | ||
492 | 33 | return request.param | ||
493 | 34 | |||
494 | 35 | |||
495 | 36 | @pytest.fixture | ||
496 | 37 | async def app(model, series, source): | ||
497 | 38 | """Return application of the charm under test.""" | ||
498 | 39 | app_name = 'sudo-pair-{}'.format(series) | ||
499 | 40 | return await model._wait_for_new('application', app_name) | ||
500 | 41 | |||
501 | 42 | |||
502 | 43 | @pytest.fixture | ||
503 | 44 | async def unit(app): | ||
504 | 45 | """Return the sudo_pair unit we've deployed.""" | ||
505 | 46 | return app.units[0] | ||
506 | 47 | |||
507 | 48 | |||
508 | 49 | ######### | ||
509 | 50 | # TESTS # | ||
510 | 51 | ######### | ||
511 | 25 | 52 | ||
512 | 53 | async def test_deploy_app(model, series, source): | ||
513 | 54 | """Deploy the sudo_pair app as a subordinate of ubuntu.""" | ||
514 | 26 | await model.deploy( | 55 | await model.deploy( |
515 | 27 | 'ubuntu', | 56 | 'ubuntu', |
518 | 28 | application_name='ubuntu-' + release, | 57 | application_name='ubuntu-' + series, |
519 | 29 | series=release, | 58 | series=series, |
520 | 30 | channel='stable' | 59 | channel='stable' |
521 | 31 | ) | 60 | ) |
522 | 32 | sudo_pair_app = await model.deploy( | 61 | sudo_pair_app = await model.deploy( |
526 | 33 | '{}/builds/sudo-pair'.format(os.getenv('JUJU_REPOSITORY')), | 62 | source[1], |
527 | 34 | application_name='sudo-pair-' + release, | 63 | application_name='sudo-pair-' + series, |
528 | 35 | series=release, | 64 | series=series, |
529 | 36 | num_units=0, | 65 | num_units=0, |
530 | 37 | config={ | 66 | config={ |
531 | 38 | 'bypass_cmds': '/bin/ls', | 67 | 'bypass_cmds': '/bin/ls', |
532 | @@ -41,27 +70,17 @@ async def deploy_app(request, model): | |||
533 | 41 | } | 70 | } |
534 | 42 | ) | 71 | ) |
535 | 43 | await model.add_relation( | 72 | await model.add_relation( |
538 | 44 | 'ubuntu-{}:juju-info'.format(release), | 73 | 'ubuntu-{}:juju-info'.format(series), |
539 | 45 | 'sudo-pair-{}:juju-info'.format(release)) | 74 | 'sudo-pair-{}:juju-info'.format(series)) |
540 | 46 | 75 | ||
541 | 47 | await model.block_until(lambda: sudo_pair_app.status == 'active') | 76 | await model.block_until(lambda: sudo_pair_app.status == 'active') |
542 | 48 | yield sudo_pair_app | ||
543 | 49 | # no need to cleanup since the model will be be torn down at the end of the | 77 | # no need to cleanup since the model will be be torn down at the end of the |
544 | 50 | # testing | 78 | # testing |
545 | 51 | 79 | ||
546 | 52 | 80 | ||
559 | 53 | @pytest.fixture(scope='module') | 81 | async def test_status(app): |
560 | 54 | async def unit(deploy_app): | 82 | """Check that the app is in active state.""" |
561 | 55 | '''Returns the sudo_pair unit we've deployed''' | 83 | assert app.status == 'active' |
550 | 56 | return deploy_app.units.pop() | ||
551 | 57 | |||
552 | 58 | ######### | ||
553 | 59 | # TESTS # | ||
554 | 60 | ######### | ||
555 | 61 | |||
556 | 62 | |||
557 | 63 | async def test_deploy(deploy_app): | ||
558 | 64 | assert deploy_app.status == 'active' | ||
562 | 65 | 84 | ||
563 | 66 | 85 | ||
564 | 67 | @pytest.mark.parametrize("path,expected_stat", [ | 86 | @pytest.mark.parametrize("path,expected_stat", [ |
565 | @@ -86,6 +105,7 @@ async def test_deploy(deploy_app): | |||
566 | 86 | 'uid': 0, | 105 | 'uid': 0, |
567 | 87 | 'mode': '0o40644'})]) | 106 | 'mode': '0o40644'})]) |
568 | 88 | async def test_stats(path, expected_stat, unit, file_stat): | 107 | async def test_stats(path, expected_stat, unit, file_stat): |
569 | 108 | """Check that created files have the correct permissions.""" | ||
570 | 89 | test_stat = await file_stat(path, unit) | 109 | test_stat = await file_stat(path, unit) |
571 | 90 | assert test_stat['size'] > 0 | 110 | assert test_stat['size'] > 0 |
572 | 91 | assert test_stat['gid'] == expected_stat['gid'] | 111 | assert test_stat['gid'] == expected_stat['gid'] |
573 | @@ -94,11 +114,13 @@ async def test_stats(path, expected_stat, unit, file_stat): | |||
574 | 94 | 114 | ||
575 | 95 | 115 | ||
576 | 96 | async def test_sudoers(file_contents, unit): | 116 | async def test_sudoers(file_contents, unit): |
577 | 117 | """Check the content of sudoers file.""" | ||
578 | 97 | sudoers_content = await file_contents("/etc/sudoers", unit) | 118 | sudoers_content = await file_contents("/etc/sudoers", unit) |
579 | 98 | assert 'Defaults log_output' in sudoers_content | 119 | assert 'Defaults log_output' in sudoers_content |
580 | 99 | 120 | ||
581 | 100 | 121 | ||
582 | 101 | async def test_sudoers_bypass_conf(file_contents, unit): | 122 | async def test_sudoers_bypass_conf(file_contents, unit): |
583 | 123 | """Check the content of sudoers bypass command file.""" | ||
584 | 102 | path = "/etc/sudoers.d/91-bypass-sudopair-cmds" | 124 | path = "/etc/sudoers.d/91-bypass-sudopair-cmds" |
585 | 103 | sudoers_bypass_content = await file_contents(path=path, | 125 | sudoers_bypass_content = await file_contents(path=path, |
586 | 104 | target=unit) | 126 | target=unit) |
587 | @@ -106,41 +128,30 @@ async def test_sudoers_bypass_conf(file_contents, unit): | |||
588 | 106 | assert content in sudoers_bypass_content | 128 | assert content in sudoers_bypass_content |
589 | 107 | 129 | ||
590 | 108 | 130 | ||
594 | 109 | async def test_reconfigure(reconfigure_app, file_contents, unit, deploy_app): | 131 | async def test_reconfigure(reconfigure_app, file_contents, unit, app): |
595 | 110 | '''Change a charm config parameter and verify that it has been propagated to | 132 | """Change a charm config parameter and verify that it has been propagated to the unit.""" |
593 | 111 | the unit''' | ||
596 | 112 | sudo_approve_path = '/usr/bin/sudo_approve' | 133 | sudo_approve_path = '/usr/bin/sudo_approve' |
597 | 113 | await reconfigure_app(cfg={'auto_approve': 'false'}, | 134 | await reconfigure_app(cfg={'auto_approve': 'false'}, |
599 | 114 | target=deploy_app) | 135 | target=app) |
600 | 115 | sudo_approve_content = await file_contents(path=sudo_approve_path, | 136 | sudo_approve_content = await file_contents(path=sudo_approve_path, |
601 | 116 | target=unit) | 137 | target=unit) |
602 | 117 | new_content = 'echo "You can\'t approve your own session."' | 138 | new_content = 'echo "You can\'t approve your own session."' |
603 | 118 | assert new_content in sudo_approve_content | 139 | assert new_content in sudo_approve_content |
604 | 119 | 140 | ||
605 | 120 | 141 | ||
608 | 121 | async def test_remove_relation(deploy_app, model, run_command): | 142 | async def test_remove_relation(app, model, run_command): |
609 | 122 | series = deploy_app.units[0].data['series'] | 143 | """Check that the relation is removed.""" |
610 | 144 | series = app.units[0].data['series'] | ||
611 | 123 | app_name = 'sudo-pair-{}'.format(series) | 145 | app_name = 'sudo-pair-{}'.format(series) |
612 | 124 | principalname = 'ubuntu-{}'.format(series) | 146 | principalname = 'ubuntu-{}'.format(series) |
614 | 125 | await deploy_app.remove_relation( | 147 | await app.remove_relation( |
615 | 126 | '{}:juju-info'.format(app_name), | 148 | '{}:juju-info'.format(app_name), |
616 | 127 | '{}:juju-info'.format(principalname)) | 149 | '{}:juju-info'.format(principalname)) |
618 | 128 | await model.block_until(lambda: not deploy_app.relations) | 150 | await model.block_until(lambda: not app.relations) |
619 | 129 | principal = model.applications[principalname].units[0] | 151 | principal = model.applications[principalname].units[0] |
620 | 130 | res = await run_command('test -f /etc/sudo.conf || echo gone', target=principal) | 152 | res = await run_command('test -f /etc/sudo.conf || echo gone', target=principal) |
621 | 131 | assert res['Stdout'].strip() == 'gone' | 153 | assert res['Stdout'].strip() == 'gone' |
622 | 132 | await model.add_relation( | 154 | await model.add_relation( |
623 | 133 | '{}:juju-info'.format(principalname), | 155 | '{}:juju-info'.format(principalname), |
624 | 134 | '{}:juju-info'.format(app_name)) | 156 | '{}:juju-info'.format(app_name)) |
637 | 135 | await model.block_until(lambda: deploy_app.relations) | 157 | await model.block_until(lambda: app.relations) |
626 | 136 | |||
627 | 137 | |||
628 | 138 | async def test_remove_unit(deploy_app, model, run_command): | ||
629 | 139 | series = deploy_app.units[0].data['series'] | ||
630 | 140 | app_name = 'sudo-pair-{}'.format(series) | ||
631 | 141 | await deploy_app.destroy() | ||
632 | 142 | while app_name in model.applications: | ||
633 | 143 | await asyncio.sleep(2) | ||
634 | 144 | principal = model.applications['ubuntu-{}'.format(series)].units[0] | ||
635 | 145 | res = await run_command('test -f /etc/sudo.conf || echo gone', target=principal) | ||
636 | 146 | assert res['Stdout'].strip() == 'gone' | ||
638 | diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py | |||
639 | index 0e2c034..c0800ea 100644 | |||
640 | --- a/tests/unit/conftest.py | |||
641 | +++ b/tests/unit/conftest.py | |||
642 | @@ -1,11 +1,12 @@ | |||
643 | 1 | #!/usr/bin/python3 | 1 | #!/usr/bin/python3 |
644 | 2 | 2 | ||
645 | 3 | import pytest | ||
646 | 4 | import pwd | ||
647 | 5 | import grp | 3 | import grp |
648 | 6 | import os | 4 | import os |
649 | 5 | import pwd | ||
650 | 7 | from pathlib import Path | 6 | from pathlib import Path |
651 | 8 | 7 | ||
652 | 8 | import pytest | ||
653 | 9 | |||
654 | 9 | 10 | ||
655 | 10 | @pytest.fixture | 11 | @pytest.fixture |
656 | 11 | def mock_hookenv_config(monkeypatch): | 12 | def mock_hookenv_config(monkeypatch): |
657 | diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt | |||
658 | index 081ed97..04dbf13 100644 | |||
659 | --- a/tests/unit/requirements.txt | |||
660 | +++ b/tests/unit/requirements.txt | |||
661 | @@ -2,3 +2,4 @@ charmhelpers | |||
662 | 2 | charms.reactive | 2 | charms.reactive |
663 | 3 | pytest | 3 | pytest |
664 | 4 | mock | 4 | mock |
665 | 5 | pytest-cov | ||
666 | 5 | \ No newline at end of file | 6 | \ No newline at end of file |
667 | diff --git a/tests/unit/test_libsudopair.py b/tests/unit/test_libsudopair.py | |||
668 | index fea4985..3a598af 100644 | |||
669 | --- a/tests/unit/test_libsudopair.py | |||
670 | +++ b/tests/unit/test_libsudopair.py | |||
671 | @@ -1,6 +1,6 @@ | |||
672 | 1 | import os | ||
673 | 2 | import grp | ||
674 | 3 | import filecmp | 1 | import filecmp |
675 | 2 | import grp | ||
676 | 3 | import os | ||
677 | 4 | 4 | ||
678 | 5 | from libsudopair import ( | 5 | from libsudopair import ( |
679 | 6 | check_valid_group, | 6 | check_valid_group, |
680 | @@ -18,14 +18,18 @@ def test_group_id(): | |||
681 | 18 | 18 | ||
682 | 19 | 19 | ||
683 | 20 | class TestSudoPairHelper(): | 20 | class TestSudoPairHelper(): |
684 | 21 | """Module to test SudoPairHelper lib.""" | ||
685 | 22 | |||
686 | 21 | def test_pytest(self): | 23 | def test_pytest(self): |
687 | 24 | """Assert testing is carryied using pytest.""" | ||
688 | 22 | assert True | 25 | assert True |
689 | 23 | 26 | ||
690 | 24 | def test_sph(self, sph): | 27 | def test_sph(self, sph): |
692 | 25 | ''' See if the ph fixture works to load charm configs ''' | 28 | """See if the sph fixture works to load charm configs.""" |
693 | 26 | assert isinstance(sph.charm_config, dict) | 29 | assert isinstance(sph.charm_config, dict) |
694 | 27 | 30 | ||
695 | 28 | def test_get_config(self, sph): | 31 | def test_get_config(self, sph): |
696 | 32 | """Check if config contains all the required entries.""" | ||
697 | 29 | default_keywords = [ | 33 | default_keywords = [ |
698 | 30 | 'binary_path', | 34 | 'binary_path', |
699 | 31 | 'user_prompt_path', | 35 | 'user_prompt_path', |
700 | @@ -39,6 +43,7 @@ class TestSudoPairHelper(): | |||
701 | 39 | assert option in config | 43 | assert option in config |
702 | 40 | 44 | ||
703 | 41 | def test_set_charm_config(self, sph): | 45 | def test_set_charm_config(self, sph): |
704 | 46 | """Set new config.""" | ||
705 | 42 | charm_config = { | 47 | charm_config = { |
706 | 43 | 'groups_enforced': 'root', | 48 | 'groups_enforced': 'root', |
707 | 44 | 'groups_exempted': '', | 49 | 'groups_exempted': '', |
708 | @@ -54,6 +59,7 @@ class TestSudoPairHelper(): | |||
709 | 54 | assert sph.get_config()[option] == charm_config[option] | 59 | assert sph.get_config()[option] == charm_config[option] |
710 | 55 | 60 | ||
711 | 56 | def test_render_sudo_conf(self, sph, tmpdir): | 61 | def test_render_sudo_conf(self, sph, tmpdir): |
712 | 62 | """Check that sudo.conf is rendered correctly.""" | ||
713 | 57 | # Default config | 63 | # Default config |
714 | 58 | content = sph.render_sudo_conf() | 64 | content = sph.render_sudo_conf() |
715 | 59 | expected_content = 'Plugin sudo_pair sudo_pair.so binary_path={} ' \ | 65 | expected_content = 'Plugin sudo_pair sudo_pair.so binary_path={} ' \ |
716 | @@ -107,7 +113,8 @@ class TestSudoPairHelper(): | |||
717 | 107 | content = sph.render_sudo_conf() | 113 | content = sph.render_sudo_conf() |
718 | 108 | assert expected_content in content | 114 | assert expected_content in content |
719 | 109 | 115 | ||
721 | 110 | def test_render_bypass_cmds(self, sph, tmpdir): | 116 | def test_render_bypass_cmds(self, sph): |
722 | 117 | """Check that sudoers file is rendered correctly.""" | ||
723 | 111 | # Root bypass /bin/ls | 118 | # Root bypass /bin/ls |
724 | 112 | expected_content = '%root ALL = (ALL) NOLOG_OUTPUT: /bin/ls' | 119 | expected_content = '%root ALL = (ALL) NOLOG_OUTPUT: /bin/ls' |
725 | 113 | charm_config = { | 120 | charm_config = { |
726 | @@ -122,6 +129,7 @@ class TestSudoPairHelper(): | |||
727 | 122 | assert expected_content in content | 129 | assert expected_content in content |
728 | 123 | 130 | ||
729 | 124 | def test_render_sudo_approve(self, sph, tmpdir): | 131 | def test_render_sudo_approve(self, sph, tmpdir): |
730 | 132 | """Check that sudo_approve file is rendered correctly.""" | ||
731 | 125 | # Auto Approve true | 133 | # Auto Approve true |
732 | 126 | expected_content = 'echo ${log_line} >> /var/log/sudo_pair.log' | 134 | expected_content = 'echo ${log_line} >> /var/log/sudo_pair.log' |
733 | 127 | socket_dir = tmpdir.join('/var/run/sudo_pair') | 135 | socket_dir = tmpdir.join('/var/run/sudo_pair') |
734 | @@ -144,10 +152,12 @@ class TestSudoPairHelper(): | |||
735 | 144 | assert expected_content in content | 152 | assert expected_content in content |
736 | 145 | 153 | ||
737 | 146 | def test_create_socket_dir(self, sph, tmpdir): | 154 | def test_create_socket_dir(self, sph, tmpdir): |
738 | 155 | """Check that sudo_pair socket dir exists.""" | ||
739 | 147 | sph.create_socket_dir() | 156 | sph.create_socket_dir() |
740 | 148 | assert os.path.exists(tmpdir.join('/var/run/sudo_pair')) | 157 | assert os.path.exists(tmpdir.join('/var/run/sudo_pair')) |
741 | 149 | 158 | ||
742 | 150 | def test_create_tmpfiles_conf(self, sph, tmpdir): | 159 | def test_create_tmpfiles_conf(self, sph, tmpdir): |
743 | 160 | """Check that sudo pair temporary conf is rendered correctly.""" | ||
744 | 151 | sph.create_tmpfiles_conf() | 161 | sph.create_tmpfiles_conf() |
745 | 152 | expected_content = 'd {} 0755 - - -\n'.format(sph.socket_dir) | 162 | expected_content = 'd {} 0755 - - -\n'.format(sph.socket_dir) |
746 | 153 | with open(tmpdir.join('/usr/lib/tmpfiles.d/sudo_pair.conf')) as f: | 163 | with open(tmpdir.join('/usr/lib/tmpfiles.d/sudo_pair.conf')) as f: |
747 | @@ -155,17 +165,21 @@ class TestSudoPairHelper(): | |||
748 | 155 | assert expected_content in content | 165 | assert expected_content in content |
749 | 156 | 166 | ||
750 | 157 | def test_install_sudo_pair_so(self, sph, tmpdir): | 167 | def test_install_sudo_pair_so(self, sph, tmpdir): |
751 | 168 | """Check that sudo system lib exists.""" | ||
752 | 158 | sph.install_sudo_pair_so() | 169 | sph.install_sudo_pair_so() |
753 | 159 | assert filecmp.cmp('./files/sudo_pair.so', tmpdir.join('/usr/lib/sudo/sudo_pair.so')) | 170 | assert filecmp.cmp('./files/sudo_pair.so', tmpdir.join('/usr/lib/sudo/sudo_pair.so')) |
754 | 160 | 171 | ||
755 | 161 | def test_copy_user_prompt(self, sph, tmpdir): | 172 | def test_copy_user_prompt(self, sph, tmpdir): |
756 | 173 | """Check that user prompt exists.""" | ||
757 | 162 | sph.copy_user_prompt() | 174 | sph.copy_user_prompt() |
758 | 163 | assert filecmp.cmp('./files/sudo.prompt.user', tmpdir.join('/etc/sudo_pair.prompt.user')) | 175 | assert filecmp.cmp('./files/sudo.prompt.user', tmpdir.join('/etc/sudo_pair.prompt.user')) |
759 | 164 | 176 | ||
760 | 165 | def test_copy_pair_prompt(self, sph, tmpdir): | 177 | def test_copy_pair_prompt(self, sph, tmpdir): |
761 | 178 | """Check that pair prompt exists.""" | ||
762 | 166 | sph.copy_pair_prompt() | 179 | sph.copy_pair_prompt() |
763 | 167 | assert filecmp.cmp('./files/sudo.prompt.pair', tmpdir.join('/etc/sudo_pair.prompt.pair')) | 180 | assert filecmp.cmp('./files/sudo.prompt.pair', tmpdir.join('/etc/sudo_pair.prompt.pair')) |
764 | 168 | 181 | ||
765 | 169 | def test_copy_sudoers(self, sph, tmpdir): | 182 | def test_copy_sudoers(self, sph, tmpdir): |
766 | 183 | """Check that sudoers file exists.""" | ||
767 | 170 | sph.copy_sudoers() | 184 | sph.copy_sudoers() |
768 | 171 | assert filecmp.cmp('./files/sudoers', tmpdir.join('/etc/sudoers')) | 185 | assert filecmp.cmp('./files/sudoers', tmpdir.join('/etc/sudoers')) |
769 | diff --git a/tox.ini b/tox.ini | |||
770 | index 562d0ba..fc364de 100644 | |||
771 | --- a/tox.ini | |||
772 | +++ b/tox.ini | |||
773 | @@ -1,6 +1,6 @@ | |||
774 | 1 | [tox] | 1 | [tox] |
775 | 2 | skipsdist=True | 2 | skipsdist=True |
777 | 3 | envlist = unit, functional, func_xenial | 3 | envlist = unit, functional |
778 | 4 | skip_missing_interpreters = True | 4 | skip_missing_interpreters = True |
779 | 5 | 5 | ||
780 | 6 | [testenv] | 6 | [testenv] |
781 | @@ -9,38 +9,43 @@ setenv = | |||
782 | 9 | PYTHONPATH = . | 9 | PYTHONPATH = . |
783 | 10 | 10 | ||
784 | 11 | [testenv:unit] | 11 | [testenv:unit] |
786 | 12 | commands = pytest -v --ignore {toxinidir}/tests/amulet --ignore {toxinidir}/tests/functional | 12 | commands = pytest -v --ignore {toxinidir}/tests/functional \ |
787 | 13 | --cov=lib \ | ||
788 | 14 | --cov=reactive \ | ||
789 | 15 | --cov=actions \ | ||
790 | 16 | --cov-report=term \ | ||
791 | 17 | --cov-report=annotate:report/annotated \ | ||
792 | 18 | --cov-report=html:report/html | ||
793 | 13 | deps = -r{toxinidir}/tests/unit/requirements.txt | 19 | deps = -r{toxinidir}/tests/unit/requirements.txt |
794 | 20 | |||
795 | 14 | setenv = PYTHONPATH={toxinidir}/lib | 21 | setenv = PYTHONPATH={toxinidir}/lib |
796 | 15 | 22 | ||
797 | 16 | [testenv:functional] | 23 | [testenv:functional] |
798 | 17 | passenv = | 24 | passenv = |
799 | 18 | HOME | 25 | HOME |
801 | 19 | JUJU_REPOSITORY | 26 | CHARM_BUILD_DIR |
802 | 20 | PATH | 27 | PATH |
804 | 21 | commands = pytest -v --ignore {toxinidir}/tests/unit --ignore {toxinidir}/tests/amulet | 28 | PYTEST_KEEP_MODEL |
805 | 29 | PYTEST_CLOUD_NAME | ||
806 | 30 | PYTEST_CLOUD_REGION | ||
807 | 31 | commands = pytest -v --ignore {toxinidir}/tests/unit | ||
808 | 22 | deps = -r{toxinidir}/tests/functional/requirements.txt | 32 | deps = -r{toxinidir}/tests/functional/requirements.txt |
809 | 23 | 33 | ||
810 | 24 | 34 | ||
811 | 25 | [testenv:func_xenial] | ||
812 | 26 | passenv = | ||
813 | 27 | HOME | ||
814 | 28 | JUJU_REPOSITORY | ||
815 | 29 | PATH | ||
816 | 30 | test_preserve_model | ||
817 | 31 | setenv = test_series=xenial | ||
818 | 32 | commands = pytest -v --ignore {toxinidir}/tests/unit --ignore {toxinidir}/tests/amulet | ||
819 | 33 | deps = -r{toxinidir}/tests/functional/requirements.txt | ||
820 | 34 | |||
821 | 35 | [testenv:lint] | 35 | [testenv:lint] |
822 | 36 | commands = flake8 | 36 | commands = flake8 |
824 | 37 | deps = flake8 | 37 | deps = |
825 | 38 | flake8 | ||
826 | 39 | flake8-docstrings | ||
827 | 40 | flake8-import-order | ||
828 | 41 | pep8-naming | ||
829 | 42 | flake8-colors | ||
830 | 38 | 43 | ||
831 | 39 | [flake8] | 44 | [flake8] |
835 | 40 | max-line-length = 120 | 45 | ignore = D100,D103 # Missing docstring in public module/function |
833 | 41 | max-complexity=10 | ||
834 | 42 | ignore = W503 | ||
836 | 43 | exclude = | 46 | exclude = |
837 | 44 | .git, | 47 | .git, |
838 | 45 | __pycache__, | 48 | __pycache__, |
839 | 46 | .tox, | 49 | .tox, |
840 | 50 | max-line-length = 120 | ||
841 | 51 | max-complexity = 10 |
MP lgtm (only code review, I haven't run make...)
Added a comment inline to possibly remove "make submodules". Thank you.