|
7 | 7 | import contextlib |
8 | 8 | import gc |
9 | 9 | import io |
| 10 | +import logging |
10 | 11 | import re |
11 | 12 | import weakref |
12 | 13 | from typing import TYPE_CHECKING, Any, assert_type |
|
22 | 23 | find_or_create_instrument, |
23 | 24 | ) |
24 | 25 | from qcodes.instrument_drivers.mock_instruments import ( |
| 26 | + DummyBase, |
25 | 27 | DummyChannelInstrument, |
26 | 28 | DummyFailingInstrument, |
27 | 29 | DummyInstrument, |
@@ -450,6 +452,101 @@ def test_recreate(request: FixtureRequest) -> None: |
450 | 452 | assert instr not in Instrument._all_instruments.values() |
451 | 453 |
|
452 | 454 |
|
| 455 | +@pytest.mark.usefixtures("close_before_and_after") |
| 456 | +def test_close_all_closes_all_instruments() -> None: |
| 457 | + """``close_all`` closes every registered instrument by default.""" |
| 458 | + dummy = DummyInstrument(name="dummy", gates=["dac1"]) |
| 459 | + parabola = MockParabola("parabola") |
| 460 | + |
| 461 | + assert Instrument.is_valid(dummy) |
| 462 | + assert Instrument.is_valid(parabola) |
| 463 | + |
| 464 | + Instrument.close_all() |
| 465 | + |
| 466 | + assert not Instrument.is_valid(dummy) |
| 467 | + assert not Instrument.is_valid(parabola) |
| 468 | + assert Instrument._all_instruments == WeakValueDictionary() |
| 469 | + |
| 470 | + |
| 471 | +@pytest.mark.usefixtures("close_before_and_after") |
| 472 | +def test_close_all_only_subclasses_from_leaf_class() -> None: |
| 473 | + """``only_subclasses`` on a leaf class leaves sibling classes open.""" |
| 474 | + dummy = DummyInstrument(name="dummy", gates=["dac1"]) |
| 475 | + parabola = MockParabola("parabola") |
| 476 | + |
| 477 | + # DummyInstrument and MockParabola are siblings (both subclass DummyBase), |
| 478 | + # so closing only DummyInstrument subclasses must leave the parabola open. |
| 479 | + DummyInstrument.close_all(only_subclasses=True) |
| 480 | + |
| 481 | + assert not Instrument.is_valid(dummy) |
| 482 | + assert Instrument.is_valid(parabola) |
| 483 | + |
| 484 | + # The remaining instrument can still be closed with a plain close_all. |
| 485 | + Instrument.close_all() |
| 486 | + assert not Instrument.is_valid(parabola) |
| 487 | + assert Instrument._all_instruments == WeakValueDictionary() |
| 488 | + |
| 489 | + |
| 490 | +@pytest.mark.usefixtures("close_before_and_after") |
| 491 | +def test_close_all_only_subclasses_from_base_class() -> None: |
| 492 | + """``only_subclasses`` closes instances of the class and its subclasses.""" |
| 493 | + dummy = DummyInstrument(name="dummy", gates=["dac1"]) |
| 494 | + parabola = MockParabola("parabola") |
| 495 | + |
| 496 | + # Both DummyInstrument and MockParabola are subclasses of DummyBase, so both |
| 497 | + # are closed when calling close_all on the shared base class. |
| 498 | + DummyBase.close_all(only_subclasses=True) |
| 499 | + |
| 500 | + assert not Instrument.is_valid(dummy) |
| 501 | + assert not Instrument.is_valid(parabola) |
| 502 | + assert Instrument._all_instruments == WeakValueDictionary() |
| 503 | + |
| 504 | + |
| 505 | +@pytest.mark.usefixtures("close_before_and_after") |
| 506 | +def test_close_all_only_subclasses_false_closes_everything() -> None: |
| 507 | + """``only_subclasses=False`` closes all instruments regardless of class.""" |
| 508 | + dummy = DummyInstrument(name="dummy", gates=["dac1"]) |
| 509 | + parabola = MockParabola("parabola") |
| 510 | + |
| 511 | + DummyInstrument.close_all(only_subclasses=False) |
| 512 | + |
| 513 | + assert not Instrument.is_valid(dummy) |
| 514 | + assert not Instrument.is_valid(parabola) |
| 515 | + assert Instrument._all_instruments == WeakValueDictionary() |
| 516 | + |
| 517 | + |
| 518 | +@pytest.mark.usefixtures("close_before_and_after") |
| 519 | +def test_close_all_log_status(caplog: pytest.LogCaptureFixture) -> None: |
| 520 | + """``log_status=True`` logs the closing of each instrument.""" |
| 521 | + dummy = DummyInstrument(name="dummy", gates=["dac1"]) |
| 522 | + |
| 523 | + with caplog.at_level(logging.INFO, logger="qcodes.instrument.instrument"): |
| 524 | + Instrument.close_all(log_status=True) |
| 525 | + |
| 526 | + assert "Closing all registered instruments" in caplog.text |
| 527 | + assert "Closing dummy" in caplog.text |
| 528 | + assert not Instrument.is_valid(dummy) |
| 529 | + |
| 530 | + |
| 531 | +@pytest.mark.usefixtures("close_before_and_after") |
| 532 | +def test_close_all_no_log_by_default(caplog: pytest.LogCaptureFixture) -> None: |
| 533 | + """``close_all`` does not log anything when ``log_status`` is not set.""" |
| 534 | + dummy = DummyInstrument(name="dummy", gates=["dac1"]) |
| 535 | + |
| 536 | + with caplog.at_level(logging.INFO, logger="qcodes.instrument.instrument"): |
| 537 | + Instrument.close_all() |
| 538 | + |
| 539 | + assert "Closing all registered instruments" not in caplog.text |
| 540 | + assert "Closing dummy" not in caplog.text |
| 541 | + assert not Instrument.is_valid(dummy) |
| 542 | + |
| 543 | + |
| 544 | +def test_close_all_only_accepts_keyword_arguments() -> None: |
| 545 | + """The ``close_all`` options are keyword-only.""" |
| 546 | + with pytest.raises(TypeError): |
| 547 | + Instrument.close_all(True) # type: ignore[misc] |
| 548 | + |
| 549 | + |
453 | 550 | def test_instrument_metadata(request: FixtureRequest) -> None: |
454 | 551 | metadatadict = {1: "data", "some": "data"} |
455 | 552 | instrument = DummyInstrument( |
|
0 commit comments