panoptes / POCS

An Observatory Control System for the PANOPTES citizen-science project designed to help find transiting exoplanets! :telescope: :stars:
https://pocs.readthedocs.io/en/latest/
MIT License
80 stars 49 forks source link

Closes #1163 Running Tests on OSX/Windows #1164

Closed programatt closed 1 year ago

programatt commented 2 years ago

Update documentation for instructions on how to run tests locally on OSX. Fix conftest.py to not error when running pytest on OSX and Windows.

Description

When attempting to run pytest on my macbook, I ran into an issue on startup where the config_server throws a strange error.

W 01-20 03:15:11.281 Problem with get_config: ConnectionError(MaxRetryError("HTTPConnectionPool(host='localhost', port=6563): Max retries exceeded with url: /get-config (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x160521490>: Failed to establish a new connection: [Errno 61] Connection refused'))"))
I 01-20 03:15:11.285 Setting up the config server.
I 01-20 03:15:11.285 Starting panoptes-config-server with  config_file='tests/testing.yaml'
S 01-20 03:15:11.337 Config server Loaded 15 top-level items
S 01-20 03:15:11.337 {'name': 'Testing PANOPTES Unit', 'pan_id': 'PAN000', 'pocs': ordereddict([('INITIALIZED', False), ('CONNECTED', False), ('INTERRUPTED', False)]), 'location': ordereddict([('name', 'Mauna Loa Observatory'), ('latitude', <Quantity 19.54 deg>), ('longitude', <Quantity -155.58 deg>), ('elevation', <Quantity 3400. m>), ('horizon', <Quantity 30. deg>), ('flat_horizon', <Quantity -6. deg>), ('focus_horizon', <Quantity -12. deg>), ('observe_horizon', <Quantity -18. deg>), ('obstructions', []), ('timezone', 'US/Hawaii'), ('gmt_offset', -600)]), 'directories': ordereddict([('base', '/Users/matt/code/POCS'), ('images', '/Users/matt/code/POCS/images'), ('data', '/Users/matt/code/POCS/data'), ('resources', '/Users/matt/code/POCS/resources'), ('fields', '/Users/matt/code/POCS/conf_files/fields'), ('mounts', '/Users/matt/code/POCS/resources/mounts')]), 'db': ordereddict([('name', 'panoptes_testing'), ('type', 'file')]), 'scheduler': ordereddict([('type', 'panoptes.pocs.scheduler.dispatch'), ('fields_file', 'simulator.yaml'), ('check_file', False), ('iers_url', 'https://storage.googleapis.com/panoptes-resources/iers/ser7.dat'), ('constraints', [ordereddict([('name', 'panoptes.pocs.scheduler.constraint.Altitude')]), ordereddict([('name', 'panoptes.pocs.scheduler.constraint.MoonAvoidance'), ('options', ordereddict([('separation', 15)]))]), ordereddict([('name', 'panoptes.pocs.scheduler.constraint.Duration')])])]), 'mount': ordereddict([('brand', 'ioptron'), ('model', 'cem40'), ('driver', 'panoptes.pocs.mount.simulator'), ('serial', ordereddict([('port', '/dev/ttyUSB0'), ('timeout', 0.0), ('baudrate', 9600)])), ('non_sidereal_available', True)]), 'pointing': ordereddict([('auto_correct', True), ('threshold', 500), ('exptime', 30), ('max_iterations', 1)]), 'cameras': ordereddict([('defaults', ordereddict([('primary', 'None'), ('auto_detect', False), ('file_extension', 'fits'), ('compress_fits', True), ('make_pretty_images', True), ('keep_jpgs', False), ('readout_time', 0.5), ('timeout', 10), ('filter_type', 'RGGB'), ('cooling', ordereddict([('enabled', True), ('temperature', ordereddict([('target', 0), ('tolerance', 0.1)])), ('stable_time', 60), ('check_interval', 5), ('timeout', 300)])), ('focuser', ordereddict([('enabled', True), ('autofocus_seconds', 0.1), ('autofocus_size', 500), ('autofocus_keep_files', False)]))])), ('devices', [ordereddict([('model', 'panoptes.pocs.camera.simulator.dslr.Camera'), ('name', 'dslr.00'), ('port', '/dev/fake/dslr.00'), ('readout_time', 0.5)]), ordereddict([('model', 'panoptes.pocs.camera.simulator.dslr.Camera'), ('name', 'dslr.focuser.cooling.00'), ('port', '/dev/fake/dslr.focuser.cooling.00'), ('cooling', ordereddict([('enabled', True), ('target', 1), ('tolerance', 0.1), ('stable_time', 1), ('check_interval', 0.5), ('timeout', 2.5)])), ('focuser', ordereddict([('model', 'panoptes.pocs.focuser.simulator.Focuser'), ('focus_port', '/dev/fake/focuser.00'), ('initial_position', 20000), ('autofocus_range', [40, 80]), ('autofocus_step', [10, 20]), ('autofocus_seconds', 0.1), ('autofocus_size', 500), ('autofocus_keep_files', False)]))]), ordereddict([('model', 'panoptes.pocs.camera.simulator.dslr.Camera'), ('name', 'dslr.filterwheel.cooling.00'), ('port', '/dev/fake/dslr.filterwheel.cooling.00'), ('cooling', ordereddict([('enabled', True), ('target', 0), ('tolerance', 0.1), ('stable_time', 60), ('check_interval', 5), ('timeout', 300)])), ('filterwheel', ordereddict([('model', 'panoptes.pocs.filterwheel.simulator.FilterWheel'), ('filter_names', ['one', 'deux', 'drei', 'quattro', 'blank']), ('move_time', 0.1), ('timeout', 0.5)]))]), ordereddict([('model', 'panoptes.pocs.camera.simulator.ccd.Camera'), ('name', 'ccd.filterwheel.focuser.cooling.00'), ('serial_number', 'ccd.filterwheel.focuser.cooling.00'), ('cooling', ordereddict([('enabled', True), ('target', 0), ('tolerance', 0.1), ('stable_time', 60), ('check_interval', 5), ('timeout', 300)])), ('focuser', ordereddict([('model', 'panoptes.pocs.focuser.simulator.Focuser'), ('focus_port', '/dev/fake/focuser.00'), ('initial_position', 20000), ('autofocus_range', [40, 80]), ('autofocus_step', [10, 20]), ('autofocus_seconds', 0.1), ('autofocus_size', 500), ('autofocus_keep_files', False)])), ('filterwheel', ordereddict([('model', 'panoptes.pocs.filterwheel.simulator.FilterWheel'), ('filter_names', ['one', 'deux', 'drei', 'quattro', 'blank']), ('move_time', 0.1), ('timeout', 0.5), ('dark_position', 'blank'), ('focus_offsets', ordereddict([('one', 0), ('deux', 1), ('drei', 2), ('quattro', 3)]))]))])])]), 'observations': ordereddict([('make_timelapse', True), ('record_observations', True)]), 'panoptes_network': ordereddict([('image_storage', True), ('service_account_key', None), ('project_id', 'panoptes-survey'), ('buckets', ordereddict([('images', 'panoptes-survey')]))]), 'state_machine': 'panoptes', 'environment': ordereddict([('auto_detect', True)]), 'weather': ordereddict([('aag_cloud', ordereddict([('serial_port', '/dev/ttyUSB1')]))]), 'config_server': {'running': True}}
I 01-20 03:15:11.337 Config items saved to flask config-server
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "/Users/matt/code/venvs/pocs/lib/python3.9/site-packages/_pytest/main.py", line 265, in wrap_session
INTERNALERROR>     config._do_configure()
INTERNALERROR>   File "/Users/matt/code/venvs/pocs/lib/python3.9/site-packages/_pytest/config/__init__.py", line 982, in _do_configure
INTERNALERROR>     self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
INTERNALERROR>   File "/Users/matt/code/venvs/pocs/lib/python3.9/site-packages/pluggy/_hooks.py", line 277, in call_historic
INTERNALERROR>     res = self._hookexec(self.name, self.get_hookimpls(), kwargs, False)
INTERNALERROR>   File "/Users/matt/code/venvs/pocs/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>   File "/Users/matt/code/venvs/pocs/lib/python3.9/site-packages/pluggy/_callers.py", line 60, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "/Users/matt/code/venvs/pocs/lib/python3.9/site-packages/pluggy/_result.py", line 60, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "/Users/matt/code/venvs/pocs/lib/python3.9/site-packages/pluggy/_callers.py", line 39, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "/Users/matt/code/POCS/conftest.py", line 65, in pytest_configure
INTERNALERROR>     config_server(config_file, host=host, port=port, load_local=False, save_local=False)
INTERNALERROR>   File "/Users/matt/code/venvs/pocs/lib/python3.9/site-packages/panoptes/utils/config/server.py", line 117, in config_server
INTERNALERROR>     server_process.start()
INTERNALERROR>   File "/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/process.py", line 121, in start
INTERNALERROR>     self._popen = self._Popen(self)
INTERNALERROR>   File "/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/context.py", line 224, in _Popen
INTERNALERROR>     return _default_context.get_context().Process._Popen(process_obj)
INTERNALERROR>   File "/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/context.py", line 284, in _Popen
INTERNALERROR>     return Popen(process_obj)
INTERNALERROR>   File "/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/popen_spawn_posix.py", line 32, in __init__
INTERNALERROR>     super().__init__(process_obj)
INTERNALERROR>   File "/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/popen_fork.py", line 19, in __init__
INTERNALERROR>     self._launch(process_obj)
INTERNALERROR>   File "/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/popen_spawn_posix.py", line 47, in _launch
INTERNALERROR>     reduction.dump(process_obj, fp)
INTERNALERROR>   File "/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/reduction.py", line 60, in dump
INTERNALERROR>     ForkingPickler(file, protocol).dump(obj)
INTERNALERROR> AttributeError: Can't pickle local object 'config_server.<locals>.start_server'

