Skip to content

Commit

Permalink
Merge pull request #18 from soda480/0.1.1
Browse files Browse the repository at this point in the history
Refactor on_state_change invocation
  • Loading branch information
soda480 authored Nov 13, 2020
2 parents fd00cb5 + c23123f commit 8d0a3ec
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 15 deletions.
2 changes: 1 addition & 1 deletion build.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
]
summary = 'A framework that exposes a simple set of APIs enabling multi-process integration with the curses screen painting library'
url = 'https://github.com/soda480/mpcurses'
version = '0.1.0'
version = '0.1.1'
default_task = [
'clean',
'analyze',
Expand Down
41 changes: 33 additions & 8 deletions src/main/python/mpcurses/mpcurses.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,41 @@ class NoActiveProcesses(Exception):
pass


class OnDict(dict):
""" subclass dict to execute method when items are added or removed changes
"""
def __init__(self, on_change=None):
""" override constructor
"""
if on_change is None:
raise ValueError('on_change method must be specified')
super(OnDict, self).__init__()
self.on_change = on_change

def __setitem__(self, *args):
""" override setitem
"""
super(OnDict, self).__setitem__(*args)
self.on_change(False)

def __delitem__(self, *args):
""" override delitem
"""
super(OnDict, self).__delitem__(*args)
self.on_change(True)

def pop(self, *args):
""" override pop
"""
value = super(OnDict, self).pop(*args)
if value is not None:
self.on_change(True)
return value


class MPcurses():
""" mpcurses process pool
"""

def __init__(self, function, *, process_data=None, shared_data=None, processes_to_start=None, screen_layout=None, init_messages=None, setup_process_queue=True):
""" MPCstate constructor
"""
Expand All @@ -61,7 +92,7 @@ def __init__(self, function, *, process_data=None, shared_data=None, processes_t
processes_to_start = len(process_data)
self.processes_to_start = processes_to_start

self.active_processes = {}
self.active_processes = OnDict(on_change=self.on_state_change)

self.process_data_offset = [(self.process_data.index(item), item) for item in self.process_data]

Expand Down Expand Up @@ -123,9 +154,6 @@ def start_next_process(self):
# update active_processes dictionary with process meta-data for the process offset
self.active_processes[str(offset)] = process

# consider better way of implementing on_state_change
self.on_state_change(process_completed=False)

def terminate_processes(self):
""" terminate all active processes
"""
Expand All @@ -147,9 +175,6 @@ def remove_active_process(self, offset):
process_id = process.pid if process else '-'
logger.info(f'process at offset {offset} process id {process_id} has completed')

# consider better way of implementing on_state_change
self.on_state_change(process_completed=True)

def update_result(self):
""" update process data with result
"""
Expand Down
57 changes: 51 additions & 6 deletions src/unittest/python/test_mpcurses.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from mpcurses.mpcurses import MPcurses
from mpcurses.mpcurses import NoActiveProcesses
from mpcurses.mpcurses import OnDict

import sys
import logging
Expand Down Expand Up @@ -83,9 +84,8 @@ def test__start_processes_Should_CallStartNextProcess_When_ProcessesToStartGreat
client.start_processes()
self.assertEqual(len(start_next_process_patch.mock_calls), 0)

@patch('mpcurses.MPcurses.on_state_change')
@patch('mpcurses.mpcurses.Process')
def test__start_next_process_Should_CallExpected_When_Called(self, process_patch, on_state_change_patch, *patches):
def test__start_next_process_Should_CallExpected_When_Called(self, process_patch, *patches):
process_mock = Mock()
process_patch.return_value = process_mock

Expand All @@ -102,7 +102,6 @@ def test__start_next_process_Should_CallExpected_When_Called(self, process_patch
'offset': 0,
'result_queue': client.result_queue
})
on_state_change_patch.assert_called_once_with(process_completed=False)

def test__terminate_processes_Should_CallExpected_When_Called(self, *patches):
function_mock = Mock()
Expand All @@ -124,16 +123,14 @@ def test__purge_process_queue_Should_PurgeProcessQueue_When_Called(self, *patche
self.assertTrue(client.process_queue.empty())

@patch('mpcurses.mpcurses.logger')
@patch('mpcurses.MPcurses.on_state_change')
def test__remove_active_process_Should_CallExpected_When_Called(self, on_state_change_patch, logger_patch, *patches):
def test__remove_active_process_Should_CallExpected_When_Called(self, logger_patch, *patches):
function_mock = Mock()
process_data = [{'range': '0-1'}, {'range': '2-3'}, {'range': '4-5'}]
client = MPcurses(function=function_mock, process_data=process_data)
process_mock = Mock(pid=121372)
client.active_processes['0'] = process_mock
client.remove_active_process('0')
logger_patch.info.assert_called_once_with('process at offset 0 process id 121372 has completed')
on_state_change_patch.assert_called_once_with(process_completed=True)

def test__update_result_Should_CallExpected_When_Called(self, *patches):
result_queue_mock = Mock()
Expand Down Expand Up @@ -416,3 +413,51 @@ def test__execute_Should_CallExpected_When_NoScreenLayout(self, run_patch, updat
client.execute()
run_patch.assert_called_once_with()
update_result_patch.assert_called_once_with()


class TestOnDict(unittest.TestCase):

def setUp(self):
"""
"""
pass

def tearDown(self):
"""
"""
pass

def test__init_Should_SetOnChange_When_Called(self, *patches):
on_change_mock = Mock()
onDict = OnDict(on_change=on_change_mock)
self.assertEqual(onDict.on_change, on_change_mock)

def test__init_Should_RaiseValueError_When_OnChangeNotSpecified(self, *patches):
with self.assertRaises(ValueError):
OnDict()

def test__setitem_Should_CallOnChange_When_Called(self, *patches):
on_change_mock = Mock()
onDict = OnDict(on_change=on_change_mock)
onDict['key1'] = 'value1'
on_change_mock.assert_called_once_with(False)

def test__deltitem_Should_CallOnChange_When_Called(self, *patches):
on_change_mock = Mock()
onDict = OnDict(on_change=on_change_mock)
onDict['key1'] = 'value1'
del onDict['key1']
self.assertTrue(call(True) in on_change_mock.mock_calls)

def test__pop_Should_CallOnChange_When_Called(self, *patches):
on_change_mock = Mock()
onDict = OnDict(on_change=on_change_mock)
onDict['key1'] = 'value1'
onDict.pop('key1', None)
self.assertTrue(call(True) in on_change_mock.mock_calls)

def test__pop_Should_NotCallOnChange_When_NoValue(self, *patches):
on_change_mock = Mock()
onDict = OnDict(on_change=on_change_mock)
onDict.pop('key1', None)
on_change_mock.assert_not_called()

0 comments on commit 8d0a3ec

Please sign in to comment.