Skip to content

Commit

Permalink
add filtering by jvm version in TastyTest
Browse files Browse the repository at this point in the history
  • Loading branch information
bishabosha committed Feb 12, 2024
1 parent b0c8120 commit fa4c3df
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/tastytest/scala/tools/tastytest/Files.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ object Files {
}
}

def filterByNames(names: Set[String])(elem: String): Boolean = {
def allowByNames(names: Set[String])(elem: String): Boolean = {
val path = JPaths.get(elem)
val name = path.getFileName.toString
names.contains(name)
Expand Down
40 changes: 40 additions & 0 deletions src/tastytest/scala/tools/tastytest/SourceFile.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.tools.tastytest

import scala.util.Using
import scala.io.Source

import SourceFile._
import scala.util.chaining._

final case class SourceFile(path: String) {
lazy val options: Options = readOptions(path)
}

object SourceFile {

private val directivePattern = raw"\s*//>\s+using\s+(\S+)(?:\s+(.*))?".r
final case class Options(data: Map[String, Option[String]])

def readOptions(path: String): Options =
Using.resource(Source.fromFile(path)) { source =>
source.getLines().takeWhile(_.trim.startsWith("//>"))
.flatMap {
case directivePattern(key, valueOrNull) => Some(key -> Option(valueOrNull))
case _ => None
}
.toMap
.pipe(Options(_))
}
}
69 changes: 54 additions & 15 deletions src/tastytest/scala/tools/tastytest/SourceKind.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,65 @@

package scala.tools.tastytest

sealed abstract class SourceKind(val name: String)(val filter: String => Boolean = _.endsWith(name)) { self =>
import scala.util.Properties

sealed abstract class SourceKind(val name: String){ self =>
def permits(file: String): Boolean
def shouldValidate: Boolean
def validate(options: SourceFile.Options): Boolean
}

sealed trait PermitByName { self: SourceKind =>
def permits(file: String): Boolean = file.endsWith(name)
def fileOf(name: String) = name + self.name
}

sealed trait AlwaysValid { self: SourceKind =>
def shouldValidate: Boolean = false
def validate(options: SourceFile.Options) = true
}

sealed trait CheckJVM { self: SourceKind =>
def shouldValidate: Boolean = true
def validate(options: SourceFile.Options) = {
import CheckJVM.versionPattern
options.data.get("jvm") match {
case None => true // nothing to check
case Some(value) => value.getOrElse("") match {
case versionPattern(raw) => Properties.isJavaAtLeast(raw.toInt)
case value => throw new IllegalArgumentException(s"Invalid JVM version: $value")
}
}
}

}

object CheckJVM {
val versionPattern: scala.util.matching.Regex = raw"(\d+)\+".r
}

object SourceKind {

case object NoSource extends SourceKind("")(filter = _ => false)
case object Scala extends SourceKind(".scala")()
case object ScalaFail extends SourceKind("_fail.scala")()
case object ScalaPre extends SourceKind("_pre.scala")()
case object Check extends SourceKind(".check")()
case object SkipCheck extends SourceKind(".skipcheck")()
case object Java extends SourceKind(".java")()
case object TastyFile extends SourceKind(".tasty")()

case class ExactFiles(names: String*) extends SourceKind("")(filter = Files.filterByNames(names.toSet)) {
override def fileOf(name: String) = names.find(_.startsWith(name)).getOrElse("")
case object Scala extends SourceKind(".scala") with PermitByName with CheckJVM
case object ScalaFail extends SourceKind("_fail.scala") with PermitByName with AlwaysValid
case object ScalaPre extends SourceKind("_pre.scala") with PermitByName with AlwaysValid
case object Check extends SourceKind(".check") with PermitByName with AlwaysValid
case object SkipCheck extends SourceKind(".skipcheck") with PermitByName with AlwaysValid
case object Java extends SourceKind(".java") with PermitByName with CheckJVM
case object TastyFile extends SourceKind(".tasty") with PermitByName with AlwaysValid

final case class ExactFiles(names: String*) extends SourceKind("") with AlwaysValid {
override def permits(file: String) = names.contains(file)
}

def filterByKind(kinds: Set[SourceKind], paths: String*): Seq[String] =
if (kinds.isEmpty) Nil
else paths.filter(kinds.foldLeft(NoSource.filter)((filter, kind) => p => kind.filter(p) || filter(p)))
def allowByKind(kinds: Set[SourceKind], paths: String*): Seq[String] = {
if (kinds.isEmpty) Nil // no kinds, so allow nothing
else {
val bigPermit = kinds.foldLeft((_: SourceFile) => false) { (permits, kind) =>
file =>
kind.permits(file.path) && (!kind.shouldValidate || kind.validate(file.options)) || permits(file)
}
paths.view.map(new SourceFile(_)).filter(bigPermit).map(_.path).toSeq
}
}
}
38 changes: 19 additions & 19 deletions src/tastytest/scala/tools/tastytest/TastyTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ object TastyTest {
tastyJar = outJ/"java-tasty.jar"
_ <- dotcPos(outCls, sourceRoot=srcRoot/src/"src-3", pipelineDottyOpts(tastyJar) ++: additionalDottySettings, src3:_*)
allOuts <- getFiles(outCls)
relTastys <- relativize(outCls, filterByKind(Set(TastyFile), allOuts:_*):_*)
relTastys <- relativize(outCls, allowByKind(Set(TastyFile), allOuts:_*):_*)
_ <- copyAll(relTastys, outCls, out)
src2Filtered = testFilter.fold(src2)(kind => filterByKind(Set(kind), src2:_*))
src2Filtered = testFilter.fold(src2)(kind => allowByKind(Set(kind), src2:_*))
_ <- scalacPos(out, tastyJar, individualCapable=true, sourceRoot=srcRoot/src/"src-2", additionalSettings, src2Filtered:_*)
_ <- javacPos(outCls, sourceRoot=srcRoot/src/"src-3", filterByKind(Set(Java), src3:_*):_*)
_ <- javacPos(outCls, sourceRoot=srcRoot/src/"src-3", allowByKind(Set(Java), src3:_*):_*)
testNames <- visibleClasses(out, pkgName, src2Filtered:_*)
_ <- runMainOn(classpath(out, outCls), testNames:_*)
} yield ()
Expand All @@ -93,8 +93,8 @@ object TastyTest {
(pre, src2, src3) <- getRunSources(srcRoot/src, preFilters = Set(Scala, Java))
_ = log(s"Sources to compile under test: ${src2.map(cyan).mkString(", ")}")
out <- outDir.fold(tempDir(pkgName))(dir)
_ <- javacPos(out, sourceRoot=srcRoot/src/"pre", filterByKind(Set(Java), pre:_*):_*)
_ <- scalacPos(out, individualCapable=false, sourceRoot=srcRoot/src/"pre", additionalSettings, filterByKind(Set(Scala), pre:_*):_*)
_ <- javacPos(out, sourceRoot=srcRoot/src/"pre", allowByKind(Set(Java), pre:_*):_*)
_ <- scalacPos(out, individualCapable=false, sourceRoot=srcRoot/src/"pre", additionalSettings, allowByKind(Set(Scala), pre:_*):_*)
_ <- dotcPos(out, sourceRoot=srcRoot/src/"src-3", additionalDottySettings, src3:_*)
_ <- scalacPos(out, individualCapable=true, sourceRoot=srcRoot/src/"src-2", additionalSettings, src2:_*)
} yield ()
Expand Down Expand Up @@ -135,9 +135,9 @@ object TastyTest {
tastyJar = outJ/"java-tasty.jar"
_ <- dotcPos(outCls, sourceRoot=srcRoot/src/"src-3", pipelineDottyOpts(tastyJar) ++: additionalDottySettings, src3:_*)
allOuts <- getFiles(outCls)
relTastys <- relativize(outCls, filterByKind(Set(TastyFile), allOuts:_*):_*)
relTastys <- relativize(outCls, allowByKind(Set(TastyFile), allOuts:_*):_*)
_ <- copyAll(relTastys, outCls, out)
src2Filtered = testFilter.fold(src2)(kind => filterByKind(Set(kind, Check, SkipCheck), src2:_*))
src2Filtered = testFilter.fold(src2)(kind => allowByKind(Set(kind, Check, SkipCheck), src2:_*))
_ <- scalacNeg(out, tastyJar, additionalSettings, src2Filtered:_*)
} yield ()

Expand Down Expand Up @@ -249,7 +249,7 @@ object TastyTest {
val errors = mutable.ArrayBuffer.empty[String]
val unexpectedFail = mutable.ArrayBuffer.empty[String]
val failMap: Map[String, (Option[String], Option[String])] = {
val (sources, rest) = files.partition(ScalaFail.filter)
val (sources, rest) = files.partition(ScalaFail.permits)
sources.map({ s =>
val name = s.stripSuffix(ScalaFail.name)
val check = Check.fileOf(name)
Expand All @@ -265,7 +265,7 @@ object TastyTest {
}
def negCompile(source: String): Unit = {
val (output, compiled) = {
if (ScalaFail.filter(source)) {
if (ScalaFail.permits(source)) {
val testName = source.stripSuffix(ScalaFail.name)
log(s"neg test ${cyan(testName)} started")
failMap(source) match {
Expand All @@ -289,7 +289,7 @@ object TastyTest {
unexpectedFail += source
System.err.println(output)
printerrln(s"ERROR: $source did not compile when expected to. Perhaps it should match (**/*${ScalaFail.name})")
case Some((Some(checkFile), _)) if Check.filter(checkFile) =>
case Some((Some(checkFile), _)) if Check.permits(checkFile) =>
processLines(checkFile) { stream =>
val checkLines = stream.iterator().asScala.toSeq
val outputLines = Diff.splitIntoLines(output)
Expand All @@ -311,7 +311,7 @@ object TastyTest {
}
}

val sources = files.filter(Scala.filter).filterNot(ScalaPre.filter)
val sources = files.filter(Scala.permits).filterNot(ScalaPre.permits)
sources.foreach(negCompile)
successWhen(errors.isEmpty && unexpectedFail.isEmpty) {
if (unexpectedFail.nonEmpty) {
Expand Down Expand Up @@ -360,7 +360,7 @@ object TastyTest {
for {
(src2, src3) <- get2And3Sources(root, src2Filters, src3Filters)
pre <- getFiles(root/"pre")
} yield (filterByKind(preFilters, pre:_*), src2, src3)
} yield (allowByKind(preFilters, pre:_*), src2, src3)
}

private def getMovePreChangeSources(root: String,
Expand All @@ -372,7 +372,7 @@ object TastyTest {
for {
(src2, src3) <- get2And3Sources(root, src2Filters, src3Filters)
(preA, preB) <- getPreChangeSources(root, preAFilters, preBFilters)
} yield (filterByKind(preAFilters, preA:_*), filterByKind(preBFilters, preB:_*), src2, src3)
} yield (allowByKind(preAFilters, preA:_*), allowByKind(preBFilters, preB:_*), src2, src3)
}

private def get2And3Sources(root: String, src2Filters: Set[SourceKind] /*= Set(Scala)*/,
Expand All @@ -381,7 +381,7 @@ object TastyTest {
for {
src2 <- getFiles(root/"src-2")
src3 <- getFiles(root/"src-3")
} yield (filterByKind(src2Filters, src2:_*), filterByKind(src3Filters, src3:_*))
} yield (allowByKind(src2Filters, src2:_*), allowByKind(src3Filters, src3:_*))
}

private def getFullCircleSources(root: String, src3upFilters: Set[SourceKind] = Set(Scala),
Expand All @@ -393,9 +393,9 @@ object TastyTest {
src2down <- getFiles(root/"src-2-downstream")
src3app <- getFiles(root/"src-3-app")
} yield (
filterByKind(src3upFilters, src3up:_*),
filterByKind(src2downFilters, src2down:_*),
filterByKind(src3appFilters, src3app:_*)
allowByKind(src3upFilters, src3up:_*),
allowByKind(src2downFilters, src2down:_*),
allowByKind(src3appFilters, src3app:_*)
)
}

Expand All @@ -405,7 +405,7 @@ object TastyTest {
for {
preA <- getFiles(root/"pre-a")
preB <- getFiles(root/"pre-b")
} yield (filterByKind(preAFilters, preA:_*), filterByKind(preBFilters, preB:_*))
} yield (allowByKind(preAFilters, preA:_*), allowByKind(preBFilters, preB:_*))
}

private def getNegIsolatedSources(root: String, src2Filters: Set[SourceKind] /*= Set(Scala)*/,
Expand All @@ -415,7 +415,7 @@ object TastyTest {
src2 <- getFiles(root/"src-2")
src3A <- getFiles(root/"src-3-A")
src3B <- getFiles(root/"src-3-B")
} yield (filterByKind(src2Filters, src2:_*), filterByKind(src3Filters, src3A:_*), filterByKind(src3Filters, src3B:_*))
} yield (allowByKind(src2Filters, src2:_*), allowByKind(src3Filters, src3A:_*), allowByKind(src3Filters, src3B:_*))
}

private def visibleClasses(classpath: String, pkgName: String, src2: String*): Try[Seq[String]] = Try {
Expand Down
6 changes: 3 additions & 3 deletions test/tasty/run-pipelined/src-2/tastytest/TestRawTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ object TestRawTypes extends scala.App {
val cd_ii: RawTypes#C[String]#D[String] = new c.D[String]()
val cde_iii: RawTypes#C[String]#D[String]#E[String] = new cd_ii.E[String]()

lazy val cd_is = new rt.C.DStatic[String]() // lazy because this fails at runtime even when reading from a classfile
// lazy val cd_is = new rt.C.DStatic[String]() // lazy because this fails at runtime even when reading from a classfile

val cd_s: RawTypes.CStatic[String] = new RawTypes.CStatic[String]()
val cd_si: RawTypes.CStatic[String]#D[String] = new cd_s.D[String]()
Expand All @@ -20,10 +20,10 @@ object TestRawTypes extends scala.App {
// RawTypes.miii_Raw_Raw_Raw(cde_iii) // fails due to wildcards, see neg-pipelined for error message

RawTypes.mss_Raw_Raw(cd_ss)
def foo1 = RawTypes.mis_Raw_Raw(cd_is) // lazy because this fails at runtime even when reading from a classfile
// def foo1 = RawTypes.mis_Raw_Raw(cd_is) // lazy because this fails at runtime even when reading from a classfile

RawTypes.mss_Raw_Gen(cd_ss)
def foo2 = RawTypes.mis_Raw_Gen(cd_is) // lazy because this fails at runtime even when reading from a classfile
// def foo2 = RawTypes.mis_Raw_Gen(cd_is) // lazy because this fails at runtime even when reading from a classfile

RawTypes.mii_Gen_Gen(cd_ii)
RawTypes.msi_Gen_Gen(cd_si)
Expand Down
16 changes: 16 additions & 0 deletions test/tasty/run-pipelined/src-2/tastytest/TestRawTypes2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//> using jvm 16+
package tastytest

import lib.RawTypes2

/** Test definitions that only compile in Java 16+ */
object TestRawTypes2 extends scala.App {
val rt: RawTypes2 = new RawTypes2()

lazy val cd_is = new rt.C.DStatic[String]() // lazy because this fails at runtime even when reading from a classfile

def foo1 = RawTypes2.mis_Raw_Raw(cd_is) // lazy because this fails at runtime even when reading from a classfile

def foo2 = RawTypes2.mis_Raw_Gen(cd_is) // lazy because this fails at runtime even when reading from a classfile

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package tastytest

import lib.SomeAnnotation
import lib.{SomeAnnotation, SomeAnnotated}

object TestSomeAnnotation extends scala.App {

Expand All @@ -13,4 +13,8 @@ object TestSomeAnnotation extends scala.App {
@SomeAnnotation(value = "hello", year = 1996, classes = Array(classOf[Long]))
def method3 = 23

assert(SomeAnnotated.method == 23)
assert(SomeAnnotated.method2 == 23)
assert(SomeAnnotated.method3 == 23)

}
2 changes: 1 addition & 1 deletion test/tasty/run-pipelined/src-3/lib/InnerClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public <U> InnerClass.Inner<U> createInner(U innerField) {
}

public static <U> InnerClass.Inner<U> createInnerStatic(U innerField) {
var innerClass = new InnerClass();
InnerClass innerClass = new InnerClass();
return innerClass.new Inner<>(innerField);
}

Expand Down
2 changes: 1 addition & 1 deletion test/tasty/run-pipelined/src-3/lib/InnerClassGen.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public <U> InnerClassGen<T>.Inner<U> createInner(T outerField, U innerField) {
}

public static <T, U> InnerClassGen<T>.Inner<U> createInnerStatic(T outerField, U innerField) {
var innerClass = new InnerClassGen<T>();
InnerClassGen<T> innerClass = new InnerClassGen<T>();
return innerClass.new Inner<>(outerField, innerField);
}

Expand Down
6 changes: 3 additions & 3 deletions test/tasty/run-pipelined/src-3/lib/RawTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class C<T> {
public class D<U> {
public class E<V> {}
}
public static class DStatic<U> {}
// public static class DStatic<U> {} // illegal in Java < 16
}

public static class CStatic<T> {
Expand All @@ -17,12 +17,12 @@ public static class DStatic<U> {}
public static void miii_Raw_Raw_Raw(C.D.E e) {}

public static void mii_Raw_Raw(C.D d) {}
public static void mis_Raw_Raw(C.DStatic d) {}
// public static void mis_Raw_Raw(C.DStatic d) {} // illegal in Java < 16
public static void msi_Raw_Raw(CStatic.D d) {}
public static void mss_Raw_Raw(CStatic.DStatic d) {}

// public static void mii_Raw_Gen(C.D<String> d) {} // illegal
public static void mis_Raw_Gen(C.DStatic<String> d) {}
// public static void mis_Raw_Gen(C.DStatic<String> d) {} // illegal in Java < 16
// public static void msi_Raw_Gen(CStatic.D<String> d) {} // illegal
public static void mss_Raw_Gen(CStatic.DStatic<String> d) {}

Expand Down
14 changes: 14 additions & 0 deletions test/tasty/run-pipelined/src-3/lib/RawTypes2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//> using jvm 16+
package lib;

/** Contains C.DStatic, only allowed since JDK 16 */
public class RawTypes2 {

public class C<T> {
public static class DStatic<U> {} // illegal in Java < 16
}

public static void mis_Raw_Raw(C.DStatic d) {} // illegal in Java < 16
public static void mis_Raw_Gen(C.DStatic<String> d) {} // illegal in Java < 16

}
18 changes: 18 additions & 0 deletions test/tasty/run-pipelined/src-3/lib/SomeAnnotated.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package lib;

public class SomeAnnotated {

private static <T> T unimplemented() {
return null;
}

@SomeAnnotation("hello")
public static int method() { return 23; }

@SomeAnnotation(value = "hello", year = 1996)
public static int method2() { return 23; }

@SomeAnnotation(value = "hello", year = 1996, classes = {long.class})
public static int method3() { return 23; }

}

0 comments on commit fa4c3df

Please sign in to comment.