Merge ~arturo-seijas/charm-ubuntu-advantage:add-integration-test into charm-ubuntu-advantage:master

Proposed by Arturo Enrique Seijas Fernández
Status: Merged
Approved by: Tom Haddon
Approved revision: 417aba595b2ec06797d0f93c2300ab1ce9519bf2
Merged at revision: 6911d2a1237a9bf43802556d96e9fd974683357e
Proposed branch: ~arturo-seijas/charm-ubuntu-advantage:add-integration-test
Merge into: charm-ubuntu-advantage:master
Diff against target: 1021 lines (+347/-236)
7 files modified
README.md (+0/-15)
dev/null (+0/-0)
pyproject.toml (+38/-0)
src/charm.py (+23/-24)
tests/integration/test_charm.py (+24/-0)
tests/unit/test_charm.py (+184/-197)
tox.ini (+78/-0)
Reviewer Review Type Date Requested Status
Tom Haddon Approve
Canonical IS Reviewers Pending
Review via email: mp+419678@code.launchpad.net

Commit message

Add integration test and use tox instead of script

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Tom Haddon (mthaddon) wrote :

Can you also update the README since the instructions for running tests have changed?

Probably best to update copyright info to be 2022 for newly created files, 2021-2022 for pre-existing ones.

Revision history for this message
Arturo Enrique Seijas Fernández (arturo-seijas) wrote :

> Can you also update the README since the instructions for running tests have
> changed?
>
> Probably best to update copyright info to be 2022 for newly created files,
> 2021-2022 for pre-existing ones.

Done

Revision history for this message
Tom Haddon (mthaddon) wrote :

