Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove most use of literal lists of simulators. #200

Merged
merged 3 commits into from
Dec 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 2 additions & 5 deletions pocs/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sys

from pocs import hardware
from pocs import __version__
from pocs.utils import config
from pocs.utils.database import PanMongo
Expand Down Expand Up @@ -37,11 +38,7 @@ def __init__(self, *args, **kwargs):
self.logger = get_root_logger()
self.logger.info('{:*^80}'.format(' Starting POCS '))

if 'simulator' in kwargs:
if 'all' in kwargs['simulator']:
self.config['simulator'] = ['camera', 'mount', 'weather', 'night']
else:
self.config['simulator'] = kwargs['simulator']
self.config['simulator'] = hardware.get_simulator_names(config=self.config, kwargs=kwargs)

# Set up connection to database
db = kwargs.get('db', self.config['db']['name'])
Expand Down
59 changes: 59 additions & 0 deletions pocs/hardware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Information about hardware supported by Panoptes."""

ALL_NAMES = sorted(['camera', 'dome', 'mount', 'night', 'weather'])


def get_all_names(all_names=ALL_NAMES, without=list()):
"""Returns the names of all the categories of hardware that POCS supports.

Note that this doesn't extend to the Arduinos for the telemetry and camera boards, for
which no simulation is supported at this time.
"""
return [v for v in all_names if v not in without]


def get_simulator_names(simulator=None, kwargs=None, config=None):
"""Returns the names of the simulators to be used in lieu of hardware drivers.

Note that returning a list containing 'X' doesn't mean that the config calls for a driver
of type 'X'; that is up to the code working with the config to create drivers for real or
simulated hardware.

This funciton is intended to be called from PanBase or similar, which receives kwargs that
may include simulator, config or both. For example:
get_simulator_names(config=self.config, kwargs=kwargs)
Or:
get_simulator_names(simulator=simulator, config=self.config)

