psychopy 2024.1.4__py3-none-any.whl → 2024.2.0__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.

Potentially problematic release.


This version of psychopy might be problematic. Click here for more details.

Files changed (325) hide show
  1. psychopy/.DS_Store +0 -0
  2. psychopy/CHANGELOG.txt +206 -0
  3. psychopy/GIT_SHA +1 -0
  4. psychopy/VERSION +1 -0
  5. psychopy/__init__.py +77 -15
  6. psychopy/app/Resources/classic/plugin16.png +0 -0
  7. psychopy/app/Resources/classic/plugin16@2x.png +0 -0
  8. psychopy/app/Resources/dark/plugin16.png +0 -0
  9. psychopy/app/Resources/dark/plugin16@2x.png +0 -0
  10. psychopy/app/Resources/light/plugin16.png +0 -0
  11. psychopy/app/Resources/light/plugin16@2x.png +0 -0
  12. psychopy/app/__init__.py +76 -2
  13. psychopy/app/_psychopyApp.py +126 -101
  14. psychopy/app/builder/builder.py +14 -10
  15. psychopy/app/builder/dialogs/__init__.py +8 -8
  16. psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
  17. psychopy/app/builder/dialogs/paramCtrls.py +24 -57
  18. psychopy/app/builder/validators.py +2 -2
  19. psychopy/app/coder/codeEditorBase.py +8 -8
  20. psychopy/app/coder/coder.py +4 -4
  21. psychopy/app/connections/sendusage.py +2 -2
  22. psychopy/app/connections/updates.py +9 -9
  23. psychopy/app/dialogs.py +34 -2
  24. psychopy/app/idle.py +31 -0
  25. psychopy/app/jobs.py +21 -3
  26. psychopy/app/linuxconfig/__init__.py +9 -0
  27. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  28. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
  29. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  30. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
  31. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
  32. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  33. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
  34. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  35. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1011 -942
  36. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
  37. psychopy/app/pavlovia_ui/_base.py +33 -3
  38. psychopy/app/pavlovia_ui/search.py +0 -1
  39. psychopy/app/plugin_manager/dialog.py +104 -51
  40. psychopy/app/plugin_manager/packages.py +5 -0
  41. psychopy/app/plugin_manager/plugins.py +145 -67
  42. psychopy/app/preferencesDlg.py +8 -8
  43. psychopy/app/psychopyApp.py +11 -5
  44. psychopy/app/ribbon.py +124 -14
  45. psychopy/app/runner/runner.py +6 -1
  46. psychopy/app/stdout/stdOutRich.py +27 -11
  47. psychopy/app/themes/icons.py +52 -2
  48. psychopy/assets/__init__.py +0 -0
  49. psychopy/assets/click.png +0 -0
  50. psychopy/assets/clicknext.png +0 -0
  51. psychopy/assets/next.png +0 -0
  52. psychopy/assets/psychopy.ico +0 -0
  53. psychopy/assets/psychopy.png +0 -0
  54. psychopy/assets/templates/__init__.py +0 -0
  55. psychopy/assets/touch.png +0 -0
  56. psychopy/assets/touchnext.png +0 -0
  57. psychopy/assets/window.ico +0 -0
  58. psychopy/changes/2023.1.0.md +9 -0
  59. psychopy/changes/2024.1.0.md +16 -0
  60. psychopy/changes/__init__.py +0 -0
  61. psychopy/clock.py +2 -2
  62. psychopy/colors.py +2 -1
  63. psychopy/compatibility.py +53 -1
  64. psychopy/contrib/.DS_Store +0 -0
  65. psychopy/contrib/configobj/__init__.py +10 -8
  66. psychopy/data/__init__.py +3 -2
  67. psychopy/data/base.py +5 -5
  68. psychopy/data/experiment.py +130 -4
  69. psychopy/data/routine.py +56 -0
  70. psychopy/data/staircase.py +2 -2
  71. psychopy/data/trial.py +559 -97
  72. psychopy/data/utils.py +56 -21
  73. psychopy/demos/.DS_Store +0 -0
  74. psychopy/demos/builder/.DS_Store +0 -0
  75. psychopy/demos/builder/Design Templates/.DS_Store +0 -0
  76. psychopy/demos/builder/Experiments/.DS_Store +0 -0
  77. psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
  78. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
  79. psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
  80. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
  81. psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
  82. psychopy/demos/builder/Hardware/.DS_Store +0 -0
  83. psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
  84. psychopy/demos/coder/.DS_Store +0 -0
  85. psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
  86. psychopy/demos/coder/iohub/.DS_Store +0 -0
  87. psychopy/demos/coder/misc/hdf5_2_csv +33 -0
  88. psychopy/event.py +30 -29
  89. psychopy/experiment/.DS_Store +0 -0
  90. psychopy/experiment/_experiment.py +6 -6
  91. psychopy/experiment/components/.DS_Store +0 -0
  92. psychopy/experiment/components/__init__.py +6 -3
  93. psychopy/experiment/components/_base.py +286 -131
  94. psychopy/experiment/components/aperture/.DS_Store +0 -0
  95. psychopy/experiment/components/brush/.DS_Store +0 -0
  96. psychopy/experiment/components/button/.DS_Store +0 -0
  97. psychopy/experiment/components/button/__init__.py +5 -1
  98. psychopy/experiment/components/buttonBox/.DS_Store +0 -0
  99. psychopy/experiment/components/camera/.DS_Store +0 -0
  100. psychopy/experiment/components/code/.DS_Store +0 -0
  101. psychopy/experiment/components/dots/.DS_Store +0 -0
  102. psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
  103. psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
  104. psychopy/experiment/components/form/.DS_Store +0 -0
  105. psychopy/experiment/components/form/__init__.py +6 -2
  106. psychopy/experiment/components/grating/.DS_Store +0 -0
  107. psychopy/experiment/components/grating/__init__.py +14 -3
  108. psychopy/experiment/components/image/.DS_Store +0 -0
  109. psychopy/experiment/components/image/__init__.py +14 -3
  110. psychopy/experiment/components/joyButtons/.DS_Store +0 -0
  111. psychopy/experiment/components/joystick/.DS_Store +0 -0
  112. psychopy/experiment/components/keyboard/.DS_Store +0 -0
  113. psychopy/experiment/components/keyboard/__init__.py +22 -10
  114. psychopy/experiment/components/microphone/.DS_Store +0 -0
  115. psychopy/experiment/components/microphone/__init__.py +59 -39
  116. psychopy/experiment/components/mouse/.DS_Store +0 -0
  117. psychopy/experiment/components/mouse/__init__.py +44 -29
  118. psychopy/experiment/components/movie/.DS_Store +0 -0
  119. psychopy/experiment/components/movie/__init__.py +1 -1
  120. psychopy/experiment/components/panorama/.DS_Store +0 -0
  121. psychopy/experiment/components/parallelOut/.DS_Store +0 -0
  122. psychopy/experiment/components/patch/.DS_Store +0 -0
  123. psychopy/experiment/components/polygon/.DS_Store +0 -0
  124. psychopy/experiment/components/polygon/__init__.py +26 -6
  125. psychopy/experiment/components/progress/.DS_Store +0 -0
  126. psychopy/experiment/components/ratingScale/.DS_Store +0 -0
  127. psychopy/experiment/components/resourceManager/.DS_Store +0 -0
  128. psychopy/experiment/components/roi/.DS_Store +0 -0
  129. psychopy/experiment/components/roi/__init__.py +5 -0
  130. psychopy/experiment/components/routineSettings/.DS_Store +0 -0
  131. psychopy/experiment/components/routineSettings/__init__.py +57 -10
  132. psychopy/experiment/components/serialOut/.DS_Store +0 -0
  133. psychopy/experiment/components/settings/.DS_Store +0 -0
  134. psychopy/experiment/components/settings/__init__.py +117 -42
  135. psychopy/experiment/components/slider/.DS_Store +0 -0
  136. psychopy/experiment/components/sound/.DS_Store +0 -0
  137. psychopy/experiment/components/sound/__init__.py +54 -19
  138. psychopy/experiment/components/static/.DS_Store +0 -0
  139. psychopy/experiment/components/static/__init__.py +1 -1
  140. psychopy/experiment/components/text/.DS_Store +0 -0
  141. psychopy/experiment/components/text/__init__.py +28 -3
  142. psychopy/experiment/components/textbox/.DS_Store +0 -0
  143. psychopy/experiment/components/textbox/__init__.py +12 -2
  144. psychopy/experiment/components/unknown/.DS_Store +0 -0
  145. psychopy/experiment/components/unknown/__init__.py +1 -2
  146. psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
  147. psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
  148. psychopy/experiment/components/variable/.DS_Store +0 -0
  149. psychopy/experiment/flow.py +11 -4
  150. psychopy/experiment/loops.py +85 -37
  151. psychopy/experiment/params.py +74 -32
  152. psychopy/experiment/py2js_transpiler.py +8 -1
  153. psychopy/experiment/routines/.DS_Store +0 -0
  154. psychopy/experiment/routines/_base.py +102 -22
  155. psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
  156. psychopy/experiment/routines/counterbalance/__init__.py +5 -1
  157. psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
  158. psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
  159. psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
  160. psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
  161. psychopy/experiment/routines/photodiodeValidator/__init__.py +6 -5
  162. psychopy/experiment/routines/unknown/.DS_Store +0 -0
  163. psychopy/gui/wxgui.py +4 -4
  164. psychopy/hardware/.DS_Store +0 -0
  165. psychopy/hardware/__init__.py +1 -1
  166. psychopy/hardware/base.py +12 -0
  167. psychopy/hardware/camera/__init__.py +1 -15
  168. psychopy/hardware/cedrus.py +10 -11
  169. psychopy/hardware/crs/colorcal.py +13 -22
  170. psychopy/hardware/crs/optical.py +10 -20
  171. psychopy/hardware/emulator.py +17 -14
  172. psychopy/hardware/eyetracker.py +42 -118
  173. psychopy/hardware/gammasci.py +4 -15
  174. psychopy/hardware/keyboard.py +102 -10
  175. psychopy/hardware/listener.py +3 -0
  176. psychopy/hardware/microphone.py +148 -18
  177. psychopy/hardware/minolta.py +8 -15
  178. psychopy/hardware/photodiode.py +191 -16
  179. psychopy/hardware/photometer/__init__.py +11 -19
  180. psychopy/hardware/pr.py +8 -15
  181. psychopy/hardware/speaker.py +39 -4
  182. psychopy/info.py +0 -71
  183. psychopy/iohub/.DS_Store +0 -0
  184. psychopy/iohub/__init__.py +1 -1
  185. psychopy/iohub/client/__init__.py +30 -20
  186. psychopy/iohub/client/keyboard.py +24 -24
  187. psychopy/iohub/datastore/__init__.py +2 -2
  188. psychopy/iohub/datastore/util.py +2 -2
  189. psychopy/iohub/default_config.yaml +1 -1
  190. psychopy/iohub/devices/.DS_Store +0 -0
  191. psychopy/iohub/devices/__init__.py +112 -25
  192. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  193. psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
  194. psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
  195. psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
  196. psychopy/iohub/devices/eyetracker/__init__.py +46 -0
  197. psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
  198. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
  199. psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
  200. psychopy/iohub/server.py +2 -2
  201. psychopy/iohub/start_iohub_process.py +3 -0
  202. psychopy/iohub/util/__init__.py +62 -70
  203. psychopy/layout.py +5 -5
  204. psychopy/logging.py +8 -1
  205. psychopy/microphone.py +10 -37
  206. psychopy/platform_specific/__init__.py +0 -2
  207. psychopy/platform_specific/darwin.py +1 -3
  208. psychopy/platform_specific/linux.py +31 -33
  209. psychopy/platform_specific/win32.py +38 -13
  210. psychopy/plugins/__init__.py +148 -116
  211. psychopy/plugins/util.py +39 -0
  212. psychopy/preferences/Darwin.spec +4 -2
  213. psychopy/preferences/FreeBSD.spec +4 -2
  214. psychopy/preferences/Linux.spec +4 -2
  215. psychopy/preferences/Windows.spec +4 -2
  216. psychopy/preferences/baseNoArch.spec +4 -2
  217. psychopy/preferences/preferences.py +47 -24
  218. psychopy/projects/pavlovia.py +47 -4
  219. psychopy/scripts/psyexpCompile.py +0 -4
  220. psychopy/session.py +153 -21
  221. psychopy/sound/__init__.py +31 -21
  222. psychopy/sound/_base.py +20 -3
  223. psychopy/sound/audioclip.py +320 -33
  224. psychopy/sound/backend_ptb.py +47 -58
  225. psychopy/sound/backend_pygame.py +1 -1
  226. psychopy/sound/backend_pysound.py +6 -15
  227. psychopy/sound/transcribe.py +53 -0
  228. psychopy/tests/.DS_Store +0 -0
  229. psychopy/tests/data/.DS_Store +0 -0
  230. psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
  231. psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
  232. psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
  233. psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
  234. psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
  235. psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
  236. psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
  237. psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
  238. psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
  239. psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
  240. psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
  241. psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
  242. psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
  243. psychopy/tests/data/correctScript/.DS_Store +0 -0
  244. psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
  245. psychopy/tests/data/test_session/.DS_Store +0 -0
  246. psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
  247. psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
  248. psychopy/tests/test_app/.DS_Store +0 -0
  249. psychopy/tests/test_app/conftest.py +2 -2
  250. psychopy/tests/test_app/test_speed.py +4 -1
  251. psychopy/tests/test_data/test_TrialHandler2.py +146 -1
  252. psychopy/tests/test_experiment/.DS_Store +0 -0
  253. psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
  254. psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
  255. psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
  256. psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
  257. psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
  258. psychopy/tests/test_experiment/test_py2js.py +1 -1
  259. psychopy/tests/test_hardware/test_keyboard.py +31 -0
  260. psychopy/tests/test_hardware/test_ports.py +1 -11
  261. psychopy/tests/test_liaison/test_Liaison.py +47 -0
  262. psychopy/tests/test_misc/test_core.py +5 -0
  263. psychopy/tests/test_session/test_Session.py +5 -1
  264. psychopy/tests/test_tools/test_versionchooser.py +39 -8
  265. psychopy/tests/test_visual/test_all_stimuli.py +0 -97
  266. psychopy/tests/test_visual/test_image.py +6 -5
  267. psychopy/tests/test_visual/test_textbox.py +36 -0
  268. psychopy/tests/utils.py +4 -0
  269. psychopy/tools/filetools.py +1 -1
  270. psychopy/tools/pkgtools.py +160 -137
  271. psychopy/tools/versionchooser.py +10 -10
  272. psychopy/tools/wizard.py +3 -3
  273. psychopy/visual/.DS_Store +0 -0
  274. psychopy/visual/backends/pygletbackend.py +24 -13
  275. psychopy/visual/basevisual.py +5 -11
  276. psychopy/visual/button.py +2 -14
  277. psychopy/visual/helpers.py +5 -5
  278. psychopy/visual/line.py +1 -2
  279. psychopy/visual/movie2.py +7 -816
  280. psychopy/visual/movie3.py +7 -589
  281. psychopy/visual/movies/__init__.py +8 -11
  282. psychopy/visual/movies/frame.py +5 -2
  283. psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
  284. psychopy/visual/noise.py +8 -7
  285. psychopy/visual/patch.py +7 -16
  286. psychopy/visual/radial.py +9 -7
  287. psychopy/visual/ratingscale.py +8 -1415
  288. psychopy/visual/secondorder.py +10 -9
  289. psychopy/visual/shape.py +7 -2
  290. psychopy/visual/text.py +1 -1
  291. psychopy/visual/textbox2/textbox2.py +28 -5
  292. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
  293. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/RECORD +307 -213
  294. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
  295. psychopy/app/Resources/click.png +0 -0
  296. psychopy/app/Resources/next.png +0 -0
  297. psychopy/experiment/components/patch/__init__.py +0 -121
  298. psychopy/experiment/components/patch/classic/patch.png +0 -0
  299. psychopy/experiment/components/patch/dark/patch.png +0 -0
  300. psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
  301. psychopy/experiment/components/patch/light/patch.png +0 -0
  302. psychopy/experiment/components/patch/light/patch@2x.png +0 -0
  303. psychopy/experiment/components/ratingScale/__init__.py +0 -337
  304. psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
  305. psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
  306. psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
  307. psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
  308. psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
  309. psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
  310. psychopy/platform_specific/posix.py +0 -16
  311. psychopy/tests/test_sound/test_microphone.py +0 -217
  312. psychopy/tests/test_visual/test_ratingScale.py +0 -299
  313. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
  314. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
  315. /psychopy/{app/Resources → assets}/USB-C.png +0 -0
  316. /psychopy/{app/Resources → assets}/USB.png +0 -0
  317. /psychopy/{app/Resources → assets}/creditCard.png +0 -0
  318. /psychopy/{app/Resources → assets}/default.mp3 +0 -0
  319. /psychopy/{app/Resources → assets}/default.mp4 +0 -0
  320. /psychopy/{app/Resources → assets}/default.png +0 -0
  321. /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
  322. /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
  323. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
  324. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
  325. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,6 +9,8 @@ from psychopy import experiment
