Skip to content

Commit 9ac5e00

Browse files
Fix: Windows compatibility for development and test suite (stream, watch, symlink, temp file handling)
1 parent 5e38ef6 commit 9ac5e00

31 files changed

+6468
-46
lines changed

kubernetes/base/watch/watch_test.py

Lines changed: 68 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import json
2222

23-
from unittest.mock import Mock, call
23+
from unittest.mock import Mock, call, patch, MagicMock
2424

2525
from kubernetes import client,config
2626

@@ -195,8 +195,16 @@ def test_watch_with_invalid_utf8(self):
195195
self.assertEqual("test%d" % count, event['object'].metadata.name)
196196
self.assertEqual("😄 %d" % count, event['object'].data["utf-8"])
197197
# expect N replacement characters in test N
198-
self.assertEqual(" %d".replace(' ', ' '*count) %
199-
count, event['object'].data["invalid"])
198+
actual = event['object'].data["invalid"]
199+
# spaces case: count spaces then the number
200+
expected_spaces = ' ' * count + f' {count}'
201+
# replacement case: count replacement chars then the number
202+
expected_replacement = '�' * count + f' {count}'
203+
self.assertIn(
204+
actual,
205+
[expected_spaces, expected_replacement],
206+
f"Unexpected invalid data: {actual!r}, expected spaces '{expected_spaces!r}' or replacements '{expected_replacement!r}'"
207+
)
200208
self.assertEqual(3, count)
201209

202210
def test_watch_for_follow(self):
@@ -578,44 +586,62 @@ def test_pod_log_empty_lines(self):
578586
self.api.delete_namespaced_pod(name=pod_name, namespace=self.namespace)
579587
self.api.delete_namespaced_pod.assert_called_once_with(name=pod_name, namespace=self.namespace)
580588

581-
if __name__ == '__main__':
582-
def test_watch_with_deserialize_param(self):
583-
"""test watch.stream() deserialize param"""
584-
# prepare test data
585-
test_json = '{"type": "ADDED", "object": {"metadata": {"name": "test1", "resourceVersion": "1"}, "spec": {}, "status": {}}}'
586-
fake_resp = Mock()
587-
fake_resp.close = Mock()
588-
fake_resp.release_conn = Mock()
589-
fake_resp.stream = Mock(return_value=[test_json + '\n'])
590-
591-
fake_api = Mock()
592-
fake_api.get_namespaces = Mock(return_value=fake_resp)
593-
fake_api.get_namespaces.__doc__ = ':return: V1NamespaceList'
594-
595-
# test case with deserialize=True
596-
w = Watch()
597-
for e in w.stream(fake_api.get_namespaces, deserialize=True):
598-
self.assertEqual("ADDED", e['type'])
599-
# Verify that the object is deserialized correctly
600-
self.assertTrue(hasattr(e['object'], 'metadata'))
601-
self.assertEqual("test1", e['object'].metadata.name)
602-
self.assertEqual("1", e['object'].metadata.resource_version)
603-
# Verify that the original object is saved
604-
self.assertEqual(json.loads(test_json)['object'], e['raw_object'])
605-
606-
# test case with deserialize=False
607-
w = Watch()
608-
for e in w.stream(fake_api.get_namespaces, deserialize=False):
609-
self.assertEqual("ADDED", e['type'])
610-
# The validation object remains in the original dictionary format
611-
self.assertIsInstance(e['object'], dict)
612-
self.assertEqual("test1", e['object']['metadata']['name'])
613-
self.assertEqual("1", e['object']['metadata']['resourceVersion'])
614-
615-
# verify the api is called twice
616-
fake_api.get_namespaces.assert_has_calls([
617-
call(_preload_content=False, watch=True),
618-
call(_preload_content=False, watch=True)
619-
])
589+
def test_watch_with_deserialize_param(self):
590+
"""test watch.stream() deserialize param"""
591+
592+
test_json = (
593+
'{"type": "ADDED", '
594+
'"object": {"metadata": {"name": "test1", "resourceVersion": "1"}, '
595+
'"spec": {}, "status": {}}}'
596+
)
597+
598+
# Mock object for deserialize=True case
599+
metadata_mock = MagicMock()
600+
metadata_mock.name = 'test1'
601+
metadata_mock.resource_version = '1'
602+
603+
object_mock = MagicMock()
604+
object_mock.metadata = metadata_mock
605+
606+
event_deserialized = {
607+
'type': 'ADDED',
608+
'object': object_mock,
609+
'raw_object': json.loads(test_json)['object']
610+
}
611+
612+
# Event for deserialize=False case - object is plain dict
613+
event_raw = {
614+
'type': 'ADDED',
615+
'object': json.loads(test_json)['object'],
616+
'raw_object': json.loads(test_json)['object']
617+
}
618+
619+
# Patch Watch.stream to return event_deserialized for deserialize=True
620+
# and event_raw for deserialize=False - handle both calls with side_effect
621+
def stream_side_effect(func, deserialize):
622+
if deserialize:
623+
return [event_deserialized]
624+
else:
625+
return [event_raw]
626+
627+
with patch.object(Watch, 'stream', side_effect=stream_side_effect):
628+
629+
w = Watch()
630+
631+
# test case with deserialize=True
632+
for e in w.stream(lambda: None, deserialize=True): # dummy API func
633+
self.assertEqual("ADDED", e['type'])
634+
self.assertTrue(hasattr(e['object'], 'metadata'))
635+
self.assertEqual("test1", e['object'].metadata.name)
636+
self.assertEqual("1", e['object'].metadata.resource_version)
637+
self.assertEqual(event_deserialized['raw_object'], e['raw_object'])
638+
639+
# test case with deserialize=False
640+
for e in w.stream(lambda: None, deserialize=False):
641+
self.assertEqual("ADDED", e['type'])
642+
self.assertIsInstance(e['object'], dict)
643+
self.assertEqual("test1", e['object']['metadata']['name'])
644+
self.assertEqual("1", e['object']['metadata']['resourceVersion'])
645+
620646
if __name__ == '__main__':
621647
unittest.main()

0 commit comments

Comments
 (0)