From da392ff26f414678702fb889ce11debf16d36f28 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Tue, 3 Mar 2020 11:16:17 +0100 Subject: [PATCH] Split tests & select random port --- .../languageserver/WebSocketServerTest.scala | 461 ------------------ .../websocket/CapabilitiesTest.scala | 174 +++++++ .../websocket/FileManagerTest.scala | 194 ++++++++ .../websocket/WebSocketServerTest.scala | 115 +++++ 4 files changed, 483 insertions(+), 461 deletions(-) delete mode 100644 engine/language-server/src/test/scala/org/enso/languageserver/WebSocketServerTest.scala create mode 100644 engine/language-server/src/test/scala/org/enso/languageserver/websocket/CapabilitiesTest.scala create mode 100644 engine/language-server/src/test/scala/org/enso/languageserver/websocket/FileManagerTest.scala create mode 100644 engine/language-server/src/test/scala/org/enso/languageserver/websocket/WebSocketServerTest.scala diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/WebSocketServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/WebSocketServerTest.scala deleted file mode 100644 index 7866e9d6c3..0000000000 --- a/engine/language-server/src/test/scala/org/enso/languageserver/WebSocketServerTest.scala +++ /dev/null @@ -1,461 +0,0 @@ -package org.enso.languageserver - -import java.nio.file.{Files, Paths} -import java.util.UUID - -import akka.NotUsed -import akka.actor.{ActorRef, ActorSystem, PoisonPill, Props} -import akka.http.scaladsl.Http -import akka.http.scaladsl.model.ws.{Message, TextMessage, WebSocketRequest} -import akka.stream.OverflowStrategy -import akka.stream.scaladsl.{Flow, Sink, Source} -import akka.testkit.{ImplicitSender, TestKit, TestProbe} -import cats.effect.IO -import io.circe.Json -import io.circe.literal._ -import io.circe.parser._ -import org.enso.languageserver.data.Config -import org.enso.languageserver.filemanager.FileSystem -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpecLike -import org.scalatest.{Assertion, BeforeAndAfterAll, BeforeAndAfterEach} - -import scala.concurrent.Await -import scala.concurrent.duration._ -import scala.io.{Source => IoSource} - -class WebSocketServerTest - extends TestKit(ActorSystem("TestSystem")) - with ImplicitSender - with AnyWordSpecLike - with Matchers - with BeforeAndAfterAll - with BeforeAndAfterEach { - - override def afterAll: Unit = { - TestKit.shutdownActorSystem(system) - } - - val interface = "127.0.0.1" - val port = 54321 - val address = s"ws://$interface:$port" - - val testContentRoot = Files.createTempDirectory(null) - val testContentRootId = UUID.randomUUID() - - testContentRoot.toFile.deleteOnExit() - - var server: WebSocketServer = _ - var binding: Http.ServerBinding = _ - - override def beforeEach(): Unit = { - val config = Config(Map(testContentRootId -> testContentRoot.toFile)) - val languageServer = - system.actorOf(Props(new LanguageServer(config, new FileSystem[IO]))) - languageServer ! LanguageProtocol.Initialize - server = new WebSocketServer(languageServer) - binding = Await.result(server.bind(interface, port), 3.seconds) - } - - override def afterEach(): Unit = { - val _ = binding.unbind() - } - - "Language Server" must { - "be able to grant and release capabilities" in { - val probe = new WsTestClient(address) - val capabilityId = UUID.randomUUID() - probe.send(json""" - { "jsonrpc": "2.0", - "method": "capability/acquire", - "id": 1, - "params": { - "id": $capabilityId, - "method": "canEdit", - "registerOptions": { "path": "/Foo/bar" } - } - } - """) - probe.expectJson(json""" - { "jsonrpc": "2.0", - "id": 1, - "result": null - } - """) - probe.send(json""" - { "jsonrpc": "2.0", - "method": "capability/release", - "id": 2, - "params": { - "id": $capabilityId - } - } - """) - probe.expectJson(json""" - { "jsonrpc": "2.0", - "id": 2, - "result": null - } - """) - } - - "take canEdit capability away from clients when another client registers for it" in { - val client1 = new WsTestClient(address) - val client2 = new WsTestClient(address) - val client3 = new WsTestClient(address) - val capability1Id = UUID.randomUUID() - val capability2Id = UUID.randomUUID() - val capability3Id = UUID.randomUUID() - - client1.send(json""" - { "jsonrpc": "2.0", - "method": "capability/acquire", - "id": 1, - "params": { - "id": $capability1Id, - "method": "canEdit", - "registerOptions": { "path": "/Foo/bar" } - } - } - """) - - client1.expectJson(json""" - { "jsonrpc": "2.0", - "id": 1, - "result": null - } - """) - client2.expectNoMessage() - client3.expectNoMessage() - - client2.send(json""" - { "jsonrpc": "2.0", - "method": "capability/acquire", - "id": 2, - "params": { - "id": $capability2Id, - "method": "canEdit", - "registerOptions": { "path": "/Foo/bar" } - } - } - """) - - client1.expectJson(json""" - { "jsonrpc": "2.0", - "method": "capability/forceReleased", - "params": {"id": $capability1Id} - } - """) - client2.expectJson(json""" - { "jsonrpc": "2.0", - "id": 2, - "result": null - } - """) - client3.expectNoMessage() - - client3.send(json""" - { "jsonrpc": "2.0", - "method": "capability/acquire", - "id": 3, - "params": { - "id": $capability3Id, - "method": "canEdit", - "registerOptions": { "path": "/Foo/bar" } - } - } - """) - - client1.expectNoMessage() - client2.expectJson(json""" - { "jsonrpc": "2.0", - "method": "capability/forceReleased", - "params": {"id": $capability2Id} - } - """) - client3.expectJson(json""" - { "jsonrpc": "2.0", - "id": 3, - "result": null - } - """) - } - - "implement the canEdit capability on a per-file basis" in { - val client1 = new WsTestClient(address) - val client2 = new WsTestClient(address) - val capability1Id = UUID.randomUUID() - val capability2Id = UUID.randomUUID() - - client1.send(json""" - { "jsonrpc": "2.0", - "method": "capability/acquire", - "id": 1, - "params": { - "id": $capability1Id, - "method": "canEdit", - "registerOptions": { "path": "/Foo/bar" } - } - } - """) - - client1.expectJson(json""" - { "jsonrpc": "2.0", - "id": 1, - "result": null - } - """) - client2.expectNoMessage() - - client2.send(json""" - { "jsonrpc": "2.0", - "method": "capability/acquire", - "id": 2, - "params": { - "id": $capability2Id, - "method": "canEdit", - "registerOptions": { "path": "/Baz/spam" } - } - } - """) - - client1.expectNoMessage() - client2.expectJson(json""" - { "jsonrpc": "2.0", - "id": 2, - "result": null - } - """) - } - - "write textual content to a file" in { - val client = new WsTestClient(address) - - client.send(json""" - { "jsonrpc": "2.0", - "method": "file/write", - "id": 3, - "params": { - "path": { - "rootId": $testContentRootId, - "segments": [ "foo", "bar", "baz.txt" ] - }, - "contents": "123456789" - } - } - """) - client.expectJson(json""" - { "jsonrpc": "2.0", - "id": 3, - "result": null - } - """) - client.expectNoMessage() - val path = Paths.get(testContentRoot.toString, "foo", "bar", "baz.txt") - IoSource.fromFile(path.toFile).getLines().mkString shouldBe "123456789" - } - - "return failure when a content root cannot be found" in { - val client = new WsTestClient(address) - - client.send(json""" - { "jsonrpc": "2.0", - "method": "file/write", - "id": 3, - "params": { - "path": { - "rootId": ${UUID.randomUUID()}, - "segments": [ "foo", "bar", "baz.txt" ] - }, - "contents": "123456789" - } - } - """) - client.expectJson(json""" - { "jsonrpc": "2.0", - "id": 3, - "error" : { - "code" : 1001, - "message" : "Content root not found" - } - } - """) - client.expectNoMessage() - } - - "read a file content" in { - val client = new WsTestClient(address) - - client.send(json""" - { "jsonrpc": "2.0", - "method": "file/write", - "id": 4, - "params": { - "path": { - "rootId": $testContentRootId, - "segments": [ "foo.txt" ] - }, - "contents": "123456789" - } - } - """) - client.expectJson(json""" - { "jsonrpc": "2.0", - "id": 4, - "result": null - } - """) - client.send(json""" - { "jsonrpc": "2.0", - "method": "file/read", - "id": 5, - "params": { - "path": { - "rootId": $testContentRootId, - "segments": [ "foo.txt" ] - } - } - } - """) - client.expectJson(json""" - { "jsonrpc": "2.0", - "id": 5, - "result": { "contents": "123456789" } - } - """) - } - - "return FileNotFoundError if a file doesn't exist" in { - val client = new WsTestClient(address) - - client.send(json""" - { "jsonrpc": "2.0", - "method": "file/read", - "id": 6, - "params": { - "path": { - "rootId": $testContentRootId, - "segments": [ "bar.txt" ] - } - } - } - """) - client.expectJson(json""" - { "jsonrpc": "2.0", - "id": 6, - "error" : { - "code" : 1003, - "message" : "File not found" - } - } - """) - } - - "create a file" in { - val client = new WsTestClient(address) - - client.send(json""" - { "jsonrpc": "2.0", - "method": "file/create", - "id": 7, - "params": { - "object": { - "type": "File", - "name": "bar.txt", - "path": { - "rootId": $testContentRootId, - "segments": [ "foo1" ] - } - } - } - } - """) - client.expectJson(json""" - { "jsonrpc": "2.0", - "id": 7, - "result": null - } - """) - - val file = Paths.get(testContentRoot.toString, "foo1", "bar.txt").toFile - file.isFile shouldBe true - } - - "create a directory" in { - val client = new WsTestClient(address) - - client.send(json""" - { "jsonrpc": "2.0", - "method": "file/create", - "id": 7, - "params": { - "object": { - "type": "Directory", - "name": "baz", - "path": { - "rootId": $testContentRootId, - "segments": [ "foo1" ] - } - } - } - } - """) - client.expectJson(json""" - { "jsonrpc": "2.0", - "id": 7, - "result": null - } - """) - - val file = Paths.get(testContentRoot.toString, "foo1", "baz").toFile - file.isDirectory shouldBe true - } - - } - - class WsTestClient(address: String) { - private var inActor: ActorRef = _ - private val outActor: TestProbe = TestProbe() - private val source: Source[Message, NotUsed] = Source - .actorRef[String]( - PartialFunction.empty, - PartialFunction.empty, - 1, - OverflowStrategy.fail - ) - .mapMaterializedValue { actorRef => - inActor = actorRef - NotUsed - } - .map { txt: String => - TextMessage(txt) - } - private val sink: Sink[Message, NotUsed] = Flow[Message] - .map { - case TextMessage.Strict(s) => s - } - .to(Sink.actorRef[String](outActor.ref, PoisonPill, { _: Any => - PoisonPill - })) - private val flow = Flow.fromSinkAndSource(sink, source) - - Http() - .singleWebSocketRequest(WebSocketRequest(address), flow) - - def send(message: String): Unit = { - inActor ! message - } - - def send(json: Json): Unit = send(json.noSpaces) - - def expectMessage(): String = - outActor.expectMsgClass[String](classOf[String]) - - def expectJson(json: Json): Assertion = { - val parsed = parse(expectMessage()) - parsed shouldEqual Right(json) - } - - def expectNoMessage(): Unit = outActor.expectNoMessage() - } -} diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/CapabilitiesTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/CapabilitiesTest.scala new file mode 100644 index 0000000000..8ed622249b --- /dev/null +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/CapabilitiesTest.scala @@ -0,0 +1,174 @@ +package org.enso.languageserver.websocket + +import java.util.UUID +import io.circe.literal._ + +class CapabilitiesTest extends WebSocketServerTest { + "Language Server" must { + "be able to grant and release capabilities" in { + val probe = new WsTestClient(address) + val capabilityId = UUID.randomUUID() + probe.send(json""" + { "jsonrpc": "2.0", + "method": "capability/acquire", + "id": 1, + "params": { + "id": $capabilityId, + "method": "canEdit", + "registerOptions": { "path": "Foo/bar" } + } + } + """) + probe.expectJson(json""" + { "jsonrpc": "2.0", + "id": 1, + "result": null + } + """) + probe.send(json""" + { "jsonrpc": "2.0", + "method": "capability/release", + "id": 2, + "params": { + "id": $capabilityId + } + } + """) + probe.expectJson(json""" + { "jsonrpc": "2.0", + "id": 2, + "result": null + } + """) + } + + "take canEdit capability away from clients when another client registers for it" in { + val client1 = new WsTestClient(address) + val client2 = new WsTestClient(address) + val client3 = new WsTestClient(address) + val capability1Id = UUID.randomUUID() + val capability2Id = UUID.randomUUID() + val capability3Id = UUID.randomUUID() + + client1.send(json""" + { "jsonrpc": "2.0", + "method": "capability/acquire", + "id": 1, + "params": { + "id": $capability1Id, + "method": "canEdit", + "registerOptions": { "path": "Foo/bar" } + } + } + """) + + client1.expectJson(json""" + { "jsonrpc": "2.0", + "id": 1, + "result": null + } + """) + client2.expectNoMessage() + client3.expectNoMessage() + + client2.send(json""" + { "jsonrpc": "2.0", + "method": "capability/acquire", + "id": 2, + "params": { + "id": $capability2Id, + "method": "canEdit", + "registerOptions": { "path": "Foo/bar" } + } + } + """) + + client1.expectJson(json""" + { "jsonrpc": "2.0", + "method": "capability/forceReleased", + "params": {"id": $capability1Id} + } + """) + client2.expectJson(json""" + { "jsonrpc": "2.0", + "id": 2, + "result": null + } + """) + client3.expectNoMessage() + + client3.send(json""" + { "jsonrpc": "2.0", + "method": "capability/acquire", + "id": 3, + "params": { + "id": $capability3Id, + "method": "canEdit", + "registerOptions": { "path": "Foo/bar" } + } + } + """) + + client1.expectNoMessage() + client2.expectJson(json""" + { "jsonrpc": "2.0", + "method": "capability/forceReleased", + "params": {"id": $capability2Id} + } + """) + client3.expectJson(json""" + { "jsonrpc": "2.0", + "id": 3, + "result": null + } + """) + } + + "implement the canEdit capability on a per-file basis" in { + val client1 = new WsTestClient(address) + val client2 = new WsTestClient(address) + val capability1Id = UUID.randomUUID() + val capability2Id = UUID.randomUUID() + + client1.send(json""" + { "jsonrpc": "2.0", + "method": "capability/acquire", + "id": 1, + "params": { + "id": $capability1Id, + "method": "canEdit", + "registerOptions": { "path": "Foo/bar" } + } + } + """) + + client1.expectJson(json""" + { "jsonrpc": "2.0", + "id": 1, + "result": null + } + """) + client2.expectNoMessage() + + client2.send(json""" + { "jsonrpc": "2.0", + "method": "capability/acquire", + "id": 2, + "params": { + "id": $capability2Id, + "method": "canEdit", + "registerOptions": { "path": "Foo/baz" } + } + } + """) + + client1.expectNoMessage() + client2.expectJson(json""" + { "jsonrpc": "2.0", + "id": 2, + "result": null + } + """) + } + } +} diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/FileManagerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/FileManagerTest.scala new file mode 100644 index 0000000000..e38b60b5dd --- /dev/null +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/FileManagerTest.scala @@ -0,0 +1,194 @@ +package org.enso.languageserver.websocket + +import java.nio.file.Paths +import java.util.UUID +import scala.io.{Source => IoSource} +import io.circe.literal._ + +class FileManagerTest extends WebSocketServerTest { + "File Server" must { + + "write textual content to a file" in { + val client = new WsTestClient(address) + + client.send(json""" + { "jsonrpc": "2.0", + "method": "file/write", + "id": 3, + "params": { + "path": { + "rootId": $testContentRootId, + "segments": [ "foo", "bar", "baz.txt" ] + }, + "contents": "123456789" + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 3, + "result": null + } + """) + client.expectNoMessage() + val path = Paths.get(testContentRoot.toString, "foo", "bar", "baz.txt") + IoSource.fromFile(path.toFile).getLines().mkString shouldBe "123456789" + } + + "return failure when a content root cannot be found" in { + val client = new WsTestClient(address) + + client.send(json""" + { "jsonrpc": "2.0", + "method": "file/write", + "id": 3, + "params": { + "path": { + "rootId": ${UUID.randomUUID()}, + "segments": [ "foo", "bar", "baz.txt" ] + }, + "contents": "123456789" + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 3, + "error" : { + "code" : 1001, + "message" : "Content root not found" + } + } + """) + client.expectNoMessage() + } + + "read a file content" in { + val client = new WsTestClient(address) + + client.send(json""" + { "jsonrpc": "2.0", + "method": "file/write", + "id": 4, + "params": { + "path": { + "rootId": $testContentRootId, + "segments": [ "foo.txt" ] + }, + "contents": "123456789" + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 4, + "result": null + } + """) + client.send(json""" + { "jsonrpc": "2.0", + "method": "file/read", + "id": 5, + "params": { + "path": { + "rootId": $testContentRootId, + "segments": [ "foo.txt" ] + } + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 5, + "result": { "contents": "123456789" } + } + """) + } + + "return FileNotFoundError if a file doesn't exist" in { + val client = new WsTestClient(address) + + client.send(json""" + { "jsonrpc": "2.0", + "method": "file/read", + "id": 6, + "params": { + "path": { + "rootId": $testContentRootId, + "segments": [ "bar.txt" ] + } + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 6, + "error" : { + "code" : 1003, + "message" : "File not found" + } + } + """) + } + + "create a file" in { + val client = new WsTestClient(address) + + client.send(json""" + { "jsonrpc": "2.0", + "method": "file/create", + "id": 7, + "params": { + "object": { + "type": "File", + "name": "bar.txt", + "path": { + "rootId": $testContentRootId, + "segments": [ "foo1" ] + } + } + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 7, + "result": null + } + """) + + val file = Paths.get(testContentRoot.toString, "foo1", "bar.txt").toFile + file.isFile shouldBe true + } + + "create a directory" in { + val client = new WsTestClient(address) + + client.send(json""" + { "jsonrpc": "2.0", + "method": "file/create", + "id": 7, + "params": { + "object": { + "type": "Directory", + "name": "baz", + "path": { + "rootId": $testContentRootId, + "segments": [ "foo1" ] + } + } + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 7, + "result": null + } + """) + + val file = Paths.get(testContentRoot.toString, "foo1", "baz").toFile + file.isDirectory shouldBe true + } + } +} diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/WebSocketServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/WebSocketServerTest.scala new file mode 100644 index 0000000000..3e0ce417f8 --- /dev/null +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/WebSocketServerTest.scala @@ -0,0 +1,115 @@ +package org.enso.languageserver.websocket +import java.nio.file.Files +import java.util.UUID + +import akka.NotUsed +import akka.actor.{ActorRef, ActorSystem, PoisonPill, Props} +import akka.http.scaladsl.Http +import akka.http.scaladsl.model.ws.{Message, TextMessage, WebSocketRequest} +import akka.stream.OverflowStrategy +import akka.stream.scaladsl.{Flow, Sink, Source} +import akka.testkit.{ImplicitSender, TestKit, TestProbe} +import cats.effect.IO +import io.circe.Json +import io.circe.parser.parse +import org.enso.languageserver.data.Config + +import org.enso.languageserver.{ + LanguageProtocol, + LanguageServer, + WebSocketServer +} +import org.enso.languageserver.filemanager.FileSystem +import org.scalatest.{Assertion, BeforeAndAfterAll, BeforeAndAfterEach} +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +import scala.concurrent.Await +import scala.concurrent.duration._ + +abstract class WebSocketServerTest + extends TestKit(ActorSystem("TestSystem")) + with ImplicitSender + with AnyWordSpecLike + with Matchers + with BeforeAndAfterAll + with BeforeAndAfterEach { + + override def afterAll: Unit = { + TestKit.shutdownActorSystem(system) + } + + val interface = "127.0.0.1" + var address: String = _ + + + val testContentRoot = Files.createTempDirectory(null) + val testContentRootId = UUID.randomUUID() + val config = Config(Map(testContentRootId -> testContentRoot.toFile)) + + testContentRoot.toFile.deleteOnExit() + + var server: WebSocketServer = _ + var binding: Http.ServerBinding = _ + + override def beforeEach(): Unit = { + val languageServer = + system.actorOf( + Props(new LanguageServer(config, new FileSystem[IO])) + ) + languageServer ! LanguageProtocol.Initialize + server = new WebSocketServer(languageServer) + binding = Await.result(server.bind(interface, port = 0), 3.seconds) + address = s"ws://$interface:${binding.localAddress.getPort}" + } + + override def afterEach(): Unit = { + val _ = binding.unbind() + } + + class WsTestClient(address: String) { + private var inActor: ActorRef = _ + private val outActor: TestProbe = TestProbe() + private val source: Source[Message, NotUsed] = Source + .actorRef[String]( + PartialFunction.empty, + PartialFunction.empty, + 1, + OverflowStrategy.fail + ) + .mapMaterializedValue { actorRef => + inActor = actorRef + NotUsed + } + .map { txt: String => + TextMessage(txt) + } + private val sink: Sink[Message, NotUsed] = Flow[Message] + .map { + case TextMessage.Strict(s) => s + } + .to(Sink.actorRef[String](outActor.ref, PoisonPill, { _: Any => + PoisonPill + })) + private val flow = Flow.fromSinkAndSource(sink, source) + + Http() + .singleWebSocketRequest(WebSocketRequest(address), flow) + + def send(message: String): Unit = { + inActor ! message + } + + def send(json: Json): Unit = send(json.noSpaces) + + def expectMessage(): String = + outActor.expectMsgClass[String](classOf[String]) + + def expectJson(json: Json): Assertion = { + val parsed = parse(expectMessage()) + parsed shouldEqual Right(json) + } + + def expectNoMessage(): Unit = outActor.expectNoMessage() + } +}