Skip to content

Commit

Permalink
Add TestManyThreads story test
Browse files Browse the repository at this point in the history
(Also adding experimental TestThreadExecutor for testing executions
in multiple threads.)
  • Loading branch information
mederly committed Dec 16, 2019
1 parent 5d164e6 commit 20cbf34
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 0 deletions.
@@ -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;
}
}
@@ -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 testing/story/src/test/resources/threads/resource-dummy.xml
@@ -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>

0 comments on commit 20cbf34

Please sign in to comment.