Skip to content

Commit 12a823a

Browse files
Update jsonrpc4s (via a fork)
1 parent 0e2c96b commit 12a823a

File tree

2 files changed

+140
-1
lines changed

2 files changed

+140
-1
lines changed

build.sc

+6-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ object Dependencies {
4848
ivy"com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core:$jsoniterVersion"
4949
def jsoniterMacros =
5050
ivy"com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros:$jsoniterVersion"
51+
def jsonrpc4s = ivy"io.github.alexarchambault.bleep::jsonrpc4s:0.1.1"
5152
def junit = ivy"com.github.sbt:junit-interface:0.13.3"
5253
def libdaemonjvm = ivy"io.github.alexarchambault.libdaemon::libdaemon:0.0.11"
5354
def libraryManagement = ivy"org.scala-sbt::librarymanagement-ivy:1.9.3"
@@ -158,7 +159,11 @@ class Shared(val crossScalaVersion: String) extends BloopCrossSbtModule with Pub
158159
emptyZip()
159160
}
160161
def ivyDeps = super.ivyDeps() ++ Agg(
161-
Dependencies.bsp4s.exclude(("com.github.plokhotnyuk.jsoniter-scala", "*")),
162+
Dependencies.bsp4s
163+
.exclude(("com.github.plokhotnyuk.jsoniter-scala", "*"))
164+
.exclude(("me.vican.jorge", "jsonrpc4s_2.12"))
165+
.exclude(("me.vican.jorge", "jsonrpc4s_2.13")),
166+
Dependencies.jsonrpc4s,
162167
Dependencies.coursierInterface,
163168
Dependencies.jsoniterCore,
164169
Dependencies.log4j,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package bloop.cli.integration
2+
3+
import java.net.{StandardProtocolFamily, UnixDomainSocketAddress}
4+
import java.nio.channels.SocketChannel
5+
import java.nio.charset.StandardCharsets
6+
import java.nio.ByteBuffer
7+
8+
class BspTests extends munit.FunSuite {
9+
10+
test("no JSON junk in errors") {
11+
TmpDir.fromTmpDir { root =>
12+
val dirArgs = Seq[os.Shellable]("--daemon-dir", root / "daemon")
13+
val bspFile = root / "bsp-socket"
14+
15+
val dummyMsg =
16+
"""{
17+
| "jsonrpc": "2.0",
18+
| "method": "workspace/buildTargetz",
19+
| "params": null,
20+
| "id": 2
21+
|}""".stripMargin
22+
23+
var bspProc: os.SubProcess = null
24+
var socket: SocketChannel = null
25+
26+
try {
27+
os.proc(Launcher.launcher, dirArgs, "about")
28+
.call(cwd = root, stdin = os.Inherit, stdout = os.Inherit, env = Launcher.extraEnv)
29+
30+
bspProc =
31+
os.proc(Launcher.launcher, dirArgs, "bsp", "--protocol", "local", "--socket", bspFile)
32+
.spawn(cwd = root, stdin = os.Inherit, stdout = os.Inherit)
33+
34+
val addr = UnixDomainSocketAddress.of(bspFile.toNIO)
35+
var connected = false
36+
var attemptCount = 0
37+
38+
while (!connected && bspProc.isAlive() && attemptCount < 10) {
39+
if (attemptCount > 0)
40+
Thread.sleep(1000L)
41+
attemptCount += 1
42+
43+
if (os.exists(bspFile)) {
44+
socket = SocketChannel.open(StandardProtocolFamily.UNIX)
45+
socket.connect(addr)
46+
socket.finishConnect()
47+
connected = true
48+
}
49+
}
50+
51+
if (!connected)
52+
sys.error("Not connected to Bloop server via BSP :|")
53+
54+
def sendMsg(msg: String): Unit = {
55+
val bytes = msg.getBytes(StandardCharsets.UTF_8)
56+
57+
def doWrite(buf: ByteBuffer): Unit =
58+
if (buf.position() < buf.limit()) {
59+
val written = socket.write(buf)
60+
if (written == 0)
61+
Thread.sleep(100L)
62+
doWrite(buf)
63+
}
64+
65+
doWrite(ByteBuffer.wrap(
66+
(s"Content-Length: ${bytes.length}" + "\r\n\r\n").getBytes(StandardCharsets.UTF_8)
67+
))
68+
doWrite(ByteBuffer.wrap(bytes))
69+
}
70+
71+
sendMsg(dummyMsg)
72+
73+
val arr = Array.ofDim[Byte](10 * 1024)
74+
val buf = ByteBuffer.wrap(arr)
75+
76+
// seems to do the job…
77+
def doRead(buf: ByteBuffer, count: Int): Int = {
78+
val read = socket.read(buf)
79+
if (read <= 0 && count < 100) {
80+
Thread.sleep(100L)
81+
doRead(buf, count + 1)
82+
}
83+
else
84+
read
85+
}
86+
87+
val read = doRead(buf, 0)
88+
assert(read > 0)
89+
90+
val resp = new String(arr, 0, read, StandardCharsets.UTF_8)
91+
92+
def validateJson(content: String): Boolean = {
93+
assert(content.startsWith("{"))
94+
assert(content.endsWith("}"))
95+
val chars = content.toCharArray
96+
val objCount = Array.ofDim[Int](chars.length)
97+
for (i <- 0 until chars.length) {
98+
val previous = if (i == 0) 0 else objCount(i - 1)
99+
val count = previous + (if (chars(i) == '{') 1 else if (chars(i) == '}') -1 else 0)
100+
objCount(i) = count
101+
}
102+
objCount.dropRight(1).forall(_ > 0)
103+
}
104+
105+
resp.linesWithSeparators.toVector match {
106+
case Seq(cl, empty, other @ _*)
107+
if cl.startsWith("Content-Length:") && empty.trim.isEmpty =>
108+
val json = other.mkString
109+
val validated = validateJson(json)
110+
if (!validated)
111+
pprint.err.log(json)
112+
assert(validated, "Unexpected JSON response shape")
113+
case _ =>
114+
pprint.err.log(resp)
115+
sys.error("Unexpected response shape")
116+
}
117+
}
118+
finally {
119+
if (socket != null)
120+
socket.close()
121+
122+
if (bspProc != null) {
123+
bspProc.waitFor(1000L)
124+
if (bspProc.isAlive())
125+
bspProc.close()
126+
127+
os.proc(Launcher.launcher, dirArgs, "exit")
128+
.call(cwd = root, stdin = os.Inherit, stdout = os.Inherit, check = false)
129+
}
130+
}
131+
}
132+
}
133+
134+
}

0 commit comments

Comments
 (0)