pytest-libiio 0.0.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. pytest_libiio/__init__.py +3 -0
  2. pytest_libiio/coverage.py +192 -0
  3. pytest_libiio/meta.py +255 -0
  4. pytest_libiio/mkpatch.py +113 -0
  5. pytest_libiio/plugin.py +696 -0
  6. pytest_libiio/resources/adi_hardware_map.yml +217 -0
  7. pytest_libiio/resources/devices/ad4020.xml +0 -0
  8. pytest_libiio/resources/devices/ad9081.xml +1 -0
  9. pytest_libiio/resources/devices/ad9081_tdd.xml +1 -0
  10. pytest_libiio/resources/devices/ad9172.xml +1 -0
  11. pytest_libiio/resources/devices/ad9265.xml +1 -0
  12. pytest_libiio/resources/devices/ad9371.xml +1 -0
  13. pytest_libiio/resources/devices/ad9375.xml +1 -0
  14. pytest_libiio/resources/devices/ad9434.xml +1 -0
  15. pytest_libiio/resources/devices/adrv9002.xml +18 -0
  16. pytest_libiio/resources/devices/adrv9002_singledma.xml +926 -0
  17. pytest_libiio/resources/devices/adrv9002_splitdma.xml +1046 -0
  18. pytest_libiio/resources/devices/adrv9361-z7035.xml +3 -0
  19. pytest_libiio/resources/devices/adrv9364-z7020.xml +4 -0
  20. pytest_libiio/resources/devices/adt7420.xml +1 -0
  21. pytest_libiio/resources/devices/adxl355.xml +2 -0
  22. pytest_libiio/resources/devices/cn0511.xml +1 -0
  23. pytest_libiio/resources/devices/cn0540.xml +1 -0
  24. pytest_libiio/resources/devices/daq2.xml +2 -0
  25. pytest_libiio/resources/devices/daq3.xml +2 -0
  26. pytest_libiio/resources/devices/fmcomms11.xml +1 -0
  27. pytest_libiio/resources/devices/fmcomms2-3.xml +3 -0
  28. pytest_libiio/resources/devices/fmcomms4.xml +4 -0
  29. pytest_libiio/resources/devices/fmcomms5.xml +5 -0
  30. pytest_libiio/resources/devices/ltc2387.xml +1 -0
  31. pytest_libiio/resources/devices/pluto.xml +4 -0
  32. pytest_libiio/tools.py +20 -0
  33. pytest_libiio-0.0.22.dist-info/METADATA +159 -0
  34. pytest_libiio-0.0.22.dist-info/RECORD +38 -0
  35. pytest_libiio-0.0.22.dist-info/WHEEL +5 -0
  36. pytest_libiio-0.0.22.dist-info/entry_points.txt +5 -0
  37. pytest_libiio-0.0.22.dist-info/licenses/LICENSE +28 -0
  38. pytest_libiio-0.0.22.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ """A pytest plugin for managing libiio contexts."""
