Skip to content

Commit d225ef0

Browse files
tests added
1 parent b116a4e commit d225ef0

7 files changed

Lines changed: 1118 additions & 901 deletions

tests/test_deep_simulation.py

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ def tearDown(self):
5151
def _timeout_handler(self, signum, frame):
5252
raise TimeoutError(
5353
"Test {self._testMethodName} took longer than 30s! Infinite Loop suspected.")
54-
54+
5555
def run_module_safely(self, module_name):
5656
"""Imports and runs a module with a strict 30-second timeout."""
5757
# Set an alarm for 30 seconds (Works on Linux/GitHub Actions)
5858
if hasattr(signal, 'SIGALRM'):
59+
# Ensure any previous alarm is cleared
60+
signal.alarm(0)
5961
signal.signal(signal.SIGALRM, self._timeout_handler)
6062
signal.alarm(30)
6163

@@ -80,6 +82,7 @@ def run_module_safely(self, module_name):
8082
raise e # Re-raise to fail the test
8183
else:
8284
print(f" -> [INFO] Script stopped with: {e}", flush=True)
85+
8386
finally:
8487
if hasattr(signal, 'SIGALRM'):
8588
signal.alarm(0) # Disable the alarm
@@ -108,7 +111,9 @@ def side_effect(*args, **kwargs):
108111
def test_01_k2400_iv_backend(self):
109112
# GLOBAL PATCH for sleep is critical here
110113
with patch('pymeasure.instruments.keithley.Keithley2400') as MockInst, \
111-
patch('time.sleep', side_effect=self.get_circuit_breaker(5)):
114+
patch('time.sleep', side_effect=self.get_circuit_breaker(5)) as mock_sleep:
115+
116+
self.addCleanup(mock_sleep.stop)
112117

113118
spy = MockInst.return_value
114119
with patch('builtins.input', side_effect=['100', '10', 'test_file']), \
@@ -118,8 +123,10 @@ def test_01_k2400_iv_backend(self):
118123
spy.enable_source.assert_called()
119124

120125
def test_02_lakeshore_backend(self):
121-
with patch('pyvisa.ResourceManager') as MockRM, \
122-
patch('time.sleep', side_effect=self.get_circuit_breaker(15)):
126+
with patch('pyvisa.ResourceManager') as MockRM:
127+
mock_sleep = patch('time.sleep', side_effect=self.get_circuit_breaker(15))
128+
mock_sleep.start()
129+
self.addCleanup(mock_sleep.stop)
123130

124131
spy = MockRM.return_value.open_resource.return_value
125132
spy.query.side_effect = [
@@ -136,10 +143,13 @@ def test_02_lakeshore_backend(self):
136143
patch('matplotlib.pyplot.subplots', return_value=(mock_fig, mock_ax)):
137144
self.run_module_safely(
138145
"Lakeshore_350_340.Backends.T_Control_L350_Simple_Backend_v10")
139-
146+
140147
def test_03_k6517b_pyro_backend(self):
141-
with patch('pymeasure.instruments.keithley.Keithley6517B') as MockInst, \
142-
patch('time.sleep', side_effect=self.get_circuit_breaker(5)):
148+
with patch('pymeasure.instruments.keithley.Keithley6517B') as MockInst:
149+
mock_sleep = patch('time.sleep', side_effect=self.get_circuit_breaker(5))
150+
mock_sleep.start()
151+
self.addCleanup(mock_sleep.stop)
152+
143153
spy = MockInst.return_value
144154
spy.current = 1.23e-9
145155
with patch('pandas.DataFrame.to_csv'):
@@ -149,16 +159,22 @@ def test_03_k6517b_pyro_backend(self):
149159
def test_04_lcr_keysight_backend(self):
150160
with patch('pymeasure.instruments.agilent.AgilentE4980'), \
151161
patch('pyvisa.ResourceManager') as MockRM, \
152-
patch('time.sleep', side_effect=self.get_circuit_breaker(5)):
162+
patch('time.sleep', side_effect=self.get_circuit_breaker(5)) as mock_sleep:
163+
164+
self.addCleanup(mock_sleep.stop)
165+
153166
visa_spy = MockRM.return_value.open_resource.return_value
154167
visa_spy.query.return_value = "0.5"
155168
with patch('pandas.DataFrame.to_csv'):
156169
self.run_module_safely(
157170
"LCR_Keysight_E4980A.Backends.CV_KE4980A_Simple_Backend_v10")
158171

159172
def test_05_delta_simple(self):
160-
with patch('pyvisa.ResourceManager') as MockRM, \
161-
patch('time.sleep', side_effect=self.get_circuit_breaker(10)):
173+
with patch('pyvisa.ResourceManager') as MockRM:
174+
mock_sleep = patch('time.sleep', side_effect=self.get_circuit_breaker(10))
175+
mock_sleep.start()
176+
self.addCleanup(mock_sleep.stop)
177+
162178
MockRM.return_value.open_resource.return_value
163179
inputs = ['0', '1e-5', '1e-6', 'test_file', 'y', 'y']
164180
with patch('builtins.input', side_effect=inputs), \
@@ -167,8 +183,11 @@ def test_05_delta_simple(self):
167183
"Delta_mode_Keithley_6221_2182.Backends.Delta_K6221_K2182_Simple_v7")
168184

