Repository: canonical/operator (language: python)

Issue:
Move real pebble tests into their own file.
This will help make it clear that those tests are special and not vanilla unit tests - they run together in a special environment where the pebble daemon is running.


Discussion / hints:

Fix patch:
diff --git a/HACKING.md b/HACKING.md
index 4badfac95..189a2aa1b 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -70,13 +70,13 @@ To do this even more manually, you could start the pebble server yourself:
 ```sh
 export PEBBLE=$HOME/pebble
 export RUN_REAL_PEBBLE_TESTS=1
-pebble run --create-dirs &>pebble.log &
+pebble run --create-dirs --http=:4000 &>pebble.log &
 
 # Then
-tox -e unit -- -k RealPebble
+tox -e unit -- test/test_real_pebble.py
 # or
 source .tox/unit/bin/activate
-pytest -v -k RealPebble
+pytest -v test/test_real_pebble.py
 ```
 
 # Documentation
diff --git a/tox.ini b/tox.ini
index a7700b048..186ca8bbf 100644
--- a/tox.ini
+++ b/tox.ini
@@ -78,7 +78,7 @@ setenv =
 deps =
     -r{toxinidir}/requirements-dev.txt
 commands =
-    bash -c "umask 0; (pebble run --http=':4000' --create-dirs &>/dev/null & ) ; sleep 1; pytest -v --tb native -k RealPebble {posargs} ; killall -y 3m pebble"
+    bash -c "umask 0; (pebble run --http=':4000' --create-dirs &>/dev/null & ) ; sleep 1; pytest -v --tb native test/test_real_pebble.py {posargs} ; killall -y 3m pebble"
 
 [testenv:smoke]
 description = Run a smoke test against a Juju controller.


Test patch:
diff --git a/.github/workflows/framework-tests.yaml b/.github/workflows/framework-tests.yaml
index 082635c89..79cab3c5f 100644
--- a/.github/workflows/framework-tests.yaml
+++ b/.github/workflows/framework-tests.yaml
@@ -86,7 +86,7 @@ jobs:
           PEBBLE: /tmp/pebble
 
       - name: Run Real pebble tests
-        run: tox -e unit -- -k RealPebble
+        run: tox -e unit -- test/test_real_pebble.py
         env:
           RUN_REAL_PEBBLE_TESTS: 1
           PEBBLE: /tmp/pebble
diff --git a/test/test_pebble.py b/test/test_pebble.py
index 6c9b6b7fe..f7275756b 100644
--- a/test/test_pebble.py
+++ b/test/test_pebble.py
@@ -18,18 +18,12 @@
 import email.parser
 import io
 import json
-import os
 import signal
 import tempfile
 import test.fake_pebble as fake_pebble
-import threading
-import time
 import unittest
 import unittest.mock
 import unittest.util
-import urllib.error
-import urllib.request
-import uuid
 
 import pytest
 import websocket
@@ -3210,228 +3204,3 @@ def recv():
     if hasattr(pytest, 'PytestUnhandledThreadExceptionWarning'):
         test_websocket_recv_raises = pytest.mark.filterwarnings(
             'ignore::pytest.PytestUnhandledThreadExceptionWarning')(test_websocket_recv_raises)
