Skip to content

Commit 4df6e06

Browse files
Implemented unittests for Job and its components
1 parent 85f05ab commit 4df6e06

File tree

13 files changed

+279
-16
lines changed

13 files changed

+279
-16
lines changed

src/ja/common/docker_context.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ def __init__(self, source_path: str, mount_path: str):
2424
self._source_path = source_path
2525
self._mount_path = mount_path
2626

27+
def __eq__(self, other: object) -> bool:
28+
if isinstance(other, MountPoint):
29+
return self.source_path == other.source_path and self.mount_path == other.mount_path
30+
else:
31+
return False
32+
2733
@property
2834
def source_path(self) -> str:
2935
"""!
@@ -55,6 +61,12 @@ class IDockerContext(Serializable, ABC):
5561
run a job in.
5662
"""
5763

64+
def __eq__(self, other: object) -> bool:
65+
if isinstance(other, IDockerContext):
66+
return self.dockerfile_source == other.dockerfile_source and self.mount_points == other.mount_points
67+
else:
68+
return False
69+
5870
@property
5971
@abstractmethod
6072
def dockerfile_source(self) -> str:
@@ -103,7 +115,7 @@ def to_dict(self) -> Dict[str, object]:
103115
return _dict
104116

105117
@classmethod
106-
def from_dict(cls, property_dict: Dict[str, object]) -> "IDockerContext":
118+
def from_dict(cls, property_dict: Dict[str, object]) -> IDockerContext:
107119
_dockerfile_source = cls._get_str_from_dict(property_dict=property_dict, key="dockerfile_source")
108120

109121
_property: object = cls._get_from_dict(property_dict=property_dict, key="mount_points")
@@ -120,9 +132,9 @@ def from_dict(cls, property_dict: Dict[str, object]) -> "IDockerContext":
120132
key="mount_points", expected_type="List[MountPoint]",
121133
actual_type="List[object]"
122134
)
123-
_mount_point_list.append(MountPoint.from_dict(
124-
cast(Dict[str, object], _object)
125-
))
135+
_mount_point_list.append(MountPoint.from_dict(
136+
cast(Dict[str, object], _object)
137+
))
126138
cls._assert_all_properties_used(property_dict)
127139
return DockerContext(dockerfile_source=_dockerfile_source, mount_points=_mount_point_list)
128140

@@ -134,15 +146,22 @@ class DockerConstraints(Serializable):
134146
def __init__(self, cpu_threads: int = -1, memory: int = 1):
135147
"""!
136148
Create a new set of Docker constraints.
137-
@param cpu_threads Initial value of @cpu_threads.
138-
@param memory The value of @memory.
149+
@param cpu_threads Initial value of @cpu_threads. -1 if unknown. Must be > 0 if set to exact number.
150+
@param memory The value of @memory, must be > 0.
139151
"""
140152
self._cpu_threads = -1
141-
self.cpu_threads = cpu_threads
153+
if cpu_threads != -1:
154+
self.cpu_threads = cpu_threads
142155
if memory < 1:
143156
raise ValueError("Cannot set memory to %s because this value is < 1." % memory)
144157
self._memory = memory
145158

159+
def __eq__(self, other: object) -> bool:
160+
if isinstance(other, DockerConstraints):
161+
return self.cpu_threads == other.cpu_threads and self.memory == other.memory
162+
else:
163+
return False
164+
146165
@property
147166
def cpu_threads(self) -> int:
148167
"""!
@@ -161,9 +180,10 @@ def cpu_threads(self, count_threads: int) -> None:
161180
"""
162181
if self._cpu_threads != -1:
163182
raise RuntimeError("cpu_threads can only be set once.")
164-
if count_threads < 1:
183+
elif count_threads < 1:
165184
raise ValueError("Cannot set cpu_threads to %s because this value is < 1." % count_threads)
166-
self._cpu_threads = count_threads
185+
else:
186+
self._cpu_threads = count_threads
167187

168188
@property
169189
def memory(self) -> int:

