Skip to content

Commit 0b5b666

Browse files
committed
Upgrade mockito-core to 5.18.0, add support for JDK 17 and 21, drop Scala 2.11 and JDK 8
1 parent e5a465d commit 0b5b666

File tree

20 files changed

+119
-345
lines changed

20 files changed

+119
-345
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
# Definition of the build matrix
2727
strategy:
2828
matrix:
29-
java: [8, 11, 14]
29+
java: [11, 17, 21]
3030

3131
# All build steps
3232
# SINGLE-MATRIX-JOB means that the step does not need to be executed on every job in the matrix
@@ -65,11 +65,11 @@ jobs:
6565
with:
6666
fetch-depth: '0' # https://github.com/shipkit/shipkit-changelog#fetch-depth-on-ci
6767

68-
- name: Set up Java 8
68+
- name: Set up Java 17
6969
uses: actions/setup-java@v2
7070
with:
7171
distribution: 'zulu'
72-
java-version: 8
72+
java-version: 17
7373

7474
- name: Build and publish to Bintray/MavenCentral
7575
run: ./gradlew writeActualVersion

.sbtopts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
-J-Xmx8G
22
-J-XX:MaxMetaspaceSize=1G
3-
-J-XX:+CMSClassUnloadingEnabled

README.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ The library has independent developers, release cycle and versioning from core m
3838
- [Scalaz](/scalaz/src/test)
3939

4040
## Partial unification
41-
If you're in Scala 2.11 or 2.12 you'll probably want to add the compiler flag `-Ypartial-unification`, if you don't you risk some compile errors when trying to stub complex types using the idiomatic syntax
41+
If you're in Scala 2.12 you'll probably want to add the compiler flag `-Ypartial-unification`, if you don't you risk some compile errors when trying to stub complex types using the idiomatic syntax
42+
43+
## Notes for 2.0.0
44+
We dropped support for Scala 2.11 and Java 8, as Mockito 5 dropped support for Java 8.
4245

4346
## Notes for 1.13.6
4447

@@ -500,14 +503,14 @@ Of course you can override the default behaviour, for this you have 2 options
500503
DefaultAnswers are also composable, so for example if you wanted empty values first and then smart nulls you could do `implicit val defaultAnswer: DefaultAnswer = ReturnsEmptyValues orElse ReturnsSmartNulls`
501504