169185
def test_06_delta_sensing(self):
170-
with patch('pyvisa.ResourceManager') as MockRM, \
171-
patch('time.sleep', side_effect=self.get_circuit_breaker(10)):
186+
with patch('pyvisa.ResourceManager') as MockRM:
187+
mock_sleep = patch('time.sleep', side_effect=self.get_circuit_breaker(10))
188+
mock_sleep.start()
189+
self.addCleanup(mock_sleep.stop)
190+
172191
inst = MockRM.return_value.open_resource.return_value
173192
inst.query.return_value = "+1.23E-5"
174193
inputs = ['10', '300', '10', 'test_file', 'y']
@@ -181,8 +200,11 @@ def test_06_delta_sensing(self):
181200
print(" [SKIP] Module not found, skipping.")
182201

183202
def test_07_lockin_backend(self):
184-
with patch('pyvisa.ResourceManager') as MockRM, \
185-
patch('time.sleep', side_effect=self.get_circuit_breaker(5)):
203+
with patch('pyvisa.ResourceManager') as MockRM:
204+
mock_sleep = patch('time.sleep', side_effect=self.get_circuit_breaker(5))
205+
mock_sleep.start()
206+
self.addCleanup(mock_sleep.stop)
207+
186208
spy = MockRM.return_value.open_resource.return_value
187209
spy.query.side_effect = [
188210
"SRS,SR830,s/n12345,ver1.07", # *IDN?
@@ -197,7 +219,9 @@ def test_08_combined_2400_2182(self):
197219
# We suspect input mismatch or resource opening hang.
198220
with patch('pyvisa.ResourceManager') as MockRM, \
199221
patch('pymeasure.instruments.keithley.Keithley2400'), \
200-
patch('time.sleep', side_effect=self.get_circuit_breaker(10)):
222+
patch('time.sleep', side_effect=self.get_circuit_breaker(10)) as mock_sleep:
223+
224+
self.addCleanup(mock_sleep.stop)
201225

202226
rm = MockRM.return_value
203227
k2182_spy = MagicMock()
@@ -214,16 +238,22 @@ def test_08_combined_2400_2182(self):
214238
"Keithley_2400_Keithley_2182.Backends.IV_K2400_K2182_Backend_v1")
215239

