Skip to content

Conversation

@dines-rl
Copy link
Contributor

No description provided.

@dines-rl dines-rl requested a review from sid-rl November 26, 2025 22:32
>>> if result.success:
... print("Task completed successfully!")
"""
# Ensure shell_name is set and cannot be overridden by user params
Copy link
Contributor

Choose a reason for hiding this comment

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

is it worth trying to make sure that the shell_name parameter is not exposed to the user at all? would probably be a hassle with types

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we want them to name their shells but we don't need to require it.


# Verify we're in the new directory
pwd_result = await shell.exec("pwd")
pwd = (await pwd_result.stdout()).strip()
Copy link
Contributor

Choose a reason for hiding this comment

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

is stripping necessary? we already remove trailing newlines when calling .stdout()

)


class AsyncNamedShell:
Copy link
Contributor

Choose a reason for hiding this comment

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

should we provide a way to close the stateful shell? how do we handle how long a stateful shell remains alive on the backend?

Copy link
Contributor

Choose a reason for hiding this comment

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

also since we're providing this as a first class object, it might make sense to provide a per-shell command history as a convenience method. what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good, future improvements

duration = end_time - start_time
assert duration >= 2.9 # Allow some margin for overhead

# Verify all commands executed in order
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure how this verifies commands executed in order

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the back end does it

@sid-rl sid-rl self-requested a review November 26, 2025 23:40
@dines-rl dines-rl merged commit c1e8f09 into main Nov 26, 2025
7 checks passed
@dines-rl dines-rl deleted the dines/named-shell-oo branch November 26, 2025 23:48
@stainless-app stainless-app bot mentioned this pull request Nov 26, 2025
Copy link
Contributor

@jrvb-rl jrvb-rl left a comment

Choose a reason for hiding this comment

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

Looks very nice!!

A couple of suggestions on the tests, but those could easily be TODOs for later as well; minor cleanups.


@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
async def test_shell_exec_sequential(self, devbox: AsyncDevbox) -> None:
"""Test sequential execution (queuing)."""
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this uses 'await shell.exec' throughouout, it isn't testing queueing; commands are not submitted before the previous one finishes

"""Test sequential async execution with queuing."""
shell = devbox.shell("test-shell-8")

# Start multiple async commands - they should queue and execute sequentially
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's also check that the time >= 3s for this test? That seems like a good idea

Comment on lines +864 to +876
result = await shell.exec(
'echo "line1" && echo "line2" && echo "line3"', stdout=lambda line: stdout_lines.append(line)
)

assert result.success is True
assert result.exit_code == 0
assert len(stdout_lines) > 0
stdout_combined = "".join(stdout_lines)
assert "line1" in stdout_combined
assert "line2" in stdout_combined
assert "line3" in stdout_combined
# Verify streaming captured same data as result
assert stdout_combined == await result.stdout()
Copy link
Contributor

Choose a reason for hiding this comment

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

At present, this isn't necessarily checking the queuing and callback behavior, since the callback might only be executed once for the whole output (it should be all available at the same time). If you add a sleep 1 or something in between, presumably this will ensure that you get 3 separate responses, and we could then check for those as separate things in stdout_lines w/o combining.

Comment on lines +892 to +893
stdout_combined = "".join(stdout_lines)
assert "async-line1" in stdout_combined
Copy link
Contributor

Choose a reason for hiding this comment

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

would be good to verify that len(stdout_lines) == 2 and the contents of each one

Comment on lines +938 to +940
# Test that additional params (like working_dir) are passed through correctly
# Note: shell_name should override any shell_name in params
result = await shell.exec("pwd", working_dir="/tmp")
Copy link
Contributor

Choose a reason for hiding this comment

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

q: does adding working_dir= to a persistent shell exec() function change the working dir for subsequent commands or not? Would be good to add a second "pwd" that verifies the documented behavior here, whatever it is.

assert stderr_combined == await result.stderr()

@pytest.mark.timeout(TWO_MINUTE_TIMEOUT)
async def test_shell_exec_async_with_both_streams(self, devbox: AsyncDevbox) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Arguably we could just have sync/async versions of this test and then get rid of the separate stdout/stderr variants, just to simplify things a bit.

shell = devbox.shell("test-shell-9")
stdout_lines: list[str] = []

result = shell.exec(
Copy link
Contributor

Choose a reason for hiding this comment

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

same comment as the async case; better to have sleep in here, check the length of stdout_lines and the contents of each line separately

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.

4 participants