Files
Alessandro Di Federico ba186a130c s/module.ll/module.bc.zstd/g
2024-11-12 14:58:00 +01:00

403 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
import asyncio
import io
import json
import os
import signal
import sys
from subprocess import STDOUT, Popen, TimeoutExpired
from tempfile import TemporaryDirectory, TemporaryFile
from typing import Any, AsyncGenerator
import yaml
from aiohttp.client import ClientConnectionError, ClientSession, ClientTimeout
from aiohttp.connector import UnixConnector
from aiohttp.tracing import TraceConfig, TraceRequestChunkSentParams, TraceRequestEndParams
from aiohttp.tracing import TraceRequestHeadersSentParams
from gql import Client, gql
from gql.client import AsyncClientSession
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.exceptions import TransportQueryError
from pytest import Config, ExceptionInfo, TestReport, mark
from pytest_asyncio import fixture
from revng.pipeline_description import YamlLoader # type: ignore
pytestmark = mark.asyncio
FILTER_ENV = [
"STARLETTE_DEBUG",
"REVNG_NOTIFY_FIFOS",
"REVNG_ORIGINS",
"REVNG_DATA_DIR",
"REVNG_PROJECT_ID",
]
def log(string: Any):
sys.stderr.write(f"{string}\n")
sys.stderr.flush()
def print_fd(fd: int):
os.lseek(fd, 0, io.SEEK_SET)
out_read = os.fdopen(fd, "r")
log(out_read.read())
async def check_server_up(connector):
async with ClientSession(
connector=connector, connector_owner=False, timeout=ClientTimeout(total=2.0)
) as session:
for _ in range(10):
try:
async with session.get("http://dummyhost/status") as req:
if req.status == 200:
return
await asyncio.sleep(1.0)
except ClientConnectionError:
await asyncio.sleep(1.0)
raise ValueError()
async def header_trace(session, trace_config_ctx, params: TraceRequestHeadersSentParams):
log(f"URL: {params.url}")
log(f"METHOD: {params.method}")
log(f"HEADERS: {params.headers}")
async def payload_trace(session, trace_config_ctx, params: TraceRequestChunkSentParams):
log(f"DATA: {params.chunk[:256]!r}")
async def response_trace(session, trace_config_ctx, params: TraceRequestEndParams):
log(f"RESPONSE: {params.response}")
@fixture
async def client(pytestconfig: Config, request) -> AsyncGenerator[AsyncClientSession, None]:
temp_dir = TemporaryDirectory(prefix="revng-daemon-test-")
log_file = TemporaryFile("wb+", prefix="revng-daemon-test-log-")
socket_path = f"{temp_dir.name}/daemon.sock"
new_env = {k: v for k, v in os.environ.items() if k not in FILTER_ENV}
process = Popen(
[
"revng",
"daemon",
"-b",
f"unix:{socket_path}",
],
stdout=log_file.fileno(),
stderr=STDOUT,
env=new_env,
)
def stop_daemon():
if process.returncode is not None:
return process.returncode
process.send_signal(signal.SIGINT)
try:
return process.wait(30.0)
except TimeoutExpired:
process.send_signal(signal.SIGKILL)
return process.wait()
def error_handler(e: BaseException):
return_code = stop_daemon()
log_file.seek(0)
log("\n\n########## BEGIN DAEMON LOG ##########\n\n")
log(log_file.read().decode("utf-8"))
log("\n\n########## END DAEMON LOG ##########\n\n")
log(f"The daemon exited with code {return_code}\n")
raise e
connector = UnixConnector(socket_path, force_close=True)
try:
await check_server_up(connector)
except ValueError as e:
error_handler(e)
binary = pytestconfig.getoption("binary")
tracing = TraceConfig()
tracing.on_request_headers_sent.append(header_trace)
tracing.on_request_chunk_sent.append(payload_trace)
tracing.on_request_end.append(response_trace)
transport = AIOHTTPTransport(
"http://dummyhost/graphql/",
client_session_args={
"connector": connector,
"timeout": ClientTimeout(),
"trace_configs": [tracing],
},
)
gql_client = Client(transport=transport, fetch_schema_from_transport=True, execute_timeout=None)
upload_q = gql(
"""
mutation upload($file: Upload!) {
uploadFile(file: $file, container: "input")
}
"""
)
try:
async with gql_client as session:
with open(binary, "rb") as binary_file:
await session.execute(
upload_q, variable_values={"file": binary_file}, upload_files=True
)
yield session
except Exception as e:
error_handler(e)
test_report: TestReport = request.node.rep_call
if test_report.failed:
if isinstance(test_report.longrepr, ExceptionInfo):
error_handler(test_report.longrepr.value)
else:
error_handler(ValueError(test_report.longreprtext))
# Terminate the daemon gracefully
return_code = stop_daemon()
# Check that the daemon exited cleanly
if return_code != 0:
error_handler(ValueError(f"Daemon exited with non-zero return code: {return_code}"))
async def test_pipeline_description(client):
q = gql("{ pipelineDescription }")
result = await client.execute(q)
yaml.load(result["pipelineDescription"], Loader=YamlLoader)
async def get_description(client):
q = gql("{ pipelineDescription }")
result = await client.execute(q)
return yaml.load(result["pipelineDescription"], Loader=YamlLoader)
async def test_info(client):
desc = await get_description(client)
binary_kind = next(k for k in desc.Kinds if k.Name == "binary")
isolated_kind = next(k for k in desc.Kinds if k.Name == "isolated-root")
assert binary_kind.Rank == "binary"
assert binary_kind.Parent == ""
assert isolated_kind.Rank == "binary"
assert isolated_kind.Parent == "root"
root_rank = next(r for r in desc.Ranks if r.Name == "binary")
function_rank = next(r for r in desc.Ranks if r.Name == "function")
assert root_rank.Depth == 0
assert root_rank.Parent == ""
assert function_rank.Depth == 1
assert function_rank.Parent == "binary"
begin_step = next(s for s in desc.Steps if s.Name == "begin")
initial_step = next(s for s in desc.Steps if s.Name == "initial")
assert begin_step.Parent == ""
assert initial_step.Parent == "begin"
container_names = [c.Name for c in desc.Containers]
assert "module.bc.zstd" in container_names
assert "input" in container_names
input_container = next(c for c in desc.Containers if c.Name == "input")
assert input_container.MIMEType != ""
auto_analysis_found = False
for alist in desc.AnalysesLists:
if alist.Name == "revng-initial-auto-analysis":
auto_analysis_found = True
assert len(alist.Analyses) > 0, f"Analyses list {alist.Name} has 0 analyses"
assert auto_analysis_found, "revng-initial-auto-analysis not found in analyses lists"
async def test_info_global(client):
desc = await get_description(client)
assert "model.yml" in desc.Globals
result = await client.execute(gql('{ getGlobal(name: "model.yml") }'))
assert result["getGlobal"] is not None
async def get_index(client):
req = await client.execute(gql("{ index: contextCommitIndex }"))
return req["index"]
async def run_preliminary_analyses(client):
index = await get_index(client)
await client.execute(
gql(
"""mutation($ctt: String!, $index: BigInt!) {
runAnalysis(step: "initial", analysis: "import-binary",
containerToTargets: $ctt, index: $index) {
__typename
}
}"""
),
{"ctt": json.dumps({"input": [":binary"]}), "index": index},
)
async def test_lift(client):
await run_preliminary_analyses(client)
index = await get_index(client)
result = await client.execute(
gql(f'{{ produceArtifacts(step: "lift", paths: "", index: "{index}") {{ __typename }} }}')
)
assert result["produceArtifacts"]["__typename"] == "Produced"
async def test_lift_ready_fail(client):
await run_preliminary_analyses(client)
index = await get_index(client)
q = gql(
f'{{ produceArtifacts(step: "lift", paths: ":binary", onlyIfReady: true, index: "{index}")'
+ "{ __typename } }"
)
try:
await client.execute(q)
raise ValueError("Exception expected")
except TransportQueryError as e:
assert len(e.errors) == 1
assert e.errors[0]["message"] == "Path components need to equal kind rank"
async def test_get_model(client):
await run_preliminary_analyses(client)
index = await get_index(client)
await client.execute(
gql(f'{{ produceArtifacts(step: "lift", paths: "", index: "{index}") {{ __typename }} }}')
)
result = await client.execute(gql('{ getGlobal(name: "model.yml") }'))
assert result["getGlobal"] is not None
async def test_targets(client):
q = gql(
"""
{
begin: targets(step: "begin", container: "input") {
kind
ready
}
lift: targets(step: "lift", container: "module.bc.zstd") {
kind
ready
}
}
"""
)
result = await client.execute(q)
binary_target = next(t for t in result["begin"] if t["kind"] == "binary")
lift_target = next(t for t in result["lift"] if t["kind"] == "root")
assert binary_target["ready"]
assert not lift_target["ready"]
async def test_produce(client):
await run_preliminary_analyses(client)
index = await get_index(client)
q = gql(
'{ produce(step: "lift", container: "module.bc.zstd", targetList: ":root", '
+ f'index: "{index}")'
+ "{ __typename } }"
)
result = await client.execute(q)
assert result["produce"]["__typename"] == "Produced"
async def test_produce_artifact(client):
await run_preliminary_analyses(client)
index = await get_index(client)
q = gql(f'{{ produceArtifacts(step: "lift", index: "{index}") {{ __typename }} }}')
result = await client.execute(q)
assert "produceArtifacts" in result
assert result["produceArtifacts"]["__typename"] == "Produced"
async def test_function_endpoint(client):
await run_preliminary_analyses(client)
index = await get_index(client)
q = gql(
"""mutation($ctt: String!, $index: BigInt!) {
runAnalysis(step: "lift", analysis: "detect-abi",
containerToTargets: $ctt, index: $index) {
__typename
}
}"""
)
await client.execute(q, {"ctt": json.dumps({"module.bc.zstd": [":root"]}), "index": index})
q = gql(
"""{
targets(step: "isolate", container: "module.bc.zstd") {
serialized
}
}"""
)
result = await client.execute(q)
first_function = next(t for t in result["targets"] if not t["serialized"].startswith(":"))
index = await get_index(client)
q = gql(
"""query function($param1: String!, $index: BigInt!) {
produceArtifacts(step: "isolate", paths: $param1, index: $index) { __typename }
}"""
)
result = await client.execute(
q, {"param1": first_function["serialized"].rsplit(":", 1)[0], "index": index}
)
assert result["produceArtifacts"]["__typename"] == "Produced"
async def test_analysis_kind_check(client):
ctt = json.dumps({"module.bc.zstd": [":isolated-root"]})
index = await get_index(client)
q = gql(
"""mutation($ctt: String!, $index: BigInt!) {
runAnalysis(step: "lift", analysis: "detect-abi",
containerToTargets: $ctt, index: $index) {
__typename
}
}"""
)
try:
await client.execute(q, {"ctt": ctt, "index": index})
raise ValueError("Expected exception")
except TransportQueryError as e:
assert len(e.errors) == 1
assert "Wrong kind for analysis" in e.errors[0]["message"]
async def test_analyses_list(client):
index = await get_index(client)
q = gql(
"mutation { "
+ f'runAnalysesList(name: "revng-initial-auto-analysis", index: "{index}")'
+ "{ __typename } }"
)
result = await client.execute(q)
assert result["runAnalysesList"]["__typename"] == "Diff"