mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe00848be2 |
+5
-8
@@ -18,7 +18,6 @@ package org.springframework.build.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
@@ -65,12 +64,10 @@ public class MultiReleaseJarPlugin implements Plugin<Project> {
|
||||
dependencies,
|
||||
objects);
|
||||
|
||||
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
|
||||
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
|
||||
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
|
||||
});
|
||||
tasks.named("check", task -> task.dependsOn(validateJarTask));
|
||||
}
|
||||
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
|
||||
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
|
||||
});
|
||||
tasks.named("check", task -> task.dependsOn(validateJarTask));
|
||||
}
|
||||
}
|
||||
|
||||
-15
@@ -29,8 +29,6 @@ import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -45,13 +43,10 @@ public class MultiReleaseJarPluginTests {
|
||||
|
||||
private File buildFile;
|
||||
|
||||
private File propertiesFile;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
this.buildFile = new File(this.projectDir, "build.gradle");
|
||||
this.propertiesFile = new File(this.projectDir, "gradle.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,7 +119,6 @@ public class MultiReleaseJarPluginTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
|
||||
void validateJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
@@ -137,9 +131,6 @@ public class MultiReleaseJarPluginTests {
|
||||
}
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeGradleProperties("""
|
||||
org.gradle.jvmargs=-Duser.language=en
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {
|
||||
|
||||
@@ -161,12 +152,6 @@ public class MultiReleaseJarPluginTests {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeGradleProperties(String properties) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
|
||||
out.print(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClass(String path, String fileName, String fileContent) throws IOException {
|
||||
Path folder = this.projectDir.toPath().resolve(path);
|
||||
Files.createDirectories(folder);
|
||||
|
||||
@@ -36,4 +36,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.20/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.19/ui-bundle.zip
|
||||
|
||||
@@ -184,10 +184,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
standaloneSetup(SimpleController())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
|
||||
.build()
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
mockMvc = standaloneSetup(PersonController()).addFilters<StandaloneMockMvcBuilder>(CharacterEncodingFilter()).build()
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -159,18 +159,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MyWebTests {
|
||||
|
||||
lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockMvc = standaloneSetup(AccountController())
|
||||
.defaultRequest<StandaloneMockMvcBuilder>(get("/")
|
||||
.contextPath("/app").servletPath("/main")
|
||||
.accept(MediaType.APPLICATION_JSON)).build()
|
||||
}
|
||||
}
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -25,13 +25,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// static import of MockMvcBuilders.standaloneSetup
|
||||
|
||||
val mockMvc = standaloneSetup(MusicController())
|
||||
.defaultRequest<StandaloneMockMvcBuilder>(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
|
||||
.build()
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
@@ -59,13 +53,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// static import of SharedHttpSessionConfigurer.sharedHttpSession
|
||||
|
||||
val mockMvc = MockMvcBuilders.standaloneSetup(TestController())
|
||||
.apply<StandaloneMockMvcBuilder>(sharedHttpSession())
|
||||
.build()
|
||||
|
||||
// Use mockMvc to perform requests...
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ use https://www.gebish.org/[Geb] to make our tests even Groovy-er.
|
||||
== Why Geb and MockMvc?
|
||||
|
||||
Geb is backed by WebDriver, so it offers many of the
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[same benefits]
|
||||
that we get from WebDriver. However, Geb makes things even easier by taking care of some
|
||||
of the boilerplate code for us.
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[same benefits] that we get from
|
||||
WebDriver. However, Geb makes things even easier by taking care of some of the
|
||||
boilerplate code for us.
|
||||
|
||||
[[mockmvc-server-htmlunit-geb-setup]]
|
||||
== MockMvc and Geb Setup
|
||||
@@ -28,8 +28,7 @@ def setup() {
|
||||
----
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
||||
usage, see
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
|
||||
This ensures that any URL referencing `localhost` as the server is directed to our
|
||||
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
|
||||
@@ -63,10 +62,10 @@ forwarded to the current page object. This removes a lot of the boilerplate code
|
||||
needed when using WebDriver directly.
|
||||
|
||||
As with direct WebDriver usage, this improves on the design of our
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by using the Page Object Pattern. As mentioned previously, we can use the Page Object
|
||||
Pattern with HtmlUnit and WebDriver, but it is even easier with Geb. Consider our new
|
||||
Groovy-based `CreateMessagePage` implementation:
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test] by using the Page Object
|
||||
Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and
|
||||
WebDriver, but it is even easier with Geb. Consider our new Groovy-based
|
||||
`CreateMessagePage` implementation:
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
|
||||
@@ -7,7 +7,8 @@ to use the raw HtmlUnit libraries.
|
||||
[[mockmvc-server-htmlunit-mah-setup]]
|
||||
== MockMvc and HtmlUnit Setup
|
||||
|
||||
First, make sure that you have included a test dependency on `org.htmlunit:htmlunit`.
|
||||
First, make sure that you have included a test dependency on
|
||||
`org.htmlunit:htmlunit`.
|
||||
|
||||
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
|
||||
`MockMvcWebClientBuilder`, as follows:
|
||||
@@ -44,7 +45,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
|
||||
see <<mockmvc-server-htmlunit-mah-advanced-builder>>.
|
||||
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
|
||||
|
||||
This ensures that any URL that references `localhost` as the server is directed to our
|
||||
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
|
||||
@@ -76,7 +77,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
|
||||
as described in <<mockmvc-server-htmlunit-mah-advanced-builder>>.
|
||||
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
|
||||
|
||||
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it
|
||||
to create a message, as the following example shows:
|
||||
@@ -143,10 +144,10 @@ Kotlin::
|
||||
======
|
||||
|
||||
The preceding code improves on our
|
||||
xref:testing/mockmvc/htmlunit/why.adoc#mockmvc-server-htmlunit-why[MockMvc test] in a
|
||||
number of ways. First, we no longer have to explicitly verify our form and then create a
|
||||
request that looks like the form. Instead, we request the form, fill it out, and submit
|
||||
it, thereby significantly reducing the overhead.
|
||||
xref:testing/mockmvc/htmlunit/why.adoc#spring-mvc-test-server-htmlunit-mock-mvc-test[MockMvc test] in a number of ways.
|
||||
First, we no longer have to explicitly verify our form and then create a request that
|
||||
looks like the form. Instead, we request the form, fill it out, and submit it, thereby
|
||||
significantly reducing the overhead.
|
||||
|
||||
Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit
|
||||
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test
|
||||
@@ -266,19 +267,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup(context)
|
||||
.apply<DefaultMockMvcBuilder>(springSecurity())
|
||||
.build()
|
||||
|
||||
webClient = MockMvcWebClientBuilder
|
||||
.mockMvcSetup(mockMvc)
|
||||
// for illustration only - defaults to ""
|
||||
.contextPath("")
|
||||
// By default MockMvc is used for localhost only;
|
||||
// the following will use MockMvc for example.com and example.org as well
|
||||
.useMockMvcForHosts("example.com", "example.org")
|
||||
.build()
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
||||
usage, see <<mockmvc-server-htmlunit-webdriver-advanced-builder>>.
|
||||
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
|
||||
The preceding example ensures that any URL that references `localhost` as the server is
|
||||
directed to our `MockMvc` instance without the need for a real HTTP connection. Any other
|
||||
@@ -259,11 +259,10 @@ Kotlin::
|
||||
======
|
||||
--
|
||||
|
||||
This improves on the design of our
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by leveraging the Page Object Pattern. As we mentioned in
|
||||
<<mockmvc-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern with
|
||||
HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
|
||||
with HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
`CreateMessagePage` implementation:
|
||||
|
||||
--
|
||||
@@ -563,19 +562,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val mockMvc: MockMvc = MockMvcBuilders
|
||||
.webAppContextSetup(context)
|
||||
.apply<DefaultMockMvcBuilder>(springSecurity())
|
||||
.build()
|
||||
|
||||
driver = MockMvcHtmlUnitDriverBuilder
|
||||
.mockMvcSetup(mockMvc)
|
||||
// for illustration only - defaults to ""
|
||||
.contextPath("")
|
||||
// By default MockMvc is used for localhost only;
|
||||
// the following will use MockMvc for example.com and example.org as well
|
||||
.useMockMvcForHosts("example.com", "example.org")
|
||||
.build()
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ assume our form looks like the following snippet:
|
||||
</form>
|
||||
----
|
||||
|
||||
How do we ensure that our form produces the correct request to create a new message? A
|
||||
How do we ensure that our form produce the correct request to create a new message? A
|
||||
naive attempt might resemble the following:
|
||||
|
||||
[tabs]
|
||||
@@ -154,7 +154,7 @@ validation.
|
||||
[[mockmvc-server-htmlunit-why-integration]]
|
||||
== Integration Testing to the Rescue?
|
||||
|
||||
To resolve the issues mentioned above, we could perform end-to-end integration testing,
|
||||
To resolve the issues mentioned earlier, we could perform end-to-end integration testing,
|
||||
but this has some drawbacks. Consider testing the view that lets us page through the
|
||||
messages. We might need the following tests:
|
||||
|
||||
@@ -171,7 +171,7 @@ leads to a number of additional challenges:
|
||||
* Testing can become slow, since each test would need to ensure that the database is in
|
||||
the correct state.
|
||||
* Since our database needs to be in a specific state, we cannot run tests in parallel.
|
||||
* Performing assertions on items such as auto-generated IDs, timestamps, and others can
|
||||
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
|
||||
be difficult.
|
||||
|
||||
These challenges do not mean that we should abandon end-to-end integration testing
|
||||
|
||||
@@ -129,7 +129,7 @@ dependencies {
|
||||
api("org.jboss.logging:jboss-logging:3.6.1.Final")
|
||||
api("org.jruby:jruby:10.0.2.0")
|
||||
api("org.jspecify:jspecify:1.0.0")
|
||||
api("org.junit.support:testng-engine:1.1.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
api("org.mozilla:rhino:1.7.15")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.4")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=7.0.1
|
||||
version=7.0.0
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @since 1.1
|
||||
* @see PropertiesBeanDefinitionReader
|
||||
* @see org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
|
||||
*/
|
||||
public abstract class BeanDefinitionReaderUtils {
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ import org.springframework.core.AliasRegistry;
|
||||
* @see DefaultListableBeanFactory
|
||||
* @see org.springframework.context.support.GenericApplicationContext
|
||||
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
|
||||
* @see PropertiesBeanDefinitionReader
|
||||
*/
|
||||
public interface BeanDefinitionRegistry extends AliasRegistry {
|
||||
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ import org.springframework.util.StringUtils;
|
||||
* @since 26.11.2003
|
||||
* @see DefaultListableBeanFactory
|
||||
* @deprecated in favor of Spring's common bean definition formats and/or
|
||||
* custom BeanDefinitionReader implementations
|
||||
* custom reader implementations
|
||||
*/
|
||||
@Deprecated(since = "5.3")
|
||||
public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
|
||||
|
||||
+2
-2
@@ -46,14 +46,14 @@ class YamlMapFactoryBeanTests {
|
||||
@Test
|
||||
void testSetIgnoreResourceNotFound() {
|
||||
this.factory.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE);
|
||||
this.factory.setResources(new FileSystemResource("non-existent-file.yml"));
|
||||
this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
|
||||
assertThat(this.factory.getObject()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetBarfOnResourceNotFound() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
this.factory.setResources(new FileSystemResource("non-existent-file.yml"));
|
||||
this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
|
||||
this.factory.getObject().size();
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ class MetadataAttachmentTests {
|
||||
void metadataIsInherited() {
|
||||
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("testBean2");
|
||||
assertThat(beanDefinition.getAttribute("foo")).as("Metadata not inherited").isEqualTo("bar");
|
||||
assertThat(beanDefinition.getAttribute("abc")).as("Child metadata not attached").isEqualTo("123");
|
||||
assertThat(beanDefinition.getAttribute("abc")).as("Child metdata not attached").isEqualTo("123");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Vendored
+20
-37
@@ -76,7 +76,7 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
|
||||
private boolean allowNullValues = true;
|
||||
|
||||
private volatile boolean dynamic = true;
|
||||
private boolean dynamic = true;
|
||||
|
||||
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
|
||||
|
||||
@@ -101,15 +101,10 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
|
||||
/**
|
||||
* Specify the set of cache names for this CacheManager's 'static' mode.
|
||||
* <p>The number of caches and their names will be fixed after a call
|
||||
* to this method, with no creation of further cache regions at runtime.
|
||||
* <p>Note that this method replaces existing caches of the given names
|
||||
* and prevents the creation of further cache regions from here on - but
|
||||
* does <i>not</i> remove unrelated existing caches. For a full reset,
|
||||
* consider calling {@link #resetCaches()} before calling this method.
|
||||
* <p>Calling this method with a {@code null} collection argument resets
|
||||
* the mode to 'dynamic', allowing for further creation of caches again.
|
||||
* @see #resetCaches()
|
||||
* <p>The number of caches and their names will be fixed after a call to this method,
|
||||
* with no creation of further cache regions at runtime.
|
||||
* <p>Calling this with a {@code null} collection argument resets the
|
||||
* mode to 'dynamic', allowing for further creation of caches again.
|
||||
*/
|
||||
public void setCacheNames(@Nullable Collection<String> cacheNames) {
|
||||
if (cacheNames != null) {
|
||||
@@ -249,6 +244,11 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Cache getCache(String name) {
|
||||
Cache cache = this.cacheMap.get(name);
|
||||
@@ -258,33 +258,6 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this cache manager's caches, removing them completely for on-demand
|
||||
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
|
||||
* @since 6.2.14
|
||||
*/
|
||||
public void resetCaches() {
|
||||
this.cacheMap.values().forEach(Cache::clear);
|
||||
if (this.dynamic) {
|
||||
this.cacheMap.keySet().retainAll(this.customCacheNames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified cache from this cache manager, applying to
|
||||
* custom caches as well as dynamically registered caches at runtime.
|
||||
* @param name the name of the cache
|
||||
* @since 6.1.15
|
||||
*/
|
||||
public void removeCache(String name) {
|
||||
this.customCacheNames.remove(name);
|
||||
this.cacheMap.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given native Caffeine Cache instance with this cache manager,
|
||||
@@ -328,6 +301,16 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified cache from this cache manager, applying to
|
||||
* custom caches as well as dynamically registered caches at runtime.
|
||||
* @param name the name of the cache
|
||||
* @since 6.1.15
|
||||
*/
|
||||
public void removeCache(String name) {
|
||||
this.customCacheNames.remove(name);
|
||||
this.cacheMap.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -95,8 +95,8 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
// Skip setBeanFactory method on BeanFactoryAware.
|
||||
if (method.getDeclaringClass() == BeanFactoryAware.class) {
|
||||
// Skip methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+2
-22
@@ -24,6 +24,7 @@ import com.github.benmanes.caffeine.cache.CaffeineSpec;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.support.SimpleValueWrapper;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -41,7 +42,7 @@ class CaffeineCacheManagerTests {
|
||||
@Test
|
||||
@SuppressWarnings("cast")
|
||||
void dynamicMode() {
|
||||
CaffeineCacheManager cm = new CaffeineCacheManager();
|
||||
CacheManager cm = new CaffeineCacheManager();
|
||||
|
||||
Cache cache1 = cm.getCache("c1");
|
||||
assertThat(cache1).isInstanceOf(CaffeineCache.class);
|
||||
@@ -75,14 +76,6 @@ class CaffeineCacheManagerTests {
|
||||
cache1.evict("key3");
|
||||
assertThat(cache1.get("key3", () -> (String) null)).isNull();
|
||||
assertThat(cache1.get("key3", () -> (String) null)).isNull();
|
||||
|
||||
cm.removeCache("c1");
|
||||
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2);
|
||||
|
||||
cm.resetCaches();
|
||||
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
|
||||
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -138,24 +131,11 @@ class CaffeineCacheManagerTests {
|
||||
|
||||
cm.setAllowNullValues(true);
|
||||
Cache cache1y = cm.getCache("c1");
|
||||
Cache cache2y = cm.getCache("c2");
|
||||
|
||||
cache1y.put("key3", null);
|
||||
assertThat(cache1y.get("key3").get()).isNull();
|
||||
cache1y.evict("key3");
|
||||
assertThat(cache1y.get("key3")).isNull();
|
||||
cache2y.put("key4", "value4");
|
||||
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
|
||||
|
||||
cm.removeCache("c1");
|
||||
assertThat(cm.getCache("c1")).isNull();
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2y);
|
||||
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
|
||||
|
||||
cm.resetCaches();
|
||||
assertThat(cm.getCache("c1")).isNull();
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2y);
|
||||
assertThat(cache2y.get("key4")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Vendored
+10
-27
@@ -55,7 +55,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
|
||||
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
|
||||
|
||||
private volatile boolean dynamic = true;
|
||||
private boolean dynamic = true;
|
||||
|
||||
private boolean allowNullValues = true;
|
||||
|
||||
@@ -82,15 +82,10 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
|
||||
/**
|
||||
* Specify the set of cache names for this CacheManager's 'static' mode.
|
||||
* <p>The number of caches and their names will be fixed after a call
|
||||
* to this method, with no creation of further cache regions at runtime.
|
||||
* <p>Note that this method replaces existing caches of the given names
|
||||
* and prevents the creation of further cache regions from here on - but
|
||||
* does <i>not</i> remove unrelated existing caches. For a full reset,
|
||||
* consider calling {@link #resetCaches()} before calling this method.
|
||||
* <p>Calling this method with a {@code null} collection argument resets
|
||||
* the mode to 'dynamic', allowing for further creation of caches again.
|
||||
* @see #resetCaches()
|
||||
* <p>The number of caches and their names will be fixed after a call to this method,
|
||||
* with no creation of further cache regions at runtime.
|
||||
* <p>Calling this with a {@code null} collection argument resets the
|
||||
* mode to 'dynamic', allowing for further creation of caches again.
|
||||
*/
|
||||
public void setCacheNames(@Nullable Collection<String> cacheNames) {
|
||||
if (cacheNames != null) {
|
||||
@@ -165,6 +160,11 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Cache getCache(String name) {
|
||||
Cache cache = this.cacheMap.get(name);
|
||||
@@ -174,23 +174,6 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this cache manager's caches, removing them completely for on-demand
|
||||
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
|
||||
* @since 6.2.14
|
||||
*/
|
||||
public void resetCaches() {
|
||||
this.cacheMap.values().forEach(Cache::clear);
|
||||
if (this.dynamic) {
|
||||
this.cacheMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified cache from this cache manager.
|
||||
* @param name the name of the cache
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -137,8 +137,8 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
// Skip setBeanFactory method on BeanFactoryAware.
|
||||
if (method.getDeclaringClass() == BeanFactoryAware.class) {
|
||||
// Skip methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+24
-6
@@ -18,6 +18,8 @@ package org.springframework.cache.support;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
@@ -31,28 +33,44 @@ import org.springframework.cache.CacheManager;
|
||||
* for disabling caching, typically used for backing cache declarations
|
||||
* without an actual backing store.
|
||||
*
|
||||
* <p>This implementation will simply accept any items into the cache,
|
||||
* not actually storing them.
|
||||
* <p>Will simply accept any items into the cache not actually storing them.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @see NoOpCache
|
||||
*/
|
||||
public class NoOpCacheManager implements CacheManager {
|
||||
|
||||
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
|
||||
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<>(16);
|
||||
|
||||
private final Set<String> cacheNames = new LinkedHashSet<>(16);
|
||||
|
||||
|
||||
/**
|
||||
* This implementation always returns a {@link Cache} implementation that will not store items.
|
||||
* Additionally, the request cache will be remembered by the manager for consistency.
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Cache getCache(String name) {
|
||||
return this.cacheMap.computeIfAbsent(name, NoOpCache::new);
|
||||
Cache cache = this.caches.get(name);
|
||||
if (cache == null) {
|
||||
this.caches.computeIfAbsent(name, NoOpCache::new);
|
||||
synchronized (this.cacheNames) {
|
||||
this.cacheNames.add(name);
|
||||
}
|
||||
}
|
||||
return this.caches.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the name of the caches previously requested.
|
||||
*/
|
||||
@Override
|
||||
public Collection<String> getCacheNames() {
|
||||
return Collections.unmodifiableSet(this.cacheMap.keySet());
|
||||
synchronized (this.cacheNames) {
|
||||
return Collections.unmodifiableSet(this.cacheNames);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -225,6 +225,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
|
||||
* @param beanFactory the bean factory to load bean definitions into
|
||||
* @throws BeansException if parsing of the bean definitions failed
|
||||
* @throws IOException if loading of bean definition files failed
|
||||
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
|
||||
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
|
||||
*/
|
||||
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
|
||||
|
||||
+3
@@ -81,6 +81,8 @@ import org.springframework.util.Assert;
|
||||
* GenericApplicationContext ctx = new GenericApplicationContext();
|
||||
* XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
|
||||
* xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
|
||||
* PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
|
||||
* propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
|
||||
* ctx.refresh();
|
||||
*
|
||||
* MyBean myBean = (MyBean) ctx.getBean("myBean");
|
||||
@@ -102,6 +104,7 @@ import org.springframework.util.Assert;
|
||||
* @see #registerBeanDefinition
|
||||
* @see #refresh()
|
||||
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
|
||||
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
|
||||
*/
|
||||
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
|
||||
|
||||
|
||||
+2
-22
@@ -19,6 +19,7 @@ package org.springframework.cache.concurrent;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -30,7 +31,7 @@ class ConcurrentMapCacheManagerTests {
|
||||
|
||||
@Test
|
||||
void testDynamicMode() {
|
||||
ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager();
|
||||
CacheManager cm = new ConcurrentMapCacheManager();
|
||||
Cache cache1 = cm.getCache("c1");
|
||||
assertThat(cache1).isInstanceOf(ConcurrentMapCache.class);
|
||||
Cache cache1again = cm.getCache("c1");
|
||||
@@ -64,14 +65,6 @@ class ConcurrentMapCacheManagerTests {
|
||||
assertThat(cache1.get("key3").get()).isNull();
|
||||
cache1.evict("key3");
|
||||
assertThat(cache1.get("key3")).isNull();
|
||||
|
||||
cm.removeCache("c1");
|
||||
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2);
|
||||
|
||||
cm.resetCaches();
|
||||
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
|
||||
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -114,24 +107,11 @@ class ConcurrentMapCacheManagerTests {
|
||||
|
||||
cm.setAllowNullValues(true);
|
||||
Cache cache1y = cm.getCache("c1");
|
||||
Cache cache2y = cm.getCache("c2");
|
||||
|
||||
cache1y.put("key3", null);
|
||||
assertThat(cache1y.get("key3").get()).isNull();
|
||||
cache1y.evict("key3");
|
||||
assertThat(cache1y.get("key3")).isNull();
|
||||
cache2y.put("key4", "value4");
|
||||
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
|
||||
|
||||
cm.removeCache("c1");
|
||||
assertThat(cm.getCache("c1")).isNull();
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2y);
|
||||
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
|
||||
|
||||
cm.resetCaches();
|
||||
assertThat(cm.getCache("c1")).isNull();
|
||||
assertThat(cm.getCache("c2")).isSameAs(cache2y);
|
||||
assertThat(cache2y.get("key4")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ class CommonAnnotationBeanRegistrationAotContributionTests {
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void contributeWhenPackagePrivateFieldInjectionInjectsUsingFieldAssignment() {
|
||||
void contributeWhenPackagePrivateFieldInjectionInjectsUsingFieldAssignement() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
|
||||
+247
-129
@@ -16,16 +16,19 @@
|
||||
|
||||
package org.springframework.scheduling.support;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedClass;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.FieldSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.scheduling.TriggerContext;
|
||||
|
||||
@@ -41,55 +44,51 @@ import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@ParameterizedClass
|
||||
@FieldSource("parameters")
|
||||
@SuppressWarnings("deprecation")
|
||||
class CronTriggerTests {
|
||||
|
||||
static List<Arguments> parameters = List.of(
|
||||
arguments(new Date(), TimeZone.getTimeZone("PST")),
|
||||
arguments(new Date(), TimeZone.getTimeZone("CET")));
|
||||
|
||||
|
||||
private final Calendar calendar = new GregorianCalendar();
|
||||
private final Date localDateTime;
|
||||
private final TimeZone timeZone;
|
||||
|
||||
|
||||
CronTriggerTests(Date localDateTime, TimeZone timeZone) {
|
||||
private void setup(Date localDateTime, TimeZone timeZone) {
|
||||
this.calendar.setTime(localDateTime);
|
||||
this.calendar.setTimeZone(timeZone);
|
||||
roundup(this.calendar);
|
||||
this.localDateTime = localDateTime;
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void matchAll() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void matchAll(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("* * * * * *", timeZone);
|
||||
TriggerContext context = getTriggerContext(localDateTime);
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchLastSecond() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void matchLastSecond(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("* * * * * *", timeZone);
|
||||
GregorianCalendar calendar = new GregorianCalendar();
|
||||
calendar.set(Calendar.SECOND, 58);
|
||||
assertMatchesNextSecond(trigger, calendar);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchSpecificSecond() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void matchSpecificSecond(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("10 * * * * *", timeZone);
|
||||
GregorianCalendar calendar = new GregorianCalendar();
|
||||
calendar.set(Calendar.SECOND, 9);
|
||||
assertMatchesNextSecond(trigger, calendar);
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementSecondByOne() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementSecondByOne(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("11 * * * * *", timeZone);
|
||||
this.calendar.set(Calendar.SECOND, 10);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -98,8 +97,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementSecondWithPreviousExecutionTooEarly() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementSecondWithPreviousExecutionTooEarly(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("11 * * * * *", timeZone);
|
||||
this.calendar.set(Calendar.SECOND, 11);
|
||||
SimpleTriggerContext context = new SimpleTriggerContext();
|
||||
@@ -109,8 +110,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementSecondAndRollover() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementSecondAndRollover(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("10 * * * * *", timeZone);
|
||||
this.calendar.set(Calendar.SECOND, 11);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -119,8 +122,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondRange() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void secondRange(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("10-15 * * * * *", timeZone);
|
||||
this.calendar.set(Calendar.SECOND, 9);
|
||||
assertMatchesNextSecond(trigger, this.calendar);
|
||||
@@ -128,8 +133,10 @@ class CronTriggerTests {
|
||||
assertMatchesNextSecond(trigger, this.calendar);
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementMinute() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementMinute(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 * * * * *", timeZone);
|
||||
this.calendar.set(Calendar.MINUTE, 10);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -144,8 +151,10 @@ class CronTriggerTests {
|
||||
assertThat(localDate).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementMinuteByOne() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementMinuteByOne(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 11 * * * *", timeZone);
|
||||
this.calendar.set(Calendar.MINUTE, 10);
|
||||
TriggerContext context = getTriggerContext(this.calendar.getTime());
|
||||
@@ -154,8 +163,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementMinuteAndRollover() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementMinuteAndRollover(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 10 * * * *", timeZone);
|
||||
this.calendar.set(Calendar.MINUTE, 11);
|
||||
this.calendar.set(Calendar.SECOND, 0);
|
||||
@@ -165,8 +176,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementHour() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementHour(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 * * * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 9);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
|
||||
@@ -184,8 +197,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementHourAndRollover() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementHourAndRollover(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 * * * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 9);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 10);
|
||||
@@ -204,8 +219,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementDayOfMonth() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementDayOfMonth(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -224,8 +241,10 @@ class CronTriggerTests {
|
||||
assertThat(this.calendar.get(Calendar.DAY_OF_MONTH)).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementDayOfMonthByOne() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementDayOfMonthByOne(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("* * * 10 * *", timeZone);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 9);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -237,8 +256,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementDayOfMonthAndRollover() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementDayOfMonthAndRollover(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("* * * 10 * *", timeZone);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 11);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -251,8 +272,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dailyTriggerInShortMonth() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void dailyTriggerInShortMonth(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 8); // September: 30 days
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
|
||||
@@ -270,8 +293,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dailyTriggerInLongMonth() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void dailyTriggerInLongMonth(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 7); // August: 31 days and not a daylight saving boundary
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
|
||||
@@ -289,8 +314,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dailyTriggerOnDaylightSavingBoundary() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void dailyTriggerOnDaylightSavingBoundary(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 * * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 9); // October: 31 days and a daylight saving boundary in CET
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
|
||||
@@ -308,8 +335,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementMonth() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementMonth(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 9);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
|
||||
@@ -327,8 +356,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementMonthAndRollover() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementMonthAndRollover(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 11);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 31);
|
||||
@@ -348,8 +379,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context2)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthlyTriggerInLongMonth() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void monthlyTriggerInLongMonth(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 31 * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 9);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
|
||||
@@ -362,8 +395,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthlyTriggerInShortMonth() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void monthlyTriggerInShortMonth(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 1 * *", timeZone);
|
||||
this.calendar.set(Calendar.MONTH, 9);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 30);
|
||||
@@ -377,8 +412,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context)).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementDayOfWeekByOne() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementDayOfWeekByOne(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("* * * * * 2", timeZone);
|
||||
this.calendar.set(Calendar.DAY_OF_WEEK, 2);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -391,8 +428,10 @@ class CronTriggerTests {
|
||||
assertThat(this.calendar.get(Calendar.DAY_OF_WEEK)).isEqualTo(Calendar.TUESDAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void incrementDayOfWeekAndRollover() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void incrementDayOfWeekAndRollover(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("* * * * * 2", timeZone);
|
||||
this.calendar.set(Calendar.DAY_OF_WEEK, 4);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -405,8 +444,10 @@ class CronTriggerTests {
|
||||
assertThat(this.calendar.get(Calendar.DAY_OF_WEEK)).isEqualTo(Calendar.TUESDAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificMinuteSecond() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void specificMinuteSecond(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("55 5 * * * *", timeZone);
|
||||
this.calendar.set(Calendar.MINUTE, 4);
|
||||
this.calendar.set(Calendar.SECOND, 54);
|
||||
@@ -422,8 +463,10 @@ class CronTriggerTests {
|
||||
assertThat(actual).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificHourSecond() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void specificHourSecond(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("55 * 10 * * *", timeZone);
|
||||
this.calendar.set(Calendar.HOUR_OF_DAY, 9);
|
||||
this.calendar.set(Calendar.SECOND, 54);
|
||||
@@ -440,8 +483,10 @@ class CronTriggerTests {
|
||||
assertThat(actual).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificMinuteHour() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void specificMinuteHour(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("* 5 10 * * *", timeZone);
|
||||
this.calendar.set(Calendar.MINUTE, 4);
|
||||
this.calendar.set(Calendar.HOUR_OF_DAY, 9);
|
||||
@@ -459,8 +504,10 @@ class CronTriggerTests {
|
||||
assertThat(actual).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificDayOfMonthSecond() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void specificDayOfMonthSecond(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("55 * * 3 * *", timeZone);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 2);
|
||||
this.calendar.set(Calendar.SECOND, 54);
|
||||
@@ -478,8 +525,10 @@ class CronTriggerTests {
|
||||
assertThat(actual).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificDate() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void specificDate(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("* * * 3 11 *", timeZone);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 2);
|
||||
this.calendar.set(Calendar.MONTH, 9);
|
||||
@@ -498,8 +547,10 @@ class CronTriggerTests {
|
||||
assertThat(actual).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonExistentSpecificDate() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void nonExistentSpecificDate(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
// TODO: maybe try and detect this as a special case in parser?
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 31 6 *", timeZone);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 10);
|
||||
@@ -509,8 +560,10 @@ class CronTriggerTests {
|
||||
assertThat(trigger.nextExecutionTime(context1)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void leapYearSpecificDate() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void leapYearSpecificDate(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 0 29 2 *", timeZone);
|
||||
this.calendar.set(Calendar.YEAR, 2007);
|
||||
this.calendar.set(Calendar.DAY_OF_MONTH, 10);
|
||||
@@ -530,8 +583,10 @@ class CronTriggerTests {
|
||||
assertThat(actual).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void weekDaySequence() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void weekDaySequence(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 0 7 ? * MON-FRI", timeZone);
|
||||
// This is a Saturday
|
||||
this.calendar.set(2009, 8, 26);
|
||||
@@ -556,138 +611,184 @@ class CronTriggerTests {
|
||||
assertThat(actual).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dayOfWeekIndifferent() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void dayOfWeekIndifferent(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("* * * 2 * *", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("* * * 2 * ?", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondIncrementer() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void secondIncrementer(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("57,59 * * * * *", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("57/2 * * * * *", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondIncrementerWithRange() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void secondIncrementerWithRange(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("1,3,5 * * * * *", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("1-6/2 * * * * *", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hourIncrementer() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void hourIncrementer(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("* * 4,8,12,16,20 * * *", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("* * 4/4 * * *", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dayNames() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void dayNames(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("* * * * * 0-6", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("* * * * * TUE,WED,THU,FRI,SAT,SUN,MON", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sundayIsZero() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void sundayIsZero(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("* * * * * 0", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("* * * * * SUN", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sundaySynonym() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void sundaySynonym(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("* * * * * 0", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("* * * * * 7", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthNames() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void monthNames(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("* * * * 1-12 *", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthNamesMixedCase() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void monthNamesMixedCase(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("* * * * 2 *", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("* * * * Feb *", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void secondInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("77 * * * * *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondRangeInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void secondRangeInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("44-77 * * * * *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void minuteInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void minuteInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* 77 * * * *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void minuteRangeInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void minuteRangeInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* 44-77 * * * *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hourInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void hourInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * 27 * * *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hourRangeInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void hourRangeInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * 23-28 * * *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dayInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void dayInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * * 45 * *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dayRangeInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void dayRangeInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * * 28-45 * *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void monthInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("0 0 0 25 13 ?", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthInvalidTooSmall() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void monthInvalidTooSmall(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("0 0 0 25 0 ?", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dayOfMonthInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void dayOfMonthInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("0 0 0 32 12 ?", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthRangeInvalid() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void monthRangeInvalid(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new CronTrigger("* * * * 11-13 *", timeZone));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whitespace() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void whitespace(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger1 = new CronTrigger("* * * * 1 *", timeZone);
|
||||
CronTrigger trigger2 = new CronTrigger("* * * * 1 *", timeZone);
|
||||
assertThat(trigger2).isEqualTo(trigger1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void monthSequence() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void monthSequence(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
CronTrigger trigger = new CronTrigger("0 30 23 30 1/3 ?", timeZone);
|
||||
this.calendar.set(2010, 11, 30);
|
||||
Date localDate = this.calendar.getTime();
|
||||
@@ -711,8 +812,10 @@ class CronTriggerTests {
|
||||
assertThat(actual).isEqualTo(this.calendar.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void daylightSavingMissingHour() {
|
||||
@ParameterizedCronTriggerTest
|
||||
void daylightSavingMissingHour(Date localDateTime, TimeZone timeZone) {
|
||||
setup(localDateTime, timeZone);
|
||||
|
||||
// This trigger has to be somewhere between 2:00 AM and 3:00 AM, so we
|
||||
// use a cron expression for 2:10 AM every day.
|
||||
CronTrigger trigger = new CronTrigger("0 10 2 * * *", timeZone);
|
||||
@@ -762,4 +865,19 @@ class CronTriggerTests {
|
||||
return new SimpleTriggerContext(null, null, lastCompletionTime);
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@ParameterizedTest(name = "[{index}] localDateTime[{0}], time zone[{1}]")
|
||||
@MethodSource("parameters")
|
||||
@interface ParameterizedCronTriggerTest {
|
||||
}
|
||||
|
||||
static Stream<Arguments> parameters() {
|
||||
return Stream.of(
|
||||
arguments(new Date(), TimeZone.getTimeZone("PST")),
|
||||
arguments(new Date(), TimeZone.getTimeZone("CET"))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ class InstrumentedMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetConstructorsShouldNotMatchWhenMissingTypeHint() {
|
||||
void classGetConstructorsShouldNotMatchWhemMissingTypeHint() {
|
||||
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
|
||||
}
|
||||
|
||||
|
||||
+3
-164
@@ -20,10 +20,8 @@ import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
@@ -31,8 +29,6 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -105,17 +101,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
/**
|
||||
* Late binding entry set.
|
||||
*/
|
||||
private @Nullable Set<Map.Entry<K, V>> entrySet;
|
||||
|
||||
/**
|
||||
* Late binding key set.
|
||||
*/
|
||||
private @Nullable Set<K> keySet;
|
||||
|
||||
/**
|
||||
* Late binding values collection.
|
||||
*/
|
||||
private @Nullable Collection<V> values;
|
||||
private volatile @Nullable Set<Map.Entry<K, V>> entrySet;
|
||||
|
||||
|
||||
/**
|
||||
@@ -526,26 +512,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
Set<K> keySet = this.keySet;
|
||||
if (keySet == null) {
|
||||
keySet = new KeySet();
|
||||
this.keySet = keySet;
|
||||
}
|
||||
return keySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
Collection<V> values = this.values;
|
||||
if (values == null) {
|
||||
values = new Values();
|
||||
this.values = values;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private <T> @Nullable T doTask(@Nullable Object key, Task<T> task) {
|
||||
int hash = getHash(key);
|
||||
return getSegmentForHash(hash).doTask(hash, key, task);
|
||||
@@ -974,7 +940,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
/**
|
||||
* Internal entry-set implementation.
|
||||
*/
|
||||
private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
|
||||
private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<K, V>> iterator() {
|
||||
@@ -1010,140 +976,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
|
||||
public void clear() {
|
||||
ConcurrentReferenceHashMap.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<Map.Entry<K, V>> spliterator() {
|
||||
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal key-set implementation.
|
||||
*/
|
||||
private final class KeySet extends AbstractSet<K> {
|
||||
|
||||
@Override
|
||||
public Iterator<K> iterator() {
|
||||
return new KeyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return ConcurrentReferenceHashMap.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return ConcurrentReferenceHashMap.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
ConcurrentReferenceHashMap.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object k) {
|
||||
return ConcurrentReferenceHashMap.this.containsKey(k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<K> spliterator() {
|
||||
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.CONCURRENT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal key iterator implementation.
|
||||
*/
|
||||
private final class KeyIterator implements Iterator<K> {
|
||||
|
||||
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
this.iterator.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next() {
|
||||
return this.iterator.next().getKey();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal values collection implementation.
|
||||
*/
|
||||
private final class Values extends AbstractCollection<V> {
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return new ValueIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return ConcurrentReferenceHashMap.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return ConcurrentReferenceHashMap.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
ConcurrentReferenceHashMap.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object v) {
|
||||
return ConcurrentReferenceHashMap.this.containsValue(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<V> spliterator() {
|
||||
return Spliterators.spliterator(this, Spliterator.CONCURRENT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal value iterator implementation.
|
||||
*/
|
||||
private final class ValueIterator implements Iterator<V> {
|
||||
|
||||
private final Iterator<Map.Entry<K, V>> iterator = entrySet().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
this.iterator.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next() {
|
||||
return this.iterator.next().getValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal entry iterator implementation.
|
||||
*/
|
||||
private final class EntryIterator implements Iterator<Map.Entry<K, V>> {
|
||||
private class EntryIterator implements Iterator<Map.Entry<K, V>> {
|
||||
|
||||
private int segmentIndex;
|
||||
|
||||
|
||||
@@ -141,32 +141,26 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
Set<K> keySet = this.keySet;
|
||||
if (keySet == null) {
|
||||
keySet = Collections.unmodifiableSet(this.delegate.keySet());
|
||||
this.keySet = keySet;
|
||||
if (this.keySet == null) {
|
||||
this.keySet = Collections.unmodifiableSet(this.delegate.keySet());
|
||||
}
|
||||
return keySet;
|
||||
return this.keySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, List<V>>> entrySet() {
|
||||
Set<Entry<K, List<V>>> entrySet = this.entrySet;
|
||||
if (entrySet == null) {
|
||||
entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
|
||||
this.entrySet = entrySet;
|
||||
if (this.entrySet == null) {
|
||||
this.entrySet = new UnmodifiableEntrySet<>(this.delegate.entrySet());
|
||||
}
|
||||
return entrySet;
|
||||
return this.entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<V>> values() {
|
||||
Collection<List<V>> values = this.values;
|
||||
if (values == null) {
|
||||
values = new UnmodifiableValueCollection<>(this.delegate.values());
|
||||
this.values = values;
|
||||
if (this.values == null) {
|
||||
this.values = new UnmodifiableValueCollection<>(this.delegate.values());
|
||||
}
|
||||
return values;
|
||||
return this.values;
|
||||
}
|
||||
|
||||
// unsupported
|
||||
|
||||
+13
-179
@@ -16,7 +16,8 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
@@ -24,8 +25,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -33,13 +32,12 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap.Entry;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap.Reference;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap.Restructure;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConcurrentReferenceHashMap}.
|
||||
@@ -49,6 +47,8 @@ import static org.assertj.core.api.Assertions.entry;
|
||||
*/
|
||||
class ConcurrentReferenceHashMapTests {
|
||||
|
||||
private static final Comparator<? super String> NULL_SAFE_STRING_SORT = Comparators.nullsLow();
|
||||
|
||||
private TestWeakConcurrentCache<Integer, String> map = new TestWeakConcurrentCache<>();
|
||||
|
||||
|
||||
@@ -450,174 +450,19 @@ class ConcurrentReferenceHashMapTests {
|
||||
assertThat(this.map.keySet()).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void keySetContains() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
assertThat(this.map.keySet()).containsExactlyInAnyOrder(123, 456, null);
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void keySetRemove() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
assertThat(this.map.keySet().remove(123)).isTrue();
|
||||
assertThat(this.map).doesNotContainKey(123);
|
||||
assertThat(this.map.keySet().remove(123)).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void keySetIterator() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
Iterator<Integer> it = this.map.keySet().iterator();
|
||||
assertThat(it).toIterable().containsExactlyInAnyOrder(123, 456, null);
|
||||
assertThat(it).isExhausted();
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void keySetIteratorRemove() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
Iterator<Integer> keySetIterator = this.map.keySet().iterator();
|
||||
while (keySetIterator.hasNext()) {
|
||||
Integer key = keySetIterator.next();
|
||||
if (key != null && key.equals(456)) {
|
||||
keySetIterator.remove();
|
||||
}
|
||||
}
|
||||
assertThat(this.map).containsOnlyKeys(123, null);
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void keySetClear() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
this.map.keySet().clear();
|
||||
assertThat(this.map).isEmpty();
|
||||
assertThat(this.map.keySet()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void keySetAdd() {
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> this.map.keySet().add(12345));
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void keySetStream() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
Set<Integer> keys = this.map.keySet().stream().collect(Collectors.toSet());
|
||||
assertThat(keys).containsExactlyInAnyOrder(123, 456, null);
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void keySetSpliteratorCharacteristics() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
Spliterator<Integer> spliterator = this.map.keySet().spliterator();
|
||||
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT, Spliterator.DISTINCT);
|
||||
assertThat(spliterator.estimateSize()).isEqualTo(3L);
|
||||
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void valuesCollection() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
assertThat(this.map.values()).containsExactlyInAnyOrder("123", null, "789");
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void valuesCollectionAdd() {
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> this.map.values().add("12345"));
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void valuesCollectionClear() {
|
||||
Collection<String> values = this.map.values();
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
assertThat(values).hasSize(3);
|
||||
values.clear();
|
||||
assertThat(values).isEmpty();
|
||||
assertThat(this.map).isEmpty();
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void valuesCollectionRemoval() {
|
||||
Collection<String> values = this.map.values();
|
||||
assertThat(values).isEmpty();
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
assertThat(values).containsExactlyInAnyOrder("123", null, "789");
|
||||
values.remove(null);
|
||||
assertThat(values).containsExactlyInAnyOrder("123", "789");
|
||||
assertThat(map).containsOnly(entry(123, "123"), entry(null, "789"));
|
||||
values.remove("123");
|
||||
values.remove("789");
|
||||
assertThat(values).isEmpty();
|
||||
assertThat(map).isEmpty();
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void valuesCollectionIterator() {
|
||||
Iterator<String> iterator = this.map.values().iterator();
|
||||
assertThat(iterator).isExhausted();
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
iterator = this.map.values().iterator();
|
||||
assertThat(iterator).toIterable().containsExactlyInAnyOrder("123", null, "789");
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void valuesCollectionIteratorRemoval() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
Iterator<String> iterator = this.map.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
String value = iterator.next();
|
||||
if (value != null && value.equals("789")) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
assertThat(iterator).isExhausted();
|
||||
assertThat(this.map.values()).containsExactlyInAnyOrder("123", null);
|
||||
assertThat(this.map).containsOnlyKeys(123, 456);
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void valuesCollectionStream() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
List<String> values = this.map.values().stream().toList();
|
||||
assertThat(values).containsExactlyInAnyOrder("123", null, "789");
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void valuesCollectionSpliteratorCharacteristics() {
|
||||
this.map.put(123, "123");
|
||||
this.map.put(456, null);
|
||||
this.map.put(null, "789");
|
||||
Spliterator<String> spliterator = this.map.values().spliterator();
|
||||
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT);
|
||||
assertThat(spliterator.estimateSize()).isEqualTo(3L);
|
||||
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
|
||||
List<String> actual = new ArrayList<>(this.map.values());
|
||||
List<String> expected = new ArrayList<>();
|
||||
expected.add("123");
|
||||
expected.add(null);
|
||||
expected.add("789");
|
||||
actual.sort(NULL_SAFE_STRING_SORT);
|
||||
expected.sort(NULL_SAFE_STRING_SORT);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -696,17 +541,6 @@ class ConcurrentReferenceHashMapTests {
|
||||
copy.forEach(entry -> assertThat(entrySet).doesNotContain(entry));
|
||||
}
|
||||
|
||||
@Test // gh-35817
|
||||
void entrySetSpliteratorCharacteristics() {
|
||||
this.map.put(1, "1");
|
||||
this.map.put(2, "2");
|
||||
this.map.put(3, "3");
|
||||
Spliterator<Map.Entry<Integer, String>> spliterator = this.map.entrySet().spliterator();
|
||||
assertThat(spliterator).hasOnlyCharacteristics(Spliterator.CONCURRENT, Spliterator.DISTINCT);
|
||||
assertThat(spliterator.estimateSize()).isEqualTo(3L);
|
||||
assertThat(spliterator.getExactSizeIfKnown()).isEqualTo(-1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportNullReference() {
|
||||
// GC could happen during restructure so we must be able to create a reference for a null entry
|
||||
|
||||
@@ -81,7 +81,7 @@ class MapTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void inlineMapIsInstanceOfMap() {
|
||||
void inelineMapIsInstanceOfMap() {
|
||||
evaluate("{a:1, b:2} instanceof T(java.util.Map)", "true", Boolean.class);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -19,6 +19,8 @@ package org.springframework.jdbc.core;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Parameterized callback interface used by the {@link JdbcTemplate} class for
|
||||
* batch updates.
|
||||
@@ -47,6 +49,6 @@ public interface ParameterizedPreparedStatementSetter<T> {
|
||||
* @param argument the object containing the values to be set
|
||||
* @throws SQLException if an SQLException is encountered (i.e. there is no need to catch SQLException)
|
||||
*/
|
||||
void setValues(PreparedStatement ps, T argument) throws SQLException;
|
||||
void setValues(PreparedStatement ps, @Nullable T argument) throws SQLException;
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -41,8 +41,9 @@ import org.springframework.util.Assert;
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @see #loadBeanDefinitions
|
||||
* @deprecated in favor of Spring's common bean definition formats and/or
|
||||
* custom BeanDefinitionReader implementations
|
||||
* @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
|
||||
* @deprecated in favor of Spring's common bean definition formats and/or custom
|
||||
* reader implementations
|
||||
*/
|
||||
@Deprecated(since = "5.3")
|
||||
public class JdbcBeanDefinitionReader {
|
||||
|
||||
+26
-23
@@ -24,8 +24,8 @@ import java.sql.ResultSet
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String): T? =
|
||||
queryForObject(sql, T::class.java)
|
||||
inline fun <reified T> JdbcOperations.queryForObject(sql: String): T =
|
||||
queryForObject(sql, T::class.java as Class<*>) as T
|
||||
|
||||
/**
|
||||
* Extensions for [JdbcOperations.queryForObject] providing a RowMapper-like function
|
||||
@@ -34,7 +34,7 @@ inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String): T? =
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: Any?, crossinline function: (ResultSet, Int) -> T): T =
|
||||
inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: Any, crossinline function: (ResultSet, Int) -> T): T =
|
||||
queryForObject(sql, { resultSet, i -> function(resultSet, i) }, *args)
|
||||
|
||||
/**
|
||||
@@ -44,18 +44,18 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: A
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, args: Array<out Any?>, argTypes: IntArray): T? =
|
||||
queryForObject(sql, args, argTypes, T::class.java)
|
||||
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>, argTypes: IntArray): T =
|
||||
queryForObject(sql, args, argTypes, T::class.java as Class<*>) as T
|
||||
|
||||
/**
|
||||
* Extension for [JdbcOperations.queryForObject] providing a
|
||||
* `queryForObject<Foo>("...", arg1, argN)` variant.
|
||||
* `queryForObject<Foo>("...", arrayOf(arg1, argN))` variant.
|
||||
*
|
||||
* @author Sébastien Deleuze
|
||||
* @since 7.0
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, vararg args: Any?): T? =
|
||||
queryForObject(sql, T::class.java, *args)
|
||||
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T =
|
||||
queryForObject(sql, T::class.java as Class<*>, args) as T
|
||||
|
||||
/**
|
||||
* Extension for [JdbcOperations.queryForList] providing a `queryForList<Foo>("...")` variant.
|
||||
@@ -63,8 +63,9 @@ inline fun <reified T : Any> JdbcOperations.queryForObject(sql: String, vararg a
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String): List<T?> =
|
||||
queryForList(sql, T::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T> JdbcOperations.queryForList(sql: String): List<T> =
|
||||
queryForList(sql, T::class.java) as List<T>
|
||||
|
||||
/**
|
||||
* Extension for [JdbcOperations.queryForList] providing a
|
||||
@@ -73,19 +74,21 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String): List<T?>
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Array<out Any?>,
|
||||
argTypes: IntArray): List<T?> =
|
||||
queryForList(sql, args, argTypes, T::class.java)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>,
|
||||
argTypes: IntArray): List<T> =
|
||||
queryForList(sql, args, argTypes, T::class.java) as List<T>
|
||||
|
||||
/**
|
||||
* Extension for [JdbcOperations.queryForList] providing a
|
||||
* `queryForList<Foo>("...", arg1, argN)` variant.
|
||||
* `queryForList<Foo>("...", arrayOf(arg1, argN))` variant.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 7.0
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, vararg args: Any?): List<T?> =
|
||||
queryForList(sql, T::class.java, *args)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
|
||||
queryForList(sql, T::class.java, args) as List<T>
|
||||
|
||||
|
||||
/**
|
||||
@@ -95,7 +98,7 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, vararg arg
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
fun <T> JdbcOperations.query(sql: String, vararg args: Any?,
|
||||
fun <T> JdbcOperations.query(sql: String, vararg args: Any,
|
||||
function: (ResultSet) -> T): T =
|
||||
query(sql, ResultSetExtractor { function(it) }, *args)
|
||||
|
||||
@@ -106,7 +109,7 @@ fun <T> JdbcOperations.query(sql: String, vararg args: Any?,
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
fun JdbcOperations.query(sql: String, vararg args: Any?, function: (ResultSet) -> Unit): Unit =
|
||||
fun JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet) -> Unit): Unit =
|
||||
query(sql, { function(it) }, *args)
|
||||
|
||||
/**
|
||||
@@ -116,5 +119,5 @@ fun JdbcOperations.query(sql: String, vararg args: Any?, function: (ResultSet) -
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
fun <T> JdbcOperations.query(sql: String, vararg args: Any?, function: (ResultSet, Int) -> T): List<T> =
|
||||
fun <T> JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): List<T> =
|
||||
query(sql, { rs, i -> function(rs, i) }, *args)
|
||||
|
||||
+24
-49
@@ -46,7 +46,7 @@ class JdbcOperationsExtensionsTests {
|
||||
@Test
|
||||
fun `queryForObject with nullable reified type parameters`() {
|
||||
every { template.queryForObject(sql, any<Class<Int>>()) } returns null
|
||||
assertThat(template.queryForObject<Int>(sql)).isNull()
|
||||
assertThat(template.queryForObject<Int?>(sql)).isNull()
|
||||
verify { template.queryForObject(sql, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@@ -65,17 +65,8 @@ class JdbcOperationsExtensionsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForObject with reified type parameters, non-null args array and argTypes`() {
|
||||
val args = arrayOf(3, 4)
|
||||
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
|
||||
every { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) } returns 2
|
||||
assertThat(template.queryForObject<Int>(sql, args, argTypes)).isEqualTo(2)
|
||||
verify { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForObject with reified type parameters, nullable args array and argTypes`() {
|
||||
val args = arrayOf(3, null)
|
||||
fun `queryForObject with reified type parameters and argTypes`() {
|
||||
val args = arrayOf(3)
|
||||
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
|
||||
every { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) } returns 2
|
||||
assertThat(template.queryForObject<Int>(sql, args, argTypes)).isEqualTo(2)
|
||||
@@ -87,22 +78,24 @@ class JdbcOperationsExtensionsTests {
|
||||
val args = arrayOf(3)
|
||||
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
|
||||
every { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) } returns null
|
||||
assertThat(template.queryForObject<Int>(sql, args, argTypes)).isNull()
|
||||
assertThat(template.queryForObject<Int?>(sql, args, argTypes)).isNull()
|
||||
verify { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForObject with reified type parameters and args`() {
|
||||
every { template.queryForObject(sql, any<Class<Int>>(), 3, null) } returns 2
|
||||
assertThat(template.queryForObject<Int>(sql, 3, null)).isEqualTo(2)
|
||||
verify { template.queryForObject(sql, any<Class<Int>>(), 3, null) }
|
||||
val args = arrayOf(3, 4)
|
||||
every { template.queryForObject(sql, any<Class<Int>>(), args) } returns 2
|
||||
assertThat(template.queryForObject<Int>(sql, args)).isEqualTo(2)
|
||||
verify { template.queryForObject(sql, any<Class<Int>>(), args) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForObject with nullable reified type parameters and args`() {
|
||||
every { template.queryForObject(sql, any<Class<Int>>(), 3, 4) } returns null
|
||||
assertThat(template.queryForObject<Int>(sql, 3, 4)).isNull()
|
||||
verify { template.queryForObject(sql, any<Class<Int>>(), 3, 4) }
|
||||
val args = arrayOf(3, 4)
|
||||
every { template.queryForObject(sql, any<Class<Int>>(), args) } returns null
|
||||
assertThat(template.queryForObject<Int?>(sql, args)).isNull()
|
||||
verify { template.queryForObject(sql, any<Class<Int>>(), args) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,12 +110,12 @@ class JdbcOperationsExtensionsTests {
|
||||
fun `queryForList with nullable reified type parameters`() {
|
||||
val list = listOf(1, null, 3)
|
||||
every { template.queryForList(sql, any<Class<Int>>()) } returns list
|
||||
assertThat(template.queryForList<Int>(sql)).isEqualTo(list)
|
||||
assertThat(template.queryForList<Int?>(sql)).isEqualTo(list)
|
||||
verify { template.queryForList(sql, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForList with reified type parameters, non-null args and argTypes`() {
|
||||
fun `queryForList with reified type parameters and argTypes`() {
|
||||
val list = listOf(1, 2, 3)
|
||||
val args = arrayOf(3)
|
||||
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
|
||||
@@ -132,49 +125,31 @@ class JdbcOperationsExtensionsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForList with reified type parameters, nullable args and argTypes`() {
|
||||
val list = listOf(1, 2, 3)
|
||||
val args = arrayOf("foo", null)
|
||||
val argTypes = intArrayOf(JDBCType.VARCHAR.vendorTypeNumber)
|
||||
every { template.queryForList(sql, args, argTypes, any<Class<Int>>()) } returns list
|
||||
assertThat(template.queryForList<Int>(sql, args, argTypes)).isEqualTo(list)
|
||||
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForList with nullable reified type parameters, non-null args and argTypes`() {
|
||||
fun `queryForList with nullable reified type parameters and argTypes`() {
|
||||
val list = listOf(1, null, 3)
|
||||
val args = arrayOf(3)
|
||||
val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber)
|
||||
every { template.queryForList(sql, args, argTypes, any<Class<Int>>()) } returns list
|
||||
assertThat(template.queryForList<Int>(sql, args, argTypes)).isEqualTo(list)
|
||||
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForList with nullable reified type parameters, nullable args and argTypes`() {
|
||||
val list = listOf(1, null, 3)
|
||||
val args = arrayOf("foo", null)
|
||||
val argTypes = intArrayOf(JDBCType.VARCHAR.vendorTypeNumber)
|
||||
every { template.queryForList(sql, args, argTypes, any<Class<Int>>()) } returns list
|
||||
assertThat(template.queryForList<Int>(sql, args, argTypes)).isEqualTo(list)
|
||||
assertThat(template.queryForList<Int?>(sql, args, argTypes)).isEqualTo(list)
|
||||
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForList with reified type parameters and args`() {
|
||||
val list = listOf(1, 2, 3)
|
||||
every { template.queryForList(sql, any<Class<Int>>(), 3, null) } returns list
|
||||
template.queryForList<Int>(sql, 3, null)
|
||||
verify { template.queryForList(sql, any<Class<Int>>(), 3, null) }
|
||||
val args = arrayOf(3, 4)
|
||||
every { template.queryForList(sql, any<Class<Int>>(), args) } returns list
|
||||
template.queryForList<Int>(sql, args)
|
||||
verify { template.queryForList(sql, any<Class<Int>>(), args) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `queryForList with nullable reified type parameters and args`() {
|
||||
val list = listOf(1, null, 3)
|
||||
every { template.queryForList(sql, any<Class<Int>>(), 3, null) } returns list
|
||||
template.queryForList<Int>(sql, 3, null)
|
||||
verify { template.queryForList(sql, any<Class<Int>>(), 3, null) }
|
||||
val args = arrayOf(3, 4)
|
||||
every { template.queryForList(sql, any<Class<Int>>(), args) } returns list
|
||||
template.queryForList<Int?>(sql, args)
|
||||
verify { template.queryForList(sql, any<Class<Int>>(), args) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+8
-4
@@ -105,10 +105,14 @@ class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeLocation.class)
|
||||
.withMemberCategories(MemberCategory.ACCESS_DECLARED_FIELDS)).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Car.class, "setId")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Car.class, "getId")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Car.class, "setModel")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Car.class, "getModel")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "setId")
|
||||
.invoke()).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "getId")
|
||||
.invoke()).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "setModel")
|
||||
.invoke()).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(Car.class, "getModel")
|
||||
.invoke()).accepts(hints);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+3
-18
@@ -153,7 +153,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
|
||||
* Returns {@link ExtensionContextScope#TEST_METHOD ExtensionContextScope.TEST_METHOD}.
|
||||
* <p>This can be effectively overridden by annotating a test class with
|
||||
* {@code @SpringExtensionConfig(useTestClassScopedExtensionContext = true)}.
|
||||
* See the {@linkplain SpringExtension class-level Javadoc} for further details.
|
||||
* See the {@link SpringExtension class-level Javadoc} for further details.
|
||||
* @since 7.0
|
||||
* @see SpringExtensionConfig#useTestClassScopedExtensionContext()
|
||||
*/
|
||||
@@ -334,10 +334,6 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
|
||||
* <li>The parameter is of type {@link ApplicationEvents} or a sub-type thereof.</li>
|
||||
* <li>{@link ParameterResolutionDelegate#isAutowirable} returns {@code true}.</li>
|
||||
* </ol>
|
||||
* <p>This method does not {@linkplain #getApplicationContext(ExtensionContext)
|
||||
* load} the {@code ApplicationContext} or verify that the application context
|
||||
* actually contains a matching candidate bean, since doing so would potentially
|
||||
* load an application context too early or unnecessarily.
|
||||
* <p><strong>WARNING</strong>: If a test class {@code Constructor} is annotated
|
||||
* with {@code @Autowired} or automatically autowirable (see
|
||||
* {@link org.springframework.test.context.TestConstructor @TestConstructor}),
|
||||
@@ -395,18 +391,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
|
||||
|
||||
|
||||
/**
|
||||
* Get the {@link ApplicationContext} associated with the supplied {@link ExtensionContext}.
|
||||
* <p><strong>NOTE</strong>: As of Spring Framework 7.0, the supplied
|
||||
* {@code ExtensionContext} may not be properly <em>scoped</em>. See the
|
||||
* {@linkplain SpringExtension class-level Javadoc} for further details.
|
||||
* <p><strong>WARNING</strong>: Invoking this method ensures that the
|
||||
* corresponding {@code ApplicationContext} is
|
||||
* {@linkplain org.springframework.test.context.TestContext#getApplicationContext()
|
||||
* loaded}. Consequently, this method should not be used if eager loading of
|
||||
* the application context is undesired. For example,
|
||||
* {@link #supportsParameter(ParameterContext, ExtensionContext)} intentionally
|
||||
* does not invoke this method, since doing so would potentially load an
|
||||
* application context too early or unnecessarily.
|
||||
* Get the {@link ApplicationContext} associated with the supplied {@code ExtensionContext}.
|
||||
* @param context the current {@code ExtensionContext} (never {@code null})
|
||||
* @return the application context
|
||||
* @throws IllegalStateException if an error occurs while retrieving the application context
|
||||
@@ -417,7 +402,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link TestContextManager} associated with the supplied {@link ExtensionContext}.
|
||||
* Get the {@link TestContextManager} associated with the supplied {@code ExtensionContext}.
|
||||
* @return the {@code TestContextManager} (never {@code null})
|
||||
*/
|
||||
static TestContextManager getTestContextManager(ExtensionContext context) {
|
||||
|
||||
+3
-42
@@ -28,13 +28,9 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.TypeRef;
|
||||
import com.jayway.jsonpath.spi.mapper.MappingProvider;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
@@ -148,10 +144,9 @@ class DefaultRestTestClient implements RestTestClient {
|
||||
client.mutate()
|
||||
.configureMessageConverters(convertersBuilder -> {
|
||||
HttpMessageConverters converters = convertersBuilder.build();
|
||||
if (converters.isEmpty()) {
|
||||
converters = HttpMessageConverters.forClient().registerDefaults().build();
|
||||
if (converters.iterator().hasNext()) {
|
||||
this.converter = JsonConverterDelegate.of(converters);
|
||||
}
|
||||
this.converter = JsonConverterDelegate.of(converters);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
@@ -490,8 +485,7 @@ class DefaultRestTestClient implements RestTestClient {
|
||||
|
||||
@Override
|
||||
public JsonPathAssertions jsonPath(String expression) {
|
||||
Configuration config = JsonPathConfigurationProvider.getConfiguration(this.result);
|
||||
return new JsonPathAssertions(this, getBodyAsString(), expression, config);
|
||||
return new JsonPathAssertions(this, getBodyAsString(), expression, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -545,37 +539,4 @@ class DefaultRestTestClient implements RestTestClient {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class JsonPathConfigurationProvider {
|
||||
|
||||
static Configuration getConfiguration(EntityExchangeResult<?> result) {
|
||||
Configuration config = Configuration.defaultConfiguration();
|
||||
JsonConverterDelegate delegate = result.getJsonConverterDelegate();
|
||||
return (delegate != null ? config.mappingProvider(new MessageConverterMappingProvider(delegate)) : config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private record MessageConverterMappingProvider(JsonConverterDelegate delegate) implements MappingProvider {
|
||||
|
||||
@Override
|
||||
public <T> T map(Object value, Class<T> targetType, Configuration configuration) {
|
||||
return mapToTargetType(value, ResolvableType.forClass(targetType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T map(Object value, TypeRef<T> targetType, Configuration configuration) {
|
||||
return mapToTargetType(value, ResolvableType.forType(targetType.getType()));
|
||||
}
|
||||
|
||||
private <T> T mapToTargetType(Object value, ResolvableType targetType) {
|
||||
try {
|
||||
return delegate().map(value, targetType);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to map " + value + " to " + targetType, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-14
@@ -23,7 +23,6 @@ import java.util.Map;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.Person;
|
||||
@@ -107,19 +106,6 @@ class JsonPathAssertionTests {
|
||||
.jsonPath("$.performers[1].name").value(v -> MatcherAssert.assertThat(v, equalTo("Yehudi Menuhin")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueConsumer() {
|
||||
client.get().uri("/music/people")
|
||||
.exchange()
|
||||
.expectBody()
|
||||
.jsonPath("$.composers[0].name").value(
|
||||
String.class,
|
||||
name -> assertThat(name).isEqualTo("Johann Sebastian Bach"))
|
||||
.jsonPath("$.composers[0].name").value(
|
||||
ParameterizedTypeReference.forType(String.class),
|
||||
name -> assertThat(name).isEqualTo("Johann Sebastian Bach"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hamcrestMatcher() {
|
||||
client.get().uri("/music/people")
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.Aware;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -163,8 +163,8 @@ public abstract class AbstractFallbackTransactionAttributeSource
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
// Skip setBeanFactory method on BeanFactoryAware.
|
||||
if (method.getDeclaringClass() == BeanFactoryAware.class) {
|
||||
// Skip methods declared on BeanFactoryAware and co.
|
||||
if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+5
-23
@@ -32,7 +32,6 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.testfixture.io.SerializationTestUtils;
|
||||
@@ -60,7 +59,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
|
||||
private final AnnotationTransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
|
||||
|
||||
|
||||
@Test
|
||||
void serializable() throws Exception {
|
||||
TestBean1 tb = new TestBean1();
|
||||
@@ -125,10 +123,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
void transactionAttributeDeclaredOnInterfaceMethodOnly() {
|
||||
TransactionAttribute actual = getTransactionAttribute(TestBean2.class, ITestBean2.class, "getAge");
|
||||
assertThat(actual).satisfies(hasNoRollbackRule());
|
||||
actual = getTransactionAttribute(TestBean2.class, ITestBean2X.class, "getAge");
|
||||
assertThat(actual).satisfies(hasNoRollbackRule());
|
||||
actual = getTransactionAttribute(ITestBean2X.class, ITestBean2X.class, "getAge");
|
||||
assertThat(actual).satisfies(hasNoRollbackRule());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,7 +249,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
assertThat(actual.isReadOnly()).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class JtaAttributeTests {
|
||||
|
||||
@@ -283,7 +276,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
assertThat(getNameAttr.getPropagationBehavior()).isEqualTo(TransactionAttribute.PROPAGATION_SUPPORTS);
|
||||
}
|
||||
|
||||
|
||||
static class JtaAnnotatedBean1 implements ITestBean1 {
|
||||
|
||||
private String name;
|
||||
@@ -313,6 +305,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@jakarta.transaction.Transactional(jakarta.transaction.Transactional.TxType.SUPPORTS)
|
||||
static class JtaAnnotatedBean2 implements ITestBean1 {
|
||||
|
||||
@@ -369,6 +362,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@jakarta.transaction.Transactional(jakarta.transaction.Transactional.TxType.SUPPORTS)
|
||||
interface ITestJta {
|
||||
|
||||
@@ -381,8 +375,8 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
|
||||
void setName(String name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Ejb3AttributeTests {
|
||||
@@ -454,6 +448,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@jakarta.ejb.TransactionAttribute(TransactionAttributeType.SUPPORTS)
|
||||
static class Ejb3AnnotatedBean2 implements ITestBean1 {
|
||||
|
||||
@@ -511,7 +506,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class GroovyTests {
|
||||
|
||||
@@ -525,7 +519,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
assertThat(attributeSource.getTransactionAttribute(getMetaClassMethod, GroovyTestBean.class)).isNull();
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
static class GroovyTestBean implements ITestBean1, GroovyObject {
|
||||
|
||||
@@ -578,7 +571,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Consumer<TransactionAttribute> hasRollbackRules(RollbackRuleAttribute... rollbackRuleAttributes) {
|
||||
return transactionAttribute -> {
|
||||
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
|
||||
@@ -634,12 +626,7 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
|
||||
|
||||
interface ITestBean2X extends ITestBean2, BeanNameAware {
|
||||
|
||||
@Transactional
|
||||
int getAge();
|
||||
|
||||
void setAge(int age);
|
||||
interface ITestBean2X extends ITestBean2 {
|
||||
|
||||
String getName();
|
||||
|
||||
@@ -748,10 +735,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -934,7 +917,6 @@ class AnnotationTransactionAttributeSourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Transactional(label = {"retryable", "long-running"})
|
||||
static class TestBean11 {
|
||||
|
||||
|
||||
-7
@@ -57,17 +57,10 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
|
||||
|
||||
private final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
|
||||
DefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.messageConverters.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<HttpMessageConverter<?>> iterator() {
|
||||
return this.messageConverters.iterator();
|
||||
|
||||
@@ -32,11 +32,6 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>> {
|
||||
|
||||
/**
|
||||
* Return true if this instance does not contain any message converters.
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Create a builder instance, tailored for HTTP client usage.
|
||||
* <p>The following HTTP message converters can be detected and registered if available, in order:
|
||||
|
||||
+8
-11
@@ -25,11 +25,12 @@ import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
|
||||
/**
|
||||
* Fatal binding exception, thrown when we want to treat binding exceptions as
|
||||
* unrecoverable.
|
||||
* Fatal binding exception, thrown when we want to
|
||||
* treat binding exceptions as unrecoverable.
|
||||
*
|
||||
* <p>Extends {@link ServletException} for convenient throwing in any Servlet
|
||||
* resource (such as a Filter).
|
||||
* <p>Extends ServletException for convenient throwing in any Servlet resource
|
||||
* (such as a Filter), and NestedServletException for proper root cause handling
|
||||
* (as the plain ServletException doesn't expose its root cause at all).
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
@@ -37,12 +38,12 @@ import org.springframework.web.ErrorResponse;
|
||||
@SuppressWarnings("serial")
|
||||
public class ServletRequestBindingException extends ServletException implements ErrorResponse {
|
||||
|
||||
private final ProblemDetail body = ProblemDetail.forStatus(getStatusCode());
|
||||
|
||||
private final String messageDetailCode;
|
||||
|
||||
private final Object @Nullable [] messageDetailArguments;
|
||||
|
||||
private @Nullable ProblemDetail body;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor with a message only.
|
||||
@@ -94,7 +95,6 @@ public class ServletRequestBindingException extends ServletException implements
|
||||
this.messageDetailArguments = messageDetailArguments;
|
||||
}
|
||||
|
||||
|
||||
private String initMessageDetailCode(@Nullable String messageDetailCode) {
|
||||
return (messageDetailCode != null ?
|
||||
messageDetailCode : ErrorResponse.getDefaultDetailMessageCode(getClass(), null));
|
||||
@@ -107,10 +107,7 @@ public class ServletRequestBindingException extends ServletException implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ProblemDetail getBody() {
|
||||
if (this.body == null) {
|
||||
this.body = ProblemDetail.forStatus(getStatusCode());
|
||||
}
|
||||
public ProblemDetail getBody() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -124,10 +124,7 @@ class HtmlCharacterEntityDecoder {
|
||||
int value = (!isHexNumberedReference ?
|
||||
Integer.parseInt(getReferenceSubstring(2)) :
|
||||
Integer.parseInt(getReferenceSubstring(3), 16));
|
||||
if (value > Character.MAX_CODE_POINT) {
|
||||
return false;
|
||||
}
|
||||
this.decodedMessage.appendCodePoint(value);
|
||||
this.decodedMessage.append((char) value);
|
||||
return true;
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
|
||||
+24
-22
@@ -21,7 +21,9 @@ import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.http.RequestEntity
|
||||
import org.springframework.http.ResponseEntity
|
||||
import java.lang.Class
|
||||
import java.net.URI
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.getForObject] providing a `getForObject<Foo>(...)`
|
||||
@@ -34,8 +36,8 @@ import java.net.URI
|
||||
* @since 5.0
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.getForObject(url: String, vararg uriVariables: Any?): T? =
|
||||
getForObject(url, T::class.java, *uriVariables)
|
||||
inline fun <reified T> RestOperations.getForObject(url: String, vararg uriVariables: Any?): T =
|
||||
getForObject(url, T::class.java as Class<*>, *uriVariables) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.getForObject] providing a `getForObject<Foo>(...)`
|
||||
@@ -48,8 +50,8 @@ inline fun <reified T : Any> RestOperations.getForObject(url: String, vararg uri
|
||||
* @since 5.0
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.getForObject(url: String, uriVariables: Map<String, Any?>): T? =
|
||||
getForObject(url, T::class.java, uriVariables)
|
||||
inline fun <reified T> RestOperations.getForObject(url: String, uriVariables: Map<String, Any?>): T =
|
||||
getForObject(url, T::class.java as Class<*>, uriVariables) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.getForObject] providing a `getForObject<Foo>(...)`
|
||||
@@ -62,8 +64,8 @@ inline fun <reified T : Any> RestOperations.getForObject(url: String, uriVariabl
|
||||
* @since 5.0
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.getForObject(url: URI): T? =
|
||||
getForObject(url, T::class.java)
|
||||
inline fun <reified T> RestOperations.getForObject(url: URI): T =
|
||||
getForObject(url, T::class.java as Class<*>) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.getForEntity] providing a `getForEntity<Foo>(...)`
|
||||
@@ -116,9 +118,9 @@ inline fun <reified T: Any> RestOperations.getForEntity(url: String, uriVariable
|
||||
* @since 5.0.2
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.patchForObject(url: String, request: Any? = null,
|
||||
vararg uriVariables: Any?): T? =
|
||||
patchForObject(url, request, T::class.java, *uriVariables)
|
||||
inline fun <reified T> RestOperations.patchForObject(url: String, request: Any? = null,
|
||||
vararg uriVariables: Any?): T =
|
||||
patchForObject(url, request, T::class.java as Class<*>, *uriVariables) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.patchForObject] providing a `patchForObject<Foo>(...)`
|
||||
@@ -130,9 +132,9 @@ inline fun <reified T : Any> RestOperations.patchForObject(url: String, request:
|
||||
* @since 5.0.2
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.patchForObject(url: String, request: Any? = null,
|
||||
uriVariables: Map<String, *>): T? =
|
||||
patchForObject(url, request, T::class.java, uriVariables)
|
||||
inline fun <reified T> RestOperations.patchForObject(url: String, request: Any? = null,
|
||||
uriVariables: Map<String, *>): T =
|
||||
patchForObject(url, request, T::class.java as Class<*>, uriVariables) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.patchForObject] providing a `patchForObject<Foo>(...)`
|
||||
@@ -144,8 +146,8 @@ inline fun <reified T : Any> RestOperations.patchForObject(url: String, request:
|
||||
* @since 5.0.2
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.patchForObject(url: URI, request: Any? = null): T? =
|
||||
patchForObject(url, request, T::class.java)
|
||||
inline fun <reified T> RestOperations.patchForObject(url: URI, request: Any? = null): T =
|
||||
patchForObject(url, request, T::class.java as Class<*>) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
|
||||
@@ -158,9 +160,9 @@ inline fun <reified T : Any> RestOperations.patchForObject(url: URI, request: An
|
||||
* @since 5.0
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.postForObject(url: String, request: Any? = null,
|
||||
vararg uriVariables: Any?): T? =
|
||||
postForObject(url, request, T::class.java, *uriVariables)
|
||||
inline fun <reified T> RestOperations.postForObject(url: String, request: Any? = null,
|
||||
vararg uriVariables: Any?): T =
|
||||
postForObject(url, request, T::class.java as Class<*>, *uriVariables) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
|
||||
@@ -173,9 +175,9 @@ inline fun <reified T : Any> RestOperations.postForObject(url: String, request:
|
||||
* @since 5.0
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.postForObject(url: String, request: Any? = null,
|
||||
uriVariables: Map<String, *>): T? =
|
||||
postForObject(url, request, T::class.java, uriVariables)
|
||||
inline fun <reified T> RestOperations.postForObject(url: String, request: Any? = null,
|
||||
uriVariables: Map<String, *>): T =
|
||||
postForObject(url, request, T::class.java as Class<*>, uriVariables) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
|
||||
@@ -188,8 +190,8 @@ inline fun <reified T : Any> RestOperations.postForObject(url: String, request:
|
||||
* @since 5.0
|
||||
*/
|
||||
@Throws(RestClientException::class)
|
||||
inline fun <reified T : Any> RestOperations.postForObject(url: URI, request: Any? = null): T? =
|
||||
postForObject(url, request, T::class.java)
|
||||
inline fun <reified T> RestOperations.postForObject(url: URI, request: Any? = null): T =
|
||||
postForObject(url, request, T::class.java as Class<*>) as T
|
||||
|
||||
/**
|
||||
* Extension for [RestOperations.postForEntity] providing a `postForEntity<Foo>(...)`
|
||||
|
||||
@@ -78,6 +78,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpMediaTypeNotSupportedException() {
|
||||
|
||||
List<MediaType> mediaTypes =
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
|
||||
|
||||
@@ -95,6 +96,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpMediaTypeNotSupportedExceptionWithParseError() {
|
||||
|
||||
ErrorResponse ex = new HttpMediaTypeNotSupportedException(
|
||||
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
|
||||
|
||||
@@ -107,6 +109,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpMediaTypeNotAcceptableException() {
|
||||
|
||||
List<MediaType> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
|
||||
HttpMediaTypeNotAcceptableException ex = new HttpMediaTypeNotAcceptableException(mediaTypes);
|
||||
|
||||
@@ -120,6 +123,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpMediaTypeNotAcceptableExceptionWithParseError() {
|
||||
|
||||
ErrorResponse ex = new HttpMediaTypeNotAcceptableException(
|
||||
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
|
||||
|
||||
@@ -132,6 +136,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void asyncRequestTimeoutException() {
|
||||
|
||||
ErrorResponse ex = new AsyncRequestTimeoutException();
|
||||
assertDetailMessageCode(ex, null, null);
|
||||
|
||||
@@ -143,6 +148,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void httpRequestMethodNotSupportedException() {
|
||||
|
||||
HttpRequestMethodNotSupportedException ex =
|
||||
new HttpRequestMethodNotSupportedException("PUT", Arrays.asList("GET", "POST"));
|
||||
|
||||
@@ -156,6 +162,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingRequestHeaderException() {
|
||||
|
||||
MissingRequestHeaderException ex = new MissingRequestHeaderException("Authorization", this.methodParameter);
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
@@ -167,6 +174,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingServletRequestParameterException() {
|
||||
|
||||
MissingServletRequestParameterException ex = new MissingServletRequestParameterException("query", "String");
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
@@ -178,8 +186,10 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingMatrixVariableException() {
|
||||
|
||||
MissingMatrixVariableException ex = new MissingMatrixVariableException("region", this.methodParameter);
|
||||
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
assertDetail(ex, "Required path parameter 'region' is not present.");
|
||||
assertDetailMessageCode(ex, null, new Object[] {ex.getVariableName()});
|
||||
@@ -189,6 +199,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingPathVariableException() {
|
||||
|
||||
MissingPathVariableException ex = new MissingPathVariableException("id", this.methodParameter);
|
||||
|
||||
assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
@@ -198,19 +209,9 @@ class ErrorResponseExceptionTests {
|
||||
assertThat(ex.getHeaders().isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingPathVariableExceptionAfterConversion() {
|
||||
MissingPathVariableException ex = new MissingPathVariableException("id", this.methodParameter, true);
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
assertDetail(ex, "Required path variable 'id' is not present.");
|
||||
assertDetailMessageCode(ex, null, new Object[] {ex.getVariableName()});
|
||||
|
||||
assertThat(ex.getHeaders().isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequestCookieException() {
|
||||
|
||||
MissingRequestCookieException ex = new MissingRequestCookieException("oreo", this.methodParameter);
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
@@ -222,6 +223,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void unsatisfiedServletRequestParameterException() {
|
||||
|
||||
UnsatisfiedServletRequestParameterException ex = new UnsatisfiedServletRequestParameterException(
|
||||
new String[] { "foo=bar", "bar=baz" }, Collections.singletonMap("q", new String[] {"1"}));
|
||||
|
||||
@@ -234,6 +236,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingServletRequestPartException() {
|
||||
|
||||
MissingServletRequestPartException ex = new MissingServletRequestPartException("file");
|
||||
|
||||
assertStatus(ex, HttpStatus.BAD_REQUEST);
|
||||
@@ -245,6 +248,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void methodArgumentNotValidException() {
|
||||
|
||||
ValidationTestHelper testHelper = new ValidationTestHelper(MethodArgumentNotValidException.class);
|
||||
BindingResult result = testHelper.bindingResult();
|
||||
|
||||
@@ -276,6 +280,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void unsupportedMediaTypeStatusException() {
|
||||
|
||||
List<MediaType> mediaTypes =
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
|
||||
|
||||
@@ -293,6 +298,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void unsupportedMediaTypeStatusExceptionWithParseError() {
|
||||
|
||||
ErrorResponse ex = new UnsupportedMediaTypeStatusException(
|
||||
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
|
||||
|
||||
@@ -305,6 +311,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void notAcceptableStatusException() {
|
||||
|
||||
List<MediaType> mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_CBOR);
|
||||
NotAcceptableStatusException ex = new NotAcceptableStatusException(mediaTypes);
|
||||
|
||||
@@ -318,6 +325,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void notAcceptableStatusExceptionWithParseError() {
|
||||
|
||||
ErrorResponse ex = new NotAcceptableStatusException(
|
||||
"Could not parse Accept header: Invalid mime type \"foo\": does not contain '/'");
|
||||
|
||||
@@ -330,6 +338,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void serverErrorException() {
|
||||
|
||||
ServerErrorException ex = new ServerErrorException("Failure", null);
|
||||
|
||||
assertStatus(ex, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
@@ -341,6 +350,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void missingRequestValueException() {
|
||||
|
||||
MissingRequestValueException ex =
|
||||
new MissingRequestValueException("foo", String.class, "header", this.methodParameter);
|
||||
|
||||
@@ -353,6 +363,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void unsatisfiedRequestParameterException() {
|
||||
|
||||
UnsatisfiedRequestParameterException ex =
|
||||
new UnsatisfiedRequestParameterException(
|
||||
Arrays.asList("foo=bar", "bar=baz"),
|
||||
@@ -367,6 +378,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void webExchangeBindException() {
|
||||
|
||||
ValidationTestHelper testHelper = new ValidationTestHelper(WebExchangeBindException.class);
|
||||
BindingResult result = testHelper.bindingResult();
|
||||
|
||||
@@ -381,6 +393,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void methodNotAllowedException() {
|
||||
|
||||
List<HttpMethod> supportedMethods = Arrays.asList(HttpMethod.GET, HttpMethod.POST);
|
||||
MethodNotAllowedException ex = new MethodNotAllowedException(HttpMethod.PUT, supportedMethods);
|
||||
|
||||
@@ -394,6 +407,7 @@ class ErrorResponseExceptionTests {
|
||||
|
||||
@Test
|
||||
void methodNotAllowedExceptionWithoutSupportedMethods() {
|
||||
|
||||
MethodNotAllowedException ex = new MethodNotAllowedException(HttpMethod.PUT, Collections.emptyList());
|
||||
|
||||
assertStatus(ex, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
@@ -403,8 +417,9 @@ class ErrorResponseExceptionTests {
|
||||
assertThat(ex.getHeaders().isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-30300
|
||||
@Test // gh-30300
|
||||
void responseStatusException() {
|
||||
|
||||
Locale locale = Locale.UK;
|
||||
LocaleContextHolder.setLocale(locale);
|
||||
|
||||
@@ -504,6 +519,7 @@ class ErrorResponseExceptionTests {
|
||||
assertThat(BindErrorUtils.resolve(errors, this.messageSource, Locale.UK)).hasSize(4)
|
||||
.containsValues("Bean A message", "Bean B message", "name is required", "age is below minimum");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HtmlUtils}.
|
||||
*
|
||||
* @author Alef Arendsen
|
||||
* @author Martin Kersten
|
||||
* @author Rick Evans
|
||||
@@ -30,7 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class HtmlUtilsTests {
|
||||
|
||||
@Test
|
||||
void htmlEscape() {
|
||||
void testHtmlEscape() {
|
||||
String unescaped = "\"This is a quote'";
|
||||
String escaped = HtmlUtils.htmlEscape(unescaped);
|
||||
assertThat(escaped).isEqualTo(""This is a quote'");
|
||||
@@ -41,7 +39,14 @@ class HtmlUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlEscapeIntoHtmlCharacterSet() {
|
||||
void testHtmlUnescape() {
|
||||
String escaped = ""This is a quote'";
|
||||
String unescaped = HtmlUtils.htmlUnescape(escaped);
|
||||
assertThat(unescaped).isEqualTo("\"This is a quote'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEncodeIntoHtmlCharacterSet() {
|
||||
assertThat(HtmlUtils.htmlEscape("")).as("An empty string should be converted to an empty string").isEmpty();
|
||||
assertThat(HtmlUtils.htmlEscape("A sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("A sentence containing no special characters.");
|
||||
|
||||
@@ -55,11 +60,12 @@ class HtmlUtilsTests {
|
||||
assertThat(HtmlUtils.htmlEscapeDecimal("" + (char) 977)).as("The special character 977 should be encoded to 'ϑ'").isEqualTo("ϑ");
|
||||
}
|
||||
|
||||
@Test // SPR-9293
|
||||
void htmlEscapeIntoHtmlCharacterSetFromUtf8() {
|
||||
// SPR-9293
|
||||
@Test
|
||||
void testEncodeIntoHtmlCharacterSetFromUtf8() {
|
||||
String utf8 = ("UTF-8");
|
||||
|
||||
assertThat(HtmlUtils.htmlEscape("", utf8)).as("An empty string should be converted to an empty string").isEmpty();
|
||||
assertThat(HtmlUtils.htmlEscape("", utf8)).as("An empty string should be converted to an empty string")
|
||||
.isEmpty();
|
||||
assertThat(HtmlUtils.htmlEscape("A sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("A sentence containing no special characters.");
|
||||
|
||||
assertThat(HtmlUtils.htmlEscape("< >", utf8)).as("'< >' should be encoded to '< >'").isEqualTo("< >");
|
||||
@@ -69,38 +75,7 @@ class HtmlUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlUnescape() {
|
||||
String escaped = ""This is a quote'";
|
||||
String unescaped = HtmlUtils.htmlUnescape(escaped);
|
||||
assertThat(unescaped).isEqualTo("\"This is a quote'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlUnescapeHandlesSupplementaryCharactersAsDecimal() {
|
||||
String expectedCharacter = "😀";
|
||||
String decimalEntity = "😀";
|
||||
String actualResultFromDecimal = HtmlUtils.htmlUnescape(decimalEntity);
|
||||
assertThat(actualResultFromDecimal).as("Decimal entity was not converted correctly.").isEqualTo(expectedCharacter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlUnescapeHandlesSupplementaryCharactersAsHexadecimal() {
|
||||
String expectedCharacter = "😀";
|
||||
String hexEntity = "😀";
|
||||
String actualResultFromHex = HtmlUtils.htmlUnescape(hexEntity);
|
||||
assertThat(actualResultFromHex).as("Hexadecimal entity was not converted correctly.").isEqualTo(expectedCharacter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlUnescapeHandlesBasicEntities() {
|
||||
String input = "<p>Tom & Jerry's "Show"</p>";
|
||||
String expectedOutput = "<p>Tom & Jerry's \"Show\"</p>";
|
||||
String actualOutput = HtmlUtils.htmlUnescape(input);
|
||||
assertThat(actualOutput).as("Basic HTML entities were not unescaped correctly.").isEqualTo(expectedOutput);
|
||||
}
|
||||
|
||||
@Test
|
||||
void htmlUnescapeFromHtmlCharacterSet() {
|
||||
void testDecodeFromHtmlCharacterSet() {
|
||||
assertThat(HtmlUtils.htmlUnescape("")).as("An empty string should be converted to an empty string").isEmpty();
|
||||
assertThat(HtmlUtils.htmlUnescape("This is a sentence containing no special characters.")).as("A string containing no special characters should not be affected").isEqualTo("This is a sentence containing no special characters.");
|
||||
|
||||
|
||||
+2
-1
@@ -270,6 +270,7 @@ class RestOperationsExtensionsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("May require Kotlin 2") // TODO Enable after Kotlin 2 upgrade
|
||||
fun `RestOperations are available`() {
|
||||
val extensions = Class.forName("org.springframework.web.client.RestOperationsExtensionsKt")
|
||||
ReflectionUtils.doWithMethods(RestOperations::class.java) { method ->
|
||||
@@ -280,7 +281,7 @@ class RestOperationsExtensionsTests {
|
||||
assertThat(f.typeParameters.size).isEqualTo(1)
|
||||
val type = f.typeParameters[0].upperBounds.first()
|
||||
assertThat(type.classifier).isEqualTo(Any::class)
|
||||
assertThat(type.isMarkedNullable).isFalse()
|
||||
assertThat(type.isMarkedNullable).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,6 @@
|
||||
<suppress files="PatternParseException" checks="JavadocVariable"/>
|
||||
<suppress files="web[\\/]reactive[\\/]socket[\\/]CloseStatus" checks="JavadocStyle"/>
|
||||
<suppress files="RestClientResponseException" checks="MutableException"/>
|
||||
<suppress files="ServletRequestBindingException" checks="MutableException"/>
|
||||
|
||||
<!-- spring-webflux -->
|
||||
<suppress files="src[\\/]test[\\/]java[\\/]org[\\/]springframework[\\/]web[\\/]reactive[\\/]resource[\\/]GzipSupport" checks="IllegalImport" id="bannedJUnitJupiterImports"/>
|
||||
|
||||
Reference in New Issue
Block a user