33Skipped automatically if pettingzoo is not installed
44(e.g. in base CI without the [multi-agent] extra).
55"""
6+
67from __future__ import annotations
78
89import pytest
910
11+
1012# Skip entire module if pettingzoo is not available
1113pytest .importorskip (
1214 "pettingzoo.test" ,
1315 reason = "pettingzoo not installed; run with: pip install highway-env[multi-agent]" ,
1416)
15- from pettingzoo .test import parallel_api_test
17+ from pettingzoo .test import parallel_api_test # noqa: E402
1618
17- import highway_env # noqa: F401 — triggers env registration
18- from highway_env .envs .intersection_pz_env import (
19+ import highway_env # noqa: E402, F401 — triggers env registration
20+ from highway_env .envs .intersection_pz_env import ( # noqa: E402
1921 ConnectedLaneIntersectionParallelEnv ,
2022 IntersectionParallelEnv ,
2123)
22- from highway_env .envs .common .pettingzoo_env import HighwayParallelEnv
2324
2425
2526# -----------------------------------------------------------------------
2627# Fixtures
2728# -----------------------------------------------------------------------
2829
30+
2931@pytest .fixture
3032def intersection_env ():
3133 env = IntersectionParallelEnv (config = {"controlled_vehicles" : 2 , "duration" : 10 })
@@ -46,6 +48,7 @@ def connected_lane_env():
4648# PettingZoo API conformance
4749# -----------------------------------------------------------------------
4850
51+
4952def test_parallel_api_test_intersection ():
5053 """PettingZoo's built-in API conformance checker must pass."""
5154 env = IntersectionParallelEnv (config = {"controlled_vehicles" : 2 , "duration" : 5 })
@@ -66,6 +69,7 @@ def test_parallel_api_test_connected_lane():
6669# Observation / action space correctness
6770# -----------------------------------------------------------------------
6871
72+
6973def test_reset_returns_agent_dict (intersection_env ):
7074 """reset() must return dicts keyed by agent name."""
7175 obs , infos = intersection_env .reset (seed = 42 )
@@ -79,20 +83,26 @@ def test_obs_in_observation_space(intersection_env):
7983 """Every observation must lie within the declared observation space."""
8084 obs , _ = intersection_env .reset (seed = 0 )
8185 while intersection_env .agents :
82- actions = {a : intersection_env .action_space (a ).sample () for a in intersection_env .agents }
86+ actions = {
87+ a : intersection_env .action_space (a ).sample ()
88+ for a in intersection_env .agents
89+ }
8390 obs , rewards , terminations , truncations , infos = intersection_env .step (actions )
8491 for agent , agent_obs in obs .items ():
85- assert intersection_env .observation_space (agent ).contains (agent_obs ), (
86- f" { agent } : obs not in observation_space"
87- )
92+ assert intersection_env .observation_space (agent ).contains (
93+ agent_obs
94+ ), f" { agent } : obs not in observation_space"
8895
8996
9097def test_possible_agents_is_stable (intersection_env ):
9198 """possible_agents must not change across the episode."""
9299 intersection_env .reset (seed = 1 )
93100 initial_possible = list (intersection_env .possible_agents )
94101 while intersection_env .agents :
95- actions = {a : intersection_env .action_space (a ).sample () for a in intersection_env .agents }
102+ actions = {
103+ a : intersection_env .action_space (a ).sample ()
104+ for a in intersection_env .agents
105+ }
96106 intersection_env .step (actions )
97107 assert intersection_env .possible_agents == initial_possible
98108
@@ -101,19 +111,23 @@ def test_possible_agents_is_stable(intersection_env):
101111# Per-agent termination semantics
102112# -----------------------------------------------------------------------
103113
114+
104115def test_terminated_agents_removed_from_agents (intersection_env ):
105116 """Agents flagged as terminated must NOT appear in self.agents next step."""
106117 intersection_env .reset (seed = 2 )
107118 while intersection_env .agents :
108119 pre_step_agents = set (intersection_env .agents )
109- actions = {a : intersection_env .action_space (a ).sample () for a in intersection_env .agents }
120+ actions = {
121+ a : intersection_env .action_space (a ).sample ()
122+ for a in intersection_env .agents
123+ }
110124 _ , _ , terminations , truncations , _ = intersection_env .step (actions )
111125
112126 for agent in pre_step_agents :
113127 if terminations .get (agent , False ) or truncations .get (agent , False ):
114- assert agent not in intersection_env . agents , (
115- f" { agent } terminated/truncated but still in env .agents"
116- )
128+ assert (
129+ agent not in intersection_env .agents
130+ ), f" { agent } terminated/truncated but still in env.agents"
117131
118132
119133def test_episode_ends_when_all_agents_done (intersection_env ):
@@ -122,7 +136,10 @@ def test_episode_ends_when_all_agents_done(intersection_env):
122136 steps = 0
123137 max_steps = 500
124138 while intersection_env .agents and steps < max_steps :
125- actions = {a : intersection_env .action_space (a ).sample () for a in intersection_env .agents }
139+ actions = {
140+ a : intersection_env .action_space (a ).sample ()
141+ for a in intersection_env .agents
142+ }
126143 intersection_env .step (actions )
127144 steps += 1
128145 # If we reach here without error, the loop terminated correctly
@@ -133,9 +150,11 @@ def test_episode_ends_when_all_agents_done(intersection_env):
133150# Seeding / reproducibility
134151# -----------------------------------------------------------------------
135152
153+
136154def test_reset_with_same_seed_is_reproducible ():
137155 """Two resets with the same seed must produce identical initial observations."""
138156 import numpy as np
157+
139158 env1 = IntersectionParallelEnv (config = {"controlled_vehicles" : 2 , "duration" : 5 })
140159 env2 = IntersectionParallelEnv (config = {"controlled_vehicles" : 2 , "duration" : 5 })
141160
@@ -153,20 +172,24 @@ def test_reset_with_same_seed_is_reproducible():
153172# Deprecation warning for MultiAgentWrapper
154173# -----------------------------------------------------------------------
155174
175+
156176def test_multi_agent_wrapper_deprecation_warning ():
157177 """Using MultiAgentWrapper must raise DeprecationWarning."""
158178 import warnings
179+
159180 import gymnasium as gym
160181
161182 with warnings .catch_warnings (record = True ) as caught :
162183 warnings .simplefilter ("always" )
163184 env = gym .make ("intersection-multi-agent-v1" )
164185 env .close ()
165186
166- deprecation_warnings = [w for w in caught if issubclass (w .category , DeprecationWarning )]
167- assert len (deprecation_warnings ) > 0 , (
168- "Expected DeprecationWarning from MultiAgentWrapper but none was raised"
169- )
187+ deprecation_warnings = [
188+ w for w in caught if issubclass (w .category , DeprecationWarning )
189+ ]
190+ assert (
191+ len (deprecation_warnings ) > 0
192+ ), "Expected DeprecationWarning from MultiAgentWrapper but none was raised"
170193 assert any (
171194 "MultiAgentWrapper" in str (w .message ) for w in deprecation_warnings
172195 ), "Expected a DeprecationWarning mentioning MultiAgentWrapper"
0 commit comments