Skip to content

#5607 add methods Send Input To Process, Read Output From Process, Process Output Should Contain - #5612

Open
IlfirinPL wants to merge 7 commits into
robotframework:masterfrom
IlfirinPL:master
Open

#5607 add methods Send Input To Process, Read Output From Process, Process Output Should Contain#5612
IlfirinPL wants to merge 7 commits into
robotframework:masterfrom
IlfirinPL:master

Conversation

@IlfirinPL

Copy link
Copy Markdown
Contributor

Add Following Methods
#5607 add methods Send Input To Process, Read Output From Process, Process Output Should Contain

@pekkaklarck

Copy link
Copy Markdown
Member

I took a quick look at the code and I'm sorry to say that it doesn't look too good. Especially Read Output From Process is problematic. Requiring users to write outputs to files and then just reading content from them avoids deadlocks and other such problems, but it has various other problems:

  1. Requiring outputs to be redirected to files is not convenient.
  2. If outputs are redirected to files, users can as easily read content from them directly.
  3. If you read outputs multiple times, there's no way to get just the new content.
  4. There is no reliable way to make sure the process has written all content. The keyword has a sleep in the beginning that apparently tries to accomplish that, but sleeping is not a good approach for synchronization.

There are also various problems with the code style, test coverage, etc., but I don't want to spend time going through these problems in detail if the above issues aren't resolved. A problem is that I'm not sure is it even possible to fix the problems reliably.

@themavik themavik left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changes — the implementation looks correct and follows the existing patterns.

@themavik themavik left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could break if Python changes the NameError hint text—the stderr assertions match the full "Did you mean..." string.

nit: read_output_from_process slurps the whole stdout/stderr file each call; fine for interactive tests, noisy if someone points it at a huge log.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new Process library keywords intended to support interactive processes by writing to stdin, reading output during execution, and asserting on that output, along with acceptance tests.

Changes:

  • Added Send Input To Process, Read Output From Process, and Process Output Should Contain keywords to Process.py.
  • Added an interactive acceptance test suite (interactive.robot) covering the new keywords.
  • Added a helper keyword in process_resource.robot to start an interactive Python process with redirected stdio.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 11 comments.

File Description
src/robot/libraries/Process.py Implements new interactive-process keywords for stdin writing, output reading, and output assertions.
atest/testdata/standard_libraries/process/process_resource.robot Adds a resource keyword to start an interactive Python process used by the new atests.
atest/testdata/standard_libraries/process/interactive.robot New atest suite validating interactive send/read/contain functionality.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Read STDERR
Send Input To Process error
${stderr} Read Output From Process stream=stderr lines=-1
Should Contain ${stderr} NameError: name 'error' is not defined. Did you mean: 'OSError'?

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion checks the exact NameError message including the Did you mean: ... suggestion, which varies across Python versions and environments. To avoid brittle atests, assert only the stable portion (e.g. NameError: name 'error' is not defined) rather than the full suggested text.

Copilot uses AI. Check for mistakes.
Send Input To Process error
${stdout} ${stderr} Read Output From Process stream=both lines=-1
Should Be Equal ${stdout} Hello
Should Contain ${stderr} NameError: name 'error' is not defined. Did you mean: 'OSError'?

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same brittleness as above: the Did you mean: ... part of NameError messages is not stable across Python versions. Prefer asserting only the stable substring.

Copilot uses AI. Check for mistakes.
Comment on lines +1108 to +1123
# Encode input data if it's a string
if isinstance(input_data, str):
data_to_send = input_data
if end_of_line is not None:
data_to_send += end_of_line
input_bytes = console_encode(data_to_send, force=True)
elif isinstance(input_data, bytes):
input_bytes = input_data
if end_of_line is not None:
input_bytes += console_encode(end_of_line, force=True)
else:
input_bytes = (
console_encode(end_of_line, force=True)
if end_of_line is not None
else b""
)

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

send_input_to_process claims to encode strings using the process output_encoding, but it currently always uses console_encode(..., force=True) without passing the process’ configured encoding. This will send incorrectly encoded bytes when the process was started with output_encoding other than CONSOLE (e.g. SYSTEM, UTF-8). Use the encoding stored in the associated ProcessResult (or configuration) when encoding both input_data and end_of_line.