The reason this function doesn't just take **kwargs as its sole arg is that we need to allow
for the case where the caller is passing in simulator (or config) twice, once on its own,
and once in the kwargs (which won't be examined). Python doesn't permit a keyword argument
to be passed in twice.

Args:
simulator:
An explicit list of names of hardware to be simulated (i.e. hardware drivers
to be replaced with simulators).
kwargs:
The kwargs passed in to the caller, which is inspected for an arg called 'simulator'.
config:
Dictionary created from pocs.yaml or similar.

Returns:
List of names of the hardware to be simulated.
"""
empty = dict()

def extract_simulator(d):
return (d or empty).get('simulator')

for v in [simulator, extract_simulator(kwargs), extract_simulator(config)]:
if not v:
continue
if isinstance(v, str):
v = [v]
if 'all' in v:
return get_all_names()
else:
return sorted(v)
return []
44 changes: 17 additions & 27 deletions pocs/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import pytest

from pocs import hardware
from pocs.utils.config import load_config
from pocs.utils.database import PanMongo

Expand All @@ -16,42 +17,31 @@ def pytest_addoption(parser):
def pytest_collection_modifyitems(config, items):
""" Modify tests to skip or not based on cli options

Certain tests should only be run when the appropriate hardware is attached.
These tests should be marked as follows:
Certain tests should only be run when the appropriate hardware is attached. The names of the
types of hardware are in hardware.py, but include 'mount' and 'camera'. For a test that
requires a mount, for example, the test should be marked as follows:

`@pytest.mark.with_camera`: Run tests with camera attached.
`@pytest.mark.with_mount`: Run tests with mount attached.
`@pytest.mark.with_weather`: Run tests with weather attached.

And the same applies for the names of other types of hardware.

Note:
We are marking which tests to skip rather than which tests to include
so the logic is opposite of the options.
"""

hardware_list = config.getoption('--with-hardware')

if 'all' in hardware_list:
has_camera = True
has_mount = True
has_weather = True
else:
has_camera = 'camera' in hardware_list
has_mount = 'mount' in hardware_list
has_weather = 'weather' in hardware_list

skip_camera = pytest.mark.skip(reason="need --with-hardware=camera option to run")
skip_mount = pytest.mark.skip(reason="need --with-hardware=mount option to run")
skip_weather = pytest.mark.skip(reason="need --with-hardware=weather option to run")

for marker in items:
if "with_camera" in marker.keywords and not has_camera:
marker.add_marker(skip_camera)

if "with_mount" in marker.keywords and not has_mount:
marker.add_marker(skip_mount)

if "with_weather" in marker.keywords and not has_weather:
marker.add_marker(skip_weather)
for name in hardware.get_all_names():
# Do we have hardware called name?
if name in hardware_list:
# Yes, so don't need to skip tests with keyword "with_name".
continue
# No, so find all the tests that need this type of hardware and mark them to be skipped.
skip = pytest.mark.skip(reason="need --with-hardware={} option to run".format(name))
keyword = 'with_' + name
for item in items:
if keyword in item.keywords:
item.add_marker(skip)


@pytest.fixture
Expand Down
13 changes: 7 additions & 6 deletions pocs/tests/test_observatory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from astropy import units as u
from astropy.time import Time

from pocs import hardware
import pocs.version
from pocs.observatory import Observatory
from pocs.scheduler.dispatch import Scheduler
Expand All @@ -18,7 +19,7 @@ def simulator():
Tests that require real hardware should be marked with the appropriate
fixtue (see `conftest.py`)
"""
return ['camera', 'mount', 'weather']
return hardware.get_all_names(without=['night'])


@pytest.fixture
Expand Down Expand Up @@ -49,7 +50,7 @@ def test_bad_site(simulator, config):

def test_bad_mount(config):
conf = config.copy()
simulator = ['weather', 'camera', 'night']
simulator = hardware.get_all_names(without=['mount'])
conf['mount']['port'] = '/dev/'
conf['mount']['driver'] = 'foobar'
with pytest.raises(error.NotFound):
Expand All @@ -74,22 +75,22 @@ def test_bad_scheduler_fields_file(config):

def test_bad_camera(config):
conf = config.copy()
simulator = ['weather', 'mount', 'night']
simulator = hardware.get_all_names(without=['camera'])
with pytest.raises(error.PanError):
Observatory(simulator=simulator, config=conf, auto_detect=True, ignore_local_config=True)


def test_camera_not_found(config):
conf = config.copy()
simulator = ['weather', 'mount', 'night']
simulator = hardware.get_all_names(without=['camera'])
with pytest.raises(error.PanError):
Observatory(simulator=simulator, config=conf, ignore_local_config=True)


def test_camera_port_error(config):
conf = config.copy()
conf['cameras']['devices'][0]['model'] = 'foobar'
simulator = ['weather', 'mount', 'night']
simulator = hardware.get_all_names(without=['camera'])
with pytest.raises(error.CameraNotFound):
Observatory(simulator=simulator, config=conf, auto_detect=False, ignore_local_config=True)

Expand All @@ -98,7 +99,7 @@ def test_camera_import_error(config):
conf = config.copy()
conf['cameras']['devices'][0]['model'] = 'foobar'
conf['cameras']['devices'][0]['port'] = 'usb:001,002'
simulator = ['weather', 'mount', 'night']
simulator = hardware.get_all_names(without=['camera'])
with pytest.raises(error.NotFound):
Observatory(simulator=simulator, config=conf, auto_detect=False, ignore_local_config=True)

Expand Down
6 changes: 2 additions & 4 deletions pocs/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import yaml

from astropy import units as u
from pocs import hardware
from pocs.utils import listify
from warnings import warn

Expand Down Expand Up @@ -42,10 +43,7 @@ def load_config(config_files=None, simulator=None, parse=True, ignore_local=Fals
warn("Problem with local config file {}, skipping".format(local_version))

if simulator is not None:
if 'all' in simulator:
config['simulator'] = ['camera', 'mount', 'weather', 'night', 'dome']
else:
config['simulator'] = simulator
config['simulator'] = hardware.get_simulator_names(simulator=simulator)

if parse:
config = parse_config(config)
Expand Down
3 changes: 2 additions & 1 deletion scripts/send_home.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#!/usr/bin/env python3

from pocs import hardware
from pocs import POCS

pocs = POCS(simulator=['camera', 'weather'])
pocs = POCS(simulator=hardware.get_all_names(without=['mount', 'night']))
pocs.observatory.mount.initialize()
pocs.observatory.mount.home_and_park()
pocs.power_down()