9
9
  from psychopy.experiment.loops import TrialHandler
10
10
  from psychopy.experiment.components import BaseComponent
11
11
  from psychopy.experiment.exports import IndentingBuffer
12
+ from psychopy.constants import FOREVER
13
+ from psychopy.tests import utils
12
14
 
13
15
 
14
16
  def _find_global_resource_in_js_experiment(script, resource):
@@ -147,6 +149,62 @@ class BaseComponentTests:
147
149
  assert buff.indentLevel == 0, errMsg.format(
148
150
  "experiment end", buff.indentLevel
149
151
  )
152
+
153
+ def test_blank_timing(self):
154
+ """
155
+ Check that this Component can handle blank start/stop values.
156
+ """
157
+ # make minimal experiment just for this test
158
+ comp, rt, exp = self.make_minimal_experiment()
159
+ # skip if Component doesn't have the relevant params (e.g. Code Component)
160
+ for key in ("startVal", "startType", "stopVal", "stopType"):
161
+ if key not in comp.params:
162
+ pytest.skip()
163
+ # StaticComponent has entirely bespoke start/stop tests so skip it here
164
+ if type(comp).__name__ == "StaticComponent":
165
+ pytest.skip()
166
+ # make sure start and stop are as times
167
+ comp.params['startType'].val = "time (s)"
168
+ comp.params['stopType'].val = "duration (s)"
169
+ # define cases and expected start/dur
170
+ cases = [
171
+ # blank start
172
+ {'startVal': "", 'stopVal': "1", 'startTime': None, 'duration': 1},
173
+ # blank stop
174
+ {'startVal': "0", 'stopVal': "", 'startTime': 0, 'duration': FOREVER},
175
+ # blank both
176
+ {'startVal': "", 'stopVal': "", 'startTime': None, 'duration': FOREVER},
177
+ ]
178
+ # run all cases
179
+ for case in cases:
180
+ # apply values from case
181
+ comp.params['startVal'].val = case['startVal']
182
+ comp.params['stopVal'].val = case['stopVal']
183
+ # get values from start and duration method
184
+ startTime, duration, nonSlipSafe = comp.getStartAndDuration()
185
+ # check against expected
186
+ assert startTime == case['startTime']
187
+ assert duration == case['duration']
188
+ # check that it's never non-slip safe
189
+ assert not nonSlipSafe
190
+ # temp file to save scripts to for flake
191
+ import tempfile
192
+ file = Path(tempfile.gettempdir()) / "test_blank_timing_script.py"
193
+ # write the script
194
+ script = exp.writeScript(target="PsychoPy")
195
+ file.write_text(script, encoding="utf-8")
196
+ # check for syntax errors
197
+ try:
198
+ compile(script, str(file), "exec")
199
+ except Exception as err:
200
+ # save script
201
+ case['fail'] = Path(utils.TESTS_FAILS_PATH) / "test_blank_timing_script.py"
202
+ case['fail'].write_text(script, encoding="utf-8")
203
+ # raise error
204
+ raise AssertionError(
205
+ "Syntax error in compiled Builder code when startVal was '%(startVal)s' and "
206
+ "stopVal was '%(stopVal)s'. Failed script saved in '%(fail)s'" % case
207
+ )
150
208
 
151
209
  def test_disabled_default_val(self):
152
210
  """
@@ -67,7 +67,7 @@ class TestTranspiler:
67
67
  {'py': "a.upper()", 'js': "a.toUpperCase();\n"},
68
68
  {'py': "a.extend([4, 5, 6])", 'js': "a.concat([4, 5, 6]);\n"},
69
69
  {'py': "a.pop(0)", 'js': "a.splice(0, 1);\n"},
70
- {'py': "a.pop()", 'js': "a.splice((- 1), 1);\n"},
70
+ {'py': "a.pop()", 'js': "a.splice((a.length - 1), 1);\n"},
71
71
  ]
72
72
  # Try each case
73
73
  for case in cases:
@@ -3,6 +3,7 @@ from psychopy.hardware import keyboard
3
3
  import pytest
4
4
  import time
5
5
  from psychopy import logging
6
+ from psychopy.tests.utils import RUNNING_IN_VM
6
7
 
7
8
 
8
9
  class _TestBaseKeyboard:
@@ -26,6 +27,33 @@ class _TestBaseKeyboard:
26
27
  assert keys[-1] is evt
27
28
  assert keys[-1].value == case['val']
28
29
 
30
+ def testAcceptDuplicateResponses(self):
31
+ """
32
+ Test that KeyboardDevice can receive multiple presses of the same key without accepting
33
+ genuine duplicates (e.g. KeyPress objects added twice, or the same object added for press
34
+ and release)
35
+ """
36
+ # clear
37
+ self.kb.clearEvents()
38
+ # press space twice and don't release
39
+ resp1 = self.kb.makeResponse(tDown=0.1, code="space")
40
+ resp2 = self.kb.makeResponse(tDown=0.2, code="space")
41
+ # make sure we only have 2 press objects
42
+ keys = self.kb.getKeys(waitRelease=False, clear=False)
43
+ assert len(keys) == 2
44
+ # simulate a release
45
+ resp1.duration = 0.2
46
+ resp2.duration = 0.1
47
+ self.kb.responses += [resp1, resp2]
48
+ # we should still only have 2 press objects (as these are duplicates)
49
+ keys = self.kb.getKeys(waitRelease=True, clear=False)
50
+ assert len(keys) == 2
51
+ # add the same objects again for no good reason
52
+ self.kb.responses += [resp1, resp2]
53
+ # we should STILL only have 2 press objects
54
+ keys = self.kb.getKeys(waitRelease=True, clear=False)
55
+ assert len(keys) == 2
56
+
29
57
  def testMuteOutsidePsychopyNotSlower(self):
30
58
  """
