Repository: scalameta/scalameta
Pull request #3286 (2023-07-28 13:27:23)

ExpectSuite: use scalaBinaryVersion to get suffix


Patch:
diff --git a/build.sbt b/build.sbt
index ca7d76c42..2dd371c88 100644
--- a/build.sbt
+++ b/build.sbt
@@ -425,6 +425,7 @@ lazy val testSettings: List[Def.SettingsDefinition] = List(
   },
   buildInfoKeys := Seq[BuildInfoKey](
     scalaVersion,
+    scalaBinaryVersion,
     "latestScala212Version" -> LatestScala212,
     "latestScala213Version" -> LatestScala213,
     "databaseSourcepath" ->
diff --git a/scalameta/io/js/src/main/scala/java/nio/file/Path.scala b/scalameta/io/js/src/main/scala/java/nio/file/Path.scala
index aa0525893..37407dfe6 100644
--- a/scalameta/io/js/src/main/scala/java/nio/file/Path.scala
+++ b/scalameta/io/js/src/main/scala/java/nio/file/Path.scala
@@ -25,4 +25,5 @@ trait Path extends Iterable[Path] {
   def toUri: URI
   def toAbsolutePath: Path
   def toFile: java.io.File
+  def toRealPath(): Path
 }
diff --git a/scalameta/io/js/src/main/scala/scala/meta/internal/io/NodeNIOPath.scala b/scalameta/io/js/src/main/scala/scala/meta/internal/io/NodeNIOPath.scala
index bfc8d9cc5..f83aa8a90 100644
--- a/scalameta/io/js/src/main/scala/scala/meta/internal/io/NodeNIOPath.scala
+++ b/scalameta/io/js/src/main/scala/scala/meta/internal/io/NodeNIOPath.scala
@@ -37,6 +37,8 @@ case class NodeNIOPath(filename: String) extends Path {
   override def toAbsolutePath: Path =
     if (isAbsolute) this
     else NodeNIOPath.workingDirectory.resolve(this)
+  override def toRealPath(): Path =
+    toAbsolutePath
   override def relativize(other: Path): Path =
     NodeNIOPath(JSPath.relative(filename, other.toString))
   override def getNameCount: Int = {
diff --git a/scalameta/io/shared/src/main/scala/scala/meta/io/RelativePath.scala b/scalameta/io/shared/src/main/scala/scala/meta/io/RelativePath.scala
index c95492acd..2a26c43f8 100644
--- a/scalameta/io/shared/src/main/scala/scala/meta/io/RelativePath.scala
+++ b/scalameta/io/shared/src/main/scala/scala/meta/io/RelativePath.scala
@@ -12,16 +12,7 @@ import scala.collection.JavaConverters._
 sealed abstract case class RelativePath(toNIO: Path) {
   require(!toNIO.isAbsolute, s"$toNIO is not relative!")
   def toFile: File = toNIO.toFile
-  def toURI(isDirectory: Boolean): URI = {
-    val suffix = if (isDirectory) "/" else ""
-    // Can't use toNIO.toUri because it produces an absolute URI.
-    val names = toNIO.iterator().asScala
-    val uris = names.map { name =>
-      // URI encode each part of the path individually.
-      new URI(null, null, name.toString, null)
-    }
-    URI.create(uris.mkString("", "/", suffix))
-  }
+  def toURI(isDirectory: Boolean): URI = RelativePath.toURI(toNIO, isDirectory)
 
   def syntax: String = toString
   def structure: String = s"""RelativePath("$syntax")"""
@@ -46,4 +37,16 @@ object RelativePath {
   // throws Illegal argument exception if path is not relative.
   def apply(path: nio.Path): RelativePath =
     new RelativePath(path) {}
+
+  private[meta] def toURI(path: Path, isDirectory: Boolean): URI = {
+    val suffix = if (isDirectory) "/" else ""
+    // Can't use toNIO.toUri because it produces an absolute URI.
+    val names = path.iterator().asScala
+    val uris = names.map { name =>
+      // URI encode each part of the path individually.
+      new URI(null, null, name.toString, null)
+    }
+    URI.create(uris.mkString("", "/", suffix))
+  }
+
 }
diff --git a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/ConfigOps.scala b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/ConfigOps.scala
index fb38056b2..a5c6ab6db 100644
--- a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/ConfigOps.scala
+++ b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/ConfigOps.scala
@@ -19,6 +19,8 @@ case class SemanticdbConfig(
     synthetics: BinaryMode,
     overrides: BinaryMode
 ) {
+  private[scalac] lazy val realSourceRoot = sourceroot.toNIO.toRealPath()
+
   def syntax: String = {
     val p = SemanticdbPlugin.name
     List(
diff --git a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/InputOps.scala b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/InputOps.scala
index d1431766e..1a7460f22 100644
--- a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/InputOps.scala
+++ b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/InputOps.scala
@@ -3,34 +3,26 @@ package scala.meta.internal.semanticdb.scalac
 import java.net.{URI, URLEncoder}
 import java.nio.CharBuffer
 import java.nio.charset.StandardCharsets.UTF_8
-import java.nio.file.Paths
+import java.nio.file.Path
 import java.security.MessageDigest
 import scala.collection.mutable
 import scala.{meta => m}
 import scala.meta.internal.io._
-import scala.meta.io.AbsolutePath
+import scala.meta.io.{AbsolutePath, RelativePath}
 import scala.reflect.internal.util.{Position => GPosition, SourceFile => GSourceFile}
 import scala.reflect.io.VirtualFile
 import scala.reflect.io.{PlainFile => GPlainFile}
 
 trait InputOps { self: SemanticdbOps =>
 
+  import InputOps._
+
   lazy val gSourceFileInputCache = mutable.Map[GSourceFile, m.Input]()
   implicit class XtensionGSourceFileInput(gsource: GSourceFile) {
-    private def uriRelativeToSourceRoot(file: AbsolutePath): URI = {
-      if (!file.toNIO.startsWith(config.sourceroot.toNIO)) {
-        // java.net.URI.relativize returns `fileUri` unchanged when it is not contained within our sourceroot.
-        // We could attempt to return a ".." URI, but java.net doesn't provide facilities for that. While nio's Path
-        // does contain facilities for that, such relative paths cannot then be used to produce a percent-encoded,
-        // relative URI. It doesn't seem worth fighting this battle at the moment, so:
-        sys.error(s"'$file' is not located within sourceroot '${config.sourceroot}'.")
-      }
-      file.toRelative(config.sourceroot).toURI(isDirectory = false)
-    }
-
     def isInSourceroot(): Boolean = gsource.file match {
       case gfile: GPlainFile =>
-        gfile.file.toPath.startsWith(config.sourceroot.toNIO) || !gfile.file.toPath.isAbsolute
+        val gpath = gfile.file.toPath
+        !gpath.isAbsolute || config.fileInSourceRoot(gpath).isDefined
       case _: VirtualFile =>
         true // Would anyone go to the trouble of building a VirtualFile that's outside of sourceroot?
       case _ =>
@@ -39,7 +31,7 @@ trait InputOps { self: SemanticdbOps =>
 
     def toUri: String = toInput match {
       case input: m.Input.File =>
-        uriRelativeToSourceRoot(input.path).toString
+        config.uriRelativeToSourceRoot(input.path).toString
       case input: m.Input.VirtualFile =>
         input.path
       case _ =>
@@ -69,7 +61,7 @@ trait InputOps { self: SemanticdbOps =>
             case gfile: GPlainFile =>
               if (config.text.isOn) {
                 val path = m.AbsolutePath(gfile.file)
-                val label = uriRelativeToSourceRoot(path).toString
+                val label = config.uriRelativeToSourceRoot(path).toString
                 // NOTE: Can't use gsource.content because it's preprocessed by scalac.
                 val contents = FileIO.slurp(path, UTF_8)
                 m.Input.VirtualFile(label, contents)
@@ -98,3 +90,38 @@ trait InputOps { self: SemanticdbOps =>
     }
   }
 }
+
+private object InputOps {
+
+  private def fileInRoot(file: Path, root: Path): Option[() => Path] =
+    if (file.startsWith(root)) Some(() => root.relativize(file)) else None
+
+  implicit class ImplicitSemanticdbConfig(private val config: SemanticdbConfig) extends AnyVal {
+
+    def fileInSourceRoot(file: Path): Option[() => Path] = {
+      val root = config.sourceroot.toNIO
+      // file is under source root; both could be symlinks to unrelated paths
+      fileInRoot(file, root).orElse {
+        val realRoot = config.realSourceRoot
+        // file is real but root was a symlink
+        (if (realRoot eq root) None else fileInRoot(file, realRoot)).orElse {
+          val realFile = file.toRealPath()
+          // file has a symlink parent but different from root
+          if (realFile eq file) None else fileInRoot(realFile, realRoot)
+        }
+      }
+    }
+
+    def uriRelativeToSourceRoot(file: AbsolutePath): URI = fileInSourceRoot(file.toNIO)
+      .map { f => RelativePath.toURI(f(), isDirectory = false) }
+      .getOrElse {
+        // java.net.URI.relativize returns `fileUri` unchanged when it is not contained within our sourceroot.
+        // We could attempt to return a ".." URI, but java.net doesn't provide facilities for that. While nio's Path
+        // does contain facilities for that, such relative paths cannot then be used to produce a percent-encoded,
+        // relative URI. It doesn't seem worth fighting this battle at the moment, so:
+        sys.error(s"'$file' is not located within sourceroot '${config.sourceroot}'.")
+      }
+
+  }
+
+}


Test patch:
diff --git a/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/ConfigSuite.scala b/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/ConfigSuite.scala
new file mode 100644
index 000000000..d062b3f95
--- /dev/null
+++ b/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/ConfigSuite.scala
@@ -0,0 +1,166 @@
+package scala.meta.tests.semanticdb
+
+import java.nio.file.Files
+import munit.FunSuite
+import scala.meta.tests.Slow
+import scala.meta.cli.Metac
+import scala.meta.internal.semanticdb.scalac.SemanticdbPaths
+import scala.meta.internal.{semanticdb => s}
+import scala.meta.io.AbsolutePath
+import scala.meta.io.RelativePath
+import scala.meta.metac
+import scala.meta.testkit.StringFS
+import scala.meta.tests.cli.CliTestUtils
+import scala.meta.tests.metacp.Library
+
+class ConfigSuite extends FunSuite {
+
+  val A = RelativePath("A.scala")
+
+  def generateTargetroot(): AbsolutePath = {
+    val target = AbsolutePath(Files.createTempDirectory("semanticdb"))
+    target.toFile.deleteOnExit()
+    target
+  }
+
+  def check(
+      name: String,
+      scalacArgs: List[String],
+      input: String,
+      fn: s.TextDocument => Unit,
+      targetroot: AbsolutePath = generateTargetroot()
+  ): Unit = {
+    // each test case takes ~1-2 seconds to run so they're modestly fast but still
+    // ~100x slower than regular unit test.
+    test(name.tag(Slow)) {
+      val sourceroot = StringFS.fromString(input)
+      val (metacIsSuccess, metacOut, metacErr) = CliTestUtils.withReporter { reporter =>
+        val settings = metac
+          .Settings()
+          .withScalacArgs(
+            scalacArgs ++ List(
+              "-d",
+              targetroot.toString,
+              "-P:semanticdb:sourceroot:" + sourceroot.toString,
+              "-cp",
+              Library.scalaLibrary.classpath().syntax,
+              sourceroot.resolve(A).toString
+            )
+          )
+        Metac.process(settings, reporter)
+      }
+      assert(metacIsSuccess, metacErr)
+      assert(metacOut.isEmpty)
+      assert(metacErr.isEmpty)
+      val semanticdbPath = SemanticdbPaths.toSemanticdb(A, targetroot)
+      val docs = s.TextDocuments.parseFrom(semanticdbPath.readAllBytes)
+      assert(docs.documents.length == 1, docs.toProtoString)
+      fn(docs.documents.head)
+    }
+  }
+
+  check(
+    "symbols:local-only",
+    List("-P:semanticdb:symbols:local-only"),
+    """
+      |/A.scala
+      |object A {
+      |  val x = {
+      |    val y = 1 // local0
+      |    val z = 2 // local1
+      |    y + z
+      |  }
+      |}
+    """.stripMargin,
+    { doc =>
+      val symbols = doc.symbols.map(_.symbol).sorted.mkString("\n")
+      assertNoDiff(
+        symbols,
+        """|local0
+           |local1
+           |""".stripMargin
+      )
+    }
+  )
+
+  check(
+    "symbols:none",
+    List("-P:semanticdb:symbols:none"),
+    """
+      |/A.scala
+      |object A {
+      |}
+    """.stripMargin,
+    { doc => assert(doc.symbols.isEmpty) }
+  )
+
+  check(
+    "symbols:all",
+    List("-P:semanticdb:symbols:all"),
+    """
+      |/A.scala
+      |object A {
+      |  val x = {
+      |    val y = 2
+      |    2
+      |  }
+      |}
+    """.stripMargin,
+    { doc =>
+      val obtained = doc.symbols.map(i => i.symbol + " " + i.displayName).sorted.mkString("\n")
+      assertNoDiff(
+        obtained,
+        """|_empty_/A. A
+           |_empty_/A.x. x
+           |local0 y
+           |""".stripMargin
+      )
+    }
+  )
+
+  private val customTargetroot = generateTargetroot()
+  check(
+    "targetroot:<custom>",
+    List(s"-P:semanticdb:targetroot:$customTargetroot"),
+    """|/A.scala
+       |object A
+       |""".stripMargin,
+    { doc =>
+      val Seq(occurrence) = doc.occurrences
+      assert(occurrence.symbol == "_empty_/A.")
+    },
+    targetroot = customTargetroot
+  )
+
+  check(
+    "md5:off",
+    List(s"-P:semanticdb:md5:off"),
+    """|/A.scala
+       |object A
+    """.stripMargin,
+    { doc => assert(doc.md5.isEmpty) }
+  )
+
+  check(
+    "synthetics:off",
+    List(s"-P:semanticdb:synthetics:off"),
+    """|/A.scala
+       |object A {
+       |   List(1).map(_ + 1)
+       |}
+       |""".stripMargin,
+    { doc => assert(doc.synthetics.isEmpty) }
+  )
+
+  check(
+    "synthetics:on",
+    List(s"-P:semanticdb:synthetics:on"),
+    """|/A.scala
+       |object A {
+       |   List(1).map(_ + 1)
+       |}
+       |""".stripMargin,
+    { doc => assert(doc.synthetics.nonEmpty) }
+  )
+
+}
diff --git a/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/JavacpSuite.scala b/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/JavacpSuite.scala
new file mode 100644
index 000000000..c2e1971fb
--- /dev/null
+++ b/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/JavacpSuite.scala
@@ -0,0 +1,49 @@
+package scala.meta.tests.semanticdb
+
+import munit.FunSuite
+import scala.meta.internal.semanticdb.ClassSignature
+import scala.meta.internal.semanticdb.Scala._
+
+class JavacpSuite extends FunSuite {
+  private val infos = MetacMetacpDiffExpect.metacpSymbols
+
+  def checkOrder(
+      name: String,
+      symbol: String,
+      filter: String => Boolean,
+      expected: List[String]
+  ): Unit =
+    test(name) {
+      val info = infos(symbol)
+      val ClassSignature(_, _, _, Some(declarations)) = info.signature
+      val obtained = declarations.symlinks.filter(filter)
+      assert(obtained == expected, info.toProtoString)
+    }
+
+  checkOrder(
+    "methods",
+    "com/javacp/MetacJava#",
+    _.desc.value == "overload",
+    List(
+      "com/javacp/MetacJava#overload().",
+      "com/javacp/MetacJava#overload(+2).",
+      "com/javacp/MetacJava#overload(+1)."
+    )
+  )
+
+  checkOrder(
+    "fields",
+    "com/javacp/Test#",
+    { s =>
+      s.desc.value == "Int" ||
+      s.desc.value == "Long" ||
+      s.desc.value == "Float"
+    },
+    List(
+      "com/javacp/Test#Int.",
+      "com/javacp/Test#Long.",
+      "com/javacp/Test#Float."
+    )
+  )
+
+}
diff --git a/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/PrintSuite.scala b/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/PrintSuite.scala
new file mode 100644
index 000000000..0a5009330
--- /dev/null
+++ b/tests/jvm/src/test/scala-2.13/scala/meta/tests/semanticdb/PrintSuite.scala
@@ -0,0 +1,156 @@
+package scala.meta.tests.semanticdb
+
+import munit.FunSuite
+import scala.meta.interactive.InteractiveSemanticdb
+import scala.meta.internal.metap.PrinterSymtab
+import scala.meta.internal.{semanticdb => s}
+import scala.meta.internal.semanticdb.Print
+import scala.meta.internal.semanticdb.SymbolInformation
+import scala.meta.internal.symtab.GlobalSymbolTable
+import scala.meta.metap.Format
+import scala.meta.tests.metacp.Library
+
+class PrintSuite extends FunSuite {
+  val symtab = GlobalSymbolTable(
+    Library.scalaLibrary.classpath(),
+    includeJdk = true
+  )
+
+  val compiler = InteractiveSemanticdb.newCompiler()
+  val printerSymtab: PrinterSymtab = new PrinterSymtab {
+    override def info(symbol: String): Option[SymbolInformation] = symtab.info(symbol)
+  }
+
+  def checkDocument(
+      name: String,
+      original: String,
+      expected: String,
+      fn: s.TextDocument => Unit
+  ): Unit = {
+    test(name) {
+      val wrapped =
+        s"""
+object Wrapped {
+$original
+}
+""".stripMargin
+      val doc = InteractiveSemanticdb.toTextDocument(
+        compiler = compiler,
+        code = wrapped,
+        options = List(
+          "-P:semanticdb:synthetics:on",
+          "-P:semanticdb:text:on"
+        )
+      )
+      fn(doc)
+    }
+  }
+
+  def checkType(symbol: String, expected: String): Unit = {
+    test("type - " + symbol) {
+      val info = symtab.info(symbol).get
+      val tpe = info.signature match {
+        case s.ValueSignature(tpe) => tpe
+        case s.MethodSignature(_, _, tpe) => tpe
+        case e => throw new MatchError(e)
+      }
+      val obtained = Print.tpe(Format.Compact, tpe, printerSymtab)
+      assertNoDiff(obtained, expected)
+    }
+  }
+
+  def checkConstant(constant: s.Constant, expected: String): Unit = {
+    test(constant.toString) {
+      val obtained = Print.constant(constant)
+      assertNoDiff(obtained, expected)
+    }
+  }
+
+  def checkSignature(symbol: String, expected: String): Unit = {
+    test("signature - " + symbol) {
+      val info = symtab.info(symbol).get
+      val obtained = Print.signature(Format.Compact, info.signature, printerSymtab)
+      assertNoDiff(obtained, expected)
+    }
+  }
+
+  def checkInfo(symbol: String, expected: String): Unit = {
+    test("info - " + symbol) {
+      val info = symtab.info(symbol).get
+      val obtained = Print.info(Format.Compact, info, printerSymtab)
+      assertNoDiff(obtained, expected)
+    }
+  }
+
+  def checkSynthetics(original: String, expected: String): Unit = {
+    checkDocument(
+      "synthetic - " + original,
+      original,
+      expected,
+      { doc =>
+        val obtained = doc.synthetics.map { synthetic =>
+          Print.synthetic(Format.Compact, doc, synthetic, printerSymtab)
+        }
+        assertNoDiff(obtained.mkString("\n"), expected)
+      }
+    )
+  }
+
+  def checkTrees(original: String, expected: String): Unit = {
+    checkDocument(
+      "trees - " + original,
+      original,
+      expected,
+      { doc =>
+        val obtained = doc.synthetics.map { synthetic =>
+          Print.tree(Format.Compact, doc, synthetic.tree, printerSymtab)
+        }
+        assertNoDiff(obtained.mkString("\n"), expected)
+      }
+    )
+  }
+
+  checkType("java/io/ByteArrayOutputStream#buf.", "Array[Byte]")
+  checkType("scala/Predef.ArrowAssoc#`->`().", "Tuple2[A, B]")
+
+  checkConstant(s.UnitConstant(), "()")
+  checkConstant(s.LongConstant(64), "64L")
+  checkConstant(s.CharConstant('a'.toInt), "'a'")
+  checkConstant(s.StringConstant("a"), "\"a\"")
+
+  checkSignature(
+    "scala/Predef.assert(+1).",
+    """(assertion: Boolean, message: => Any): Unit"""
+  )
+  checkSignature(
+    "scala/Predef.ArrowAssoc#`->`().",
+    "[B](y: B): Tuple2[A, B]"
+  )
+
+  checkInfo(
+    "scala/Predef.assert(+1).",
+    """scala/Predef.assert(+1). => @inline @elidable final method assert(assertion: Boolean, message: => Any): Unit"""
+  )
+  checkInfo(
+    "scala/Any#",
+    """scala/Any# => abstract class Any { +10 decls }"""
+  )
+  checkInfo(
+    "java/util/Collections#singletonList().",
+    """java/util/Collections#singletonList(). => static method singletonList[T](param0: T): List[T]"""
+  )
+
+  checkSynthetics(
+    "List(1).map(_ + 2)",
+    """|[2:0..2:11): List(1).map => *[Int]
+       |[2:0..2:4): List => *.apply[Int]
+       |""".stripMargin
+  )
+
+  checkTrees(
+    "List(1).map(_ + 2)",
+    """|orig(List(1).map)[Int]
+       |orig(List).apply[Int]
+       |""".stripMargin
+  )
+}
diff --git a/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/ExpectSuite.scala b/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/ExpectSuite.scala
index 7dc1dc74b..da30dbd28 100644
--- a/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/ExpectSuite.scala
+++ b/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/ExpectSuite.scala
@@ -82,7 +82,7 @@ trait ExpectHelpers extends munit.Assertions {
   def path: Path = {
     def filenameToPath(filename: String) =
       Paths.get("tests", "jvm", "src", "test", "resources", filename)
-    val filenameWithVersion = if (ScalaVersion.is212) filename + "_2.12" else filename + "_2.13"
+    val filenameWithVersion = filename + "_" + BuildInfo.scalaBinaryVersion
     val baseFile = filenameToPath(filename)
     if (baseFile.toFile().exists()) baseFile
     else filenameToPath(filenameWithVersion)
diff --git a/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/OccurrenceSuite.scala b/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/OccurrenceSuite.scala
index d39808347..18f4d79b1 100644
--- a/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/OccurrenceSuite.scala
+++ b/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/OccurrenceSuite.scala
@@ -50,6 +50,8 @@ object OccurrenceSuite {
       body: () => TestBody
   )
   def testCases(): collection.Seq[TestCase] = {
+    val sourceroot = AbsolutePath(BuildInfo.databaseSourcepath)
+    val targetroot = AbsolutePath(BuildInfo.databaseClasspath)
     for {
       dir <- BuildInfo.integrationSourceDirectories
       absdir = AbsolutePath(dir)
@@ -57,14 +59,11 @@ object OccurrenceSuite {
       source <- FileIO.listAllFilesRecursively(absdir)
       if PathIO.extension(source.toNIO) == "scala"
       if !isExcluded(source)
+      relpath = source.toRelative(absdir)
     } yield {
-      val relpath = source.toRelative(absdir)
-      val testname = relpath.toURI(false).toString
       TestCase(
-        testname,
+        relpath.toURI(false).toString,
         () => {
-          val sourceroot = AbsolutePath(BuildInfo.databaseSourcepath)
-          val targetroot = AbsolutePath(BuildInfo.databaseClasspath)
           val semanticdb = SemanticdbPaths.toSemanticdb(source.toRelative(sourceroot), targetroot)
           val textdocument = TextDocuments.parseFrom(semanticdb.readAllBytes).documents.head
           val expectpath = expectroot.resolve(relpath)