Skip to content

Add a TestConsole #4394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: series/3.x
Choose a base branch
from

Conversation

morgen-peschke
Copy link

Implements suggestion in #4393

Copy link
Member

@djspiewak djspiewak left a comment

Choose a reason for hiding this comment

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

Is there a reason to have a separate logsRef? It seems redundant given that we have stderr/stdout

*/
final class TestConsole[F[_]: Parallel](
stdInSemaphore: Semaphore[F],
stdInStateRef: Ref[F, TestStdInState[F]],
Copy link
Member

Choose a reason for hiding this comment

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

I'm kind of surprised at this structure and would have expected something more like a Queue. Wouldn't it be more useful to allow users to build tests which supply input more ergonomically?

Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure I understand what specifically you're asking about.

If you're asking about the API, the users use TestConsole#write and TestConsole#writeln to supply input, they don't need to interact with the internal bits.

If you're asking about the structure of the internal bits, if this were implemented with a Queue, it would still need something like TestStdInState to track the partial lines, so it would only really get rid of the Semaphore.

Since I had to work with TestStdInState anyway, it was simpler to track the requests locally instead of delegating that to a Queue (especially since only one state needs that functionality anyway).

Copy link
Author

Choose a reason for hiding this comment

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

I've separated out the algebras that control stdIn and inspect the state of things to hopefully clarify how the user would build tests and supply input.

@iRevive
Copy link
Contributor

iRevive commented May 22, 2025

Would it make sense to shape a test console around an ADT? We have this implementation in otel4s: https://github.com/typelevel/otel4s/blob/4b5f51093e3bb2fbb20914f901a5752a88d8e26c/sdk/common/shared/src/test/scala/org/typelevel/otel4s/sdk/test/InMemoryConsole.scala#L28

The ADT offers enough flexibility to test operations.

P. S. To be fair, it doesn't support read operations, but it can be implemented with a separate read queue.

@iRevive
Copy link
Contributor

iRevive commented May 22, 2025

On the other hand, since TestConsole covers different scenarios, it might be worth exploring two different implementations.

@morgen-peschke
Copy link
Author

Is there a reason to have a separate logsRef? It seems redundant given that we have stderr/stdout

Those are for capturing meta information that's useful when tests fail, like that there was X amount left in the buffer at close, the exact sequence of reads and writes, etc.

For context: I initially wrote this as a local helper when writing tests for typelevel/log4cats#912, and the additional context was really helpful for debugging those tests.

@morgen-peschke morgen-peschke requested a review from djspiewak May 26, 2025 17:42
@morgen-peschke
Copy link
Author

Would it make sense to shape a test console around an ADT?

I think the main reason I went with a design that provided the full output of each stream, rather than an ADT tracking each operation, is that it would encourage writing tests using it which would be less sensitive to internal implementation changes.

It feels like a smell if we encourage users to write tests that care if there's a switch under the hood from console.println(fooNel.mkString_("\n")) to fooNel.traverse_(console.println(_)), since the observable behavior is the same.

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.

3 participants