502505
## Function Answers
503-
`org.mockito.Answer[T]` can be a bit boilerplate-ish, mostly if you're still in Scala 2.11 (in 2.12 with SAM is much nicer),
506+
`org.mockito.Answer[T]` can be a bit boilerplate-ish,
504507
to simplify the usage for both versions is that we replaced it by standard scala functions, so instead of
505508
```scala
506509
when(myMock.foo("bar", 42)) thenAnswer new Answer[String] {
507510
override def answer(invocation: InvocationOnMock): String = i.getArgument[String](0) + i.getArgument[Int](1)
508511
}
509512
```
510-
We can now write: (this may be nothing new for users of 2.12, but at least now the API is consistent for both 2.11 and 2.12)
513+
We can now write:
511514
```scala
512515
when(myMock.foo("bar", 42)) thenAnswer ((i: InvocationOnMock) => i.getArgument[String](0) + i.getArgument[Int](1))
513516
```
@@ -797,13 +800,6 @@ your build.sbt and that warning will be ignored for your tests **only**
797800
matchers usage then you have to explicitly provide the type for the matcher, thus `any` would become `any[MyType]` and
798801
`*` would become `*[MyType]` (you can also use `anyShort`, `anyInt`, etc for the primitive types)
799802
800-
### Scala 2.11
801-
Please note that in Scala 2.11 the following features are not supported
802-
803-
* Default arguments on methods defined in traits (they will behave as before, getting `null` or a default value if they
804-
are of a primitive type)
805-
* Any kind of `ArgumentMatcher[T]` for methods with by-name parameters (they'll throw an exception if used with `ArgumentMatcher[T]`)
806-
807803
## Authors
808804
809805
* **Bruno Bonanno** - *Initial work* - [bbonanno](https://github.com/bbonanno)

build.sbt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ lazy val commonSettings =
2626
Seq(
2727
organization := "org.mockito",
2828
// Load version from the file so that Gradle/Shipkit and SBT use the same version
29-
crossScalaVersions := Seq(currentScalaVersion, "2.12.20", "2.11.12"),
29+
crossScalaVersions := Seq(currentScalaVersion, "2.12.20"),
3030
scalafmtOnCompile := true,
3131
scalacOptions ++= Seq(
3232
"-unchecked",
@@ -122,8 +122,8 @@ lazy val cats = (project in file("cats"))
122122
commonSettings,
123123
publishSettings,
124124
libraryDependencies ++= Seq(
125-
Dependencies.cats.value,
126-
Dependencies.catsLaws.value % "test",
125+
Dependencies.cats,
126+
Dependencies.catsLaws % "test",
127127
Dependencies.disciplineScalatest % "test",
128128
Dependencies.scalatest % "test"
129129
)
@@ -150,8 +150,8 @@ lazy val common = (project in file("common"))
150150
noPublishingSettings,
151151
libraryDependencies ++= Dependencies.commonLibraries ++
152152
Dependencies.scalaReflection.value ++ Seq(
153-
Dependencies.catsLaws.value % "test",
154-
Dependencies.scalacheck.value % "test"
153+
Dependencies.catsLaws % "test",
154+
Dependencies.scalacheck % "test"
155155
)
156156
)
157157

build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
#!/usr/bin/env bash
2-
java -Xms512M -Xmx2G -Xss2M -XX:ReservedCodeCacheSize=192m -XX:+CMSClassUnloadingEnabled -Dfile.encoding=UTF-8 -jar sbt/sbt-launch.jar -Dsbt.parser.simple=true clean +test 'set every publishTo := Some(Resolver.file("local-repo", file("target/dist")))' '+ publish'
2+
java -Xms512M -Xmx2G -Xss2M -XX:ReservedCodeCacheSize=192m -Dfile.encoding=UTF-8 -jar sbt/sbt-launch.jar -Dsbt.parser.simple=true clean +test 'set every publishTo := Some(Resolver.file("local-repo", file("target/dist")))' '+ publish'

common/src/main/scala-2.11/org/mockito/internal/handler/package.scala

Lines changed: 0 additions & 11 deletions
This file was deleted.

common/src/main/scala-2.11/org/mockito/stubbing/ReturnsEmptyValues.scala

Lines changed: 0 additions & 41 deletions
This file was deleted.

common/src/main/scala/org/mockito/ReflectionUtils.scala

Lines changed: 84 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ object ReflectionUtils {
1616
import scala.reflect.runtime.{ universe => ru }
1717
import ru._
1818

19+
private val JavaVersion: Int =
20+
System.getProperty("java.version").split("\\.") match {
21+
case Array("1", v, _*) => v.toInt // Java 8 style: 1.8.x
22+
case Array(v, _*) => v.toInt // Java 9+ style: 11.x, 17.x, etc.
23+
}
24+
1925
implicit def symbolToMethodSymbol(sym: Symbol): Symbols#MethodSymbol = sym.asInstanceOf[Symbols#MethodSymbol]
2026

2127
private val mirror = runtimeMirror(getClass.getClassLoader)
@@ -51,29 +57,23 @@ object ReflectionUtils {
5157
def returnsValueClass: Boolean = findTypeSymbol.exists(_.returnType.typeSymbol.isDerivedValueClass)
5258

5359
private def resolveWithScalaGenerics: Option[Class[_]] =
54-
scala.util
55-
.Try {
56-
findTypeSymbol
57-
.filter(_.returnType.typeSymbol.isClass)
58-
.map(_.asMethod.returnType.typeSymbol.asClass)
59-
.map(mirror.runtimeClass)
60-
}
61-
.toOption
62-
.flatten
60+
uTry {
61+
findTypeSymbol
62+
.filter(_.returnType.typeSymbol.isClass)
63+
.map(_.asMethod.returnType.typeSymbol.asClass)
64+
.map(mirror.runtimeClass)
65+
}.toOption.flatten
6366

6467
private def findTypeSymbol =
65-
scala.util
66-
.Try {
67-
mirror
68-
.classSymbol(method.getDeclaringClass)
69-
.info
70-
.decls
71-
.collectFirst {
72-
case symbol if isNonConstructorMethod(symbol) && customMirror.methodToJava(symbol) === method => symbol
73-
}
74-
}
75-
.toOption
76-
.flatten
68+
uTry {
69+
mirror
70+
.classSymbol(method.getDeclaringClass)
71+
.info
72+
.decls
73+
.collectFirst {
74+
case symbol if isNonConstructorMethod(symbol) && customMirror.methodToJava(symbol) === method => symbol
75+
}
76+
}.toOption.flatten
7777

7878
private def resolveWithJavaGenerics: Option[Class[_]] =
7979
try Some(GenericsResolver.resolve(invocation.getMock.getClass).`type`(method.getDeclaringClass).method(method).resolveReturnClass())
@@ -85,45 +85,47 @@ object ReflectionUtils {
8585
private def isNonConstructorMethod(d: ru.Symbol): Boolean = d.isMethod && !d.isConstructor
8686

8787
def extraInterfaces[T](implicit $wtt: WeakTypeTag[T], $ct: ClassTag[T]): List[Class[_]] =
88-
scala.util
89-
.Try {
90-
val cls = clazz($ct)
91-
$wtt.tpe match {
92-
case RefinedType(types, _) =>
93-
types.map($wtt.mirror.runtimeClass).collect {
94-
case c: Class[_] if c.isInterface && c != cls => c
95-
}
96-
case _ => List.empty
97-
}
88+
uTry {
89+
val cls = clazz($ct)
90+
$wtt.tpe match {
91+
case RefinedType(types, _) =>
92+
types.map($wtt.mirror.runtimeClass).collect {
93+
case c: Class[_] if c.isInterface && c != cls => c
94+
}
95+
case _ => List.empty
9896
}
99-
.toOption
97+
}.toOption
10098
.getOrElse(List.empty)
10199

102100
def methodsWithLazyOrVarArgs(classes: Seq[Class[_]]): Seq[(Method, Set[Int])] =
103101
classes.flatMap { clazz =>
104-
scala.util
105-
.Try {
106-
mirror
107-
.classSymbol(clazz)
108-
.info
109-
.members
110-
.collect {
111-
case symbol if isNonConstructorMethod(symbol) =>
112-
symbol -> symbol.typeSignature.paramLists.flatten.zipWithIndex.collect {
113-
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
114-
case (p, idx) if p.typeSignature.toString.endsWith("*") => idx
115-
}.toSet
116-
}
117-
.collect {
118-
case (symbol, indices) if indices.nonEmpty => customMirror.methodToJava(symbol) -> indices
119-
}
120-
.toSeq
121-
}
122-
.toOption
102+
uTry {
103+
mirror
104+
.classSymbol(clazz)
105+
.info
106+
.members
107+
.collect {
108+
case symbol if isNonConstructorMethod(symbol) =>
109+
symbol -> symbol.typeSignature.paramLists.flatten.zipWithIndex.collect {
110+
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
111+
case (p, idx) if p.typeSignature.toString.endsWith("*") => idx
112+
}.toSet
113+
}
114+
.collect {
115+
case (symbol, indices) if indices.nonEmpty => customMirror.methodToJava(symbol) -> indices
116+
}
117+
.toSeq
118+
}.toOption
123119
.getOrElse(Seq.empty)
124120
}
125121

126-
def setFinalStatic(field: Field, newValue: Any): Unit = {
122+
def setFinalStatic(field: Field, newValue: AnyRef): Unit =
123+
if (JavaVersion < 17)
124+
setFinalStatic17Minus(field, newValue)
125+
else
126+
setFinalStatic17Plus(field, newValue)
127+
128+
private def setFinalStatic17Minus(field: Field, newValue: AnyRef): Unit = {
127129
val clazz = classOf[java.lang.Class[_]]
128130
field.setAccessible(true)
129131
val modifiersField: Field = uTry(clazz.getDeclaredField("modifiers")) match {
@@ -150,4 +152,34 @@ object ReflectionUtils {
150152
field.set(null, newValue)
151153
}
152154

155+
private def setFinalStatic17Plus(field: Field, newValue: AnyRef): Unit =
156+
try {
157+
// Try to get Unsafe instance (works with both sun.misc.Unsafe and jdk.internal.misc.Unsafe)
158+
val unsafeClass: Class[_] =
159+
try
160+
Class.forName("sun.misc.Unsafe")
161+
catch {
162+
case _: ClassNotFoundException => Class.forName("jdk.internal.misc.Unsafe")
163+
}
164+
165+
val unsafeField = unsafeClass.getDeclaredField("theUnsafe")
166+
unsafeField.setAccessible(true)
167+
val unsafe = unsafeField.get(null)
168+
169+
// Get methods via reflection to handle both Unsafe implementations
170+
val staticFieldBaseMethod = unsafeClass.getMethod("staticFieldBase", classOf[Field])
171+
val staticFieldOffsetMethod = unsafeClass.getMethod("staticFieldOffset", classOf[Field])
172+
val putObjectMethod = unsafeClass.getMethod("putObject", classOf[Object], classOf[Long], classOf[Object])
173+
174+
// Get base and offset for the field
175+
val base: Object = staticFieldBaseMethod.invoke(unsafe, field)
176+
val offset: Long = staticFieldOffsetMethod.invoke(unsafe, field).asInstanceOf[Long]
177+
178+
// Set the field value directly
179+
putObjectMethod.invoke(unsafe, base, java.lang.Long.valueOf(offset), newValue)
180+
} catch {
181+
case NonFatal(e) =>
182+
throw new IllegalStateException(s"Cannot modify final field ${field.getName}", e)
183+
}
184+
153185
}

core/src/main/scala-2.11/org/mockito/matchers/EqMatchers_VersionSpecific.scala

Lines changed: 0 additions & 15 deletions
This file was deleted.

macro-common/src/main/scala/org/mockito/internal/ScalaVersion.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import scala.util.Properties
44

55
sealed trait ScalaVersion
66
object ScalaVersion {
7-
case object V2_11 extends ScalaVersion
87
case object V2_12 extends ScalaVersion
98
case object V2_13 extends ScalaVersion
109

1110
val Current: ScalaVersion = {
1211
val version = Properties.scalaPropOrElse("version.number", "unknown")
13-
if (version.startsWith("2.11")) ScalaVersion.V2_11
14-
else if (version.startsWith("2.12")) ScalaVersion.V2_12
12+
if (version.startsWith("2.12")) ScalaVersion.V2_12
1513
else if (version.startsWith("2.13")) ScalaVersion.V2_13
1614
else throw new Exception(s"Unsupported scala version $version")
1715
}

0 commit comments

Comments
 (0)