Suggested change
# Encode input data if it's a string
if isinstance(input_data, str):
data_to_send = input_data
if end_of_line is not None:
data_to_send += end_of_line
input_bytes = console_encode(data_to_send, force=True)
elif isinstance(input_data, bytes):
input_bytes = input_data
if end_of_line is not None:
input_bytes += console_encode(end_of_line, force=True)
else:
input_bytes = (
console_encode(end_of_line, force=True)
if end_of_line is not None
else b""
)
# Determine encoding configured for this process, if any.
encoding = (
getattr(process, "encoding", None)
or getattr(process, "output_encoding", None)
)
def _encode(text: str) -> bytes:
"""Encode text according to the process output encoding."""
if encoding in (None, "CONSOLE"):
# Preserve existing behavior when no encoding is configured
# or when console encoding is explicitly requested.
return console_encode(text, force=True)
if encoding in ("SYSTEM", LOCALE_ENCODING):
return system_encode(text)
try:
return text.encode(encoding, errors="replace")
except (LookupError, TypeError):
# Fallback to console encoding if the encoding is invalid
# or not a usable value.
return console_encode(text, force=True)
# Encode input data if it's a string
if isinstance(input_data, str):
data_to_send = input_data
if end_of_line is not None:
data_to_send += end_of_line
input_bytes = _encode(data_to_send)
elif isinstance(input_data, bytes):
input_bytes = input_data
if end_of_line is not None:
input_bytes += _encode(end_of_line)
else:
input_bytes = _encode(end_of_line) if end_of_line is not None else b""

Copilot uses AI. Check for mistakes.
Comment on lines +1071 to +1076
def send_input_to_process(
self,
input_data: "str | bytes | None" = None,
handle: Handle = None,
end_of_line: "str | None" = "\n",
) -> None:

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The positional argument order of send_input_to_process makes Send Input To Process ${handle} text interpret ${handle} as input_data. Because most Process keywords take handle as the first argument (and the linked issue example shows handle first), consider moving handle to be the first argument (or otherwise supporting the common “handle first” call style) to avoid a confusing API.

Copilot uses AI. Check for mistakes.
Comment on lines +1195 to +1200
else:
if stream in ("stderr", "both"):
raise RuntimeError(
"Process not started with stderr redirected to file. "
"Use: Start Process | cmd | stderr=/path/file |"
)

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above for stderr: the validation uses the original (potentially mixed/upper-case) stream value. Normalize stream before these checks so stream=STDERR and similar behave correctly.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +23
Send Input To Process print('Hello')
${stdout} Read Output From Process
Should Be Equal ${stdout} Hello

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test assumes the latest stdout line equals exactly Hello, but the interactive Python process may write additional lines (banner/prompt) into the redirected stdout file. To make the test robust, consider reading more lines (lines=-1) and asserting Should Contain for Hello, or starting Python in quiet mode (e.g. -q) so the output is deterministic.

Copilot uses AI. Check for mistakes.

Read Output From Process stderr
Send Input To Process error
Process Output Should Contain NameError: name 'error' is not defined. Did you mean: 'OSError'? stream=stderr lines=-1

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same brittleness as above: this exact NameError message (including suggestions) varies across Python versions/environments. Prefer matching only the stable part.

Copilot uses AI. Check for mistakes.
Comment on lines +1179 to +1184
else:
if stream in ("stdout", "both"):
raise RuntimeError(
"Process not started with stdout redirected to file. "
"Use: Start Process | cmd | stdout=/path/file |"
)

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read_output_from_process treats stream case-sensitively in the “not redirected to file” checks, but later lowercases it. Passing stream=STDOUT/STDERR/BOTH will skip these guards and can return empty output instead of raising the intended error. Normalize stream (e.g. .lower()) before the validation checks and use the normalized value consistently.

Copilot uses AI. Check for mistakes.
Comment on lines +1172 to +1174
logger.debug(f"Read std output from file:{result_obj.stdout_path}")
with open(result_obj.stdout_path, "rb") as f:
stdout = f.read()

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read_output_from_process reads the entire stdout/stderr file into memory on every call and then splits all lines to trim. In interactive use where this keyword is called repeatedly, this becomes increasingly expensive as the files grow. Consider tracking per-handle read offsets (to return only new output) or tailing only the needed last N lines instead of reading the whole file each time.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +79
Run Python Process Interactive
[Arguments] ${command}=${EMPTY} ${alias}=${NONE} ${stdout}=${STDOUT} ${stderr}=${STDERR} ${stdin}=PIPE
${result}= Start Process python -i ${command}
... alias=${alias} stdout=${stdout} stderr=${stderr} stdin=${stdin}
RETURN ${result}

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keyword name Run Python Process Interactive is misleading because it uses Start Process and returns a process handle (like the existing Start Python Process). Consider renaming to Start Python Process Interactive (or changing it to use Run Process and return a result) so callers can infer what it returns.

Suggested change
Run Python Process Interactive
[Arguments] ${command}=${EMPTY} ${alias}=${NONE} ${stdout}=${STDOUT} ${stderr}=${STDERR} ${stdin}=PIPE
${result}= Start Process python -i ${command}
... alias=${alias} stdout=${stdout} stderr=${stderr} stdin=${stdin}
RETURN ${result}
Start Python Process Interactive
[Arguments] ${command}=${EMPTY} ${alias}=${NONE} ${stdout}=${STDOUT} ${stderr}=${STDERR} ${stdin}=PIPE
${handle}= Start Process python -i ${command}
... alias=${alias} stdout=${stdout} stderr=${stderr} stdin=${stdin}
RETURN ${handle}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants