mirror of https://github.com/sbt/sbt.git
[2.x] fix: Fixes stale resources in test (#9469)
**Problem** sbt 2.x currently constructs the test classloader using symlinks to the JARs in CAS (content-addressable storage). This causes issues because JVM apparently caches opened JAR files by path, and since the symlink itself doesn't change it could end up serving stale resource files. **Solution** We can workaround this issue by resolving the symlinks to the real path.
This commit is contained in:
parent
b76ac09d44
commit
204468b84c
|
|
@ -9,7 +9,7 @@
|
|||
package sbt
|
||||
package internal
|
||||
|
||||
import java.io.File
|
||||
import java.io.{ IOException, File }
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import sbt.ClassLoaderLayeringStrategy.*
|
||||
|
|
@ -29,8 +29,22 @@ import xsbti.ArtifactInfo
|
|||
import xsbti.HashedVirtualFileRef
|
||||
|
||||
private[sbt] object ClassLoaders {
|
||||
|
||||
/**
|
||||
* https://github.com/sbt/sbt/issues/9468
|
||||
* Resolves symlinks before converting to a URL. Classpath entries backed by a
|
||||
* CAS cache would otherwise all share one URL across builds;
|
||||
* the JVM's jar/URL connection caching is keyed by path, so once a jar: URL for that
|
||||
* stable path has been opened, later reads can keep returning the first content seen\
|
||||
* even after the symlink target (and the underlying file content) changes.
|
||||
*/
|
||||
private[sbt] def toRealURL(f: File): URL =
|
||||
val real = try f.toPath.toRealPath().toFile
|
||||
catch case _: IOException => f
|
||||
real.toURI.toURL
|
||||
|
||||
extension (files: Seq[File]) {
|
||||
def urls: Array[URL] = files.toArray.map(_.toURI.toURL)
|
||||
def urls: Array[URL] = files.toArray.map(toRealURL)
|
||||
}
|
||||
private val interfaceLoader = classOf[sbt.testing.Framework].getClassLoader
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -45,10 +45,10 @@ private[internal] final class ReverseLookupClassLoaderHolder(
|
|||
val closeThis: Boolean,
|
||||
val allowZombies: Boolean,
|
||||
val logger: Logger
|
||||
) extends URLClassLoader(Array.empty, null) {
|
||||
) extends URLClassLoader(Array.empty, null):
|
||||
private val cached: AtomicReference[ReverseLookupClassLoader] = new AtomicReference
|
||||
private val closed = new AtomicBoolean(false)
|
||||
private val urls = classpath.map(_.toURI.toURL).toArray
|
||||
private val urls = classpath.map(ClassLoaders.toRealURL).toArray
|
||||
|
||||
/**
|
||||
* Get a classloader. If there is a loader available in the cache, it will use that loader,
|
||||
|
|
@ -69,7 +69,7 @@ private[internal] final class ReverseLookupClassLoaderHolder(
|
|||
reverseLookupClassLoader.setup(tempDir)
|
||||
new BottomClassLoader(
|
||||
ReverseLookupClassLoaderHolder.this,
|
||||
fullClasspath.map(_.toURI.toURL).toArray,
|
||||
fullClasspath.map(ClassLoaders.toRealURL).toArray,
|
||||
reverseLookupClassLoader,
|
||||
tempDir,
|
||||
closeThis,
|
||||
|
|
@ -97,7 +97,7 @@ private[internal] final class ReverseLookupClassLoaderHolder(
|
|||
case c => c.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
end ReverseLookupClassLoaderHolder
|
||||
|
||||
/**
|
||||
* This is more or less copied from the NativeCopyLoader in zinc. It differs from the zinc
|
||||
|
|
|
|||
|
|
@ -6,3 +6,5 @@ libraryDependencies ++= Seq(
|
|||
"commons-io" % "commons-io" % "2.5" % Runtime,
|
||||
)
|
||||
libraryDependencies += scalaVersion("org.scala-lang" % "scala-compiler" % _ ).value
|
||||
|
||||
Test / fork := true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
val munit = "org.scalameta" %% "munit" % "1.0.4"
|
||||
scalaVersion := "3.8.4"
|
||||
|
||||
lazy val root = rootProject
|
||||
.settings(
|
||||
libraryDependencies += munit % Test
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
bad
|
||||
|
|
@ -0,0 +1 @@
|
|||
hello
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package testpkg
|
||||
|
||||
import munit.*
|
||||
import scala.io.Source
|
||||
import scala.util.Using
|
||||
|
||||
class ATest extends FunSuite:
|
||||
test("hello.txt should contain hello") {
|
||||
Using.resource(Source.fromResource("hello.txt")): r =>
|
||||
val actual = r.mkString.trim
|
||||
assert(actual == "hello", s"actual: $actual")
|
||||
}
|
||||
end ATest
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
> test
|
||||
|
||||
$ copy-file changes/bad.txt src/test/resources/hello.txt
|
||||
|
||||
-> test
|
||||
Loading…
Reference in New Issue