Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ build/
*.charm
*.orig
.coverage
.report
**/__pycache__/
*.py[cod]
.idea/
Expand Down
5 changes: 4 additions & 1 deletion lib/charms/operator_libs_linux/v2/snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 10
LIBPATCH = 11


# Regex to locate 7-bit C1 ANSI sequences
Expand Down Expand Up @@ -577,6 +577,9 @@ def _refresh(
if revision:
args.append(f'--revision="{revision}"')

if self.confinement == 'classic':
args.append('--classic')

if devmode:
args.append("--devmode")

Expand Down
57 changes: 49 additions & 8 deletions tests/unit/test_snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@

# pyright: reportPrivateUsage=false

from __future__ import annotations

import datetime
import io
import json
import time
import typing
import unittest
from subprocess import CalledProcessError
from typing import Any, Dict, Iterable, Optional
from typing import Any, Iterable
from unittest.mock import MagicMock, mock_open, patch

import fake_snapd as fake_snapd
import pytest
from charms.operator_libs_linux.v2 import snap

patch("charms.operator_libs_linux.v2.snap._cache_init", lambda x: x).start()
Expand Down Expand Up @@ -621,6 +624,43 @@ def test_services_property(self, patched):
)


@patch('charms.operator_libs_linux.v2.snap.subprocess.check_output')
@pytest.mark.parametrize(
'confinement,classic,expected_flag',
[
('classic', False, ['--classic']),
('classic', True, ['--classic']),
('strict', False, []),
('strict', True, ['--classic']),
],
)
def test_refresh_classic(
mock_subprocess: MagicMock, confinement: str, classic: bool, expected_flag: list[str]
):
"""Test that ensure and _refresh add the --classic flag with confinement set to classic."""
foo = snap.Snap(
name='foo',
state=snap.SnapState.Present,
channel='stable',
revision='1',
confinement=confinement,
apps=None,
cohort='A',
)
foo.ensure(snap.SnapState.Latest, revision='2', classic=classic)
mock_subprocess.assert_called_with(
[
'snap',
'refresh',
'foo',
'--revision="2"',
*expected_flag,
'--cohort="A"',
],
text=True,
)


class TestSocketClient(unittest.TestCase):
def test_socket_not_found(self):
client = snap.SnapClient(socket_path="/does/not/exist")
Expand Down Expand Up @@ -707,8 +747,8 @@ def test_wait_changes(self):
def _request_raw(
method: str,
path: str,
query: Dict = None,
headers: Dict = None,
query: dict = None,
headers: dict = None,
data: bytes = None,
) -> typing.IO[bytes]:
nonlocal change_finished
Expand Down Expand Up @@ -818,8 +858,8 @@ def test_wait_failed(self):
def _request_raw(
method: str,
path: str,
query: Dict = None,
headers: Dict = None,
query: dict = None,
headers: dict = None,
data: bytes = None,
) -> typing.IO[bytes]:
if method == "PUT" and path == "snaps/test/conf":
Expand Down Expand Up @@ -884,7 +924,7 @@ def test_can_run_bare_changes(self, mock_subprocess: MagicMock):
self.assertTrue(foo.present)
snap.add("curl", state="latest") # cover string conversion path
mock_subprocess.assert_called_with(
["snap", "refresh", "curl", '--channel="latest"'],
["snap", "refresh", "curl", '--channel="latest"', '--classic'],
text=True,
)
with self.assertRaises(TypeError): # cover error path
Expand Down Expand Up @@ -925,6 +965,7 @@ def test_cohort(self, mock_subprocess):
"refresh",
"curl",
'--channel="latest/beta"',
'--classic',
'--cohort="+"',
],
text=True,
Expand Down Expand Up @@ -1011,7 +1052,7 @@ def test_snap_get(self):
An invalid key will raise an error if typed=False, but return None if typed=True.
"""

def fake_snap(command: str, optargs: Optional[Iterable[str]] = None) -> str:
def fake_snap(command: str, optargs: Iterable[str] | None) -> str:
"""Snap._snap would normally call subprocess.check_output(["snap", ...], ...).

Here we only handle the "get" commands generated by Snap.get:
Expand Down Expand Up @@ -1040,7 +1081,7 @@ def fake_snap(command: str, optargs: Optional[Iterable[str]] = None) -> str:

foo = snap.Snap("foo", snap.SnapState.Latest, "stable", "1", "classic")
foo._snap = MagicMock(side_effect=fake_snap)
keys_and_values: Dict[str, Any] = {
keys_and_values: dict[str, Any] = {
"key_w_string_value": "string",
"key_w_float_value": 4.2,
"key_w_int_value": 13,
Expand Down