diff --git a/build.sbt b/build.sbt index 55b672e83bdb..e3bc3a88bfe4 100644 --- a/build.sbt +++ b/build.sbt @@ -46,7 +46,7 @@ val partestDep = scalaDep("org.scala-lang.modules", "scala-par // Non-Scala dependencies: val junitDep = "junit" % "junit" % "4.12" val junitInterfaceDep = "com.novocode" % "junit-interface" % "0.11" % "test" -val scalacheckDep = "org.scalacheck" % "scalacheck_2.12" % "1.13.4" % "test" +val scalacheckDep = "org.scalacheck" % "scalacheck_2.12" % "1.14.3" % "test" val jolDep = "org.openjdk.jol" % "jol-core" % "0.9" val asmDep = "org.scala-lang.modules" % "scala-asm" % versionProps("scala-asm.version") val jlineDep = "jline" % "jline" % versionProps("jline.version") @@ -184,6 +184,8 @@ val mimaFilterSettings = Seq { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.HashMap#HashMapCollision1.foreachEntry"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.util.hashing.MurmurHash3.product2Hash"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.util.hashing.MurmurHash3.emptyMapHash"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.HashMap$HashMapKeys"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.HashMap$HashMapValues"), // Some static forwarder changes detected after a MiMa upgrade. // e.g. static method apply(java.lang.Object)java.lang.Object in class scala.Symbol does not have a correspondent in current version @@ -901,9 +903,6 @@ lazy val scalacheck = project.in(file("test") / "scalacheck") .settings( // enable forking to workaround https://github.com/sbt/sbt/issues/4009 fork in Test := true, - // customise framework for early acess to https://github.com/rickynils/scalacheck/pull/388 - // TODO remove this when we upgrade scalacheck - testFrameworks := Seq(TestFramework("org.scalacheck.CustomScalaCheckFramework")), javaOptions in Test += "-Xss1M", testOptions ++= { if ((fork in Test).value) Nil diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index c2e8f8e01ed8..243dc9172633 100644 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -415,7 +415,7 @@ trait DocComments { self: Global => if (pos == NoPosition) NoPosition else { val start1 = pos.start + start - val end1 = pos.end + end + val end1 = pos.start + end pos withStart start1 withPoint start1 withEnd end1 } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index b2348f4c0e0a..ede0c3bc3de0 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -137,10 +137,15 @@ abstract class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } def staticHandleFromSymbol(sym: Symbol): asm.Handle = { - val owner = if (sym.owner.isModuleClass) sym.owner.linkedClassOfClass else sym.owner val descriptor = methodBTypeFromMethodType(sym.info, isConstructor = false).descriptor - val ownerBType = classBTypeFromSymbol(owner) - new asm.Handle(asm.Opcodes.H_INVOKESTATIC, ownerBType.internalName, sym.name.encoded, descriptor, /* itf = */ ownerBType.isInterface.get) + val ownerBType = classBTypeFromSymbol(sym.owner) + val rawInternalName = ownerBType.internalName + // object Qux { def foo = 1 } --> we want the handle to be to (static) Qux.foo, not (member) Qux$.foo + val mustUseMirrorClass = !sym.isJava && sym.owner.isModuleClass && !sym.isStaticMember + // ... but we don't know that the mirror class exists! (if there's no companion, the class is synthesized in jvm without a symbol) + val ownerInternalName = if (mustUseMirrorClass) rawInternalName stripSuffix nme.MODULE_SUFFIX_STRING else rawInternalName + val isInterface = sym.owner.linkedClassOfClass.isTraitOrInterface + new asm.Handle(asm.Opcodes.H_INVOKESTATIC, ownerInternalName, sym.name.encoded, descriptor, isInterface) } /** diff --git a/src/compiler/scala/tools/nsc/util/package.scala b/src/compiler/scala/tools/nsc/util/package.scala index 33cbd6628524..d59556a067c6 100644 --- a/src/compiler/scala/tools/nsc/util/package.scala +++ b/src/compiler/scala/tools/nsc/util/package.scala @@ -64,7 +64,7 @@ package object util { writer.toString() } - /** Generate a string using a routine that wants to write on a stream. */ + /** Generate a string using a routine that wants to write on a writer. */ def stringFromWriter(writer: PrintWriter => Unit): String = { val stringWriter = new StringWriter() val stream = new NewLinePrintWriter(stringWriter) @@ -72,23 +72,25 @@ package object util { stream.close() stringWriter.toString } + /** Generate a string using a routine that wants to write on a stream. */ def stringFromStream(stream: OutputStream => Unit): String = { + val utf8 = java.nio.charset.StandardCharsets.UTF_8 val bs = new ByteArrayOutputStream() - val ps = new PrintStream(bs) + val ps = new PrintStream(bs, /*autoflush=*/ false, utf8.name) // use Charset directly in jdk10 stream(ps) ps.close() - bs.toString() + bs.toString(utf8.name) } - def stackTraceString(ex: Throwable): String = stringFromWriter(ex printStackTrace _) + def stackTraceString(t: Throwable): String = stringFromWriter(t.printStackTrace(_)) /** A one line string which contains the class of the exception, the * message if any, and the first non-Predef location in the stack trace * (to exclude assert, require, etc.) */ - def stackTraceHeadString(ex: Throwable): String = { - val frame = ex.getStackTrace.dropWhile(_.getClassName contains "Predef") take 1 mkString "" - val msg = ex.getMessage match { case null | "" => "" ; case s => s"""("$s")""" } - val clazz = ex.getClass.getName.split('.').last + def stackTraceHeadString(t: Throwable): String = { + val frame = t.getStackTrace.dropWhile(_.getClassName contains "Predef").take(1).mkString("") + val msg = t.getMessage match { case null | "" => "" ; case s => s"""("$s")""" } + val clazz = t.getClass.getName.split('.').last s"$clazz$msg @ $frame" } diff --git a/src/library/scala/collection/immutable/HashMap.scala b/src/library/scala/collection/immutable/HashMap.scala index 630f65244375..0061b58efe0f 100644 --- a/src/library/scala/collection/immutable/HashMap.scala +++ b/src/library/scala/collection/immutable/HashMap.scala @@ -139,6 +139,21 @@ sealed class HashMap[A, +B] extends AbstractMap[A, B] override def par = ParHashMap.fromTrie(this) + /* Override to avoid tuple allocation in foreach */ + private[collection] class HashMapKeys extends ImmutableDefaultKeySet { + override def foreach[U](f: A => U) = foreachEntry((key, _) => f(key)) + override lazy val hashCode = super.hashCode() + } + override def keySet: immutable.Set[A] = new HashMapKeys + + /** The implementation class of the iterable returned by `values`. + */ + private[collection] class HashMapValues extends DefaultValuesIterable { + override def foreach[U](f: B => U) = foreachEntry((_, value) => f(value)) + } + override def values: scala.collection.Iterable[B] = new HashMapValues + + } /** $factoryInfo diff --git a/src/library/scala/collection/immutable/TreeMap.scala b/src/library/scala/collection/immutable/TreeMap.scala index 95dcc060d816..7f21d073373b 100644 --- a/src/library/scala/collection/immutable/TreeMap.scala +++ b/src/library/scala/collection/immutable/TreeMap.scala @@ -215,4 +215,11 @@ final class TreeMap[A, +B] private (tree: RB.Tree[A, B])(implicit val ordering: } } + override def keySet: SortedSet[A] = new DefaultKeySortedSet { + override def foreach[U](f: A => U): Unit = RB.foreachEntry(tree, {(key: A, _: B) => f(key)}) + } + + override def values: scala.Iterable[B] = new DefaultValuesIterable { + override def foreach[U](f: B => U): Unit = RB.foreachEntry(tree, {(_: A, value: B) => f(value)}) + } } diff --git a/src/library/scala/collection/mutable/ListBuffer.scala b/src/library/scala/collection/mutable/ListBuffer.scala index 85fca96295d8..477bc67ef979 100644 --- a/src/library/scala/collection/mutable/ListBuffer.scala +++ b/src/library/scala/collection/mutable/ListBuffer.scala @@ -306,13 +306,18 @@ final class ListBuffer[A] def result: List[A] = toList /** Converts this buffer to a list. Takes constant time. The buffer is - * copied lazily, the first time it is mutated. + * copied lazily the first time it is mutated. */ override def toList: List[A] = { exported = !isEmpty start } + // scala/bug#11869 + override def toSeq: collection.Seq[A] = toList + override def toIterable: collection.Iterable[A] = toList + override def toStream: immutable.Stream[A] = toList.toStream // mind the laziness + // New methods in ListBuffer /** Prepends the elements of this buffer to a given list diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 7ae62ddb41a2..810c89ac1ec5 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -21,12 +21,10 @@ import PartialFunction.{cond => when} import interpreter.session._ import StdReplTags._ import scala.tools.asm.ClassReader -import scala.util.Properties.jdkHome import scala.tools.nsc.util.{ClassPath, stringFromStream} import scala.reflect.classTag -import scala.reflect.internal.util.{BatchSourceFile, ScalaClassLoader, NoPosition} +import scala.reflect.internal.util.{BatchSourceFile, NoPosition} import scala.reflect.io.{Directory, File, Path} -import scala.tools.util._ import io.AbstractFile import scala.concurrent.{Await, Future} import java.io.BufferedReader @@ -278,27 +276,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) extend } } - private def findToolsJar() = PathResolver.SupplementalLocations.platformTools - - private def addToolsJarToLoader() = { - val cl = findToolsJar() match { - case Some(tools) => ScalaClassLoader.fromURLs(Seq(tools.toURL), intp.classLoader) - case _ => intp.classLoader - } - if (Javap.isAvailable(cl)) { - repldbg(":javap available.") - cl - } - else { - repldbg(":javap unavailable: no tools.jar at " + jdkHome) - intp.classLoader - } - } - - protected def newJavap() = JavapClass(addToolsJarToLoader(), new IMain.ReplStrippingWriter(intp), intp) - - private lazy val javap = substituteAndLog[Javap]("javap", NoJavap)(newJavap()) - // Still todo: modules. private def typeCommand(line0: String): Result = { line0.trim match { @@ -371,17 +348,11 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) extend ok && rest.isEmpty } - private def javapCommand(line: String): Result = { - if (javap == null) - s":javap unavailable, no tools.jar at $jdkHome. Set JDK_HOME." - else if (line == "") - Javap.helpText - else - javap(words(line)) foreach { res => - if (res.isError) return s"Failed: ${res.value}" - else res.show() - } - } + private def javapCommand(line: String): Result = + Javap(intp)(words(line): _*) foreach { res => + if (res.isError) return s"${res.value}" + else res.show() + } private def pathToPhaseWrapper = intp.originalPath("$r") + ".phased.atCurrent" diff --git a/src/repl/scala/tools/nsc/interpreter/JavapClass.scala b/src/repl/scala/tools/nsc/interpreter/JavapClass.scala index c91263ea2538..a67708a46359 100644 --- a/src/repl/scala/tools/nsc/interpreter/JavapClass.scala +++ b/src/repl/scala/tools/nsc/interpreter/JavapClass.scala @@ -10,58 +10,52 @@ * additional information regarding copyright ownership. */ -package scala -package tools.nsc +package scala.tools.nsc package interpreter import scala.language.reflectiveCalls -import java.lang.{ Iterable => JIterable } +import java.io.PrintWriter import scala.reflect.internal.util.ScalaClassLoader -import java.io.{ ByteArrayInputStream, CharArrayWriter, FileNotFoundException, PrintWriter, StringWriter, Writer } -import java.util.{ Locale } -import java.util.concurrent.ConcurrentLinkedQueue -import javax.tools.{ Diagnostic, DiagnosticListener, - ForwardingJavaFileManager, JavaFileManager, JavaFileObject, - SimpleJavaFileObject, StandardLocation } -import scala.reflect.io.File -import scala.io.Source -import scala.util.{ Try, Success, Failure } -import scala.util.Properties.{ lineSeparator => EOL } -import scala.collection.JavaConverters._ -import scala.collection.generic.Clearable -import java.net.URL -import Javap.{ JpResult, JpError, Showable, helper, toolArgs, DefaultOptions } +import scala.tools.nsc.util.stringFromWriter +import scala.util.{Failure, Success, Try} +import scala.util.{Either, Left, Right} + +import Javap.JpResult /** Javap command implementation. */ class JavapClass( val loader: ScalaClassLoader, - val printWriter: PrintWriter, - intp: IMain -) extends Javap { + intp: IMain, + tool: JavapTool +) { import JavapClass._ + import Javap.{DefaultOptions, HashSplit, helper, toolArgs} + import JavapTool.Input + import java.io.FileNotFoundException + import scala.reflect.io.File - lazy val tool = JavapTool() + private val printWriter: PrintWriter = new IMain.ReplStrippingWriter(intp) def apply(args: Seq[String]): List[JpResult] = { - val (options0, targets) = args partition (s => (s startsWith "-") && s.length > 1) + val (options0, targets) = args.partition(s => s.startsWith("-") && s.length > 1) val (options, filter) = { val (opts, flag) = toolArgs(options0) (if (opts.isEmpty) DefaultOptions else opts, flag) } - if ((options contains "-help") || targets.isEmpty) + if (options.contains("-help") || targets.isEmpty) List(JpResult(helper(printWriter))) else - tool(options, filter)(targets map targeted) + tool(options, filter)(targets.map(targeted)) } /** Associate the requested path with a possibly failed or empty array of bytes. */ - private def targeted(path: String): (String, Try[Array[Byte]]) = + private def targeted(path: String): Input = bytesFor(path) match { - case Success((target, bytes)) => (target, Try(bytes)) - case f: Failure[_] => (path, Failure(f.exception)) + case Success((actual, bytes)) => Input(path, actual, Try(bytes)) + case f: Failure[_] => Input(path, path, Failure(f.exception)) } /** Find bytes. Handle "-", "Foo#bar" (by ignoring member), "#bar" (by taking "bar"). @@ -74,18 +68,18 @@ class JavapClass( case HashSplit(_, member) if member != null => member case s => s } - (path, findBytes(req)) match { - case (_, bytes) if bytes.isEmpty => throw new FileNotFoundException(s"Could not find class bytes for '$path'") - case ok => ok + findBytes(req) match { + case (_, bytes) if bytes.isEmpty => throw new FileNotFoundException(s"Could not find class bytes for '$path'") + case ok @ (actual @ _, bytes @ _) => ok } } - def findBytes(path: String): Array[Byte] = tryFile(path) getOrElse tryClass(path) + // data paired with actual path where it was found + private def findBytes(path: String): (String, Array[Byte]) = tryFile(path).map(data => (path, data)).getOrElse(tryClass(path)) /** Assume the string is a path and try to find the classfile it represents. */ - def tryFile(path: String): Option[Array[Byte]] = - (Try (File(path.asClassResource)) filter (_.exists) map (_.toByteArray())).toOption + private def tryFile(path: String): Option[Array[Byte]] = Try(File(path.asClassResource)).filter(_.exists).map(_.toByteArray()).toOption /** Assume the string is a fully qualified class name and try to * find the class object it represents. @@ -93,22 +87,22 @@ class JavapClass( * - a definition that is wrapped in an enclosing class * - a synthetic that is not in scope but its associated class is */ - def tryClass(path: String): Array[Byte] = { - def load(name: String) = loader classBytes name - def loadable(name: String) = loader resourceable name + private def tryClass(path: String): (String, Array[Byte]) = { + def load(name: String) = loader.classBytes(name) + def loadable(name: String) = loader.resourceable(name) // if path has an interior dollar, take it as a synthetic // if the prefix up to the dollar is a symbol in scope, // result is the translated prefix + suffix def desynthesize(s: String) = { - val i = s indexOf '$' + val i = s.indexOf('$') if (0 until s.length - 1 contains i) { - val name = s substring (0, i) - val sufx = s substring i - val tran = intp translatePath name + val name = s.substring(0, i) + val sufx = s.substring(i) + val tran = intp.translatePath(name) def loadableOrNone(strip: Boolean) = { def suffix(strip: Boolean)(x: String) = - (if (strip && (x endsWith "$")) x.init else x) + sufx - val res = tran map (suffix(strip) _) + (if (strip && x.endsWith("$")) x.init else x) + sufx + val res = tran.map(suffix(strip)(_)) if (res.isDefined && loadable(res.get)) res else None } // try loading translated+suffix @@ -134,278 +128,156 @@ class JavapClass( // just try it plain getOrElse p ) - load(q) - } - - class JavapTool { - type ByteAry = Array[Byte] - type Input = Tuple2[String, Try[ByteAry]] - - implicit protected class Failer[A](a: =>A) { - def orFailed[B >: A](b: =>B) = if (failed) b else a - } - protected def noToolError = new JpError(s"No javap tool available: ${getClass.getName} failed to initialize.") - - // output filtering support - val writer = new CharArrayWriter - def written = { - writer.flush() - val w = writer.toString - writer.reset() - w - } - - def filterLines(target: String, text: String): String = { - // take Foo# as Foo#apply for purposes of filtering. - val filterOn = target.splitHashMember._2 map { s => if (s.isEmpty) "apply" else s } - var filtering = false // true if in region matching filter - // turn filtering on/off given the pattern of interest - def filterStatus(line: String, pattern: String) = { - def isSpecialized(method: String) = (method startsWith pattern+"$") && (method endsWith "$sp") - def isAnonymized(method: String) = (pattern == "$anonfun") && (method startsWith "$anonfun$") - // cheap heuristic, todo maybe parse for the java sig. - // method sigs end in paren semi - def isAnyMethod = line endsWith ");" - // take the method name between the space char and left paren. - // accept exact match or something that looks like what we might be asking for. - def isOurMethod = { - val lparen = line lastIndexOf '(' - val blank = line.lastIndexOf(' ', lparen) - if (blank < 0) false - else { - val method = line.substring(blank+1, lparen) - (method == pattern || isSpecialized(method) || isAnonymized(method)) - } - } - filtering = - if (filtering) { - // next blank line terminates section - // in non-verbose mode, next line is next method, more or less - line.trim.nonEmpty && (!isAnyMethod || isOurMethod) - } else { - isAnyMethod && isOurMethod - } - filtering - } - // do we output this line? - def checkFilter(line: String) = filterOn map (filterStatus(line, _)) getOrElse true - val sw = new StringWriter - val pw = new PrintWriter(sw) - for { - line <- Source.fromString(text).getLines() - if checkFilter(line) - } pw println line - pw.flush() - sw.toString - } - - import JavapTool._ - type Task = { - def call(): Boolean // true = ok - //def run(args: Array[String]): Int // all args - //def handleOptions(args: Array[String]): Unit // options, then run() or call() - } - // result of Task.run - //object TaskResult extends Enumeration { - // val Ok, Error, CmdErr, SysErr, Abnormal = Value - //} - val TaskClass = loader.tryToInitializeClass[Task](JavapTask).orNull - // Since the tool is loaded by reflection, check for catastrophic failure. - protected def failed = TaskClass eq null - - val TaskCtor = TaskClass.getConstructor( - classOf[Writer], - classOf[JavaFileManager], - classOf[DiagnosticListener[_]], - classOf[JIterable[String]], - classOf[JIterable[String]] - ) orFailed null - - class JavaReporter extends DiagnosticListener[JavaFileObject] with Clearable { - type D = Diagnostic[_ <: JavaFileObject] - val diagnostics = new ConcurrentLinkedQueue[D] - override def report(d: Diagnostic[_ <: JavaFileObject]) { - diagnostics add d - } - override def clear() = diagnostics.clear() - /** All diagnostic messages. - * @param locale Locale for diagnostic messages, null by default. - */ - def messages(implicit locale: Locale = null) = diagnostics.asScala.map(_ getMessage locale).toList - - def reportable(): String = { - clear() - if (messages.nonEmpty) messages mkString ("", EOL, EOL) else "" - } - } - val reporter = new JavaReporter - - // DisassemblerTool.getStandardFileManager(reporter,locale,charset) - val defaultFileManager: JavaFileManager = - (loader.tryToLoadClass[JavaFileManager]("com.sun.tools.javap.JavapFileManager").get getMethod ( - "create", - classOf[DiagnosticListener[_]], - classOf[PrintWriter] - ) invoke (null, reporter, new PrintWriter(System.err, true))).asInstanceOf[JavaFileManager] orFailed null - - // manages named arrays of bytes, which might have failed to load - class JavapFileManager(val managed: Seq[Input])(delegate: JavaFileManager = defaultFileManager) - extends ForwardingJavaFileManager[JavaFileManager](delegate) { - import JavaFileObject.Kind - import Kind._ - import StandardLocation._ - import JavaFileManager.Location - import java.net.{ URI, URISyntaxException } - - // name#fragment is OK, but otherwise fragile - def uri(name: String): URI = - try new URI(name) // new URI("jfo:" + name) - catch { case _: URISyntaxException => new URI("dummy") } - - def inputNamed(name: String): Try[ByteAry] = (managed find (_._1 == name)).get._2 - def managedFile(name: String, kind: Kind) = kind match { - case CLASS => fileObjectForInput(name, inputNamed(name), kind) - case _ => null - } - // todo: just wrap it as scala abstractfile and adapt it uniformly - def fileObjectForInput(name: String, bytes: Try[ByteAry], kind: Kind): JavaFileObject = - new SimpleJavaFileObject(uri(name), kind) { - override def openInputStream(): InputStream = new ByteArrayInputStream(bytes.get) - // if non-null, ClassWriter wrongly requires scheme non-null - override def toUri: URI = null - override def getName: String = name - // suppress - override def getLastModified: Long = -1L - } - override def getJavaFileForInput(location: Location, className: String, kind: Kind): JavaFileObject = - location match { - case CLASS_PATH => managedFile(className, kind) - case _ => null - } - override def hasLocation(location: Location): Boolean = - location match { - case CLASS_PATH => true - case _ => false - } - } - def fileManager(inputs: Seq[Input]) = new JavapFileManager(inputs)() - - /** Create a Showable to show tool messages and tool output, with output massage. - * @param target attempt to filter output to show region of interest - * @param filter whether to strip REPL names - */ - def showable(target: String, filter: Boolean): Showable = - new Showable { - val output = filterLines(target, s"${reporter.reportable()}${written}") - def show() = - if (filter) intp.withoutTruncating(printWriter.write(output)) - else intp.withoutUnwrapping(printWriter.write(output, 0, output.length)) - } - - // eventually, use the tool interface - def task(options: Seq[String], classes: Seq[String], inputs: Seq[Input]): Task = { - //ServiceLoader.load(classOf[javax.tools.DisassemblerTool]). - //getTask(writer, fileManager, reporter, options.asJava, classes.asJava) - val toolopts = options filter (_ != "-filter") - TaskCtor.newInstance(writer, fileManager(inputs), reporter, toolopts.asJava, classes.asJava) - .orFailed (throw new IllegalStateException) - } - // a result per input - private def applyOne(options: Seq[String], filter: Boolean, klass: String, inputs: Seq[Input]): Try[JpResult] = - Try { - task(options, Seq(klass), inputs).call() - } map { - case true => JpResult(showable(klass, filter)) - case _ => JpResult(reporter.reportable()) - } recoverWith { - case e: java.lang.reflect.InvocationTargetException => e.getCause match { - case t: IllegalArgumentException => Success(JpResult(t.getMessage)) // bad option - case x => Failure(x) - } - } lastly { - reporter.clear() - } - /** Run the tool. */ - def apply(options: Seq[String], filter: Boolean)(inputs: Seq[Input]): List[JpResult] = (inputs map { - case (klass, Success(_)) => applyOne(options, filter, klass, inputs).get - case (_, Failure(e)) => JpResult(e.toString) - }).toList orFailed List(noToolError) - } - - object JavapTool { - // >= 1.7 - val JavapTask = "com.sun.tools.javap.JavapTask" - - private def hasClass(cl: ScalaClassLoader, cn: String) = cl.tryToInitializeClass[AnyRef](cn).isDefined - - def isAvailable = hasClass(loader, JavapTask) - - /** Select the tool implementation for this platform. */ - def apply() = { - require(isAvailable) - new JavapTool - } + (q, load(q)) } } object JavapClass { - - def apply( - loader: ScalaClassLoader = ScalaClassLoader.appLoader, - printWriter: PrintWriter = new PrintWriter(System.out, true), - intp: IMain - ) = new JavapClass(loader, printWriter, intp) - - /** Match foo#bar, both groups are optional (may be null). */ - val HashSplit = "([^#]+)?(?:#(.+)?)?".r + private final val classSuffix = ".class" // We enjoy flexibility in specifying either a fully-qualified class name com.acme.Widget // or a resource path com/acme/Widget.class; but not widget.out - implicit class MaybeClassLike(val s: String) extends AnyVal { - /* private[this] final val suffix = ".class" */ - private def suffix = ".class" - def asClassName = (s stripSuffix suffix).replace('/', '.') - def asClassResource = if (s endsWith suffix) s else s.replace('.', '/') + suffix - def splitSuffix: (String, String) = if (s endsWith suffix) (s dropRight suffix.length, suffix) else (s, "") - def strippingSuffix(f: String => String): String = - if (s endsWith suffix) f(s dropRight suffix.length) else s - // e.g. Foo#bar. Foo# yields zero-length member part. - def splitHashMember: (String, Option[String]) = { - val i = s lastIndexOf '#' - if (i < 0) (s, None) - //else if (i >= s.length - 1) (s.init, None) - else (s take i, Some(s drop i+1)) - } + implicit private class MaybeClassLike(val s: String) extends AnyVal { + def asClassName = s.stripSuffix(classSuffix).replace('/', '.') + def asClassResource = if (s.endsWith(classSuffix)) s else s.replace('.', '/') + classSuffix } - implicit class ClassLoaderOps(val loader: ScalaClassLoader) extends AnyVal { + implicit private class ClassLoaderOps(val loader: ScalaClassLoader) extends AnyVal { /* would classBytes succeed with a nonempty array */ def resourceable(className: String): Boolean = loader.getResource(className.asClassResource) != null } - implicit class URLOps(val url: URL) extends AnyVal { - def isFile: Boolean = url.getProtocol == "file" - } } -abstract class Javap { +abstract class Javap(protected val intp: IMain) { + def loader: Either[String, ClassLoader] + + def task(loader: ClassLoader): Either[String, JavapTool] + /** Run the tool. Option args start with "-", except that "-" itself * denotes the last REPL result. * The default options are "-protected -verbose". * Byte data for filename args is retrieved with findBytes. * @return results for invoking JpResult.show() */ - def apply(args: Seq[String]): List[Javap.JpResult] + final def apply(args: Seq[String]): List[Javap.JpResult] = + if (args.isEmpty) List(JpResult(Javap.helpText)) + else + loader match { + case Left(msg) => List(JpResult(msg)) + case Right(cl) => + task(cl) match { + case Left(msg) => List(JpResult(msg)) + case Right(tk) => new JavapClass(cl, intp, tk).apply(args) + } + } } object Javap { - def isAvailable(cl: ScalaClassLoader = ScalaClassLoader.appLoader) = JavapClass(cl, intp = null).JavapTool.isAvailable + import scala.util.Properties.isJavaAtLeast + import java.io.File + import java.net.URL + + private val javap8 = "scala.tools.nsc.interpreter.Javap8" + private val javap9 = "scala.tools.nsc.interpreter.Javap9" + private val javapP = "scala.tools.nsc.interpreter.JavapProvider" + + // load and run a tool + def apply(intp: IMain)(targets: String*): List[JpResult] = { + def outDirIsClassPath: Boolean = intp.settings.Yreploutdir.isSetByUser && { + val outdir = intp.replOutput.dir.file.getAbsoluteFile + intp.compilerClasspath.exists(url => url.isFile && new File(url.toURI).getAbsoluteFile == outdir) + } + def create(toolName: String) = { + val loader = new ClassLoader(getClass.getClassLoader) with ScalaClassLoader + loader.create[Javap](toolName, Console.println(_))(intp) + } + def advisory = { + val msg = "On JDK 9 or higher, use -nobootcp to enable :javap, or set -Yrepl-outdir to a file system path on the tool class path with -toolcp." + List(JpResult(msg)) + } - def apply(path: String): Unit = apply(Seq(path)) - def apply(args: Seq[String]): Unit = JavapClass(intp=null) apply args foreach (_.show()) + if (targets.isEmpty) List(JpResult(Javap.helpText)) + else if (!isJavaAtLeast("9")) create(javap8)(targets) + else { + var res: Option[List[JpResult]] = None + if (classOf[scala.tools.nsc.interpreter.IMain].getClassLoader != null) { + val javap = create(javap9) + if (javap.loader.isRight) + res = Some(javap(targets)) + } + res.getOrElse { + if (outDirIsClassPath) create(javapP)(targets) + else advisory + } + } + } + + implicit private class URLOps(val url: URL) extends AnyVal { + def isFile: Boolean = url.getProtocol == "file" + } + + /** Match foo#bar, both groups are optional (may be null). */ + val HashSplit = "([^#]+)?(?:#(.+)?)?".r + + // e.g. Foo#bar. Foo# yields zero-length member part. + private def splitHashMember(s: String): Option[String] = + s.lastIndexOf('#') match { + case -1 => None + case i => Some(s.drop(i+1)) + } + + // filter lines of javap output for target such as Klass#methode + def filterLines(target: String, text: String): String = { + // take Foo# as Foo#apply for purposes of filtering. + val filterOn = splitHashMember(target).map(s => if (s.isEmpty) "apply" else s) + var filtering = false // true if in region matching filter + // turn filtering on/off given the pattern of interest + def filterStatus(line: String, pattern: String) = { + def isSpecialized(method: String) = (method startsWith pattern+"$") && (method endsWith "$sp") + def isAnonymized(method: String) = (pattern == "$anonfun") && (method startsWith "$anonfun$") + // cheap heuristic, todo maybe parse for the java sig. + // method sigs end in paren semi + def isAnyMethod = line endsWith ");" + // take the method name between the space char and left paren. + // accept exact match or something that looks like what we might be asking for. + def isOurMethod = { + val lparen = line lastIndexOf '(' + val blank = line.lastIndexOf(' ', lparen) + if (blank < 0) false + else { + val method = line.substring(blank+1, lparen) + (method == pattern || isSpecialized(method) || isAnonymized(method)) + } + } + filtering = + if (filtering) { + // next blank line terminates section + // in non-verbose mode, next line is next method, more or less + line.trim.nonEmpty && (!isAnyMethod || isOurMethod) + } else { + isAnyMethod && isOurMethod + } + filtering + } + // do we output this line? + def checkFilter(line: String) = filterOn.map(filterStatus(line, _)).getOrElse(true) + stringFromWriter(pw => text.linesIterator.foreach(line => if (checkFilter(line)) pw.println(line))) + } private[interpreter] trait Showable { def show(): Unit } + /** Create a Showable to show tool messages and tool output, with output massage. + * @param filter whether to strip REPL names + */ + def showable(intp: IMain, filter: Boolean, text: String): Showable = + new Showable { + val out = new IMain.ReplStrippingWriter(intp) + def show() = + if (filter) intp.withoutTruncating(out.write(text)) + else intp.withoutUnwrapping(out.write(text, 0, text.length)) + } + sealed trait JpResult { type ResultType def isError: Boolean @@ -433,8 +305,9 @@ object Javap { def show() = value.show() // output to tool's PrintWriter } + // split javap options from REPL's -filter flag, also take prefixes of flag names def toolArgs(args: Seq[String]): (Seq[String], Boolean) = { - val (opts, rest) = args flatMap massage partition (_ != "-filter") + val (opts, rest) = args.flatMap(massage).partition(_ != "-filter") (opts, rest.nonEmpty) } @@ -489,13 +362,212 @@ object Javap { def helpText: String = (helps map { case (name, help) => f"$name%-12.12s$help%n" }).mkString - def helper(pw: PrintWriter) = new Showable { - def show() = pw print helpText - } + def helper(pw: PrintWriter) = new Showable { def show() = pw.print(helpText) } val DefaultOptions = List("-protected", "-verbose") } -object NoJavap extends Javap { - def apply(args: Seq[String]): List[Javap.JpResult] = Nil +/** Loaded reflectively under JDK8 to locate tools.jar and load JavapTask tool. */ +class Javap8(intp0: IMain) extends Javap(intp0) { + import scala.tools.util.PathResolver + import scala.util.Properties.jdkHome + + private def findToolsJar() = PathResolver.SupplementalLocations.platformTools + + private def addToolsJarToLoader() = + findToolsJar() match { + case Some(tools) => ScalaClassLoader.fromURLs(Seq(tools.toURL), intp.classLoader) + case _ => intp.classLoader + } + override def loader = + Right(addToolsJarToLoader()).filterOrElse( + _.tryToInitializeClass[AnyRef](JavapTask.taskClassName).isDefined, + s":javap unavailable: no ${JavapTask.taskClassName} or no tools.jar at $jdkHome" + ) + override def task(loader: ClassLoader) = Right(new JavapTask(loader, intp)) +} + +/** Loaded reflectively under JDK9 to load JavapTask tool. */ +class Javap9(intp0: IMain) extends Javap(intp0) { + override def loader = + Right(new ClassLoader(intp.classLoader) with ScalaClassLoader).filterOrElse( + _.tryToInitializeClass[AnyRef](JavapTask.taskClassName).isDefined, + s":javap unavailable: no ${JavapTask.taskClassName}" + ) + override def task(loader: ClassLoader) = Right(new JavapTask(loader, intp)) +} + +/** Loaded reflectively under JDK9 to locate ToolProvider. */ +class JavapProvider(intp0: IMain) extends Javap(intp0) { + import JavapTool.Input + import Javap.{filterLines, HashSplit} + import java.util.Optional + //import java.util.spi.ToolProvider + + type ToolProvider = AnyRef { def run(out: PrintWriter, err: PrintWriter, args: Array[String]): Unit } + + override def loader = Right(getClass.getClassLoader) + + private def tool(provider: ToolProvider) = new JavapTool { + override def apply(options: Seq[String], filter: Boolean)(inputs: Seq[Input]): List[JpResult] = inputs.map { + case Input(target @ HashSplit(klass, _), actual, Success(_)) => + val more = List("-cp", intp.replOutput.dir.file.getAbsoluteFile.toString, actual) + val s = stringFromWriter(w => provider.run(w, w, (options ++ more).toArray)) + JpResult(filterLines(target, s)) + case Input(_, _, Failure(e)) => JpResult(e.toString) + }.toList + } + + //ToolProvider.findFirst("javap") + override def task(loader: ClassLoader) = { + val provider = Class.forName("java.util.spi.ToolProvider", /*initialize=*/ true, loader) + .getDeclaredMethod("findFirst", classOf[String]) + .invoke(null, "javap").asInstanceOf[Optional[ToolProvider]] + if (provider.isPresent) + Right(tool(provider.get)) + else + Left(s":javap unavailable: provider not found") + } +} + +/** The task or tool provider. */ +abstract class JavapTool { + import JavapTool._ + def apply(options: Seq[String], filter: Boolean)(inputs: Seq[Input]): List[JpResult] +} +object JavapTool { + case class Input(target: String, actual: String, data: Try[Array[Byte]]) +} + +// Machinery to run JavapTask reflectively +class JavapTask(val loader: ScalaClassLoader, intp: IMain) extends JavapTool { + import javax.tools.{Diagnostic, DiagnosticListener, + ForwardingJavaFileManager, JavaFileManager, JavaFileObject, + SimpleJavaFileObject, StandardLocation} + import java.io.CharArrayWriter + import java.util.Locale + import java.util.concurrent.ConcurrentLinkedQueue + import scala.collection.JavaConverters._ + import scala.collection.generic.Clearable + import JavapTool._ + import Javap.{filterLines, showable} + + // output filtering support + val writer = new CharArrayWriter + def written = { + writer.flush() + val w = writer.toString + writer.reset() + w + } + + type Task = { + def call(): Boolean // true = ok + //def run(args: Array[String]): Int // all args + //def handleOptions(args: Array[String]): Unit // options, then run() or call() + } + // result of Task.run + //object TaskResult extends Enumeration { + // val Ok, Error, CmdErr, SysErr, Abnormal = Value + //} + + class JavaReporter extends DiagnosticListener[JavaFileObject] with Clearable { + type D = Diagnostic[_ <: JavaFileObject] + val diagnostics = new ConcurrentLinkedQueue[D] + override def report(d: Diagnostic[_ <: JavaFileObject]) = diagnostics.add(d) + override def clear() = diagnostics.clear() + /** All diagnostic messages. + * @param locale Locale for diagnostic messages, null by default. + */ + def messages(implicit locale: Locale = null) = diagnostics.asScala.map(_.getMessage(locale)).toList + + def reportable(): String = { + import scala.util.Properties.lineSeparator + clear() + if (messages.nonEmpty) messages.mkString("", lineSeparator, lineSeparator) else "" + } + } + val reporter = new JavaReporter + + // DisassemblerTool.getStandardFileManager(reporter,locale,charset) + val defaultFileManager: JavaFileManager = + (loader.tryToLoadClass[JavaFileManager]("com.sun.tools.javap.JavapFileManager").get getMethod ( + "create", + classOf[DiagnosticListener[_]], + classOf[PrintWriter] + ) invoke (null, reporter, new PrintWriter(System.err, true))).asInstanceOf[JavaFileManager] + + // manages named arrays of bytes, which might have failed to load + class JavapFileManager(val managed: Seq[Input])(delegate: JavaFileManager = defaultFileManager) + extends ForwardingJavaFileManager[JavaFileManager](delegate) { + import JavaFileObject.Kind + import Kind._ + import StandardLocation._ + import JavaFileManager.Location + import java.net.{URI, URISyntaxException} + import java.io.ByteArrayInputStream + + // name#fragment is OK, but otherwise fragile + def uri(name: String): URI = + try new URI(name) // new URI("jfo:" + name) + catch { case _: URISyntaxException => new URI("dummy") } + + // look up by actual class name or by target descriptor (unused?) + def inputNamed(name: String): Try[Array[Byte]] = managed.find(m => m.actual == name || m.target == name).get.data + + def managedFile(name: String, kind: Kind) = kind match { + case CLASS => fileObjectForInput(name, inputNamed(name), kind) + case _ => null + } + // todo: just wrap it as scala abstractfile and adapt it uniformly + def fileObjectForInput(name: String, bytes: Try[Array[Byte]], kind: Kind): JavaFileObject = + new SimpleJavaFileObject(uri(name), kind) { + override def openInputStream(): InputStream = new ByteArrayInputStream(bytes.get) + // if non-null, ClassWriter wrongly requires scheme non-null + override def toUri: URI = null + override def getName: String = name + // suppress + override def getLastModified: Long = -1L + } + override def getJavaFileForInput(location: Location, className: String, kind: Kind): JavaFileObject = + location match { + case CLASS_PATH => managedFile(className, kind) + case _ => null + } + override def hasLocation(location: Location): Boolean = + location match { + case CLASS_PATH => true + case _ => false + } + } + def fileManager(inputs: Seq[Input]) = new JavapFileManager(inputs)() + + // eventually, use the tool interface [Edit: which became ToolProvider] + //ServiceLoader.load(classOf[javax.tools.DisassemblerTool]). + //getTask(writer, fileManager, reporter, options.asJava, classes.asJava) + def task(options: Seq[String], classes: Seq[String], inputs: Seq[Input]): Task = + loader.create[Task](JavapTask.taskClassName, Console.println(_))(writer, fileManager(inputs), reporter, options.asJava, classes.asJava) + + /** Run the tool. */ + override def apply(options: Seq[String], filter: Boolean)(inputs: Seq[Input]): List[JpResult] = inputs.map { + case Input(target, actual, Success(_)) => + import java.lang.reflect.InvocationTargetException + try { + if (task(options, Seq(actual), inputs).call()) JpResult(showable(intp, filter, filterLines(target, s"${reporter.reportable()}${written}"))) + else JpResult(reporter.reportable()) + } catch { + case e: InvocationTargetException => e.getCause match { + case t: IllegalArgumentException => JpResult(t.getMessage) // bad option + case x => throw x + } + } finally { + reporter.clear() + } + case Input(_, _, Failure(e)) => JpResult(e.getMessage) + }.toList +} + +object JavapTask { + // introduced in JDK7 as internal API + val taskClassName = "com.sun.tools.javap.JavapTask" } diff --git a/test/files/run/repl-paste-parse.scala b/test/files/run/repl-paste-parse.scala index e93ad4d02bb3..80b7f5922546 100644 --- a/test/files/run/repl-paste-parse.scala +++ b/test/files/run/repl-paste-parse.scala @@ -19,7 +19,7 @@ object Test extends DirectTest { val w = new StringWriter val p = new PrintWriter(w, true) new ILoop(r, p).process(settings) - w.toString.lines foreach { s => + w.toString.linesIterator foreach { s => if (!s.startsWith("Welcome to Scala")) println(s) } } diff --git a/test/files/run/t6502.scala b/test/files/run/t6502.scala index cb2b3ff4493a..17f573945f8c 100644 --- a/test/files/run/t6502.scala +++ b/test/files/run/t6502.scala @@ -5,8 +5,8 @@ import scala.tools.partest._ object Test extends StoreReporterDirectTest { def code = ??? - lazy val headerLength = replProps.welcome.lines.size - lazy val promptLength = replProps.prompt.lines.size - 1 // extra newlines + lazy val headerLength = replProps.welcome.linesIterator.size + lazy val promptLength = replProps.prompt.linesIterator.size - 1 // extra newlines def compileCode(code: String, jarFileName: String) = { val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator") @@ -55,12 +55,12 @@ object Test extends StoreReporterDirectTest { |test.Test.test() |""".stripMargin.trim val output = ILoop.run(codeToRun, settings) - var lines = output.lines.drop(headerLength) + var lines = output.linesIterator.drop(headerLength) lines = lines drop promptLength val added = lines.next assert ( added.contains("Added") && added.contains("test1.jar"), - s"[${added}] in [${output.lines.mkString("/")}]" + s"[${added}] in [${output.linesIterator.mkString("/")}]" ) lines = lines drop promptLength val r = lines.next @@ -78,7 +78,7 @@ object Test extends StoreReporterDirectTest { |:require ${testOutput.path}/$jar2 |""".stripMargin.trim val output = ILoop.run(codeToRun, settings) - var lines = output.lines.drop(headerLength) + var lines = output.linesIterator.drop(headerLength) lines = lines drop promptLength val added = lines.next assert(added.contains("Added") && added.contains("test1.jar"), added) @@ -99,7 +99,7 @@ object Test extends StoreReporterDirectTest { |test.Test3.test() |""".stripMargin.trim val output = ILoop.run(codeToRun, settings) - var lines = output.lines.drop(headerLength) + var lines = output.linesIterator.drop(headerLength) lines = lines drop promptLength val added = lines.next assert(added.contains("Added") && added.contains("test1.jar"), added) @@ -116,7 +116,7 @@ object Test extends StoreReporterDirectTest { |:require ${testOutput.path}/$jar1 |""".stripMargin.trim val output = ILoop.run(codeToRun, settings) - var lines = output.lines.drop(headerLength) + var lines = output.linesIterator.drop(headerLength) lines = lines drop promptLength val added = lines.next assert(added.contains("Added") && added.contains("test1.jar"), added) diff --git a/test/files/run/t8549.scala b/test/files/run/t8549.scala index d57ad4b454fe..8867b7f8d225 100644 --- a/test/files/run/t8549.scala +++ b/test/files/run/t8549.scala @@ -1,4 +1,4 @@ -import javax.xml.bind.DatatypeConverter._ +import java.util.Base64 import scala.reflect.io.File // This test is self-modifying when run as follows: @@ -10,6 +10,9 @@ import scala.reflect.io.File // // Use this to re-establish a baseline for serialization compatibility. object Test extends App { + def printBase64Binary(x: Array[Byte]): String = Base64.getEncoder().encodeToString(x) + def parseBase64Binary(x: String): Array[Byte] = Base64.getDecoder().decode(x) + val overwrite: Option[File] = sys.props.get("overwrite.source").map(s => new File(new java.io.File(s))) def serialize(o: AnyRef): String = { @@ -28,7 +31,7 @@ object Test extends App { def patch(file: File, line: Int, prevResult: String, result: String) { amend(file) { content => - content.lines.toList.zipWithIndex.map { + content.linesIterator.toList.zipWithIndex.map { case (content, i) if i == line - 1 => val newContent = content.replaceAllLiterally(quote(prevResult), quote(result)) if (newContent != content) @@ -48,7 +51,7 @@ object Test extends App { val newComment = s" // Generated on $timestamp with Scala ${scala.util.Properties.versionString})" amend(file) { content => - content.lines.toList.map { + content.linesIterator.toList.map { f => f.replaceAll("""^ +// Generated on.*""", newComment) }.mkString("\n") } diff --git a/test/files/run/t8608-no-format.scala b/test/files/run/t8608-no-format.scala index 71c369a7eac0..900f59aaa930 100644 --- a/test/files/run/t8608-no-format.scala +++ b/test/files/run/t8608-no-format.scala @@ -4,7 +4,7 @@ import scala.tools.partest.JavapTest object Test extends JavapTest { def code = """ |f"hello, world" - |:javap -prv - + |:javap -pv - """.stripMargin // no format diff --git a/test/junit/scala/collection/IndexedSeqTest.scala b/test/junit/scala/collection/IndexedSeqTest.scala index 4c89f7274ec9..72f0ab0210c0 100644 --- a/test/junit/scala/collection/IndexedSeqTest.scala +++ b/test/junit/scala/collection/IndexedSeqTest.scala @@ -2,14 +2,10 @@ package scala.collection import org.junit.Test import org.junit.Ignore -import org.junit.Assert.{assertEquals, _} +import org.junit.Assert._ import org.junit.runner.RunWith import org.junit.runners.JUnit4 -// with the Ant JUnit runner, it's necessary to @Ignore the abstract -// classes here, or JUnit tries to instantiate them. the annotations -// can be removed when this is merged forward (TODO 2.12.x) - /** * base class for testing common methods on a various implementations * @@ -17,7 +13,6 @@ import org.junit.runners.JUnit4 * @tparam E the element type */ @RunWith(classOf[JUnit4]) -@Ignore abstract class IndexedTest[T, E] { protected def size = 10 diff --git a/test/junit/scala/collection/mutable/ListBufferTest.scala b/test/junit/scala/collection/mutable/ListBufferTest.scala new file mode 100644 index 000000000000..e6380ced07d7 --- /dev/null +++ b/test/junit/scala/collection/mutable/ListBufferTest.scala @@ -0,0 +1,36 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.collection.mutable + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class ListBufferTest { + @Test def `toSeq and friends should not expose mutability (scala/bug#11869)` { + def check[CC[x] <: collection.Traversable[x]](export: ListBuffer[Int] => CC[Int]) { + val buf = ListBuffer.empty[Int] + buf += 1; buf += 2 + val res = export(buf) + buf += 3 + assertEquals(3, buf.size) + assertEquals(2, res.size) + } + check(_.toList) + check(_.toSeq) + check(_.toIterable) + check(_.toStream) + } +} diff --git a/test/scalacheck/org/scalacheck/CustomScalaCheckRunner.scala b/test/scalacheck/org/scalacheck/CustomScalaCheckRunner.scala deleted file mode 100644 index 340940d7cb01..000000000000 --- a/test/scalacheck/org/scalacheck/CustomScalaCheckRunner.scala +++ /dev/null @@ -1,232 +0,0 @@ -package org.scalacheck - -import java.util.concurrent.atomic.AtomicInteger - -import org.scalacheck.Test.Parameters -import sbt.testing._ - -private abstract class CustomScalaCheckRunner extends Runner { - - val args: Array[String] - val loader: ClassLoader - val applyCmdParams: Parameters => Parameters - - val successCount = new AtomicInteger(0) - val failureCount = new AtomicInteger(0) - val errorCount = new AtomicInteger(0) - val testCount = new AtomicInteger(0) - - def deserializeTask(task: String, deserializer: String => TaskDef) = { - val taskDef = deserializer(task) - val countTestSelectors = taskDef.selectors.toSeq.count { - case _:TestSelector => true - case _ => false - } - if (countTestSelectors == 0) rootTask(taskDef) - else checkPropTask(taskDef, single = true) - } - - def serializeTask(task: Task, serializer: TaskDef => String) = - serializer(task.taskDef) - - def tasks(taskDefs: Array[TaskDef]): Array[Task] = { - val isForked = taskDefs.exists(_.fingerprint().getClass.getName.contains("ForkMain")) - taskDefs.map { taskDef => - if (isForked) checkPropTask(taskDef, single = false) - else rootTask(taskDef) - } - } - - abstract class BaseTask(override val taskDef: TaskDef) extends Task { - val tags: Array[String] = Array() - - val props: Seq[(String,Prop)] = { - val fp = taskDef.fingerprint.asInstanceOf[SubclassFingerprint] - val obj = if (fp.isModule) Platform.loadModule(taskDef.fullyQualifiedName,loader) - else Platform.newInstance(taskDef.fullyQualifiedName, loader)(Seq()) - obj match { - case props: Properties => props.properties - case prop: Prop => Seq("" -> prop) - } - } - - // TODO copypasted from props val - val properties: Option[Properties] = { - val fp = taskDef.fingerprint.asInstanceOf[SubclassFingerprint] - val obj = if (fp.isModule) Platform.loadModule(taskDef.fullyQualifiedName,loader) - else Platform.newInstance(taskDef.fullyQualifiedName, loader)(Seq()) - obj match { - case props: Properties => Some(props) - case prop: Prop => None - } - } - - def log(loggers: Array[Logger], ok: Boolean, msg: String) = - loggers foreach { l => - val logstr = - if(!l.ansiCodesSupported) msg - else s"${if (ok) Console.GREEN else Console.RED}$msg${Console.RESET}" - l.info(logstr) - } - - def execute(handler: EventHandler, loggers: Array[Logger], - continuation: Array[Task] => Unit - ): Unit = continuation(execute(handler,loggers)) - } - - def rootTask(td: TaskDef) = { - new BaseTask(td) { - def execute(handler: EventHandler, loggers: Array[Logger]): Array[Task] = { - props.map(_._1).toSet.toArray map { name => - checkPropTask(new TaskDef(td.fullyQualifiedName, td.fingerprint, - td.explicitlySpecified, Array(new TestSelector(name))) - , single = true) - } - } - } - } - - def checkPropTask(taskDef: TaskDef, single: Boolean) = new BaseTask(taskDef) { - def execute(handler: EventHandler, loggers: Array[Logger]): Array[Task] = { - val params = applyCmdParams(properties.foldLeft(Parameters.default)((params, props) => props.overrideParameters(params))) - val propertyFilter = None - - if (single) { - val names = taskDef.selectors flatMap { - case ts: TestSelector => Array(ts.testName) - case _ => Array.empty[String] - } - names foreach { name => - for ((`name`, prop) <- props) - executeInternal(prop, name, handler, loggers, propertyFilter) - } - } else { - for ((name, prop) <- props) - executeInternal(prop, name, handler, loggers, propertyFilter) - } - Array.empty[Task] - } - - def executeInternal(prop: Prop, name: String, handler: EventHandler, loggers: Array[Logger], propertyFilter: Option[scala.util.matching.Regex]): Unit = { - import util.Pretty.{Params, pretty} - val params = applyCmdParams(properties.foldLeft(Parameters.default)((params, props) => props.overrideParameters(params))) - val result = Test.check(params, prop) - - val event = new Event { - val status = result.status match { - case Test.Passed => Status.Success - case _: Test.Proved => Status.Success - case _: Test.Failed => Status.Failure - case Test.Exhausted => Status.Failure - case _: Test.PropException => Status.Error - } - val throwable = result.status match { - case Test.PropException(_, e, _) => new OptionalThrowable(e) - case _: Test.Failed => new OptionalThrowable( - new Exception(pretty(result, Params(0))) - ) - case _ => new OptionalThrowable() - } - val fullyQualifiedName = taskDef.fullyQualifiedName - val selector = new TestSelector(name) - val fingerprint = taskDef.fingerprint - val duration = -1L - } - - handler.handle(event) - - event.status match { - case Status.Success => successCount.incrementAndGet() - case Status.Error => errorCount.incrementAndGet() - case Status.Skipped => errorCount.incrementAndGet() - case Status.Failure => failureCount.incrementAndGet() - case _ => failureCount.incrementAndGet() - } - testCount.incrementAndGet() - - // TODO Stack traces should be reported through event - val verbosityOpts = Set("-verbosity", "-v") - val verbosity = - args.grouped(2).filter(twos => verbosityOpts(twos.head)) - .toSeq.headOption.map(_.last).map(_.toInt).getOrElse(0) - val s = if (result.passed) "+" else "!" - val n = if (name.isEmpty) taskDef.fullyQualifiedName else name - val logMsg = s"$s $n: ${pretty(result, Params(verbosity))}" - log(loggers, result.passed, logMsg) - } - } -} - - -final class CustomScalaCheckFramework extends Framework { - - private def mkFP(mod: Boolean, cname: String, noArgCons: Boolean = true) = - new SubclassFingerprint { - def superclassName(): String = cname - val isModule = mod - def requireNoArgConstructor(): Boolean = noArgCons - } - - val name = "ScalaCheck" - - def fingerprints: Array[Fingerprint] = Array( - mkFP(false, "org.scalacheck.Properties"), - mkFP(false, "org.scalacheck.Prop"), - mkFP(true, "org.scalacheck.Properties"), - mkFP(true, "org.scalacheck.Prop") - ) - - def runner(_args: Array[String], _remoteArgs: Array[String], - _loader: ClassLoader - ): Runner = new CustomScalaCheckRunner { - - val args = _args - val remoteArgs = _remoteArgs - val loader = _loader - val (prms,unknownArgs) = Test.cmdLineParser.parseParams(args) - val applyCmdParams = prms.andThen { - p => p.withTestCallback(new Test.TestCallback {}) - .withCustomClassLoader(Some(loader)) - } - - def receiveMessage(msg: String): Option[String] = msg(0) match { - case 'd' => - val Array(t,s,f,e) = msg.tail.split(',') - testCount.addAndGet(t.toInt) - successCount.addAndGet(s.toInt) - failureCount.addAndGet(f.toInt) - errorCount.addAndGet(e.toInt) - None - } - - def done = if (testCount.get > 0) { - val heading = if (testCount.get == successCount.get) "Passed" else "Failed" - s"$heading: Total $testCount, " + - s"Failed $failureCount, Errors $errorCount, Passed $successCount" + - (if(unknownArgs.isEmpty) "" else - s"\nWarning: Unknown ScalaCheck args provided: ${unknownArgs.mkString(" ")}") - } else "" - - } - - def slaveRunner(_args: Array[String], _remoteArgs: Array[String], - _loader: ClassLoader, send: String => Unit - ): Runner = new ScalaCheckRunner { - val args = _args - val remoteArgs = _remoteArgs - val loader = _loader - val applyCmdParams = Test.cmdLineParser.parseParams(args)._1.andThen { - p => p.withTestCallback(new Test.TestCallback {}) - .withCustomClassLoader(Some(loader)) - } - - def receiveMessage(msg: String) = None - - def done = { - send(s"d$testCount,$successCount,$failureCount,$errorCount") - "" - } - - } - -} diff --git a/versions.properties b/versions.properties index 49578ec1540d..39c54adb2e37 100644 --- a/versions.properties +++ b/versions.properties @@ -23,5 +23,5 @@ scala-xml.version.number=1.0.6 scala-parser-combinators.version.number=1.0.7 scala-swing.version.number=2.0.3 partest.version.number=1.1.9 -scala-asm.version=7.0.0-scala-1 +scala-asm.version=7.3.1-scala-1 jline.version=2.14.6