diff options
Diffstat (limited to 'libraries/eval/test')
-rw-r--r-- | libraries/eval/test/EvalTest.scala | 268 | ||||
-rw-r--r-- | libraries/eval/test/resources/Base.scala | 4 | ||||
-rw-r--r-- | libraries/eval/test/resources/Deprecated.scala | 6 | ||||
-rw-r--r-- | libraries/eval/test/resources/Derived.scala | 1 | ||||
-rw-r--r-- | libraries/eval/test/resources/DerivedWithInclude.scala | 3 | ||||
-rw-r--r-- | libraries/eval/test/resources/HelloJoe.scala | 3 | ||||
-rw-r--r-- | libraries/eval/test/resources/IncludeInclude.scala | 7 | ||||
-rw-r--r-- | libraries/eval/test/resources/OnePlusOne.scala | 1 | ||||
-rw-r--r-- | libraries/eval/test/resources/RubyInclude.scala | 3 | ||||
-rw-r--r-- | libraries/eval/test/resources/file-with-dash.scala | 1 |
10 files changed, 297 insertions, 0 deletions
diff --git a/libraries/eval/test/EvalTest.scala b/libraries/eval/test/EvalTest.scala new file mode 100644 index 0000000..0489fba --- /dev/null +++ b/libraries/eval/test/EvalTest.scala @@ -0,0 +1,268 @@ +package cbt.eval.test + +import java.io.{File, FileWriter} +import org.scalatest.WordSpec +import scala.io.Source +import scala.language.reflectiveCalls +import scala.reflect.internal.util.Position +import scala.tools.nsc.Settings +import scala.tools.nsc.reporters.{AbstractReporter, Reporter} +import java.nio.file._ + +class EvalTest extends WordSpec { + class Eval(f: Option[File] = None) extends cbt.eval.Eval(f){ + override def classLoader = EvalTest.this.getClass.getClassLoader + } + object Eval extends Eval(None) + def fromResourcePath(path: String): File = { + assert(path.endsWith(".scala")) + val tmpFile = File.createTempFile(path.stripSuffix(".scala"),"scala") + Files.copy( + Paths.get( getClass.getResource(path).getFile), + tmpFile.toPath, + StandardCopyOption.REPLACE_EXISTING + ) + tmpFile.deleteOnExit() + tmpFile + } + "Evaluator" should { + + "apply('expression')" in { + assert((new Eval).apply[Int]("1 + 1") == 2) + } + + "apply(new File(...))" in { + assert((new Eval).apply[Int](fromResourcePath("/OnePlusOne.scala")) == 2) + } + + "apply(new File(...), new File(...))" in { + val derived = (new Eval).apply[() => String]( + fromResourcePath("/Base.scala"), + fromResourcePath("/Derived.scala")) + assert(derived() == "hello") + } + + "apply(new File(...) with a dash in the name with target" in { + val f = File.createTempFile("eval", "target") + f.delete() + f.mkdir() + val e = new Eval(Some(f)) + val sourceFile = fromResourcePath("/file-with-dash.scala") + val res: String = e(sourceFile) + assert(res == "hello") + val className = e.fileToClassName(sourceFile) + val processedSource = e.sourceForString(Source.fromFile(sourceFile).getLines().mkString("\n")) + val fullClassName = "Evaluator__%s_%s.class".format( + className, e.uniqueId(processedSource, None)) + val targetFileName = f.getAbsolutePath() + File.separator + fullClassName + val targetFile = new File(targetFileName) + assert(targetFile.exists) + } + + "apply(new File(...) with target" in { + val f = File.createTempFile("eval", "target") + f.delete() + f.mkdir() + val e = new Eval(Some(f)) + val sourceFile = fromResourcePath("/OnePlusOne.scala") + val res: Int = e(sourceFile) + assert(res == 2) + + // make sure it created a class file with the expected name + val className = e.fileToClassName(sourceFile) + val processedSource = e.sourceForString(Source.fromFile(sourceFile).getLines().mkString("\n")) + val fullClassName = "Evaluator__%s_%s.class".format( + className, e.uniqueId(processedSource, None)) + val targetFileName = f.getAbsolutePath() + File.separator + fullClassName + val targetFile = new File(targetFileName) + assert(targetFile.exists) + val targetMod = targetFile.lastModified + + // eval again, make sure it works + val res2: Int = e(sourceFile) + // and make sure it didn't create a new file (1 + checksum) + assert(f.listFiles.length == 2) + // and make sure it didn't update the file + val targetFile2 = new File(targetFileName) + assert(targetFile2.lastModified == targetMod) + + // touch source, ensure no-recompile (checksum hasn't changed) + sourceFile.setLastModified(System.currentTimeMillis()) + val res3: Int = e(sourceFile) + assert(res3 == 2) + // and make sure it didn't create a different file + assert(f.listFiles.length == 2) + // and make sure it updated the file + val targetFile3 = new File(targetFileName) + assert(targetFile3.lastModified == targetMod) + + // append a newline, altering checksum, verify recompile + val writer = new FileWriter(sourceFile) + writer.write("//a comment\n2\n") + writer.close() + val res4: Int = e(sourceFile) + assert(res4 == 2) + // and make sure it created a new file + val targetFile4 = new File(targetFileName) + assert(!targetFile4.exists) + } + + "apply(InputStream)" in { + assert((new Eval).apply[Int](getClass.getResourceAsStream("/OnePlusOne.scala")) == 2) + } + + "uses deprecated" in { + val deprecated = (new Eval).apply[() => String]( + fromResourcePath("/Deprecated.scala")) + assert(deprecated() == "hello") + } + + "inPlace('expression')" in { + // Old object API works + Eval.compile("object Doubler { def apply(n: Int) = n * 2 }") + assert(Eval.inPlace[Int]("Doubler(2)") == 4) + assert(Eval.inPlace[Int]("Doubler(14)") == 28) + // New class API fails + // val eval = new Eval + // eval.compile("object Doubler { def apply(n: Int) = n * 2 }") + // assert(eval.inPlace[Int]("Doubler(2)") == 4) + // assert(eval.inPlace[Int]("Doubler(14)") == 28) + } + + "check" in { + (new Eval).check("23") + intercept[Eval.CompilerException] { + (new Eval).check("invalid") + } + } + + "#include" in { + val derived = Eval[() => String]( + fromResourcePath("/Base.scala"), + fromResourcePath("/DerivedWithInclude.scala")) + assert(derived() == "hello") + assert(derived.toString == "hello, joe") + } + + "recursive #include" in { + val derived = Eval[() => String]( + fromResourcePath("/Base.scala"), + fromResourcePath("/IncludeInclude.scala")) + assert(derived() == "hello") + assert(derived.toString == "hello, joe; hello, joe") + } + + "toSource returns post-processed code" in { + val derived = Eval.toSource(fromResourcePath("/DerivedWithInclude.scala")) + assert(derived.contains("hello, joe")) + assert(derived.contains("new Base")) + } + + "throws a compilation error when Ruby is #included" in { + intercept[Throwable] { + Eval[() => String]( + fromResourcePath("RubyInclude.scala") + ) + } + } + + "clean class names" in { + val e = new Eval() + // regular old scala file + assert(e.fileToClassName(new File("foo.scala")) == "foo") + // without an extension + assert(e.fileToClassName(new File("foo")) == "foo") + // with lots o dots + assert(e.fileToClassName(new File("foo.bar.baz")) == "foo$2ebar") + // with dashes + assert(e.fileToClassName(new File("foo-bar-baz.scala")) == "foo$2dbar$2dbaz") + // with crazy things + assert(e.fileToClassName(new File("foo$! -@@@")) == "foo$24$21$20$2d$40$40$40") + } + + "allow custom error reporting" when { + class Ctx { + val eval = new Eval { + @volatile var errors: Seq[(String, String)] = Nil + + override lazy val compilerMessageHandler: Option[Reporter] = Some(new AbstractReporter { + override val settings: Settings = compilerSettings + override def displayPrompt(): Unit = () + override def display(pos: Position, msg: String, severity: this.type#Severity): Unit = { + errors = errors :+ ((msg, severity.toString)) + } + override def reset() = { + super.reset() + errors = Nil + } + }) + } + } + + "not report errors on success" in { + val ctx = new Ctx + import ctx._ + + assert(eval[Int]("val a = 3; val b = 2; a + b", true) == 5) + assert(eval.errors.isEmpty) + } + + "report errors on bad code" in { + val ctx = new Ctx + import ctx._ + + intercept[Throwable] { + eval[Int]("val a = 3; val b = q; a + b", true) + } + assert(eval.errors.nonEmpty) + } + + "reset state between invocations" in { + val ctx = new Ctx + import ctx._ + + intercept[Throwable] { + eval[Int]("val a = 3; val b = q; a + b", true) + } + assert(eval.errors.nonEmpty) + assert(eval[Int]("val d = 3; val e = 2; d + e", true) == 5) + assert(eval.errors.isEmpty) + } + + "reset reporter between inPlace invocations" in { + val ctx = new Ctx + import ctx._ + + intercept[Throwable] { + eval.inPlace[Int]("val a = 3; val b = q; a + b") + } + assert(eval.errors.nonEmpty) + assert(eval.inPlace[Int]("val d = 3; val e = 2; d + e") == 5) + assert(eval.errors.isEmpty) + } + + "reporter should be reset between checks, but loaded class should remain" in { + val ctx = new Ctx + import ctx._ + + // compile and load compiled class + eval.compile("class A()") + + intercept[Throwable] { + eval.check("new B()") + } + assert(eval.errors.nonEmpty) + + eval.check("new A()") + assert(eval.errors.isEmpty) + } + + "eval with overriden classloader" in { + val eval = new Eval{ + override def classLoader = this.getClass.getClassLoader + } + assert(eval.apply[Int]("1 + 1") == 2) + } + } + } +} diff --git a/libraries/eval/test/resources/Base.scala b/libraries/eval/test/resources/Base.scala new file mode 100644 index 0000000..ec8ae22 --- /dev/null +++ b/libraries/eval/test/resources/Base.scala @@ -0,0 +1,4 @@ +trait Base extends (() => String) { + def apply() = "hello" +} + diff --git a/libraries/eval/test/resources/Deprecated.scala b/libraries/eval/test/resources/Deprecated.scala new file mode 100644 index 0000000..979affb --- /dev/null +++ b/libraries/eval/test/resources/Deprecated.scala @@ -0,0 +1,6 @@ +new (() => String) { + @deprecated("don't use hello") + def hello() = "hello" + + def apply() = hello() +} diff --git a/libraries/eval/test/resources/Derived.scala b/libraries/eval/test/resources/Derived.scala new file mode 100644 index 0000000..1d8e0d8 --- /dev/null +++ b/libraries/eval/test/resources/Derived.scala @@ -0,0 +1 @@ +new Base { } diff --git a/libraries/eval/test/resources/DerivedWithInclude.scala b/libraries/eval/test/resources/DerivedWithInclude.scala new file mode 100644 index 0000000..476d60e --- /dev/null +++ b/libraries/eval/test/resources/DerivedWithInclude.scala @@ -0,0 +1,3 @@ +new Base { +#include HelloJoe.scala +} diff --git a/libraries/eval/test/resources/HelloJoe.scala b/libraries/eval/test/resources/HelloJoe.scala new file mode 100644 index 0000000..c6ea42e --- /dev/null +++ b/libraries/eval/test/resources/HelloJoe.scala @@ -0,0 +1,3 @@ +/* real-time declarative programming now */ +override def toString = "hello, joe" + diff --git a/libraries/eval/test/resources/IncludeInclude.scala b/libraries/eval/test/resources/IncludeInclude.scala new file mode 100644 index 0000000..cc1f966 --- /dev/null +++ b/libraries/eval/test/resources/IncludeInclude.scala @@ -0,0 +1,7 @@ +val b1 = +#include DerivedWithInclude.scala +val b2 = +#include DerivedWithInclude.scala +new Base { + override def toString = b1 + "; " + b2 +} diff --git a/libraries/eval/test/resources/OnePlusOne.scala b/libraries/eval/test/resources/OnePlusOne.scala new file mode 100644 index 0000000..8d2f097 --- /dev/null +++ b/libraries/eval/test/resources/OnePlusOne.scala @@ -0,0 +1 @@ +1 + 1 diff --git a/libraries/eval/test/resources/RubyInclude.scala b/libraries/eval/test/resources/RubyInclude.scala new file mode 100644 index 0000000..a763d52 --- /dev/null +++ b/libraries/eval/test/resources/RubyInclude.scala @@ -0,0 +1,3 @@ +object CodeThatIncludesSomeRuby { +#include hello.rb +} diff --git a/libraries/eval/test/resources/file-with-dash.scala b/libraries/eval/test/resources/file-with-dash.scala new file mode 100644 index 0000000..3580093 --- /dev/null +++ b/libraries/eval/test/resources/file-with-dash.scala @@ -0,0 +1 @@ +"hello" |