More tests
This commit is contained in:
parent
a3bc96efa5
commit
6c109d1875
|
@ -176,10 +176,7 @@ class Bundle:
|
|||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
raise TypeError(
|
||||
f"== not supported between instances of '{self.__class__.__name__}'"
|
||||
f" and '{other.__class__.__name__}"
|
||||
)
|
||||
return False
|
||||
if self.csv_operator_name != other.csv_operator_name:
|
||||
return False
|
||||
try:
|
||||
|
@ -204,7 +201,6 @@ class Bundle:
|
|||
f" and '{other.__class__.__name__}"
|
||||
)
|
||||
if self.csv_operator_name != other.csv_operator_name:
|
||||
# raise ValueError("Can't compare bundles from different operators")
|
||||
return self.csv_operator_name < other.csv_operator_name
|
||||
try:
|
||||
return Version.parse(self.csv_operator_version.lstrip("v")) < Version.parse(
|
||||
|
@ -434,21 +430,15 @@ class Operator:
|
|||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
raise NotImplementedError(
|
||||
f"Can't compare {self.__class__.__name__} to {other.__class__.__name__}"
|
||||
)
|
||||
return False
|
||||
return self.operator_name == other.operator_name
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
raise NotImplementedError(
|
||||
f"Can't compare {self.__class__.__name__} to {other.__class__.__name__}"
|
||||
)
|
||||
return self.operator_name != other.operator_name
|
||||
return not self == other
|
||||
|
||||
def __lt__(self, other: object) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
raise NotImplementedError(
|
||||
raise TypeError(
|
||||
f"Can't compare {self.__class__.__name__} to {other.__class__.__name__}"
|
||||
)
|
||||
return self.operator_name < other.operator_name
|
||||
|
@ -551,9 +541,7 @@ class Repo:
|
|||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
raise NotImplementedError(
|
||||
f"Can't compare {self.__class__.__name__} to {other.__class__.__name__}"
|
||||
)
|
||||
return False
|
||||
return self._repo_path == other._repo_path
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
|
@ -9,7 +9,7 @@ import sys
|
|||
from collections.abc import Iterator
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
from .checks import get_checks, run_suite
|
||||
from .classes import Bundle, Operator, Repo
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import yaml
|
||||
|
||||
|
@ -153,3 +153,19 @@ def bundle_files(
|
|||
},
|
||||
other_files or {},
|
||||
)
|
||||
|
||||
|
||||
def make_nested_dict(items: dict[str, Any]) -> dict:
|
||||
"""
|
||||
_make_nested_dict({"foo.bar": "baz"}) -> {"foo": {"bar": "baz"}}
|
||||
"""
|
||||
result = {}
|
||||
for path, value in items.items():
|
||||
current = result
|
||||
keys = path.split(".")
|
||||
for key in keys[:-1]:
|
||||
if key not in current:
|
||||
current[key] = {}
|
||||
current = current[key]
|
||||
current[keys[-1]] = value
|
||||
return result
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
import re
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
|
||||
from operator_repo import Repo
|
||||
from operator_repo.checks.bundle import check_image, check_operator_name, check_semver
|
||||
from tests import bundle_files, create_files, make_nested_dict
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bundle, extra_files, expected_results",
|
||||
[
|
||||
(
|
||||
bundle_files("hello", "0.0.1"),
|
||||
{},
|
||||
set(),
|
||||
),
|
||||
(
|
||||
bundle_files(
|
||||
"hello",
|
||||
"0.0.1",
|
||||
annotations={"operators.operatorframework.io.bundle.package.v1": "foo"},
|
||||
),
|
||||
{},
|
||||
{
|
||||
"Operator name from annotations.yaml (foo) does not match the operator's directory name (hello)",
|
||||
"Operator name from annotations.yaml (foo) does not match the name defined in the CSV (hello)",
|
||||
},
|
||||
),
|
||||
(
|
||||
bundle_files("hello", "0.0.1"),
|
||||
{"operators/hello/0.0.1/metadata/annotations.yaml": {"annotations": {}}},
|
||||
{
|
||||
"Bundle does not define the operator name in annotations.yaml",
|
||||
},
|
||||
),
|
||||
],
|
||||
False,
|
||||
["Names ok", "Wrong annotations.yaml", "Empty annotations.yaml"],
|
||||
)
|
||||
def test_operator_name(
|
||||
tmp_path: Path, bundle: dict, extra_files: dict, expected_results: set[str]
|
||||
) -> None:
|
||||
create_files(tmp_path, bundle, extra_files)
|
||||
repo = Repo(tmp_path)
|
||||
operator = next(repo.all_operators())
|
||||
bundle = next(operator.all_bundles())
|
||||
assert {x.reason for x in check_operator_name(bundle)} == expected_results
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bundle, extra_files, expected_results",
|
||||
[
|
||||
(
|
||||
bundle_files("hello", "0.0.1"),
|
||||
{},
|
||||
{"CSV doesn't define .metadata.annotations.containerImage"},
|
||||
),
|
||||
(
|
||||
bundle_files(
|
||||
"hello",
|
||||
"0.0.1",
|
||||
csv=make_nested_dict(
|
||||
{
|
||||
"metadata.annotations.containerImage": "example.com/namespace/image:tag",
|
||||
}
|
||||
),
|
||||
),
|
||||
{},
|
||||
{"CSV doesn't define .spec.install.spec.deployments"},
|
||||
),
|
||||
(
|
||||
bundle_files(
|
||||
"hello",
|
||||
"0.0.1",
|
||||
csv=make_nested_dict(
|
||||
{
|
||||
"metadata.annotations.containerImage": "example.com/namespace/image:tag",
|
||||
"spec.install.spec.deployments": [
|
||||
make_nested_dict(
|
||||
{
|
||||
"spec.template.spec.containers": [
|
||||
{"image": "example.com/namespace/image:tag"}
|
||||
]
|
||||
}
|
||||
),
|
||||
],
|
||||
}
|
||||
),
|
||||
),
|
||||
{},
|
||||
set(),
|
||||
),
|
||||
(
|
||||
bundle_files(
|
||||
"hello",
|
||||
"0.0.1",
|
||||
csv=make_nested_dict(
|
||||
{
|
||||
"metadata.annotations.containerImage": "example.com/namespace/image:tag",
|
||||
"spec.install.spec.deployments": [
|
||||
make_nested_dict(
|
||||
{
|
||||
"spec.template.spec.containers": [
|
||||
{
|
||||
"image": "example.com/namespace/image:othertag"
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
],
|
||||
}
|
||||
),
|
||||
),
|
||||
{},
|
||||
{
|
||||
"container image example.com/namespace/image:tag not used by any deployment"
|
||||
},
|
||||
),
|
||||
(
|
||||
bundle_files("hello", "0.0.1"),
|
||||
{"operators/hello/0.0.1/manifests/hello.clusterserviceversion.yaml": ""},
|
||||
re.compile("Invalid CSV contents "),
|
||||
),
|
||||
],
|
||||
False,
|
||||
[
|
||||
"Missing containerImage",
|
||||
"Missing deployments",
|
||||
"Matching images",
|
||||
"Mismatched images",
|
||||
"Empty CSV",
|
||||
],
|
||||
)
|
||||
def test_image(
|
||||
tmp_path: Path,
|
||||
bundle: dict,
|
||||
extra_files: dict,
|
||||
expected_results: Union[set[str], re.Pattern],
|
||||
) -> None:
|
||||
create_files(tmp_path, bundle, extra_files)
|
||||
repo = Repo(tmp_path)
|
||||
operator = next(repo.all_operators())
|
||||
bundle = next(operator.all_bundles())
|
||||
reasons = {x.reason for x in check_image(bundle)}
|
||||
if isinstance(expected_results, re.Pattern):
|
||||
assert len(reasons) == 1
|
||||
reason = reasons.pop()
|
||||
assert expected_results.match(reason)
|
||||
else:
|
||||
assert reasons == expected_results
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bundle, extra_files, expected_results",
|
||||
[
|
||||
(
|
||||
bundle_files("hello", "0.0.1"),
|
||||
{},
|
||||
set(),
|
||||
),
|
||||
(
|
||||
bundle_files("hello", "latest"),
|
||||
{},
|
||||
{
|
||||
"Version from CSV (latest) is not valid semver",
|
||||
"Version from filesystem (latest) is not valid semver",
|
||||
},
|
||||
),
|
||||
],
|
||||
False,
|
||||
["All versions ok", "Both versions invalid"],
|
||||
)
|
||||
def test_semver(
|
||||
tmp_path: Path, bundle: dict, extra_files: dict, expected_results: set[str]
|
||||
) -> None:
|
||||
create_files(tmp_path, bundle, extra_files)
|
||||
repo = Repo(tmp_path)
|
||||
operator = next(repo.all_operators())
|
||||
bundle = next(operator.all_bundles())
|
||||
assert {x.reason for x in check_semver(bundle)} == expected_results
|
|
@ -0,0 +1,75 @@
|
|||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from operator_repo import Bundle, Repo
|
||||
from operator_repo.checks import Fail, Warn, get_checks, run_check, run_suite
|
||||
from tests import bundle_files, create_files
|
||||
|
||||
|
||||
def test_check_result() -> None:
|
||||
result1 = Warn("foo")
|
||||
result2 = Fail("bar")
|
||||
result3 = Warn("foo")
|
||||
assert result1 != result2
|
||||
assert result1 < result2
|
||||
assert result1 == result3
|
||||
assert "foo" in str(result1)
|
||||
assert "bar" in str(result2)
|
||||
assert "foo" in repr(result1)
|
||||
assert "bar" in repr(result2)
|
||||
assert "warning" in str(result1)
|
||||
assert "failure" in str(result2)
|
||||
assert {result1, result2, result3} == {result1, result2}
|
||||
|
||||
|
||||
@patch("importlib.import_module")
|
||||
def test_get_checks(mock_import_module: MagicMock) -> None:
|
||||
fake_module = MagicMock()
|
||||
|
||||
def check_fake(x):
|
||||
pass
|
||||
|
||||
fake_module.check_fake = check_fake
|
||||
fake_module.non_check_bar = lambda x: None
|
||||
mock_import_module.return_value = fake_module
|
||||
assert get_checks("suite.name") == {
|
||||
"operator": [check_fake],
|
||||
"bundle": [check_fake],
|
||||
}
|
||||
mock_import_module.assert_has_calls(
|
||||
[call("suite.name.operator"), call("suite.name.bundle")], any_order=True
|
||||
)
|
||||
|
||||
|
||||
@patch("importlib.import_module")
|
||||
def test_get_checks_missing_modules(mock_import_module: MagicMock) -> None:
|
||||
mock_import_module.side_effect = ModuleNotFoundError()
|
||||
assert get_checks("suite.name") == {
|
||||
"operator": [],
|
||||
"bundle": [],
|
||||
}
|
||||
mock_import_module.assert_has_calls(
|
||||
[call("suite.name.operator"), call("suite.name.bundle")], any_order=True
|
||||
)
|
||||
|
||||
|
||||
def test_run_check(mock_bundle: Bundle) -> None:
|
||||
def check_fake(x):
|
||||
yield Warn("foo")
|
||||
|
||||
assert list(run_check(check_fake, mock_bundle)) == [
|
||||
Warn("foo", "check_fake", mock_bundle)
|
||||
]
|
||||
|
||||
|
||||
@patch("operator_repo.checks.get_checks")
|
||||
def test_run_suite(mock_get_checks: MagicMock, mock_bundle: Bundle) -> None:
|
||||
def check_fake(x):
|
||||
yield Warn("foo")
|
||||
|
||||
mock_get_checks.return_value = {"bundle": [check_fake], "operator": []}
|
||||
assert list(run_suite([mock_bundle], "fake.suite")) == [
|
||||
Warn("foo", "check_fake", mock_bundle)
|
||||
]
|
|
@ -0,0 +1,59 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from operator_repo import Repo
|
||||
from operator_repo.checks.operator import check_upgrade
|
||||
from tests import bundle_files, create_files
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bundles, operator_name, expected_results",
|
||||
[
|
||||
(
|
||||
[bundle_files("hello", "0.0.1")],
|
||||
"hello",
|
||||
set(),
|
||||
),
|
||||
(
|
||||
[
|
||||
bundle_files("hello", "0.0.1"),
|
||||
bundle_files("hello", "0.0.2"),
|
||||
],
|
||||
"hello",
|
||||
{"Channel beta has dangling bundles: {Bundle(hello/0.0.1)}"},
|
||||
),
|
||||
(
|
||||
[
|
||||
bundle_files("hello", "0.0.1"),
|
||||
bundle_files(
|
||||
"hello", "0.0.2", csv={"spec": {"replaces": "hello.v0.0.1"}}
|
||||
),
|
||||
],
|
||||
"hello",
|
||||
set(),
|
||||
),
|
||||
(
|
||||
[
|
||||
bundle_files("hello", "0.0.1"),
|
||||
bundle_files("hello", "0.0.2", csv={"spec": {"replaces": "rubbish"}}),
|
||||
],
|
||||
"hello",
|
||||
{"Bundle(hello/0.0.2) has invalid 'replaces' field: 'rubbish'"},
|
||||
),
|
||||
],
|
||||
False,
|
||||
[
|
||||
"Single bundle",
|
||||
"Two bundles, no replaces",
|
||||
"Two bundles",
|
||||
"Two bundles, invalid replaces",
|
||||
],
|
||||
)
|
||||
def test_upgrade(
|
||||
tmp_path: Path, bundles: list[dict], operator_name: str, expected_results: set[str]
|
||||
) -> None:
|
||||
create_files(tmp_path, *bundles)
|
||||
repo = Repo(tmp_path)
|
||||
operator = repo.operator(operator_name)
|
||||
assert {x.reason for x in check_upgrade(operator)} == expected_results
|
|
@ -0,0 +1,13 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from operator_repo import Bundle, Repo
|
||||
from tests import bundle_files, create_files
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bundle(tmp_path: Path) -> Bundle:
|
||||
create_files(tmp_path, bundle_files("hello", "0.0.1"))
|
||||
repo = Repo(tmp_path)
|
||||
return repo.operator("hello").bundle("0.0.1")
|
|
@ -2,7 +2,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from operator_repo import Bundle, Repo
|
||||
from operator_repo import Bundle, Operator, Repo
|
||||
from operator_repo.exceptions import InvalidBundleException, InvalidOperatorException
|
||||
from tests import bundle_files, create_files
|
||||
|
||||
|
@ -23,6 +23,9 @@ def test_bundle(tmp_path: Path) -> None:
|
|||
== "hello"
|
||||
)
|
||||
assert bundle.dependencies == []
|
||||
assert bundle != "foo"
|
||||
with pytest.raises(TypeError):
|
||||
_ = bundle < "foo"
|
||||
|
||||
|
||||
def test_bundle_compare(tmp_path: Path) -> None:
|
||||
|
@ -118,3 +121,8 @@ def test_bundle_invalid(tmp_path: Path) -> None:
|
|||
assert list(repo.operator("one_empty_bundle")) == [
|
||||
repo.operator("one_empty_bundle").bundle("0.0.1")
|
||||
]
|
||||
|
||||
|
||||
def test_bundle_caching(mock_bundle: Bundle) -> None:
|
||||
assert Bundle(mock_bundle.root).operator == mock_bundle.operator
|
||||
assert Bundle(mock_bundle.root).operator is not mock_bundle.operator
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from operator_repo import Operator, Repo
|
||||
from tests import bundle_files, create_files
|
||||
|
||||
|
@ -25,6 +27,9 @@ def test_operator_one_bundle(tmp_path: Path) -> None:
|
|||
assert bundle.dependencies == []
|
||||
assert operator.root == repo.root / "operators" / "hello"
|
||||
assert "hello" in repr(operator)
|
||||
assert operator != "foo"
|
||||
with pytest.raises(TypeError):
|
||||
_ = operator < "foo"
|
||||
|
||||
|
||||
def test_channels(tmp_path: Path) -> None:
|
||||
|
|
|
@ -39,3 +39,6 @@ def test_repo_one_bundle(tmp_path: Path) -> None:
|
|||
assert len(list(repo)) == 1
|
||||
assert set(repo) == {repo.operator("hello")}
|
||||
assert repo.has("hello")
|
||||
assert repo != "foo"
|
||||
with pytest.raises(TypeError):
|
||||
_ = repo < "foo"
|
||||
|
|
Loading…
Reference in New Issue