-
-
-# Set the RUN_REAL_PEBBLE_TESTS environment variable to run these tests
-# against a real Pebble server. For example, in one terminal, run Pebble:
-#
-# $ PEBBLE=~/pebble pebble run --http=:4000
-# 2021-09-20T04:10:34.934Z [pebble] Started daemon
-#
-# In another terminal, run the tests:
-#
-# $ source .tox/unit/bin/activate
-# $ RUN_REAL_PEBBLE_TESTS=1 PEBBLE=~/pebble pytest test/test_pebble.py -v -k RealPebble
-# $ deactivate
-#
-@unittest.skipUnless(os.getenv('RUN_REAL_PEBBLE_TESTS'), 'RUN_REAL_PEBBLE_TESTS not set')
-class TestRealPebble(unittest.TestCase):
-    def setUp(self):
-        socket_path = os.getenv('PEBBLE_SOCKET')
-        if not socket_path and os.getenv('PEBBLE'):
-            socket_path = os.path.join(os.getenv('PEBBLE'), '.pebble.socket')
-        assert socket_path, 'PEBBLE or PEBBLE_SOCKET must be set if RUN_REAL_PEBBLE_TESTS set'
-
-        self.client = pebble.Client(socket_path=socket_path)
-
-    def test_checks_and_health(self):
-        self.client.add_layer('layer', {
-            'checks': {
-                'bad': {
-                    'override': 'replace',
-                    'level': 'ready',
-                    'period': '50ms',
-                    'threshold': 2,
-                    'exec': {
-                        'command': 'sleep x',
-                    },
-                },
-                'good': {
-                    'override': 'replace',
-                    'level': 'alive',
-                    'period': '50ms',
-                    'exec': {
-                        'command': 'echo foo',
-                    },
-                },
-                'other': {
-                    'override': 'replace',
-                    'exec': {
-                        'command': 'echo bar',
-                    },
-                },
-            },
-        }, combine=True)
-
-        # Checks should all be "up" initially
-        checks = self.client.get_checks()
-        self.assertEqual(len(checks), 3)
-        self.assertEqual(checks[0].name, 'bad')
-        self.assertEqual(checks[0].level, pebble.CheckLevel.READY)
-        self.assertEqual(checks[0].status, pebble.CheckStatus.UP)
-        self.assertEqual(checks[1].name, 'good')
-        self.assertEqual(checks[1].level, pebble.CheckLevel.ALIVE)
-        self.assertEqual(checks[1].status, pebble.CheckStatus.UP)
-        self.assertEqual(checks[2].name, 'other')
-        self.assertEqual(checks[2].level, pebble.CheckLevel.UNSET)
-        self.assertEqual(checks[2].status, pebble.CheckStatus.UP)
-
-        # And /v1/health should return "healthy"
-        health = self._get_health()
-        self.assertEqual(health, {
-            'result': {'healthy': True},
-            'status': 'OK',
-            'status-code': 200,
-            'type': 'sync',
-        })
-
-        # After two retries the "bad" check should go down
-        for i in range(5):
-            checks = self.client.get_checks()
-            bad_check = [c for c in checks if c.name == 'bad'][0]
-            if bad_check.status == pebble.CheckStatus.DOWN:
-                break
-            time.sleep(0.06)
-        else:
-            assert False, 'timed out waiting for "bad" check to go down'
-        self.assertEqual(bad_check.failures, 2)
-        self.assertEqual(bad_check.threshold, 2)
-        good_check = [c for c in checks if c.name == 'good'][0]
-        self.assertEqual(good_check.status, pebble.CheckStatus.UP)
-
-        # And /v1/health should return "unhealthy" (with status HTTP 502)
-        with self.assertRaises(urllib.error.HTTPError) as cm:
-            self._get_health()
-        self.assertEqual(cm.exception.code, 502)
-        health = json.loads(cm.exception.read())
-        self.assertEqual(health, {
-            'result': {'healthy': False},
-            'status': 'Bad Gateway',
-            'status-code': 502,
-            'type': 'sync',
-        })
-
-        # Then test filtering by check level and by name
-        checks = self.client.get_checks(level=pebble.CheckLevel.ALIVE)
-        self.assertEqual(len(checks), 1)
-        self.assertEqual(checks[0].name, 'good')
-        checks = self.client.get_checks(names=['good', 'bad'])
-        self.assertEqual(len(checks), 2)
-        self.assertEqual(checks[0].name, 'bad')
-        self.assertEqual(checks[1].name, 'good')
-
-    def _get_health(self):
-        f = urllib.request.urlopen('http://localhost:4000/v1/health')
-        return json.loads(f.read())
-
-    def test_exec_wait(self):
-        process = self.client.exec(['true'])
-        process.wait()
-
-        with self.assertRaises(pebble.ExecError) as cm:
-            process = self.client.exec(['/bin/sh', '-c', 'exit 42'])
-            process.wait()
-        self.assertEqual(cm.exception.exit_code, 42)
-
-    def test_exec_wait_output(self):
-        process = self.client.exec(['/bin/sh', '-c', 'echo OUT; echo ERR >&2'])
-        out, err = process.wait_output()
-        self.assertEqual(out, 'OUT\n')
-        self.assertEqual(err, 'ERR\n')
-
-        process = self.client.exec(['/bin/sh', '-c', 'echo OUT; echo ERR >&2'], encoding=None)
-        out, err = process.wait_output()
-        self.assertEqual(out, b'OUT\n')
-        self.assertEqual(err, b'ERR\n')
-
-        with self.assertRaises(pebble.ExecError) as cm:
-            process = self.client.exec(['/bin/sh', '-c', 'echo OUT; echo ERR >&2; exit 42'])
-            process.wait_output()
-        self.assertEqual(cm.exception.exit_code, 42)
-        self.assertEqual(cm.exception.stdout, 'OUT\n')
-        self.assertEqual(cm.exception.stderr, 'ERR\n')
-
-    def test_exec_send_stdin(self):
-        process = self.client.exec(['awk', '{ print toupper($0) }'], stdin='foo\nBar\n')
-        out, err = process.wait_output()
-        self.assertEqual(out, 'FOO\nBAR\n')
-        self.assertEqual(err, '')
-
-        process = self.client.exec(['awk', '{ print toupper($0) }'], stdin=b'foo\nBar\n',
-                                   encoding=None)
-        out, err = process.wait_output()
-        self.assertEqual(out, b'FOO\nBAR\n')
-        self.assertEqual(err, b'')
-
-    def test_push_pull(self):
-        fname = os.path.join(tempfile.gettempdir(), f'pebbletest-{uuid.uuid4()}')
-        content = 'foo\nbar\nbaz-42'
-        self.client.push(fname, content)
-        with self.client.pull(fname) as f:
-            data = f.read()
-            self.assertEqual(data, content)
-        os.remove(fname)
-
-    def test_exec_timeout(self):
-        process = self.client.exec(['sleep', '0.2'], timeout=0.1)
-        with self.assertRaises(pebble.ChangeError) as cm:
-            process.wait()
-        self.assertIn('timed out', cm.exception.err)
-
-    def test_exec_working_dir(self):
-        with tempfile.TemporaryDirectory() as temp_dir:
-            process = self.client.exec(['pwd'], working_dir=temp_dir)
-            out, err = process.wait_output()
-            self.assertEqual(out, f"{temp_dir}\n")
-            self.assertEqual(err, '')
-
-    def test_exec_environment(self):
-        process = self.client.exec(['/bin/sh', '-c', 'echo $ONE.$TWO.$THREE'],
-                                   environment={'ONE': '1', 'TWO': '2'})
-        out, err = process.wait_output()
-        self.assertEqual(out, '1.2.\n')
-        self.assertEqual(err, '')
-
-    def test_exec_streaming(self):
-        process = self.client.exec(['cat'])
-
-        def stdin_thread():
-            try:
-                for line in ['one\n', '2\n', 'THREE\n']:
-                    process.stdin.write(line)
-                    process.stdin.flush()
-                    time.sleep(0.1)
-            finally:
-                process.stdin.close()
-
-        threading.Thread(target=stdin_thread).start()
-
-        reads = []
-        for line in process.stdout:
-            reads.append(line)
-
-        process.wait()
-
-        self.assertEqual(reads, ['one\n', '2\n', 'THREE\n'])
-
-    def test_exec_streaming_bytes(self):
-        process = self.client.exec(['cat'], encoding=None)
-
-        def stdin_thread():
-            try:
-                for line in [b'one\n', b'2\n', b'THREE\n']:
-                    process.stdin.write(line)
-                    process.stdin.flush()
-                    time.sleep(0.1)
-            finally:
-                process.stdin.close()
-
-        threading.Thread(target=stdin_thread).start()
-
-        reads = []
-        for line in process.stdout:
-            reads.append(line)
-
-        process.wait()
-
-        self.assertEqual(reads, [b'one\n', b'2\n', b'THREE\n'])
diff --git a/test/test_real_pebble.py b/test/test_real_pebble.py
new file mode 100644
index 000000000..c94b51d1c
--- /dev/null
+++ b/test/test_real_pebble.py
@@ -0,0 +1,276 @@
+#!/usr/bin/python3
+# Copyright 2021 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import os
+import shutil
+import tempfile
+import threading
+import time
+import unittest
+import urllib.error
+import urllib.request
+import uuid
+
+from ops import pebble
+
+from .test_testing import PebbleStorageAPIsTestMixin
+
+
+# Set the RUN_REAL_PEBBLE_TESTS environment variable to run these tests
+# against a real Pebble server. For example, in one terminal, run Pebble:
+#
+# $ PEBBLE=~/pebble pebble run --http=:4000
+# 2021-09-20T04:10:34.934Z [pebble] Started daemon
+#
+# In another terminal, run the tests:
+#
+# $ source .tox/unit/bin/activate
+# $ RUN_REAL_PEBBLE_TESTS=1 PEBBLE=~/pebble pytest test/test_real_pebble.py -v
+# $ deactivate
+#
+@unittest.skipUnless(os.getenv('RUN_REAL_PEBBLE_TESTS'), 'RUN_REAL_PEBBLE_TESTS not set')
+class TestRealPebble(unittest.TestCase):
+    def setUp(self):
+        socket_path = os.getenv('PEBBLE_SOCKET')
+        if not socket_path and os.getenv('PEBBLE'):
+            socket_path = os.path.join(os.getenv('PEBBLE'), '.pebble.socket')
+        assert socket_path, 'PEBBLE or PEBBLE_SOCKET must be set if RUN_REAL_PEBBLE_TESTS set'
+
+        self.client = pebble.Client(socket_path=socket_path)
+
+    def test_checks_and_health(self):
+        self.client.add_layer('layer', {
+            'checks': {
+                'bad': {
+                    'override': 'replace',
+                    'level': 'ready',
+                    'period': '50ms',
+                    'threshold': 2,
+                    'exec': {
+                        'command': 'sleep x',
+                    },
+                },
+                'good': {
+                    'override': 'replace',
+                    'level': 'alive',
+                    'period': '50ms',
+                    'exec': {
+                        'command': 'echo foo',
+                    },
+                },
+                'other': {
+                    'override': 'replace',
+                    'exec': {
+                        'command': 'echo bar',
+                    },
+                },
+            },
+        }, combine=True)
+
+        # Checks should all be "up" initially
+        checks = self.client.get_checks()
+        self.assertEqual(len(checks), 3)
+        self.assertEqual(checks[0].name, 'bad')
+        self.assertEqual(checks[0].level, pebble.CheckLevel.READY)
+        self.assertEqual(checks[0].status, pebble.CheckStatus.UP)
+        self.assertEqual(checks[1].name, 'good')
+        self.assertEqual(checks[1].level, pebble.CheckLevel.ALIVE)
+        self.assertEqual(checks[1].status, pebble.CheckStatus.UP)
+        self.assertEqual(checks[2].name, 'other')
+        self.assertEqual(checks[2].level, pebble.CheckLevel.UNSET)
+        self.assertEqual(checks[2].status, pebble.CheckStatus.UP)
+
+        # And /v1/health should return "healthy"
+        health = self._get_health()
+        self.assertEqual(health, {
+            'result': {'healthy': True},
+            'status': 'OK',
+            'status-code': 200,
+            'type': 'sync',
+        })
+
+        # After two retries the "bad" check should go down
+        for _ in range(5):
+            checks = self.client.get_checks()
+            bad_check = [c for c in checks if c.name == 'bad'][0]
+            if bad_check.status == pebble.CheckStatus.DOWN:
+                break
+            time.sleep(0.06)
+        else:
+            assert False, 'timed out waiting for "bad" check to go down'
+        self.assertEqual(bad_check.failures, 2)
+        self.assertEqual(bad_check.threshold, 2)
+        good_check = [c for c in checks if c.name == 'good'][0]
+        self.assertEqual(good_check.status, pebble.CheckStatus.UP)
+
+        # And /v1/health should return "unhealthy" (with status HTTP 502)
+        with self.assertRaises(urllib.error.HTTPError) as cm:
+            self._get_health()
+        self.assertEqual(cm.exception.code, 502)
+        health = json.loads(cm.exception.read())
+        self.assertEqual(health, {
+            'result': {'healthy': False},
+            'status': 'Bad Gateway',
+            'status-code': 502,
+            'type': 'sync',
+        })
+
+        # Then test filtering by check level and by name
+        checks = self.client.get_checks(level=pebble.CheckLevel.ALIVE)
+        self.assertEqual(len(checks), 1)
+        self.assertEqual(checks[0].name, 'good')
+        checks = self.client.get_checks(names=['good', 'bad'])
+        self.assertEqual(len(checks), 2)
+        self.assertEqual(checks[0].name, 'bad')
+        self.assertEqual(checks[1].name, 'good')
+
+    def _get_health(self):
+        f = urllib.request.urlopen('http://localhost:4000/v1/health')
+        return json.loads(f.read())
+
+    def test_exec_wait(self):
+        process = self.client.exec(['true'])
+        process.wait()
+
+        with self.assertRaises(pebble.ExecError) as cm:
+            process = self.client.exec(['/bin/sh', '-c', 'exit 42'])
+            process.wait()
+        self.assertEqual(cm.exception.exit_code, 42)
+
+    def test_exec_wait_output(self):
+        process = self.client.exec(['/bin/sh', '-c', 'echo OUT; echo ERR >&2'])
+        out, err = process.wait_output()
+        self.assertEqual(out, 'OUT\n')
+        self.assertEqual(err, 'ERR\n')
+
+        process = self.client.exec(['/bin/sh', '-c', 'echo OUT; echo ERR >&2'], encoding=None)
+        out, err = process.wait_output()
+        self.assertEqual(out, b'OUT\n')
+        self.assertEqual(err, b'ERR\n')
+
+        with self.assertRaises(pebble.ExecError) as cm:
+            process = self.client.exec(['/bin/sh', '-c', 'echo OUT; echo ERR >&2; exit 42'])
+            process.wait_output()
+        self.assertEqual(cm.exception.exit_code, 42)
+        self.assertEqual(cm.exception.stdout, 'OUT\n')
+        self.assertEqual(cm.exception.stderr, 'ERR\n')
+
+    def test_exec_send_stdin(self):
+        process = self.client.exec(['awk', '{ print toupper($0) }'], stdin='foo\nBar\n')
+        out, err = process.wait_output()
+        self.assertEqual(out, 'FOO\nBAR\n')
+        self.assertEqual(err, '')
+
+        process = self.client.exec(['awk', '{ print toupper($0) }'], stdin=b'foo\nBar\n',
+                                   encoding=None)
+        out, err = process.wait_output()
+        self.assertEqual(out, b'FOO\nBAR\n')
+        self.assertEqual(err, b'')
+
+    def test_push_pull(self):
+        fname = os.path.join(tempfile.gettempdir(), f'pebbletest-{uuid.uuid4()}')
+        content = 'foo\nbar\nbaz-42'
+        self.client.push(fname, content)
+        with self.client.pull(fname) as f:
+            data = f.read()
+            self.assertEqual(data, content)
+        os.remove(fname)
+
+    def test_exec_timeout(self):
+        process = self.client.exec(['sleep', '0.2'], timeout=0.1)
+        with self.assertRaises(pebble.ChangeError) as cm:
+            process.wait()
+        self.assertIn('timed out', cm.exception.err)
+
+    def test_exec_working_dir(self):
+        with tempfile.TemporaryDirectory() as temp_dir:
+            process = self.client.exec(['pwd'], working_dir=temp_dir)
+            out, err = process.wait_output()
+            self.assertEqual(out, f"{temp_dir}\n")
+            self.assertEqual(err, '')
+
+    def test_exec_environment(self):
+        process = self.client.exec(['/bin/sh', '-c', 'echo $ONE.$TWO.$THREE'],
+                                   environment={'ONE': '1', 'TWO': '2'})
+        out, err = process.wait_output()
+        self.assertEqual(out, '1.2.\n')
+        self.assertEqual(err, '')
+
+    def test_exec_streaming(self):
+        process = self.client.exec(['cat'])
+
+        def stdin_thread():
+            try:
+                for line in ['one\n', '2\n', 'THREE\n']:
+                    process.stdin.write(line)
+                    process.stdin.flush()
+                    time.sleep(0.1)
+            finally:
+                process.stdin.close()
+
+        threading.Thread(target=stdin_thread).start()
+
+        reads = []
+        for line in process.stdout:
+            reads.append(line)
+
+        process.wait()
+
+        self.assertEqual(reads, ['one\n', '2\n', 'THREE\n'])
+
+    def test_exec_streaming_bytes(self):
+        process = self.client.exec(['cat'], encoding=None)
+
+        def stdin_thread():
+            try:
+                for line in [b'one\n', b'2\n', b'THREE\n']:
+                    process.stdin.write(line)
+                    process.stdin.flush()
+                    time.sleep(0.1)
+            finally:
+                process.stdin.close()
+
+        threading.Thread(target=stdin_thread).start()
+
+        reads = []
+        for line in process.stdout:
+            reads.append(line)
+
+        process.wait()
+
+        self.assertEqual(reads, [b'one\n', b'2\n', b'THREE\n'])
+
+
+@unittest.skipUnless(os.getenv('RUN_REAL_PEBBLE_TESTS'), 'RUN_REAL_PEBBLE_TESTS not set')
+class TestPebbleStorageAPIsUsingRealPebble(unittest.TestCase, PebbleStorageAPIsTestMixin):
+    def setUp(self):
+        socket_path = os.getenv('PEBBLE_SOCKET')
+        pebble_dir = os.getenv('PEBBLE')
+        if not socket_path and pebble_dir:
+            socket_path = os.path.join(pebble_dir, '.pebble.socket')
+        assert socket_path and pebble_dir, 'PEBBLE must be set if RUN_REAL_PEBBLE_TESTS set'
+
+        self.prefix = tempfile.mkdtemp(dir=pebble_dir)
+        self.client = pebble.Client(socket_path=socket_path)
+
+    def tearDown(self):
+        shutil.rmtree(self.prefix)
+
+    # Remove this entirely once the associated bug is fixed; it overrides the original test in the
+    # test mixin class.
+    @unittest.skip('pending resolution of https://github.com/canonical/pebble/issues/80')
+    def test_make_dir_with_permission_mask(self):
+        pass
diff --git a/test/test_testing.py b/test/test_testing.py
index 7e25285f7..d49c4a949 100644
--- a/test/test_testing.py
+++ b/test/test_testing.py
@@ -4050,7 +4050,7 @@ def test_send_signal(self):
 # For testing file-ops of the pebble client.  This is refactored into a
 # separate mixin so we can run these tests against both the mock client as
 # well as a real pebble server instance.