31
59
  Test that responses aren't worryingly slower when using muteOutsidePsychopy
@@ -33,6 +61,9 @@ class _TestBaseKeyboard:
33
61
  # skip this test on Linux (as MOP *is* slower due to having to use subprocess)
34
62
  if sys.platform == "linux":
35
63
  pytest.skip()
64
+ # skip speed tests under vm
65
+ if RUNNING_IN_VM:
66
+ pytest.skip()
36
67
 
37
68
  # array to store times
38
69
  times = {}
@@ -1,3 +1,4 @@
1
+
1
2
  import psychopy.hardware as hw
2
3
  import pytest
3
4
  try:
@@ -109,17 +110,6 @@ def test_getCRSPhotometers():
109
110
  photoms = list(hw.getAllPhotometers())
110
111
  assert faked in photoms
111
112
 
112
- def test_getPhotometers():
113
- photoms = hw.getAllPhotometers()
114
-
115
- # Always iterable
116
- assert isinstance(photoms, Iterable)
117
-
118
- photoms = list(photoms)
119
-
120
- assert len(photoms) > 0
121
-
122
-
123
113
  # I wish our PR650 would behave like this ;-)
124
114
  _MockPhotometer = type("MockPhotometer",(),{"OK": True,"type": "MockPhotometer"})
