diff --git a/tests/helpers/constants.py b/tests/helpers/constants.py
index 4778674f..52cce102 100644
--- a/tests/helpers/constants.py
+++ b/tests/helpers/constants.py
@@ -33,6 +33,7 @@
BENCHMARKS_DIR = TEST_DIR / "benchmarks"
BLOCKSWORLD_DIR = BENCHMARKS_DIR / "deterministic" / "BF" / "blocksworld_ppltl"
+OPENSTACKS_DIR = BENCHMARKS_DIR / "deterministic" / "BF" / "openstacks_ppltl"
DOCKER_DIR = TEST_DIR / "docker"
diff --git a/tests/test_compiler/base.py b/tests/test_compiler/base.py
new file mode 100644
index 00000000..a582080c
--- /dev/null
+++ b/tests/test_compiler/base.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2021 -- 2023 WhiteMech
+#
+# ------------------------------
+#
+# This file is part of Plan4Past.
+#
+# Plan4Past is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Plan4Past is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Plan4Past. If not, see .
+#
+
+"""Base test module for testing the compiler module."""
+from abc import abstractmethod
+from pathlib import Path
+from typing import Tuple
+
+import pytest
+from pddl.parser.domain import DomainParser
+from pddl.parser.problem import ProblemParser
+from pylogics.parsers import parse_pltl
+
+from plan4past.compiler import Compiler
+from tests.helpers.misc import check_compilation
+from tests.helpers.planutils.base import BasePlannerWrapper
+from tests.helpers.planutils.val import VALWrapper
+
+
+class BaseCompilerTest:
+ """Base test class for compiler tests."""
+
+ PATH_TO_DOMAINS_DIR: Path
+ PATH_TO_INSTANCES_DIR: Path
+
+ @abstractmethod
+ def make_formula(self, instance_id: int, domain: Path, problem: Path) -> str:
+ """Make the formula for the given instance."""
+ raise NotImplementedError
+
+ @abstractmethod
+ def get_expected_plan(self, instance_id: int) -> Tuple[str, ...]:
+ """Get the expected plan for the given instance."""
+ raise NotImplementedError
+
+ def get_instance_file(self, instance_id: int) -> Path:
+ """Get the instance file for the given instance."""
+ return self.PATH_TO_INSTANCES_DIR / f"p{instance_id}.pddl"
+
+ def get_domain_file(self, _instance_id: int) -> Path:
+ """Get the domain file for the given instance."""
+ return self.PATH_TO_DOMAINS_DIR / "domain.pddl"
+
+ def _test_instance(
+ self, instance_id: int, val: VALWrapper, planner: BasePlannerWrapper
+ ) -> None:
+ """Test the instance with the given id."""
+ domain_parser = DomainParser()
+ problem_parser = ProblemParser()
+
+ domain_path = self.get_domain_file(instance_id)
+ problem_path = self.get_instance_file(instance_id)
+ if not domain_path.exists():
+ pytest.fail(f"Domain {domain_path} does not exist.")
+ if not problem_path.exists():
+ pytest.fail(f"Instance {problem_path} does not exist.")
+
+ domain = domain_parser(domain_path.read_text(encoding="utf-8"))
+ problem = problem_parser(problem_path.read_text(encoding="utf-8"))
+ goal = parse_pltl(self.make_formula(instance_id, domain_path, problem_path))
+
+ compiler = Compiler(domain, problem, goal)
+ compiler.compile()
+ compiled_domain, compiled_problem = compiler.result
+
+ check_compilation(
+ compiled_domain,
+ compiled_problem,
+ val,
+ planner,
+ self.get_expected_plan(instance_id),
+ )
diff --git a/tests/test_compiler/test_blocksworld_det.py b/tests/test_compiler/test_blocksworld_det.py
index 8d28e9dc..2e412a3c 100644
--- a/tests/test_compiler/test_blocksworld_det.py
+++ b/tests/test_compiler/test_blocksworld_det.py
@@ -21,69 +21,22 @@
#
"""This module contain tests for the compiler module, using the blocksworld domain."""
-from abc import abstractmethod
from pathlib import Path
from typing import Tuple
import pytest
-from pddl.parser.domain import DomainParser
-from pddl.parser.problem import ProblemParser
-from pylogics.parsers import parse_pltl
-from plan4past.compiler import Compiler
from tests.helpers.constants import BLOCKSWORLD_DIR
-from tests.helpers.misc import check_compilation
-from tests.helpers.planutils.base import BasePlannerWrapper
-from tests.helpers.planutils.val import VALWrapper
+from tests.test_compiler.base import BaseCompilerTest
-class BaseTestBlocksworldDet:
- """Base test class for deterministic Blocksworld experiments."""
+class TestBlocksworldDetSimpleSequence(BaseCompilerTest):
+ """Test class for deterministic Blocksworld experiments, simple sequence."""
- PATH_TO_DOMAIN = BLOCKSWORLD_DIR / "domain.pddl"
+ PATH_TO_DOMAINS_DIR = BLOCKSWORLD_DIR
PATH_TO_INSTANCES_DIR = BLOCKSWORLD_DIR
-
- @abstractmethod
- def make_formula(self, instance_id: int, domain: Path, problem: Path) -> str:
- """Make the formula for the given instance."""
- raise NotImplementedError
-
- @abstractmethod
- def get_expected_plan(self, instance_id: int) -> Tuple[str, ...]:
- """Get the expected plan for the given instance."""
- raise NotImplementedError
-
- def _test_instance(
- self, instance_id: int, val: VALWrapper, planner: BasePlannerWrapper
- ) -> None:
- """Test the instance with the given id."""
- domain_parser = DomainParser()
- problem_parser = ProblemParser()
-
- domain_path = self.PATH_TO_DOMAIN
- problem_path = self.PATH_TO_INSTANCES_DIR / f"p{instance_id}.pddl"
- if not problem_path.exists():
- pytest.fail(f"Instance {instance_id} does not exist.")
-
- domain = domain_parser(domain_path.read_text(encoding="utf-8"))
- problem = problem_parser(problem_path.read_text(encoding="utf-8"))
- goal = parse_pltl(self.make_formula(instance_id, domain_path, problem_path))
-
- compiler = Compiler(domain, problem, goal)
- compiler.compile()
- compiled_domain, compiled_problem = compiler.result
-
- check_compilation(
- compiled_domain,
- compiled_problem,
- val,
- planner,
- self.get_expected_plan(instance_id),
- )
-
-
-class TestBlocksworldDetSimpleSequence(BaseTestBlocksworldDet):
- """Test class for deterministic Blocksworld experiments, simple sequence."""
+ MIN_INSTANCE_ID = 2
+ MAX_INSTANCE_ID = 10
def make_formula(self, instance_id: int, domain: Path, problem: Path) -> str:
"""
@@ -109,7 +62,9 @@ def get_expected_plan(self, instance_id: int) -> Tuple[str, ...]:
result.append(f"(stack b{i} b{i + 1})")
return tuple(result)
- @pytest.mark.parametrize("instance_id", list(range(2, 11)))
+ @pytest.mark.parametrize(
+ "instance_id", list(range(MIN_INSTANCE_ID, MAX_INSTANCE_ID + 1))
+ )
def test_run(self, instance_id, val, default_planner):
"""Test the instance with the given id."""
self._test_instance(instance_id, val=val, planner=default_planner)
diff --git a/tests/test_compiler/test_openstacks_det.py b/tests/test_compiler/test_openstacks_det.py
new file mode 100644
index 00000000..3402a719
--- /dev/null
+++ b/tests/test_compiler/test_openstacks_det.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2021 -- 2023 WhiteMech
+#
+# ------------------------------
+#
+# This file is part of Plan4Past.
+#
+# Plan4Past is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Plan4Past is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Plan4Past. If not, see .
+#
+
+"""This module contain tests for the compiler module, using the openstacks domain."""
+import json
+from pathlib import Path
+from typing import Tuple
+
+import pytest
+
+from tests.helpers.constants import OPENSTACKS_DIR
+from tests.test_compiler.base import BaseCompilerTest
+
+
+class TestOpenStacksDet(BaseCompilerTest):
+ """Test class for deterministic OpenStacks experiments."""
+
+ PATH_TO_DOMAINS_DIR = OPENSTACKS_DIR
+ PATH_TO_INSTANCES_DIR = OPENSTACKS_DIR
+ TEGS = OPENSTACKS_DIR / "openstacks_teg.json"
+ MIN_INSTANCE_ID = 1
+ MAX_INSTANCE_ID = 5
+ EXPECTED_PLANS = {
+ "p01": (
+ "(setup-machine-p1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o1-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o3-n1-n0 )",
+ "(make-product-p1-n0 )",
+ "(setup-machine-p2-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o5-n1-n0 )",
+ "(make-product-p2-n0 )",
+ "(ship-order-o1-n0-n1 )",
+ "(setup-machine-p3-n1 )",
+ "(start-order-o4-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o2-n1-n0 )",
+ "(make-product-p3-n0 )",
+ "(setup-machine-p4-n0 )",
+ "(make-product-p4-n0 )",
+ "(ship-order-o5-n0-n1 )",
+ "(setup-machine-p5-n1 )",
+ "(make-product-p5-n1 )",
+ "(ship-order-o3-n1-n2 )",
+ "(ship-order-o4-n2-n3 )",
+ "(ship-order-o2-n3-n4 )",
+ ),
+ "p02": (
+ "(setup-machine-p1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o1-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o3-n1-n0 )",
+ "(make-product-p1-n0 )",
+ "(setup-machine-p2-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o4-n1-n0 )",
+ "(make-product-p2-n0 )",
+ "(ship-order-o1-n0-n1 )",
+ "(setup-machine-p3-n1 )",
+ "(start-order-o2-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o5-n1-n0 )",
+ "(make-product-p3-n0 )",
+ "(setup-machine-p4-n0 )",
+ "(make-product-p4-n0 )",
+ "(setup-machine-p5-n0 )",
+ "(make-product-p5-n0 )",
+ "(ship-order-o3-n0-n1 )",
+ "(ship-order-o4-n1-n2 )",
+ "(ship-order-o2-n2-n3 )",
+ "(ship-order-o5-n3-n4 )",
+ ),
+ "p03": (
+ "(setup-machine-p1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o1-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o4-n1-n0 )",
+ "(make-product-p1-n0 )",
+ "(setup-machine-p2-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o5-n1-n0 )",
+ "(make-product-p2-n0 )",
+ "(ship-order-o1-n0-n1 )",
+ "(setup-machine-p3-n1 )",
+ "(start-order-o2-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o3-n1-n0 )",
+ "(make-product-p3-n0 )",
+ "(setup-machine-p4-n0 )",
+ "(make-product-p4-n0 )",
+ "(ship-order-o4-n0-n1 )",
+ "(ship-order-o2-n1-n2 )",
+ "(setup-machine-p5-n2 )",
+ "(make-product-p5-n2 )",
+ "(ship-order-o5-n2-n3 )",
+ "(ship-order-o3-n3-n4 )",
+ ),
+ "p04": (
+ "(setup-machine-p1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o1-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o4-n1-n0 )",
+ "(make-product-p1-n0 )",
+ "(setup-machine-p2-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o5-n1-n0 )",
+ "(make-product-p2-n0 )",
+ "(ship-order-o1-n0-n1 )",
+ "(setup-machine-p3-n1 )",
+ "(start-order-o3-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o2-n1-n0 )",
+ "(make-product-p3-n0 )",
+ "(setup-machine-p4-n0 )",
+ "(make-product-p4-n0 )",
+ "(ship-order-o5-n0-n1 )",
+ "(setup-machine-p5-n1 )",
+ "(make-product-p5-n1 )",
+ "(ship-order-o4-n1-n2 )",
+ "(ship-order-o3-n2-n3 )",
+ "(ship-order-o2-n3-n4 )",
+ ),
+ "p05": (
+ "(setup-machine-p1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o1-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o5-n1-n0 )",
+ "(make-product-p1-n0 )",
+ "(setup-machine-p2-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o4-n1-n0 )",
+ "(make-product-p2-n0 )",
+ "(ship-order-o1-n0-n1 )",
+ "(setup-machine-p3-n1 )",
+ "(start-order-o2-n1-n0 )",
+ "(open-new-stack-n0-n1 )",
+ "(start-order-o3-n1-n0 )",
+ "(make-product-p3-n0 )",
+ "(setup-machine-p4-n0 )",
+ "(make-product-p4-n0 )",
+ "(ship-order-o5-n0-n1 )",
+ "(ship-order-o2-n1-n2 )",
+ "(setup-machine-p5-n2 )",
+ "(make-product-p5-n2 )",
+ "(ship-order-o4-n2-n3 )",
+ "(ship-order-o3-n3-n4 )",
+ ),
+ }
+
+ def make_formula(self, instance_id: int, domain: Path, problem: Path) -> str:
+ """Make the formula for the given instance."""
+ tegs = json.loads(self.TEGS.read_text())
+ return tegs[f"p{instance_id:02d}"]
+
+ def get_domain_file(self, instance_id: int) -> Path:
+ """Get the domain file for the given instance."""
+ return self.PATH_TO_DOMAINS_DIR / f"domain_p{instance_id:02d}.pddl"
+
+ def get_instance_file(self, instance_id: int) -> Path:
+ """Get the instance file for the given instance."""
+ return self.PATH_TO_INSTANCES_DIR / f"p{instance_id:02d}.pddl"
+
+ def get_expected_plan(self, instance_id: int) -> Tuple[str, ...]:
+ """Get the expected plan for the given instance."""
+ return self.EXPECTED_PLANS[f"p{instance_id:02d}"]
+
+ @pytest.mark.parametrize(
+ "instance_id", list(range(MIN_INSTANCE_ID, MAX_INSTANCE_ID + 1))
+ )
+ def test_run(self, instance_id, val, default_planner):
+ """Test the instance with the given id."""
+ self._test_instance(instance_id, val=val, planner=default_planner)