Rework @TestPropertySource compatibility
Rework @TestPropertySource changes introduced in commit d251b513
to restore compatibility with Spring Boot 1.1
- Only add the `server.port` property when no @IntegrationTest
annotation is found.
- Always add a default `spring.jmx.enabled=false` property.
- Restore the SpringApplicationContextLoader.getEnvironmentProperties
protected method.
- Remove the @IntegrationTest.properties attribute.
See gh-1697
This commit is contained in:
parent
2e7aa4685b
commit
24f95b975d
|
@ -25,7 +25,6 @@ import java.lang.annotation.Target;
|
||||||
|
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.test.context.TestExecutionListeners;
|
import org.springframework.test.context.TestExecutionListeners;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
|
||||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||||
|
@ -42,21 +41,16 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
// Leave out the ServletTestExecutionListener because it only deals with Mock* servlet
|
// Leave out the ServletTestExecutionListener because it only deals with Mock* servlet
|
||||||
// stuff. A real embedded application will not need the mocks.
|
// stuff. A real embedded application will not need the mocks.
|
||||||
@TestExecutionListeners(listeners = { IntegrationTestPropertiesListener.class, DependencyInjectionTestExecutionListener.class,
|
@TestExecutionListeners(listeners = { IntegrationTestPropertiesListener.class,
|
||||||
|
DependencyInjectionTestExecutionListener.class,
|
||||||
DirtiesContextTestExecutionListener.class,
|
DirtiesContextTestExecutionListener.class,
|
||||||
TransactionalTestExecutionListener.class })
|
TransactionalTestExecutionListener.class })
|
||||||
@TestPropertySource
|
|
||||||
public @interface IntegrationTest {
|
public @interface IntegrationTest {
|
||||||
|
|
||||||
/**
|
|
||||||
* Synonym for properties().
|
|
||||||
*/
|
|
||||||
String[] value() default {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Properties in form {@literal key=value} that should be added to the Spring
|
* Properties in form {@literal key=value} that should be added to the Spring
|
||||||
* {@link Environment} before the test runs.
|
* {@link Environment} before the test runs.
|
||||||
*/
|
*/
|
||||||
String[] properties() default {"server.port=-1", "spring.jmx.enabled=false"};
|
String[] value() default {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,96 +19,59 @@ import java.util.Arrays;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotationAttributes;
|
||||||
import org.springframework.test.context.MergedContextConfiguration;
|
import org.springframework.test.context.MergedContextConfiguration;
|
||||||
import org.springframework.test.context.TestContext;
|
import org.springframework.test.context.TestContext;
|
||||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manipulate the TestContext to merge properties from <code>@IntegrationTest</code> value
|
* Manipulate the TestContext to merge properties from {@code @IntegrationTest}.
|
||||||
* and properties attributes.
|
|
||||||
*
|
|
||||||
* @author Dave Syer
|
|
||||||
*
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.2.0
|
||||||
*/
|
*/
|
||||||
public class IntegrationTestPropertiesListener extends AbstractTestExecutionListener {
|
class IntegrationTestPropertiesListener extends AbstractTestExecutionListener {
|
||||||
|
|
||||||
private String[] defaultValues = (String[]) AnnotationUtils.getDefaultValue(
|
private static final String ANNOTATION_TYPE = IntegrationTest.class.getName();
|
||||||
IntegrationTest.class, "properties");
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareTestInstance(TestContext testContext) throws Exception {
|
public void prepareTestInstance(TestContext testContext) throws Exception {
|
||||||
MergedContextConfiguration config = null;
|
Class<?> testClass = testContext.getTestClass();
|
||||||
|
if (AnnotatedElementUtils.isAnnotated(testClass, ANNOTATION_TYPE)) {
|
||||||
|
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
||||||
|
.getAnnotationAttributes(testClass, ANNOTATION_TYPE);
|
||||||
|
addPropertySourceProperties(testContext,
|
||||||
|
annotationAttributes.getStringArray("value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPropertySourceProperties(TestContext testContext, String[] properties) {
|
||||||
try {
|
try {
|
||||||
// Here be hacks...
|
addPropertySourcePropertiesUsingReflection(testContext, properties);
|
||||||
config = (MergedContextConfiguration) ReflectionTestUtils.getField(
|
|
||||||
testContext, "mergedContextConfiguration");
|
|
||||||
ReflectionTestUtils.setField(config, "propertySourceProperties",
|
|
||||||
getEnvironmentProperties(config));
|
|
||||||
}
|
}
|
||||||
catch (IllegalStateException e) {
|
catch (RuntimeException ex) {
|
||||||
throw e;
|
throw ex;
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String[] getEnvironmentProperties(MergedContextConfiguration config) {
|
private void addPropertySourcePropertiesUsingReflection(TestContext testContext,
|
||||||
IntegrationTest annotation = AnnotationUtils.findAnnotation(
|
String[] properties) throws Exception {
|
||||||
config.getTestClass(), IntegrationTest.class);
|
if (properties.length == 0) {
|
||||||
return mergeProperties(
|
return;
|
||||||
getDefaultEnvironmentProperties(config.getPropertySourceProperties(),
|
|
||||||
annotation), getEnvironmentProperties(annotation));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] getDefaultEnvironmentProperties(String[] original,
|
|
||||||
IntegrationTest annotation) {
|
|
||||||
String[] defaults = mergeProperties(original, defaultValues);
|
|
||||||
if (annotation == null || defaults.length == 0) {
|
|
||||||
// Without an @IntegrationTest we can assume the defaults are fine
|
|
||||||
return defaults;
|
|
||||||
}
|
}
|
||||||
// If @IntegrationTest is present we don't provide a default for the server.port
|
MergedContextConfiguration configuration = (MergedContextConfiguration) ReflectionTestUtils
|
||||||
return filterPorts((String[]) AnnotationUtils.getDefaultValue(annotation,
|
.getField(testContext, "mergedContextConfiguration");
|
||||||
"properties"));
|
Set<String> merged = new LinkedHashSet<String>((Arrays.asList(configuration
|
||||||
}
|
.getPropertySourceProperties())));
|
||||||
|
merged.addAll(Arrays.asList(properties));
|
||||||
private String[] filterPorts(String[] values) {
|
ReflectionTestUtils.setField(configuration, "propertySourceProperties",
|
||||||
|
merged.toArray(new String[merged.size()]));
|
||||||
Set<String> result = new LinkedHashSet<String>();
|
|
||||||
for (String value : values) {
|
|
||||||
if (!value.contains(".port")) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toArray(new String[0]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] getEnvironmentProperties(IntegrationTest annotation) {
|
|
||||||
if (annotation == null) {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
if (Arrays.asList(annotation.properties()).equals(Arrays.asList(defaultValues))) {
|
|
||||||
return annotation.value();
|
|
||||||
}
|
|
||||||
if (annotation.value().length == 0) {
|
|
||||||
return annotation.properties();
|
|
||||||
}
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Either properties or value can be provided but not both");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] mergeProperties(String[] original, String[] extra) {
|
|
||||||
Set<String> result = new LinkedHashSet<String>();
|
|
||||||
for (String value : original) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
for (String value : extra) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
return result.toArray(new String[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -90,7 +91,7 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
|
||||||
.addAfter(
|
.addAfter(
|
||||||
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
||||||
new MapPropertySource("integrationTest",
|
new MapPropertySource("integrationTest",
|
||||||
extractEnvironmentProperties(config.getPropertySourceProperties())));
|
getEnvironmentProperties(config)));
|
||||||
application.setEnvironment(environment);
|
application.setEnvironment(environment);
|
||||||
List<ApplicationContextInitializer<?>> initializers = getInitializers(config,
|
List<ApplicationContextInitializer<?>> initializers = getInitializers(config,
|
||||||
application);
|
application);
|
||||||
|
@ -101,36 +102,9 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
|
||||||
application.setWebEnvironment(false);
|
application.setWebEnvironment(false);
|
||||||
}
|
}
|
||||||
application.setInitializers(initializers);
|
application.setInitializers(initializers);
|
||||||
|
|
||||||
return application.run();
|
return application.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instead of parsing the keys ourselves, we rely on standard handling
|
|
||||||
protected Map<String, Object> extractEnvironmentProperties(String[] values) {
|
|
||||||
Map<String, Object> properties = new HashMap<String, Object>();
|
|
||||||
if (values==null) {
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (String value : values) {
|
|
||||||
sb.append(value).append(LINE_SEPARATOR);
|
|
||||||
}
|
|
||||||
String content = sb.toString();
|
|
||||||
Properties props = new Properties();
|
|
||||||
try {
|
|
||||||
props.load(new StringReader(content));
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
throw new IllegalStateException("Unexpected could not load properties from '"
|
|
||||||
+ content + "'", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String name : props.stringPropertyNames()) {
|
|
||||||
properties.put(name, props.getProperty(name));
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processContextConfiguration(
|
public void processContextConfiguration(
|
||||||
ContextConfigurationAttributes configAttributes) {
|
ContextConfigurationAttributes configAttributes) {
|
||||||
|
@ -176,8 +150,52 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
|
||||||
return AnnotationConfigContextLoaderUtils
|
return AnnotationConfigContextLoaderUtils
|
||||||
.detectDefaultConfigurationClasses(declaringClass);
|
.detectDefaultConfigurationClasses(declaringClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Map<String, Object> getEnvironmentProperties(
|
||||||
|
MergedContextConfiguration config) {
|
||||||
|
Map<String, Object> properties = new LinkedHashMap<String, Object>();
|
||||||
|
// JMX bean names will clash if the same bean is used in multiple contexts
|
||||||
|
disableJmx(properties);
|
||||||
|
properties.putAll(extractEnvironmentProperties(config
|
||||||
|
.getPropertySourceProperties()));
|
||||||
|
if (AnnotationUtils.findAnnotation(config.getTestClass(), IntegrationTest.class) == null) {
|
||||||
|
properties.putAll(getDefaultEnvironmentProperties());
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableJmx(Map<String, Object> properties) {
|
||||||
|
properties.put("spring.jmx.enabled", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> getDefaultEnvironmentProperties() {
|
||||||
|
return Collections.singletonMap("server.port", "-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> extractEnvironmentProperties(String[] values) {
|
||||||
|
// Instead of parsing the keys ourselves, we rely on standard handling
|
||||||
|
if (values == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
String content = StringUtils.arrayToDelimitedString(values, LINE_SEPARATOR);
|
||||||
|
Properties properties = new Properties();
|
||||||
|
try {
|
||||||
|
properties.load(new StringReader(content));
|
||||||
|
return asMap(properties);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new IllegalStateException("Unexpected could not load properties from '"
|
||||||
|
+ content + "'", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> asMap(Properties properties) {
|
||||||
|
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||||
|
for (String name : properties.stringPropertyNames()) {
|
||||||
|
map.put(name, properties.getProperty(name));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
private List<ApplicationContextInitializer<?>> getInitializers(
|
private List<ApplicationContextInitializer<?>> getInitializers(
|
||||||
MergedContextConfiguration mergedConfig, SpringApplication application) {
|
MergedContextConfiguration mergedConfig, SpringApplication application) {
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.boot.test;
|
package org.springframework.boot.test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
@ -27,6 +25,8 @@ import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for disabling JMX by default
|
* Tests for disabling JMX by default
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.boot.test;
|
package org.springframework.boot.test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -27,6 +24,9 @@ import org.springframework.test.context.TestContext;
|
||||||
import org.springframework.test.context.TestContextManager;
|
import org.springframework.test.context.TestContextManager;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SpringApplicationContextLoader}
|
* Tests for {@link SpringApplicationContextLoader}
|
||||||
*
|
*
|
||||||
|
@ -43,24 +43,12 @@ public class SpringApplicationContextLoaderTests {
|
||||||
assertKey(config, "anotherKey", "anotherValue");
|
assertKey(config, "anotherKey", "anotherValue");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void environmentPropertiesDefaults() throws Exception {
|
|
||||||
Map<String, Object> config = getEnvironmentProperties(SimpleConfig.class);
|
|
||||||
assertMissingKey(config, "server.port");
|
|
||||||
assertKey(config, "spring.jmx.enabled", "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void environmentPropertiesOverrideDefaults() throws Exception {
|
public void environmentPropertiesOverrideDefaults() throws Exception {
|
||||||
Map<String, Object> config = getEnvironmentProperties(OverrideConfig.class);
|
Map<String, Object> config = getEnvironmentProperties(OverrideConfig.class);
|
||||||
assertKey(config, "server.port", "2345");
|
assertKey(config, "server.port", "2345");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=IllegalStateException.class)
|
|
||||||
public void environmentPropertiesIllegal() throws Exception {
|
|
||||||
getEnvironmentProperties(IllegalConfig.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void environmentPropertiesAppend() throws Exception {
|
public void environmentPropertiesAppend() throws Exception {
|
||||||
Map<String, Object> config = getEnvironmentProperties(AppendConfig.class);
|
Map<String, Object> config = getEnvironmentProperties(AppendConfig.class);
|
||||||
|
@ -82,12 +70,15 @@ public class SpringApplicationContextLoaderTests {
|
||||||
assertKey(config, "anotherKey", "another=Value");
|
assertKey(config, "anotherKey", "another=Value");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> getEnvironmentProperties(Class<?> testClass) throws Exception {
|
private Map<String, Object> getEnvironmentProperties(Class<?> testClass)
|
||||||
TestContext context = new ExposedTestContextManager(testClass).getExposedTestContext();
|
throws Exception {
|
||||||
|
TestContext context = new ExposedTestContextManager(testClass)
|
||||||
|
.getExposedTestContext();
|
||||||
new IntegrationTestPropertiesListener().prepareTestInstance(context);
|
new IntegrationTestPropertiesListener().prepareTestInstance(context);
|
||||||
MergedContextConfiguration config = (MergedContextConfiguration) ReflectionTestUtils.getField(
|
MergedContextConfiguration config = (MergedContextConfiguration) ReflectionTestUtils
|
||||||
context, "mergedContextConfiguration");
|
.getField(context, "mergedContextConfiguration");
|
||||||
return this.loader.extractEnvironmentProperties(config.getPropertySourceProperties());
|
return this.loader.extractEnvironmentProperties(config
|
||||||
|
.getPropertySourceProperties());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertKey(Map<String, Object> actual, String key, Object value) {
|
private void assertKey(Map<String, Object> actual, String key, Object value) {
|
||||||
|
@ -95,10 +86,6 @@ public class SpringApplicationContextLoaderTests {
|
||||||
assertEquals(value, actual.get(key));
|
assertEquals(value, actual.get(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertMissingKey(Map<String, Object> actual, String key) {
|
|
||||||
assertTrue("Key '" + key + "' found", !actual.containsKey(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
@IntegrationTest({ "key=myValue", "anotherKey:anotherValue" })
|
@IntegrationTest({ "key=myValue", "anotherKey:anotherValue" })
|
||||||
static class SimpleConfig {
|
static class SimpleConfig {
|
||||||
}
|
}
|
||||||
|
@ -107,11 +94,7 @@ public class SpringApplicationContextLoaderTests {
|
||||||
static class OverrideConfig {
|
static class OverrideConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntegrationTest(value = { "key=aValue", "anotherKey:anotherValue" }, properties = { "key=myValue", "otherKey=otherValue" })
|
@IntegrationTest({ "key=myValue", "otherKey=otherValue" })
|
||||||
static class IllegalConfig {
|
|
||||||
}
|
|
||||||
|
|
||||||
@IntegrationTest(properties = { "key=myValue", "otherKey=otherValue" })
|
|
||||||
static class AppendConfig {
|
static class AppendConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,18 +105,20 @@ public class SpringApplicationContextLoaderTests {
|
||||||
@IntegrationTest({ "key=my:Value", "anotherKey:another=Value" })
|
@IntegrationTest({ "key=my:Value", "anotherKey:another=Value" })
|
||||||
static class AnotherSeparatorInValue {
|
static class AnotherSeparatorInValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link TestContextManager} which exposes the {@link TestContext}.
|
||||||
|
*/
|
||||||
private static class ExposedTestContextManager extends TestContextManager {
|
private static class ExposedTestContextManager extends TestContextManager {
|
||||||
|
|
||||||
public ExposedTestContextManager(Class<?> testClass) {
|
public ExposedTestContextManager(Class<?> testClass) {
|
||||||
super(testClass);
|
super(testClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final TestContext getExposedTestContext() {
|
public final TestContext getExposedTestContext() {
|
||||||
return super.getTestContext();
|
return super.getTestContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue