From a0b71ba9fa8defee40b4a76a1f90333ef83e8cef Mon Sep 17 00:00:00 2001 From: Pedro N Date: Thu, 27 Aug 2026 10:37:38 -0300 Subject: [PATCH 1/3] Feat: Add docker setup for ASTOR --- .dockerignore | 10 +++++ Dockerfile | 30 ++++++++++++++ docker-run.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-run.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..255684c70 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +# Files/directories ignored by the container when being built +target +#examples +.github +docs +.git +.gitignore +CITATION.cff +LICENSE +README.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..536a862e2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM maven:3.9.16-eclipse-temurin-26-alpine AS build + +WORKDIR /astor +COPY . . +RUN --mount=type=cache,target=/root/.m2/repository mvn clean package -DskipTests -B + +FROM eclipse-temurin:26 AS astor_image + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + wget gnupg software-properties-common maven \ + && wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /etc/apt/trusted.gpg.d/adoptium.gpg \ + && echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" > /etc/apt/sources.list.d/adoptium.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + temurin-8-jdk \ + temurin-11-jdk \ + temurin-17-jdk \ + temurin-25-jdk \ + temurin-26-jdk \ + # for the entrypoint + python3 \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +COPY --from=build /astor/target /astor/target +COPY docker-run.py docker-run.py +RUN chmod +x docker-run.py + +ENTRYPOINT ["python3", "docker-run.py"] \ No newline at end of file diff --git a/docker-run.py b/docker-run.py new file mode 100644 index 000000000..76ff6b0b6 --- /dev/null +++ b/docker-run.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# docker-run.py +import os +import sys +from itertools import batched +from shutil import copytree, rmtree +import glob + +INPUT_DIR = "/input-project" +TARGET_DIR = "/target-project" + +def astor_jar() -> str: + matches = glob.glob("/astor/target/astor-*-jar-with-dependencies.jar") + if not matches: + print("Astor jar not found in /astor/target") + exit(1) + return matches[0] + +def prepare_target_project() -> None: + if not os.path.isdir(INPUT_DIR): + print(f"No mounted project in {INPUT_DIR}. Use -v :{INPUT_DIR}:ro") + exit(1) + if os.path.exists(TARGET_DIR): + rmtree(TARGET_DIR) + copytree(INPUT_DIR, TARGET_DIR) + +# Allowed arguments. See docs/arguments.md +ALLOWED_ARGS: set[str] = { + "javacompliancelevel", + "location", + "srcjavafolder", + "srctestfolder", + "binjavafolder", + "bintestfolder", + "resourcesfolder", + "mode", + "maxtime", + "maxGeneration", + "population", + "workingDirectory", + "stopfirst", + "maxnumbersolutions", + "maxsuspcandidates", + "faultlocalization", + "maxmodificationpoints", + "flthreshold", + "regressionforfaultlocalization", + "packageToInstrument", + "skipfaultlocalization", + "skipfitnessinitialpopulation", + "scope", + "modificationpointnavigation", + "mutationrate", + "validation", + "tmax1", + "tmax2", + "overridemaxtime", + "testbystep", + "ignoredTestCases", + "savesolution", + "saveall", + "multipointmodification", + "pvariantfoldername", + "loglevel", + "logpatternlayout", + "jsonoutputname", + "parameters" +} + +def build_command(argv: list[str]) -> list[str]: + args: list[str] = [ + "java", + "-cp", + astor_jar(), + "fr.inria.main.evolution.AstorMain", + "-location", + "/target-project" + ] + # We iterate the parameters as a batch, so we can catch the parameter's value + for pair in batched(argv, 2): + if len(pair) != 2: + print(f"Missing value for parameter: {pair[0]}") + exit(1) + + if pair[0][1:] == "location": + # We override the location to point to our /target-project + continue + + if pair[0][1:] not in ALLOWED_ARGS: + print(f"An unknown parameter has been given: {pair[0]}") + exit(1) + args.append(pair[0]) + args.append(pair[1]) + + return args + + +if __name__ == "__main__": + argv = sys.argv[1:] + if len(argv) == 0: + print(f"Usage: {sys.argv[0]} ") + exit(1) + + prepare_target_project() + command = build_command(argv) + os.execvp(command[0], command) \ No newline at end of file From ca2e77f2cb1be901d1a01f62c02dbc505f680796 Mon Sep 17 00:00:00 2001 From: Pedro N Date: Thu, 27 Aug 2026 16:20:36 -0300 Subject: [PATCH 2/3] Chore: Solve output location permissions --- Dockerfile | 7 +++++-- docker-run.py | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 536a862e2..0b3e78d81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get clean && rm -rf /var/lib/apt/lists/* COPY --from=build /astor/target /astor/target -COPY docker-run.py docker-run.py -RUN chmod +x docker-run.py +COPY docker-run.py /astor/docker-run.py +RUN chmod a+x /astor/docker-run.py +RUN chmod -R 777 /astor + +WORKDIR /astor ENTRYPOINT ["python3", "docker-run.py"] \ No newline at end of file diff --git a/docker-run.py b/docker-run.py index 76ff6b0b6..ade53874d 100644 --- a/docker-run.py +++ b/docker-run.py @@ -7,7 +7,7 @@ import glob INPUT_DIR = "/input-project" -TARGET_DIR = "/target-project" + def astor_jar() -> str: matches = glob.glob("/astor/target/astor-*-jar-with-dependencies.jar") @@ -16,13 +16,27 @@ def astor_jar() -> str: exit(1) return matches[0] -def prepare_target_project() -> None: + +def prepare_target_project() -> str: if not os.path.isdir(INPUT_DIR): - print(f"No mounted project in {INPUT_DIR}. Use -v :{INPUT_DIR}:ro") + print(f"No mounted project in {INPUT_DIR}. Use -v :{INPUT_DIR}/:ro") exit(1) - if os.path.exists(TARGET_DIR): - rmtree(TARGET_DIR) - copytree(INPUT_DIR, TARGET_DIR) + + entries = os.listdir(INPUT_DIR) + if len(entries) != 1: + print(f"Expected exactly one project mounted in {INPUT_DIR}, found: {entries}") + exit(1) + + project_name = entries[0] + source = os.path.join(INPUT_DIR, project_name) + target = os.path.join("/astor", project_name) + + if os.path.exists(target): + rmtree(target) + copytree(source, target) + + return target + # Allowed arguments. See docs/arguments.md ALLOWED_ARGS: set[str] = { @@ -64,28 +78,27 @@ def prepare_target_project() -> None: "loglevel", "logpatternlayout", "jsonoutputname", - "parameters" + "parameters", } -def build_command(argv: list[str]) -> list[str]: + +def build_command(argv: list[str], target_dir: str) -> list[str]: args: list[str] = [ "java", "-cp", astor_jar(), "fr.inria.main.evolution.AstorMain", "-location", - "/target-project" + target_dir, ] # We iterate the parameters as a batch, so we can catch the parameter's value for pair in batched(argv, 2): if len(pair) != 2: print(f"Missing value for parameter: {pair[0]}") exit(1) - if pair[0][1:] == "location": - # We override the location to point to our /target-project + # We override the location to point to our target dir continue - if pair[0][1:] not in ALLOWED_ARGS: print(f"An unknown parameter has been given: {pair[0]}") exit(1) @@ -101,6 +114,6 @@ def build_command(argv: list[str]) -> list[str]: print(f"Usage: {sys.argv[0]} ") exit(1) - prepare_target_project() - command = build_command(argv) + target_dir = prepare_target_project() + command = build_command(argv, target_dir) os.execvp(command[0], command) \ No newline at end of file From 34397a7d9b9df3b1449c44309b9d46691b23c7ac Mon Sep 17 00:00:00 2001 From: Pedro N Date: Thu, 27 Aug 2026 16:50:35 -0300 Subject: [PATCH 3/3] Ignore I/O related parameters --- docker-run.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docker-run.py b/docker-run.py index ade53874d..3c430e94b 100644 --- a/docker-run.py +++ b/docker-run.py @@ -41,7 +41,6 @@ def prepare_target_project() -> str: # Allowed arguments. See docs/arguments.md ALLOWED_ARGS: set[str] = { "javacompliancelevel", - "location", "srcjavafolder", "srctestfolder", "binjavafolder", @@ -51,7 +50,6 @@ def prepare_target_project() -> str: "maxtime", "maxGeneration", "population", - "workingDirectory", "stopfirst", "maxnumbersolutions", "maxsuspcandidates", @@ -81,6 +79,11 @@ def prepare_target_project() -> str: "parameters", } +# Parameters that shouldn't be allowed as the container should deal with the hard work +BLACKLISTED_ARGS: set[str] = { + "location", + "workingDirectory", +} def build_command(argv: list[str], target_dir: str) -> list[str]: args: list[str] = [ @@ -96,12 +99,15 @@ def build_command(argv: list[str], target_dir: str) -> list[str]: if len(pair) != 2: print(f"Missing value for parameter: {pair[0]}") exit(1) - if pair[0][1:] == "location": - # We override the location to point to our target dir + + param: str = pair[0][1:] + if param in BLACKLISTED_ARGS: + # This parameter is related to the I/O directories which are already handled by the container continue - if pair[0][1:] not in ALLOWED_ARGS: + elif param not in ALLOWED_ARGS: print(f"An unknown parameter has been given: {pair[0]}") exit(1) + args.append(pair[0]) args.append(pair[1])