LGTM, thx

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 6911d2a1237a9bf43802556d96e9fd974683357e

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.flake8 b/.flake8
2deleted file mode 100644
3index 8ef84fc..0000000
4--- a/.flake8
5+++ /dev/null
6@@ -1,9 +0,0 @@
7-[flake8]
8-max-line-length = 99
9-select: E,W,F,C,N
10-exclude:
11- venv
12- .git
13- build
14- dist
15- *.egg_info
16diff --git a/README.md b/README.md
17index a89be47..6884762 100644
18--- a/README.md
19+++ b/README.md
20@@ -11,18 +11,3 @@ Be sure to replace `<token>` with a valid value from [the Ubuntu Advantage websi
21 juju deploy ubuntu
22 juju deploy ubuntu-advantage --config token=<token>
23 juju add-relation ubuntu ubuntu-advantage
24-
25-## Developing
26-
27-Create and activate a virtualenv with the development requirements:
28-
29- virtualenv -p python3 venv
30- source venv/bin/activate
31- pip install -r requirements-dev.txt
32-
33-## Testing
34-
35-The Python operator framework includes a very nice harness for testing
36-operator behaviour without full deployment. Just `run_tests`:
37-
38- ./run_tests
39diff --git a/pyproject.toml b/pyproject.toml
40new file mode 100644
41index 0000000..754fb1a
42--- /dev/null
43+++ b/pyproject.toml
44@@ -0,0 +1,38 @@
45+# Copyright 2022 Canonical Ltd.
46+# See LICENSE file for licensing details.
47+
48+# Testing tools configuration
49+[tool.coverage.run]
50+branch = true
51+
52+[tool.coverage.report]
53+show_missing = true
54+
55+[tool.pytest.ini_options]
56+minversion = "6.0"
57+log_cli_level = "INFO"
58+
59+# Formatting tools configuration
60+[tool.black]
61+line-length = 99
62+target-version = ["py38"]
63+
64+[tool.isort]
65+profile = "black"
66+
67+# Linting tools configuration
68+[tool.flake8]
69+max-line-length = 99
70+max-doc-length = 99
71+max-complexity = 10
72+exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"]
73+select = ["E", "W", "F", "C", "N", "R", "D", "H"]
74+# Ignore D107 Missing docstring in __init__
75+ignore = ["D107"]
76+# D100, D101, D102, D103: Ignore missing docstrings in tests
77+per-file-ignores = ["tests/*:D100,D101,D102,D103,D104"]
78+docstring-convention = "google"
79+# Check for properly formatted copyright header in each file
80+copyright-check = "True"
81+copyright-author = "Canonical Ltd."
82+copyright-regexp = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+%(author)s"
83\ No newline at end of file
84diff --git a/requirements-dev.txt b/requirements-dev.txt
85deleted file mode 100644
86index 4f2a3f5..0000000
87--- a/requirements-dev.txt
88+++ /dev/null
89@@ -1,3 +0,0 @@
90--r requirements.txt
91-coverage
92-flake8
93diff --git a/run_tests b/run_tests
94deleted file mode 100755
95index ef9f058..0000000
96--- a/run_tests
97+++ /dev/null
98@@ -1,18 +0,0 @@
99-#!/bin/sh -e
100-# Copyright 2021 Canonical Ltd.
101-# See LICENSE file for licensing details.
102-PYTHONPATH_INCLUDES="src:lib"
103-
104-if [ -z "$VIRTUAL_ENV" -a -d venv/ ]; then
105- . venv/bin/activate
106-fi
107-
108-if [ -z "$PYTHONPATH" ]; then
109- export PYTHONPATH="$PYTHONPATH_INCLUDES"
110-else
111- export PYTHONPATH="$PYTHONPATH_INCLUDES:$PYTHONPATH"
112-fi
113-
114-flake8 --extend-exclude operator_libs_linux
115-coverage run --source=src -m unittest -v "$@"
116-coverage report -m
117diff --git a/src/charm.py b/src/charm.py
118index 84362d3..f2e6e41 100755
119--- a/src/charm.py
120+++ b/src/charm.py
121@@ -1,37 +1,37 @@
122 #!/usr/bin/env python3
123-
124 # Copyright 2021 Canonical Ltd.
125 # See LICENSE file for licensing details.
126
127+"""Charmed Operator to enable Ubuntu Advantage (https://ubuntu.com/advantage) subscriptions."""
128+
129 import hashlib
130 import json
131 import logging
132 import os
133 import subprocess
134-import yaml
135
136+import yaml
137 from charms.operator_libs_linux.v0 import apt
138 from ops.charm import CharmBase
139 from ops.framework import StoredState
140 from ops.main import main
141 from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus
142
143-
144 logger = logging.getLogger(__name__)
145
146
147 def install_ppa(ppa, env):
148- """Install specified ppa"""
149+ """Install specified ppa."""
150 subprocess.check_call(["add-apt-repository", "--yes", ppa], env=env)
151
152
153 def remove_ppa(ppa, env):
154- """Remove specified ppa"""
155+ """Remove specified ppa."""
156 subprocess.check_call(["add-apt-repository", "--remove", "--yes", ppa], env=env)
157
158
159 def update_configuration(contract_url):
160- """Write the contract_url to the uaclient configuration file"""
161+ """Write the contract_url to the uaclient configuration file."""
162 with open("/etc/ubuntu-advantage/uaclient.conf", "r+") as f:
163 client_config = yaml.safe_load(f)
164 client_config["contract_url"] = contract_url
165@@ -41,17 +41,17 @@ def update_configuration(contract_url):
166
167
168 def detach_subscription():
169- """Detach from any ubuntu-advantage subscription"""
170+ """Detach from any ubuntu-advantage subscription."""
171 subprocess.check_call(["ubuntu-advantage", "detach", "--assume-yes"])
172
173
174 def attach_subscription(token):
175- """Attach an ubuntu-advantage subscription using the specified token"""
176+ """Attach an ubuntu-advantage subscription using the specified token."""
177 return subprocess.call(["ubuntu-advantage", "attach", token])
178
179
180 def get_status_output():
181- """Return the parsed output from ubuntu-advantage status"""
182+ """Return the parsed output from ubuntu-advantage status."""
183 output = subprocess.check_output(["ubuntu-advantage", "status", "--all", "--format", "json"])
184 # handle different return type from xenial
185 if isinstance(output, bytes):
186@@ -60,26 +60,25 @@ def get_status_output():
187
188
189 class UbuntuAdvantageCharm(CharmBase):
190- """Charm to handle ubuntu-advantage installation and configuration"""
191+ """Charm to handle ubuntu-advantage installation and configuration."""
192+
193 _state = StoredState()
194
195 def __init__(self, *args):
196 super().__init__(*args)
197 self._setup_proxy_env()
198 self._state.set_default(
199- contract_url=None,
200- hashed_token=None,
201- package_needs_installing=True,
202- ppa=None)
203+ contract_url=None, hashed_token=None, package_needs_installing=True, ppa=None
204+ )
205
206 self.framework.observe(self.on.config_changed, self.config_changed)
207
208 def _setup_proxy_env(self):
209- """Setup proxy variables from model"""
210+ """Setup proxy variables from model."""
211 self.env = dict(os.environ)
212- self.env['http_proxy'] = self.env.get('JUJU_CHARM_HTTP_PROXY', '')
213- self.env['https_proxy'] = self.env.get('JUJU_CHARM_HTTPS_PROXY', '')
214- self.env['no_proxy'] = self.env.get('JUJU_CHARM_NO_PROXY', '')
215+ self.env["http_proxy"] = self.env.get("JUJU_CHARM_HTTP_PROXY", "")
216+ self.env["https_proxy"] = self.env.get("JUJU_CHARM_HTTPS_PROXY", "")
217+ self.env["no_proxy"] = self.env.get("JUJU_CHARM_NO_PROXY", "")
218
219 # The values for 'http_proxy' and 'https_proxy' will be used for the
220 # PPA install/remove operations (passed as environment variables), as
221@@ -93,7 +92,7 @@ class UbuntuAdvantageCharm(CharmBase):
222 logger.debug("Envvar '%s' => '%s'", envvar, value)
223
224 def config_changed(self, event):
225- """Install and configure ubuntu-advantage tools and attachment"""
226+ """Install and configure ubuntu-advantage tools and attachment."""
227 logger.info("Beginning config_changed")
228 self.unit.status = MaintenanceStatus("Configuring")
229 self._handle_ppa_state()
230@@ -105,7 +104,7 @@ class UbuntuAdvantageCharm(CharmBase):
231 logger.info("Finished config_changed")
232
233 def _handle_ppa_state(self):
234- """Handle installing/removing ppa based on configuration and state"""
235+ """Handle installing/removing ppa based on configuration and state."""
236 ppa = self.config.get("ppa", "").strip()
237 old_ppa = self._state.ppa
238
239@@ -124,7 +123,7 @@ class UbuntuAdvantageCharm(CharmBase):
240 self._state.package_needs_installing = True
241
242 def _handle_package_state(self):
243- """Install apt package if necessary"""
244+ """Install apt package if necessary."""
245 if self._state.package_needs_installing:
246 logger.info("Removing package ubuntu-advantage-tools")
247 apt.remove_package("ubuntu-advantage-tools")
248@@ -133,7 +132,7 @@ class UbuntuAdvantageCharm(CharmBase):
249 self._state.package_needs_installing = False
250
251 def _handle_subscription_state(self):
252- """Handle uaclient configuration and subscription attachment"""
253+ """Handle uaclient configuration and subscription attachment."""
254 token = self.config.get("token", "").strip()
255 hashed_token = hashlib.sha256(token.encode("utf-8")).hexdigest()
256 old_hashed_token = self._state.hashed_token
257@@ -172,7 +171,7 @@ class UbuntuAdvantageCharm(CharmBase):
258 self._state.hashed_token = hashed_token
259
260 def _handle_status_state(self):
261- """Parse status output to determine which services are active"""
262+ """Parse status output to determine which services are active."""
263 status = get_status_output()
264 services = []
265 for service in status.get("services"):
266@@ -182,7 +181,7 @@ class UbuntuAdvantageCharm(CharmBase):
267 self.unit.status = ActiveStatus(message)
268
269 def _configure_ua_proxy(self):
270- """Configure the proxy options for the ubuntu-advantage client"""
271+ """Configure the proxy options for the ubuntu-advantage client."""
272 for config_key in ("http_proxy", "https_proxy"):
273 subprocess.check_call(
274 [
275diff --git a/tests/__init__.py b/tests/__init__.py
276deleted file mode 100644
277index e69de29..0000000
278--- a/tests/__init__.py
279+++ /dev/null
280diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py
281new file mode 100644
282index 0000000..2066e26
283--- /dev/null
284+++ b/tests/integration/test_charm.py
285@@ -0,0 +1,24 @@
286+# Copyright 2022 Canonical Ltd.
287+# See LICENSE file for licensing details.
288+
289+import pytest
290+from ops.model import ActiveStatus, BlockedStatus
291+from pytest_operator.plugin import OpsTest
292+
293+
294+@pytest.mark.abort_on_fail
295+async def test_build_and_deploy(ops_test: OpsTest):
296+
297+ charm = await ops_test.build_charm(".")
298+ await ops_test.model.deploy("ubuntu")
299+ await ops_test.model.deploy(charm, num_units=0)
300+ await ops_test.model.add_relation(
301+ "ubuntu",
302+ "ubuntu-advantage",
303+ )
304+ await ops_test.model.wait_for_idle()
305+
306+
307+async def test_status(ops_test: OpsTest):
308+ assert ops_test.model.applications["ubuntu"].status == ActiveStatus.name
309+ assert ops_test.model.applications["ubuntu-advantage"].status == BlockedStatus.name
310diff --git a/tests/test_charm.py b/tests/unit/test_charm.py
311similarity index 50%
312rename from tests/test_charm.py
313rename to tests/unit/test_charm.py
314index 37da2a7..14369f5 100644
315--- a/tests/test_charm.py
316+++ b/tests/unit/test_charm.py
317@@ -12,24 +12,14 @@ from ops.testing import Harness
318
319 from charm import UbuntuAdvantageCharm
320
321-
322 STATUS_ATTACHED = json.dumps(
323 {
324 "attached": True,
325 "services": [
326- {
327- "name": "esm-apps",
328- "status": "enabled"
329- },
330- {
331- "name": "esm-infra",
332- "status": "enabled"
333- },
334- {
335- "name": "livepatch",
336- "status": "enabled"
337- }
338- ]
339+ {"name": "esm-apps", "status": "enabled"},
340+ {"name": "esm-infra", "status": "enabled"},
341+ {"name": "livepatch", "status": "enabled"},
342+ ],
343 }
344 )
345
346@@ -38,19 +28,10 @@ STATUS_DETACHED = json.dumps(
347 {
348 "attached": False,
349 "services": [
350- {
351- "name": "esm-apps",
352- "available": "yes"
353- },
354- {
355- "name": "esm-infra",
356- "available": "yes"
357- },
358- {
359- "name": "livepatch",
360- "available": "yes"
361- }
362- ]
363+ {"name": "esm-apps", "available": "yes"},
364+ {"name": "esm-infra", "available": "yes"},
365+ {"name": "livepatch", "available": "yes"},
366+ ],
367 }
368 )
369
370@@ -81,11 +62,9 @@ class TestCharm(TestCase):
371 "check_output": patch("subprocess.check_output").start(),
372 "open": patch("builtins.open").start(),
373 "environ": patch.dict("os.environ", clear=True).start(),
374- "apt": patch("charm.apt").start()
375+ "apt": patch("charm.apt").start(),
376 }
377- self.mocks["check_output"].side_effect = [
378- STATUS_DETACHED
379- ]
380+ self.mocks["check_output"].side_effect = [STATUS_DETACHED]
381 self.mocks["call"].return_value = 0
382 mock_open(self.mocks["open"], read_data=DEFAULT_CLIENT_CONFIG)
383 self.harness = Harness(UbuntuAdvantageCharm)
384@@ -94,17 +73,22 @@ class TestCharm(TestCase):
385 self.env = self.harness.charm.env
386
387 def test_config_defaults(self):
388- self.assertEqual(self.harness.charm.config.get("contract_url"),
389- "https://contracts.canonical.com")
390+ self.assertEqual(
391+ self.harness.charm.config.get("contract_url"), "https://contracts.canonical.com"
392+ )
393 self.assertEqual(self.harness.charm.config.get("ppa"), "ppa:ua-client/stable")
394 self.assertEqual(self.harness.charm.config.get("token"), "")
395
396 def test_config_changed_ppa_new(self):
397 self.harness.update_config({"ppa": "ppa:ua-client/stable"})
398 self.assertEqual(self.mocks["check_call"].call_count, 3)
399- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
400- call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
401- ]))
402+ self.mocks["check_call"].assert_has_calls(
403+ self._add_ua_proxy_setup_calls(
404+ [
405+ call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
406+ ]
407+ )
408+ )
409 self._assert_apt_calls()
410 self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable")
411 self.assertFalse(self.harness.charm._state.package_needs_installing)
412@@ -112,26 +96,36 @@ class TestCharm(TestCase):
413 def test_config_changed_ppa_updated(self):
414 self.harness.update_config({"ppa": "ppa:ua-client/stable"})
415 self.assertEqual(self.mocks["check_call"].call_count, 3)
416- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
417- call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
418- ]))
419+ self.mocks["check_call"].assert_has_calls(
420+ self._add_ua_proxy_setup_calls(
421+ [
422+ call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
423+ ]
424+ )
425+ )
426 self._assert_apt_calls()
427 self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable")
428 self.assertFalse(self.harness.charm._state.package_needs_installing)
429
430- self.mocks["check_output"].side_effect = [
431- STATUS_DETACHED
432- ]
433+ self.mocks["check_output"].side_effect = [STATUS_DETACHED]
434 self.mocks["check_call"].reset_mock()
435 self.mocks["apt"].reset_mock()
436 self.harness.update_config({"ppa": "ppa:different-client/unstable"})
437 self.assertEqual(self.mocks["check_call"].call_count, 4)
438- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
439- call(["add-apt-repository", "--remove", "--yes", "ppa:ua-client/stable"],
440- env=self.env),
441- call(["add-apt-repository", "--yes", "ppa:different-client/unstable"],
442- env=self.env),
443- ]))
444+ self.mocks["check_call"].assert_has_calls(
445+ self._add_ua_proxy_setup_calls(
446+ [
447+ call(
448+ ["add-apt-repository", "--remove", "--yes", "ppa:ua-client/stable"],
449+ env=self.env,
450+ ),
451+ call(
452+ ["add-apt-repository", "--yes", "ppa:different-client/unstable"],
453+ env=self.env,
454+ ),
455+ ]
456+ )
457+ )
458 self._assert_apt_calls()
459 self.assertEqual(self.harness.charm._state.ppa, "ppa:different-client/unstable")
460 self.assertFalse(self.harness.charm._state.package_needs_installing)
461@@ -139,18 +133,20 @@ class TestCharm(TestCase):
462 def test_config_changed_ppa_unmodified(self):
463 self.harness.update_config({"ppa": "ppa:ua-client/stable"})
464 self.assertEqual(self.mocks["check_call"].call_count, 3)
465- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
466- call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
467- ]))
468+ self.mocks["check_call"].assert_has_calls(
469+ self._add_ua_proxy_setup_calls(
470+ [
471+ call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
472+ ]
473+ )
474+ )
475 self._assert_apt_calls()
476 self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable")
477 self.assertFalse(self.harness.charm._state.package_needs_installing)
478
479 self.mocks["check_call"].reset_mock()
480 self.mocks["check_output"].reset_mock()
481- self.mocks["check_output"].side_effect = [
482- STATUS_DETACHED
483- ]
484+ self.mocks["check_output"].side_effect = [STATUS_DETACHED]
485 self.harness.update_config({"ppa": "ppa:ua-client/stable"})
486 self.assertEqual(self.mocks["check_call"].call_count, 2)
487 self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([]))
488@@ -160,9 +156,13 @@ class TestCharm(TestCase):
489 def test_config_changed_ppa_unset(self):
490 self.harness.update_config({"ppa": "ppa:ua-client/stable"})
491 self.assertEqual(self.mocks["check_call"].call_count, 3)
492- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
493- call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
494- ]))
495+ self.mocks["check_call"].assert_has_calls(
496+ self._add_ua_proxy_setup_calls(
497+ [
498+ call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
499+ ]
500+ )
501+ )
502 self._assert_apt_calls()
503 self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable")
504 self.assertFalse(self.harness.charm._state.package_needs_installing)
505@@ -170,22 +170,27 @@ class TestCharm(TestCase):
506 self.mocks["check_call"].reset_mock()
507 self.mocks["check_output"].reset_mock()
508 self.mocks["apt"].reset_mock()
509- self.mocks["check_output"].side_effect = [
510- STATUS_DETACHED
511- ]
512+ self.mocks["check_output"].side_effect = [STATUS_DETACHED]
513 self.harness.update_config({"ppa": ""})
514 self.assertEqual(self.mocks["check_call"].call_count, 3)
515- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
516- call(["add-apt-repository", "--remove", "--yes", "ppa:ua-client/stable"],
517- env=self.env),
518- ]))
519+ self.mocks["check_call"].assert_has_calls(
520+ self._add_ua_proxy_setup_calls(
521+ [
522+ call(
523+ ["add-apt-repository", "--remove", "--yes", "ppa:ua-client/stable"],
524+ env=self.env,
525+ ),
526+ ]
527+ )
528+ )
529 self._assert_apt_calls()
530 self.assertIsNone(self.harness.charm._state.ppa)
531 self.assertFalse(self.harness.charm._state.package_needs_installing)
532
533 def test_config_changed_ppa_apt_failure(self):
534- self.mocks["check_call"].side_effect = CalledProcessError("apt failure",
535- "add-apt-repository")
536+ self.mocks["check_call"].side_effect = CalledProcessError(
537+ "apt failure", "add-apt-repository"
538+ )
539 with self.assertRaises(CalledProcessError):
540 self.harness.update_config({"ppa": "ppa:ua-client/stable"})
541 self.assertIsNone(self.harness.charm._state.ppa)
542@@ -193,78 +198,81 @@ class TestCharm(TestCase):
543 self.assertIsInstance(self.harness.model.unit.status, MaintenanceStatus)
544
545 def test_config_changed_token_unattached(self):
546- self.mocks["check_output"].side_effect = [
547- STATUS_DETACHED,
548- STATUS_ATTACHED
549- ]
550+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_ATTACHED]
551 self.harness.update_config({"token": "test-token"})
552 self.mocks["open"].assert_called_with("/etc/ubuntu-advantage/uaclient.conf", "r+")
553 handle = self.mocks["open"]()
554- expected = dedent("""\
555+ expected = dedent(
556+ """\
557 contract_url: https://contracts.canonical.com
558 data_dir: /var/lib/ubuntu-advantage
559 log_file: /var/log/ubuntu-advantage.log
560 log_level: debug
561- """)
562+ """
563+ )
564 self.assertEqual(_written(handle), expected)
565 handle.truncate.assert_called_once()
566 self.assertEqual(self.mocks["call"].call_count, 1)
567- self.mocks["call"].assert_has_calls([
568- call(["ubuntu-advantage", "attach", "test-token"])
569- ])
570- self.assertEqual(self.harness.charm._state.hashed_token,
571- "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e")
572- self.assertEqual(self.harness.model.unit.status,
573- ActiveStatus("Attached (esm-apps,esm-infra,livepatch)"))
574+ self.mocks["call"].assert_has_calls([call(["ubuntu-advantage", "attach", "test-token"])])
575+ self.assertEqual(
576+ self.harness.charm._state.hashed_token,
577+ "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e",
578+ )
579+ self.assertEqual(
580+ self.harness.model.unit.status, ActiveStatus("Attached (esm-apps,esm-infra,livepatch)")
581+ )
582
583 def test_config_changed_token_reattach(self):
584- self.mocks["check_output"].side_effect = [
585- STATUS_DETACHED,
586- STATUS_ATTACHED
587- ]
588+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_ATTACHED]
589 self.harness.update_config({"token": "test-token"})
590 self.assertEqual(self.mocks["check_call"].call_count, 3)
591- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
592- call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
593- ]))
594+ self.mocks["check_call"].assert_has_calls(
595+ self._add_ua_proxy_setup_calls(
596+ [
597+ call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
598+ ]
599+ )
600+ )
601 self.mocks["open"].assert_called_with("/etc/ubuntu-advantage/uaclient.conf", "r+")
602 self._assert_apt_calls()
603 handle = self.mocks["open"]()
604- expected = dedent("""\
605+ expected = dedent(
606+ """\
607 contract_url: https://contracts.canonical.com
608 data_dir: /var/lib/ubuntu-advantage
609 log_file: /var/log/ubuntu-advantage.log
610 log_level: debug
611- """)
612+ """
613+ )
614 self.assertEqual(_written(handle), expected)
615 handle.truncate.assert_called_once()
616 self.assertEqual(self.mocks["call"].call_count, 1)
617- self.mocks["call"].assert_has_calls([
618- call(["ubuntu-advantage", "attach", "test-token"])
619- ])
620- self.assertEqual(self.harness.charm._state.hashed_token,
621- "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e")
622+ self.mocks["call"].assert_has_calls([call(["ubuntu-advantage", "attach", "test-token"])])
623+ self.assertEqual(
624+ self.harness.charm._state.hashed_token,
625+ "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e",
626+ )
627
628 self.mocks["call"].reset_mock()
629 self.mocks["check_call"].reset_mock()
630 self.mocks["check_output"].reset_mock()
631- self.mocks["check_output"].side_effect = [
632- STATUS_ATTACHED,
633- STATUS_ATTACHED
634- ]
635+ self.mocks["check_output"].side_effect = [STATUS_ATTACHED, STATUS_ATTACHED]
636 self.harness.update_config({"token": "test-token-2"})
637 self.assertEqual(self.mocks["check_call"].call_count, 3)
638- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
639- call(["ubuntu-advantage", "detach", "--assume-yes"])
640- ], append=False))
641+ self.mocks["check_call"].assert_has_calls(
642+ self._add_ua_proxy_setup_calls(
643+ [call(["ubuntu-advantage", "detach", "--assume-yes"])], append=False
644+ )
645+ )
646 self.assertEqual(self.mocks["call"].call_count, 1)
647- self.mocks["call"].assert_has_calls([
648- call(["ubuntu-advantage", "attach", "test-token-2"])
649- ])
650- self.assertEqual(self.harness.charm._state.hashed_token,
651- "ab8a83efb364bf3f6739348519b53c8e8e0f7b4c06b6eeb881ad73dcf0059107")
652- self.assertEqual(self.harness.model.unit.status,
653- ActiveStatus("Attached (esm-apps,esm-infra,livepatch)"))
654+ self.mocks["call"].assert_has_calls([call(["ubuntu-advantage", "attach", "test-token-2"])])
655+ self.assertEqual(
656+ self.harness.charm._state.hashed_token,
657+ "ab8a83efb364bf3f6739348519b53c8e8e0f7b4c06b6eeb881ad73dcf0059107",
658+ )
659+ self.assertEqual(
660+ self.harness.model.unit.status, ActiveStatus("Attached (esm-apps,esm-infra,livepatch)")
661+ )
662
663 def test_config_changed_attach_failure(self):
664 self.mocks["call"].return_value = 1
665@@ -273,30 +281,26 @@ class TestCharm(TestCase):
666 self.assertEqual(self.harness.model.unit.status, BlockedStatus(message))
667
668 def test_config_changed_token_detach(self):
669- self.mocks["check_output"].side_effect = [
670- STATUS_DETACHED,
671- STATUS_ATTACHED
672- ]
673+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_ATTACHED]
674 self.harness.update_config({"token": "test-token"})
675 self.assertEqual(self.mocks["call"].call_count, 1)
676- self.mocks["call"].assert_has_calls([
677- call(["ubuntu-advantage", "attach", "test-token"])
678- ])
679- self.assertEqual(self.harness.charm._state.hashed_token,
680- "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e")
681+ self.mocks["call"].assert_has_calls([call(["ubuntu-advantage", "attach", "test-token"])])
682+ self.assertEqual(
683+ self.harness.charm._state.hashed_token,
684+ "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e",
685+ )
686
687 self.mocks["call"].reset_mock()
688 self.mocks["check_call"].reset_mock()
689 self.mocks["check_output"].reset_mock()
690- self.mocks["check_output"].side_effect = [
691- STATUS_ATTACHED,
692- STATUS_DETACHED
693- ]
694+ self.mocks["check_output"].side_effect = [STATUS_ATTACHED, STATUS_DETACHED]
695 self.harness.update_config({"token": ""})
696 self.assertEqual(self.mocks["check_call"].call_count, 3)
697- self.mocks["check_call"].assert_has_calls(self._add_ua_proxy_setup_calls([
698- call(["ubuntu-advantage", "detach", "--assume-yes"])
699- ], append=False))
700+ self.mocks["check_call"].assert_has_calls(
701+ self._add_ua_proxy_setup_calls(
702+ [call(["ubuntu-advantage", "detach", "--assume-yes"])], append=False
703+ )
704+ )
705 self.assertEqual(self.mocks["call"].call_count, 0)
706 self.assertIsNone(self.harness.charm._state.hashed_token)
707 self.assertEqual(self.harness.model.unit.status, BlockedStatus("No token configured"))
708@@ -307,132 +311,118 @@ class TestCharm(TestCase):
709
710 self.mocks["call"].reset_mock()
711 self.mocks["check_output"].reset_mock()
712- self.mocks["check_output"].side_effect = [
713- STATUS_DETACHED,
714- STATUS_ATTACHED
715- ]
716+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_ATTACHED]
717 self.harness.update_config({"token": "test-token"})
718 self.assertEqual(self.mocks["call"].call_count, 1)
719- self.mocks["call"].assert_has_calls([
720- call(["ubuntu-advantage", "attach", "test-token"])
721- ])
722+ self.mocks["call"].assert_has_calls([call(["ubuntu-advantage", "attach", "test-token"])])
723 self.assertIsInstance(self.harness.model.unit.status, ActiveStatus)
724
725 def test_config_changed_token_contains_newline(self):
726- self.mocks["check_output"].side_effect = [
727- STATUS_DETACHED,
728- STATUS_ATTACHED
729- ]
730+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_ATTACHED]
731 self.harness.update_config({"token": "test-token\n"})
732- self.mocks["call"].assert_has_calls([
733- call(["ubuntu-advantage", "attach", "test-token"])
734- ])
735- self.assertEqual(self.harness.charm._state.hashed_token,
736- "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e")
737+ self.mocks["call"].assert_has_calls([call(["ubuntu-advantage", "attach", "test-token"])])
738+ self.assertEqual(
739+ self.harness.charm._state.hashed_token,
740+ "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e",
741+ )
742
743 def test_config_changed_ppa_contains_newline(self):
744 self.harness.update_config({"ppa": "ppa:ua-client/stable\n"})
745- self.mocks["check_call"].assert_has_calls([
746- call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
747- ])
748+ self.mocks["check_call"].assert_has_calls(
749+ [
750+ call(["add-apt-repository", "--yes", "ppa:ua-client/stable"], env=self.env),
751+ ]
752+ )
753 self.assertEqual(self.harness.charm._state.ppa, "ppa:ua-client/stable")
754
755 def test_config_changed_check_output_returns_bytes(self):
756 self.mocks["check_output"].side_effect = [
757 bytes(STATUS_DETACHED, "utf-8"),
758- bytes(STATUS_ATTACHED, "utf-8")
759+ bytes(STATUS_ATTACHED, "utf-8"),
760 ]
761 self.harness.update_config({"token": "test-token"})
762- self.assertEqual(self.harness.model.unit.status,
763- ActiveStatus("Attached (esm-apps,esm-infra,livepatch)"))
764+ self.assertEqual(
765+ self.harness.model.unit.status, ActiveStatus("Attached (esm-apps,esm-infra,livepatch)")
766+ )
767
768 def test_config_changed_contract_url(self):
769- self.mocks["check_output"].side_effect = [
770- STATUS_DETACHED,
771- STATUS_ATTACHED
772- ]
773+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_ATTACHED]
774 self.harness.update_config({"contract_url": "https://contracts.staging.canonical.com"})
775 self.mocks["open"].assert_called_with("/etc/ubuntu-advantage/uaclient.conf", "r+")
776 handle = self.mocks["open"]()
777- expected = dedent("""\
778+ expected = dedent(
779+ """\
780 contract_url: https://contracts.staging.canonical.com
781 data_dir: /var/lib/ubuntu-advantage
782 log_file: /var/log/ubuntu-advantage.log
783 log_level: debug
784- """)
785+ """
786+ )
787 self.assertEqual(_written(handle), expected)
788 handle.truncate.assert_called_once()
789- self.assertEqual(self.harness.charm._state.contract_url,
790- "https://contracts.staging.canonical.com")
791+ self.assertEqual(
792+ self.harness.charm._state.contract_url, "https://contracts.staging.canonical.com"
793+ )
794
795 def test_config_changed_contract_url_reattach(self):
796- self.mocks["check_output"].side_effect = [
797- STATUS_DETACHED,
798- STATUS_ATTACHED
799- ]
800+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_ATTACHED]
801 self.harness.update_config({"token": "test-token"})
802- self.mocks["call"].assert_has_calls([
803- call(["ubuntu-advantage", "attach", "test-token"])
804- ])
805- self.assertEqual(self.harness.charm._state.hashed_token,
806- "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e")
807+ self.mocks["call"].assert_has_calls([call(["ubuntu-advantage", "attach", "test-token"])])
808+ self.assertEqual(
809+ self.harness.charm._state.hashed_token,
810+ "4c5dc9b7708905f77f5e5d16316b5dfb425e68cb326dcd55a860e90a7707031e",
811+ )
812
813 self.mocks["call"].reset_mock()
814 self.mocks["check_call"].reset_mock()
815 self.mocks["check_output"].reset_mock()
816 self.mocks["open"].reset_mock()
817- self.mocks["check_output"].side_effect = [
818- STATUS_ATTACHED,
819- STATUS_ATTACHED
820- ]
821+ self.mocks["check_output"].side_effect = [STATUS_ATTACHED, STATUS_ATTACHED]
822 mock_open(self.mocks["open"], read_data=DEFAULT_CLIENT_CONFIG)
823 self.harness.update_config({"contract_url": "https://contracts.staging.canonical.com"})
824 self.mocks["open"].assert_called_with("/etc/ubuntu-advantage/uaclient.conf", "r+")
825 handle = self.mocks["open"]()
826- expected = dedent("""\
827+ expected = dedent(
828+ """\
829 contract_url: https://contracts.staging.canonical.com
830 data_dir: /var/lib/ubuntu-advantage
831 log_file: /var/log/ubuntu-advantage.log
832 log_level: debug
833- """)
834+ """
835+ )
836 self.assertEqual(_written(handle), expected)
837 handle.truncate.assert_called_once()
838- self.mocks["check_call"].assert_has_calls([
839- call(["ubuntu-advantage", "detach", "--assume-yes"])
840- ])
841- self.mocks["call"].assert_has_calls([
842- call(["ubuntu-advantage", "attach", "test-token"])
843- ])
844- self.assertEqual(self.harness.model.unit.status,
845- ActiveStatus("Attached (esm-apps,esm-infra,livepatch)"))
846+ self.mocks["check_call"].assert_has_calls(
847+ [call(["ubuntu-advantage", "detach", "--assume-yes"])]
848+ )
849+ self.mocks["call"].assert_has_calls([call(["ubuntu-advantage", "attach", "test-token"])])
850+ self.assertEqual(
851+ self.harness.model.unit.status, ActiveStatus("Attached (esm-apps,esm-infra,livepatch)")
852+ )
853
854 self.mocks["call"].reset_mock()
855 self.mocks["check_call"].reset_mock()
856 self.mocks["check_output"].reset_mock()
857 self.mocks["open"].reset_mock()
858- self.mocks["check_output"].side_effect = [
859- STATUS_ATTACHED,
860- STATUS_ATTACHED
861- ]
862+ self.mocks["check_output"].side_effect = [STATUS_ATTACHED, STATUS_ATTACHED]
863 mock_open(self.mocks["open"], read_data=DEFAULT_CLIENT_CONFIG)
864 self.harness.update_config()
865 self.mocks["open"].assert_not_called()
866 self.mocks["call"].assert_not_called()
867
868 def test_config_changed_unset_contract_url(self):
869- self.mocks["check_output"].side_effect = [
870- STATUS_DETACHED,
871- STATUS_DETACHED
872- ]
873+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_DETACHED]
874 self.harness.update_config({"contract_url": "https://contracts.staging.canonical.com"})
875 self.mocks["open"].assert_called_with("/etc/ubuntu-advantage/uaclient.conf", "r+")
876 handle = self.mocks["open"]()
877- expected = dedent("""\
878+ expected = dedent(
879+ """\
880 contract_url: https://contracts.staging.canonical.com
881 data_dir: /var/lib/ubuntu-advantage
882 log_file: /var/log/ubuntu-advantage.log
883 log_level: debug
884- """)
885+ """
886+ )
887 self.assertEqual(_written(handle), expected)
888 handle.truncate.assert_called_once()
889 self.mocks["call"].assert_not_called()
890@@ -441,19 +431,18 @@ class TestCharm(TestCase):
891 self.mocks["call"].reset_mock()
892 self.mocks["check_call"].reset_mock()
893 self.mocks["check_output"].reset_mock()
894- self.mocks["check_output"].side_effect = [
895- STATUS_DETACHED,
896- STATUS_DETACHED
897- ]
898+ self.mocks["check_output"].side_effect = [STATUS_DETACHED, STATUS_DETACHED]
899 self.harness.update_config({"contract_url": "https://contracts.canonical.com"})
900 self.mocks["open"].assert_called_with("/etc/ubuntu-advantage/uaclient.conf", "r+")
901 handle = self.mocks["open"]()
902- expected = dedent("""\
903+ expected = dedent(
904+ """\
905 contract_url: https://contracts.canonical.com
906 data_dir: /var/lib/ubuntu-advantage
907 log_file: /var/log/ubuntu-advantage.log
908 log_level: debug
909- """)
910+ """
911+ )
912 self.assertEqual(_written(handle), expected)
913 handle.truncate.assert_called_once()
914 self.mocks["call"].assert_not_called()
915@@ -474,7 +463,7 @@ class TestCharm(TestCase):
916 self.assertEqual(self.harness.charm.env["no_proxy"], TEST_NO_PROXY)
917
918 def _add_ua_proxy_setup_calls(self, call_list, append=True):
919- """Helper to generate the calls used for UA proxy setup"""
920+ """Helper to generate the calls used for UA proxy setup."""
921 proxy_calls = [
922 call(
923 [
924@@ -496,10 +485,8 @@ class TestCharm(TestCase):
925 return call_list + proxy_calls if append else proxy_calls + call_list
926
927 def _assert_apt_calls(self):
928- """Helper to run the assertions for apt install/remove"""
929- self.mocks["apt"].remove_package.assert_called_once_with(
930- "ubuntu-advantage-tools"
931- )
932+ """Helper to run the assertions for apt install/remove."""
933+ self.mocks["apt"].remove_package.assert_called_once_with("ubuntu-advantage-tools")
934 self.mocks["apt"].add_package.assert_called_once_with(
935 "ubuntu-advantage-tools", update_cache=True
936 )
937diff --git a/tox.ini b/tox.ini
938new file mode 100644
939index 0000000..f17fc45
940--- /dev/null
941+++ b/tox.ini
942@@ -0,0 +1,78 @@
943+# Copyright 2022 Canonical Ltd.
944+# See LICENSE file for licensing details.
945+
946+[tox]
947+skipsdist=True
948+skip_missing_interpreters = True
949+envlist = lint, unit
950+
951+[vars]
952+src_path = {toxinidir}/src/
953+tst_path = {toxinidir}/tests/
954+lib_path = {toxinidir}/lib/charms/operator_libs_linux
955+all_path = {[vars]src_path} {[vars]tst_path}
956+
957+[testenv]
958+setenv =
959+ PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path}
960+ PYTHONBREAKPOINT=ipdb.set_trace
961+ PY_COLORS=1
962+passenv =
963+ PYTHONPATH
964+ HOME
965+ PATH
966+ CHARM_BUILD_DIR
967+ MODEL_SETTINGS
968+ HTTP_PROXY
969+ HTTPS_PROXY
970+ NO_PROXY
971+
972+[testenv:fmt]
973+description = Apply coding style standards to code
974+deps =
975+ black
976+ isort
977+commands =
978+ isort {[vars]all_path}
979+ black {[vars]all_path}
980+
981+[testenv:lint]
982+description = Check code against coding style standards
983+deps =
984+ black
985+ flake8
986+ flake8-docstrings
987+ flake8-copyright
988+ flake8-builtins
989+ pyproject-flake8
990+ pep8-naming
991+ isort
992+ codespell
993+commands =
994+ codespell {toxinidir}/*.yaml {toxinidir}/*.ini {toxinidir}/*.md \
995+ {toxinidir}/*.toml {toxinidir}/*.txt {toxinidir}/.github
996+ # pflake8 wrapper supports config from pyproject.toml
997+ pflake8 {[vars]all_path}
998+ isort --check-only --diff {[vars]all_path}
999+ black --check --diff {[vars]all_path}
1000+
1001+[testenv:unit]
1002+description = Run unit tests
1003+deps =
1004+ pytest
1005+ coverage[toml]
1006+ -r{toxinidir}/requirements.txt
1007+commands =
1008+ coverage run --source={[vars]src_path} \
1009+ -m pytest --ignore={[vars]tst_path}integration -v --tb native -s {posargs}
1010+ coverage report
1011+
1012+[testenv:integration]
1013+description = Run integration tests
1014+deps =
1015+ git+https://github.com/juju/python-libjuju.git
1016+ pytest
1017+ git+https://github.com/charmed-kubernetes/pytest-operator.git
1018+ -r{toxinidir}/requirements.txt
1019+commands =
1020+ pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs}
1021\ No newline at end of file

Subscribers

People subscribed via source and target branches