diff --git a/.github/workflows/aries.yml b/.github/workflows/aries.yml index f1c61d8e..249b2cfb 100644 --- a/.github/workflows/aries.yml +++ b/.github/workflows/aries.yml @@ -94,10 +94,26 @@ jobs: run: ./just ci-up-solve - name: Validator tests run: ./just ci-up-val + + + ipc-tests: + name: IPC Tests + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - name: Submodules initialization + run: git submodule update --init # get our own copy of unified-planing repos + - name: Install python dependencies + run: python3 -m pip install -r planning/unified/requirements.txt "numpy<2" + - name: Install just + run: curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to . + - name: Resolution + run: ./just ci-ipc tests: # Meta-job that only requires all test-jobs to pass - needs: [ lints, unit-tests, integration-tests, unified-planning-api, unified-planning-integration ] + needs: [ lints, unit-tests, integration-tests, unified-planning-api, unified-planning-integration, ipc-tests ] runs-on: ubuntu-latest steps: - run: true diff --git a/ci/ipc.py b/ci/ipc.py new file mode 100644 index 00000000..9a0b5eb3 --- /dev/null +++ b/ci/ipc.py @@ -0,0 +1,247 @@ +from pathlib import Path +import re +import subprocess # nosec: B404 +import sys +from tempfile import NamedTemporaryFile +import time +from typing import List, Optional, Tuple + +from unified_planning.engines.results import PlanGenerationResultStatus as Status +from unified_planning.io.pddl_reader import PDDLReader +from unified_planning.plans import Plan +from unified_planning.shortcuts import OneshotPlanner + + +# ============================================================================ # +# Constants # +# ============================================================================ # + + +ESC_TABLE = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "purple": 35, + "cyan": 36, + "white": 37, + "Black": 40, + "Red": 41, + "Green": 42, + "Yellow": 43, + "Blue": 44, + "Purple": 45, + "Cyan": 46, + "White": 47, + "bold": 1, + "light": 2, + "blink": 5, + "invert": 7, +} +VALID_STATUS = {Status.SOLVED_SATISFICING, Status.SOLVED_OPTIMALLY} + + +# ============================================================================ # +# Utils # +# ============================================================================ # + + +def write(text: str = "", **markup: bool) -> None: + """Write text with ANSI escape sequences for formatting.""" + for name in markup: + if name not in ESC_TABLE: + raise ValueError(f"Unknown markup: {name}") + esc = [ESC_TABLE[name] for name, value in markup.items() if value] + if esc: + text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m" + print(text) + + +def separator( + sep: str = "=", + title: Optional[str] = None, + width: int = 80, + before: str = "\n", + after: str = "\n", + **markup: bool, +) -> None: + """Print a separator line with optional title.""" + if title is None: + line = sep * (width // len(sep)) + else: + n = max((width - len(title) - 2) // (2 * len(sep)), 1) + fill = sep * n + line = f"{fill} {title} {fill}" + if len(line) + len(sep.rstrip()) <= width: + line += sep.rstrip() + write(f"{before}{line}{after}", **markup) + + +def big_separator( + sep: str = "=", + title: Optional[str] = None, + width: int = 80, + **markup: bool, +): + """Print a big separator line with optional title.""" + separator(sep=sep, title=None, width=width, before="\n", after="", **markup) + separator(sep=" ", title=title, width=width, before="", after="", **markup) + separator(sep=sep, title=None, width=width, before="", after="\n", **markup) + + +# ============================================================================ # +# Validators # +# ============================================================================ # + + +def extract_plan_for_val(plan: Plan, dom: Path, pb: Path, out: Path) -> None: + """Reformat a plan to be validated by Val.""" + + domain_content = dom.read_text() + problem_content = pb.read_text() + + def fix_upf_shit(name: str) -> str: + name_alt = name.replace("-", "_") + if name in domain_content or name in problem_content: + return name + if name_alt in domain_content or name_alt in problem_content: + return name_alt + raise ValueError(f"Name {name} not found in domain or problem") + + def format_line(line: str) -> str: + if line.startswith(";"): + return line + + regex = r"^\s*(?:(?P