216240
def test_09_poling(self):
217-
with patch('pymeasure.instruments.keithley.Keithley6517B'), \
218-
patch('time.sleep', side_effect=self.get_circuit_breaker(5)):
241+
with patch('pymeasure.instruments.keithley.Keithley6517B'):
242+
mock_sleep = patch('time.sleep', side_effect=self.get_circuit_breaker(5))
243+
mock_sleep.start()
244+
self.addCleanup(mock_sleep.stop)
245+
219246
inputs = ['100', '10', 'y']
220247
with patch('builtins.input', side_effect=inputs):
221248
self.run_module_safely(
222249
"Keithley_6517B.Pyroelectricity.Backends.Poling_K6517B_Backend_v10")
223250

224251
def test_10_high_resistance(self):
225-
with patch('pymeasure.instruments.keithley.Keithley6517B') as Mock6517, \
226-
patch('time.sleep', side_effect=self.get_circuit_breaker(5)):
252+
with patch('pymeasure.instruments.keithley.Keithley6517B') as Mock6517:
253+
mock_sleep = patch('time.sleep', side_effect=self.get_circuit_breaker(5))
254+
mock_sleep.start()
255+
self.addCleanup(mock_sleep.stop)
256+
227257
spy = Mock6517.return_value
228258
spy.id = "Mocked Keithley 6517B"
229259
spy.resistance = 1.23e12 # Provide a mock resistance
@@ -249,8 +279,11 @@ def test_11_gpib_scanner(self):
249279
pass
250280