Doing some searching it looks like python 3.8 on OSX changed to use spawn instead of fork for processes by default. See https://github.com/pytest-dev/pytest-flask/issues/104#issuecomment-577908228

So I modified conftest to detect the OS and change the behavior on windows and osx and the pytest command runs as expected.

Related Issue

1163

How Has This Been Tested?

The changes allowed pytest to run successfully on a mac

Screenshots (if appropriate):

n/a

Types of changes

Checklist:

codecov[bot] commented 2 years ago

Codecov Report

Merging #1164 (9953291) into develop (4908119) will decrease coverage by 4.76%. The diff coverage is 60.30%.

:exclamation: Current head 9953291 differs from pull request most recent head 402350b. Consider uploading reports for the commit 402350b to get more accurate results Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1164      +/-   ##
===========================================
- Coverage    83.08%   78.32%   -4.77%     
===========================================
  Files           86       89       +3     
  Lines         7425     7634     +209     
  Branches       635      717      +82     
===========================================
- Hits          6169     5979     -190     
- Misses        1083     1492     +409     
+ Partials       173      163      -10     
Impacted Files Coverage Δ
src/panoptes/pocs/camera/gphoto/remote.py 0.00% <0.00%> (-21.57%) :arrow_down:
...rc/panoptes/pocs/scheduler/observation/compound.py 0.00% <0.00%> (ø)
src/panoptes/pocs/sensor/power.py 40.23% <0.00%> (-1.74%) :arrow_down:
...rc/panoptes/pocs/state/states/default/analyzing.py 5.88% <0.00%> (-54.12%) :arrow_down:
src/panoptes/pocs/state/states/default/tracking.py 6.66% <0.00%> (-76.67%) :arrow_down:
src/panoptes/pocs/utils/cli/main.py 0.00% <0.00%> (-70.00%) :arrow_down:
src/panoptes/pocs/utils/logger.py 100.00% <ø> (ø)
src/panoptes/pocs/utils/service/power.py 0.00% <0.00%> (-66.67%) :arrow_down:
tests/test_observatory.py 96.28% <ø> (+0.66%) :arrow_up:
...rc/panoptes/pocs/state/states/default/observing.py 14.28% <16.66%> (-61.91%) :arrow_down:
... and 55 more

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 64107a4...402350b. Read the comment docs.

programatt commented 2 years ago

@wtgee anything else needed?

programatt commented 2 years ago

@wtgee looks like the mac tests are now failing with this error

___________ ERROR collecting src/panoptes/pocs/filterwheel/libefw.py ___________
src/panoptes/pocs/filterwheel/libefw.py:12: in <module>
    class EFWDriver(AbstractSDKDriver):
src/panoptes/pocs/filterwheel/libefw.py:15: in EFWDriver
    _libudev = load_c_library('udev', mode=ctypes.RTLD_GLOBAL)
/usr/local/lib/python3.9/site-packages/panoptes/utils/library.py:39: in load_c_library
    raise error.NotFound(f"Cound not find {name} library!")
E   panoptes.utils.error.NotFound: NotFound: Cound not find udev library!

Have you seen that before? I'm guessing theres a library that needs to be added to the mac gh action image.

wtgee commented 1 year ago

Better late than never?

Fixed this upstream at https://github.com/panoptes/panoptes-utils/pull/304