src/ja/common/job.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ def __init__(self,
4343
self._is_preemptible = is_preemtible
4444
self._special_resources = special_resources
4545

46+
def __eq__(self, other: object) -> bool:
47+
if isinstance(other, JobSchedulingConstraints):
48+
return self.priority == other.priority \
49+
and self.is_preemptible == other.is_preemptible \
50+
and self.special_resources == other.special_resources
51+
else:
52+
return False
53+
4654
@property
4755
def priority(self) -> JobPriority:
4856
"""!
@@ -74,7 +82,7 @@ def to_dict(self) -> Dict[str, object]:
7482
@classmethod
7583
def from_dict(cls, property_dict: Dict[str, object]) -> "JobSchedulingConstraints":
7684
_priority = JobPriority(cls._get_from_dict(property_dict=property_dict, key="priority"))
77-
_is_preemtible = cls._get_bool_from_dict(property_dict=property_dict, key="is_preemtible")
85+
_is_preemtible = cls._get_bool_from_dict(property_dict=property_dict, key="is_preemptible")
7886
_special_resources = cls._get_str_list_from_dict(property_dict=property_dict, key="special_resources")
7987

8088
cls._assert_all_properties_used(property_dict)
@@ -108,6 +116,19 @@ def __init__(self,
108116
self._docker_constraints = docker_constraints
109117
self._label = label
110118

119+
def __eq__(self, other: object) -> bool:
120+
if isinstance(other, Job):
121+
return self.uid == other.uid \
122+
and self.status == other.status \
123+
and self.owner_id == other.owner_id \
124+
and self.email == other.email \
125+
and self.scheduling_constraints == other.scheduling_constraints \
126+
and self.docker_context == other.docker_context \
127+
and self.docker_constraints == other.docker_constraints \
128+
and self.label == other.label
129+
else:
130+
return False
131+
111132
@property
112133
def uid(self) -> str:
113134
"""!
@@ -207,7 +228,7 @@ def to_dict(self) -> Dict[str, object]:
207228
@classmethod
208229
def from_dict(cls, property_dict: Dict[str, object]) -> "Job":
209230
_uid = cls._get_str_from_dict(property_dict=property_dict, key="uid", mandatory=False)
210-
_owner_id = cls._get_int_from_dict(property_dict=property_dict, key="uid")
231+
_owner_id = cls._get_int_from_dict(property_dict=property_dict, key="owner_id")
211232
_email = cls._get_str_from_dict(property_dict=property_dict, key="email")
212233

213234
_scheduling_constraints = JobSchedulingConstraints.from_dict(

src/ja/common/message/base.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,21 @@ def _assert_all_properties_used(cls, property_dict: Dict[str, object]) -> None:
4444
def _get_dict_from_dict(
4545
cls, property_dict: Dict[str, object], key: str, mandatory: bool = True) -> Dict[str, object]:
4646
_property = cls._get_from_dict(property_dict=property_dict, key=key, mandatory=mandatory)
47-
if not isinstance(_property, dict):
47+
if mandatory and not isinstance(_property, dict):
4848
cls._raise_error_wrong_type(key=key, expected_type="dict", actual_type=_property.__class__.__name__)
4949
return cast(Dict[str, object], _property)
5050

5151
@classmethod
5252
def _get_str_from_dict(cls, property_dict: Dict[str, object], key: str, mandatory: bool = True) -> str:
5353
_property = cls._get_from_dict(property_dict=property_dict, key=key, mandatory=mandatory)
54-
if not isinstance(_property, str):
54+
if mandatory and not isinstance(_property, str):
5555
cls._raise_error_wrong_type(key=key, expected_type="str", actual_type=_property.__class__.__name__)
5656
return cast(str, _property)
5757

5858
@classmethod
5959
def _get_str_list_from_dict(cls, property_dict: Dict[str, object], key: str, mandatory: bool = True) -> List[str]:
6060
_property = cls._get_from_dict(property_dict=property_dict, key=key, mandatory=mandatory)
61-
if not isinstance(_property, list):
61+
if mandatory and not isinstance(_property, list):
6262
cls._raise_error_wrong_type(key=key, expected_type="List[str]", actual_type=_property.__class__.__name__)
6363
_list = cast(List[object], _property)
6464
for _element in _list:
@@ -69,14 +69,14 @@ def _get_str_list_from_dict(cls, property_dict: Dict[str, object], key: str, man
6969
@classmethod
7070
def _get_int_from_dict(cls, property_dict: Dict[str, object], key: str, mandatory: bool = True) -> int:
7171
_property = cls._get_from_dict(property_dict=property_dict, key=key, mandatory=mandatory)
72-
if not isinstance(_property, int):
72+
if mandatory and not isinstance(_property, int):
7373
cls._raise_error_wrong_type(key=key, expected_type="int", actual_type=_property.__class__.__name__)
7474
return cast(int, _property)
7575

7676
@classmethod
7777
def _get_bool_from_dict(cls, property_dict: Dict[str, object], key: str, mandatory: bool = True) -> bool:
7878
_property = cls._get_from_dict(property_dict=property_dict, key=key, mandatory=mandatory)
79-
if not isinstance(_property, bool):
79+
if mandatory and not isinstance(_property, bool):
8080
cls._raise_error_wrong_type(key=key, expected_type="int", actual_type=_property.__class__.__name__)
8181
return cast(bool, _property)
8282

src/setup.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python3
2+
3+
import unittest
4+
5+
from setuptools import find_packages, setup
6+
7+
8+
def discover_ja_tests():
9+
_tl = unittest.TestLoader()
10+
_ts = _tl.discover('test', 'test*.py')
11+
return _ts
12+
13+
14+
setup(
15+
name='jobadder',
16+
version="0.0.1",
17+
description='JobAdder: a package for priority-based scheduling of Docker jobs to mwork machines',
18+
long_description='',
19+
author='Johannes Gäßler, ', # TODO add everyone else
20+
author_email='johannesg@5d6.de, ', # TODO add everyone else
21+
url='https://github.com/DistributedTaskScheduling/JobAdder',
22+
packages=find_packages(),
23+
package_data={},
24+
scripts=[],
25+
test_suite='setup.discover_ja_tests',
26+
keywords=[],
27+
license='GPL3',
28+
install_requires=[],
29+
classifiers=[],
30+
)

src/test/__init__.py

Whitespace-only changes.

src/test/serializable/__init__.py

Whitespace-only changes.

src/test/serializable/base.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from copy import deepcopy
2+
from typing import no_type_check, Dict, List
3+
4+
from ja.common.message.base import Serializable
5+
6+
7+
class SerializableTestMixin:
8+
"""
9+
Mixin class for testing sub-classes of Serializable.
10+
"""
11+
def __init__(self) -> None:
12+
self._object: Serializable = None
13+
self._object_dict: Dict[str, object] = None
14+
self._other_object_dict: Dict[str, object] = None
15+
self._optional_properties: List[str] = None
16+
17+
@no_type_check
18+
def test_roundtrip(self) -> None:
19+
_dict = self._object.to_dict()
20+
_recreated_object = self._object.__class__.from_dict(_dict)
21+
self.assertTrue(
22+
self._object == _recreated_object,
23+
msg="Roundtrip failed for %s: recreated object not equal." % self._object.__class__.__name__
24+
)
25+
26+
@no_type_check
27+
def test_from_dict(self) -> None:
28+
_read_object = self._object.__class__.from_dict(self._object_dict)
29+
self.assertTrue(
30+
self._object == _read_object,
31+
msg="Reading in object failed for %s: read in object not equal." % self._object.__class__.__name__
32+
)
33+
34+
@no_type_check
35+
def test_from_other_dict(self) -> None:
36+
_read_object = self._object.__class__.from_dict(self._object_dict)
37+
_other_read_object = self._object.__class__.from_dict(self._other_object_dict)
38+
self.assertFalse(
39+
_read_object == _other_read_object,
40+
msg="Reading in different objects failed for %s: read in objects are the same."
41+
% self._object.__class__.__name__
42+
)
43+
44+
@no_type_check
45+
def test_removing_property_raises_error(self) -> None:
46+
for _key in list(self._object_dict.keys()):
47+
_object_dict_copy = deepcopy(self._object_dict)
48+
_object_dict_copy.pop(_key)
49+
if _key in self._optional_properties:
50+
self._object.__class__.from_dict(_object_dict_copy)
51+
else:
52+
with self.assertRaises(ValueError):
53+
self._object.__class__.from_dict(_object_dict_copy)
54+
55+
@no_type_check
56+
def test_adding_property_raises_error(self) -> None:
57+
self._object_dict["AYY"] = "LAMO"
58+
with self.assertRaises(ValueError):
59+
self._object.__class__.from_dict(self._object_dict)

src/test/serializable/job/__init__.py

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from unittest import TestCase
2+
3+
from ja.common.docker_context import DockerConstraints
4+
from test.serializable.base import SerializableTestMixin
5+
6+
7+
class DockerConstraintsTest(TestCase, SerializableTestMixin):
8+
"""
9+
Class for testing DockerConstraints.
10+
"""
11+
def setUp(self) -> None:
12+
self._optional_properties = []
13+
self._object = DockerConstraints(cpu_threads=-1, memory=1024)
14+
self._object_dict = {"cpu_threads": -1, "memory": 1024}
15+
self._other_object_dict = {"cpu_threads": 4, "memory": 1024}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from unittest import TestCase
2+
3+
from ja.common.docker_context import DockerContext, MountPoint
4+
from test.serializable.base import SerializableTestMixin
5+
6+
7+
class DockerContextTest(TestCase, SerializableTestMixin):
8+
"""
9+
Class for testing DockerContext.
10+
"""
11+
def setUp(self) -> None:
12+
self._optional_properties = []
13+
self._object = DockerContext(
14+
dockerfile_source="sudo apt install docker",
15+
mount_points=[
16+
MountPoint(source_path="/home/user", mount_path="/home/user"),
17+
MountPoint(source_path="/opt/thing", mount_path="/opt/THING")
18+
]
19+
)
20+
self._object_dict = {
21+
"dockerfile_source": "sudo apt install docker",
22+
"mount_points": [
23+
{"source_path": "/home/user", "mount_path": "/home/user"},
24+
{"source_path": "/opt/thing", "mount_path": "/opt/THING"}
25+
]
26+
}
27+
self._other_object_dict = {
28+
"dockerfile_source": "sudo apt install docker",
29+
"mount_points": [
30+
{"source_path": "/home/user", "mount_path": "/home/user"},
31+
]
32+
}

0 commit comments

Comments
 (0)