Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@dubinsky
Copy link
Contributor

@dubinsky dubinsky commented Aug 19, 2025

TL;DR: make sbt.testing.Framework implementation:

  • cleaner and easier to understand;
  • 600+ lines shorter;
  • behave more uniformly across the back-ends;
  • testable on all the back-ends.

Currently, code implementing sbt.testing.Framework for JVM, Scala.js and Scala Native suffers from severe code triplication: code doing the same thing is present in three places. This pull request removes this gratuitous redundancy.

An unfortunate - and inevitable - consequence of code duplication is code drift: different copies of the code do things differently for no good reason.

Backends sbt.testing.Framework runs on do have inherent differences: tests running on Scala.js and Scala Native are running outside of the JVM, and thus their outcomes need to be communicated to the process managing the test run; Scala.js is single-threaded by nature, which affects the way tests have to be run. Code differences not warranted by the inherent differences between the backends seem gratuitous, and this pull request removes some of them.

One area where implementations of the sbt.testing.Framework diverged from one another is the machinery to test them: only JVM implementation is currently testable. With this pull request, sbt.testing.Framework tests run on all the backends.

Another divergence is in the storage of test summaries: JVM uses AtomicReference[Vector], Scala.js uses Buffer, Scala Native uses ConcurrentLinkedQueue; this pull request unifies summary storage for all backends: AtomicReference[Vector] works for all of them.

Another divergence is the way test summary is rendered: counts of tests executed, ignored, or failed are included in the summary on JVM but not on Scala.js or Scala Native; setting test renderer to IntelliJ in the test arguments is honored only on JVM. This pull request makes test summary uniform across the backends.

Another divergence is: currently, signal handlers are installed only on JVM. This pull request installs them on all backends.

Another divergence affects a recent enhancement, which turned out to be effective only on JVM; this pull request applies it to all backends (and tests that it works ;)).

@kyri-petrou, your remark