125
115
 
@@ -1,3 +1,4 @@
1
+ import threading
1
2
  from psychopy import liaison, session, hardware
2
3
  from psychopy.hardware import DeviceManager
3
4
  from psychopy.tests import utils, skip_under_vm
@@ -88,6 +89,52 @@ class TestLiaison:
88
89
  self.server, self.protocol, "session", "runExperiment",
89
90
  "exp1"
90
91
  )
92
+
93
+ def test_future_trials(self):
94
+ # add experiment
95
+ runInLiaison(
96
+ self.server, self.protocol, "session", "addExperiment",
97
+ "testFutureTrials/testFutureTrials.psyexp", "testFutureTrials"
98
+ )
99
+ time.sleep(1)
100
+ # define a threaded task to run alongside experiment
101
+ def _thread():
102
+ # wait for first meaningful result
103
+ resp = None
104
+ i = 0
105
+ while resp is None and i < 24:
106
+ # get future trial
107
+ runInLiaison(
108
+ self.server, self.protocol, "session", "getFutureTrial",
109
+ "1", "True"
110
+ )
111
+ # get result
112
+ resp = json.loads(self.protocol.messages[-1]["result"])
113
+ # wait 0.1s
114
+ time.sleep(0.1)
115
+ # iterate towards limit
116
+ i += 1
117
+ # if we hit iteration limit, fail
118
+ assert i < 24, "Timed out waiting for a non-None result from getFutureTrial"
119
+ # does response have all the keys we expect?
120
+ expectedKeys = (
121
+ "type", "thisN", "thisRepN", "thisTrialN", "thisIndex", "data"
122
+ )
123
+ keysPresent = [key in resp for key in expectedKeys]
124
+ assert all(keysPresent), "Trial object missing key(s): {}".format(
125
+ expectedKeys[i] for i, val in enumerate(keysPresent) if not val
126
+ )
127
+ # resp should have type "trial_data"
128
+ assert resp['type'] == "trial_data", (
129
+ f"First non-None result from getFutureTrial doesn't look like a Trial object: {resp}"
130
+ )
131
+ # start thread
132
+ threading.Thread(target=_thread).start()
133
+ # run experiment
134
+ runInLiaison(
135
+ self.server, self.protocol, "session", "runExperiment",
136
+ "testFutureTrials"
137
+ )
91
138
 
