/* * sbt * Copyright 2023, Scala center * Copyright 2011 - 2022, Lightbend, Inc. * Copyright 2008 - 2010, Mark Harrah * Licensed under Apache License 2.0 (see LICENSE) */ package testpkg import java.io.IOException import java.net.Socket import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit } import java.util.concurrent.atomic.AtomicBoolean import sbt.protocol.ClientSocket import scala.annotation.tailrec import scala.concurrent.duration.* class ChannelCursorTest extends AbstractServerTest { override val testDirectory: String = "channel-cursor" private def createSecondConnection() : (Socket, java.io.OutputStream, LinkedBlockingQueue[String], AtomicBoolean) = { val portfile = testPath.resolve("project/target/active.json").toFile @tailrec def connect(attempt: Int): Socket = { val res = try Some(ClientSocket.socket(portfile)._1) catch { case _: IOException if attempt < 10 => None } res match { case Some(s) => s case _ => Thread.sleep(100) connect(attempt + 1) } } val sk = connect(0) val out = sk.getOutputStream val in = sk.getInputStream val lines = new LinkedBlockingQueue[String] val running = new AtomicBoolean(true) new Thread( () => { while (running.get) { try lines.put(sbt.ReadJson(in, running)) catch { case _: Exception => running.set(false) } } }, "sbt-server-test-read-thread-2" ) { setDaemon(true) start() } (sk, out, lines, running) } private def sendJsonRpc(out: java.io.OutputStream, message: String): Unit = { def writeLine(s: String): Unit = { val retByte: Byte = '\r'.toByte val delimiter: Byte = '\n'.toByte if (s != "") { out.write(s.getBytes("UTF-8")) } out.write(retByte.toInt) out.write(delimiter.toInt) out.flush } writeLine(s"""Content-Length: ${message.size + 2}""") writeLine("") writeLine(message) } private def waitForString(lines: LinkedBlockingQueue[String], duration: FiniteDuration)( f: String => Boolean ): Boolean = { val deadline = duration.fromNow @tailrec def impl(): Boolean = lines.poll(deadline.timeLeft.toMillis, TimeUnit.MILLISECONDS) match { case null => false case s => if (!f(s) && !deadline.isOverdue) impl() else !deadline.isOverdue() } impl() } test("channel cursor - independent project cursors") { val (sk2, out2, lines2, running2) = createSecondConnection() try { sendJsonRpc( out2, """{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { "skipAnalysis": true } } }""" ) waitForString(lines2, 10.seconds)(_.contains(""""capabilities":{""")) svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": 10, "method": "sbt/exec", "params": { "commandLine": "project projectA" } }""" ) assert( svr.waitForString(10.seconds) { s => println(s"[channel1] $s") s.contains("projectA") || s.contains("\"execId\":10") }, "Channel 1 should switch to projectA" ) sendJsonRpc( out2, """{ "jsonrpc": "2.0", "id": 20, "method": "sbt/exec", "params": { "commandLine": "project projectB" } }""" ) assert( waitForString(lines2, 10.seconds) { s => println(s"[channel2] $s") s.contains("projectB") || s.contains("\"execId\":20") }, "Channel 2 should switch to projectB" ) svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": 11, "method": "sbt/exec", "params": { "commandLine": "printCurrentProject" } }""" ) var foundProjectA = false assert( svr.waitForString(30.seconds) { s => println(s"[channel1 name] $s") if (s.contains("CURRENT_PROJECT_IS:project-a")) foundProjectA = true s.contains("\"execId\":11") && s.contains("\"status\":\"Done\"") }, "First channel printCurrentProject command should complete" ) assert(foundProjectA, "First channel should still be on projectA") sendJsonRpc( out2, """{ "jsonrpc": "2.0", "id": 21, "method": "sbt/exec", "params": { "commandLine": "printCurrentProject" } }""" ) var foundProjectB = false assert( waitForString(lines2, 30.seconds) { s => println(s"[channel2 name] $s") if (s.contains("CURRENT_PROJECT_IS:project-b")) foundProjectB = true s.contains("\"execId\":21") && s.contains("\"status\":\"Done\"") }, "Second channel printCurrentProject command should complete" ) assert(foundProjectB, "Second channel should still be on projectB") } finally { running2.set(false) sk2.close() } } }