Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(Also adding experimental TestThreadExecutor for testing executions in multiple threads.)
- Loading branch information
Showing
3 changed files
with
333 additions
and
0 deletions.
There are no files selected for viewing
92 changes: 92 additions & 0 deletions
92
repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/ThreadTestExecutor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
* Copyright (c) 2019 Evolveum and contributors | ||
* | ||
* This work is dual-licensed under the Apache License 2.0 | ||
* and European Union Public License. See LICENSE file for details. | ||
*/ | ||
|
||
package com.evolveum.midpoint.test; | ||
|
||
import com.evolveum.midpoint.util.logging.Trace; | ||
import com.evolveum.midpoint.util.logging.TraceManager; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* EXPERIMENTAL | ||
*/ | ||
public class ThreadTestExecutor { | ||
|
||
private static final Trace LOGGER = TraceManager.getTrace(ThreadTestExecutor.class); | ||
|
||
private static final long WAIT_STEP = 100; | ||
|
||
private int threadsCount; | ||
private long timeout; | ||
|
||
private List<Thread> threads; | ||
private List<Thread> failedThreads; | ||
private boolean lastExecutionFinished; | ||
|
||
public ThreadTestExecutor(int threadsCount, long timeout) { | ||
this.threadsCount = threadsCount; | ||
this.timeout = timeout; | ||
} | ||
|
||
@FunctionalInterface | ||
public interface ThrowingRunnable { | ||
void run() throws Exception; | ||
} | ||
|
||
public void execute(ThrowingRunnable runnable) throws InterruptedException { | ||
threads = new ArrayList<>(); | ||
failedThreads = new ArrayList<>(); | ||
|
||
for (int i = 0; i < threadsCount; i++) { | ||
threads.add(new Thread(() -> { | ||
try { | ||
runnable.run(); | ||
} catch (Throwable t) { | ||
failedThreads.add(Thread.currentThread()); | ||
LOGGER.error("Thread got an exception: " + t.getMessage(), t); | ||
throw new AssertionError("Thread got an exception: " + t.getMessage(), t); | ||
} | ||
})); | ||
} | ||
for (int i = 0; i < threadsCount; i++) { | ||
threads.get(i).start(); | ||
} | ||
|
||
long start = System.currentTimeMillis(); | ||
lastExecutionFinished = false; | ||
while (System.currentTimeMillis() - start < timeout) { | ||
Optional<Thread> firstAlive = threads.stream().filter(Thread::isAlive).findFirst(); | ||
if (firstAlive.isPresent()) { | ||
firstAlive.get().join(WAIT_STEP); | ||
} else { | ||
lastExecutionFinished = true; | ||
break; | ||
} | ||
} | ||
|
||
if (!lastExecutionFinished) { | ||
throw new AssertionError("Some of the threads have not finished in time: " + | ||
threads.stream().filter(Thread::isAlive).collect(Collectors.toList())); | ||
} | ||
} | ||
|
||
public List<Thread> getThreads() { | ||
return threads; | ||
} | ||
|
||
public List<Thread> getFailedThreads() { | ||
return failedThreads; | ||
} | ||
|
||
public boolean isLastExecutionFinished() { | ||
return lastExecutionFinished; | ||
} | ||
} |
137 changes: 137 additions & 0 deletions
137
testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestManyThreads.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* Copyright (c) 2019 Evolveum and contributors | ||
* | ||
* This work is dual-licensed under the Apache License 2.0 | ||
* and European Union Public License. See LICENSE file for details. | ||
*/ | ||
package com.evolveum.midpoint.testing.story; | ||
|
||
import com.evolveum.icf.dummy.resource.DummyAccount; | ||
import com.evolveum.icf.dummy.resource.DummyResource; | ||
import com.evolveum.midpoint.prism.PrismObject; | ||
import com.evolveum.midpoint.prism.query.ObjectQuery; | ||
import com.evolveum.midpoint.repo.cache.RepositoryCache; | ||
import com.evolveum.midpoint.schema.SearchResultList; | ||
import com.evolveum.midpoint.schema.internals.CachingStatistics; | ||
import com.evolveum.midpoint.schema.internals.InternalMonitor; | ||
import com.evolveum.midpoint.schema.result.OperationResult; | ||
import com.evolveum.midpoint.task.api.Task; | ||
import com.evolveum.midpoint.test.DummyResourceContoller; | ||
import com.evolveum.midpoint.test.TestResource; | ||
import com.evolveum.midpoint.test.ThreadTestExecutor; | ||
import com.evolveum.midpoint.test.util.MidPointTestConstants; | ||
import com.evolveum.midpoint.util.exception.ObjectNotFoundException; | ||
import com.evolveum.midpoint.util.exception.SchemaException; | ||
import com.evolveum.midpoint.util.logging.Trace; | ||
import com.evolveum.midpoint.util.logging.TraceManager; | ||
import com.evolveum.midpoint.xml.ns._public.common.common_3.*; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.test.annotation.DirtiesContext; | ||
import org.springframework.test.annotation.DirtiesContext.ClassMode; | ||
import org.springframework.test.context.ContextConfiguration; | ||
import org.testng.annotations.Test; | ||
|
||
import java.io.File; | ||
|
||
import static org.testng.AssertJUnit.*; | ||
|
||
/** | ||
* Tests behavior of selected components when executing in large number of threads. | ||
*/ | ||
@ContextConfiguration(locations = {"classpath:ctx-story-test-main.xml"}) | ||
@DirtiesContext(classMode = ClassMode.AFTER_CLASS) | ||
public class TestManyThreads extends AbstractStoryTest { | ||
|
||
public static final File TEST_DIR = new File(MidPointTestConstants.TEST_RESOURCES_DIR, "threads"); | ||
|
||
private static final Trace LOGGER = TraceManager.getTrace(TestManyThreads.class); | ||
|
||
private static final TestResource RESOURCE_DUMMY = new TestResource(TEST_DIR, "resource-dummy.xml", "be4d88ff-bbb7-45f2-91dc-4b0fc9a00ced"); | ||
|
||
@Autowired RepositoryCache repositoryCache; | ||
|
||
private DummyResource dummyResource; | ||
private DummyResourceContoller dummyResourceCtl; | ||
private PrismObject<ResourceType> resourceDummy; | ||
|
||
@Override | ||
public void initSystem(Task initTask, OperationResult initResult) throws Exception { | ||
super.initSystem(initTask, initResult); | ||
|
||
resourceDummy = importAndGetObjectFromFile(ResourceType.class, RESOURCE_DUMMY.file, RESOURCE_DUMMY.oid, initTask, initResult); | ||
dummyResourceCtl = DummyResourceContoller.create(null, resourceDummy); | ||
dummyResource = dummyResourceCtl.getDummyResource(); | ||
|
||
setAutoTaskManagementEnabled(true); | ||
} | ||
|
||
@Test | ||
public void test000Sanity() throws Exception { | ||
Task task = getTask(); | ||
|
||
assertSuccess(modelService.testResource(RESOURCE_DUMMY.oid, task)); | ||
} | ||
|
||
@Test | ||
public void test100SearchResourceObjects() throws Exception { | ||
Task globalTask = getTask(); | ||
OperationResult globalResult = getResult(); | ||
|
||
dummyResource.addAccount(new DummyAccount("jack")); | ||
|
||
// trigger version increment (will invalidate cached resource object) | ||
// repositoryService.modifyObject(ResourceType.class, RESOURCE_DUMMY.oid, | ||
// deltaFor(ResourceType.class).item(ResourceType.F_DESCRIPTION).replace("aaa").asItemDeltas(), | ||
// globalResult); | ||
|
||
for (int i = 0; i < 10000; i++) { | ||
|
||
System.out.println("***** STARTING ITERATION #" + i + " *****"); | ||
|
||
CachingStatistics statsBefore = InternalMonitor.getResourceCacheStats().clone(); | ||
|
||
ThreadTestExecutor executor = new ThreadTestExecutor(700, 60000L); | ||
executor.execute(() -> { | ||
login(userAdministrator.clone()); | ||
Task localTask = createTask("execute"); | ||
OperationResult localResult = localTask.getResult(); | ||
|
||
ObjectQuery query = prismContext.queryFor(ShadowType.class) | ||
.item(ShadowType.F_RESOURCE_REF).ref(RESOURCE_DUMMY.oid) | ||
.and().item(ShadowType.F_OBJECT_CLASS).eq(dummyResourceCtl.getAccountObjectClass()) | ||
.build(); | ||
SearchResultList<PrismObject<ShadowType>> accounts = modelService | ||
.searchObjects(ShadowType.class, query, null, localTask, localResult); | ||
System.out.println(Thread.currentThread().getName() + ": " + accounts); | ||
assertEquals("Wrong # of accounts found", 1, accounts.size()); | ||
|
||
provisioningService.getObject(ShadowType.class, accounts.get(0).getOid(), null, localTask, localResult); | ||
|
||
assertResourceSanity(RESOURCE_DUMMY.oid, localTask, localResult); | ||
}); | ||
|
||
assertEquals("Wrong # of failed threads", 0, executor.getFailedThreads().size()); | ||
|
||
CachingStatistics statsAfter = InternalMonitor.getResourceCacheStats(); | ||
|
||
System.out.println("Statistics before: " + statsBefore); | ||
System.out.println("Statistics after: " + statsAfter); | ||
} | ||
} | ||
|
||
private void assertResourceSanity(String oid, Task task, OperationResult result) | ||
throws SchemaException, ObjectNotFoundException { | ||
ResourceType resource = repositoryCache.getObject(ResourceType.class, oid, null, result).asObjectable(); | ||
assertNotNull("No schemaHandling", resource.getSchemaHandling()); | ||
assertEquals("Wrong # of object type defs", 1, resource.getSchemaHandling().getObjectType().size()); | ||
ResourceObjectTypeDefinitionType typeDef = resource.getSchemaHandling().getObjectType().get(0); | ||
assertEquals("Wrong # of attribute defs", 1, typeDef.getAttribute().size()); | ||
ResourceAttributeDefinitionType nameDef = typeDef.getAttribute().get(0); | ||
assertNotNull("No outbound", nameDef.getOutbound()); | ||
ExpressionType expression = nameDef.getOutbound().getExpression(); | ||
assertNotNull("No outbound expression", expression); | ||
String code = ((ScriptExpressionEvaluatorType) expression.getExpressionEvaluator().get(0).getValue()).getCode(); | ||
assertNotNull("No <code>", code); | ||
assertTrue("Wrong <code>", code.contains("name")); | ||
} | ||
} |
104 changes: 104 additions & 0 deletions
104
testing/story/src/test/resources/threads/resource-dummy.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- | ||
~ Copyright (c) 2019 Evolveum and contributors | ||
~ | ||
~ This work is dual-licensed under the Apache License 2.0 | ||
~ and European Union Public License. See LICENSE file for details. | ||
--> | ||
<resource oid="be4d88ff-bbb7-45f2-91dc-4b0fc9a00ced" | ||
xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3" | ||
xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3" | ||
xmlns:icfs="http://midpoint.evolveum.com/xml/ns/public/connector/icf-1/resource-schema-3" | ||
xmlns:ri="http://midpoint.evolveum.com/xml/ns/public/resource/instance-3"> | ||
<name>Dummy Resource</name> | ||
<connectorRef type="ConnectorType"> | ||
<filter> | ||
<q:and> | ||
<q:equal> | ||
<q:path>connectorType</q:path> | ||
<q:value>com.evolveum.icf.dummy.connector.DummyConnector</q:value> | ||
</q:equal> | ||
<q:equal> | ||
<q:path>connectorVersion</q:path> | ||
<q:value>2.0</q:value> | ||
</q:equal> | ||
</q:and> | ||
</filter> | ||
</connectorRef> | ||
<connectorConfiguration xmlns:icfi="http://midpoint.evolveum.com/xml/ns/public/connector/icf-1/bundle/com.evolveum.icf.dummy/com.evolveum.icf.dummy.connector.DummyConnector" | ||
xmlns:icfc="http://midpoint.evolveum.com/xml/ns/public/connector/icf-1/connector-schema-3"> | ||
<icfc:configurationProperties> | ||
<icfi:instanceId/> <!-- Default instance. --> | ||
</icfc:configurationProperties> | ||
<icfc:resultsHandlerConfiguration> | ||
<icfc:enableNormalizingResultsHandler>false</icfc:enableNormalizingResultsHandler> | ||
<icfc:enableFilteredResultsHandler>false</icfc:enableFilteredResultsHandler> | ||
<icfc:enableAttributesToGetSearchResultsHandler>false</icfc:enableAttributesToGetSearchResultsHandler> | ||
</icfc:resultsHandlerConfiguration> | ||
</connectorConfiguration> | ||
<schemaHandling> | ||
<objectType> | ||
<kind>account</kind> | ||
<intent>default</intent> | ||
<displayName>Default Account</displayName> | ||
<default>true</default> | ||
<objectClass>ri:AccountObjectClass</objectClass> | ||
<attribute> | ||
<ref>icfs:name</ref> | ||
<outbound> | ||
<source> | ||
<path>name</path> | ||
</source> | ||
<expression> | ||
<script> | ||
<code> | ||
name | ||
</code> | ||
</script> | ||
</expression> | ||
</outbound> | ||
</attribute> | ||
</objectType> | ||
</schemaHandling> | ||
<synchronization> | ||
<objectSynchronization> | ||
<objectClass>ri:AccountObjectClass</objectClass> | ||
<kind>account</kind> | ||
<intent>default</intent> | ||
<enabled>true</enabled> | ||
<correlation> | ||
<q:equal> | ||
<q:path>name</q:path> | ||
<expression> | ||
<path>$account/attributes/icfs:name</path> | ||
</expression> | ||
</q:equal> | ||
</correlation> | ||
<reaction> | ||
<situation>linked</situation> | ||
<synchronize>true</synchronize> | ||
</reaction> | ||
<reaction> | ||
<situation>deleted</situation> | ||
<synchronize>true</synchronize> | ||
<action> | ||
<handlerUri>http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink</handlerUri> | ||
</action> | ||
</reaction> | ||
<reaction> | ||
<situation>unlinked</situation> | ||
<synchronize>true</synchronize> | ||
<action> | ||
<handlerUri>http://midpoint.evolveum.com/xml/ns/public/model/action-3#link</handlerUri> | ||
</action> | ||
</reaction> | ||
<reaction> | ||
<situation>unmatched</situation> | ||
<synchronize>true</synchronize> | ||
<action> | ||
<handlerUri>http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus</handlerUri> | ||
</action> | ||
</reaction> | ||
</objectSynchronization> | ||
</synchronization> | ||
</resource> |