Skip to content

Commit

Permalink
Created BaseRepl with basic test
Browse files Browse the repository at this point in the history
Determined a testing technique (running the REPL in a separate process)
  • Loading branch information
John Johnson II committed May 20, 2022
1 parent 15f88a4 commit 6980bd3
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
metals.sbt
.bsp/
.bloop/
.metals/
target/
5 changes: 5 additions & 0 deletions README.md
@@ -0,0 +1,5 @@
Rebel
=====
#### _Vive la_ REPL!

Build applications that use the Scala REPL as a user interface.
27 changes: 27 additions & 0 deletions build.sbt
@@ -0,0 +1,27 @@
import Dependencies._
import LiveReloadStartMode._

inThisBuild(Def.settings(
scalaVersion := "2.13.8",
version := "0.1.0-SNAPSHOT",
organization := "com.potenciasoftware",
organizationName := "rebel",
scalacOptions ++= Seq(
"-encoding", "utf8",
"-deprecation",
"-unchecked",
"-feature",
"-Xlint:unused",
),
))

Global / liveReloadStartMode := Manual

lazy val root = (project in file("."))
.settings(
name := "rebel",
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value,
libraryDependencies += scalaTest % Test,
)

// See https://www.scala-sbt.org/1.x/docs/Using-Sonatype.html for instructions on how to publish to Sonatype.
5 changes: 5 additions & 0 deletions project/Dependencies.scala
@@ -0,0 +1,5 @@
import sbt._

object Dependencies {
lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.2.11"
}
1 change: 1 addition & 0 deletions project/build.properties
@@ -0,0 +1 @@
sbt.version=1.6.2
2 changes: 2 additions & 0 deletions project/plugins.sbt
@@ -0,0 +1,2 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0-RC1")
addSbtPlugin("com.potenciasoftware" % "sbt-scalajs-live-reload" % "0.0.1-SNAPSHOT")
23 changes: 23 additions & 0 deletions src/main/scala/com/potenciasoftware/rebel/BaseRepl.scala
@@ -0,0 +1,23 @@
package com.potenciasoftware.rebel

import scala.tools.nsc.interpreter.shell.ILoop
import scala.tools.nsc.Settings
import scala.tools.nsc.interpreter.shell.ShellConfig

class BaseRepl {

protected def settings: Settings = {
val sets = new Settings
if (sets.classpath.isDefault)
sets.classpath.value = sys.props("java.class.path")
sets
}

protected def config: ShellConfig = ShellConfig(settings)

protected def repl: ILoop = new ILoop(config)

def run(): Unit = {
repl.run(settings)
}
}
29 changes: 29 additions & 0 deletions src/test/scala/com/potenciasoftware/rebel/BaseReplTest.scala
@@ -0,0 +1,29 @@
package com.potenciasoftware.rebel

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.tools.nsc.interpreter.shell.ILoop

import TestUtils._

class BaseReplTest extends AnyFlatSpec with Matchers {

"BaseRepl" should "look behave like the normal ILoop" in {

replTest[BaseReplTest]("basic", Seq("42 + 42")).out.mkString("\n") shouldBe
"""
|scala> val res0: Int = 84
|
|scala> """.stripMargin
}

def basic(): Unit = {
new BaseRepl {
override protected def repl: ILoop = new ILoop(config) {
override def welcome: String = ""
}
}.run()
}
}

82 changes: 82 additions & 0 deletions src/test/scala/com/potenciasoftware/rebel/TestUtils.scala
@@ -0,0 +1,82 @@
package com.potenciasoftware.rebel

import java.lang.Thread.currentThread
import java.net.URLClassLoader
import java.io.InputStream
import scala.collection.mutable.ArrayBuffer
import scala.reflect.ClassTag

object TestUtils {

private lazy val modifiedTestClasspath: Seq[String] = {

def modified(in: String): Seq[String] =
in match {
case scalaCompiler: String if scalaCompiler contains "scala-compiler" =>
Seq(in,
in.replaceAll("scala-compiler", "scala-library"),
in.replaceAll("scala-compiler", "scala-reflect"))
case _ => Seq(in)
}

currentThread.getContextClassLoader match {
case cl: URLClassLoader =>
(for {
url <- cl.getURLs
file <- modified(url.toString)
} yield file).toSeq.distinct
case _ => sys.error("classloader is not a URLClassLoader")
}
}

private class InputLinesStream(lines: Iterable[String]) extends InputStream {

private val endOfStream = -1
private val lineSeparator = '\n'.toInt

private val linesIterator = lines.iterator
private def nextLine: Option[Iterator[Int]] =
Option.when(linesIterator.hasNext) {
linesIterator.next().iterator.map(_.toInt)
}
private var currentLine: Option[Iterator[Int]] = nextLine

override def read(): Int =
currentLine match {
case None => endOfStream
case Some(l) =>
if (l.hasNext) l.next()
else {
currentLine = nextLine
lineSeparator
}
}
}

case class RunResults(out: Seq[String], errOut: Seq[String], exitCode: Int)

def replTest[C: ClassTag](methodName: String, inputLines: Iterable[String]): RunResults = {
import scala.sys.process._

val in = new InputLinesStream(inputLines ++ Seq(":q"))
val out = ArrayBuffer.empty[String]
val errOut = ArrayBuffer.empty[String]
val logger = ProcessLogger(
line => out.append(line),
line => errOut.append(line))

val exitCode = Seq("java",
"-classpath", modifiedTestClasspath mkString ":",
"com.potenciasoftware.rebel.TestUtils",
implicitly[ClassTag[C]].runtimeClass.getName(),
methodName) #< in !< logger

RunResults(out.toSeq, errOut.toSeq, exitCode)
}

def main(args: Array[String]): Unit = {
val Array(className, methodName) = args
val cls = Class.forName(className)
cls.getMethod(methodName).invoke(cls.newInstance())
}
}

0 comments on commit 6980bd3

Please sign in to comment.