-class _PebbleStorageAPIsTestMixin:
+class PebbleStorageAPIsTestMixin:
     # Override this in classes using this mixin.
     # This should be set to any non-empty path, but without a trailing /.
     prefix: str
@@ -4374,7 +4374,7 @@ class _MakedirArgs(typing.TypedDict):
 class TestPebbleStorageAPIsUsingMocks(
         unittest.TestCase,
         _TestingPebbleClientMixin,
-        _PebbleStorageAPIsTestMixin):
+        PebbleStorageAPIsTestMixin):
     def setUp(self):
         self.prefix = '/prefix'
         self.client = self.get_testing_client()
@@ -4537,28 +4537,6 @@ def test_make_dir_with_ownership(self):
             self.assertEqual(dir_.path, f"{self.prefix}/dir{idx}")
 
 
-@unittest.skipUnless(os.getenv('RUN_REAL_PEBBLE_TESTS'), 'RUN_REAL_PEBBLE_TESTS not set')
-class TestPebbleStorageAPIsUsingRealPebble(unittest.TestCase, _PebbleStorageAPIsTestMixin):
-    def setUp(self):
-        socket_path = os.getenv('PEBBLE_SOCKET')
-        pebble_dir = os.getenv('PEBBLE')
-        if not socket_path and pebble_dir:
-            socket_path = os.path.join(pebble_dir, '.pebble.socket')
-        assert socket_path and pebble_dir, 'PEBBLE must be set if RUN_REAL_PEBBLE_TESTS set'
-
-        self.prefix = tempfile.mkdtemp(dir=pebble_dir)
-        self.client = pebble.Client(socket_path=socket_path)
-
-    def tearDown(self):
-        shutil.rmtree(self.prefix)
-
-    # Remove this entirely once the associated bug is fixed; it overrides the original test in the
-    # test mixin class.
-    @unittest.skip('pending resolution of https://github.com/canonical/pebble/issues/80')
-    def test_make_dir_with_permission_mask(self):
-        pass
-
-
 class TestFilesystem(unittest.TestCase, _TestingPebbleClientMixin):
     def setUp(self) -> None:
         self.harness = ops.testing.Harness(ops.CharmBase, meta='''