diff --git a/share/revng/test/configuration/revng/storage.yml b/share/revng/test/configuration/revng/storage.yml index 465a327ac..b3123f6c5 100644 --- a/share/revng/test/configuration/revng/storage.yml +++ b/share/revng/test/configuration/revng/storage.yml @@ -11,4 +11,4 @@ commands: - type: revng-qa.compiled filter: example-executable-1 and with-debug-info command: |- - "${SOURCES_ROOT}/share/revng/test/tests/storage.sh" "$INPUT" "$$(temp -d)" + "${SOURCES_ROOT}/share/revng/test/tests/s3_storage.py" "$INPUT" "$$(temp -d)" diff --git a/share/revng/test/tests/s3_storage.py b/share/revng/test/tests/s3_storage.py new file mode 100755 index 000000000..e6ee9ef98 --- /dev/null +++ b/share/revng/test/tests/s3_storage.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 + +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# + +import atexit +import hashlib +import json +import socket +import sys +from pathlib import Path +from subprocess import DEVNULL, Popen +from uuid import uuid4 + +import yaml + +from revng.internal.api import make_manager + + +def get_free_port(): + """Get an unbound port to spawn the S3 server on. Note that this is not + failproof and might fail since there's a time gap between when the script + is run and the server actually binds the socket""" + with socket.socket() as s: + s.bind(("127.0.0.1", 0)) + return s.getsockname()[1] + + +def file_hash(path: str | Path): + with open(path, "rb") as f: + return hashlib.file_digest(f, "sha256").hexdigest() + + +def resolve_path(root: Path, subpath: str): + index_file = root / "index.yml._S3rver_object" + assert index_file.is_file() + with open(index_file) as f: + index_data = yaml.safe_load(f) + return root / f"{index_data[subpath]}._S3rver_object" + + +def main(): + input_binary = sys.argv[1] + temporary_dir = sys.argv[2] + temporary_dir_path = Path(temporary_dir) + + server_port = get_free_port() + # Run the server with the specified port + server_process = Popen( + [ + "s3rver", + "--directory", + temporary_dir, + "--address", + "127.0.0.1", + "--port", + str(server_port), + "--configure-bucket", + "test", + ], + stdout=DEVNULL, + stderr=DEVNULL, + ) + atexit.register(server_process.kill) + + workdir = f"s3://S3RVER:S3RVER@region+127.0.0.1:{server_port}/test/project-test-dir" + s3_root = temporary_dir_path / "test/project-test-dir" + manager = make_manager(workdir) + + with open(input_binary, "rb") as f: + manager.set_input("input", f.read()) + + manager.run_analyses_list("revng-initial-auto-analysis").unwrap() + + # Simple file check, this looks into the persistence directory of s3rver and + # checks that the input file and the file in 'begin/input' have the same + # contents. This is to guarantee that the S3 backend does not change the + # contents of the file. + s3_input_file_path = resolve_path(s3_root, "begin/input") + assert file_hash(input_binary) == file_hash(s3_input_file_path) + + # Produce some stuff and then check that the file is actually saved + step_name = "disassemble" + container_name = "assembly.ptml.tar.gz" + targets = manager.get_targets(step_name, container_name) + manager.produce_target(step_name, [t.serialize() for t in targets], container_name) + assert manager.save() + + s3_decompiled_path = resolve_path(s3_root, "disassemble/assembly.ptml.tar.gz") + assert s3_decompiled_path.is_file() and s3_decompiled_path.stat().st_size > 0 + s3_decompiled_metadata = s3_decompiled_path.parent / ( + s3_decompiled_path.name.removesuffix("._S3rver_object") + "._S3rver_metadata.json" + ) + with open(s3_decompiled_metadata) as f: + metadata = json.load(f) + assert metadata["content-encoding"] == "gzip" + + # Rename the first function in the model and check if it exists in the + # saved model + model = yaml.safe_load(manager.get_global("model.yml")) + first_function = model["Functions"][0] + unique_id = str(uuid4()).replace("-", "") + diff = { + "Path": f"/Functions/{first_function['Entry']}/CustomName", + "Add": unique_id, + "Remove": "", + } + if "CustomName" in first_function: + diff["Remove"] = first_function["CustomName"] + + diff_content = yaml.safe_dump({"Changes": [diff]}) + manager.run_analysis( + "initial", + "apply-diff", + {}, + {"apply-diff-global-name": "model.yml", "apply-diff-diff-content": diff_content}, + ).unwrap() + model_path = resolve_path(s3_root, "context/model.yml") + assert unique_id in model_path.read_text() + # Also check that the partial save did not delete some file + assert s3_decompiled_path.is_file() and s3_decompiled_path.stat().st_size > 0 + + +if __name__ == "__main__": + main() diff --git a/share/revng/test/tests/storage.sh b/share/revng/test/tests/storage.sh deleted file mode 100755 index 5592d788b..000000000 --- a/share/revng/test/tests/storage.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -# -# This file is distributed under the MIT License. See LICENSE.md for details. -# - -set -euo pipefail - -# Python oneliner to get an unbound port to spawn the S3 server on. Note that -# this is not failproof and might fail since there's a time gap between when -# the script is run and the server actually binds the socket -S3_PORT=$(python -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()') -# Run the server with the specified port -s3rver --directory "$2" --address 127.0.0.1 --port "$S3_PORT" --configure-bucket test &>/dev/null & -# Save the server pid to kill it on exit -S3RVER_PID=$! -trap 'kill -9 $S3RVER_PID' EXIT - -# Run revng with the server as the resume directory -revng artifact \ - --resume="s3://S3RVER:S3RVER@region+127.0.0.1:$S3_PORT/test/project-test-dir" \ - --analyze \ - -o /dev/null \ - disassemble "$1" - -# Simple file check, this looks into the persistence directory of s3rver and -# checks that the input file and the file in 'begin/input' have the same -# contents. This is to guarantee that the S3 backend does not change the -# contents of the file. -INDEX_FILE="$2/test/project-test-dir/index.yml._S3rver_object" -test -f "$INDEX_FILE" -INPUT_ID="$(grep -Po '(?<=^begin/input:).*' "$INDEX_FILE" | tr -d ' ' | tr -d "'")" -INPUT_FILE="$2/test/project-test-dir/$INPUT_ID._S3rver_object" -sha256sum --quiet --check <(sha256sum <"$INPUT_FILE") <"$1"