92
139
  def test_experiment_error(self):
93
140
  """
@@ -25,6 +25,7 @@ import pytest
25
25
 
26
26
  import psychopy
27
27
  import psychopy.logging as logging
28
+ from psychopy.tests.utils import RUNNING_IN_VM
28
29
  from psychopy.visual import Window
29
30
  from psychopy.core import (getTime, MonotonicClock, Clock, CountdownTimer, wait,
30
31
  StaticPeriod, shellCall)
@@ -346,6 +347,10 @@ def test_LoggingDefaultClock():
346
347
 
347
348
  @pytest.mark.staticperiod
348
349
  def test_StaticPeriod():
350
+ # this test is speed sensitive, so skip under VM
351
+ if RUNNING_IN_VM:
352
+ pytest.skip()
353
+
349
354
  static = StaticPeriod()
350
355
  static.start(0.1)
351
356
  wait(0.05)
@@ -22,7 +22,8 @@ class TestSession:
22
22
  'exp2': "exp2/exp2.psyexp",
23
23
  'testCtrls': "testCtrls/testCtrls.psyexp",
24
24
  'error': "error/error.psyexp",
25
- 'annotation': "annotation/annotation.psyexp"
25
+ 'annotation': "annotation/annotation.psyexp",
26
+ 'trialNav': "testTrialNav/trialNav.psyexp"
26
27
  }
27
28
  )
28
29
  # setup devices
@@ -45,6 +46,9 @@ class TestSession:
45
46
  self.sess.runExperiment("exp2")
46
47
  self.sess.runExperiment("exp1")
47
48
 
49
+ def test_trial_navigation(self):
50
+ self.sess.runExperiment("trialNav")
51
+
48
52
  def test_ctrls(self):
49
53
  """
50
54
  Check that experiments check Session often enough for pause/resume commands sent asynchronously will still work.
@@ -2,6 +2,9 @@
2
2
  """Tests for psychopy.tools.versionchooser"""
3
3
  import os
4
4
  import sys
5
+ import unittest
6
+ import subprocess
7
+ import shutil
5
8
  from pathlib import Path
6
9
 
7
10
  import psychopy
@@ -168,11 +171,39 @@ class TestVersionRange:
168
171
  )
169
172
 
170
173
 
