Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
__pycache__/
*.py[cod]
*$py.class
Expand Down
47 changes: 45 additions & 2 deletions goth/runner/probe/mixin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Probe mixins containing high-level steps."""

import ast
import asyncio
from datetime import datetime, timedelta, timezone
import logging
Expand Down Expand Up @@ -49,6 +50,40 @@ class ProbeProtocol(Protocol):
"""Payment configuration used for the probe's yagna node."""


def stdout_safe_decode(output):
"""Decode output of the exe_script stdout/stderr safely.

Decodes bytes, list of integers (0-255) or returns the string as is.
"""
if output is None:
return ""

if isinstance(output, str):
if output.startswith("[") and output.endswith("]"):
# bytes encoded as list of integers
try:
vec = ast.literal_eval(output)
if isinstance(vec, list) and all(isinstance(x, int) and 0 <= x <= 255 for x in vec):
b = bytes(vec)
output = b
else:
print("Error: String must represent a list of integers 0–255")
except (ValueError, SyntaxError):
print("Error: Invalid string format, returning original string")
return output
else:
return output

if isinstance(output, bytes):
try:
return output.decode("utf-8", errors="replace")
except Exception:
# fallback in case of unexpected encoding
return str(output)

return "Cannot decode"


class ActivityApiMixin:
"""Probe mixin providing high-level test steps which use yagna activity API."""

Expand Down Expand Up @@ -80,8 +115,10 @@ async def collect_results(
last_index = -1

while len(results) < num_results:
current_results = await self.api.activity.control.get_exec_batch_results(
activity_id, batch_id, timeout=1
current_results: List[ExeScriptCommandResult] = (
await self.api.activity.control.get_exec_batch_results(
activity_id, batch_id, timeout=1
)
)

# Check for new results
Expand All @@ -99,6 +136,12 @@ async def collect_results(
if result.result == "Error":
error_msg = result.message or "Unknown error"
logger.error("Execution failed with error: %s", error_msg)
logger.info(
"Full stdout of failed command: %s", stdout_safe_decode(result.stdout)
)
logger.info(
"Full stderr of failed command: %s", stdout_safe_decode(result.stderr)
)
raise RuntimeError(f"Activity execution failed: {error_msg}")

results = current_results
Expand Down
4 changes: 2 additions & 2 deletions goth/runner/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ async def _run_command():

out, err = await proc.communicate()

logger.info(f"Command finished with return code: {proc.returncode}")
logger.info(f"Local command finished with return code: {proc.returncode}")
# Always log output regardless of success/failure
if out:
output_text = out.decode("utf-8").strip()
cmd_logger.log(log_level, f"{log_prefix}{output_text}")

if proc.returncode:
error_msg = f"Command failed (exit code {proc.returncode}): {' '.join(args)}"
error_msg = f"Local command failed (exit code {proc.returncode}): {' '.join(args)}"
if out:
error_msg += f"\nOUTPUT: {out.decode('utf-8').strip()}"
raise CommandError(error_msg)
Expand Down