Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
4905: Add types to shared test modules r=jenshnielsen a=jenshnielsen

Mainly to the fixtures and so on with the aim of making the tests easier to understand

4932: Deprecate QDevil QDAC 1 driver r=jenshnielsen a=jenshnielsen

The driver has been moved to qcodes_contrib_drivers (0.18.0)

TODO 

- [x] Changelog
- [x] Add the notebook to qcodes_contrib_drivers QCoDeS/Qcodes_contrib_drivers#198

Co-authored-by: Jens H. Nielsen <[email protected]>
Co-authored-by: Jens H. Nielsen <[email protected]>
  • Loading branch information
3 people authored Jan 23, 2023
3 parents 9ac90e6 + fd0642b + 6d2bf03 commit 69d35e0
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 745 deletions.
1 change: 1 addition & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ genapi:
../qcodes/instrument_drivers/oxford \
../qcodes/instrument_drivers/Lakeshore \
../qcodes/instrument_drivers/QDev/* \
../qcodes/instrument_drivers/QDevil/* \
../qcodes/instrument_drivers/QuantumDesign/* \
../qcodes/instrument_drivers/rigol/* \
../qcodes/instrument_drivers/rohde_schwarz/* \
Expand Down
2 changes: 2 additions & 0 deletions docs/changes/newsfragments/4932.breaking
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The QDevil QDAC 1 driver has been migrated to qcodes_contrib_drivers and is included from version
0.18.0. The driver in QCoDeS is deprecated and will be removed in a future release.
591 changes: 0 additions & 591 deletions docs/examples/driver_examples/QCodes example with QDevil_QDAC.ipynb

This file was deleted.

1 change: 1 addition & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ sphinx-apidoc -o _auto -d 10 ..\qcodes ^
..\qcodes\instrument_drivers\oxford ^
..\qcodes\instrument_drivers\QuantumDesign\* ^
..\qcodes\instrument_drivers\QDev\* ^
..\qcodes\instrument_drivers\QDevil\* ^
..\qcodes\instrument_drivers\rigol\* ^
..\qcodes\instrument_drivers\rohde_schwarz\* ^
..\qcodes\instrument_drivers\stahl\* ^
Expand Down
2 changes: 2 additions & 0 deletions qcodes/instrument_drivers/QDevil/QDevil_QDAC.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from qcodes import validators as vals
from qcodes.instrument import ChannelList, InstrumentChannel, VisaInstrument
from qcodes.parameters import MultiChannelInstrumentParameter, ParamRawDataType
from qcodes.utils import deprecate

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -188,6 +189,7 @@ def get_raw(self) -> Tuple[ParamRawDataType, ...]:
return output


@deprecate(alternative="QDevil QDAC 1 driver in qcodes_contrib_drivers.")
class QDac(VisaInstrument):
"""
Channelised driver for the QDevil QDAC voltage source.
Expand Down
59 changes: 34 additions & 25 deletions qcodes/tests/common.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from __future__ import annotations

import copy
import cProfile
import os
import tempfile
from contextlib import contextmanager
from functools import wraps
from pathlib import Path
from time import sleep
from typing import (
TYPE_CHECKING,
Expand All @@ -13,11 +16,14 @@
Generator,
Hashable,
Optional,
Sequence,
Tuple,
Type,
TypeVar,
)

import pytest
from typing_extensions import ParamSpec

import qcodes
from qcodes.configuration import Config, DotDict
Expand All @@ -28,20 +34,24 @@
from pytest import ExceptionInfo


def strip_qc(d, keys=('instrument', '__class__')):
def strip_qc(
d: dict[str, Any], keys: Sequence[str] = ("instrument", "__class__")
) -> dict[str, Any]:
# depending on how you run the tests, __module__ can either
# have qcodes on the front or not. Just strip it off.
for key in keys:
if key in d:
d[key] = d[key].replace('qcodes.tests.', 'tests.')
return d

T = TypeVar("T")
P = ParamSpec("P")

def retry_until_does_not_throw(
exception_class_to_expect: Type[Exception] = AssertionError,
tries: int = 5,
delay: float = 0.1
) -> Callable[..., Any]:
exception_class_to_expect: type[Exception] = AssertionError,
tries: int = 5,
delay: float = 0.1,
) -> Callable[[Callable[P, T]], Callable[P, T]]:
"""
Call the decorated function given number of times with given delay between
the calls until it does not throw an exception of a given class.
Expand Down Expand Up @@ -73,10 +83,11 @@ def assert_x_is_true(): ...
A callable that runs the decorated function until it does not throw
a given exception
"""
def retry_until_passes_decorator(func: Callable[..., Any]):

def retry_until_passes_decorator(func: Callable[P, T]) -> Callable[P, T]:

@wraps(func)
def func_retry(*args, **kwargs):
def func_retry(*args: P.args, **kwargs: P.kwargs) -> T:
tries_left = tries - 1
while tries_left > 0:
try:
Expand All @@ -94,7 +105,7 @@ def func_retry(*args, **kwargs):
return retry_until_passes_decorator


def profile(func):
def profile(func: Callable[P, T]) -> Callable[P, T]:
"""
Decorator that profiles the wrapped function with cProfile.
Expand All @@ -106,7 +117,8 @@ def profile(func):
where 'p' is an instance of the 'Stats' class), and print the data
(for example, 'p.print_stats()').
"""
def wrapper(*args, **kwargs):

def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
profile_filename = func.__name__ + '.prof'
profiler = cProfile.Profile()
result = profiler.runcall(func, *args, **kwargs)
Expand All @@ -115,7 +127,7 @@ def wrapper(*args, **kwargs):
return wrapper


def error_caused_by(excinfo: 'ExceptionInfo[Any]', cause: str) -> bool:
def error_caused_by(excinfo: ExceptionInfo[Any], cause: str) -> bool:
"""
Helper function to figure out whether an exception was caused by another
exception with the message provided.
Expand Down Expand Up @@ -145,7 +157,7 @@ def error_caused_by(excinfo: 'ExceptionInfo[Any]', cause: str) -> bool:
return False


def skip_if_no_fixtures(dbname):
def skip_if_no_fixtures(dbname: str | Path) -> None:
if not os.path.exists(dbname):
pytest.skip(
"No db-file fixtures found. "
Expand All @@ -158,22 +170,22 @@ class DumyPar(Metadatable):

"""Docstring for DumyPar. """

def __init__(self, name):
def __init__(self, name: str):
super().__init__()
self.name = name
self.full_name = name

def __str__(self):
def __str__(self) -> str:
return self.full_name

def set(self, value):
def set(self, value: float) -> float:
value = value * 2
return value


@deprecate(reason="Unused internally", alternative="default_config fixture")
@contextmanager
def default_config(user_config: Optional[str] = None):
def default_config(user_config: str | None = None) -> Generator[None, None, None]:
"""
Context manager to temporarily establish default config settings.
This is achieved by overwriting the config paths of the user-,
Expand Down Expand Up @@ -207,8 +219,7 @@ def default_config(user_config: Optional[str] = None):
Config.cwd_file_name = ''
Config.schema_cwd_file_name = ''

default_config_obj: Optional[DotDict] = copy.\
deepcopy(qcodes.config.current_config)
default_config_obj: DotDict | None = copy.deepcopy(qcodes.config.current_config)
qcodes.config = Config()

try:
Expand All @@ -231,9 +242,7 @@ def reset_config_on_exit() -> Generator[None, None, None]:
Context manager to clean any modification of the in memory config on exit
"""
default_config_obj: Optional[DotDict] = copy.deepcopy(
qcodes.config.current_config
)
default_config_obj: DotDict | None = copy.deepcopy(qcodes.config.current_config)

try:
yield
Expand All @@ -242,12 +251,12 @@ def reset_config_on_exit() -> Generator[None, None, None]:


def compare_dictionaries(
dict_1: Dict[Hashable, Any],
dict_2: Dict[Hashable, Any],
dict_1_name: Optional[str] = "d1",
dict_2_name: Optional[str] = "d2",
dict_1: dict[Hashable, Any],
dict_2: dict[Hashable, Any],
dict_1_name: str | None = "d1",
dict_2_name: str | None = "d2",
path: str = "",
) -> Tuple[bool, str]:
) -> tuple[bool, str]:
"""
Compare two dictionaries recursively to find non matching elements.
Expand Down
28 changes: 17 additions & 11 deletions qcodes/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import gc
import os
import sys
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING, Generator

import pytest
Expand All @@ -13,9 +13,10 @@
import qcodes as qc
from qcodes.configuration import Config
from qcodes.dataset import initialise_database, new_data_set
from qcodes.dataset.data_set import DataSet
from qcodes.dataset.descriptions.dependencies import InterDependencies_
from qcodes.dataset.descriptions.param_spec import ParamSpecBase
from qcodes.dataset.experiment_container import new_experiment
from qcodes.dataset.experiment_container import Experiment, new_experiment

settings.register_profile("ci", deadline=1000)

Expand All @@ -24,19 +25,19 @@
if TYPE_CHECKING:
from qcodes.configuration import DotDict

def pytest_configure(config):
def pytest_configure(config: pytest.Config) -> None:
config.addinivalue_line("markers", "win32: tests that only run under windows")


def pytest_runtest_setup(item):
def pytest_runtest_setup(item: pytest.Item) -> None:
ALL = set("darwin linux win32".split())
supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers())
if supported_platforms and sys.platform not in supported_platforms:
pytest.skip(f"cannot run on platform {sys.platform}")


@pytest.fixture(scope="session", autouse=True)
def disable_telemetry():
def disable_telemetry() -> Generator[None, None, None]:
"""
We do not want the tests to send up telemetric information, so we disable
that with this fixture.
Expand All @@ -52,7 +53,7 @@ def disable_telemetry():


@pytest.fixture(scope="function")
def default_config(tmp_path) -> Generator[None, None, None]:
def default_config(tmp_path: Path) -> Generator[None, None, None]:
"""
Fixture to temporarily establish default config settings.
This is achieved by overwriting the config paths of the user-,
Expand Down Expand Up @@ -110,7 +111,7 @@ def reset_config_on_exit() -> Generator[None, None, None]:


@pytest.fixture(scope="session", autouse=True)
def disable_config_subscriber():
def disable_config_subscriber() -> Generator[None, None, None]:
"""
We do not want the tests to send generate subscription events unless specifically
enabled in the test. So disable any default subscriber defined.
Expand All @@ -126,7 +127,7 @@ def disable_config_subscriber():


@pytest.fixture(scope="function", name="empty_temp_db")
def _make_empty_temp_db(tmp_path):
def _make_empty_temp_db(tmp_path: Path) -> Generator[None, None, None]:
global n_experiments
n_experiments = 0
# create a temp database for testing
Expand All @@ -147,8 +148,11 @@ def _make_empty_temp_db(tmp_path):
gc.collect()


# note that you cannot use mark.usefixtures in a fixture
# so empty_temp_db needs to be passed to this fixture
# even if unused https://github.com/pytest-dev/pytest/issues/3664
@pytest.fixture(scope="function", name="experiment")
def _make_experiment(empty_temp_db):
def _make_experiment(empty_temp_db: None) -> Generator[Experiment, None, None]:
e = new_experiment("test-experiment", sample_name="test-sample")
try:
yield e
Expand All @@ -157,7 +161,7 @@ def _make_experiment(empty_temp_db):


@pytest.fixture(scope="function", name="dataset")
def _make_dataset(experiment):
def _make_dataset(experiment: Experiment) -> Generator[DataSet, None, None]:
dataset = new_data_set("test-dataset")
try:
yield dataset
Expand All @@ -167,7 +171,9 @@ def _make_dataset(experiment):


@pytest.fixture(name="standalone_parameters_dataset")
def _make_standalone_parameters_dataset(dataset):
def _make_standalone_parameters_dataset(
dataset: DataSet,
) -> Generator[DataSet, None, None]:
n_params = 3
n_rows = 10 ** 3
params_indep = [
Expand Down
Loading

0 comments on commit 69d35e0

Please sign in to comment.