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

3 implement ci for repo #10

Merged
merged 4 commits into from
Mar 19, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[flake8]
extend-ignore = W605
per-file-ignores =
__init__.py:F401,F403,F405
max-line-length = 100
extend-select = E225,E226
41 changes: 41 additions & 0 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Pull request workflows

on:
pull_request:
branches: ["main"]
types: ["opened", "synchronize"]
workflow_dispatch:

jobs:
flake8:
name: "Flake 8 style"
runs-on: ubuntu-latest
steps:
- name: "📥 Fetching Repository Contents"
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: "💫 Install package dependencies"
run: python -m pip install flake8

- name: "🤖 Check flake8 style"
run: flake8 -v --count

pytest:
name: "Run python tests"
runs-on: ubuntu-latest
steps:
- name: "📥 Fetching Repository Contents"
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: "💫 Install package dependencies"
run: |
python -m pip install pytest
python -m pip install -r requirements.txt

- name: "🤖 Run pytest"
run: |
pytest test
4 changes: 2 additions & 2 deletions AIs/random_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@


@decorator.turn
def turn(possible_actions,):
""" Return a random action from the possible ones.
def turn(possible_actions):
"""Return a random action from the possible ones.

Parameters
----------
Expand Down
57 changes: 47 additions & 10 deletions src/ai_tools/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ def _handle_arguments(func, arg_names, /, *args, **kwargs):
if name not in arg_names:
raise TypeError(f"{func.__name__} got an unexpected keyword argument '{name}'")
# Clean arguments: transfer only those were defined by func
full_kwargs = {**{k: v for k, v in zip(arg_names[:len(args)], args)}, **kwargs}
full_kwargs = {**{k: v for k, v in zip(arg_names[: len(args)], args)}, **kwargs}
new_kwargs = {k: v for k, v in full_kwargs.items() if k in func.__code__.co_varnames}
return new_kwargs


class decorator:
"""Class that embeds the different decorators
"""
"""Class that embeds the different decorators"""

@staticmethod
def preprocessing(func):
"""Decorator that removes parameters that are not used by preprocessing
Expand All @@ -37,12 +37,23 @@ def preprocessing(func):
callable
the wrapped function
"""

@wraps(func)
def wrapper(*args, **kwargs):
arg_names = ['maze', 'maze_width', 'maze_height', 'name', 'teams',
'player_locations', 'cheese', 'possible_actions', 'memory']
arg_names = [
"maze",
"maze_width",
"maze_height",
"name",
"teams",
"player_locations",
"cheese",
"possible_actions",
"memory",
]
new_kwargs = _handle_arguments(func, arg_names, *args, **kwargs)
return func(**new_kwargs)

return wrapper

@staticmethod
Expand All @@ -65,13 +76,26 @@ def postprocessing(func):
callable
the wrapped function
"""

@wraps(func)
def wrapper(*args, **kwargs):
arg_names = ['maze', 'maze_width', 'maze_height', 'name', 'teams', 'player_locations',
'player_scores', 'player_muds', 'cheese', 'possible_actions', 'memory',
'stats']
arg_names = [
"maze",
"maze_width",
"maze_height",
"name",
"teams",
"player_locations",
"player_scores",
"player_muds",
"cheese",
"possible_actions",
"memory",
"stats",
]
new_kwargs = _handle_arguments(func, arg_names, *args, **kwargs)
return func(**new_kwargs)

return wrapper

@staticmethod
Expand All @@ -94,10 +118,23 @@ def turn(func):
callable
the wrapped function
"""

@wraps(func)
def wrapper(*args, **kwargs):
arg_names = ['maze', 'maze_width', 'maze_height', 'name', 'teams', 'player_locations',
'player_scores', 'player_muds', 'cheese', 'possible_actions', 'memory']
arg_names = [
"maze",
"maze_width",
"maze_height",
"name",
"teams",
"player_locations",
"player_scores",
"player_muds",
"cheese",
"possible_actions",
"memory",
]
new_kwargs = _handle_arguments(func, arg_names, *args, **kwargs)
return func(**new_kwargs)

return wrapper
23 changes: 13 additions & 10 deletions src/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,29 @@ def launch_game_in_pyrat(players, **kwargs):
stats = game.start()

# Limit turn_durations to 10 values
for _, player in stats['players'].items():
num_turns = len(player['turn_durations'])
player['turn_durations'] = player['turn_durations'][:10]
for _, player in stats["players"].items():
num_turns = len(player["turn_durations"])
player["turn_durations"] = player["turn_durations"][:10]
if num_turns > 10:
player['turn_durations'].append('...')
player["turn_durations"].append("...")

print(stats)


def main():
"""Command-line interface to run a simple game between two agents
"""
"""Command-line interface to run a simple game between two agents"""
# Include script players in arguments
parser.add_argument("--players", nargs=2, required=True,
help="Modules with agent programs. Each program must follow the format: "
"<package>.<subpackage>...<program>")
parser.add_argument(
"--players",
nargs=2,
required=True,
help="Modules with agent programs. Each program must follow the format: "
"<package>.<subpackage>...<program>",
)
args = parser.parse_args()

