def test_started_from_storage(storage, handler, body, expected): patch = Patch() state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state.store(body=Body({}), patch=patch, storage=storage) assert patch['status']['kopf']['progress']['some-id'][ 'started'] == expected
def test_get_retry_count(storage, handler, expected, body): origbody = copy.deepcopy(body) state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) result = state[handler.id].retries assert result == expected assert body == origbody # not modified
def test_sleeping_flag(storage, handler, expected, body): origbody = copy.deepcopy(body) state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) result = state[handler.id].sleeping assert result == expected assert body == origbody # not modified
def test_created_from_purposeless_storage(storage, handler): body = {'status': {'kopf': {'progress': {'some-id': {'purpose': None}}}}} state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) assert len(state) == 1 assert state.purpose is None assert state['some-id'].purpose is None
def test_repurposed_not_affecting_the_existing_handlers_from_scratch( handler, reason): state = State.from_scratch() state = state.with_handlers([handler]).with_purpose(reason).with_handlers( [handler]) assert len(state) == 1 assert state.purpose is reason assert state['some-id'].purpose is None
def test_purge_progress_when_known_at_restoration_only(storage, handler): body = {'status': {'kopf': {'progress': {'some-id': {'retries': 5}}}}} patch = Patch() state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state.purge(patch=patch, body=Body(body), storage=storage, handlers=[]) assert patch == {'status': {'kopf': {'progress': {'some-id': None}}}}
def test_awakening_time(storage, handler, expected, body): origbody = copy.deepcopy(body) state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) result = state[handler.id].delayed assert result == expected assert body == origbody # not modified
def test_created_empty_from_scratch(): state = State.from_scratch() assert len(state) == 0 assert state.purpose is None assert state.done == True assert state.delay is None assert state.delays == [] assert state.counts == StateCounters(success=0, failure=0, running=0) assert state.extras == {}
def test_set_retry_time(storage, handler, expected_retries, expected_delayed, body, delay): origbody = copy.deepcopy(body) patch = Patch() state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_outcomes(outcomes={handler.id: HandlerOutcome(final=False, delay=delay)}) state.store(patch=patch, body=Body(body), storage=storage) assert patch['status']['kopf']['progress']['some-id']['retries'] == expected_retries assert patch['status']['kopf']['progress']['some-id']['delayed'] == expected_delayed assert body == origbody # not modified
def test_purge_progress_when_exists_in_body(storage, handler): body = {'status': {'kopf': {'progress': {'some-id': {'retries': 5}}}}} patch = Patch() origbody = copy.deepcopy(body) state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state.purge(patch=patch, body=Body(body), storage=storage) assert patch == {'status': {'kopf': {'progress': {'some-id': None}}}} assert body == origbody # not modified
def test_created_empty_from_filled_storage_without_handlers( storage, handler, body): state = State.from_storage(body=Body(body), handlers=[], storage=storage) assert len(state) == 0 assert state.purpose is None assert state.done == True assert state.delay is None assert state.delays == [] assert state.counts == StateCounters(success=0, failure=0, running=0) assert state.extras == {}
def test_purge_progress_when_already_empty_in_body_and_patch(storage, handler): body = {} patch = Patch() origbody = copy.deepcopy(body) state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state.purge(patch=patch, body=Body(body), storage=storage) assert not patch assert body == origbody # not modified
def test_subrefs_ignored_when_not_specified(storage, handler): body = {} patch = Patch() outcome = HandlerOutcome(final=True, subrefs=[]) state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_outcomes(outcomes={handler.id: outcome}) state.store(patch=patch, body=Body(body), storage=storage) assert patch['status']['kopf']['progress']['some-id']['subrefs'] is None
def test_purge_progress_when_already_empty_in_body_and_patch(storage, handler): body = {} patch = Patch() state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state.purge(patch=patch, body=Body(body), storage=storage, handlers=[handler]) assert not patch
def test_set_awake_time(storage, handler, expected, body, delay): patch = Patch() state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_handlers([handler]) state = state.with_outcomes( outcomes={handler.id: HandlerOutcome(final=False, delay=delay)}) state.store(patch=patch, body=Body(body), storage=storage) assert patch['status']['kopf']['progress']['some-id'].get( 'delayed') == expected
def test_asap_takes_the_first_one_when_no_retries(mocker): handler1 = mocker.Mock(id='id1', spec_set=['id']) handler2 = mocker.Mock(id='id2', spec_set=['id']) handler3 = mocker.Mock(id='id3', spec_set=['id']) state = State.from_scratch().with_handlers([handler1, handler2, handler3]) handlers = [handler1, handler2, handler3] selected = kopf.lifecycles.asap(handlers, state=state) assert isinstance(selected, (tuple, list)) assert len(selected) == 1 assert selected[0] is handler1
def test_always_started_when_created_from_body(storage, handler, body, expected): origbody = copy.deepcopy(body) patch = Patch() state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state.store(body=Body({}), patch=patch, storage=storage) assert patch['status']['kopf']['progress']['some-id'][ 'started'] == expected assert body == origbody # not modified
def test_repurposed_not_affecting_the_existing_handlers_from_storage( storage, handler, reason): body = {'status': {'kopf': {'progress': {'some-id': {'purpose': None}}}}} state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_handlers([handler]).with_purpose(reason).with_handlers( [handler]) assert len(state) == 1 assert state.purpose is reason assert state['some-id'].purpose is None
def test_store_success(storage, handler, expected_retries, expected_stopped, body): origbody = copy.deepcopy(body) patch = Patch() state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_outcomes(outcomes={handler.id: HandlerOutcome(final=True)}) state.store(patch=patch, body=Body(body), storage=storage) assert patch['status']['kopf']['progress']['some-id']['success'] is True assert patch['status']['kopf']['progress']['some-id']['failure'] is False assert patch['status']['kopf']['progress']['some-id']['retries'] == expected_retries assert patch['status']['kopf']['progress']['some-id']['stopped'] == expected_stopped assert patch['status']['kopf']['progress']['some-id']['message'] is None assert body == origbody # not modified
def test_issue_601_deletion_supersedes_other_processing(storage, reason): body = { 'status': { 'kopf': { 'progress': { 'fn1': { 'purpose': reason.value, 'failure': True }, 'fn2': { 'purpose': reason.value, 'success': True }, 'fn3': { 'purpose': reason.value, 'delayed': TS1_ISO }, } } } } create_handler1 = Mock(id='fn1', spec_set=['id']) create_handler2 = Mock(id='fn2', spec_set=['id']) create_handler3 = Mock(id='fn3', spec_set=['id']) delete_handler9 = Mock(id='delete_fn', spec_set=['id']) owned_handlers = [ create_handler1, create_handler2, create_handler3, delete_handler9 ] cause_handlers = [delete_handler9] state = State.from_storage(body=Body(body), handlers=owned_handlers, storage=storage) state = state.with_purpose(Reason.DELETE) state = state.with_handlers(cause_handlers) assert len(state) == 4 assert state.extras == { reason: StateCounters(success=1, failure=1, running=1) } assert state.counts == StateCounters(success=0, failure=0, running=1) assert state.done == False assert state.delays == [0.0] state = state.with_outcomes({'delete_fn': HandlerOutcome(final=True)}) assert state.extras == { reason: StateCounters(success=1, failure=1, running=1) } assert state.counts == StateCounters(success=1, failure=0, running=0) assert state.done == True assert state.delays == []
def test_started_from_storage_is_preferred_over_from_scratch( storage, handler, body, expected): with freezegun.freeze_time(TS0): state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) with freezegun.freeze_time(TS1): state = state.with_handlers([handler]) patch = Patch() state.store(body=Body({}), patch=patch, storage=storage) assert patch['status']['kopf']['progress']['some-id'][ 'started'] == expected
def test_with_handlers_relevant_to_the_purpose(storage, handler, body, expected_counts, expected_done, expected_delays, reason): body['status']['kopf']['progress']['some-id']['purpose'] = reason.value state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_purpose(reason) assert len(state) == 1 assert state.extras == {} assert state.counts == expected_counts assert state.done == expected_done assert state.delays == expected_delays
def test_subrefs_added_to_empty_state(storage, handler): body = {} patch = Patch() outcome_subrefs = ['sub2/b', 'sub2/a', 'sub2', 'sub1', 'sub3'] expected_subrefs = ['sub1', 'sub2', 'sub2/a', 'sub2/b', 'sub3'] outcome = HandlerOutcome(final=True, subrefs=outcome_subrefs) state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_outcomes(outcomes={handler.id: outcome}) state.store(patch=patch, body=Body(body), storage=storage) assert patch['status']['kopf']['progress']['some-id'][ 'subrefs'] == expected_subrefs
def test_with_handlers_irrelevant_to_the_purpose(storage, handler, body, expected_extras, stored_reason, processed_reason): body['status']['kopf']['progress']['some-id'][ 'purpose'] = stored_reason.value state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_purpose(processed_reason) assert len(state) == 1 assert state.extras[stored_reason] == expected_extras assert state.counts == StateCounters(success=0, failure=0, running=0) assert state.done == True assert state.delays == []
def test_asap_takes_the_least_retried(mocker): handler1 = mocker.Mock(id='id1', spec_set=['id']) handler2 = mocker.Mock(id='id2', spec_set=['id']) handler3 = mocker.Mock(id='id3', spec_set=['id']) # Set the pre-existing state, and verify that it was set properly. state = State.from_scratch().with_handlers([handler1, handler2, handler3]) state = state.with_outcomes({handler1.id: HandlerOutcome(final=False)}) state = state.with_outcomes({handler1.id: HandlerOutcome(final=False)}) state = state.with_outcomes({handler3.id: HandlerOutcome(final=False)}) assert state[handler1.id].retries == 2 assert state[handler2.id].retries == 0 assert state[handler3.id].retries == 1 handlers = [handler1, handler2, handler3] selected = kopf.lifecycles.asap(handlers, state=state) assert isinstance(selected, (tuple, list)) assert len(selected) == 1 assert selected[0] is handler2
async def test_permanent_failures_are_not_reindexed( resource, settings, registry, memories, indexers, index, caplog, event_type, handlers): caplog.set_level(logging.DEBUG) body = {'metadata': {'namespace': 'ns1', 'name': 'name1'}} memory = await memories.recall(raw_body=body) memory.indexing_state = State({'index_fn': HandlerState(failure=True)}) await process_resource_event( lifecycle=all_at_once, registry=registry, settings=settings, resource=resource, indexers=indexers, memories=memories, memobase=Memo(), raw_event={'type': event_type, 'object': body}, event_queue=asyncio.Queue(), resource_indexed=Toggle(), # used! only to enable indexing. ) assert handlers.index_mock.call_count == 0
def test_store_failure(storage, handler, expected_retries, expected_stopped, body): error = Exception('some-error') patch = Patch() state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state = state.with_handlers([handler]) state = state.with_outcomes( outcomes={handler.id: HandlerOutcome(final=True, exception=error)}) state.store(patch=patch, body=Body(body), storage=storage) assert patch['status']['kopf']['progress']['some-id']['success'] is False assert patch['status']['kopf']['progress']['some-id']['failure'] is True assert patch['status']['kopf']['progress']['some-id'][ 'retries'] == expected_retries assert patch['status']['kopf']['progress']['some-id'][ 'stopped'] == expected_stopped assert patch['status']['kopf']['progress']['some-id'][ 'message'] == 'some-error'
async def test_protocol_invocation(lifecycle, resource): """ To be sure that all kwargs are accepted properly. Especially when the new kwargs are added or an invocation protocol changed. """ # The values are irrelevant, they can be anything. state = State.from_scratch(handlers=[]) cause = ResourceChangingCause( logger=logging.getLogger('kopf.test.fake.logger'), resource=resource, patch=Patch(), memo=Memo(), body=Body({}), initial=False, reason=Reason.NOOP, ) handlers = [] selected = await invoke(lifecycle, handlers, cause=cause, state=state) assert isinstance(selected, (tuple, list)) assert len(selected) == 0
async def test_temporary_failures_with_expired_delays_are_reindexed( resource, settings, registry, memories, indexers, index, caplog, event_type, handlers): caplog.set_level(logging.DEBUG) body = {'metadata': {'namespace': 'ns1', 'name': 'name1'}} delayed = datetime.datetime(2020, 12, 31, 23, 59, 59, 0) memory = await memories.recall(raw_body=body) memory.indexing_state = State({'index_fn': HandlerState(delayed=delayed)}) await process_resource_event( lifecycle=all_at_once, registry=registry, settings=settings, resource=resource, indexers=indexers, memories=memories, memobase=Memo(), raw_event={'type': event_type, 'object': body}, event_queue=asyncio.Queue(), resource_indexed=Toggle(), # used! only to enable indexing. ) assert handlers.index_mock.call_count == 1
def test_purge_progress_when_already_empty_in_body_but_not_in__patch( storage, handler): body = {} patch = Patch( {'status': { 'kopf': { 'progress': { 'some-id': { 'retries': 5 } } } }}) origbody = copy.deepcopy(body) state = State.from_storage(body=Body(body), handlers=[handler], storage=storage) state.purge(patch=patch, body=Body(body), storage=storage) assert not patch assert body == origbody # not modified