Skip to content

Commit bf55bdb

Browse files
committed
add job ts tests
1 parent 762af03 commit bf55bdb

File tree

8 files changed

+391
-36
lines changed

8 files changed

+391
-36
lines changed

src/biokbase/narrative/jobs/job.py

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import biokbase.narrative.clients as clients
1111
from biokbase.narrative.app_util import map_inputs_from_job, map_outputs_from_state
1212
from biokbase.narrative.exception_util import transform_job_exception
13+
from biokbase.narrative.jobs.util import merge, merge_inplace
1314

1415
from .specmanager import SpecManager
1516

@@ -82,33 +83,6 @@
8283
STATE_ATTRS = list(set(JOB_ATTRS) - set(JOB_INPUT_ATTRS) - set(NARR_CELL_INFO_ATTRS))
8384

8485

85-
def merge(d0: dict, d1: dict):
86-
d0 = copy.deepcopy(d0)
87-
merge_inplace(d0, d1)
88-
return d0
89-
90-
91-
def merge_inplace(d0: dict, d1: dict):
92-
"""
93-
Recursively merge nested dicts d1 into d0,
94-
overwriting any values in d0 that are not nested dicts.
95-
Mutates d0
96-
"""
97-
for k, v1 in d1.items():
98-
if k in d0:
99-
v0 = d0[k]
100-
is_dict_0 = isinstance(v0, dict)
101-
is_dict_1 = isinstance(v1, dict)
102-
if is_dict_0 ^ is_dict_1:
103-
raise ValueError(f"For key {k}: is_dict(v0) xor is_dict(v1)")
104-
elif not is_dict_0 and not is_dict_1:
105-
d0[k] = v1
106-
elif is_dict_0 and is_dict_1:
107-
merge_inplace(v0, v1)
108-
else:
109-
d0[k] = v0
110-
111-
11286
class Job:
11387
_job_logs = []
11488
_acc_state = None # accumulates state
@@ -561,7 +535,7 @@ def log(self, first_line=0, num_lines=None):
561535
return (num_available_lines, [])
562536
return (
563537
num_available_lines,
564-
self._job_logs[first_line : first_line + num_lines],
538+
self._job_logs[first_line: first_line + num_lines],
565539
)
566540

567541
def _update_log(self):

src/biokbase/narrative/jobs/jobcomm.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -355,11 +355,6 @@ def _get_job_states(self, job_id_list: list, ts: int = None) -> dict:
355355
:rtype: dict
356356
"""
357357
output_states = self._jm.get_job_states(job_id_list, ts)
358-
359-
now = time.time_ns()
360-
for output_state in output_states.values():
361-
output_state["last_checked"] = now
362-
363358
self.send_comm_message(MESSAGE_TYPE["STATUS"], output_states)
364359
return output_states
365360

@@ -520,6 +515,14 @@ def send_comm_message(self, msg_type: str, content: dict) -> None:
520515
Sends a ipykernel.Comm message to the KBaseJobs channel with the given msg_type
521516
and content. These just get encoded into the message itself.
522517
"""
518+
# For STATUS responses, add a last_checked field
519+
# to each output_state. Note: error states will have
520+
# the last_checked field too
521+
if msg_type == MESSAGE_TYPE["STATUS"]:
522+
now = time.time_ns()
523+
for output_state in content.values():
524+
output_state["last_checked"] = now
525+
523526
msg = {"msg_type": msg_type, "content": content}
524527
self._comm.send(msg)
525528

src/biokbase/narrative/jobs/util.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import json
23
import os
34

@@ -58,3 +59,30 @@ def load_job_constants(relative_path_to_file=JOB_CONFIG_FILE_PATH_PARTS):
5859
)
5960

6061
return (config["params"], config["message_types"])
62+
63+
64+
def merge(d0: dict, d1: dict):
65+
d0 = copy.deepcopy(d0)
66+
merge_inplace(d0, d1)
67+
return d0
68+
69+
70+
def merge_inplace(d0: dict, d1: dict):
71+
"""
72+
Recursively merge nested dicts d1 into d0,
73+
overwriting any values in d0 that are not nested dicts.
74+
Mutates d0
75+
"""
76+
for k, v1 in d1.items():
77+
if k in d0:
78+
v0 = d0[k]
79+
is_dict_0 = isinstance(v0, dict)
80+
is_dict_1 = isinstance(v1, dict)
81+
if is_dict_0 ^ is_dict_1:
82+
raise ValueError(f"For key {k}: is_dict(v0) xor is_dict(v1)")
83+
elif not is_dict_0 and not is_dict_1:
84+
d0[k] = v1
85+
elif is_dict_0 and is_dict_1:
86+
merge_inplace(v0, v1)
87+
else:
88+
d0[k] = v1

src/biokbase/narrative/tests/job_test_constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def get_test_jobs(job_ids):
4141

4242

4343
CLIENTS = "biokbase.narrative.clients.get"
44-
TIME_NS = "biokbase.narrative.jobs.jobcomm.time.time_ns"
44+
JC_TIME_NS = "biokbase.narrative.jobs.jobcomm.time.time_ns"
4545

4646
TEST_EPOCH_NS = 42 # arbitrary epoch ns
4747
MAX_LOG_LINES = 10