I gave it a go at this (basically trying to unify the JVM / JS / Native implementations so that we can reuse the existing event test suites that exist for the JVM, but it doesn't seem possible.
... writing a full test suite from scratch (which frankly it's going to be extremely time consuming)

is on target: this was not trivial ;)

@dubinsky dubinsky force-pushed the zio-test-more-uniform-reporting branch 6 times, most recently from 61d736b to 246ce39 Compare August 20, 2025 04:52
@dubinsky dubinsky marked this pull request as draft August 20, 2025 05:29
@dubinsky dubinsky force-pushed the zio-test-more-uniform-reporting branch 6 times, most recently from e0332e7 to a908303 Compare August 20, 2025 23:32
@dubinsky dubinsky marked this pull request as ready for review August 20, 2025 23:46
@dubinsky dubinsky force-pushed the zio-test-more-uniform-reporting branch 8 times, most recently from e4458d5 to 5464462 Compare August 24, 2025 17:26
Currently, code implementing `sbt.testing.Framework` for JVM, Scala.js and Scala Native suffers from severe code triplication: code doing the same thing is present in three places. This pull request removes this gratuitous redundancy.

An unfortunate - and inevitable - consequence of code duplication is code drift: different copies of the code do things differently for no good reason.

Backends `sbt.testing.Framework` runs on _do_ have inherent differences: tests running on Scala.js and Scala Native are not running on JVM, and thus their outcomes need to be communicated to the process managing the test run; Scala.js is single-threaded by nature, which affects the way tests have to be run.
Code differences not warranted by the inherent differences between the backends seem gratuitous, and this pull request removes some of them.

One area where implementations of the `sbt.testing.Framework` diverged from one another is the machinery to test them: only JVM implementation is currently testable. With this pull request, `sbt.testing.Framework` tests run on all the backends.

Another divergence is in the storage of test summaries: JVM uses `AtomicReference[Vector]`, Scala.js uses `Buffer`, Scala Native uses `ConcurrentLinkedQueue`; this pull request unifies summary storage for all backends: `AtomicReference[Vector]` works for all of them.

Another divergence is the way test summary is rendered: counts of tests executed, ignored, or failed are included in the summary on JVM but not on Scala.js or Scala Native; setting test renderer to IntelliJ in the test arguments is honored only on JVM. This pull request makes test summary uniform across the backends.

Another divergence is: currently, signal handlers are installed only on JVM. This pull request installs them on all backends.

Another divergence affects a recent [enhancement](zio#9756), which turned out to be effective only on JVM; this pull request applies it to all backends (and tests that it works ;)).

@kyri-petrou, your [remark](zio#9629 (comment))

> I gave it a go at this (basically trying to unify the JVM / JS / Native implementations so that we can reuse the existing event test suites that exist for the JVM, but it doesn't seem possible.
> ... writing a full test suite from scratch (which frankly it's going to be extremely time consuming)

is on target: this was not trivial ;)
@dubinsky dubinsky force-pushed the zio-test-more-uniform-reporting branch from 5464462 to af719de Compare August 24, 2025 18:25
@dubinsky
Copy link
Contributor Author

@kyri-petrou what do I need to do for this to get merged? Thanks!

@kyri-petrou
Copy link
Contributor

what do I need to do for this to get merged? Thanks!

@dubinsky I'm trying to get through the PR but it's taking some time. I'll leave comments when I'm finished

@dubinsky
Copy link
Contributor Author

what do I need to do for this to get merged? Thanks!

@dubinsky I'm trying to get through the PR but it's taking some time. I'll leave comments when I'm finished

Thank you!!

Copy link
Contributor

@kyri-petrou kyri-petrou left a comment

Choose a reason for hiding this comment

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

Overall looks good to me, just a few minor-ish comments.

The one main comment I do have though is, can you please make sure that these changes don't break the IntelliJ ZIO plugin?

Comment on lines 8 to 12
lazy val red: String => String = colored(Console.RED)
lazy val green: String => String = colored(Console.GREEN)
lazy val cyan: String => String = colored(Console.CYAN)
lazy val blue: String => String = colored(Console.BLUE)
lazy val yellow: String => String = colored(Console.YELLOW)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need these to be lazy, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They always were for some (or no) reason; changing this now.

Copy link
Contributor Author

@dubinsky dubinsky Aug 25, 2025

Choose a reason for hiding this comment

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

done in 174ab24


protected def sharedRuntimeSupported: Boolean

private val summaries: AtomicReference[Vector[Summary]] = new AtomicReference(Vector.empty)
Copy link
Contributor

Choose a reason for hiding this comment

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

Very minor, but ConcurrentLinkedQueue is supported for all Scala flavours (JVM, JS and Native) and should be a lot more memory-friendly than using a Vector and appending items to it.

Alternatively, you could use AtomicReference[List[Summary]], prepend the summaries and the reverse the order in the end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I followed the approach currently used on JVM :)
Changing to ConcurrentLinkedQueue now.
Thank you!

Copy link
Contributor Author

@dubinsky dubinsky Aug 25, 2025

Choose a reason for hiding this comment

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

done in 174ab24

Comment on lines 139 to 140
testEventHandler: ZTestEventHandler,
console: Console
Copy link
Contributor

Choose a reason for hiding this comment

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

I would keep the old method with the old signature, overload it, deprecate it and use the new method (where you need to use it). The reason for this is there is a discrepancy between the zio-test-sbt and zio-test versions for some reason in a codebase it'll start throwing errors

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Doing that.

Copy link
Contributor Author

@dubinsky dubinsky Aug 25, 2025

Choose a reason for hiding this comment

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

done in 174ab24

import scala.collection.mutable.ArrayBuffer
import scala.util.Try

object ZTestFrameworkSbtSpec {
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, why do you want to throw away this code? I'm not against it but I thought perhaps it had a reason for being there

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it was there since the beginning, when there was no way to test using sbt.testing.Framework at all, even on JVM. When this became possible, some tests were moved (or copied) to real automated testing. As the behavior of test-sbt changed, the test left behind in this class eventually started failing, since they were checking the actual framework output, not the events emitted, and were disabled one-by-one. I moved whatever was salvageable and deleted the rest, which did not do anything anyway. The reason I think this should be removed is: it creates confusion about where new tests should be added; for instance, when working on #9756, I added tests in this class - and that was the wrong place to add them ;)

final override def done(): String = {
// If tests are forked, this will only be relevant in the forked
// JVM, and will not be set in the original JVM.
sharedRuntimes.get.foreach(_.unsafe.shutdown()(zio.Unsafe))
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 use getAndSet(Vector.empty) instead here? Or it doesn't matter?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also potentially we can use ConcurrentLinkedQueue as per my previous comment as well to ensure that we're closing each one exactly once

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Calling Runner.done() more than once is not a supported scenario. I am switching to ConcurrentLinkedQueue as you suggested :)

Copy link
Contributor Author

@dubinsky dubinsky Aug 25, 2025

Choose a reason for hiding this comment

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

done in 174ab24

import sbt.testing._

/**
* TODO update/remove outdated/misplaced doc comment.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this still TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think so.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comment was outdated and/or misplaced before my pull request; it's ok for it to stay that way a bit longer I 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.

turned TODO into a note

Comment on lines 25 to 26
// TODO SBT loggers should be hooked in...
final private[sbt] def run(eventHandler: EventHandler)(implicit trace: zio.Trace): ZIO[Any, Throwable, Unit] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this TODO still needs to be resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Currently, test-sbt ignores the sbt loggers supplied via Task.execute() and just prints the progress of test execution to the Console. My pull request does not change that - but ideally, it should change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does not need to be resolved for this pull request though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

turned TODO into a note

.mapError((error: Throwable) => ::(error, Nil))

for {
// TODO make this test console silent
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this needs to be resolved?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, test-sbt is not testable on Scala.js nor Scala Native; with my pull request, it is. During testing I noticed that progress of execution of tests within framework instances used by tests is visible on non-JVM back-ends - but not on JVM. It would be nice to make this output more uniform across the back-ends - but it is not needed for this pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

turned TODO into a note

specs: Seq[ZIOSpecAbstract],
args: Array[String],
selectors: Array[Selector] = Array(new SuiteSelector)
): ZIO[Any, ::[Throwable], (Seq[String], Seq[Event])] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we're initializing a mutable object inside, we need to be referentially transparent

Suggested change
): ZIO[Any, ::[Throwable], (Seq[String], Seq[Event])] = {
): ZIO[Any, ::[Throwable], (Seq[String], Seq[Event])] = ZIO.suspendSucceed {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Fixing it.

Copy link
Contributor Author

@dubinsky dubinsky Aug 25, 2025

Choose a reason for hiding this comment

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

done in 174ab24

@dubinsky
Copy link
Contributor Author

Overall looks good to me, just a few minor-ish comments.

Thank you!

The one main comment I do have though is, can you please make sure that these changes don't break the IntelliJ ZIO plugin?

AFAIK, coupling between the IntelliJ ZIO plugin and zio.test.sbt classes is lose enough to not break.

Same seems to be true about the ZIO Test runner support for the ZIO IntelliJ plugin, which anyway is archived and was not touched for two years...

Address review comments.
@dubinsky dubinsky force-pushed the zio-test-more-uniform-reporting branch from 90bfe81 to 174ab24 Compare August 25, 2025 23:01
runtime
) {
// Note: on Scala.js this method is never called - next one is.
override def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = ???
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you throw an error with a clear message stating the comment, something like:

new IllegalStateException("ZTestTask.execute unexpectedly invoked on Scala.js...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in eadcaa9


override def execute(eventHandler: EventHandler, loggers: Array[Logger], continuation: Array[Task] => Unit): Unit =
unsafeAPI
.fork(run(eventHandler)(zio.Trace.empty))(zio.Trace.empty, zio.Unsafe)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you import these to avoid the prefix? same with exit?

Copy link
Contributor Author

@dubinsky dubinsky Aug 28, 2025

Choose a reason for hiding this comment

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

done in eadcaa9 throughout the code; Runtime, System and Console remain zio.-prefixed to disambiguate them from the ones from Java/Scala.

try {
resOutter = unsafeAPI.runToFuture(run(eventHandler)(zio.Trace.empty))(zio.Trace.empty, zio.Unsafe)
Await.result(resOutter, Duration.Inf)
} catch {
Copy link
Collaborator

Choose a reason for hiding this comment

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

try/finally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so: resOutter.cancel() needs to be invoked only when the try block throws...

Address more review comments.
Address more review comments.
Copy link
Contributor

@kyri-petrou kyri-petrou left a comment

Choose a reason for hiding this comment

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

I think it looks good. I'm not too familiar with the sbt integration part but I'm really in favour of unifying the implementations for JVM/JS/Native as it's been the source of several bugs on the JS / Native side

@hearnadam are you okay with merging this one in as well?

@kyri-petrou kyri-petrou merged commit 07efbcb into zio:series/2.x Sep 4, 2025
19 checks passed
@kyri-petrou
Copy link
Contributor

Many thanks @dubinsky! I know this was not a trivial task - really appreciate this :)

@dubinsky
Copy link
Contributor Author

dubinsky commented Sep 4, 2025

Many thanks @dubinsky! I know this was not a trivial task - really appreciate this :)

Glad I could be of help :)

Thank you @kyri-petrou and @hearnadam!

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