Skip to content

Commit 9239009

Browse files
jenshnielsenCopilot
andcommitted
Add tests for previously uncovered utility, dataset, and metadatable modules
Add 128 new tests across 12 new test files targeting modules that previously had 0% or very low test coverage (excluding instrument_drivers). New test files: - tests/utils/test_types.py: numpy type tuple definitions and composition - tests/utils/test_abstractmethod.py: qcodes_abstractmethod decorator - tests/utils/test_deprecate.py: QCoDeSDeprecationWarning class - tests/utils/test_deep_update_utils.py: recursive dict merging - tests/utils/test_path_helpers.py: QCoDeS path resolution utilities - tests/utils/test_numpy_utils.py: ragged array conversion - tests/dataset/test_snapshot_utils.py: dataset snapshot diffing - tests/dataset/test_json_exporter.py: JSON linear/heatmap export - tests/dataset/test_export_config.py: export config get/set functions - tests/dataset/test_rundescribertypes.py: TypedDict versioned schemas - tests/dataset/test_sqlite_settings_extended.py: SQLite settings/limits - tests/test_metadatable_base.py: Metadatable and MetadatableWithName Non-driver test coverage: 64.7% -> 65.4% Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 40837cf commit 9239009

12 files changed

Lines changed: 1355 additions & 0 deletions
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
3+
from qcodes.dataset.export_config import (
4+
DataExportType,
5+
get_data_export_name_elements,
6+
get_data_export_prefix,
7+
get_data_export_type,
8+
set_data_export_prefix,
9+
set_data_export_type,
10+
)
11+
12+
13+
def test_data_export_type_enum_members() -> None:
14+
assert DataExportType.NETCDF.value == "nc"
15+
assert DataExportType.CSV.value == "csv"
16+
assert len(DataExportType) == 2
17+
18+
19+
def test_get_data_export_type_with_string_netcdf() -> None:
20+
result = get_data_export_type("NETCDF")
21+
assert result is DataExportType.NETCDF
22+
23+
24+
def test_get_data_export_type_with_string_csv() -> None:
25+
result = get_data_export_type("CSV")
26+
assert result is DataExportType.CSV
27+
28+
29+
def test_get_data_export_type_case_insensitive() -> None:
30+
assert get_data_export_type("netcdf") is DataExportType.NETCDF
31+
assert get_data_export_type("csv") is DataExportType.CSV
32+
assert get_data_export_type("Csv") is DataExportType.CSV
33+
34+
35+
def test_get_data_export_type_with_enum_input() -> None:
36+
result = get_data_export_type(DataExportType.NETCDF)
37+
assert result is DataExportType.NETCDF
38+
39+
result = get_data_export_type(DataExportType.CSV)
40+
assert result is DataExportType.CSV
41+
42+
43+
def test_get_data_export_type_with_none_returns_none() -> None:
44+
# When config export_type is also None/empty, should return None
45+
set_data_export_type(None) # type: ignore[arg-type]
46+
result = get_data_export_type(None)
47+
assert result is None
48+
49+
50+
def test_get_data_export_type_with_invalid_string_returns_none() -> None:
51+
result = get_data_export_type("nonexistent_format")
52+
assert result is None
53+
54+
55+
def test_set_and_get_data_export_prefix_roundtrip() -> None:
56+
set_data_export_prefix("my_prefix_")
57+
assert get_data_export_prefix() == "my_prefix_"
58+
59+
set_data_export_prefix("")
60+
assert get_data_export_prefix() == ""
61+
62+
63+
def test_get_data_export_name_elements_returns_list() -> None:
64+
result = get_data_export_name_elements()
65+
assert isinstance(result, list)
66+
67+
68+
def test_set_data_export_type_valid() -> None:
69+
set_data_export_type("netcdf")
70+
result = get_data_export_type()
71+
assert result is DataExportType.NETCDF
72+
73+
set_data_export_type("csv")
74+
result = get_data_export_type()
75+
assert result is DataExportType.CSV
76+
77+
78+
def test_set_data_export_type_invalid_does_not_change_config() -> None:
79+
set_data_export_type("netcdf")
80+
set_data_export_type("invalid_type")
81+
# Config should still have the previous valid value
82+
result = get_data_export_type()
83+
assert result is DataExportType.NETCDF
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from __future__ import annotations
2+
3+
import copy
4+
import json
5+
from pathlib import Path
6+
7+
import numpy as np
8+
9+
from qcodes.dataset.json_exporter import (
10+
export_data_as_json_heatmap,
11+
export_data_as_json_linear,
12+
json_template_heatmap,
13+
json_template_linear,
14+
)
15+
16+
17+
def test_json_template_linear_structure() -> None:
18+
assert json_template_linear["type"] == "linear"
19+
assert "x" in json_template_linear
20+
assert "y" in json_template_linear
21+
assert isinstance(json_template_linear["x"], dict)
22+
assert isinstance(json_template_linear["y"], dict)
23+
assert "data" in json_template_linear["x"]
24+
assert "data" in json_template_linear["y"]
25+
assert json_template_linear["x"]["is_setpoint"] is True
26+
assert json_template_linear["y"]["is_setpoint"] is False
27+
28+
29+
def test_json_template_heatmap_structure() -> None:
30+
assert json_template_heatmap["type"] == "heatmap"
31+
assert "x" in json_template_heatmap
32+
assert "y" in json_template_heatmap
33+
assert "z" in json_template_heatmap
34+
assert isinstance(json_template_heatmap["x"], dict)
35+
assert isinstance(json_template_heatmap["y"], dict)
36+
assert isinstance(json_template_heatmap["z"], dict)
37+
assert json_template_heatmap["x"]["is_setpoint"] is True
38+
assert json_template_heatmap["y"]["is_setpoint"] is True
39+
assert json_template_heatmap["z"]["is_setpoint"] is False
40+
41+
42+
def test_export_linear_writes_correct_json(tmp_path: Path) -> None:
43+
location = str(tmp_path / "linear.json")
44+
state: dict = {"json": copy.deepcopy(json_template_linear)}
45+
data = [[1.0, 10.0], [2.0, 20.0], [3.0, 30.0]]
46+
47+
export_data_as_json_linear(data, len(data), state, location)
48+
49+
with open(location) as f:
50+
result = json.load(f)
51+
52+
assert result["type"] == "linear"
53+
assert result["x"]["data"] == [1.0, 2.0, 3.0]
54+
assert result["y"]["data"] == [10.0, 20.0, 30.0]
55+
56+
57+
def test_export_linear_accumulates_data(tmp_path: Path) -> None:
58+
location = str(tmp_path / "linear.json")
59+
state: dict = {"json": copy.deepcopy(json_template_linear)}
60+
61+
export_data_as_json_linear([[1.0, 10.0]], 1, state, location)
62+
export_data_as_json_linear([[2.0, 20.0]], 2, state, location)
63+
64+
with open(location) as f:
65+
result = json.load(f)
66+
67+
assert result["x"]["data"] == [1.0, 2.0]
68+
assert result["y"]["data"] == [10.0, 20.0]
69+
70+
71+
def test_export_linear_does_nothing_for_empty_data(tmp_path: Path) -> None:
72+
location = str(tmp_path / "linear.json")
73+
state: dict = {"json": copy.deepcopy(json_template_linear)}
74+
75+
export_data_as_json_linear([], 0, state, location)
76+
77+
assert not Path(location).exists()
78+
79+
80+
def test_export_heatmap_writes_correct_json(tmp_path: Path) -> None:
81+
location = str(tmp_path / "heatmap.json")
82+
xlen = 2
83+
ylen = 3
84+
total = xlen * ylen
85+
86+
state: dict = {
87+
"json": copy.deepcopy(json_template_heatmap),
88+
"data": {
89+
"x": np.zeros(total),
90+
"y": np.zeros(total),
91+
"z": np.zeros(total),
92+
"location": 0,
93+
"xlen": xlen,
94+
"ylen": ylen,
95+
},
96+
}
97+
98+
# 2x3 grid: x varies slowly, y varies fast
99+
data = [
100+
[0.0, 0.0, 1.0],
101+
[0.0, 1.0, 2.0],
102+
[0.0, 2.0, 3.0],
103+
[1.0, 0.0, 4.0],
104+
[1.0, 1.0, 5.0],
105+
[1.0, 2.0, 6.0],
106+
]
107+
108+
export_data_as_json_heatmap(data, total, state, location)
109+
110+
with open(location) as f:
111+
result = json.load(f)
112+
113+
assert result["type"] == "heatmap"
114+
assert result["x"]["data"] == [0.0, 1.0]
115+
assert result["y"]["data"] == [0.0, 1.0, 2.0]
116+
assert result["z"]["data"] == [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
117+
118+
119+
def test_export_heatmap_does_nothing_for_empty_data(tmp_path: Path) -> None:
120+
location = str(tmp_path / "heatmap.json")
121+
state: dict = {
122+
"json": copy.deepcopy(json_template_heatmap),
123+
"data": {
124+
"x": np.zeros(4),
125+
"y": np.zeros(4),
126+
"z": np.zeros(4),
127+
"location": 0,
128+
"xlen": 2,
129+
"ylen": 2,
130+
},
131+
}
132+
133+
export_data_as_json_heatmap([], 0, state, location)
134+
135+
assert not Path(location).exists()
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"""
2+
Tests for qcodes.dataset.descriptions.versioning.rundescribertypes.
3+
4+
Verifies the TypedDict classes, inheritance relationships, type aliases,
5+
and the RunDescriberDicts union.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import typing
11+
12+
from typing_extensions import get_annotations, get_original_bases
13+
14+
from qcodes.dataset.descriptions.versioning.rundescribertypes import (
15+
InterDependencies_Dict,
16+
InterDependenciesDict,
17+
RunDescriberDicts,
18+
RunDescriberV0Dict,
19+
RunDescriberV1Dict,
20+
RunDescriberV2Dict,
21+
RunDescriberV3Dict,
22+
Shapes,
23+
)
24+
25+
# --------------- Shapes type alias ---------------
26+
27+
28+
def test_shapes_type_alias() -> None:
29+
sample: Shapes = {"param": (1, 2, 3)}
30+
assert sample["param"] == (1, 2, 3)
31+
32+
33+
# --------------- InterDependenciesDict ---------------
34+
35+
36+
def test_interdependencies_dict_instantiation() -> None:
37+
d: InterDependenciesDict = {"paramspecs": ()}
38+
assert d["paramspecs"] == ()
39+
40+
41+
# --------------- InterDependencies_Dict ---------------
42+
43+
44+
def test_interdependencies_underscore_dict_instantiation() -> None:
45+
d: InterDependencies_Dict = {
46+
"parameters": {},
47+
"dependencies": {},
48+
"inferences": {},
49+
"standalones": [],
50+
}
51+
assert d["parameters"] == {}
52+
assert d["standalones"] == []
53+
54+
55+
# --------------- RunDescriberV0Dict ---------------
56+
57+
58+
def test_v0_dict_instantiation() -> None:
59+
d: RunDescriberV0Dict = {
60+
"version": 0,
61+
"interdependencies": {"paramspecs": ()},
62+
}
63+
assert d["version"] == 0
64+
65+
66+
# --------------- RunDescriberV1Dict ---------------
67+
68+
69+
def test_v1_dict_instantiation() -> None:
70+
d: RunDescriberV1Dict = {
71+
"version": 1,
72+
"interdependencies": {
73+
"parameters": {},
74+
"dependencies": {},
75+
"inferences": {},
76+
"standalones": [],
77+
},
78+
}
79+
assert d["version"] == 1
80+
81+
82+
# --------------- RunDescriberV2Dict inherits from V0 ---------------
83+
84+
85+
def test_v2_dict_inherits_from_v0() -> None:
86+
# typing_extensions TypedDict flattens __bases__ to (dict,) at runtime;
87+
# verify structural inheritance via __orig_bases__ and annotations.
88+
assert RunDescriberV0Dict in get_original_bases(RunDescriberV2Dict)
89+
# V2 should contain all V0 keys plus its own
90+
v0_keys = set(get_annotations(RunDescriberV0Dict))
91+
v2_keys = set(get_annotations(RunDescriberV2Dict))
92+
assert v0_keys.issubset(v2_keys)
93+
94+
95+
def test_v2_dict_instantiation() -> None:
96+
d: RunDescriberV2Dict = {
97+
"version": 2,
98+
"interdependencies": {"paramspecs": ()},
99+
"interdependencies_": {
100+
"parameters": {},
101+
"dependencies": {},
102+
"inferences": {},
103+
"standalones": [],
104+
},
105+
}
106+
assert d["version"] == 2
107+
assert "interdependencies_" in d
108+
109+
110+
# --------------- RunDescriberV3Dict inherits from V2 ---------------
111+
112+
113+
def test_v3_dict_inherits_from_v2() -> None:
114+
assert RunDescriberV2Dict in get_original_bases(RunDescriberV3Dict)
115+
v2_keys = set(get_annotations(RunDescriberV2Dict))
116+
v3_keys = set(get_annotations(RunDescriberV3Dict))
117+
assert v2_keys.issubset(v3_keys)
118+
119+
120+
def test_v3_dict_inherits_from_v0_transitively() -> None:
121+
# V3 inherits from V2 which inherits from V0 — all V0 keys present
122+
v0_keys = set(get_annotations(RunDescriberV0Dict))
123+
v3_keys = set(get_annotations(RunDescriberV3Dict))
124+
assert v0_keys.issubset(v3_keys)
125+
126+
127+
def test_v3_dict_instantiation() -> None:
128+
d: RunDescriberV3Dict = {
129+
"version": 3,
130+
"interdependencies": {"paramspecs": ()},
131+
"interdependencies_": {
132+
"parameters": {},
133+
"dependencies": {},
134+
"inferences": {},
135+
"standalones": [],
136+
},
137+
"shapes": {"x": (10,)},
138+
}
139+
assert d["version"] == 3
140+
assert d["shapes"] == {"x": (10,)}
141+
142+
143+
def test_v3_dict_shapes_none() -> None:
144+
d: RunDescriberV3Dict = {
145+
"version": 3,
146+
"interdependencies": {"paramspecs": ()},
147+
"interdependencies_": {
148+
"parameters": {},
149+
"dependencies": {},
150+
"inferences": {},
151+
"standalones": [],
152+
},
153+
"shapes": None,
154+
}
155+
assert d["shapes"] is None
156+
157+
158+
# --------------- RunDescriberDicts union ---------------
159+
160+
161+
def test_rundescriber_dicts_includes_all_versions() -> None:
162+
args = typing.get_args(RunDescriberDicts)
163+
expected = {
164+
RunDescriberV0Dict,
165+
RunDescriberV1Dict,
166+
RunDescriberV2Dict,
167+
RunDescriberV3Dict,
168+
}
169+
assert set(args) == expected

0 commit comments

Comments
 (0)