src/biokbase/narrative/tests/test_job.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def mock_state(self, state=None):
374374
"updated": 0,
375375
}
376376

377-
with mock.patch.object(Job, "state", mock_state):
377+
with mock.patch.object(Job, "refresh_state", mock_state):
378378
state = job.output_state()
379379
self.assertEqual(expected, state)
380380

src/biokbase/narrative/tests/test_job_util.py

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import copy
2+
import re
13
import unittest
24

3-
from biokbase.narrative.jobs.util import load_job_constants
5+
from biokbase.narrative.jobs.util import load_job_constants, merge, merge_inplace
46

57

68
class JobUtilTestCase(unittest.TestCase):
@@ -57,5 +59,187 @@ def test_load_job_constants__valid(self):
5759
self.assertIn(item, message_types)
5860

5961

62+
class MergeTest(unittest.TestCase):
63+
def _check_merge_inplace(self, d0: dict, d1: dict, exp_merge: dict):
64+
d1_copy = copy.deepcopy(d1)
65+
merge_inplace(d0, d1)
66+
self.assertEqual(
67+
d0,
68+
exp_merge
69+
)
70+
self.assertEqual(
71+
d1,
72+
d1_copy
73+
)
74+
75+
def test_merge_inplace__empty(self):
76+
d0 = {}
77+
d1 = {}
78+
self._check_merge_inplace(
79+
d0,
80+
d1,
81+
{}
82+
)
83+
84+
def test_merge_inplace__d0_empty(self):
85+
# flat
86+
d0 = {}
87+
d1 = {"level00": "l00"}
88+
self._check_merge_inplace(
89+
d0,
90+
d1,
91+
{"level00": "l00"}
92+
)
93+
94+
# nested
95+
d0 = {}
96+
d1 = {
97+
"level00": "l00",
98+
"level01": {
99+
"level10": "l10"
100+
}
101+
}
102+
self._check_merge_inplace(
103+
d0,
104+
d1,
105+
{
106+
"level00": "l00",
107+
"level01": {
108+
"level10": "l10"
109+
}
110+
}
111+
)
112+
113+
def test_merge_inplace__d1_empty(self):
114+
# flat
115+
d0 = {"level00": "l00"}
116+
d1 = {}
117+
self._check_merge_inplace(
118+
d0,
119+
d1,
120+
{"level00": "l00"}
121+
)
122+
123+
# nested
124+
d0 = {
125+
"level00": "l00",
126+
"level01": {
127+
"level10": "l10"
128+
}
129+
}
130+
d1 = {}
131+
self._check_merge_inplace(
132+
d0,
133+
d1,
134+
{
135+
"level00": "l00",
136+
"level01": {
137+
"level10": "l10"
138+
}
139+
}
140+
)
141+
142+
def test_merge_inplace__flat(self):
143+
d0 = {
144+
"level00": "l00",
145+
"level01": "l01"
146+
}
147+
d1 = {
148+
"level01": "l01_",
149+
"level02": "l02"
150+
}
151+
self._check_merge_inplace(
152+
d0,
153+
d1,
154+
{
155+
"level00": "l00",
156+
"level01": "l01_",
157+
"level02": "l02"
158+
}
159+
)
160+
161+
def test_merge_inplace__nested(self):
162+
d0 = {
163+
"level00": {
164+
"level10": {
165+
"level20": "l20",
166+
"level21": "l21"
167+
}
168+
},
169+
"level01": "l01"
170+
}
171+
d1 = {
172+
"level00": {
173+
"level10": {
174+
"level22": "l22"
175+
}
176+
},
177+
"level01": "l01_"
178+
}
179+
self._check_merge_inplace(
180+
d0,
181+
d1,
182+
{
183+
"level00": {
184+
"level10": {
185+
"level20": "l20",
186+
"level21": "l21",
187+
"level22": "l22"
188+
}
189+
},
190+
"level01": "l01_"
191+
}
192+
)
193+
194+
def test_merge_inplace__xor_dicts(self):
195+
d0 = {
196+
"level00": {}
197+
}
198+
d1 = {
199+
"level00": "l00",
200+
"level01": "l01"
201+
}
202+
with self.assertRaisesRegex(
203+
ValueError,
204+
re.escape("For key level00: is_dict(v0) xor is_dict(v1)")
205+
):
206+
merge_inplace(d0, d1)
207+
208+
def test_merge(self):
209+
d0 = {
210+
"level00": "l00",
211+
"level01": {
212+
"level10": {
213+
"level20": "l20"
214+
}
215+
},
216+
"level02": "l02"
217+
}
218+
d1 = {
219+
"level01": {
220+
"level10": {
221+
"level20": "l20_"
222+
}
223+
}
224+
}
225+
d0_copy = copy.deepcopy(d0)
226+
d1_copy = copy.deepcopy(d1)
227+
d0_merge = merge(d0, d1)
228+
self.assertEqual(d0, d0_copy)
229+
self.assertEqual(d1, d1_copy)
230+
self.assertEqual(
231+
d0_merge,
232+
{
233+
"level00": "l00",
234+
"level01": {
235+
"level10": {
236+
"level20": "l20_"
237+
}
238+
},
239+
"level02": "l02"
240+
}
241+
)
242+
243+
60244
if __name__ == "__main__":
61245
unittest.main()

0 commit comments

Comments
 (0)