pull/9712/head
Phillip Webb 7 years ago
parent 222a09cfd3
commit aa57ca7e18

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -69,8 +69,12 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
return this.timeToLive;
}
public void setTimeToLive(long ttl) {
this.timeToLive = ttl;
/**
* Set the time to live for cached results.
* @param timeToLive the time to live in milliseconds
*/
public void setTimeToLive(long timeToLive) {
this.timeToLive = timeToLive;
}
/**

@ -172,20 +172,13 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint
private Health getCurrentHealth() {
long accessTime = System.currentTimeMillis();
CachedHealth cachedHealth = this.cachedHealth;
if (isStale(cachedHealth, accessTime)) {
CachedHealth cached = this.cachedHealth;
if (cached == null || cached.isStale(accessTime, getDelegate().getTimeToLive())) {
Health health = getDelegate().invoke();
this.cachedHealth = new CachedHealth(health, accessTime);
return health;
}
return cachedHealth.health;
}
private boolean isStale(CachedHealth cachedHealth, long accessTime) {
if (cachedHealth == null) {
return true;
}
return (accessTime - cachedHealth.creationTime) >= getDelegate().getTimeToLive();
return cached.getHealth();
}
protected boolean exposeHealthDetails(HttpServletRequest request,
@ -241,6 +234,14 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint
this.creationTime = creationTime;
}
public boolean isStale(long accessTime, long timeToLive) {
return (accessTime - this.creationTime) >= timeToLive;
}
public Health getHealth() {
return this.health;
}
}
}

@ -107,8 +107,8 @@ public class OAuth2RestOperationsConfiguration {
private final AccessTokenRequest accessTokenRequest;
public ClientContextConfiguration(@Qualifier("accessTokenRequest")
ObjectProvider<AccessTokenRequest> accessTokenRequest) {
public ClientContextConfiguration(
@Qualifier("accessTokenRequest") ObjectProvider<AccessTokenRequest> accessTokenRequest) {
this.accessTokenRequest = accessTokenRequest.getIfAvailable();
}

@ -93,7 +93,7 @@ public class ResourceProperties implements ResourceLoaderAware {
String[] normalized = new String[staticLocations.length];
for (int i = 0; i < staticLocations.length; i++) {
String location = staticLocations[i];
normalized[i] = location.endsWith("/") ? location : location + "/";
normalized[i] = (location.endsWith("/") ? location : location + "/");
}
return normalized;
}

@ -76,7 +76,8 @@ public class Neo4jPropertiesTests {
Neo4jProperties properties = load(true,
"spring.data.neo4j.uri=https://localhost:7474");
Configuration configuration = properties.createConfiguration();
assertDriver(configuration, Neo4jProperties.HTTP_DRIVER, "https://localhost:7474");
assertDriver(configuration, Neo4jProperties.HTTP_DRIVER,
"https://localhost:7474");
}
@Test

@ -47,13 +47,18 @@ public class OAuth2RestOperationsConfigurationTests {
public void clientIdConditionMatches() throws Exception {
EnvironmentTestUtils.addEnvironment(this.environment,
"security.oauth2.client.client-id=acme");
this.context = new SpringApplicationBuilder(OAuth2RestOperationsConfiguration.class).environment(this.environment).web(false).run();
assertThat(this.context.getBean(OAuth2RestOperationsConfiguration.class)).isNotNull();
this.context = new SpringApplicationBuilder(
OAuth2RestOperationsConfiguration.class).environment(this.environment)
.web(false).run();
assertThat(this.context.getBean(OAuth2RestOperationsConfiguration.class))
.isNotNull();
}
@Test
public void clientIdConditionDoesNotMatch() throws Exception {
this.context = new SpringApplicationBuilder(OAuth2RestOperationsConfiguration.class).environment(this.environment).web(false).run();
this.context = new SpringApplicationBuilder(
OAuth2RestOperationsConfiguration.class).environment(this.environment)
.web(false).run();
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(OAuth2RestOperationsConfiguration.class);
}

@ -63,9 +63,8 @@ public class ResourcePropertiesTests {
@Test
public void customStaticLocationsAreNormalizedToEndWithTrailingSlash() {
this.properties.setStaticLocations(new String[] { "/foo", "/bar", "/baz/" });
assertThat(this.properties.getStaticLocations()).containsExactly("/foo/", "/bar/",
"/baz/");
String[] actual = this.properties.getStaticLocations();
assertThat(actual).containsExactly("/foo/", "/bar/", "/baz/");
}
}

@ -22,7 +22,6 @@ import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
@ -208,21 +207,27 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
public ResourcePatternResolver getResourcePatternResolver(
ApplicationContext applicationContext, ResourceLoader resourceLoader) {
return new PathMatchingResourcePatternResolver(resourceLoader == null
? createResourceLoader(applicationContext) : resourceLoader);
if (resourceLoader == null) {
resourceLoader = new DefaultResourceLoader();
copyProtocolResolvers(applicationContext, resourceLoader);
}
return new PathMatchingResourcePatternResolver(resourceLoader);
}
private ResourceLoader createResourceLoader(
ApplicationContext applicationContext) {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
if (applicationContext instanceof DefaultResourceLoader) {
Collection<ProtocolResolver> protocolResolvers = ((DefaultResourceLoader) applicationContext)
.getProtocolResolvers();
for (ProtocolResolver protocolResolver : protocolResolvers) {
resourceLoader.addProtocolResolver(protocolResolver);
}
protected final void copyProtocolResolvers(ApplicationContext applicationContext,
ResourceLoader resourceLoader) {
if (applicationContext instanceof DefaultResourceLoader
&& resourceLoader instanceof DefaultResourceLoader) {
copyProtocolResolvers((DefaultResourceLoader) applicationContext,
(DefaultResourceLoader) resourceLoader);
}
}
protected final void copyProtocolResolvers(DefaultResourceLoader source,
DefaultResourceLoader destination) {
for (ProtocolResolver resolver : source.getProtocolResolvers()) {
destination.addProtocolResolver(resolver);
}
return resourceLoader;
}
}
@ -238,25 +243,21 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
public ResourcePatternResolver getResourcePatternResolver(
ApplicationContext applicationContext, ResourceLoader resourceLoader) {
if (applicationContext instanceof WebApplicationContext) {
return new ServletContextResourcePatternResolver(resourceLoader == null
? createResourceLoader((WebApplicationContext) applicationContext)
: resourceLoader);
return getResourcePatternResolver(
(WebApplicationContext) applicationContext, resourceLoader);
}
return super.getResourcePatternResolver(applicationContext, resourceLoader);
}
private ResourceLoader createResourceLoader(
WebApplicationContext applicationContext) {
WebApplicationContextResourceLoader resourceLoader = new WebApplicationContextResourceLoader(
applicationContext);
if (applicationContext instanceof DefaultResourceLoader) {
Collection<ProtocolResolver> protocolResolvers = ((DefaultResourceLoader) applicationContext)
.getProtocolResolvers();
for (ProtocolResolver protocolResolver : protocolResolvers) {
resourceLoader.addProtocolResolver(protocolResolver);
}
private ResourcePatternResolver getResourcePatternResolver(
WebApplicationContext applicationContext, ResourceLoader resourceLoader) {
if (resourceLoader == null) {
resourceLoader = new WebApplicationContextResourceLoader(
applicationContext);
copyProtocolResolvers(applicationContext, resourceLoader);
}
return resourceLoader;
return new ServletContextResourcePatternResolver(resourceLoader);
}
}

@ -440,65 +440,64 @@ Maven build to run the app.
[[cloud-deployment-gae]]
=== Google Cloud
Google Cloud has several options that could be used to launch Spring Boot applications. The
easiest to get started with is probably App Engine, but you could also find ways to run
Spring Boot in a container with Container Engine, or on a virtual machine using Compute Engine.
To run in App Engine you can create a project in the UI first, which
sets up a unique identifier for you and also HTTP routes. Add a Java
app to the project and leave it empty, then use the
https://cloud.google.com/sdk/downloads[Google Cloud SDK] to push your
Google Cloud has several options that could be used to launch Spring Boot applications.
The easiest to get started with is probably App Engine, but you could also find ways to
run Spring Boot in a container with Container Engine, or on a virtual machine using
Compute Engine.
To run in App Engine you can create a project in the UI first, which sets up a unique
identifier for you and also HTTP routes. Add a Java app to the project and leave it empty,
then use the https://cloud.google.com/sdk/downloads[Google Cloud SDK] to push your
Spring Boot app into that slot from the command line or CI build.
App Engine needs you to create an `app.yaml` file to describe the
resources your app requires. Normally you put this in
`src/min/appengine`, and it looks something like this:
App Engine needs you to create an `app.yaml` file to describe the resources your app
requires. Normally you put this in `src/min/appengine`, and it looks something like this:
[source,yaml,indent=0]
----
service: default
service: default
runtime: java
env: flex
runtime: java
env: flex
runtime_config:
jdk: openjdk8
runtime_config:
jdk: openjdk8
handlers:
- url: /.*
script: this field is required, but ignored
handlers:
- url: /.*
script: this field is required, but ignored
manual_scaling:
instances: 1
manual_scaling:
instances: 1
health_check:
enable_health_check: False
health_check:
enable_health_check: False
env_variables:
ENCRYPT_KEY: your_encryption_key_here
env_variables:
ENCRYPT_KEY: your_encryption_key_here
----
You can deploy the app, for example, with a Maven plugin by simply
adding the project ID to the build configuration:
You can deploy the app, for example, with a Maven plugin by simply adding the project ID
to the build configuration:
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
----
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>1.3.0</version>
<configuration>
<project>myproject</project>
</configuration>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>1.3.0</version>
<configuration>
<project>myproject</project>
</configuration>
</plugin>
----
Then deploy with `mvn appengine:deploy` (if you need to authenticate first the build will fail).
Then deploy with `mvn appengine:deploy` (if you need to authenticate first the build will
fail).
NOTE: Google App Engine Classic is tied to the Servlet 2.5 API, so you can't deploy a Spring Application
there without some modifications. See the <<howto.adoc#howto-servlet-2-5, Servlet 2.5 section>>
of this guide.
NOTE: Google App Engine Classic is tied to the Servlet 2.5 API, so you can't deploy a
Spring Application there without some modifications. See the
<<howto.adoc#howto-servlet-2-5, Servlet 2.5 section>> of this guide.

@ -137,6 +137,7 @@ key defined via `@PropertySource` will be loaded too late to have any effect on
auto-configuration.
[[howto-build-an-application-context-hierarchy]]
=== Build an ApplicationContext hierarchy (adding a parent or root context)
You can use the `ApplicationBuilder` class to create parent/child `ApplicationContext`

@ -283,10 +283,12 @@ returned, and for an authenticated connection additional details are also displa
Health information is collected from all
{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] beans defined
in your `ApplicationContext`. Spring Boot includes a number of auto-configured
`HealthIndicators` and you can also write your own. By default, the final system state is derived
by the `HealthAggregator` which sorts the statuses from each `HealthIndicator` based on an ordered list of statuses.
The first status in the sorted list is used as the overall health status.
If no `HealthIndicator` returns a status that is known to the `HealthAggregator`, an `UNKNOWN` status is used.
`HealthIndicators` and you can also write your own. By default, the final system state is
derived by the `HealthAggregator` which sorts the statuses from each `HealthIndicator`
based on an ordered list of statuses. The first status in the sorted list is used as the
overall health status. If no `HealthIndicator` returns a status that is known to the
`HealthAggregator`, an `UNKNOWN` status is used.
=== Security with HealthIndicators

@ -2474,8 +2474,8 @@ and `AuthenticationManagerConfiguration` for authentication configuration which
relevant in non-web applications). To switch off the default web application security
configuration completely you can add a bean with `@EnableWebSecurity` (this does not
disable the authentication manager configuration or Actuator's security).
To customize it you normally use external properties and beans of type `WebSecurityConfigurerAdapter`
(e.g. to add form-based login).
To customize it you normally use external properties and beans of type
`WebSecurityConfigurerAdapter` (e.g. to add form-based login).
NOTE: If you add `@EnableWebSecurity` and also disable Actuator security, you will get
the default form-based login for the entire application unless you add a custom
@ -6027,7 +6027,7 @@ where one will be auto-configured for you.
[source,java,indent=0]
----
EnvironmentTestUtils.addEnvironment(env, "org=Spring", "name=Boot");
EnvironmentTestUtils.addEnvironment(env, "org=Spring", "name=Boot");
----
@ -6040,25 +6040,25 @@ for assertions:
[source,java,indent=0]
----
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.rule.OutputCapture;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.rule.OutputCapture;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class MyTest {
public class MyTest {
@Rule
public OutputCapture capture = new OutputCapture();
@Rule
public OutputCapture capture = new OutputCapture();
@Test
public void testName() throws Exception {
System.out.println("Hello World!");
assertThat(capture.toString(), containsString("World"));
}
@Test
public void testName() throws Exception {
System.out.println("Hello World!");
assertThat(capture.toString(), containsString("World"));
}
}
}
----
[[boot-features-rest-templates-test-utility]]
@ -6079,17 +6079,17 @@ features will be enabled:
[source,java,indent=0]
----
public class MyTest {
public class MyTest {
private TestRestTemplate template = new TestRestTemplate();
private TestRestTemplate template = new TestRestTemplate();
@Test
public void testRequest() throws Exception {
HttpHeaders headers = template.getForEntity("http://myhost.com/example", String.class).getHeaders();
assertThat(headers.getLocation().toString(), containsString("myotherhost"));
}
@Test
public void testRequest() throws Exception {
HttpHeaders headers = template.getForEntity("http://myhost.com/example", String.class).getHeaders();
assertThat(headers.getLocation().toString(), containsString("myotherhost"));
}
}
}
----
Alternatively, if you are using the `@SpringBootTest` annotation with
@ -6100,32 +6100,32 @@ specify a host and port will automatically connect to the embedded server:
[source,java,indent=0]
----
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {
@Autowired
private TestRestTemplate template;
@Autowired
private TestRestTemplate template;
@Test
public void testRequest() throws Exception {
HttpHeaders headers = template.getForEntity("/example", String.class).getHeaders();
assertThat(headers.getLocation().toString(), containsString("myotherhost"));
}
@Test
public void testRequest() throws Exception {
HttpHeaders headers = template.getForEntity("/example", String.class).getHeaders();
assertThat(headers.getLocation().toString(), containsString("myotherhost"));
}
@TestConfiguration
static class Config {
@TestConfiguration
static class Config {
@Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.additionalMessageConverters(...)
.customizers(...);
}
@Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.additionalMessageConverters(...)
.customizers(...);
}
}
}
----

@ -32,11 +32,9 @@ import org.springframework.core.io.Resource;
* @author Stephane Nicoll
*/
// tag::example[]
public class EnvironmentPostProcessorExample
implements EnvironmentPostProcessor {
public class EnvironmentPostProcessorExample implements EnvironmentPostProcessor {
private final YamlPropertySourceLoader loader
= new YamlPropertySourceLoader();
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
@ -48,15 +46,14 @@ public class EnvironmentPostProcessorExample
private PropertySource<?> loadYaml(Resource path) {
if (!path.exists()) {
throw new IllegalArgumentException("Resource " + path
+ " does not exist");
throw new IllegalArgumentException("Resource " + path + " does not exist");
}
try {
return this.loader.load("custom-resource", path, null);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to load yaml configuration "
+ "from " + path, ex);
throw new IllegalStateException(
"Failed to load yaml configuration from " + path, ex);
}
}

@ -35,8 +35,8 @@ public class EnvironmentPostProcessorExampleTests {
@Test
public void applyEnvironmentPostProcessor() {
assertThat(this.environment.containsProperty("test.foo.bar")).isFalse();
new EnvironmentPostProcessorExample().postProcessEnvironment(
this.environment, new SpringApplication());
new EnvironmentPostProcessorExample().postProcessEnvironment(this.environment,
new SpringApplication());
assertThat(this.environment.containsProperty("test.foo.bar")).isTrue();
assertThat(this.environment.getProperty("test.foo.bar")).isEqualTo("value");
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

@ -29,6 +29,8 @@ import org.springframework.boot.test.json.JacksonTester;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link SpringBootTest} with {@link AutoConfigureJsonTesters}.
*
@ -41,17 +43,19 @@ import org.springframework.test.context.junit4.SpringRunner;
public class SpringBootTestWithAutoConfigureJsonTestersTests {
@Autowired
BasicJsonTester basicJson;
private BasicJsonTester basicJson;
@Autowired
JacksonTester<ExampleBasicObject> jacksonTester;
private JacksonTester<ExampleBasicObject> jacksonTester;
@Autowired
GsonTester<ExampleBasicObject> gsonTester;
private GsonTester<ExampleBasicObject> gsonTester;
@Test
public void contextLoads() {
assertThat(this.basicJson).isNotNull();
assertThat(this.jacksonTester).isNotNull();
assertThat(this.gsonTester).isNotNull();
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

@ -187,11 +187,11 @@ final class JarURLConnection extends java.net.JarURLConnection {
@Override
public int getContentLength() {
long longContentLength = getContentLengthLong();
if (longContentLength > Integer.MAX_VALUE) {
long length = getContentLengthLong();
if (length > Integer.MAX_VALUE) {
return -1;
}
return (int) longContentLength;
return (int) length;
}
@Override

@ -151,8 +151,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
/**
* Make a fully executable jar for *nix machines by prepending a launch script to the
* jar.
* <br/>
* jar. <br/>
* Currently, some tools do not accept this format so you may not always be able to
* use this technique. For example, <code>jar -xf</code> may silently fail to extract
* a jar or war that has been made fully-executable. It is recommended that you only

@ -65,7 +65,6 @@ final class EnvironmentConverter {
* environment is already a {@code StandardEnvironment} and is not a
* {@link ConfigurableWebEnvironment} no conversion is performed and it is returned
* unchanged.
*
* @param environment The Environment to convert
* @return The converted Environment
*/

@ -101,8 +101,8 @@ import org.springframework.validation.BindException;
* @author Andy Wilkinson
* @author Eddú Meléndez
*/
public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
SmartApplicationListener, Ordered {
public class ConfigFileApplicationListener
implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties";
@ -153,8 +153,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType) ||
ApplicationPreparedEvent.class.isAssignableFrom(eventType);
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
|| ApplicationPreparedEvent.class.isAssignableFrom(eventType);
}
@Override

@ -321,7 +321,6 @@ public class UndertowEmbeddedServletContainerFactory
keyManagerFactory.getKeyManagers());
}
return keyManagerFactory.getKeyManagers();
}
catch (Exception ex) {
throw new IllegalStateException(ex);

@ -420,8 +420,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
if (AnnotatedElementUtils.hasAnnotation(type, Validated.class)) {
return true;
}
if (type.getPackage() != null &&
type.getPackage().getName().startsWith("org.springframework.boot")) {
if (type.getPackage() != null && type.getPackage().getName()
.startsWith("org.springframework.boot")) {
return false;
}
if (getConstraintsForClass(type).isBeanConstrained()) {

@ -346,7 +346,7 @@ public class LoggingApplicationListener implements GenericApplicationListener {
String name = entry.getKey();
if (name.equalsIgnoreCase(LoggingSystem.ROOT_LOGGER_NAME)) {
if (rootProcessed) {
return;
continue;
}
name = null;
rootProcessed = true;

@ -257,8 +257,11 @@ public class UndertowEmbeddedServletContainerFactoryTests
UndertowEmbeddedServletContainerFactory factory = getFactory();
Ssl ssl = getSsl(null, "password", "src/test/resources/test.jks");
factory.setSsl(ssl);
KeyManager[] keyManagers = ReflectionTestUtils.invokeMethod(factory, "getKeyManagers");
Class<?> name = Class.forName("org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory$ConfigurableAliasKeyManager");
KeyManager[] keyManagers = ReflectionTestUtils.invokeMethod(factory,
"getKeyManagers");
Class<?> name = Class
.forName("org.springframework.boot.context.embedded.undertow."
+ "UndertowEmbeddedServletContainerFactory$ConfigurableAliasKeyManager");
assertThat(keyManagers[0]).isNotInstanceOf(name);
}

@ -485,10 +485,14 @@ public class LoggingApplicationListenerTests {
}
@Test
public void lowPriorityPropertySourceShouldNotOverrideRootLoggerConfig() throws Exception {
MutablePropertySources propertySources = this.context.getEnvironment().getPropertySources();
propertySources.addFirst(new MapPropertySource("test1", Collections.<String, Object>singletonMap("logging.level.ROOT", "DEBUG")));
propertySources.addLast(new MapPropertySource("test2", Collections.<String, Object>singletonMap("logging.level.root", "WARN")));
public void lowPriorityPropertySourceShouldNotOverrideRootLoggerConfig()
throws Exception {
MutablePropertySources propertySources = this.context.getEnvironment()
.getPropertySources();
propertySources.addFirst(new MapPropertySource("test1",
Collections.<String, Object>singletonMap("logging.level.ROOT", "DEBUG")));
propertySources.addLast(new MapPropertySource("test2",
Collections.<String, Object>singletonMap("logging.level.root", "WARN")));
this.initializer.initialize(this.context.getEnvironment(),
this.context.getClassLoader());
this.logger.debug("testatdebug");

Loading…
Cancel
Save