# Load players
players = load_players(args.__dict__.pop('players'))
players = load_players(args.__dict__.pop("players"))

# Launch game
launch_game_in_pyrat(players, **vars(args))
Expand Down
16 changes: 9 additions & 7 deletions src/load_programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ def load_players(program_names):
except AttributeError as error:
raise AttributeError(f"Unsupported player: {str(error)}")
team_name = f"team_{idx+1}"
players.append({
"name": f"{team_name}:{os.path.splitext(os.path.basename(player_spec))[0]}",
"team": team_name,
"preprocessing_function": player.__dict__.get("preprocessing", None),
"turn_function": player_turn_fn,
"postprocessing_function": player.__dict__.get("postprocessing", None)
})
players.append(
{
"name": f"{team_name}:{os.path.splitext(os.path.basename(player_spec))[0]}",
"team": team_name,
"preprocessing_function": player.__dict__.get("preprocessing", None),
"turn_function": player_turn_fn,
"postprocessing_function": player.__dict__.get("postprocessing", None),
}
)
return players
1 change: 1 addition & 0 deletions src/pyrat_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ def suppress_output():
# Import pyrat arguments inside of suppress_output context to support new arguments
with suppress_output():
import pyrat

parser = pyrat.parser
8 changes: 3 additions & 5 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ def turn(*args):

def from_path_to_program(path):
# A program must follow the format <package>.<subpackage>...<program>
program = os.path.splitext(str(path))[0].replace(os.sep, '.')
if program.startswith('C:.'):
# Remove 'C:' in windows
program = program[3:]
return program
program = os.path.splitext(os.path.abspath(str(path)))[0].split(os.sep)
# Remove root directory (Windows 'C:' - Linux '/')
return ".".join(program[1:])


@pytest.fixture(scope="session")
Expand Down
31 changes: 18 additions & 13 deletions test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,25 @@ def dict_to_args(dict_args):

def test_cli(random_ai):
# Run a single game with fixed seed.
args = {"random_seed": 2,
"maze_width": 1,
"maze_height": 2,
"mud_percentage": 0.0,
"nb_cheese": 1,
"cell_percentage": 100.0,
"wall_percentage": 0.0,
"preprocessing_time": 0.0,
"turn_time": 0.0,
"render_mode": "no_rendering",
"players": (random_ai, random_ai)}
args = {
"random_seed": 2,
"maze_width": 1,
"maze_height": 2,
"mud_percentage": 0.0,
"nb_cheese": 1,
"cell_percentage": 100.0,
"wall_percentage": 0.0,
"preprocessing_time": 0.0,
"turn_time": 0.0,
"render_mode": "no_rendering",
"players": (random_ai, random_ai),
}

process = subprocess.run([sys.executable, "-m", "src.cli"] + dict_to_args(args),
stderr=subprocess.PIPE, stdout=subprocess.PIPE)
process = subprocess.run(
[sys.executable, "-m", "src.cli"] + dict_to_args(args),
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)

if process.returncode != 0:
main_error = str(subprocess.CalledProcessError(process.returncode, sys.executable))
Expand Down
10 changes: 5 additions & 5 deletions test/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ def func_to_wrapper(arg1, kwarg1=None):
_handle_arguments(func_to_wrapper, arg_names, unsupported_arg="value")


@pytest.mark.parametrize("decorator_fn, num_args",
[(decorator.preprocessing, 9),
(decorator.postprocessing, 12),
(decorator.turn, 11)],
ids=["preprocessing", "postprocessing", "turn"])
@pytest.mark.parametrize(
"decorator_fn, num_args",
[(decorator.preprocessing, 9), (decorator.postprocessing, 12), (decorator.turn, 11)],
ids=["preprocessing", "postprocessing", "turn"],
)
def test_decorators(decorator_fn, num_args):
# We must instance fake_fn globally to avoid local references, incompatible with pickle
global fake_fn
Expand Down
4 changes: 2 additions & 2 deletions test/test_load_players.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def test_load_player(random_ai):
assert player["postprocessing_function"] is None

# Run a fake turn: this program choose a random action
possible_actions = ['R', 'L', 'U', 'D']
action = player["turn_function"](possible_actions=possible_actions)
possible_actions = ["north", "east", "south", "west"]
action = player["turn_function"]()
assert action in possible_actions


Expand Down
4 changes: 2 additions & 2 deletions test/test_pyrat_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ def test_pyrat_args(capsys):
parser.add_argument("--new_arg", default="default", help="introduce new args")

# Parse args in helper mode. New argument(s) should be printed
with pytest.raises(SystemExit,):
args, _ = parser.parse_known_args(['-h'])
with pytest.raises(SystemExit):
args, _ = parser.parse_known_args(["-h"])

captured = capsys.readouterr()
assert "introduce new args" in captured.out
Expand Down