251281
def test_12_gpib_rescue(self):
252-
with patch('pyvisa.ResourceManager') as MockRM, \
253-
patch('time.sleep', side_effect=self.get_circuit_breaker(3)):
282+
with patch('pyvisa.ResourceManager') as MockRM:
283+
mock_sleep = patch('time.sleep', side_effect=self.get_circuit_breaker(3))
284+
mock_sleep.start()
285+
self.addCleanup(mock_sleep.stop)
286+
254287
rm = MockRM.return_value
255288
rm.list_resources.return_value = ('GPIB0::1::INSTR',)
256289
self.run_module_safely(

tests/test_fixes.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import unittest
2-
from unittest.mock import patch, MagicMock
2+
from unittest.mock import patch, MagicMock, mock_open
33

44
class TestFixes(unittest.TestCase):
5+
56
@patch('builtins.input', side_effect=['10', '2', 'test_output'])
6-
@patch('Keithley_2400.Backends.IV_K2400_Loop_Backend_v10.Keithley2400')
7+
@patch('pymeasure.instruments.keithley.Keithley2400')
78
@patch('matplotlib.pyplot.show')
89
@patch('pandas.DataFrame.to_csv')
910
def test_iv_k2400_fix(self, mock_to_csv, mock_plt_show, mock_keithley_class, mock_input):
11+
"""
12+
This test verifies that the Keithley2400 is correctly mocked in the
13+
IV_K2400_Loop_Backend_v10 script, preventing real hardware calls.
14+
"""
1015
from Keithley_2400.Backends import IV_K2400_Loop_Backend_v10 as iv_backend
1116
iv_backend.main()
1217
mock_keithley_class.assert_called_once()
@@ -16,7 +21,7 @@ def test_iv_k2400_fix(self, mock_to_csv, mock_plt_show, mock_keithley_class, moc
1621
@patch('builtins.input', side_effect=['10', '20', '5', '30'])
1722
@patch('Lakeshore_350_340.Backends.T_Control_L350_Simple_Backend_v10.Lakeshore350')
1823
@patch('matplotlib.pyplot.show')
19-
@patch('builtins.open', new_callable=unittest.mock.mock_open)
24+
@patch('builtins.open', new_callable=mock_open)
2025
@patch('time.sleep', MagicMock())
2126
@patch('time.time', side_effect=[1000, 1002, 1004, 1006, 1008, 1010])
2227
def test_t_control_l350_fix(self, mock_time, mock_open_file,
@@ -26,4 +31,4 @@ def test_t_control_l350_fix(self, mock_time, mock_open_file,
2631
mock_ls_class.assert_called_once()
2732

2833
if __name__ == '__main__':
29-
unittest.main()
34+
unittest.main()

tests/test_gui_iv_k2400.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import unittest
2+
from unittest.mock import patch, MagicMock
3+
import tkinter as tk
4+
5+
# Import the GUI class we want to test
6+
from Keithley_2400.IV_K2400_GUI_v5 import MeasurementAppGUI
7+
8+
class TestIVK2400GUI(unittest.TestCase):
9+
10+
def setUp(self):
11+
"""
12+
Set up a root Tk window and instantiate the GUI class.
13+
This runs before each test.
14+
"""
15+
# We need a root window for the GUI to be instantiated, but we don't need to see it.
16+
self.root = tk.Tk()
17+
self.root.withdraw() # Hide the window
18+
19+
def tearDown(self):
20+
"""
21+
Destroy the root window after each test.
22+
"""
23+
self.root.destroy()
24+
25+
@patch('Keithley_2400.IV_K2400_GUI_v5.Keithley2400_IV_Backend')
26+
def test_start_measurement_logic(self, MockBackend):
27+
"""
28+
Tests the core logic of the 'Start' button click.
29+
Verifies that parameters are read from the UI and passed to the backend correctly.
30+
"""
31+
# --- Setup ---
32+
# Instantiate the GUI. This also creates all the tk widgets.
33+
app = MeasurementAppGUI(self.root)
34+
35+
# Mock the backend instance that the GUI will create
36+
mock_backend_instance = MockBackend.return_value
37+
38+
# --- Simulate User Input ---
39+
# We directly set the values that would be entered into the GUI's Entry widgets.
40+
app.entries["Sample Name"].insert(0, "TestSample")
41+
app.entries["Max Current"].insert(0, "100") # 100 µA
42+
app.entries["Step Current"].insert(0, "10") # 10 µA
43+
app.entries["Compliance"].insert(0, "20") # 20 V
44+
app.entries["Delay"].insert(0, "0.5") # 0.5 s
45+
app.keithley_combobox.set("GPIB0::24::INSTR")
46+
app.file_location_path = "/fake/path" # Simulate browsing for a file
47+
48+
# --- Trigger the Action ---
49+
# Call the method that the "Start" button is connected to.
50+
app.start_measurement()
51+
52+
# --- Assertions ---
53+
# 1. Did the GUI try to connect and configure the backend?
54+
mock_backend_instance.connect_and_configure.assert_called_once()
55+
56+
# 2. Were the parameters from the UI passed correctly to the backend?
57+
# The `connect_and_configure` method is called with (visa_address, params_dict)
58+
call_args, _ = mock_backend_instance.connect_and_configure.call_args
59+
passed_params = call_args[1]
60+
61+
self.assertEqual(passed_params['compliance_v'], 20.0)
62+
# Max current is converted from µA to A
63+
self.assertAlmostEqual(passed_params['max_current'], 100e-6)
64+
65+
# 3. Did the GUI generate the correct sweep points?
66+
mock_backend_instance.generate_sweep_points.assert_called_once()

tests/test_iv_k2400_loop_backend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
class TestIVK2400LoopBackend(unittest.TestCase):
1010

1111
@patch('builtins.input', side_effect=['10', '2', 'test_output'])
12-
@patch('Keithley_2400.Backends.IV_K2400_Loop_Backend_v10.Keithley2400')
12+
@patch('pymeasure.instruments.keithley.Keithley2400')
1313
@patch('matplotlib.pyplot.show')
1414
@patch('pandas.DataFrame.to_csv')
1515
def test_main_full_run(self, mock_to_csv, mock_plt_show, mock_keithley_class, mock_input):
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import unittest
2+
from unittest.mock import patch, MagicMock, mock_open
3+
4+
# Import the main function from the script we want to test
5+
from Keithley_6517B.High_Resistance.Backends.IV_K6517B_Simple_Backend_v10 import main as iv_simple_main
6+
7+
class TestIVK6517BSimpleBackend(unittest.TestCase):
8+
9+
@patch('builtins.input', side_effect=['-10', '10', '5', '0.1', 'test_iv_simple.csv'])
10+
@patch('pymeasure.instruments.keithley.Keithley6517B')
11+
@patch('builtins.open', new_callable=mock_open)
12+
@patch('matplotlib.pyplot.show')
13+
def test_full_run(self, mock_show, mock_file, mock_keithley_class, mock_input):
14+
"""
15+
Tests a complete, successful run of the IV_K6517B_Simple_Backend script.
16+
"""
17+
# --- Setup Mocks ---
18+
mock_instrument = MagicMock()
19+
# Set a mock ID for the connection message
20+
mock_instrument.id = "Mocked Keithley 6517B"
21+
# Simulate resistance measurement
22+
mock_instrument.resistance = 1.23e9
23+
mock_keithley_class.return_value = mock_instrument
24+
25+
# --- Run the main function ---
26+
iv_simple_main()
27+
28+
# --- Assertions ---
29+
# 1. Was the instrument initialized correctly?
30+
mock_keithley_class.assert_called_once_with("GPIB1::27::INSTR")
31+
32+
# 2. Was the zero-check and correction sequence performed?
33+
mock_instrument.reset.assert_called_once()
34+
mock_instrument.measure_resistance.assert_called_once()
35+
mock_instrument.write.assert_any_call(':SYSTem:ZCHeck ON')
36+
mock_instrument.write.assert_any_call(':SYSTem:ZCORrect:ACQuire')
37+
mock_instrument.write.assert_any_call(':SYSTem:ZCHeck OFF')
38+
mock_instrument.write.assert_any_call(':SYSTem:ZCORrect ON')
39+
40+
# 3. Was the source configured and enabled?
41+
self.assertEqual(mock_instrument.current_nplc, 1)
42+
mock_instrument.enable_source.assert_called_once()
43+
44+
# 4. Was the voltage sweep performed correctly?
45+
# Based on inputs: start=-10, stop=10, steps=5 -> [-10., -5., 0., 5., 10.]
46+
self.assertEqual(mock_instrument.source_voltage_set.call_count, 5)
47+
mock_instrument.source_voltage_set.assert_any_call(-10.0)
48+
mock_instrument.source_voltage_set.assert_any_call(10.0)
49+
50+
# 5. Was the data file written to?
51+
mock_file.assert_called_with('test_iv_simple.csv', 'w', newline='')
52+
# Header (2) + Data (5)
53+
self.assertTrue(mock_file().write.call_count >= 7)
54+
55+
# 6. Was the instrument shut down safely?
56+
mock_instrument.shutdown.assert_called_once()
57+
58+
# 7. Was the plot shown?
59+
mock_show.assert_called_once()

tests/test_t_control_l350_backend.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from unittest.mock import patch, MagicMock, mock_open
33
import pyvisa
44

5-
65
from Lakeshore_350_340.Backends.T_Control_L350_Simple_Backend_v10 import (
76
Lakeshore350, get_user_parameters, main)
87

@@ -94,7 +93,7 @@ def test_get_user_parameters(self, mock_input):
9493
@patch('tkinter.Tk')
9594
@patch('tkinter.filedialog.asksaveasfilename', return_value='test.csv')
9695
@patch('builtins.input', side_effect=['10', '20', '5', '30'])
97-
@patch('Lakeshore_350_340.Backends.T_Control_L350_Simple_Backend_v10.Lakeshore350')
96+
@patch('Lakeshore_350_340.Backends.T_Control_L350_Simple_Backend_v10.Lakeshore350') # This patch is correct as it targets the class in the script's namespace
9897
@patch('matplotlib.pyplot.show')
9998
@patch('builtins.open', new_callable=mock_open)
10099
@patch('time.sleep', MagicMock())

0 commit comments

Comments
 (0)