171
- """
172
-
173
- TODO: Tests to write:
174
-
175
- * Fail if git isn't there
176
- * Fail if git can't download repo
177
-
178
- """
174
+ class TestGitInstallation(unittest.TestCase):
175
+ def test_git_installed(self):
176
+ # Test if Git is installed on this system.
177
+ try:
178
+ # Attempt to get the Git version
179
+ result = subprocess.run(["git", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
180
+ # Check if the command was successful
181
+ self.assertTrue(result.returncode == 0, "Git is not installed or not in the PATH.")
182
+ except subprocess.CalledProcessError:
183
+ # If an error occurs, the test should fail
184
+ self.fail("Git is not installed or not in the PATH.")
185
+
186
+
187
+ class TestGitClone(unittest.TestCase):
188
+ def test_git_can_clone_repo(self):
189
+ # Test that Git can clone a repository
190
+ repo_url = "https://github.com/git/git" # Using a reliable repo that is always available
191
+ target_dir = "temp_repo"
192
+
193
+ try:
194
+ # Ensure the target directory does not exist before cloning
195
+ if os.path.exists(target_dir):
196
+ shutil.rmtree(target_dir)
197
+
198
+ # Run 'git clone' and capture output
199
+ subprocess.run(['git', 'clone', repo_url, target_dir], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
200
+ except subprocess.CalledProcessError as e:
201
+ # If Git clone fails for any reason
202
+ self.fail(f"Git clone command failed: {e}")
203
+ except FileNotFoundError:
204
+ # If the 'git' command is not found
205
+ self.fail("Git is not installed on this system.")
206
+ finally:
207
+ # Clean up by removing the cloned directory if it exists
208
+ if os.path.exists(target_dir):
209
+ shutil.rmtree(target_dir)
@@ -100,21 +100,6 @@ class _baseVisualTest():
100
100
  #make sure we start with a clean window
101
101
  self.win.flip()
102
102
 
103
- def test_auto_draw(self):
104
- win = self.win
105
- stims=[]
106
- stims.append(visual.PatchStim(win))
107
- stims.append(visual.ShapeStim(win))
108
- stims.append(visual.TextStim(win))
109
- for stim in stims:
110
- assert stim.status==constants.NOT_STARTED
111
- stim.autoDraw = True
112
- assert stim.status==constants.STARTED
113
- stim.autoDraw = False
114
- assert stim.status==constants.FINISHED
115
- assert stim.status==constants.STOPPED
116
- "{}".format(stim) #check that str(xxx) is working
117
-
118
103
  def test_imageAndGauss(self):
119
104
  win = self.win
120
105
  fileName = os.path.join(utils.TESTS_DATA_PATH, 'testimage.jpg')
@@ -219,47 +204,6 @@ class _baseVisualTest():
219
204
  utils.compareScreenshot('circleHex_%s.png' %(self.contextName), win)
220
205
  win.flip()
221
206
 
222
-
223
- def test_gabor(self):
224
- win = self.win
225
- #using init
226
- gabor = visual.PatchStim(win, mask='gauss', ori=-45,
227
- pos=[0.6 * self.scaleFactor, -0.6 * self.scaleFactor],
228
- sf=2.0 / self.scaleFactor, size=2 * self.scaleFactor,
229
- interpolate=True)
230
- gabor.draw()
231
- utils.compareScreenshot('gabor1_%s.png' %(self.contextName), win)
232
- win.flip()#AFTER compare screenshot
233
-
234
- #using .set()
235
- gabor.ori = 45
236
- gabor.size -= 0.2 * self.scaleFactor
237
- gabor.setColor([45, 30, 0.3], colorSpace='dkl')
238
- gabor.sf += 0.2 / self.scaleFactor
239
- gabor.pos += [-0.5*self.scaleFactor, 0.5*self.scaleFactor]
240
- gabor.contrast = 0.8
241
- gabor.opacity = 0.8
242
- gabor.draw()
243
- utils.compareScreenshot('gabor2_%s.png' %(self.contextName), win)
244
- win.flip()
245
- "{}".format(gabor) #check that str(xxx) is working
246
-
247
- @pytest.mark.bufferimage
248
- def test_bufferImage(self):
249
- """BufferImage inherits from ImageStim, so test .ori. .pos etc there not here
250
- """
251
- win = self.win
252
- gabor = visual.PatchStim(win, mask='gauss', ori=-45,
253
- pos=[0.6*self.scaleFactor, -0.6*self.scaleFactor],
254
- sf=2.0/self.scaleFactor, size=2*self.scaleFactor,
255
- interpolate=True)
256
-
257
- bufferImgStim = visual.BufferImageStim(self.win, stim=[gabor],
258
- interpolate=True)
259
- bufferImgStim.draw()
260
- utils.compareScreenshot('bufferimg_gabor_%s.png' %(self.contextName), win, crit=8)
261
- win.flip()
262
-
263
207
  #def testMaskMatrix(self):
264
208
  # #aims to draw the exact same stimulus as in testGabor, but using filters
265
209
  # win=self.win
@@ -334,33 +278,6 @@ class _baseVisualTest():
334
278
  utils.compareScreenshot('blend_add_%s.png' %self.contextName,
335
279
  win, crit=20)
336
280
 
337
- def test_mov(self):
338
- win = self.win
339
- if self.win.winType == 'pygame':
340
- pytest.skip("movies only available for pyglet backend")
341
-
342
- win.flip()
343
- #construct full path to the movie file
344
- fileName = os.path.join(utils.TESTS_DATA_PATH, 'testMovie.mp4')
345
- #check if present
346
- if not os.path.isfile(fileName):
347
- raise IOError('Could not find movie file: %s'
348
- % os.path.abspath(fileName))
349
- #then do actual drawing
350
- pos = [0.6*self.scaleFactor, -0.6*self.scaleFactor]
351
- mov = visual.MovieStim3(win, fileName, pos=pos, noAudio=True)
352
- mov.setFlipVert(True)
353
- mov.setFlipHoriz(True)
354
- threshold = 30
355
- for frameN in range(10):
356
- mov.draw()
357
-
358
- if frameN==0:
359
- utils.compareScreenshot('movFrame1_%s.png' %self.contextName,
360
- win, crit=threshold)
361
- win.flip()
362
- "{}".format(mov) #check that str(xxx) is working
363
-
364
281
  def test_rect(self):
365
282
  win = self.win
366
283
  rect = visual.Rect(win)
@@ -550,20 +467,6 @@ class _baseVisualTest():
550
467
  win, crit=30)
551
468
  #aperture should automatically disable on exit
552
469
 
553
- def test_rating_scale(self):
554
- if self.win.winType=='pygame':
555
- pytest.skip("RatingScale not available on pygame")
556
- # try to avoid text; avoid default / 'triangle' because it does not display on win XP
557
- win = self.win
558
- win.flip()
559
- rs = visual.RatingScale(win, low=0, high=1, precision=100, size=3, pos=(0,-.4),
560
- labels=[' ', ' '], scale=' ',
561
- marker='glow', markerStart=0.7, markerColor='darkBlue', autoLog=False)
562
- "{}".format(rs) #check that str(xxx) is working
563
- rs.draw()
564
- utils.compareScreenshot('ratingscale1_%s.png' %(self.contextName), win, crit=40.0)
565
- win.flip()#AFTER compare screenshot
566
-
567
470
  @skip_under_vm
568
471
  def test_refresh_rate(self):
569
472
  if self.win.winType=='pygame':
@@ -3,10 +3,8 @@ from pathlib import Path
3
3
  from psychopy import visual, colors, core
4
4
  from .test_basevisual import _TestUnitsMixin
5
5
  from psychopy.tests.test_experiment.test_component_compile_python import _TestBoilerplateMixin
6
- from .. import utils
7
-
8
- from ..utils import TESTS_DATA_PATH
9
-
6
+ from psychopy.tests import utils
7
+ import pytest
10
8
 
11
9
  class TestImage(_TestUnitsMixin, _TestBoilerplateMixin):
12
10
  """
@@ -17,7 +15,7 @@ class TestImage(_TestUnitsMixin, _TestBoilerplateMixin):
17
15
  self.win = visual.Window()
18
16
  self.obj = visual.ImageStim(
19
17
  self.win,
20
- str(Path(TESTS_DATA_PATH) / 'testimage.jpg'),
18
+ str(Path(utils.TESTS_DATA_PATH) / 'testimage.jpg'),
21
19
  colorSpace='rgb1',
22
20
  )
23
21
 
@@ -187,6 +185,9 @@ class TestImageAnimation:
187
185
  """
188
186
  Check that images can be updated sufficiently fast to create frame animations
189
187
  """
188
+ # skip speed tests under vm
189
+ if utils.RUNNING_IN_VM:
190
+ pytest.skip()
190
191
  # Create clock
191
192
  clock = core.Clock()
192
193
  # Try at each size
@@ -102,6 +102,42 @@ class Test_textbox(_TestColorMixin, _TestUnitsMixin, _TestBoilerplateMixin):
102
102
  #self.win.getMovieFrame(buffer='back').save(Path(utils.TESTS_DATA_PATH) / filename)
103
103
  utils.compareScreenshot(Path(utils.TESTS_DATA_PATH) / filename, self.win, crit=20)
104
104
 
105
+ def test_ori(self):
106
+ # setup textbox
107
+ self.textbox.color = "black"
108
+ self.textbox.fillColor = "white"
109
+ self.textbox.units = "pix"
110
+ self.textbox.size = (100, 50)
111
+ self.textbox.pos = (0, 0)
112
+ self.textbox.letterHeight = 5
113
+ # define params to use
114
+ orientations = [
115
+ 0, 120, 180, 240,
116
+ ]
117
+ anchors = [
118
+ "top left", "center", "bottom right",
119
+ ]
120
+ # try each combination
121
+ for ori in orientations:
122
+ for anchor in anchors:
123
+ # flip
124
+ self.win.flip()
125
+ # set params
126
+ self.textbox.ori = ori
127
+ self.textbox.anchor = anchor
128
+ self.textbox._layout()
129
+ # draw
130
+ self.textbox.draw()
131
+ # construct exemplar filename
132
+ exemplar = f"test_ori_{ori}_{anchor}.png"
133
+ # check/make exemplar
134
+ # self.win.getMovieFrame(buffer='back').save(
135
+ # Path(utils.TESTS_DATA_PATH) / "Test_textbox" / exemplar
136
+ # )
137
+ utils.compareScreenshot(
138
+ Path(utils.TESTS_DATA_PATH) / "Test_textbox" / exemplar, self.win, crit=20
139
+ )
140
+
105
141
  def test_colors(self):
106
142
  # Do base tests
107
143
  _TestColorMixin.test_colors(self)
psychopy/tests/utils.py CHANGED
@@ -6,6 +6,7 @@ import shutil
6
6
  import numpy as np
7
7
  import io
8
8
  from psychopy import logging, colors
9
+ from psychopy.tools import systemtools
9
10
 
10
11
  try:
11
12
  from PIL import Image
@@ -14,6 +15,9 @@ except ImportError:
14
15
 
15
16
  import pytest
16
17
 
18
+ # boolean indicating whether tests are running in a VM
19
+ RUNNING_IN_VM = systemtools.isVM_CI() is not None
20
+
17
21
  # define the path where to find testing data
18
22
  # so tests could be ran from any location
19
23
  TESTS_PATH = abspath(dirname(__file__))
@@ -74,7 +74,7 @@ def _synonymiseExtensions(assets):
74
74
 
75
75
 
76
76
  # Names accepted by stimulus classes & the filename of the default stimulus to use
77
- defaultStimRoot = Path(__file__).parent.parent / "app" / "Resources"
77
+ defaultStimRoot = Path(__file__).parent.parent / "assets"
78
78
  defaultStim = {
79
79
  # Image stimuli
80
80
  "default.png": "default.png",