Skip to content

Commit c5f9ab1

Browse files
Add optional install tests (#14)
1 parent 1319b19 commit c5f9ab1

10 files changed

Lines changed: 205 additions & 19 deletions

.github/workflows/build-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
steps:
1414
- uses: actions/checkout@v2
1515
- run: |
16-
docker build -f py.Dockerfile \
16+
docker build -f bin/all.Dockerfile \
1717
--build-arg PYTHON_VERSION=${{ matrix.python-version }} \
1818
--tag shimmy-docker .
1919
- name: Run tests
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: build
2+
on: [pull_request, push]
3+
4+
permissions:
5+
contents: read # to fetch code (actions/checkout)
6+
7+
jobs:
8+
optional-install:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v2
12+
13+
# Atari
14+
- run: |
15+
docker build -f bin/atari.Dockerfile \
16+
--build-arg PYTHON_VERSION='3.10' \
17+
--tag shimmy-atari-docker .
18+
- name: Run atari tests
19+
run: docker run shimmy-atari-docker pytest tests/test_atari.py
20+
21+
# dm-control
22+
- run: |
23+
docker build -f bin/dm_control.Dockerfile \
24+
--build-arg PYTHON_VERSION='3.10' \
25+
--tag shimmy-dm-control-docker .
26+
- name: Run dm-control tests
27+
run: docker run shimmy-dm-control-docker pytest tests/test_dm_control.py
28+
29+
# openspiel
30+
- run: |
31+
docker build -f bin/openspiel.Dockerfile \
32+
--build-arg PYTHON_VERSION='3.10' \
33+
--tag shimmy-openspiel-docker .
34+
- name: Run openspiel tests
35+
run: docker run shimmy-openspiel-docker pytest tests/test_openspiel.py
36+
37+
# add more options

py.Dockerfile renamed to bin/all.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ RUN apt-get -y update \
2020
COPY . /usr/local/shimmy/
2121
WORKDIR /usr/local/shimmy/
2222

23-
RUN pip install .[testing] --no-cache-dir
23+
RUN pip install ".[all, testing]" --no-cache-dir
2424

2525
ENTRYPOINT ["/usr/local/shimmy/bin/docker_entrypoint"]

bin/atari.Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# A Dockerfile that sets up a full shimmy install with test dependencies
2+
ARG PYTHON_VERSION
3+
FROM python:$PYTHON_VERSION
4+
5+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
6+
7+
RUN apt-get -y update \
8+
&& apt-get install --no-install-recommends -y \
9+
unzip \
10+
libglu1-mesa-dev \
11+
libgl1-mesa-dev \
12+
libosmesa6-dev \
13+
xvfb \
14+
patchelf \
15+
ffmpeg cmake \
16+
&& apt-get autoremove -y \
17+
&& apt-get clean \
18+
&& rm -rf /var/lib/apt/lists/*
19+
20+
COPY . /usr/local/shimmy/
21+
WORKDIR /usr/local/shimmy/
22+
23+
RUN pip install ".[atari, testing]" --no-cache-dir
24+
25+
ENTRYPOINT ["/usr/local/shimmy/bin/docker_entrypoint"]

bin/dm_control.Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# A Dockerfile that sets up a full shimmy install with test dependencies
2+
ARG PYTHON_VERSION
3+
FROM python:$PYTHON_VERSION
4+
5+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
6+
7+
RUN apt-get -y update \
8+
&& apt-get install --no-install-recommends -y \
9+
unzip \
10+
libglu1-mesa-dev \
11+
libgl1-mesa-dev \
12+
libosmesa6-dev \
13+
xvfb \
14+
patchelf \
15+
ffmpeg cmake \
16+
&& apt-get autoremove -y \
17+
&& apt-get clean \
18+
&& rm -rf /var/lib/apt/lists/*
19+
20+
COPY . /usr/local/shimmy/
21+
WORKDIR /usr/local/shimmy/
22+
23+
RUN pip install ".[dm-control, testing]" --no-cache-dir
24+
25+
ENTRYPOINT ["/usr/local/shimmy/bin/docker_entrypoint"]

bin/openspiel.Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# A Dockerfile that sets up a full shimmy install with test dependencies
2+
ARG PYTHON_VERSION
3+
FROM python:$PYTHON_VERSION
4+
5+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
6+
7+
RUN apt-get -y update \
8+
&& apt-get install --no-install-recommends -y \
9+
unzip \
10+
libglu1-mesa-dev \
11+
libgl1-mesa-dev \
12+
libosmesa6-dev \
13+
xvfb \
14+
patchelf \
15+
ffmpeg cmake \
16+
&& apt-get autoremove -y \
17+
&& apt-get clean \
18+
&& rm -rf /var/lib/apt/lists/*
19+
20+
COPY . /usr/local/shimmy/
21+
WORKDIR /usr/local/shimmy/
22+
23+
RUN pip install ".[openspiel, testing]" --no-cache-dir
24+
25+
ENTRYPOINT ["/usr/local/shimmy/bin/docker_entrypoint"]

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ def get_version():
3333
header_count, long_description = get_description()
3434

3535
extras = {
36+
"gym": ["gym>=0.26"],
37+
"atari": ["ale-py~=0.8.0"],
3638
# "imageio" should be "gymnasium[mujoco]>=0.26" but there are install conflicts
3739
"dm-control": ["dm-control>=1.0.8", "imageio"],
3840
"openspiel": ["open_spiel>=1.2", "pettingzoo>=1.22"],
39-
"atari": ["ale-py~=0.8.0"],
4041
}
4142
extras["all"] = list({lib for libs in extras.values() for lib in libs})
42-
extras["testing"] = extras["all"] + [
43+
extras["testing"] = [
4344
"pytest==7.1.3",
4445
"pillow>=9.3.0",
4546
"autorom[accept-rom-license]~=0.4.2",

shimmy/openai_gym_compatibility.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Compatibility wrappers for OpenAI gym V22 and V26."""
2+
# pyright: reportGeneralTypeIssues=false, reportPrivateImportUsage=false
23
from __future__ import annotations
34

45
import sys
@@ -80,9 +81,7 @@ def __init__(
8081
self.metadata = getattr(self.gym_env, "metadata", {"render_modes": []})
8182
self.render_mode = self.gym_env.render_mode
8283
self.reward_range = getattr(self.gym_env, "reward_range", None)
83-
self.spec = getattr( # pyright: ignore[reportGeneralTypeIssues]
84-
self.gym_env, "spec", None
85-
)
84+
self.spec = getattr(self.gym_env, "spec", None)
8685

8786
def reset(
8887
self, seed: int | None = None, options: dict | None = None
@@ -183,24 +182,24 @@ def __init__(
183182
make_kwargs = {}
184183

185184
if env is not None:
186-
self.gym_env = env
185+
gym_env = env
187186
elif env_id is not None:
188-
self.gym_env = gym.make(env_id, **make_kwargs)
187+
gym_env = gym.make(env_id, **make_kwargs)
189188
else:
190189
raise MissingArgument(
191190
"Either env_id or env must be provided to create a legacy gym environment."
192191
)
193-
self.observation_space = _convert_space(self.gym_env.observation_space)
194-
self.action_space = _convert_space(self.gym_env.action_space)
192+
self.observation_space = _convert_space(gym_env.observation_space)
193+
self.action_space = _convert_space(gym_env.action_space)
195194

196-
self.gym_env = _strip_default_wrappers(self.gym_env)
195+
gym_env = _strip_default_wrappers(gym_env)
197196

198-
self.metadata = getattr(self.gym_env, "metadata", {"render_modes": []})
197+
self.metadata = getattr(gym_env, "metadata", {"render_modes": []})
199198
self.render_mode = render_mode
200-
self.reward_range = getattr(self.gym_env, "reward_range", None)
201-
self.spec = getattr( # pyright: ignore[reportGeneralTypeIssues]
202-
self.gym_env, "spec", None
203-
)
199+
self.reward_range = getattr(gym_env, "reward_range", None)
200+
self.spec = getattr(gym_env, "spec", None)
201+
202+
self.gym_env: LegacyV22Env = gym_env
204203

205204
def reset(
206205
self, seed: int | None = None, options: dict | None = None

shimmy/registration.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
from typing import Any, Callable, Mapping, NamedTuple, Sequence
77

88
import numpy as np
9-
from ale_py.roms import utils as rom_utils
109
from gymnasium.envs.registration import register
1110

12-
from shimmy.dm_control_compatibility import DmControlCompatibility
1311
from shimmy.utils.envs_configs import (
1412
ALL_ATARI_GAMES,
1513
DM_CONTROL_MANIPULATION_ENVS,
@@ -25,6 +23,8 @@ def _register_dm_control_envs():
2523
except ImportError:
2624
return
2725

26+
from shimmy.dm_control_compatibility import DmControlCompatibility
27+
2828
# Add generic environment support
2929
def _make_dm_control_generic_env(env, **render_kwargs):
3030
return DmControlCompatibility(env, **render_kwargs)
@@ -128,6 +128,8 @@ def _register_atari_configs(
128128
configs: Sequence[GymConfig],
129129
prefix: str = "",
130130
):
131+
from ale_py.roms import utils as rom_utils
132+
131133
for rom in roms:
132134
for obs_type in obs_types:
133135
for config in configs:
@@ -162,6 +164,11 @@ def _register_atari_configs(
162164

163165

164166
def _register_atari_envs():
167+
try:
168+
import ale_py
169+
except ImportError:
170+
return
171+
165172
frameskip: dict[str, int] = defaultdict(lambda: 4, [("space_invaders", 3)])
166173

167174
configs = [

tests/test_gym.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Tests that gym compatibility environment work as expected."""
2+
3+
import warnings
4+
5+
import gym
6+
import gymnasium
7+
import pytest
8+
from gymnasium.error import Error
9+
from gymnasium.utils.env_checker import check_env
10+
11+
CHECK_ENV_IGNORE_WARNINGS = [
12+
f"\x1b[33mWARN: {message}\x1b[0m"
13+
for message in [
14+
"This version of the mujoco environments depends on the mujoco-py bindings, which are no longer maintained and may stop working. Please upgrade to the v4 versions of the environments (which depend on the mujoco python bindings instead), unless you are trying to precisely replicate previous works).",
15+
"A Box observation space minimum value is -infinity. This is probably too low.",
16+
"A Box observation space maximum value is -infinity. This is probably too high.",
17+
"For Box action spaces, we recommend using a symmetric and normalized space (range=[-1, 1] or [0, 1]). See https://stable-baselines3.readthedocs.io/en/master/guide/rl_tips.html for more information.",
18+
]
19+
]
20+
21+
# We do not test Atari environment's here because we check all variants of Pong in test_envs.py (There are too many Atari environments)
22+
CLASSIC_CONTROL_ENVS = [
23+
env_id
24+
for env_id, spec in gym.envs.registry.items() # pyright: ignore[reportGeneralTypeIssues]
25+
if ("classic_control" in spec.entry_point)
26+
]
27+
28+
29+
@pytest.mark.parametrize(
30+
"env_id", CLASSIC_CONTROL_ENVS, ids=[env_id for env_id in CLASSIC_CONTROL_ENVS]
31+
)
32+
def test_gym_conversion_by_id(env_id):
33+
"""Tests that the gym conversion works through specifying the env_id."""
34+
env = gymnasium.make("GymV26Environment-v0", env_id=env_id).unwrapped
35+
36+
with warnings.catch_warnings(record=True) as caught_warnings:
37+
check_env(env)
38+
39+
for warning in caught_warnings:
40+
if (
41+
isinstance(warning.message, Warning)
42+
and warning.message.args[0] not in CHECK_ENV_IGNORE_WARNINGS
43+
):
44+
raise Error(f"Unexpected warning: {warning.message}")
45+
46+
env.close()
47+
48+
49+
@pytest.mark.parametrize(
50+
"env_id", CLASSIC_CONTROL_ENVS, ids=[env_id for env_id in CLASSIC_CONTROL_ENVS]
51+
)
52+
def test_gym_conversion_instantiated(env_id):
53+
"""Tests that the gym conversion works with an instantiated gym environment."""
54+
env = gym.make(env_id)
55+
env = gymnasium.make("GymV26Environment-v0", env=env).unwrapped
56+
57+
with warnings.catch_warnings(record=True) as caught_warnings:
58+
check_env(env)
59+
60+
for warning in caught_warnings:
61+
if (
62+
isinstance(warning.message, Warning)
63+
and warning.message.args[0] not in CHECK_ENV_IGNORE_WARNINGS
64+
):
65+
raise Error(f"Unexpected warning: {warning.message}")
66+
67+
env.close()

0 commit comments

Comments
 (0)