| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| """ |
| Direct tests of the SIGINT handler. |
| |
| Higher-level testing of SIGINT during queries appears to be flaky in CI due to |
| having to send the signal, so this tests the handler itself instead. |
| """ |
| |
| import os |
| import signal |
| import threading |
| import time |
| |
| import pytest |
| |
| from adbc_driver_manager import _lib |
| |
| # https://github.com/apache/arrow-adbc/issues/1522 |
| # It works fine on the normal Windows builds, but not under the Conda builds |
| # where there is an unexplained/unreplicable crash, and so for now this is |
| # disabled on Windows |
| pytestmark = pytest.mark.skipif(os.name == "nt", reason="Disabled on Windows") |
| |
| |
| def _send_sigint(): |
| # Windows behavior is different |
| # https://stackoverflow.com/questions/35772001 |
| if os.name == "nt": |
| os.kill(os.getpid(), signal.CTRL_C_EVENT) |
| else: |
| os.kill(os.getpid(), signal.SIGINT) |
| |
| |
| def _blocking(event): |
| _send_sigint() |
| event.wait() |
| |
| |
| def test_sigint_fires(): |
| # Run the thing that fires SIGINT itself as the "blocking" call |
| event = threading.Event() |
| |
| def _cancel(): |
| event.set() |
| |
| _lib._blocking_call(_blocking, (event,), {}, _cancel) |
| |
| |
| def test_handler_restored(): |
| event = threading.Event() |
| _lib._blocking_call(_blocking, (event,), {}, event.set) |
| |
| # After it returns, this should raise KeyboardInterrupt like usual |
| with pytest.raises(KeyboardInterrupt): |
| _blocking(event) |
| # Needed on Windows so the handler runs before we exit the block (we |
| # won't sleep for the full time) |
| time.sleep(60) |
| |
| |
| def test_args_return(): |
| def _blocking(a, *, b): |
| return a, b |
| |
| assert _lib._blocking_call( |
| _blocking, |
| (1,), |
| {"b": 2}, |
| lambda: None, |
| ) == (1, 2) |
| |
| |
| def test_blocking_raise(): |
| def _blocking(): |
| raise ValueError("expected error") |
| |
| with pytest.raises(ValueError, match="expected error"): |
| _lib._blocking_call(_blocking, (), {}, lambda: None) |
| |
| |
| def test_cancel_raise(): |
| event = threading.Event() |
| |
| def _cancel(): |
| event.set() |
| raise ValueError("expected error") |
| |
| with pytest.raises(ValueError, match="expected error"): |
| _lib._blocking_call(_blocking, (event,), {}, _cancel) |
| |
| |
| def test_both_raise(): |
| event = threading.Event() |
| |
| def _blocking(event): |
| _send_sigint() |
| event.wait() |
| raise ValueError("expected error 1") |
| |
| def _cancel(): |
| event.set() |
| raise ValueError("expected error 2") |
| |
| with pytest.raises(ValueError, match="expected error 1") as excinfo: |
| _lib._blocking_call(_blocking, (event,), {}, _cancel) |
| assert excinfo.value.__cause__ is not None |
| with pytest.raises(ValueError, match="expected error 2"): |
| raise excinfo.value.__cause__ |
| |
| |
| def test_nested(): |
| # To be clear, don't ever do this. |
| event = threading.Event() |
| |
| def _wrap_blocking(): |
| _lib._blocking_call(_blocking, (event,), {}, event.set) |
| |
| _lib._blocking_call(_wrap_blocking, (), {}, lambda: None) |
| |
| # The original handler should be restored |
| with pytest.raises(KeyboardInterrupt): |
| _send_sigint() |
| # Needed on Windows so the handler runs before we exit the block (we |
| # won't sleep for the full time) |
| time.sleep(60) |