Skip to content

Commit

Permalink
Merge pull request #17 from Macr0Nerd/add_tests
Browse files Browse the repository at this point in the history
Add tests
  • Loading branch information
Macr0Nerd authored Dec 25, 2023
2 parents 75f622c + 1d11fba commit 4372560
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 30 deletions.
Empty file added examples/algorithms/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions examples/config/pytest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
simulation_id = "Pytest Simulation"
algorithms_directory = "examples/algorithms/"
nodes = [ { node_id = "node_1", algorithm = { file = "simple.py", object = "AlwaysCooperate" } },
{ node_id = "node_2", algorithm = { file = "simple.py", object = "AlwaysDefect" } },
{ node_id = "node_3", algorithm = { file = "tit_for_tat.py", object = "TitForTat" } }, ]
rounds_data = "examples/rounds/pytest.json"
rounds_output = "examples/rounds/pytest.json"
simulation = { object = "StandardSimulation" }
simulation_arguments = { rounds = 10 }
simulation_output = "examples/results/pytest.json"
1 change: 1 addition & 0 deletions examples/results/pytest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"node_1": 30, "node_2": 100, "node_3": 30}
1 change: 1 addition & 0 deletions examples/rounds/pytest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"node_1:node_2": [{"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}, {"node_1": true, "node_2": false}], "node_1:node_3": [{"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}, {"node_1": true, "node_3": true}], "node_2:node_3": [{"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}, {"node_2": false, "node_3": true}]}
19 changes: 17 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ classifiers = [
"Topic :: Games/Entertainment :: Simulation"
]
dependencies = [
"platformdirs~=4.1.0"
"platformdirs"
]

[project.optional-dependencies]
test = [
"pytest"
]

[project.urls]
Expand All @@ -41,4 +46,14 @@ Repository = "https://github.com/Macr0Nerd/project_dilemma"
project_dilemma = "project_dilemma.__main__:main"

[tool.setuptools.dynamic]
version = {attr = "project_dilemma.__version__"}
version = {attr = "project_dilemma.__version__"}

[tool.pytest.ini_options]
addopts = "-ra"
pythonpath = [
"src",
"examples"
]
testpaths = [
"tests"
]
2 changes: 1 addition & 1 deletion src/project_dilemma/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def arguments() -> dict:
parser_out.add_argument('-sO', '--simulation-output', help='output the results as JSON',
dest='simulation_output')

if not len(sys.argv):
if len(sys.argv) <= 1:
parser.print_help()
sys.exit(0)

Expand Down
12 changes: 8 additions & 4 deletions src/project_dilemma/interfaces/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
limitations under the License.
"""
import random
from typing import Self, Type

from project_dilemma.interfaces.algorithm import Algorithm
from project_dilemma.interfaces import Algorithm
from project_dilemma.interfaces.base import Base


Expand All @@ -27,7 +28,7 @@ class Node(Base):
:var node_id: id of the node
:vartype node_id: str
:var algorithm: cooperation algorithm
:vartype algorithm: Algorithm
:vartype algorithm: Type[Algorithm]
"""
_required_attributes = [
'algorithm',
Expand All @@ -36,12 +37,15 @@ class Node(Base):
]

node_id: str
algorithm: Algorithm
algorithm: Type[Algorithm]

def __init__(self, node_id: str, algorithm: Algorithm):
def __init__(self, node_id: str, algorithm: Type[Algorithm]):
self.node_id = node_id
self.algorithm = algorithm

def __eq__(self, other: Self):
return (self.node_id == other.node_id) and (self.algorithm.algorithm_id == other.algorithm.algorithm_id)

def mutate(self):
"""set the node to a random algorithm mutation"""
if self.algorithm.mutations:
Expand Down
45 changes: 22 additions & 23 deletions src/project_dilemma/object_loaders.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
import importlib
import _json
import json
import os.path
import sys
from typing import Dict, List
from typing import Dict, List, Type

from project_dilemma.config import ProjectDilemmaConfig
from project_dilemma.interfaces import Algorithm, Node, Simulation, SimulationRounds
from project_dilemma.simulations import simulations_map


def load_algorithms(config: ProjectDilemmaConfig) -> Dict[str, Algorithm]:
def create_nodes(config: ProjectDilemmaConfig, algorithms_map: Dict[str, Type[Algorithm]]) -> List[Node]:
"""create the simulation nodes
:param config: configuration data
:type config: ProjectDilemmaConfig
:param algorithms_map: map of algorithm class names to algorithms
:type algorithms_map: Dict[str, Type[Algorithm]]
:return:
"""
nodes = []

for node in config['nodes']:
nodes.append(Node(node['node_id'], algorithms_map[node['algorithm']['object']]))

return nodes


def load_algorithms(config: ProjectDilemmaConfig) -> Dict[str, Type[Algorithm]]:
"""load all algorithms used
:param config: configuration data
:type config: ProjectDilemmaConfig
:return: map of algorithm class names to algorithms
:rtype: Dict[str, Algorithm]
:rtype: Dict[str, Type[Algorithm]]
"""
sys.path.append(config['algorithms_directory'])

algorithms = [node['algorithm'] for node in config['nodes']]
algorithm_map: Dict[str, Algorithm] = {}
algorithm_map: Dict[str, Type[Algorithm]] = {}

for algorithm in algorithms:
if algorithm_map.get(algorithm['object']):
Expand Down Expand Up @@ -51,7 +67,7 @@ def load_rounds(config: ProjectDilemmaConfig) -> SimulationRounds:
return round_data


def load_simulation(config: ProjectDilemmaConfig) -> Simulation:
def load_simulation(config: ProjectDilemmaConfig) -> Type[Simulation]:
"""load the simulation
:param config: configuration data
Expand All @@ -76,20 +92,3 @@ def load_simulation(config: ProjectDilemmaConfig) -> Simulation:
simulation = simulations_map[config['simulation']['object']]

return simulation


def create_nodes(config: ProjectDilemmaConfig, algorithms_map: Dict[str, Algorithm]) -> List[Node]:
"""create the simulation nodes
:param config: configuration data
:type config: ProjectDilemmaConfig
:param algorithms_map: map of algorithm class names to algorithms
:type algorithms_map: Dict[str, Algorithm]
:return:
"""
nodes = []

for node in config['nodes']:
nodes.append(Node(node['node_id'], algorithms_map[node['algorithm']['object']]))

return nodes
111 changes: 111 additions & 0 deletions tests/integration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import json

import pytest

import project_dilemma.config
from project_dilemma.config import load_configuration, ProjectDilemmaConfig
from project_dilemma.interfaces import Node
from project_dilemma.object_loaders import create_nodes, load_algorithms, load_rounds, load_simulation
from project_dilemma.simulations import StandardSimulation

from algorithms.simple import AlwaysCooperate, AlwaysDefect
from algorithms.tit_for_tat import TitForTat


@pytest.fixture
def test_configuration_loading(monkeypatch):
def mock_args():
return {'config': 'examples/config/pytest.toml'}

monkeypatch.setattr(project_dilemma.config, 'arguments', mock_args)

expected: ProjectDilemmaConfig = {
'simulation_id': 'Pytest Simulation',
'algorithms_directory': 'examples/algorithms/',
'nodes': [
{'node_id': 'node_1', 'algorithm': {'file': 'simple.py', 'object': 'AlwaysCooperate'}},
{'node_id': 'node_2', 'algorithm': {'file': 'simple.py', 'object': 'AlwaysDefect'}},
{'node_id': 'node_3', 'algorithm': {'file': 'tit_for_tat.py', 'object': 'TitForTat'}},
],
'rounds_data': 'examples/rounds/pytest.json',
'rounds_output': 'examples/rounds/pytest.json',
'simulation': {'object': 'StandardSimulation'},
'simulation_arguments': {'rounds': 10},
'simulation_output': 'examples/results/pytest.json'
}

actual = load_configuration()

assert expected == actual

return actual


@pytest.fixture
def test_object_loading(test_configuration_loading):
with open('examples/rounds/pytest.json', 'r') as f:
expected_rounds = json.load(f)

actual_rounds = load_rounds(test_configuration_loading)

assert expected_rounds == actual_rounds

expected_algorithm_map = {
'AlwaysCooperate': AlwaysCooperate,
'AlwaysDefect': AlwaysDefect,
'TitForTat': TitForTat
}

actual_algorithm_map = load_algorithms(test_configuration_loading)

assert False not in [
algo.algorithm_id == actual_algorithm_map[aid].algorithm_id for aid, algo in expected_algorithm_map.items()
]

expected_simulation = StandardSimulation

actual_simulation = load_simulation(test_configuration_loading)

assert expected_simulation == actual_simulation

expected_nodes = [
Node('node_1', AlwaysCooperate),
Node('node_2', AlwaysDefect),
Node('node_3', TitForTat)
]

actual_nodes = create_nodes(test_configuration_loading, expected_algorithm_map)

assert expected_nodes == actual_nodes

return actual_simulation(
simulation_id=test_configuration_loading['simulation_id'],
nodes=actual_nodes,
**test_configuration_loading['simulation_arguments']
)


@pytest.fixture
def test_simulation_run(test_object_loading):
with open('examples/rounds/pytest.json', 'r') as f:
expected_rounds = json.load(f)

actual_rounds = test_object_loading.run_simulation()

assert expected_rounds == actual_rounds


def test_simulation_process(test_object_loading):
with open('examples/rounds/pytest.json', 'r') as f:
rounds = json.load(f)

with open('examples/results/pytest.json', 'r') as f:
expected_results = json.load(f)

test_object_loading.simulation_rounds = rounds

actual_results = test_object_loading.process_results()

assert expected_results == actual_results


0 comments on commit 4372560

Please sign in to comment.