|
23 | 23 | import time
|
24 | 24 | import uuid
|
25 | 25 |
|
| 26 | +from contextlib import contextmanager |
26 | 27 | from itertools import product
|
27 | 28 |
|
28 | 29 | sys.path[0:0] = ['']
|
@@ -535,26 +536,65 @@ def test_resume_on_error(self):
|
535 | 536 | self.kill_change_stream_cursor(change_stream)
|
536 | 537 | self.insert_one_and_check(change_stream, {'_id': 2})
|
537 | 538 |
|
| 539 | + # Prose test no. 4 |
| 540 | + @client_context.require_failCommand_fail_point |
| 541 | + def test_no_resume_attempt_if_aggregate_command_fails(self): |
| 542 | + # Set non-retryable error on aggregate command. |
| 543 | + fail_point = {'mode': {'times': 1}, |
| 544 | + 'data': {'errorCode': 2, 'failCommands': ['aggregate']}} |
| 545 | + client, listener = self._client_with_listener("aggregate", "getMore") |
| 546 | + with self.fail_point(fail_point): |
| 547 | + try: |
| 548 | + _ = self.change_stream_with_client(client) |
| 549 | + except OperationFailure: |
| 550 | + pass |
| 551 | + |
| 552 | + # Driver should have attempted aggregate command only once. |
| 553 | + self.assertEqual(len(listener.results['started']), 1) |
| 554 | + self.assertEqual(listener.results['started'][0].command_name, |
| 555 | + 'aggregate') |
| 556 | + |
538 | 557 | # Prose test no. 5
|
539 | 558 | def test_does_not_resume_fatal_errors(self):
|
540 | 559 | """ChangeStream will not attempt to resume fatal server errors."""
|
541 |
| - for code in _NON_RESUMABLE_GETMORE_ERRORS: |
542 |
| - with self.change_stream() as change_stream: |
543 |
| - self.watched_collection().insert_one({}) |
544 |
| - |
| 560 | + if client_context.supports_failCommand_fail_point: |
| 561 | + # failCommand does not support returning no errorCode. |
| 562 | + TEST_ERROR_CODES = _NON_RESUMABLE_GETMORE_ERRORS - {None} |
| 563 | + @contextmanager |
| 564 | + def generate_error(change_stream, code): |
| 565 | + fail_point = {'mode': {'times': 1}, 'data': { |
| 566 | + 'errorCode': code, 'failCommands': ['getMore']}} |
| 567 | + with self.fail_point(fail_point): |
| 568 | + yield |
| 569 | + else: |
| 570 | + TEST_ERROR_CODES = _NON_RESUMABLE_GETMORE_ERRORS |
| 571 | + @contextmanager |
| 572 | + def generate_error(change_stream, code): |
545 | 573 | def mock_try_next(*args, **kwargs):
|
546 | 574 | change_stream._cursor.close()
|
547 | 575 | raise OperationFailure('Mock server error', code=code)
|
548 | 576 |
|
549 | 577 | original_try_next = change_stream._cursor._try_next
|
550 | 578 | change_stream._cursor._try_next = mock_try_next
|
| 579 | + try: |
| 580 | + yield |
| 581 | + finally: |
| 582 | + change_stream._cursor._try_next = original_try_next |
551 | 583 |
|
552 |
| - with self.assertRaises(OperationFailure): |
553 |
| - next(change_stream) |
554 |
| - change_stream._cursor._try_next = original_try_next |
| 584 | + for code in TEST_ERROR_CODES: |
| 585 | + with self.change_stream() as change_stream: |
| 586 | + self.watched_collection().insert_one({}) |
| 587 | + with generate_error(change_stream, code): |
| 588 | + with self.assertRaises(OperationFailure): |
| 589 | + next(change_stream) |
555 | 590 | with self.assertRaises(StopIteration):
|
556 | 591 | next(change_stream)
|
557 | 592 |
|
| 593 | + # Prose test no. 6 - SKIPPED |
| 594 | + # readPreference is not configurable using the watch() helpers so we can |
| 595 | + # skip this test. Also, PyMongo performs server selection for each |
| 596 | + # operation which ensure compliance with this prose test. |
| 597 | + |
558 | 598 | # Prose test no. 7
|
559 | 599 | def test_initial_empty_batch(self):
|
560 | 600 | with self.change_stream() as change_stream:
|
@@ -603,6 +643,9 @@ def test_start_at_operation_time_caching(self):
|
603 | 643 | "startAtOperationTime"), optime, str([k.command for k in
|
604 | 644 | listener.results['started']]))
|
605 | 645 |
|
| 646 | + # Prose test no. 10 - SKIPPED |
| 647 | + # This test is identical to prose test no. 3. |
| 648 | + |
606 | 649 | # Prose test no. 11
|
607 | 650 | @client_context.require_version_min(4, 0, 7)
|
608 | 651 | def test_resumetoken_empty_batch(self):
|
|
0 commit comments