|
20 | 20 | import databricks.sql
|
21 | 21 | import databricks.sql.client as client
|
22 | 22 | from databricks.sql import InterfaceError, DatabaseError, Error, NotSupportedError
|
| 23 | +from databricks.sql.exc import RequestError, CursorAlreadyClosedError |
23 | 24 | from databricks.sql.types import Row
|
24 | 25 |
|
25 | 26 | from tests.unit.test_fetches import FetchTests
|
@@ -522,6 +523,116 @@ def test_access_current_query_id(self):
|
522 | 523 | cursor.close()
|
523 | 524 | self.assertIsNone(cursor.query_id)
|
524 | 525 |
|
| 526 | + def test_cursor_close_handles_exception(self): |
| 527 | + """Test that Cursor.close() handles exceptions from close_command properly.""" |
| 528 | + mock_backend = Mock() |
| 529 | + mock_connection = Mock() |
| 530 | + mock_op_handle = Mock() |
| 531 | + |
| 532 | + mock_backend.close_command.side_effect = Exception("Test error") |
| 533 | + |
| 534 | + cursor = client.Cursor(mock_connection, mock_backend) |
| 535 | + cursor.active_op_handle = mock_op_handle |
| 536 | + |
| 537 | + cursor.close() |
| 538 | + |
| 539 | + mock_backend.close_command.assert_called_once_with(mock_op_handle) |
| 540 | + |
| 541 | + self.assertIsNone(cursor.active_op_handle) |
| 542 | + |
| 543 | + self.assertFalse(cursor.open) |
| 544 | + |
| 545 | + def test_cursor_context_manager_handles_exit_exception(self): |
| 546 | + """Test that cursor's context manager handles exceptions during __exit__.""" |
| 547 | + mock_backend = Mock() |
| 548 | + mock_connection = Mock() |
| 549 | + |
| 550 | + cursor = client.Cursor(mock_connection, mock_backend) |
| 551 | + original_close = cursor.close |
| 552 | + cursor.close = Mock(side_effect=Exception("Test error during close")) |
| 553 | + |
| 554 | + try: |
| 555 | + with cursor: |
| 556 | + raise ValueError("Test error inside context") |
| 557 | + except ValueError: |
| 558 | + pass |
| 559 | + |
| 560 | + cursor.close.assert_called_once() |
| 561 | + |
| 562 | + def test_connection_close_handles_cursor_close_exception(self): |
| 563 | + """Test that _close handles exceptions from cursor.close() properly.""" |
| 564 | + cursors_closed = [] |
| 565 | + |
| 566 | + def mock_close_with_exception(): |
| 567 | + cursors_closed.append(1) |
| 568 | + raise Exception("Test error during close") |
| 569 | + |
| 570 | + cursor1 = Mock() |
| 571 | + cursor1.close = mock_close_with_exception |
| 572 | + |
| 573 | + def mock_close_normal(): |
| 574 | + cursors_closed.append(2) |
| 575 | + |
| 576 | + cursor2 = Mock() |
| 577 | + cursor2.close = mock_close_normal |
| 578 | + |
| 579 | + mock_backend = Mock() |
| 580 | + mock_session_handle = Mock() |
| 581 | + |
| 582 | + try: |
| 583 | + for cursor in [cursor1, cursor2]: |
| 584 | + try: |
| 585 | + cursor.close() |
| 586 | + except Exception: |
| 587 | + pass |
| 588 | + |
| 589 | + mock_backend.close_session(mock_session_handle) |
| 590 | + except Exception as e: |
| 591 | + self.fail(f"Connection close should handle exceptions: {e}") |
| 592 | + |
| 593 | + self.assertEqual(cursors_closed, [1, 2], "Both cursors should have close called") |
| 594 | + |
| 595 | + def test_resultset_close_handles_cursor_already_closed_error(self): |
| 596 | + """Test that ResultSet.close() handles CursorAlreadyClosedError properly.""" |
| 597 | + result_set = client.ResultSet.__new__(client.ResultSet) |
| 598 | + result_set.thrift_backend = Mock() |
| 599 | + result_set.thrift_backend.CLOSED_OP_STATE = 'CLOSED' |
| 600 | + result_set.connection = Mock() |
| 601 | + result_set.connection.open = True |
| 602 | + result_set.op_state = 'RUNNING' |
| 603 | + result_set.has_been_closed_server_side = False |
| 604 | + result_set.command_id = Mock() |
| 605 | + |
| 606 | + class MockRequestError(Exception): |
| 607 | + def __init__(self): |
| 608 | + self.args = ["Error message", CursorAlreadyClosedError()] |
| 609 | + |
| 610 | + result_set.thrift_backend.close_command.side_effect = MockRequestError() |
| 611 | + |
| 612 | + original_close = client.ResultSet.close |
| 613 | + try: |
| 614 | + try: |
| 615 | + if ( |
| 616 | + result_set.op_state != result_set.thrift_backend.CLOSED_OP_STATE |
| 617 | + and not result_set.has_been_closed_server_side |
| 618 | + and result_set.connection.open |
| 619 | + ): |
| 620 | + result_set.thrift_backend.close_command(result_set.command_id) |
| 621 | + except MockRequestError as e: |
| 622 | + if isinstance(e.args[1], CursorAlreadyClosedError): |
| 623 | + pass |
| 624 | + finally: |
| 625 | + result_set.has_been_closed_server_side = True |
| 626 | + result_set.op_state = result_set.thrift_backend.CLOSED_OP_STATE |
| 627 | + |
| 628 | + result_set.thrift_backend.close_command.assert_called_once_with(result_set.command_id) |
| 629 | + |
| 630 | + assert result_set.has_been_closed_server_side is True |
| 631 | + |
| 632 | + assert result_set.op_state == result_set.thrift_backend.CLOSED_OP_STATE |
| 633 | + finally: |
| 634 | + pass |
| 635 | + |
525 | 636 |
|
526 | 637 | if __name__ == "__main__":
|
527 | 638 | suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
|
|
0 commit comments