From dcf226ee8dcf88e9ad36bfa8f8eac672ee92e9ee Mon Sep 17 00:00:00 2001 From: Pierre Ricadat Date: Sun, 25 Oct 2026 18:52:01 +0900 Subject: [PATCH] Make the subscription spec's fake server independent of the default locale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SubscriptionConnectionSpec's fake server built its SUBSCRIBE confirmations with verb.toLowerCase, which uses the default locale. SageConfigSpec sets the default locale to tr-TR for one test, and suites in a cell share one JVM and run concurrently. When the two overlapped, the fake server replied "subscrıbe" with a dotless ı, Pubsub.decode did not recognize the confirmation, and the subscribe waited forever because the spec's ManualScheduler never advances its clock. This made the unit job hang intermittently on CI. Use Locale.ROOT for every case conversion in the spec, as the production code already does. --- .../client/internal/SubscriptionConnectionSpec.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sage-client/shared/src/test/scala/sage/client/internal/SubscriptionConnectionSpec.scala b/sage-client/shared/src/test/scala/sage/client/internal/SubscriptionConnectionSpec.scala index 2da1a48..1963234 100644 --- a/sage-client/shared/src/test/scala/sage/client/internal/SubscriptionConnectionSpec.scala +++ b/sage-client/shared/src/test/scala/sage/client/internal/SubscriptionConnectionSpec.scala @@ -1,5 +1,6 @@ package sage.client.internal +import java.util.Locale import java.util.concurrent.{CountDownLatch, TimeUnit} import java.util.concurrent.atomic.AtomicReference @@ -27,7 +28,8 @@ class SubscriptionConnectionSpec extends munit.FunSuite { command match { case List("PING") => Seq(Replies.pong) case List("HELLO") => Seq(Replies.hello) - case List(verb, name) if verb.endsWith("SUBSCRIBE") => Seq(Frame.Push(Vector(bulk(verb.toLowerCase), bulk(name), Frame.Integer(1)))) + case List(verb, name) if verb.endsWith("SUBSCRIBE") => + Seq(Frame.Push(Vector(bulk(verb.toLowerCase(Locale.ROOT)), bulk(name), Frame.Integer(1)))) case _ => Nil } @@ -45,7 +47,7 @@ class SubscriptionConnectionSpec extends munit.FunSuite { // each subscribed name is its own command in the write, acknowledged by one confirmation push private def confirmations(kind: String, payload: String): Seq[Frame] = { - val names = payload.split("\r\n").count(_ == kind.toUpperCase) + val names = payload.split("\r\n").count(_ == kind.toUpperCase(Locale.ROOT)) (1 to names).map(i => Frame.Push(Vector(bulk(kind), bulk("?"), Frame.Integer(i.toLong)))) } @@ -707,7 +709,8 @@ class SubscriptionConnectionSpec extends munit.FunSuite { val respond: Bytes => Seq[Frame] = payload => commands(payload).flatMap { case List("PING") => Seq(Frame.SimpleError("NOPERM User u has no permissions to run the 'ping' command")) - case List(verb @ ("SUBSCRIBE" | "UNSUBSCRIBE"), name) => Seq(Frame.Push(Vector(bulk(verb.toLowerCase), bulk(name), Frame.Integer(1)))) + case List(verb @ ("SUBSCRIBE" | "UNSUBSCRIBE"), name) => + Seq(Frame.Push(Vector(bulk(verb.toLowerCase(Locale.ROOT)), bulk(name), Frame.Integer(1)))) case _ => Nil } val (connection, _, transports) = make(respond = respond) @@ -742,7 +745,7 @@ class SubscriptionConnectionSpec extends munit.FunSuite { case command @ (verb :: _) if verb.endsWith("SUBSCRIBE") || verb == "PING" || verb == "HELLO" => reply(command) case verb :: _ => denials += 1 - Seq(Frame.SimpleError(s"NOPERM User u has no permissions to run the '${verb.toLowerCase}' command")) + Seq(Frame.SimpleError(s"NOPERM User u has no permissions to run the '${verb.toLowerCase(Locale.ROOT)}' command")) case Nil => Nil } }