2
+
3
+ __version__ = "0.0.22"
@@ -0,0 +1,192 @@
1
+ """Coverage tracking for iio attributes using monkey patching."""
2
+
3
+ import json
4
+ import os
5
+ from pprint import pprint
6
+
7
+ import iio
8
+
9
+
10
+ class MultiContextTracker:
11
+ """Class to track coverage across multiple DUTs."""
12
+
13
+ def __init__(self):
14
+ self.trackers = {}
15
+ self.track_debug_props = False
16
+ self.results_folder = "iio_coverage_results"
17
+
18
+ def do_monkey_patch(self):
19
+ """Apply monkey patch to iio.py."""
20
+ from . import mkpatch
21
+ from .mkpatch import reset_coverage_tracker
22
+
23
+ reset_coverage_tracker()
24
+
25
+ def add_instance(self, name, uri):
26
+ """Add a new context to track.
27
+
28
+ Args:
29
+ name (str): Name of the context. Just a label, not used in iio.
30
+ uri (str): URI of the context.
31
+ """
32
+ if uri not in self.trackers:
33
+ self.trackers[name] = CoverageTracker(name, uri, self.track_debug_props)
34
+
35
+ def set_tracker(self, name):
36
+ from .mkpatch import set_coverage_tracker
37
+
38
+ if name not in self.trackers:
39
+ raise ValueError(f"{name} not tracked.")
40
+
41
+ set_coverage_tracker(tracker=self.trackers[name])
42
+
43
+
44
+ class CoverageTracker:
45
+ """Class to track coverage of iio attributes."""
46
+
47
+ def __init__(self, name, uri, track_debug_props=False, results_folder=None):
48
+ self.name = name
49
+ self.context_attr_reads_writes = {}
50
+ self.device_attr_reads_writes = {}
51
+ self.channel_attr_reads_writes = {}
52
+ self.debug_attr_reads_writes = {}
53
+ self.uri = uri
54
+ self.ctx = iio.Context(uri)
55
+ self.track_debug_props = track_debug_props
56
+ self.results_folder = results_folder or "iio_coverage_results"
57
+ self.build_context_map()
58
+
59
+ def reset(self):
60
+ """Reset the coverage tracker."""
61
+ self.context_attr_reads_writes.clear()
62
+ self.device_attr_reads_writes.clear()
63
+ self.channel_attr_reads_writes.clear()
64
+ self.debug_attr_reads_writes.clear()
65
+
66
+ def build_context_map(self):
67
+ """Build a map of context attributes."""
68
+ self.context_attr_reads_writes = {attr: 0 for attr in self.ctx.attrs}
69
+ for dev in self.ctx.devices:
70
+ self.device_attr_reads_writes[dev.name] = {attr: 0 for attr in dev.attrs}
71
+ self.channel_attr_reads_writes[dev.name] = {
72
+ chn.id: {attr: 0 for attr in chn.attrs} for chn in dev.channels
73
+ }
74
+ if self.track_debug_props:
75
+ self.debug_attr_reads_writes[dev.name] = {
76
+ attr: 0 for attr in dev.debug_attrs
77
+ }
78
+
79
+ def export(self):
80
+ """Export raw coverage data."""
81
+ out = {
82
+ "context_attr_reads_writes": self.context_attr_reads_writes,
83
+ "device_attr_reads_writes": self.device_attr_reads_writes,
84
+ "channel_attr_reads_writes": self.channel_attr_reads_writes,
85
+ }
86
+ if self.track_debug_props:
87
+ out["debug_attr_reads_writes"] = self.debug_attr_reads_writes
88
+ return out
89
+
90
+ def export_to_file(self, filename=None):
91
+ """Export coverage data to a file.
92
+
93
+ Args:
94
+ filename (str, optional): Name of the file to save the coverage data.
95
+ If None, defaults to "{self.name}_coverage.json".
96
+ """
97
+ if filename is None:
98
+ filename = f"{self.name}_coverage.json"
99
+ if not os.path.exists(self.results_folder):
100
+ os.makedirs(self.results_folder)
101
+ filename = os.path.join(os.getcwd(), self.results_folder, filename)
102
+ with open(filename, "w") as f:
103
+ json.dump(self.export(), f, indent=4)
104
+ print(f"Coverage data exported to {filename}")
105
+
106
+ def print_context_map(self):
107
+ print("Context Attribute Reads:")
108
+ pprint(self.context_attr_reads_writes)
109
+ print("Device Attribute Reads:")
110
+ pprint(self.device_attr_reads_writes)
111
+ print("Channel Attribute Reads:")
112
+ pprint(self.channel_attr_reads_writes)
113
+ if self.track_debug_props:
114
+ print("Debug Attribute Reads:")
115
+ pprint(self.debug_attr_reads_writes)
116
+
117
+ def calculate_coverage(self):
118
+ """Calculate coverage based on attribute reads and writes."""
119
+
120
+ total_context_reads_writes = sum(self.context_attr_reads_writes.values())
121
+ total_context_attributes = len(self.context_attr_reads_writes)
122
+
123
+ total_device_reads_writes = sum(
124
+ sum(device.values()) for device in self.device_attr_reads_writes.values()
125
+ )
126
+ total_device_attributes = sum(
127
+ len(device) for device in self.device_attr_reads_writes.values()
128
+ )
129
+ if self.track_debug_props:
130
+ total_debug_reads_writes = sum(
131
+ self.debug_attr_reads_writes[dev].values()
132
+ for dev in self.debug_attr_reads_writes
133
+ )
134
+ total_device_attributes = sum(
135
+ len(self.debug_attr_reads_writes[dev])
136
+ for dev in self.debug_attr_reads_writes
137
+ )
138
+
139
+ total_channel_reads_writes = 0
140
+ total_channel_attributes = 0
141
+ for device in self.channel_attr_reads_writes:
142
+ for channel in self.channel_attr_reads_writes[device]:
143
+ total_channel_reads_writes += sum(
144
+ self.channel_attr_reads_writes[device][channel].values()
145
+ )
146
+ total_channel_attributes += len(
147
+ self.channel_attr_reads_writes[device][channel]
148
+ )
149
+
150
+ out = {
151
+ "context_coverage": (
152
+ total_context_reads_writes / total_context_attributes
153
+ if total_context_attributes
154
+ else 0
155
+ ),
156
+ "device_coverage": (
157
+ total_device_reads_writes / total_device_attributes
158
+ if total_device_attributes
159
+ else 0
160
+ ),
161
+ "channel_coverage": (
162
+ total_channel_reads_writes / total_channel_attributes
163
+ if total_channel_attributes
164
+ else 0
165
+ ),
166
+ "total_coverage": (
167
+ total_context_reads_writes
168
+ + total_device_reads_writes
169
+ + total_channel_reads_writes
170
+ )
171
+ / (
172
+ total_context_attributes
173
+ + total_device_attributes
174
+ + total_channel_attributes
175
+ ),
176
+ "total_context_reads_writes": total_context_reads_writes,
177
+ "total_device_reads_writes": total_device_reads_writes,
178
+ "total_channel_reads_writes": total_channel_reads_writes,
179
+ "total_context_attributes": total_context_attributes,
180
+ "total_device_attributes": total_device_attributes,
181
+ "total_channel_attributes": total_channel_attributes,
182
+ }
183
+
184
+ if self.track_debug_props:
185
+ out["total_debug_reads_writes"] = total_debug_reads_writes
186
+ out["total_debug_attributes"] = total_device_attributes
187
+ out["debug_coverage"] = (
188
+ total_debug_reads_writes / total_device_attributes
189
+ if total_device_attributes
190
+ else 0
191
+ )
192
+ return out
pytest_libiio/meta.py ADDED
@@ -0,0 +1,255 @@
1
+ """This is a set of function to help extract metadata from tests and hardware"""
2
+
3
+ import os
4
+ import re
5
+ import xml.etree.ElementTree as ET
6
+ from io import BytesIO, StringIO
7
+ from pprint import pprint
8
+
9
+ import iio
10
+
11
+ import lxml.etree as etree
12
+
13
+ try:
14
+ import paramiko
15
+
16
+ useSSH = True
17
+ except ImportError:
18
+ useSSH = False
19
+
20
+
21
+ def __get_value_from_hw(
22
+ ctx, attrib, ptype, attr_name, dev_name=None, ch_name=None, is_output=False
23
+ ):
24
+ if "value" in attrib:
25
+ value = attrib["value"]
26
+ else:
27
+ if ptype == "context":
28
+ value = ctx.attrs[attr_name].value
29
+ elif ptype == "device":
30
+ dev = ctx.find_device(str(dev_name))
31
+ try:
32
+ value = dev.attrs[attr_name].value
33
+ except OSError:
34
+ value = "ERROR"
35
+ elif ptype == "channel":
36
+ dev = ctx.find_device(str(dev_name))
37
+ ch = dev.find_channel(str(ch_name), is_output)
38
+ if hasattr(ch, "attrs") and attr_name in ch.attrs:
39
+ try:
40
+ value = ch.attrs[attr_name].value
41
+ except OSError:
42
+ value = "ERROR"
43
+ else:
44
+ value = "NO ATTRS FOR CHANNEL"
45
+ elif ptype == "debug":
46
+ dev = ctx.find_device(str(dev_name))
47
+ try:
48
+ value = dev.debug_attrs[attr_name].value
49
+ except OSError:
50
+ value = "ERROR"
51
+ elif ptype == "buffer":
52
+ dev = ctx.find_device(str(dev_name))
53
+ try:
54
+ value = dev.buffer_attrs[attr_name].value
55
+ except OSError:
56
+ value = "ERROR"
57
+ else:
58
+ raise Exception("Unknown property type")
59
+
60
+ return value
61
+
62
+
63
+ def __get_name_id(item):
64
+ return item.attrib["name"] if "name" in item.attrib else item.attrib["id"]
65
+
66
+
67
+ def dprint(*args, **kwargs):
68
+ """Debug print"""
69
+ if os.environ.get("DEBUG"):
70
+ print(*args, **kwargs)
71
+
72
+
73
+ def get_emulated_context(ctx: iio.Context):
74
+ cxml = ctx.xml
75
+ cxml = str(cxml)
76
+
77
+ # Convert string to xml
78
+ root = ET.fromstring(cxml)
79
+
80
+ context_fields = list(root.attrib.keys())
81
+
82
+ # loop through items
83
+ for item in root:
84
+ if item.tag == "context-attribute":
85
+ dprint("Context-attribute---")
86
+ attr_name_id = __get_name_id(item)
87
+ value = __get_value_from_hw(ctx, item.attrib, "context", attr_name_id)
88
+ dprint("CONTEXT", attr_name_id, value)
89
+ item.attrib["value"] = value
90
+ elif item.tag == "device":
91
+ dprint("Device---")
92
+ device_name_id = __get_name_id(item)
93
+ # Devices
94
+ for sitem in item:
95
+ attr_name_id = __get_name_id(sitem)
96
+ # Device attributes
97
+ if sitem.tag == "attribute":
98
+ value = __get_value_from_hw(
99
+ ctx, sitem.attrib, "device", attr_name_id, device_name_id
100
+ )
101
+ dprint("DEVICE", device_name_id, attr_name_id, value)
102
+ sitem.attrib["value"] = value
103
+ # Channel attributes
104
+ elif sitem.tag == "channel":
105
+ channel_name_id = attr_name_id
106
+ if sitem.attrib["type"] == "output":
107
+ is_output = True
108
+ elif sitem.attrib["type"] == "input":
109
+ is_output = False
110
+ else:
111
+ raise Exception("Unknown channel type")
112
+ for ssitem in sitem:
113
+ if ssitem.tag == "scan-element":
114
+ continue # FIXME
115
+ channel_attr_name_id = __get_name_id(ssitem)
116
+ value = __get_value_from_hw(
117
+ ctx,
118
+ ssitem.attrib,
119
+ "channel",
120
+ channel_attr_name_id,
121
+ device_name_id,
122
+ channel_name_id,
123
+ is_output,
124
+ )
125
+ dprint(
126
+ "CHANNEL",
127
+ device_name_id,
128
+ channel_name_id,
129
+ is_output,
130
+ channel_attr_name_id,
131
+ value,
132
+ )
133
+ ssitem.attrib["value"] = value
134
+
135
+ elif sitem.tag == "debug-attribute":
136
+ value = __get_value_from_hw(
137
+ ctx, sitem.attrib, "debug", attr_name_id, device_name_id
138
+ )
139
+ dprint("DEBUG", device_name_id, attr_name_id, value)
140
+ sitem.attrib["value"] = value
141
+
142
+ elif sitem.tag == "buffer-attribute":
143
+ value = __get_value_from_hw(
144
+ ctx, sitem.attrib, "buffer", attr_name_id, device_name_id
145
+ )
146
+ dprint("BUFFER", device_name_id, attr_name_id, value)
147
+ sitem.attrib["value"] = value
148
+ else:
149
+ raise Exception("Unknown device item")
150
+ else:
151
+ raise Exception("Unknown item")
152
+
153
+ # Update context ATTLIST of DOCTYPE to include all context fields
154
+ context_fields += ["description"]
155
+ context_fields = list(set(context_fields))
156
+ template = "<!ATTLIST context "
157
+ context_fields_list = [
158
+ f"{field.replace(',','').replace(' ','_')} CDATA #IMPLIED "
159
+ for field in context_fields
160
+ ]
161
+ full = template + "".join(context_fields_list)
162
+ full = f"{full[:-1]}>"
163
+
164
+ doctype = f"<!DOCTYPE context [\n\
165
+ <!ELEMENT context (device | context-attribute)*>\n\
166
+ <!ELEMENT context-attribute EMPTY>\n\
167
+ <!ELEMENT device (channel | attribute | debug-attribute | buffer-attribute)*>\n\
168
+ <!ELEMENT channel (scan-element?, attribute*)>\n\
169
+ <!ELEMENT attribute EMPTY><!ELEMENT scan-element EMPTY>\n\
170
+ <!ELEMENT debug-attribute EMPTY>\n\
171
+ <!ELEMENT buffer-attribute EMPTY>\n\
172
+ {full}\n\
173
+ <!ATTLIST context-attribute name CDATA #REQUIRED value CDATA #REQUIRED>\n\
174
+ <!ATTLIST device id CDATA #REQUIRED name CDATA #IMPLIED>\n\
175
+ <!ATTLIST channel id CDATA #REQUIRED type (input|output) #REQUIRED name CDATA #IMPLIED>\n\
176
+ <!ATTLIST scan-element index CDATA #REQUIRED format CDATA #REQUIRED scale CDATA #IMPLIED>\n\
177
+ <!ATTLIST attribute name CDATA #REQUIRED filename CDATA #IMPLIED value CDATA #IMPLIED>\n\
178
+ <!ATTLIST debug-attribute name CDATA #REQUIRED value CDATA #IMPLIED>\n\
179
+ <!ATTLIST buffer-attribute name CDATA #REQUIRED value CDATA #IMPLIED>\n\
180
+ ]>"
181
+
182
+ xml_str = ET.tostring(root, encoding="unicode")
183
+
184
+ tree = etree.parse(StringIO(xml_str))
185
+ xml_str = etree.tostring(
186
+ tree, pretty_print=True, xml_declaration=True, doctype=doctype, encoding="utf-8"
187
+ )
188
+ xml_str = str(xml_str, "utf-8") # type: ignore
189
+ return xml_str
190
+
191
+
192
+ def get_ssh_session(ctx: iio.Context):
193
+ """Get ssh session"""
194
+ uri = ctx.attrs["uri"].split(":")
195
+ if not useSSH:
196
+ print("Paramiko is not installed, cannot use SSH")
197
+ return None
198
+ if uri[0] == "ip":
199
+ print(f"Starting SSH session for uri: {ctx.attrs['uri']}")
200
+ ip = uri[1]
201
+ ssh = paramiko.SSHClient()
202
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy)
203
+ ssh.connect(ip, username="root", password="analog")
204
+ return ssh
205
+ else:
206
+ print(f"URI: {ctx.attrs['uri']} is not supported for SSH telemetry")
207
+ return None
208
+
209
+
210
+ def get_hardware_info(ctx: iio.Context, ssh=None):
211
+ """Get hardware information from the context"""
212
+ local = {}
213
+ remote = {}
214
+
215
+ local["libiio"] = iio.version
216
+
217
+ # Get context xml and values from HW
218
+ remote["iio_context"] = get_emulated_context(ctx)
219
+
220
+ # Get telemetry data from remote linux system
221
+ uri = ctx.attrs["uri"].split(":")
222
+ if uri[0] == "ip" and ssh is not None:
223
+ stdin, stdout, stderr = ssh.exec_command("dmesg")
224
+ remote["dmesg"] = stdout.read().decode()
225
+ stdin, stdout, stderr = ssh.exec_command("uname -a")
226
+ remote["uname"] = stdout.read().decode()
227
+ stdin, stdout, stderr = ssh.exec_command("cat /proc/cpuinfo")
228
+ remote["cpuinfo"] = stdout.read().decode()
229
+ stdin, stdout, stderr = ssh.exec_command("cat /proc/meminfo")
230
+ remote["meminfo"] = stdout.read().decode()
231
+ stdin, stdout, stderr = ssh.exec_command("cat /proc/version")
232
+ remote["version"] = stdout.read().decode()
233
+ stdin, stdout, stderr = ssh.exec_command("cat /etc/os-release")
234
+ remote["os-release"] = stdout.read().decode()
235
+ stdin, stdout, stderr = ssh.exec_command("df -h")
236
+ remote["df"] = stdout.read().decode()
237
+ stdin, stdout, stderr = ssh.exec_command(
238
+ 'python -c "import iio; print(iio.version)"'
239
+ )
240
+ remote["libiio"] = stdout.read().decode()
241
+ stdin, stdout, stderr = ssh.exec_command("iio_info")
242
+ remote["iio_info"] = stdout.read().decode()
243
+ # ssh.close()
244
+
245
+ metadata = {}
246
+ metadata["local"] = local
247
+ metadata["remote"] = remote
248
+
249
+ return metadata
250
+
251
+
252
+ if __name__ == "__main__":
253
+ ctx = iio.Context("ip:analog.local")
254
+ # root = get_hardware_info(ctx)
255
+ root = get_emulated_context(ctx)
@@ -0,0 +1,113 @@
1
+ import logging
2
+ from pprint import pprint
3
+
4
+ import iio
5
+ from iio import ChannelAttr as _Attr
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ coverage_tracker = None
10
+
11
+
12
+ def _check_tracker():
13
+ """Check if the coverage tracker is set."""
14
+ global coverage_tracker # noqa: F824
15
+ if coverage_tracker is None:
16
+ raise RuntimeError(
17
+ "Coverage tracker is not set. Call set_coverage_tracker first."
18
+ )
19
+
20
+
21
+ def _read(self):
22
+ """MP Read method to capture attribute reads"""
23
+ attr_name = self.name
24
+ if self._channel:
25
+ name_raw = iio._c_get_id(self._channel)
26
+ channel_name = name_raw.decode("ascii") if name_raw is not None else None
27
+ dev_ptr = iio._channel_get_device(self._channel)
28
+ name_raw = iio._d_get_name(dev_ptr)
29
+ device_name = name_raw.decode("ascii") if name_raw is not None else None
30
+ else:
31
+ channel_name = None
32
+ device_name = self._name
33
+
34
+ _check_tracker()
35
+
36
+ global coverage_tracker # noqa: F824
37
+ if channel_name and device_name:
38
+ coverage_tracker.channel_attr_reads_writes[device_name][channel_name][
39
+ attr_name
40
+ ] += 1
41
+ elif device_name:
42
+ coverage_tracker.device_attr_reads_writes[device_name][attr_name] += 1
43
+ else:
44
+ coverage_tracker.context_attr_reads_writes[attr_name] += 1
45
+
46
+ logger.debug(
47
+ f"Reading attribute: {attr_name}, Channel: {channel_name}, Device: {device_name}"
48
+ )
49
+ return self._read_org()
50
+
51
+
52
+ def _write(self, value):
53
+ """MP Write method to capture attribute writes"""
54
+ attr_name = self.name
55
+ if self._channel:
56
+ name_raw = iio._c_get_id(self._channel)
57
+ channel_name = name_raw.decode("ascii") if name_raw is not None else None
58
+ dev_ptr = iio._channel_get_device(self._channel)
59
+ name_raw = iio._d_get_name(dev_ptr)
60
+ device_name = name_raw.decode("ascii") if name_raw is not None else None
61
+ else:
62
+ channel_name = None
63
+ device_name = self._name
64
+
65
+ _check_tracker()
66
+
67
+ global coverage_tracker # noqa: F824
68
+ if channel_name and device_name:
69
+ coverage_tracker.channel_attr_reads_writes[device_name][channel_name][
70
+ attr_name
71
+ ] += 1
72
+ elif device_name:
73
+ coverage_tracker.device_attr_reads_writes[device_name][attr_name] += 1
74
+ else:
75
+ coverage_tracker.context_attr_reads_writes[attr_name] += 1
76
+
77
+ logger.debug(
78
+ f"Writing attribute: {attr_name}, Channel: {channel_name}, Device: {device_name}, Value: {value}"
79
+ )
80
+ self._write_org(value)
81
+
82
+
83
+ _Attr._read_org = _Attr._read
84
+ _Attr._read = _read
85
+
86
+ _Attr._write_org = _Attr._write
87
+ _Attr._write = _write
88
+
89
+
90
+ def set_coverage_tracker(tracker):
91
+ """Set up the coverage tracker for iio attributes."""
92
+ global coverage_tracker
93
+
94
+ coverage_tracker = tracker
95
+
96
+
97
+ def reset_coverage_tracker():
98
+ """Reset the coverage tracker."""
99
+ global coverage_tracker
100
+ coverage_tracker = None
101
+
102
+
103
+ def unset_monkey_patch():
104
+ """Unset the monkey patch for iio attributes."""
105
+ global coverage_tracker
106
+ if coverage_tracker:
107
+ coverage_tracker = None
108
+
109
+ _Attr._read = _Attr._read_org
110
+ _Attr._write = _Attr._write_org
111
+
112
+
113
+ logger.debug("iio.py monkey patch applied")